How to use External Filters with Dash AG Grid

Thanks to the dedicated work of Dash Community member @Skiks, the Dash AG Grid documentation is continuously improving. Keep an eye out for regular updates!

He recently added the documentation for External Filters, which is a powerful way to filter grid data using external Dash components such as figures, dropdowns, and sliders.

This new addition served as inspiration for my latest tutorial on the Dash AG Grid Examples site.

Learn how to leverage the grid’s External Filter API and explore alternatives like updating the rowData or the Filter Model in a callback.

Please note, the gif does not fully capture the lightning-fast grid updates. Be sure to check out the site to see this and other examples live!

Here is the code for the first example:

from dash import Dash, dcc, html, Input, Output, callback, no_update
import plotly.express as px
import pandas as pd
import dash_ag_grid as dag

app =  Dash(__name__)

df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/us-cities-top-1k-multi-year.csv")

fig = px.scatter_mapbox(
    df[df.year==2018],
    lat="lat", lon="lon",
    hover_data=["City", "State"],
    size="Population",  color="Population",
    zoom=4,
)
fig.update_layout(mapbox_style="open-street-map")
fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})

app.layout = html.Div(
    [
        html.H3("Population 2018"),
        html.H4("Filter the grid by hovering on the map"),
        dcc.Graph(id="graph", figure=fig),
        dag.AgGrid(
            id="grid",
            rowData=df.to_dict("records"),
            columnDefs=[{"field": i} for i in ["City", "State", "Population", "year"]],
            defaultColDef={"flex": 1, "filter": True},
        ),
    ],
)


@callback(
    Output("grid", "dashGridOptions"),
    Input("graph", "hoverData"),
)
def generate_chart(hoverdata):
    if not hoverdata:
        return no_update

    city = hoverdata["points"][0]["customdata"][0]
    state = hoverdata["points"][0]["customdata"][1]

    return {
        "isExternalFilterPresent": {"function": "true"},
        "doesExternalFilterPass": {"function": f"params.data.City === '{city}' && params.data.State === '{state}'"}
    }


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

5 Likes

Thanks @AnnMarieW for those amazing examples!
I love the idea to filter the grid while hovering the graph :star_struck:

4 Likes

Thank you @Skiks for your constant and thorough work in the Docs. The radio button / ag-grid example was really fun to play with and amazingly fast.

@AnnMarieW , what a great new set of examples. I can’t even see a lag in the data updating every time I move my mouse :astonished: It’s so fast. I think I’m going to promote this on social media as well if that’s ok by you. Let me know. I want more people to see these examples.

3 Likes

Thanks for the plot example.

Where can we find the dashAgGridComponentFunctions.js used for the filter function ?

Does anyone know if there is an example with treemap ?

Thanks for all of your hard work.
AgGrid and Dash, Plotly, Plotly Express are impressive, most impressive.

Hi @Willy and welcome to the Dash community :slightly_smiling_face:

