Editable Dash Table is not Working

Dear all,

I have a datatable reading the data from the dcc.Store. I want it to be editable such that I can modify the data manually. However, the editing feature is not working.

The code is shown below.

@app.callback(
    Output('raw-data-div', 'children'),
    [Input('stock-data', 'modified_timestamp'),
    Input('data-start', 'n_intervals')],
    State('stock-data', 'data'),
)
def gen_data_table(stock_data_timestamp, data_start_n_intervals, stock_data):

    if stock_data_timestamp != -1 or data_start_n_intervals is not None:

        div_children = []

        stock_data = jsonpickle.decode(stock_data)

        for symbol, stock in stock_data.items():

            df_current_and_others = stock.raw_data['current_and_others']
            df_quarterly = stock.raw_data['quarterly']
            df_annual = stock.raw_data['annual']

            div_children.extend(
                [
                    html.P(id='hihi'),
                    html.H3(f'{symbol}'),
                    html.H4('Current and others'),
                    dash_table.DataTable(
                        id=f'hihi_{symbol}',
                        data=df_current_and_others.to_dict('records'),
                        editable=True,
                        persistence=True,
                    ),
                    html.H4('Quarterly'),
                    dash_table.DataTable(
                        data=df_quarterly.to_dict('records'),
                        editable=True,
                        persistence=True,
                    ),
                    html.H4('Annual'),
                    dash_table.DataTable(
                        data=df_annual.to_dict('records'),
                        editable=True,
                        persistence=True,
                    ),
                    html.Hr(),
                ]
            )
        return div_children
    else:
        return dash.no_update

Please help me. Thank you.

1 Like

I have tried adding an arbitrary callback function for the dashtable but it still did not work.

Anyone, please?

@lester1027
I’d like to help, but I’m not sure I understand your question. if you created a complete minimal example with some sample data that reproduces the issue, it will be easier for someone to give some feedback.

@AnnMarieW Thank you. The minimal example is provided below.

Apart from dash, pandas and jsonpickle are used to deal with the data.

The structure of the repository is like this.

repository
├── vis_components
│ ├── data_tab.py
│ └── visualization_tab.py
├── dashboard_app.py
└── main_dash.py

dashboard_app.py

from dash import Dash, dcc, html
from dash.dependencies import Input, Output, State
from vis_components import visualization_tab, data_tab

from main_dash import app

app.layout = html.Div(
    children=[
        dcc.Interval(id='app-start', interval=1, max_intervals=1),
        dcc.Store(id='stock-symbol-all', storage_type='local'),
        dcc.Store(id='stock-symbol-selected', storage_type='local'),
        dcc.Store(id='stock-data', storage_type='local'),
        dcc.Tabs(id='tabs-bar', value='tab-visualization', persistence=True, children=[
            dcc.Tab(label='Visualization', value='tab-visualization'),
            dcc.Tab(label='Data', value='tab-data'),
        ]),
        html.Div(id='tabs-content')
    ]
)

# choose different tabs
@app.callback(
    Output('tabs-content', 'children'),
    Input('tabs-bar', 'value'),
)
def render_content(tab):
    if tab == 'tab-visualization':
        return visualization_tab.tab_visualization_layout
    elif tab == 'tab-data':
        return data_tab.tab_data_layout

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

main_dash.py

from dash import Dash

app = Dash(__name__)
app.config.suppress_callback_exceptions = True

visualization_tab.py

import dash
from dash import Dash, dcc, html
from dash.dependencies import Input, Output, State
import plotly.graph_objects as go
import pandas as pd
import jsonpickle
import jsonpickle.ext.pandas as jsonpickle_pandas
jsonpickle_pandas.register_handlers()

from main_dash import app

tab_visualization_layout = html.Div([
    html.Button('Refresh data', id='refresh-data', n_clicks=0),
])


# get the stock data according to the stock names chosen
@app.callback(
    Output('stock-data', 'data'),
    Input('refresh-data', 'n_clicks'),
)
def get_stock_data(refresh_data,):
    if refresh_data >= 1:

        stock_data = {}

        stock_data['foo'] = pd.DataFrame(
            [
                {
                    'aaa': 213,
                    'bbb': 123,
                },
                {
                    'aaa': 65,
                    'bbb': 153,
                },
            ]
        )

        stock_data['bar'] = pd.DataFrame(
            [
                {
                    'aaa': 77,
                    'bbb': 153,
                },
                {
                    'aaa': 99,
                    'bbb': 73,
                },
            ]
        )

        stock_data = jsonpickle.encode(stock_data)

        return stock_data

    else:
        return dash.no_update

data_tab.py

import dash
from dash import Dash, dcc, html, dash_table
from dash.dependencies import Input, Output, State
import pandas as pd
import jsonpickle

from main_dash import app

tab_data_layout = html.Div(
    children=[
        html.P(id='trial'),
        dcc.Interval(id='data-start', interval=1, max_intervals=1),
        html.H2('Raw Data'),
        html.Hr(),
        html.Div(
            id='raw-data-div',
        )
    ]
)

@app.callback(
    Output('raw-data-div', 'children'),
    [Input('stock-data', 'modified_timestamp'),
    Input('data-start', 'n_intervals')],
    State('stock-data', 'data'),
)
def gen_data_table(stock_data_timestamp, data_start_n_intervals, stock_data):

    if stock_data_timestamp != -1 or data_start_n_intervals is not None:

        div_children = []

        stock_data = jsonpickle.decode(stock_data)

        for symbol, stock in stock_data.items():
            df = stock

            div_children.extend(
                [
                    html.H3(f'{symbol}'),
                    dash_table.DataTable(
                        id=f'hihi',
                        data=df.to_dict('records'),
                        editable=True,
                        persistence=True,
                    ),
                    html.Hr(),
                ]
            )
        return div_children
    else:
        return dash.no_update



@lester1027 Try defining the columns in the DataTable as well:

data=df.to_dict('records'),
columns=[{"name": i, "id": i} for i in df.columns],
1 Like

@AnnMarieW Thank you very much. It works.

1 Like

Hi @AnnMarieW . Thanks for your insight here. I’m unable to apply your solution to my app. The user makes some choices in dropdowns before clicking on a button that pulls the data that needs to be shown in an editable datatable. I define the datatable with editable=True as part of the app layout but I don’t define the columns at that stage because I don’t know how many I need. The app is able to populate the datatable with the correct data but it is not editable. How can I solve this issue?

Hi @chon - it should still work. Can you provide a minimal example of what you are trying to do?

Hi @AnnMarieW , thank you for your response. Here is a minimal example.

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

app = dash.Dash(__name__)
city_dropdown = html.Div(
    [
        html.Label("Select city", htmlFor="city"),
        dcc.Dropdown(
            id="city",
            options=[
                {"label": "NYC", "value": "NYC"},
                {"label": "LDN", "value": "LDN"},
            ],
            placeholder="Select city",
        ),
    ]
)


initialise = html.Button(id="initialise", n_clicks=0, children="Initialise app")

table = html.Div(
    children=[
        dash_table.DataTable(
            id="table",
            # columns=[{"name": i, "id": i} for i in ["Column1", "Column2", "Column3"]],
            editable=True,
        )
    ]
)

app.layout = [city_dropdown, initialise, table]


@callback(
    Output("table", "data"),
    Input("initialise", "n_clicks"),
    State("city", "value"),
    prevent_initial_call=True,
)
def initialise(n_clicks, city):
    if city == "NYC":
        df = pd.DataFrame(
            {"Column1": [1, 2, 3], "Column2": [4, 5, 6], "Column3": [7, 8, 9]}
        )
    elif city == "LDN":
        df = pd.DataFrame(
            {"Column1": [9, 7, 8], "Column2": [6, 5, 4], "Column3": [3, 2, 1]}
        )
    return df.to_dict("records")


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

In reality I define df based on some dropdowns, then clicking on initialise pulls the data from an API and populates the datatable. As you can see in this minimal example, if I don’t define the columns, the datatable isn’t editable. I cannot really define them because I don’t know how many of them I need before the user makes their choice in the dropdowns and clicks on initialise but if I “cheat” for the sake of this demo and define the columns, then the resulting datatable is editable (test it by uncommenting the one line of code that’s commented out in my minimal example).

Hi @chon

Try defining the columns in the callback as well:


@callback(
    Output("table", "data"),
    Output("table", "columns"),
    Input("initialise", "n_clicks"),
    State("city", "value"),
    prevent_initial_call=True,
)
def initialise(n_clicks, city):
    if city == "NYC":
        df = pd.DataFrame(
            {"Column1": [1, 2, 3], "Column2": [4, 5, 6], "Column3": [7, 8, 9]}
        )
    elif city == "LDN":
        df = pd.DataFrame(
            {"Column1": [9, 7, 8], "Column2": [6, 5, 4], "Column3": [3, 2, 1]}
        )
    return df.to_dict("records"), [{"name": i, "id": i} for i in df.columns]


1 Like

Hi @AnnMarieW , thank you for your response. It works.

1 Like