✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
⚾️ It's finally Baseball season! Root for the home team... & Register for our Sports Analytics Webinar!

Update a dash datatable with callbacks

Hi everyone,

I have a data-table which I have to include in my layout as I need to use it as Input for one of my callbacks

I also want to update this table with another callback.
This works fine if I update the data

But since with the new release we can format the columns I would like to update the columns too as I round some numbers depending on the data itself

If I create a callback with multiple outputs (data and columns) this fails
it seems that the only way is to separate the callbacks in this case one for data and one for columns

Is this an intended behavior? If so why?
Or is it an identified bug which will be fixed in a next release?

It should be possible to output both the data and the columns in a single callback.

Here’s a working, if crude example:

# -*- coding: utf-8 -*-
import dash
import dash_html_components as html

from dash.dependencies import Input, Output, State
from dash_table import DataTable

import pandas as pd

url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv"
df = pd.read_csv(url)

app = dash.Dash(__name__)
app.scripts.config.serve_locally = True

columns = [
    {"id": 0, "name": "Complaint ID"},
    {"id": 1, "name": "Product"},
    {"id": 2, "name": "Sub-product"},
    {"id": 3, "name": "Issue"},
    {"id": 4, "name": "Sub-issue"},
    {"id": 5, "name": "State"},
    {"id": 6, "name": "ZIP"},
    {"id": 7, "name": "code"},
    {"id": 8, "name": "Date received"},
    {"id": 9, "name": "Date sent to company"},
    {"id": 10, "name": "Company"},
    {"id": 11, "name": "Company response"},
    {"id": 12, "name": "Timely response?"},
    {"id": 13, "name": "Consumer disputed?"},
]

app.layout = html.Div([
    html.Button(
        ['Update'],
        id='btn'
    ),
    DataTable(
        id='table',
        data=[]
    )
])

@app.callback(
    [Output("table", "data"), Output('table', 'columns')],
    [Input("btn", "n_clicks")]
)
def updateTable(n_clicks):
    if n_clicks is None:
        return df.values[0:100], columns

    return df.values[100:110], columns[0:3]

if __name__ == "__main__":
    app.run_server(port=8053)

If still experiencing issues, can you provide a simple example of the problematic behavior you’re experiencing?

Thanks!

3 Likes

Thank you Marc-Andre

I worked on creating a code snippet to illustrate the problem I see without any dependencies:

version 1 works I am updating only the data

version 2 I am updating the data and the columns and the table is empty as no columns are showing:

for version 2 please do comment back in the 2nd Output and return of the callback function

Thanks

This is the code :

import dash
import dash_core_components as dcc 
import dash_html_components as html 
import dash_table 
from dash_table.Format import Format, Scheme, Sign, Symbol
import dash_table.FormatTemplate as FormatTemplate
from dash.dependencies import Input, Output, State
import pandas as pd
import numpy as np

app = dash.Dash(__name__)
       
app.title = 'Futures'

app.layout = html.Div(style={'marginLeft':25,'marginRight':25},
                    children=[ 
                        html.Div(className='row',children=[
                                        html.Div(className='four columns',
                                        children =[
                                        ]),
                                html.Div(className='four columns',
                                    children =[
                                                html.H6('Order Book'),
                                                html.Hr(style={'border-color':'#cb1828'}),
                                                html.Div(children=[dash_table.DataTable(id='fut-order-table',
                                                columns=[{'id':'from_mid','name':'From Mid','type':'numeric','format':FormatTemplate.percentage(4)},
                                                {'id':'price','name':'Price'},{'id':'size','name':'Size'},{'id':'cum_size','name': 'Size Total'},
                                                {'id':'size_$','name':'Size $','type':'numeric','format':FormatTemplate.money(0)},
                                                {'id':'cum_size_$','name':'Size Total $','type':'numeric','format':FormatTemplate.money(0)},
                                                {'id':'average_fill','name':'Averge Fill'},{'id':'exc','name':'Exchange'},
                                                {'id':'side','name':'side','hidden':True}],
                                                    style_table={'border': 'thin lightgrey solid'},
                                                    style_header={'backgroundColor':'lightgrey','fontWeight':'bold'},
                                                    style_cell={'textAlign':'center','width':'12%'},
                                                    style_data_conditional=[{
                                                        'if' : {'filter':  'side eq "bid"' },
                                                        'color':'blue'
                                                                }
                                                        ]+[
                                                        {
                                                        'if' : {'filter': 'side eq "ask"' },
                                                        'color':'rgb(203,24,40)'
                                                    }]+[
                                                        { 'if': {'row_index':'odd'},
                                                        'backgroundColor':'rgb(242,242,242)'}
                                                    ]+[
                                                        {'if':{'column_id':'price'},
                                                        'fontWeight':'bold',
                                                        'border': 'thin lightgrey solid'}
                                                    ]+[{'if':{'column_id':'from_mid'},
                                                        'fontWeight':'bold'}
                                                    ],
                                                    style_as_list_view=True
                                                )
                                                ]),
                                                html.Hr(style={'border-color':'#cb1828'}),
                                                html.H6('Liquidity Metrics'),
                                                html.P(id='fut-liquidity-table'),
                                                html.H6('Slippage %'),
                                                html.P(id='fut-depth-table'),
                                                html.H6('Coin Stats'),
                                                html.P(id='fut-stat-table')
                                        ]),

                                html.Div(className='four columns',
                                    children =[html.H6('Order Management'),
                                    html.Hr(style={'border-color':'#cb1828'}),
                                    html.H6('Orders'),
                                    html.Hr(style={'border-color':'#cb1828'}),
                                    html.Div(children = [
                                        html.Button(id='update_click',children =['Refresh'],
                                        style = {'margin-top' :'10px','background-color':'#cb1828','color':'#FFF'})]),
                            
                                    ])     
                        ]),
                    ])
