romanzdk
romanzdk

Reputation: 1332

Dash multiple independent callbacks

I have a simple dash app with line chart and radio buttons which serve as a filter.

I created 2 callbacks - first for the url and the second one for the filter. However when I run the app it keeps updating (I suppose calling the second callback in a loop) all the time. It seems these two callbacks are somewhat dependent on each other but I want them to be independent - I need to run the second one only when the radio button (filter) is changed.

Without the first callback everything worked fine.

@app.callback(
    Output('page-content', 'children'),
    [Input('url', 'pathname')]
)
def display_page(pathname):
    if pathname == '/':
        return '404'
    elif pathname == '/something/my-dashboard':
        return app.layout
    else:
        return '404'

@app.callback(
    Output("main-chart", "figure"), 
    Input("category", "value")
)
def update_graph(category):
    dff = melted[melted["category"] == category]
    fig = create_chart(dff)
    set_style(fig, category)
    return fig

EDIT: Added layout

app.layout = html.Div(
    [   dcc.Location(id='url', refresh=False),
        html.Div(
            [
                html.Div(
                    [
                        html.H1(children="My dashboard"),
                        html.Div([dcc.Graph(id="main-chart", figure=fig)]),
                    ],
                    className="column1",
                ),
                html.Div(
                    [
                        dbc.Label("Filter", style={'fontWeight':'bold'}),
                        dcc.RadioItems(
                            id="category",
                            options=[{"label": i, "value": i} for i in categories],
                            value="Product A",
                            labelStyle={"display": "block"},
                        ),
                    ],
                    className="column2",
                ),
            ],
            className="row",
            id='page-content'
        ),
    ]
)

EDIT2 after @BasvanderLinden advice:

fig = create_chart(df)
set_style(fig, "Marketing")

app.layout = html.Div(
    [   dcc.Location(id='url', refresh=False),
        html.Div(
            className="row",
            id='page-content'
        ),
    ]
)

dashboard_layout = html.Div(
    [
                html.Div(
                    [
                        html.H1(children="My dashboard"),
                        html.Div([dcc.Graph(id="main-chart", figure=fig)]),
                    ],
                    className="column1",
                ),
                html.Div(
                    [
                        dbc.Label("Category", style={'fontWeight':'bold'}),
                        dcc.RadioItems(
                            id="category",
                            options=[{"label": i, "value": i} for i in categories],
                            value="Marketing",
                            labelStyle={"display": "block"},
                        ),
                    ],
                    className="column2",
                ),
            ],
)


@app.callback(Output("page-content", "children"), [Input("url", "pathname")])
def display_page(pathname):
    print(pathname)
    if pathname == "/":
        return "404"
    elif pathname == "/my-dashboard":
        return dashboard_layout
    else:
        return "404"

@app.callback(
    Output("main-chart", "figure"), 
    Input("category", "value")
)
def update_graph(category):
    dff = df[df["Category"] == category]
    fig = create_chart(dff)
    set_style(fig, category)
    return fig


if __name__ == "__main__":
    app.run_server(debug=True, host="0.0.0.0")

Upvotes: 1

Views: 8736

Answers (1)

bas
bas

Reputation: 15722

The problem is actually in your first callback:

@app.callback(
    Output('page-content', 'children'),
    [Input('url', 'pathname')]
)
def display_page(pathname):
    if pathname == '/':
        return '404'
    elif pathname == '/something/my-dashboard':
        return app.layout
    else:
        return '404'

The problem is that you return app.layout here.

It's actually really interesting what's happening here. Since app.layout covers your whole layout, this means it will include the Location component with an id value of url as well. So since app.layout is appended to the page-content div, this means that after appending app.layout to the page-content div there are now two Location components with the same id (url). The fact that there is a new Location components registered with id url results in the callback being triggered again. This all results in the first callback being recursively triggered and appending elements to the dom. You can see this happening if you inspect the elements in the browser.

The solution therefore is to not return app.layout in your callback, but to instead abstract a part of your layout and return that in the callback. That is a component that doesn't include a Location component in its definition.

So you could abstract the dashboard in its own component something like this:

dashboard_layout = html.Div(
    [
        html.Div(
            [
                html.H1(children="My dashboard"),
                html.Div([dcc.Graph(id="main-chart", figure=fig)]),
            ],
            className="column1",
        ),
        html.Div(
            [
                dbc.Label("Filter", style={"fontWeight": "bold"}),
                dcc.RadioItems(
                    id="category",
                    options=[{"label": i, "value": i} for i in categories],
                    value="Product A",
                    labelStyle={"display": "block"},
                ),
            ],
            className="column2",
        ),
    ]
)

app.layout = html.Div(
    [
        dcc.Location(id="url", refresh=False),
        html.Div(
            className="row",
            id="page-content",
        ),
    ]
)

@app.callback(Output("page-content", "children"), [Input("url", "pathname")])
def display_page(pathname):
    print(pathname)
    if pathname == "/":
        return "404"
    elif pathname == "/something/my-dashboard":
        return dashboard_layout
    else:
        return "404"

Upvotes: 1

Related Questions