Dash-ag-grid -- Chart API: Create range chart with data that is not in a column?

Might be a long shot, but I am trying to make use of the enterprise range/pivot/combo chart API within dash-ag-grid. Looking for help to add additional details to the chart that are not already column data.

Here is the data I am currently plotting:

  • The data is in Pivot mode.
  • The chart is generated using Range Chart (combination chart), link to docs above.
  • ‘Food’ and ‘Misc’ data plotted as stackedColumn. Pivot amount total plotted as line chart, which displays a single dot at the value.

Note that there is a custom valueFormatter on the Amount field. It displays a target budget alongside the current amount, and a progress bar for percentage.

As I mentioned in the beginning, I’m looking for ways to add additional details to the chart that are not already column data.

For example, I’d like to plot the budget amounts (-$500.00) as dotted lines on the chart. Or perhaps add another, slightly transparent bar in the background representing the sum of all the budget amounts (-$1000.00).

I couldn’t see any good way to do this without creating a bunch of arbitrary hidden columns in the grid. Those budget values are hardcoded in the Amount valueFormatter, and they only really make sense within that context.

I started playing around with dcc.Graph as well to see if I could create a more customizable chart… However I do like to utilize the popup window that comes with the built-in chart tools.

Any suggestions are welcome.

Thank you!

Hello @daflamingpotato,

Could you please provide a MRE:

This can help with troubleshooting.

The grid’s rowData object can always contain more info than what is displayed, this can be accessed via params.data[extraCol] where extraCol is your additional column in your data.

I haven’t used the charts from their enterprise, but I would think that this is possible.

1 Like

Hi @jinnyzor ,

Here is an example that you can run for testing purposes:
Some info in the comments of what I am trying to achieve:

How to display this ‘marker’ value as a horizontal line within the ag-grid range chart (y-axis)?
If there is no way: how to export the filtered data from ag-grid back to python/dash?

  • I set up a demo callback that saves gridApi.getRenderedNodes().
  • However this only works if the data fits within the grid screen.
  • Then there is still the matter of parsing the ag-grid data back into a pandas dataframe to generate a dcc.Graph.

Desired result: (marker = 30000000)

test.py:

import os
from dash import Dash, dcc, html, Input, Output, State, callback, clientside_callback
import dash_ag_grid as dag
import plotly.express as px

app = Dash(__name__)

df = px.data.gapminder()

# How to display this 'marker' value as a horizontal line within the ag-grid range chart (y-axis)?
# If there is no way: how to export the filtered data from ag-grid back to python/dash?
# - I set up a demo callback that saves gridApi.getRenderedNodes().
# - However this only works if the data fits within the grid screen.
# - Then there is still the matter of parsing the ag-grid data back into a pandas dataframe to generate a dcc.Graph.
marker = 30000000

defaultColDef = {
    # Row Group
    "enableRowGroup": True,  # Allows the column to be grouped in the UI
    # Sort and Filter
    "editable": False,
    "sortable": True,
    "filter": True,
    # Auto-size and set minimum resizable column width
    "resizable": True,
    "minWidth": 50,
    # Sorting infer data type?
    "cellDataType": True,
    # Pivot
    "enablePivot": True,
    "pivot": False,
}

columnDefs = [
    {"field": 'year', "rowGroup": True},
    {"field": 'continent', "rowGroup": True},
    {"field": 'country'},
    {"field": 'pop', "aggFunc": 'avg'},
]

dashGridOptions = {
    "animateRows": True,
    "enableCharts": True,
    "enableRangeSelection": True,
    "sideBar": True,
    "rowGroupPanelShow": 'always',
    "pivotMode": False,  # True, False
    "pivotPanelShow": 'always',  # (Initial--cannot be updated) 'never', 'always', 'onlyWhenPivoting'
    # "suppressExpandablePivotGroups ": False,
}