@app.callback(
            Output('fut-order-table','data'),#, Output('fut-order-table','columns')],
            [Input('update_click','n_clicks')],)

def update_page(n_clicks):
    step = .0001
    # order table columns
    r = int(np.ceil(-np.log(step)/np.log(10)))-2
    columns_ob=[{'id':'from_mid','name':'From Mid','type':'numeric','format':FormatTemplate.percentage(r)},
                {'id':'price','name':'Price'},{'id':'size','name':'Size'},{'id':'cum_size','name': 'Size Total'},
                {'id':'size_$','name':'Size $','type':'numeric','format':FormatTemplate.money(0)},
                {'id':'cum_size_$','name':'Size Total $','type':'numeric','format':FormatTemplate.money(0)},
                {'id':'average_fill','name':'Averge Fill'},{'id':'exc','name':'Ex'},
                {'id':'side','name':'side','hidden':True}],
    # order table data
    df =  pd.DataFrame(np.random.randn(24,9),columns=['from_mid','price','size','cum_size','size_$','cum_size_$','average_fill','exc','side'])
    data_ob = df.to_dict('rows')
    return (data_ob)#,columns_ob) 

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

I suspect this related to the FormatTemplate objects being not Json

Any solution for this one already?

If the problem is the datatable why not just storing your data in another hidden div? This way you don’t have to deal with the table itself but just a json stored in an html component, update it via first callback and let this div be the input that fires callbacks for other components in your page https://dash.plot.ly/sharing-data-between-callbacks.

@Marc-Andre
This totally works

the problem was a coma at the end of my columns definition
columns = [{},{},{}] ,
which had made my columns variable a tuple instead of a list
so columns are updatable by callbacks including FormatingTemplate

The new debuging tool of Dash == 0.43.0 made this solution possible
Thanks

1 Like

