Trigger callback from dropdowns created in previous callback

Hello,

First time posting, i hope my post is clear enough.

VERSIONS

Dash 2.17.1
Plotly 5.20.0
Dash-core-components 2.0.0
Dash-html-components 2.0.0
Dash-table 5.0.0

GOAL

I am coding an app that lets a user choose the sheet of an excel file with a dropdown. After choosing the excel sheet, a table stores the sheet content and dropdowns appear and they are populated with the column names from this sheet, to let the user choose which excel column corresponds to the expected column type. This is used to perform calculations without hardcoding the column names. Once a button is pressed, graphs and data tables appear as well as new dropdowns that are meant to interact with one the graphs.

The aim here is to guide the user and make things appear sequentially. In the end, i would like the possibility to use any excel file that the user can drop (with an upload component).

PROBLEM

The layout is simple as I need just the first dropdown for excel sheet selection, and divs to receive the components.

Components are created inside callbacks. It works well until the last dropdowns to update the last graph. Using those doesn’t trigger the corresponding callback.

app.layout = html.Div(
    [
        html.H3(children="Start by selecting the excel sheet to import"),
        dcc.Dropdown(
            id="excel_tab_dropdown",
            options=list(sheets),
            placeholder="Select Excel tab to import",
            persistence=True,
        ),
        dcc.Loading(
            type="default",
            children=[
                dash_table.DataTable(
                    id="df_table",
                    page_size="15",
                    style_table={"height": "300px", "overflowX": "auto"},
                    persistence=True,
                )
            ],
        ),
        html.Div(id="raw_data_div"),
        html.Div(id="processed_data_div"),
    ]
)

###### RAW DATA FRAME TABLE AND COLUMN DROPDOWN ######
@callback(
    Output(component_id="df_table", component_property="data"),
    Output(component_id="df_table", component_property="columns"),
    Output(component_id="raw_data_div", component_property="children"),
    Input(component_id="excel_tab_dropdown", component_property="value"),
)
def update_df_table(selected_excel_sheet):
    data = {}
    columns = []
    children = []
    if selected_excel_sheet != None:
        df = pd.read_excel(data_file, sheet_name=selected_excel_sheet)
        data = df.to_dict("records")
        columns = [{"name": i, "id": i} for i in df.columns]
        options = df.columns
        children = [
            html.H3(children="Select the names for the corresponding columns"),
            ####### CLASS DROPDOWN #######
            html.Div(
                style={"display": "inline-block", "margin-left": "20px"},
                children=[
                    "Class Column",
                    dcc.Dropdown(
                        id="class_dropdown", options=options, persistence=True)]),
            ...
            html.Button("Generate Plots", id=("plot_button"), n_clicks=0)
        ]
    return data, columns, children

###### GENERATE PLOTS AFTER COLUMN SELECTION ######

@callback(
    Output(component_id="processed_data_div", component_property="children"),
    Input(component_id="plot_button", component_property="n_clicks"),
    State(component_id="df_table", component_property="data"),
    State(component_id="class_dropdown", component_property="value"),
    State(component_id="dbe_dropdown", component_property="value"),
    State(component_id="formula_dropdown", component_property="value"),
    State(component_id="c_dropdown", component_property="value"),
    State(component_id="n_dropdown", component_property="value"),
    State(component_id="o_dropdown", component_property="value"),
    State(component_id="s_dropdown", component_property="value"),
    State(component_id="samples_dropdown", component_property="value"),
    prevent_initial_call=True,
)
def generate_plots(
    n_clicks,
    table_data,
    class_col,
    dbe_col,
    formula_col,
    c_col,
    n_col,
    o_col,
    s_col,
    samples_cols,
):

    if table_data is not None:
        ###### VARIABLES TO COMPUTE ######
        ...
        ###### SHOW GRAPHS AND TABLES######
        
        children = [
            ...,
            html.Div(
                children=[
                    html.Label(
                        ["Class : "],
                        style={"font-weight": "bold", "text-align": "center"},
                    ),
###Problematic dropdowns####
                    dcc.Dropdown(
                        options=classes,
                        value=classes[0],
                        id="dbe_class_dropdown",
                        placeholder="Class",
                        style={"display": "inline-block", "margin-right": "10px"},
                    ),
                    html.Label(
                        ["DBE : "],
                        style={"font-weight": "bold", "text-align": "center"},
                    ),
                    dcc.Dropdown(
                        options=dbe,
                        value=[],
                        id="dbe_dbe_dropdown",
                        placeholder="DBE",
                        multi=True,
                        style={"display": "inline-block", "margin-right": "10px"},
                    ),
                ],
                style={
                    "display": "flex",
                    "align-items": "center",
                    "justify-content": "center",
                    "margin": "auto",
                },
            ),
            html.Div(children=[dcc.Graph(id="fig_dbe_classes")]),
        ]
    return children

###This callback is not triggered###
###### DBE GRAPHS CALLBACK ######
@callback(
    Output(component_id="fig_dbe_classes", component_property="figure"),
    Input(component_id="dbe_class_dropdown", component_property="value"),
    Input(component_id="dbe_dbe_dropdown", component_property="value"),
    State(component_id="dbe_classes_df_table", component_property="data"),
    State(component_id="classes_dropdown", component_property="value"),
    State(component_id="dbe_dropdown", component_property="value"),
    State(component_id="n_dropdown", component_property="value"),
    State(component_id="o_dropdown", component_property="value"),
)
def update_dbe_graphs(
    class_selection, dbe_selection, table_data, class_col, dbe_col, n_col, o_col
):

    dbe_classes_df = pd.DataFrame.from_records(table_data)
    print(dbe_classes_df)
    if dbe_selection == []:
        fig_dbe_classes = px.scatter(
            data_frame=dbe_classes_df[(dbe_classes_df[class_col] == class_selection)],
            x="#C",
            y="DBE",
            color="Intensity",
            opacity=0.8,
            hover_data=[n_col, o_col],
            facet_col="Sample",
            facet_col_wrap=5,
        )
    else:
        fig_dbe_classes = px.scatter(
            data_frame=dbe_classes_df[
                (dbe_classes_df[class_col] == class_selection)
                & (dbe_classes_df[dbe_col].isin(dbe_selection))
            ],
            x="#C",
            y="DBE",
            color="Intensity",
            opacity=0.8,
            hover_data=[n_col, o_col],
            facet_col="Sample",
            facet_col_wrap=5,
        )
    return fig_dbe_classes

QUESTIONS

Is it an expected behaviour that these dropdowns don’t trigger the callback ?

Is my coding the proper way of showing sequentially the components ?

If I understand it right, this kind of thing should work, and there’s a discussion of adding elements to the layout dynamically, and how this works with callbacks, here:

The usual reasons for a callback not firing are things like duplicate element ids, failure to specify allow_duplicate in a callback output, illegal properties specified in a callback, circularity or mistyped ID values. Usually the browser console gives useful diagnostics, but may be harder to navigate in this case because some console log errors are expected if elements reference by a callback are not in the layout when the page is loaded, as in your example.

2 Likes

Hello,

Thanks for your feedback. I already read a bit of the documentation but didn’t find something useful until now.

Turning on the debug function helped me though. Some errors appeared that were not shown in the terminal.

The first is I should have added suppress_callback_exceptions=True as otherwise a layout error will come up for components defined in callbacks. This is stated in the advanced callbacks documentation but i didn’t read through it as it is written in the “callbacks with no output” paragraph.

The second is a simple typo that remained even though I reviewed my code several times.

I think i will keep the debug function on in the future.