plotly / dash-ag-grid

Dash AG Grid is a high-performance and highly customizable component that wraps AG Grid, designed for creating rich datagrids.
https://dash.plotly.com/dash-ag-grid
MIT License
175 stars 25 forks source link

Problem when toggling pivot mode with date column that uses `valueGetter` and `valueFormatter` #258

Closed l47y closed 10 months ago

l47y commented 10 months ago

Hi,

first, thank you so much for making ag-grid available within the Dash framework. This elevated my apps to another level and I get very positive feedback from users.

Summary of problem

I have a dataframe with a date column date. I use a valueGetter and valueFormatter as stated in the docs, and filtering/sorting works perfectly. I recently enabled the pivot mode (we have an enterprise license) and the app is structured as follows: I have two navlinks that show/hide two different views. In my minimal example, I have Tab1, Tab2 while Tab1 contains the AgGrid. When doing the following:

  1. Navigate to Tab2 and then back to Tab1
  2. Activate Pivot Mode
  3. Deactivate Pivot Mode again

Then my app jumps back to Tab2 and throws some errors in the JavaScript console. When going back to the AgGrid, nothing seems wrong, the table is just shown normally. When you don't navigate to Tab2 first, you wouldn't notice the problem by just looking to the app, but the same errors are shown in the console.

Root cause

When I don't use the valueGetter and valueFormatter, the problem doesn't happen. However, I couldn't figure out what exactly is wrong with my setup. I guess there is an event triggered when toggling the pivot mode, that is responsible for this behavior. In the Browser console, the error also refers to the date column.

Asking for help

In my production app, there are many more tabs, and users switch back and forth a lot and do ad-hoc analyses using the pivot mode. My app feels a bit buggy, when every time the app jumps back to the last tab. I reduced my app to the necessary parts, please apologize that the setup might be a bit more complex. Below you can find the code and environment to reproduce the issue, and I attached a screenshot with the errors from the Browser console. Huge thanks for taking a look.

Environment

python 3.9.6 with:

dash==2.9.2
dash-ag-grid==2.4.0
dash-bootstrap-components==1.4.1
pandas==1.5.3
plotly==5.14.0
Flask==2.2.3

App

import dash_ag_grid as dag
from dash import html, Input, Output, dcc, dash, State
import dash_bootstrap_components as dbc
import pandas as pd

data = pd.DataFrame({"a": [1,2,3], "date": ["2012-01-01", "2012-01-02", "2012-01-03"]})
ag_grid_licence_key = <LICENCE_KEY> 

app = dash.Dash("App")

sideBar = {
    "toolPanels": [
        {
            "id": "columns",
            "labelDefault": "Columns",
            "labelKey": "columns",
            "iconKey": "columns",
            "toolPanel": "agColumnsToolPanel",
        },
    ],
    "position": "left",
    "defaultToolPanel": "columns",
}

# following the docs here to format the date column
date_obj = "d3.timeParse('%Y-%m-%d')(params.data.date)"

view1 = html.Div([
    dag.AgGrid(
        id="table",
        rowData= data.to_dict('records'),
        enableEnterpriseModules=True,
        licenseKey=ag_grid_licence_key,
        columnDefs=[
            {"headerName": "a", "field": "a", "filter": "agNumberColumnFilter", }, 
            {
                "headerName": "date", 
                "filter": "agDateColumnFilter",
                "field": "date", 
                 "valueGetter": {"function": date_obj}, 
                 "valueFormatter": {"function": f"""d3.timeFormat('%b %d, %Y')({date_obj})"""},
            }],
        dashGridOptions={"sideBar": sideBar,},
    ),
], className="dbc")

view2 = html.Div(["Another View"], className="dbc")

sidebar = html.Div(
    [
        html.H2("App"),
        dbc.Nav(
            [
                dbc.NavLink(["Tab1 ", ], href="/view1", active="exact"),
                dbc.NavLink(["Tab2 ", ], href="/view2", active="exact"),
            ],
            vertical=True,
            pills=True,
        ),
    ],
    className="sidebar", hidden=True, id="sidebar",
)

initial_content = html.Div([
    sidebar,
    html.Div(view1, id='container-1', hidden=True, className="content"),
    html.Div(view2, id='container-2', hidden=True, className="content"),
])

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content', children=initial_content),
], style={"padding": "0px"})

@app.callback(
    Output('container-1', 'hidden'),
    Output('container-2', 'hidden'),
    Output("sidebar", "hidden"),
    Output("url", "pathname") ,
    Input('url', 'pathname'), 
    prevent_initial_call=True
)
def display_page(pathname):
    if pathname == '/view1':
        return False, True, False, dash.no_update
    elif pathname == '/view2':
        return True, False, False, dash.no_update
    else:
        return True, True, False, dash.no_update

if __name__ == '__main__':
    app.run_server(debug=True)

Browser console error

I am using Chrome 119.0.6045.123 browser_console_error

BSd3v commented 10 months ago

Hello @l47y,

This is more than likely due to an issue with your value*, you should be testing to see if there is a value in the params first:

params.value ? valuegetterfunction : null

Something like the above should work. 😄

l47y commented 10 months ago

Hey @BSd3v - thanks a lot for your quick answer. Could you point me to the line in my code where I have to insert your suggested test? Sorry, my JavaScript understanding at this point is very basic :-)

BSd3v commented 10 months ago

Sure:

"valueGetter": {"function": f"params.value ? {date_obj} : null"}, 
"valueFormatter": {"function": f"""params.value ? d3.timeFormat('%b %d, %Y')({date_obj}) : null"""},
l47y commented 10 months ago

@BSd3v awesome - that solved it. I had to slightly change it to:

"valueGetter": {"function": f"params.data ? {date_obj} : null"}, 
"valueFormatter": {"function": f"""params.data ? d3.timeFormat('%b %d, %Y')({date_obj}) : null"""},

so I used params.data instead of params.value. With your initial version, the date column was just shown with only blank values and no error appeared in the console.

So all solved now. Thanks a lot for your quick response and solution :-)