I am trying to refactor my one-page dashboard app into a multipage app in order to provide an "About" page where I lay out some details about the dashboard. However, I run into duplicate callback erorrs. Note that
I provide below a full MWE which reproduces the error.
I think the issue lies with the fact that I am getting the Dash
instance in the pages via app = dash.get_app()
. Whenever the duplicate callback error occurs, I notice in the callback tree debugger tool that the connections have doubled.
I think I need to pass the app
to the utility functions, since I need them to set up the callback. I decided to put the callback inside the utility function, because
.
├── main.py
├── pages
│ ├── about.py
│ └── home.py
└── utils.py
utils.py
import plotly.graph_objects as go
import numpy as np
from dash import Dash, Patch, html, dcc
from dash.dependencies import Input, Output
def sine(xs: np.array) -> np.array:
return 100 * np.sin(xs)
def make_figure(app: Dash) -> html.Div:
fig = go.Figure()
fig.add_trace(go.Scatter(
x=None,
y=None,
name="Randomized Data"
))
fig.update_layout(height=800, template="plotly_dark")
@app.callback(
Output("plotly-figure", "figure"),
Input("dcc-interval", "n_intervals")
)
def update(n_clicks: int) -> Patch:
xs = np.linspace(0, 6*np.pi, 100)
ys = sine(xs) * np.random.random(size=len(xs))
patch = Patch()
patch["data"][0]["x"] = xs
patch["data"][0]["y"] = ys
return patch
return html.Div(children=[
dcc.Graph(figure=fig, id="plotly-figure")
])
def make_random_title(app: Dash) -> html.Div:
adjectives = [
"Affable",
"Affluent",
"Deprived",
"Destitute",
"Elaborate",
"Evocative",
"Frivolous",
"Gritty",
"Immense"
]
nouns = [
"Crackdown",
"Culprit",
"Discrepancy",
"Endorsement",
"Enmity",
"Gravity",
"Immunity",
"Juncture",
"Legislation",
"Offspring"
]
@app.callback(
Output("random-title", "children"),
Input("dcc-interval", "n_intervals")
)
def update(n_intervals: int) -> html.H1:
return html.H1(f"{np.random.choice(adjectives)} {np.random.choice(nouns)}")
return html.Div(children=[
f"{np.random.choice(adjectives)} {np.random.choice(nouns)}"
], id="random-title")
pages/about.py
import dash
dash.register_page(__name__, path="/about")
from dash import html
def layout() -> html.Div:
return html.Div(children=[
html.H1("About Page")
])
pages/home.py
import dash
import utils
dash.register_page(__name__, path="/")
app = dash.get_app()
from dash import html
def layout() -> html.Div:
return html.Div(children=[
utils.make_random_title(app),
utils.make_figure(app)
])
main.py
import dash
import dash_bootstrap_components as dbc
from dash import Dash, html, dcc
def main() -> Dash:
app = Dash(__name__, use_pages=True, external_stylesheets=[dbc.themes.SLATE])
app.layout = html.Div(children=[
dash.page_container,
dcc.Interval(id="dcc-interval", n_intervals=0, interval=1000)
])
return app
if __name__ == "__main__":
app = main()
app.run(debug=True)
requirements.txt
plotly
dash
numpy
dash-bootstrap-components
The root cause of the initial problem was that you were binding (the same) callbacks multiple times. Specifically, because you made layout
of the home page a function instead of a variable,
def layout() -> html.Div:
return html.Div(children=[
utils.make_random_title(app),
utils.make_figure(app)
])
this function will be executed every time the home page is loaded, thus invoking utils.make_random_title
, which tries to bind this callback (again)
@app.callback(
Output("random-title", "children"),
Input("dcc-interval", "n_intervals")
)
def update(n_intervals: int) -> html.H1:
return html.H1(f"{np.random.choice(adjectives)} {np.random.choice(nouns)}")
resulting in a duplicate callback error. The solution is to make sure that the code defining the callback(s) is only executed only once.
There are many ways achieve that goal, with the simplest for your case being to change the layout
function into a layout
variable. However, the path that you came up with (using the @callback
decorator from within the global scope), is also perfectly valid approach. In fact, I tend to prefer this structure for readability (unless it hinders code reuse).
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments