Update figure by button with Input 'n_clicks' and State

Hi all,

I reproduced a strange behaviour in my dashboards with this example:

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import plotly.graph_objs as go


app = dash.Dash(__name__)

app.layout = html.Div([
    html.Button('update', id='update-button'),
    dcc.Dropdown(
        id='update-dropdown',
        options=[
            {'label': 'one', 'value': '1'},
            {'label': 'two', 'value': '2'}
        ],
        value='1'
    ),
    dcc.Graph(id='updated-graph')
])

@app.callback(
    Output('updated-graph', 'figure'), [
        Input('update-button', 'n_clicks')
    ],
    [
        State('update-dropdown', 'value')
    ]
)
def update_figure(n_clicks, value):
    return {
        'data':[
            go.Scatter(x=[1,2,3], y=[int(value)]*3)
        ],
        'layout': go.Layout(
            title='number of clicks: ' + str(n_clicks),
            yaxis={'title': 'Selected value'}
        )
    }

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

I want the figure to be updated only by clicking the button. But why gets the figure updated also on page load? It seems like the event ‘n_clicks is None’ also leeds to a figure update. But this happens only with a given state. If I remove the state from the callback the graph isn’t updated on page load.

Furthermore, I tried to catch this strange behaviour by adding the line ‘if n_clicks is not None’ to the callback function:

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import plotly.graph_objs as go


app = dash.Dash(__name__)

app.layout = html.Div([
    html.Button('update', id='update-button'),
    dcc.Dropdown(
        id='update-dropdown',
        options=[
            {'label': 'one', 'value': '1'},
            {'label': 'two', 'value': '2'}
        ],
        value='1'
    ),
    dcc.Graph(id='updated-graph')
])

@app.callback(
    Output('updated-graph', 'figure'), [
        Input('update-button', 'n_clicks')
    ],
    [
        State('update-dropdown', 'value')
    ]
)
def update_figure(n_clicks, value):
    if n_clicks is not None:
        return {
            'data':[
                go.Scatter(x=[1,2,3], y=[int(value)]*3)
            ],
            'layout': go.Layout(
                title='number of clicks: ' + str(n_clicks),
                yaxis={'title': 'Selected value'}
            )
        }

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

But this gives me another strange error thrown by JavaScript: ‘n is null’

I have no clue how to deal with these behaviours. Could someone please explain it to me?

This is the full JavaScript error:

*(This error originated from the built-in JavaScript code that runs Dash apps. Click to see the full stack trace or open your browser's console.)*

value@http://127.0.0.1:8050/_dash-component-suites/dash_core_components/dash_core_components.min.js?v=1.0.0&m=1564125374:47:216067

value@http://127.0.0.1:8050/_dash-component-suites/dash_core_components/dash_core_components.min.js?v=1.0.0&m=1564125374:47:218049

Zf@http://127.0.0.1:8050/_dash-component-suites/dash_renderer/react-dom@16.8.6.min.js?v=1.0.0&m=1564125371:67:368

qg@http://127.0.0.1:8050/_dash-component-suites/dash_renderer/react-dom@16.8.6.min.js?v=1.0.0&m=1564125371:95:336

hi@http://127.0.0.1:8050/_dash-component-suites/dash_renderer/react-dom@16.8.6.min.js?v=1.0.0&m=1564125371:104:285

Qg@http://127.0.0.1:8050/_dash-component-suites/dash_renderer/react-dom@16.8.6.min.js?v=1.0.0&m=1564125371:144:293

Rg@http://127.0.0.1:8050/_dash-component-suites/dash_renderer/react-dom@16.8.6.min.js?v=1.0.0&m=1564125371:145:168

Sc@http://127.0.0.1:8050/_dash-component-suites/dash_renderer/react-dom@16.8.6.min.js?v=1.0.0&m=1564125371:158:109

Z@http://127.0.0.1:8050/_dash-component-suites/dash_renderer/react-dom@16.8.6.min.js?v=1.0.0&m=1564125371:156:492

Kc@http://127.0.0.1:8050/_dash-component-suites/dash_renderer/react-dom@16.8.6.min.js?v=1.0.0&m=1564125371:155:69

ya@http://127.0.0.1:8050/_dash-component-suites/dash_renderer/react-dom@16.8.6.min.js?v=1.0.0&m=1564125371:153:185

enqueueSetState@http://127.0.0.1:8050/_dash-component-suites/dash_renderer/react-dom@16.8.6.min.js?v=1.0.0&m=1564125371:202:409

t.prototype.setState@http://127.0.0.1:8050/_dash-component-suites/dash_renderer/react@16.8.6.min.js?v=1.0.0&m=1564125371:20:420

handleChange@http://127.0.0.1:8050/_dash-component-suites/dash_renderer/dash_renderer.dev.js?v=1.0.0&m=1564125371:32559:9

dispatch@http://127.0.0.1:8050/_dash-component-suites/dash_renderer/dash_renderer.dev.js?v=1.0.0&m=1564125371:33559:7

createThunkMiddleware/</</<@http://127.0.0.1:8050/_dash-component-suites/dash_renderer/dash_renderer.dev.js?v=1.0.0&m=1564125371:33047:16

dispatch@http://127.0.0.1:8050/_dash-component-suites/dash_renderer/dash_renderer.dev.js?v=1.0.0&m=1564125371:33105:18

handleResponse@http://127.0.0.1:8050/_dash-component-suites/dash_renderer/dash_renderer.dev.js?v=1.0.0&m=1564125371:37587:17

handleJson@http://127.0.0.1:8050/_dash-component-suites/dash_renderer/dash_renderer.dev.js?v=1.0.0&m=1564125371:37704:17

This is my environment:
Mozilla Firefox @ Win10
Python 3.6.8
dash==1.0.2
dash-core-components==1.0.0
dash-html-components==1.0.0
dash-renderer==1.0.0
plotly==4.0.0

I’m not sure why you get different behaviours on page load when using state. I can’t quite remember the rules on which callbacks get fired and with which values.

The second error you get is because your callback is implicitly returning None if you don’t enter the if statement, which gets converted to a javascript null which dcc.Graph tries to use as the figure for the graph but fails. Instead, you should specify a return value, or prevent an update. For example

@app.callback(
    Output("updated-graph", "figure"),
    [Input("update-button", "n_clicks")],
    [State("update-dropdown", "value")],
)
def update_figure(n_clicks, value):
    if n_clicks is None:
        return dash.no_update
    return {
        "data": [go.Scatter(x=[1, 2, 3], y=[int(value)] * 3)],
        "layout": go.Layout(
            title="number of clicks: " + str(n_clicks),
            yaxis={"title": "Selected value"},
        ),
    }

though you can also raise dash.exceptions.PreventUpdate or simply return some default figure of your choice if you prefer.

1 Like

The second error you get is because your callback is implicitly returning None if you don’t enter the if statement, which gets converted to a javascript null which dcc.Graph tries to use as the figure for the graph but fails.

Makes totally sense, thank you. So this is at least a proper workaround for the original problem.