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

Attribute "aggFunc" of ColumnState not update when ColumnState change #279

Closed ducnva closed 6 months ago

ducnva commented 7 months ago

Hi every one, I encountered an issue; I believe it is a bug in dash-ag-grid. Please help me resolve this issue. Thank you. a) This is example to debug this error. To replicate error We will proceed as follows.

  1. Drag one column, then drop it to group.
  2. For the "Age" column, we will set the Value Aggregation to "avg" .
  3. Below, in the "Age" column, the "aggFunc" attribute has the value "avg".
  4. Next, we will set up the Value Aggregation as "count" again. However, we will see below that the "aggFunc" is not updated; it still shows "avg". That is a fault.
import json
import dash_ag_grid as dag
from dash import Dash, html, Input, Output, callback, State
import pandas as pd

app = Dash(__name__)

df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)

# columnDefs = [{"field": i} for i in ["country", "year", "athlete", "age", "sport", "total"]]
columnDefs = [
    {'field': 'country'}, 
    {'field': 'year'}, 
    {'field': 'athlete'}, 
    {'field': 'age'}, 
    {'field': 'sport'}, 
    {'field': 'total'}
]

app.layout = html.Div(
    [    html.Button('Click',id="btn_click"),
        dag.AgGrid(
            id="column-state-retrival",
            rowData=df.to_dict("records"),
            columnDefs=columnDefs,
            columnSize="sizeToFit",
            dashGridOptions={"rowSelection": "multiple", "enableCharts": True, "animateRows": False, "sideBar": True, "enableRangeSelection": True, "rowDragManaged": True, "rowGroupPanelShow": 'always',},
                    defaultColDef={"editable": True, "enableRowGroup": True,"enableValue": True},
            enableEnterpriseModules=True,
        ),
        html.Pre(id="pre-col-state",
                 style={'border': 'thin lightgrey solid', 'display': 'inline-block'}),
        html.Div(id="column"),

    ]
)

@callback(
    Output("pre-col-state", "children"),
    Input("column-state-retrival", "columnState"),
    prevent_initial_call=True,
)
def display_state(col_state):
    return json.dumps(col_state, indent=2)

app.clientside_callback(
        """
            function (state ,n_clicks) {
                const gridApi =  dash_ag_grid.getApi("column-state-retrival")
                let colState = gridApi.getColumnState();
                console.log('columnState from API AGRID', colState);

                console.log('columnState from INPUT', state)

            }
        """,
        Output("column", "children"),
        Input("column-state-retrival", "columnState"),
        Input("btn_click", "n_clicks"),
        prevent_initial_call=True
    )

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

https://github.com/plotly/dash-ag-grid/assets/103547955/913b5b44-decc-4335-95a6-59bab6d891fe

BSd3v commented 7 months ago

Hello @ducnva,

The columnState of the underlying grid is extremely hard to listen to all the events that could trigger it. My recommendation is if you need the columnState to be updated, then you use the getColumnState prop to maintain the sync.

ducnva commented 7 months ago

Hi @BSd3v Thank you for your response. In my case, I need a callback that would be automatically triggered when the user changed the columnState (change the Aggregate value). I need to obtain the column state and save it to the database. If I use getColumnState(), I have no idea how to automatically trigger the callback to get columnState. In this example above, I need to click a button to use getColumnState and get columnState

BSd3v commented 7 months ago

You can add an eventlistener to the grid, and as long as you are running dash 2.16.1+, you can send props directly to the dash eco-system.

dash_ag_grid.getApiAsync(id).then((grid) => {grid.addEventListener('columnValueChanged', (e) => {dash_clientside.set_props(id, {updateColumnState: true}))})

This should fit into your flow nicely.

To add this listener, just do something like this:

app.clientside_callback(
"""(id) => {
    dash_ag_grid.getApiAsync(id).then((grid) => {grid.addEventListener('columnValueChanged', (e) => {dash_clientside.set_props(id, {updateColumnState: true}))})
   return dash_clientside.no_update
}""", Output(grid, 'id'), INput(grid, 'id')
BSd3v commented 7 months ago

here you are:

import json
import dash_ag_grid as dag
from dash import Dash, html, Input, Output, callback, State
import pandas as pd

app = Dash(__name__)

df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)

# columnDefs = [{"field": i} for i in ["country", "year", "athlete", "age", "sport", "total"]]
columnDefs = [
    {'field': 'country'},
    {'field': 'year'},
    {'field': 'athlete'},
    {'field': 'age'},
    {'field': 'sport'},
    {'field': 'total'}
]

app.layout = html.Div(
    [html.Button('Click', id="btn_click"),
     dag.AgGrid(
         id="column-state-retrival",
         rowData=df.to_dict("records"),
         columnDefs=columnDefs,
         columnSize="sizeToFit",
         dashGridOptions={"rowSelection": "multiple", "enableCharts": True, "animateRows": False, "sideBar": True,
                          "enableRangeSelection": True, "rowDragManaged": True, "rowGroupPanelShow": 'always', },
         defaultColDef={"editable": True, "enableRowGroup": True, "enableValue": True},
         enableEnterpriseModules=True,
     ),
     html.Pre(id="pre-col-state",
              style={'border': 'thin lightgrey solid', 'display': 'inline-block'}),
     html.Div(id="column"),

     ]
)

@callback(
    Output("pre-col-state", "children"),
    Input("column-state-retrival", "columnState"),
    prevent_initial_call=True,
)
def display_state(col_state):
    return json.dumps(col_state, indent=2)

app.clientside_callback(
    """
        function (state ,n_clicks) {
            const gridApi =  dash_ag_grid.getApi("column-state-retrival")
            let colState = gridApi.getColumnState();
            console.log('columnState from API AGRID', colState);

            console.log('columnState from INPUT', state)

        }
    """,
    Output("column", "children"),
    Input("column-state-retrival", "columnState"),
    Input("btn_click", "n_clicks"),
    prevent_initial_call=True
)

app.clientside_callback(
"""(id) => {
    dash_ag_grid.getApiAsync(id).then((grid) => {
    grid.addEventListener('columnValueChanged', 
    (e) => {
        dash_clientside.set_props(id, {updateColumnState: true})
    })})
   return dash_clientside.no_update
}""", Output('column-state-retrival', 'id'),
    Input('column-state-retrival', 'id')
)

if __name__ == "__main__":
    app.run(debug=True)
ducnva commented 7 months ago

Ohh, That is perfect. It great for me. Thank you alot.