Search code examples
pythonplotlydashboardplotly-dash

Update plots for multiple plots in Dash


i have a really basic question, mostly to understand how Dash works regarding the graph updating process when a variable changes. I'm just starting building a Dashboard and i'm still not familiar with the full syntax.

I have a plot in my layout, a dropdown box, the callback and the update function, something like this:

@app.callback(
    Output(component_id='plot1', component_property='figure'),
    Input(component_id='drop1', component_property='value')
)
def update_graph(sel):
    """ Stuff for updating the plot """
    return fig

Which works perfect, but i don't fully understand "why".

Now, my questions are...

  1. How the callback knows that update_graph() is the function to call in order to update that plot? It's never called inside the callback method, the dropdown selected value is never actually passed anywhere, etc.
  2. What happens if i have more than one dynamic plot? Another dropdown box or any other reactive element in my layout and i wanna update a second graph based on that second input object. I have to make another callback and another update function? How will, again, each callback know which one of the update functions to use?

Thanks in advance!


Solution

  • Okay so Im going to explain a simple example from the plotly documentation with just one graph. Once we understand a single graph then multiple graphs become easier to understand.

    import dash
    import dash_core_components as dcc
    import dash_html_components as html
    from dash.dependencies import Input, Output
    
    external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
    
    app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
    
    app.layout = html.Div([
        html.H6("Change the value in the text box to see callbacks in action!"),
        html.Div(["Input: ",
                  dcc.Input(id='my-input', value='initial value', type='text')]),
        html.Br(),
        html.Div(id='my-output'),
    
    ])
    
    
    @app.callback(
        Output(component_id='my-output', component_property='children'),
        Input(component_id='my-input', component_property='value')
    )
    def update_output_div(input_value):
        return 'Output: {}'.format(input_value)
    
    
    if __name__ == '__main__':
        app.run_server(debug=True)
    

    So starting off lets notice the input and output component_id. Notice that the component_id of the input matches the dcc.Input id. The matching id means there is a link between the decorator, @app.callback and the layout object. This link means that once the input changes then the decorator will be called and the decorator will look at its respective output id. The decorator will then seek to update the output component which is the HTML div. In order to do this the decorator will always look to the function directly beneath itself, which in this case is the update_output_div. The value that was changed (input) will be passed to this function.

    Okay now onto the multiple graphs. In the following code I will leave out the app.layout declaration, just assume that each of the ids below (square, cube twos, threes, x^x, num-multi,dropdown) are all linked to their counterparts.

    @app.callback(
        Output('square', 'children'),
        Output('cube', 'children'),
        Output('twos', 'children'),
        Output('threes', 'children'),
        Output('x^x', 'children'),
        Input('num-multi', 'value'),
        Input('dropdown', 'value2'))
    
    def callback_a(v, v2):
       # update 
    

    Again the decorator will just look for a change in any of the inputs and then update each of the outputs via the callback_a function

    So to answer your first question. The callback will always just call the function directly below itself without you implicitly coding it. You can certainly decide for further functions to be called inside that function. For example, if I have multiple inputs that may not be related I can look for what was actually triggered.

    @app.callback(
        Output('map', 'figure'),
        Input('map','clickData'),
        Input('map','selectedData'))
    def update_scatter_plots(
        ctx = dash.callback_context
        if ctx.triggered[0]['prop_id'] == 'map.clickData':
           #it was a click 
        elif ctx.triggered[0]['prop_id'] == 'map.selectedData':
           #it was a selection
    

    Here I have a map graph and I have two different inputs a click event and a select event and depending on what the context was I can decide what to do with the information was passed through.

    And as for you second question let me know if you would like me to elaborate on that more.