Restore original values of DataTable with buttons

I am new to Dash and I am trying to set up a basic app where some data is shown, which the user can modify (and plot too although I’ve not included the chart in this minimal piece of code for simplicity). The user should be able to restore the original values i.e. revert back after making some changes. I’ve included a button for that. It seems to work. Have I done it properly?

The user should also have the option to restore the original values of a specific country (row) instead of the whole table e.g. if they are happy with the changes they made to various countries except for one country and they want to restore the original values for that one country. I’ve created selection buttons for that but I don’t know what the callback should be.

Here is my “work in progress” code:

import dash
import pandas as pd
from dash.dependencies import Input, Output, State

df = {
    "country": ["angola", "china", "greece", "italy", "myanmar"],
    "2024-01-01": [4, 24, 31, 2, 3],
    "2024-02-01": [25, 94, 57, 62, 70],
    "2024-03-01": [25, 94, 57, 62, 70],
    "2024-04-01": [25, 94, 57, 62, 70],
}
df = pd.DataFrame(df)


app = dash.Dash(__name__)
app.layout = dash.html.Div(
    children=[
        dash.dash_table.DataTable(
            id="table",
            data=df.to_dict("records"),
            columns=[{"name": i, "id": i} for i in df.columns],
            editable=True,
            row_selectable="multi",
        ),
        dash.html.Button("Restore", id="button", n_clicks=0),
    ]
)


@dash.callback(
    Output("table", "data"),
    Input("button", "n_clicks"),
    State("table", "selected_rows"),
)
def update_table(n_clicks, selected_rows):
    return df.to_dict("records")


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

Please is anyone able to give me a hand?

hi @chon
Welcome to the community :wave:

What you could possibly do is use the selected_cells or selected_rows property as an Input argument in the callback; then, compare the row data with the original df row data to see if there were any changes made. If there were changes, update only that row in the data.

Then, you might be able to use Partial Property updates to return patched data instead of the whole dataframe.

Hi @adamschroeder
Thank you for your response. For now I’d just like to restore the rows that aren’t (or are) selected and I’m stuck. I’d be very grateful for a few lines of code or a link to an example that demonstrates how to do it, please.

hi @chon
here’s an example to determine how a particular row has changed. And here’s another post on the topic of row data update.

Hi @adamschroeder
Thanks for those links. I appreciate your response but to be honest, I don’t really see how I can use them to achieve my purpose. This is the latest code I’ve been able to develop.

@dash.callback(
    Output("table", "data"),
    Input("button", "n_clicks"),
    Input("table", "selected_rows"),
)
def update_table(n_clicks, selected_rows):
    if selected_rows is None:
        return df.to_dict("records")
    else:
        return df.update(pd.DataFrame(selected_rows)).to_dict("records")

It seems to work when no row is selected, as all rows get restored to the initial values when the user clicks on the “Restore” button.

However, when the user selects a row to adjust the numbers, the callback is triggered even without clicking of the “Restore” button and there is the following error message:

AttributeError: 'NoneType' object has no attribute 'to_dict'

I have two questions:

  • Why is callback triggered when a row is selected? I’m expecting the callback to be triggered only when the “Restore” button is clicked.
  • Why am I getting that error message? Is selected_rows not an object that can be converted to a dataframe with pd.DataFrame(selected_rows)?

I’d be grateful for specific feedback about my code, and ideally a solution that works. Thanks!

hi @chon
Is this helpful?

from dash import Dash, html, callback, Input, Output, State, dash_table
import pandas as pd

df = {
    "country": ["angola", "china", "greece", "italy", "myanmar"],
    "2024-01-01": [4, 24, 31, 2, 3],
    "2024-02-01": [25, 94, 57, 62, 70],
    "2024-03-01": [25, 94, 57, 62, 70],
    "2024-04-01": [25, 94, 57, 62, 70],
}
df = pd.DataFrame(df)

app = Dash(__name__)
app.layout = html.Div(
    children=[
        dash_table.DataTable(
            id="table",
            data=df.to_dict("records"),
            columns=[{"name": i, "id": i} for i in df.columns],
            editable=True,
            row_selectable="multi",
        ),
        html.Button("Restore", id="button", n_clicks=0),
    ]
)

@callback(
    Output("table", "data"),
    Input("button", "n_clicks"),
    State("table", "selected_rows"),
    State("table", "data"),
)
def update_table(n_clicks, selected_rows, current_data):
    if n_clicks > 0 and selected_rows:
        updated_data = current_data.copy()
        # Restore selected rows to original data
        for i in selected_rows:
            print(i)
            print(updated_data[i])
            updated_data[i] = df.iloc[i].to_dict()
        return updated_data
    else:
        return current_data


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

Thank you!