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
170 stars 24 forks source link

persistence does not work when editing cell #180

Open RunQi-Han opened 1 year ago

RunQi-Han commented 1 year ago

Based on the Docs:. We would like to make the data persist after editing the table. The persisted_props might be rowData. However, the persistence does not work as in the sample below:

"""
Accessing Row Data in an editable grid
"""
import dash
import dash_ag_grid as dag
from dash import Dash, html, dcc, Input, Output, State,ctx
import plotly.express as px
import pandas as pd
import json

app = Dash(__name__)

df = px.data.medals_wide()

app.layout = html.Div(
    [
        dcc.Markdown(id= 'dummy-input', children="Example of using `rowData` in a callback with an editable grid and added persistence to cell"),
        dag.AgGrid(
            id="editing-grid",
            columnDefs=[{"field": i} for i in df.columns],
            rowData=df.to_dict("records"),
            columnSize="sizeToFit",
            defaultColDef={"editable": True},
            persistence=True,
            persisted_props=['rowData'],     
        ),
    ],
    style={"margin": 20},
)

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

A workaround is to use dcc.Store() as below:

"""
Accessing Row Data in an editable grid
"""
import dash
import dash_ag_grid as dag
from dash import Dash, html, dcc, Input, Output, State,ctx
import plotly.express as px
import pandas as pd
import json

app = Dash(__name__)

df = px.data.medals_wide()

app.layout = html.Div(
    [
        dcc.Markdown(id= 'dummy-input', children="Example of using `rowData` in a callback with an editable grid and added persistence to cell"),
        dag.AgGrid(
            id="editing-grid",
            columnDefs=[{"field": i} for i in df.columns],
            rowData=df.to_dict("records"),
            columnSize="sizeToFit",
            defaultColDef={"editable": True},
        ),
        dcc.Store(id='memory',storage_type = 'session'),
        html.Div(id="editing-grid-output2"),
    ],
    style={"margin": 20},
)

@app.callback(
    Output("memory", "data"),
    Output("editing-grid", "rowData"),
    Input("editing-grid", "cellValueChanged"),
    State("editing-grid", "rowData"),
    State("memory", "data"),
)
def update(cell,rows,saved_rows):
    if saved_rows is None:
        saved_rows = df.to_dict("records")
    if cell is None:
        return saved_rows, saved_rows
    else:
        return rows,rows

if __name__ == "__main__":
    app.run_server(debug=True)
BSd3v commented 1 year ago

Hello @RunQi-Han,

This is actually an issue with how the grid manipulates the rowData, they manipulate the variable in place, so there is no way for dash to distinguish that the rowData has changed.

Unfortunately, this might be something that we cant do because it could cause a performance hit to separate them.

May revisit later.

For now, this work around for persistence will need to mainstream.

RunQi-Han commented 1 year ago

Hello @RunQi-Han,

This is actually an issue with how the grid manipulates the rowData, they manipulate the variable in place, so there is no way for dash to distinguish that the rowData has changed.

Unfortunately, this might be something that we cant do because it could cause a performance hit to separate them.

May revisit later.

For now, this work around for persistence will need to mainstream.

@BSd3v thanks for your explanation.

celia-lm commented 5 months ago

Hi! Here's an updated version of the workaround that takes advantage of Patch() (partial property updates) to improve performance, especially with large datasets:

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

app = Dash(__name__)

df = px.data.gapminder()
# df["id"] = df.index # optional

app.layout = html.Div(
    [
        dag.AgGrid(
            id="editing-grid",
            columnDefs=[{"field": i} for i in df.columns if i != 'id'],
            rowData=df.to_dict("records"),
            columnSize="sizeToFit",
            defaultColDef={"editable": True},
            #getRowId="params.data.id" # optional
        ),
        dcc.Store(id='memory',storage_type = 'session', data=[]),
    ],
    style={"margin": 20},
)

@callback(
    Output("memory", "data"),
    Output("editing-grid", "rowData"),
    Input("editing-grid", "cellValueChanged"),
    State("memory", "data")
    # it's important that prevent_initial_call=False (the default)
)
def update(cells, edits):

    row_id_var = 'rowIndex' # you could use rowId instead

    if cells :
        cells_info = [{k:c[k] for k in [row_id_var, 'colId', 'value']} for c in cells]
        # you may want to include some logic to check if previous edits have been made to the same cells and remove those
        # keeping old edits won't cause a problem anyway
        edits += cells_info
        return edits, dash.no_update

    else :
        saved_rows = Patch()
        for e in edits: 
            index = e[row_id_var] 
            saved_rows[index][e['colId']] = e['value'] 
        return dash.no_update, saved_rows

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