首页 > 解决方案 > 破折号多个独立的回调

问题描述

我有一个简单的破折号应用程序,带有折线图和用作过滤器的单选按钮。

我创建了 2 个回调 - 第一个用于 url,第二个用于过滤器。但是,当我运行应用程序时,它一直在更新(我想在循环中调用第二个回调)。似乎这两个回调在某种程度上相互依赖,但我希望它们是独立的 - 我只需要在单选按钮(过滤器)更改时运行第二个。

没有第一个回调一切正常。

@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

编辑:添加布局

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'
        ),
    ]
)

@BasvanderLinden 建议后的EDIT2:

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")

标签: pythonplotly-dash

解决方案


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"

推荐阅读