'Handling dash callback of an input inside the multi Tab app
I am trying to build a multi tab dash application based on the reference code from https://dash-bootstrap-components.opensource.faculty.ai/examples/graphs-in-tabs/#sourceCode
My requirement is to have the Button inside the Histogram Tab page instead on the Main page. I have modified the reference code to achieve this. Below is the modified code.
Commented out from app.layout and Moved dbc.Button inside the Tab as the first column (Callback modification):
@app.callback(
Output("tab-content", "children"),
[Input("tabs", "active_tab"), Input("store", "data")],
)
def render_tab_content(active_tab, data):
"""
This callback takes the 'active_tab' property as input, as well as the
stored graphs, and renders the tab content depending on what the value of
'active_tab' is.
"""
if active_tab and data is not None:
if active_tab == "scatter":
return dcc.Graph(figure=data["scatter"])
elif active_tab == "histogram":
return dbc.Row(
[
#Modified to add the Button inside the Tab
dbc.Col(dbc.Button(
"Regenerate graphs",
color="primary",
block=True,
id="iButton",
className="mb-3",
), width=4
),
dbc.Col(dcc.Graph(figure=data["hist_1"]), width=4),
dbc.Col(dcc.Graph(figure=data["hist_2"]), width=4),
]
)
return "No tab selected"
2nd change is as below, updated the iButton as input in the callback:
@app.callback(Output("store", "data"), [Input("iButton", "n_clicks")])
def generate_graphs(n):
With this change, exception is raised when the application is run
ID not found in layout
Attempting to connect a callback Input item to component:
"iButton"
but no components with that id exist in the layout.
I am not sure how to handle the Button behaviour when inside the Tab.
Solution 1:[1]
When dash first evaluates the app it tries to resolve the callback inputs using the layout components in the àpp.layout
. The Button cannot be found because it is added later inside your callback so dash doesn't know about it just yet.
What I would recommend is restructuring the layout and callbacks a little bit, so that each component is known from the beginning.
Layout
Define every component of the layout beforehand. Define each Div
but don't show both of them at the start
app.layout = dbc.Container(
[
html.H1("Dynamically rendered tab content"),
html.Hr(),
dbc.Tabs(
[
dbc.Tab(label = "Scatter", tab_id = "scatter"),
dbc.Tab(label = "Histograms", tab_id = "histogram"),
],
id = "tabs",
active_tab = "scatter",
),
html.Div(id = "hist-tab", className = "p-4", children = [
dbc.Row(
[
# Modified to add the Button inside the Tab
dbc.Col(dbc.Button(
"Regenerate graphs",
color = "primary",
block = True,
id = "iButton",
className = "mb-3",
), width = 4
),
dbc.Col(dcc.Graph(id = 'hist-1', figure = {}), width = 4),
dbc.Col(dcc.Graph(id = 'hist-2', figure = {}), width = 4)
]
)
]),
html.Div(id = "scatter-tab", className = "p-4", children = [
dcc.Graph(figure = {}, id = 'scatter-plot')
], style = {'display': 'none'}),
])
Setting the Div
style to {'display': 'block'}
will show the Div, setting it to {'display': 'none'}
will hide it for now.
Callbacks
The first callback listening to the Tab
will be in charge of making the Div
s vizible and invizble:
@app.callback(
[Output("hist-tab", "style"), Output("scatter-tab", "style")],
[Input("tabs", "active_tab")],
)
def render_tab_content(active_tab):
"""
This callback takes the 'active_tab' property as input, as well as the
stored graphs, and renders the tab content depending on what the value of
'active_tab' is.
"""
on = {'display': 'block'}
off = {'display': 'none'}
if active_tab is not None:
if active_tab == "scatter":
return off, on
elif active_tab == "histogram":
return on, off
return "No tab selected"
Since every component exists at every point, the second callback can directly write the data to the Figure
s
@app.callback([Output("hist-1", "figure"), Output("hist-2", "figure"), Output("scatter-plot", "figure")],
[Input("iButton", "n_clicks")])
def generate_graphs(n):
"""
This callback generates three simple graphs from random data.
"""
if not n:
# generate empty graphs when app loads
return {}, {}, {}
# simulate expensive graph generation process
time.sleep(2)
# generate 100 multivariate normal samples
data = np.random.multivariate_normal([0, 0], [[1, 0.5], [0.5, 1]], 100)
scatter = go.Figure(
data = [go.Scatter(x = data[:, 0], y = data[:, 1], mode = "markers")]
)
hist_1 = go.Figure(data = [go.Histogram(x = data[:, 0])])
hist_2 = go.Figure(data = [go.Histogram(x = data[:, 1])])
# save figures in a dictionary for sending to the dcc.Store
return hist_1, hist_2, scatter
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | ConstantinB |