dash-ag-grid icon indicating copy to clipboard operation
dash-ag-grid copied to clipboard

persistence does not work when editing cell

Open RunQi-Han opened this issue 2 years ago • 3 comments
trafficstars

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)

RunQi-Han avatar May 08 '23 20:05 RunQi-Han

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 avatar Jul 27 '23 18:07 BSd3v

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.

RunQi-Han avatar Jul 31 '23 13:07 RunQi-Han

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)

celia-lm avatar Apr 12 '24 16:04 celia-lm