Hello Marc-Andre,
it seems to me that I’ve similar problem like mentioned here.
I try to update the table in general by the value selected in the dropdown.
In the layout, I define the table in this way:
html.Div(dtab.DataTable
(id = ‘exch_table’,
data=[]
),

The call back is like:
@app.callback([Output(‘exch_table’, ‘data’), Output(‘exch_table’, ‘columns’)],
[Input(‘exch_dropdown’, ‘value’)])

def update_table(selected_value):
df = pd.read_sql(selected_value, conn)
columns=[{“name”: i, “id”: i} for i in df.columns],
data=df.to_dict(‘records’)
return data, columns

However, the outputs are no dash.Output.

Any feedback is very appreciated.
Kind regards,
Vaclav

Hi, this is my solution:
as the Das h v39 enables multiple out put (update is necessary) the layout is:
html.Div(dtab.DataTable(id = ‘exch_table’)

The call back is:
def update_table(selected_value):
df = pd.read_sql(selected_value, conn)
columns=[{“name”: i, “id”: i} for i in df.columns]
data=df.to_dict(‘records’)
return columns, data

It should work. The dash datatable is being updated based on the new pandas dataframe.

Good luck.
Vaclav

edit nvm I figured out the problem myself.

the example @Marc-Andre suggested works so far. However if I use debug=True I always get an error meassage. Does anyone know why?

what’s the error message?

I should have send it in my first message. I always get the following error, but only with debug=True.

Invalid argument columns[0].id passed into DataTable with ID “table”.
Expected string.

Failed component prop type: Invalid component prop data[0] of type array supplied to function t(){return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),y(this,m(t).apply(this,arguments))}
Was supplied type number.

@chriddyp any idea what is the problem?

I have the same issue, but it only happens when I have debug=True and the table appears just fine. Here are other threads I found with the same issue but no solution quite yet.

@Marc-Andre

Does that example really work? I tried extending it and it won’t even hit a breakpoint in the callback on Pycharm debugger.

My layout for the tables:

DataTable(
        id="conversion-table",
        data=[],
        style_cell={'textAlign': 'left'},
        style_data_conditional=[{
            'if': {'row_index': 'odd'},
            'backgroundColor': 'rgb(248,248,248)'
        }],
        style_header={
            'backgroundColor': 'rgb(230,230,230)',
            'fontWeight': 'bold'
        },
    ),
    html.Div(
        html.H5("Nutrients")
    ),
    html.Br(),
    DataTable(
        id="nutrients-table",
        data=[],
        style_cell_conditional=[{
            'if': {'column_id': c},
            'textAlign': 'left'
        } for c in ['Name']
        ],
        style_data_conditional=[{
            'if': {'row_index': 'odd'},
            'backgroundColor': 'rgb(248,248,248)'
        }],
        style_header={
            'backgroundColor': 'rgb(230,230,230)',
            'fontWeight': 'bold'
        },
    ),

and my callback:

@app.callback(
        Output('table-foodgroup-source', 'children'),
        Output('conversion-table', 'data'),
        Output('conversions-table', 'columns'),
        Output('nutrients-table', 'data'),
        Output('nutrients-table', 'columns'),
        Input('search-ingredient', 'value')
    )
    def show_tables(ingredient):
        '''
        return top table, conversions table, nutrients table, yield table,
        refuse table
        '''
        food_id = food_to_id_dict[ingredient]
        food = CNFFoodName.objects.get(id=str(food_id))
        food_grp = food.food_group.name
        food_src = food.food_source.description
        food_sci_name = "n/a"
        if food.scientific_name:
            food_sci_name = food.scientific_name
        # table for food group, source, scientific name
        food_group_table = html.Table([
                                html.Thead([
                                    html.Tr([html.Th("Group"), html.Th("Source"), html.Th("Scientific Name")])
                                ]),
                                html.Tbody([
                                    html.Tr([
                                        html.Td(food_grp),html.Td(food_src),html.Td(food_sci_name)
                                    ])
                                ])
                            ])

        conversions_df = make_conversions_df(food_id)
        #conversions_cols = [{"id": i, "name": conversions_df.columns[i]} for i in range(len(conversions_df.columns))]
        conversions_cols = [{"name": i, "id": i} for i in conversions_df.columns]
        conversions_data = conversions_df.to_dict('records')

        nutrients_df = make_nutrients_df(food_id)
        #nutrients_cols = [{"id": i, "name": nutrients_df.columns[i]} for i in range(len(nutrients_df.columns))]
        nutrients_cols = [{"name": i, "id": i} for i in nutrients_df.columns]
        nutrients_data = nutrients_df.to_dict('records')
        #nutrients_table = make_nutrients_table(nutrients_df)

        return food_group_table, conversions_data, conversions_cols,\
               nutrients_data, nutrients_cols

I have similar issue. I try to update data_table from callback, returning columns and rows like:

@app.callback(
    Output('data_prev', 'columns'),
    Output('data_prev', 'data'),
    
    Input('btn_parse','n_clicks'),
    <...>
    , prevent_initial_call =True
)
def parse_base(n_clicks, ...)
	<...>

	cols = [{"name": i, "id": i} for i in prsr.data.columns]
	rows = prsr.data.head(20).to_dict('records')
	
	return cols, rows

But it ends up with an error:

The value in question is either the only value returned,
or is in the top level of the returned list,

and has string representation
`([{'name': 'process', 'id': 'process'}, {'name': 'Func', 'id': 'Func'}, {'name': 'res', 'id': 'res'}, {'name': 'txt', 'id': 'txt'}, {'name': 'time', 'id': 'time'}, {'name': 'duration', 'id': 'duration'}, {'name': 'event', 'id': 'event'}, {'name': 'level', 'id': 'level'}, {'name': 'src', 'id': 'src'}, {'name': 'raw_row', 'id': 'raw_row'}, {'name': 'Exception', 'id': 'Exception'}, {'name': 'Descr', 'id': 'Descr'}, {'name': 'ServerComputerName', 'id': 'ServerComputerName'}, {'name': 'Usr', 'id': 'Usr'}, {'name': 'OSThread', 'id': 'OSThread'}, {'name': 'p:processName', 'id': 'p:processName'}, {'name': 't:clientID', 'id': 't:clientID'}, {'name': 't:applicationName', 'id': 't:applicationName'}, {'name': 't:computerName', 'id': 't:computerName'}, {'name': 't:connectID', 'id': 't:connectID'}, {'name': 'SessionID', 'id': 'SessionID'}, {'name': 'Query', 'id': 'Query'}, {'name': 'Context', 'id': 'Context'}, {'name': 'DeadlockConnectionIntersections', 'id': 'DeadlockConnectionIntersections'}, {'name': 'dbpid', 'id': 'dbpid'}], 
[{'process': 'mon_info_plugin', 'Func': 'getLicense', 'res': <...skipped 15400 chars..> 'dbpid': <NA>}])`

In general, Dash properties can only be
dash components, strings, dictionaries, numbers, None,
or lists of those.

For some reason, my output really gets wrapped into tuple.

Changing Debug switch does not change anything.

Maybe I’m passing too many data?

Problem solved.

There were < NA > values inside ‘rows’. They confused Dash.

Had to ‘fillna’ before showing data