'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 Divs 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 Figures

@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