app.layout = html.Div([
        # buttons
        html.Button('Generate Chart', id="btn-make-chart"),

        # grid div
        html.Div([
            dcc.Store(id="grid-api", data='empty'),
            dag.AgGrid(
                id="grid-id",
                enableEnterpriseModules=True,
                # licenseKey = os.environ['AGGRID_ENTERPRISE'],
                columnDefs=columnDefs,
                style={"height": "100%", "width": "100%"},
                defaultColDef=defaultColDef,
                rowData=df.to_dict('records'),
                columnSize="sizeToFit",
                dashGridOptions=dashGridOptions,
            ),

        ],
            style={'height': '95%'},
        ),

        # debug log div
        html.Div(
            id='debug-log',
            # style={'display': 'none'},  # # none | block
        ),

    ],
    style={
        'height': '95vh',
        'margin': 0,
    },
)


clientside_callback(
    """
    function(gridId, n_clicks_timestamp, savedData) {
        console.log('')
        var debug_output = "btn-make-chart >> n_clicks_timestamp = " + n_clicks_timestamp
        // console.log(debug_output)
        
        const gridApi = dash_ag_grid.getApi(gridId);
        console.log('saved data=', savedData)
        console.log('new gridApi=', gridApi)
        console.log('selectedNodes=', gridApi.getSelectedNodes())
        console.log('renderedNodes=', gridApi.getRenderedNodes())
        var newSavedData = gridApi.getRenderedNodes()
        console.log('newSavedData=', 'renderedNodes')

        
        
        // console.log('make chart')
        gridApi.setGridOption('popupParent', document.body);  // sneaky way of setting up the DOM

        // number of nodes to plot
        var numberOfNodes = -1
        gridApi.forEachNode((node) => {
            numberOfNodes = numberOfNodes + 1
        });
        // console.log(numberOfNodes)

        // combo chart
        var createRangeChartParams = {
            chartType: 'groupedColumn',
            cellRange: {
                rowStartIndex: 0,
                rowEndIndex: numberOfNodes,
                columns: ['pop'],
            },
            aggFunc: 'avg',
            chartContainer: document.querySelector('#myChart'),
            // suppressChartRanges: true,
        };
        // make chart
        gridApi.createRangeChart(createRangeChartParams);
        
        
        return newSavedData
    }
    """,
    Output('grid-api', 'data'),
    Input('grid-id', 'id'),
    Input("btn-make-chart", "n_clicks_timestamp"),
    State("grid-api", "data"),
    prevent_initial_call=True
)


if __name__ == "__main__":
    app.run(debug=True)

For this you can use, virtualRowData. :slight_smile:

Thanks. I dug into this a bit more and found a few other options as well for anyone curious.

The virtualRowData did not exactly have the information for what I was trying to plot, since the data I needed was based on grouping and aggregation.

First version:

nodes = []
gridApi.forEachNodeAfterFilterAndSort((node) => {
            if (node.rowIndex !== null) {
                nodes.push(node)
            }
        });

Second version (same data as first version:)

var model = gridApi.getModel()
var rows = model.rowsToDisplay

It may not work in all cases but just wanted to share what worked for me. Thanks!
I’ll mark as solved for now.

1 Like

The thing you mentioned first is the basis for the virtualRowData, the concern is with how the underlying chartApi works in that the data has to derive from the grid itself.

Did you figure out a workaround to plot the constant? Or did you end up using a plotly chart?

1 Like

I see, that’s interesting. Unfortunately I did not find any way to plot the constant. AG Charts which it is based on is relatively new (June 2023, from GitHub?) and the plotting features are still somewhat limited.

However I did find several ways using a plotly chart:

  • There is ‘fig.add_hline()’, which works the best, but is fairly limited in that there is no option for hover data.
  • Then there are shapes, which work but they also do not support hover data directly.
  • Finally there are scatter traces, which DO have hover data, but there is no way to position them absolutely like with shapes. So it just looks awkward and does not extend across the entire graph.

Due to this I thought of a better way to display the data using overlaid px.bar charts. Rather than a line, each group or category has a 2nd bar in the background corresponding to the marker. It can be different for each category.

Here is an example:

So rather than a line at -500, each group has its own ‘budget bar’.

There is also a stacked plot version of this:

However for some reason I can’t get the bars to align at 0 on the stacked version. If I set the mode to ‘overlay’ it overlays all the traces and messes up the ones that are supposed to be stacked. Was thinking to make a new thread asking about that. Although I don’t particularly hate having them staggered, either.

1 Like