Black Lives Matter. Please consider donating to Black Girls Code today.

Multi-page app with synced dropdown menu not working

Hi everyone,

I’m quite new to Dash, and I’m currently struggling with the multi-page-dropdown.py example I found on the Dash Recipes Github because it is not working as I would expect.

I would like to have a multi-page app, with a dropdown menu on all pages that works as a filter on what is displayed on such pages; the dropdown selection should be persistent on all pages. The behaviour I’m looking for is displayed in the following GIF.

https://github.com/plotly/dash-recipes/blob/master/multi-page-dropdown-example.gif

When I run the recipe, however, I see an empty plot, when I open a new page.

My understanding of the callbacks that regulate the functioning of the code is that update_graph updates the plot based on the dropdown selection or when the URL changes (i.e., when a new page opens).

What I notice when I run the code provided in the recipe is that the update_graph callback updates correctly the graph when the dropdown selection is changed, but it fails to update the graph on a new page, when I open it.

Please find below the code I’m running, for reference.

Is there anything I’m doing wrong and should be done differently?

Thanks in advance for your support!

import dash
from dash.dependencies import Input, State, Output
import dash_html_components as html
import dash_core_components as dcc
import pandas as pd

df = pd.DataFrame({
    'x': [1, 2, 3, 1, 2, 3, 1, 2, 3],
    'y': [3, 2, 4, 1, 4, 5, 4, 3, 1],
    'group-1': ['/', '/exhibit-b', '/exhibit-c', '/', '/exhibit-b', '/exhibit-c', '/', '/exhibit-b', '/exhibit-c'],
    'group-2': ['LA', 'LA', 'LA', 'London', 'London', 'London', 'Montreal', 'Montreal', 'Montreal'],
})

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

# app.config.supress_callback_exceptions = True
app.config.suppress_callback_exceptions = True  

app.layout = html.Div([
    # This "header" will persist across pages
    html.H2('Multi Page Dash App'),


    # Each "page" will modify this element
    html.Div(id='content-container-part-1'),


    dcc.Dropdown(
        id='graph-control',
        options=[{'label': i, 'value': i} for i in df['group-2'].unique()],
        value='LA'
    ),

    # Each "page" will modify this element
    html.Div(id='content-container-part-2'),

    # This Location component represents the URL bar
    dcc.Location(id='url', refresh=False)
], className="container")

link_mapping = {
    '/': 'Exhibit A',
    '/exhibit-b': 'Exhibit B',
    '/exhibit-c': 'Exhibit C',
}

styles = {
    'link': {'padding': '20'}
}

@app.callback(
    Output('content-container-part-1', 'children'),
    [Input('url', 'pathname')])
def display_page(pathname):
    return html.Div([
        html.Div([
            html.Span(
                dcc.Link(link_mapping['/'], href="/") if pathname != '/' else 'Exhibit A',
                style=styles['link']
            ),

            html.Span(
                dcc.Link(link_mapping['/exhibit-b'], href="/exhibit-b") if pathname != '/exhibit-b' else 'Exhibit B',
                style=styles['link']
            ),

            html.Span(
                dcc.Link(link_mapping['/exhibit-c'], href="/exhibit-c") if pathname != '/exhibit-c' else 'Exhibit C',
                style=styles['link']
            )

        ]),

        dcc.Markdown('### {}'.format(link_mapping[pathname])),
    ])


@app.callback(
    Output('content-container-part-2', 'children'),
    [Input('url', 'pathname')])
def display_page(*args):
    return html.Div([
        dcc.Graph(
            id='graph',
        )
    ])


@app.callback(
    Output('graph', 'figure'),
    [Input('graph-control', 'value'),
     Input('url', 'pathname')])
def update_graph(value, pathname):
    dff = df[(df['group-1'] == pathname) & (df['group-2'] == value)]
    return {
        'data': [{
            'x': dff.x,
            'y': dff.y,
            'type': 'bar'
        }],
        'layout': {
            'title': '{} in {}'.format(value, link_mapping[pathname])
        }
    }

app.css.append_css({"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})

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

Hi,

The issue seems to be resolved by simply swapping the order of display_page and update_graph callbacks: the correct requires update_graph to come first in the code, and display_page to follow.

Now, my question is if it is possible to control the execution order of callbacks, to make sure everything is executed correctly?

Obviously, if anyone has more brilliant explanations or solutions, please do come forward!

For reference, the piece should look like this.

@app.callback(
    Output('graph', 'figure'),
    [Input('graph-control', 'value'),
     Input('url', 'pathname')])
def update_graph(value, pathname):
    dff = df[(df['group-1'] == pathname) & (df['group-2'] == value)]
    return {
        'data': [{
            'x': dff.x,
            'y': dff.y,
            'type': 'bar'
        }],
        'layout': {
            'title': '{} in {}'.format(value, link_mapping[pathname])
        }
    }

@app.callback(
    Output('content-container-part-2', 'children'),
    [Input('url', 'pathname')])
def display_page(*args):
    return html.Div([
        dcc.Graph(
            id='graph',
        )
    ])

To me, the structure of the callbacks is weird. Why don’t you just update the graph directly in the display_page callback?

Thanks @Emil! I replicated what was being done in the recipe, so the structure of the callbacks comes from there. But I appreciate your point and I updated my code, which now works even better than before!