Preserving Filtered Data in AG Grid and Efficient Data Sharing Between Pages in Dash

I have the following minimal example that shows a multi-page app, in which I have a map and an AG Grid.

app.py

import dash
from dash import dcc  # pip install dash
import dash_bootstrap_components as dbc  # pip install dash-bootstrap-components
import plotly.express as px

df = px.data.carshare()
# convert dataframe to list of dictionaries because dcc.Store
# accepts dict | list | number | string | boolean
df = df.to_dict('records')

app = dash.Dash(
    __name__, external_stylesheets=[dbc.themes.FLATLY], suppress_callback_exceptions=True, use_pages=True
)

navbar = dbc.NavbarSimple(
    dbc.DropdownMenu(
        [
            dbc.DropdownMenuItem(page["name"], href=page["path"])
            for page in dash.page_registry.values()
            if page["module"] != "pages.not_found_404"
        ],
        nav=True,
        label="More Pages",
    ),
    brand="Multi Page App Plugin Demo",
    color="primary",
    dark=True,
    className="mb-2",
)

app.layout = dbc.Container(
    [navbar,dash.page_container,
     dcc.Store(id="stored-data", data=df),
     dcc.Store(id="store-dropdown-value", data=None)
     ],
    fluid=True)

if __name__ == "__main__":
    app.run_server(debug=True, port=8050)

pages/table.py

import dash
import dash_ag_grid as dag

dash.register_page(__name__)

from dash import html, Input, Output, callback
import pandas as pd

columnDefs = [
    {"field": "centroid_lat","maxWidth": 300},
    {"field": "centroid_lon","maxWidth": 300},
    {"field": "car_hours","filter": "agNumberColumnFilter", "maxWidth": 300},
    {"field": "peak_hour", "filter": "agNumberColumnFilter", "maxWidth": 300},
]

layout = html.Div(
    [
        html.Div(id="table-container", children=[]),
    ]
)

@callback(Output("table-container", "children"),
          Input("stored-data", "data")
          )
# def populate_checklist(data, day):
def populate_checklist(data):
    dff = pd.DataFrame(data)
    my_table = dag.AgGrid(
            id='table',
            rowData=dff.to_dict("records"),
            columnSize="sizeToFit",
            columnDefs=columnDefs,                        
            defaultColDef={"resizable": True, "sortable": True, "filter": True},
            dashGridOptions={"pagination": True,
                            "enableCellTextSelection": True,
                            "ensureDomOrder": True,
                            "rowSelection": 'simple',},
            # getRowId="params.data.State",
            persistence_type ='session',
            persisted_props=["selectedRows"],
            persistence=True,
            style={"height": "80vh",}
            )
    return my_table

pages/map.py

import dash
import dash_leaflet as dl
import dash_leaflet.express as dlx
dash.register_page(__name__, path="/")

from dash import html, Input, Output, callback

layout = html.Div(
    [
        html.P("Choose Day:"),
        # html.Div(id="dropdown-container", children=[]),
        html.Div(id="map-container", children=[]),
    ]
)

@callback(
    Output("map-container", "children"),
    Input("stored-data", "data")
     
)
def graph_and_table(data):
    return [dl.Map([
            dl.TileLayer(),
            # From in-memory geojson. All markers at same point forces spiderfy at any zoom level.
            dl.GeoJSON(data=dlx.dicts_to_geojson(data, lon="centroid_lon", lat="centroid_lat"), cluster=True,zoomToBoundsOnClick=True,
                   superClusterOptions={"radius": 100}),

        ], center=(45.471549, -73.588684), zoom=11, style={'height': '50vh'})]

While working on this Dash app, I’m encountering two main issues:

Preserving Filtered Data in AG Grid:
I have implemented AG Grid with persistence=True, intending to keep the data filtered across different pages. However, the filtered data doesn’t seem to persist when navigating between pages. My goal is to plot only the filtered data on a map. How can I ensure that the filtered data in the AG Grid table is maintained when the page changes?

Efficient Data Sharing Between Pages:
Currently, I’m using dcc.Store to share data between pages. However, my dataset is quite large, making dcc.Store unsuitable for this purpose. I’m considering using Flask-Cache with the filesystem for better performance. Unfortunately, I’m struggling to integrate Flask-Cache into my app successfully. Are there any resources or advice available for implementing this? I’m aware of this resource (GitHub - AnnMarieW/dash-multi-page-app-demos: Minimal examples of multi-page apps using Dash Pages), but it hasn’t solved my specific problem.

Any insights or assistance would be greatly appreciated!

hi @lorenzo
Can you share your df so I can run your app locally as well?

Try to adjust the persisted_props to include the filterModel. The filterModel will allow filters to persist, but technically the data won’t persist unless yiou have some callbacks to go to a dcc.Store that populate from the virtualRowData.

Hi @adamschroeder ,

For this example I use df = px.data.carshare()

1 Like

I have good experience with sharing one table across pages - that way you avoid data duplication and sync logic :blush:

1 Like

Hi @Emil,

Do you have some examples to share?

There are many ways to do it. Using dash-extensions, you just place the dynamic component container similar to the page container,

from dash import dash, Dash, html
from dash_extensions.pages import setup_dynamic_components

app = Dash(use_pages=True)
app.layout = html.Div(
    children=[
        dash.page_container,
        setup_dynamic_components(),
    ]
)

if __name__ == "__main__":
    app.run_server()

and then register the dynamic components on the pages where they should be visible,

from dash import html, register_page
from ... import complex_table

register_page(
    __name__,
    path="/",
    dynamic_components=[complex_table]
)


def layout():
    return html.Div("Welcome home!")

The complex table will now appear below the greeting on the welcome page (as well as on any other page, where it is registered).

Does that make sense? :slight_smile:

1 Like

Hi @Emil,
Thanks for reply. I did not tested your solution yet, but it seems that it will reproduce the table in different pages. I was probably unclear with my question but what I would like to achieve is to have the table in one page only and persist (filtered) data in the table when change page and come back to it. There is an example by @adamschroeder here but I have two issue with that:

  1. It use dcc.Store (my dataset is to big for dcc.Store) and
  2. using AgGrid I’m unable to achieve this persistence through pages.

@lorenzo did you update your code to this? persisted_props=["filterModel"],

Whenever I filter the table, I can switch between pages and come back to the table with the same filter still set.

2 Likes

No, I think your question was quite clear. The solution I have proposed will not create a table on each page (even though the syntax might look like it); only a single component will be created, which is then shared across the pages :slight_smile:

2 Likes