Initializing empty graph

I only started having this problem a few hours into coding my application. I am trying to start on an empty graph with no data and add to the graph as data is entered in the table. Initially this seemed to work, but now I am getting an error that the property ‘data’ of null cannot be read. I think it is coming from my second callback, but none of the inputs for this callback should be changing. Here is my code:

    dbc.Row([
    dbc.Col([
        html.Div([
            # datatable, button, and plot
            dash_table.DataTable(
                id='table',
                columns = [{'name':i,'id':i} for i in ['col1','col2','col3']],
                data=[],
                editable=True,
                row_deletable=True,
                style_as_list_view=True,
                style_cell={'textAlign': 'center'}
            ),
            html.Button('Add Row', id='add-row-lithology-table', n_clicks=0),
          
            dcc.Graph(
                id='graph',
                config={
                    'displayModeBar': False
                },
                figure={
                    'data': [],
                    'layout': go.Layout(
                        xaxis={
                            'showticklabels': False,
                            'ticks': '',
                            'showgrid': False,
                            'zeroline': False
                        },
                        yaxis={
                            'showticklabels': False,
                            'ticks': '',
                            'showgrid': False,
                            'zeroline': False
                        }
                    )
                }
            )
        ])

    ])
],
style = {'padding': 10}
)

@app.callback(
    Output('table', 'data'),
    [Input('add-row-table', 'n_clicks')],
    [State('table', 'data'),
     State('table', 'columns')])
def add_row(n_clicks, rows, columns):
    if n_clicks > 0:
        rows.append({c['id']: '' for c in columns})
        rows[-1]['col1'] = len(rows)
    return rows


@app.callback(
    Output('graph', 'figure'),
    [Input('table', 'data'),
     Input('table', 'columns')])
def display_output(rows, columns):
    if len(rows)>0:
        return {
            'data': [{
                'type': 'heatmap',
                'z': [[row.get('col1', None)] for row in rows], # z is data entered in datatable ex. 'z': [['10', '203', '', '', '', '']]
                'y': [row.get('col3', None) for row in rows],
                'showscale': False,
                'showlegend': False
            }],
            'layout': go.Layout(
                title='Title',
                xaxis={
                    'showticklabels': False,
                    'ticks': '',
                    'showgrid': False,
                    'zeroline': False
                },
                yaxis={
                    'ticks': '',
                    'showgrid': False,
                    'zeroline': False,
                    'autorange':'reversed'
                }
            )
        }
1 Like

Your second callback doesn’t return anything if len(rows) is zero. This means figure gets a null value, and so when the graph is rendered, it looks for the data attribute of the figure (null) which causes an error.

To fix it, just return the empty graph you’re initialising with, or I think it should also work to either return dash.no_update or raise PreventUpdate (the latter needs to be imported from dash.exceptions)

1 Like

Thank you! Works great!

Can anyone explain why this callback is being triggered if the inputs aren’t changing though?

1 Like

From the docs:

When the Dash app starts, it automatically calls all of the callbacks with the initial values of the input components in order to populate the initial state of the output components.

Since you instantiate the table with data=[], rows initially has length 0.

1 Like

Thanks tcbegley. That makes sense!

1 Like