If you are referring to the filter example in the original post, there is no function in the dashAgGridComponentFunctions.js file. Try copying that code and running it (and be sure to have the most recent version of the grid installed 31.2.0

For the treemap, can you say more about what example you are looking for?

Thanks for the super fast response AnnMarieW and thanks for your great examples so far.

pip list |grep grid shows:
dash_ag_grid 31.2.0

For the treemap example I am looking to filter agGrid based on the element in the treemap that the user selects. ( exactly the same as in your example where the agGrid shows the element that the user selected in the plot chart)

Your example works on my local host. (although I do understand where params.data.City and State is coming from in your example).
Therefore I tried to modify the code to work with px.treemap and my excel file as follows and it displays but does not filter, so something is not correct:

from dash import Dash, dcc, html, Input, Output, callback, no_update
import plotly.express as px
import pandas as pd
import dash_ag_grid as dag

app =  Dash(__name__)

df = pd.read_excel('Bepo-agGrid.xlsx')

fig = px.treemap(
    df, 
    path = ["Group","SubGroup","Device Type","Finding","Code", "Risk Level","Finding1","Criteria","Assessment"], hover_data = ["Finding"],
                  color = "SubGroup", maxdepth=2
    )
fig.update_layout(width = 800, height = 800, font=dict(size=18))
fig.update_layout(uniformtext_minsize=18, uniformtext_mode ='show')
app.layout = html.Div(
    [
        #html.H3("Population 2018"),
        html.H4("Filter the grid by hovering on the map"),
        dcc.Graph(id="graph", figure=fig),
        dag.AgGrid(
            id="grid",
            rowData=df.to_dict("records"),
            columnDefs=[{"field": i} for i in ["Group","SubGroup","Device Type","Finding","Code", "Risk Level","Finding1","Criteria","Assessment"]],
            defaultColDef={"flex": 1, "filter": True},
        ),
    ],
)

@callback(
    Output("grid", "dashGridOptions"),
    Input("graph", "hoverData"),
)
def generate_chart(hoverdata):
    if not hoverdata:
        return no_update

    Group = hoverdata["points"][0]["customdata"][0]
    SubGroup = hoverdata["points"][0]["customdata"][1]

    return {
        "isExternalFilterPresent": {"function": "true"},
        "doesExternalFilterPass": {"function": f"params.data.Group === '{Group}' && params.data.SubGroup === '{SubGroup}'"}
    }


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

Hi @Willy
I did a quick edit on your post to make the code more readable by enclosing it in three backticks (I hope you don’t mind)

It would be easier to help if you could make an example that uses generic data or including a couple rows of test data. But a quick look at the code - you may need to use something like clickData rather than hoverData

Thank you for the very fast feedback! I will try and modify your example to treemap on my local machine using your data. FYI I do not see a clickData method in treemap, only hover_data or custom_data

Ok - hover data might work too depending on how you want it to work :slight_smile:

The treemap and graph are displaying with new data and different data definitions upon launch…

However, as soon as the mouse is over the treemap the graph goes blank (assumably because the external filter is not finding results in the new data with the new definitions).

original code

the modified code with the modified data and data definitions [quote=“Willy, post:6, topic:83118”]

    group = hoverdata["points"][0]["customdata"][0]
    subgroup = hoverdata["points"][0]["customdata"][1]
    device = hoverdata["points"][0]["customdata"][2]
    finding = hoverdata["points"][0]["customdata"][3]

    return {
        "isExternalFilterPresent": {"function": "true"},
        "doesExternalFilterPass": {"function": f"params.data.Group === '{group}' && params.data.SubGroup === '{subgroup}' && params.data.device === '{device}' && params.data.finding === '{finding}'"}
    }

Searching has not produced any results on how or where to set the filter variables for dash/dcc/async-graph.js
Any suggestions on how to change from params.data.City to params.data.Group in dash/dcc/async-graph.js to find the new data which is in the new px.treemap structure ?

@Willy I’m not sure what you mean. Could you please post a complete minimal example that reproduces the error that I can run locally? More info on how to do that here:
https://community.plotly.com/t/how-to-get-your-questions-answered-on-the-plotly-forum/40551/12

Here is your original example with population added to the filter:

from dash import Dash, dcc, html, Input, Output, callback, no_update
import plotly.express as px
import pandas as pd
import dash_ag_grid as dag

app =  Dash(__name__)

df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/us-cities-top-1k-multi-year.csv")

fig = px.scatter_mapbox(
    df[df.year==2018],
    lat="lat", lon="lon",
    hover_data=["City", "State", "Population"],
    size="Population",  color="Population",
    zoom=4,
)
fig.update_layout(mapbox_style="open-street-map")
fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})

app.layout = html.Div(
    [
        html.H3("Population 2018"),
        html.H4("Filter the grid by hovering on the map"),
        dcc.Graph(id="graph-external-filter", figure=fig),
        dag.AgGrid(
            id="grid-graph-external-filter",
            rowData=df.to_dict("records"),
            columnDefs=[{"field": i} for i in ["City", "State", "Population", "year"]],
            defaultColDef={"flex": 1, "filter": True},
        ),
    ], style={"padding": 24}
)


@callback(
    Output("grid-graph-external-filter", "dashGridOptions"),
    Input("graph-external-filter", "hoverData"),
)
def generate_chart(hoverdata):
    if not hoverdata:
        return no_update

    city = hoverdata["points"][0]["customdata"][0]
    state = hoverdata["points"][0]["customdata"][1]
    population = hoverdata["points"][0]["customdata"][2]

    return {
        "isExternalFilterPresent": {"function": "true"},
        "doesExternalFilterPass": {"function": f"params.data.City === '{city}' && params.data.State === '{state}' && params.data.State === '{population}'"}
    }


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

Hi @Willy - thanks for the example - that was helpful!

Try changing the filter to this:

return {
        "isExternalFilterPresent": {"function": "true"},
        "doesExternalFilterPass": {"function": f"params.data.City === '{city}' && params.data.State === '{state}' && params.data.Population === {population}"}
    }

Just need to use the correct field name and data type. Your version was using a string for the population.

1 Like