Callbacks with identical Input fail on dashboard start-up but otherwise work as intended

When i spin up my dashboard, initially i will be thrown an “ValueError: Invalid value”, and only 1 of my 4 graphs will show. which one shows, is random for each time i run the dashboard.

However, if i change the “area-radio” button to another value and then change it back to the initial one (the exact same one as when i spin up the dashboard…) everything works fine.

Each callback works without any errors if i comment out the remaining ones (ie. test them out one at a time). But if they are there simultaniously i get an error as mentioned on startup.

The dashboard is fed a df on creation.

Has anyone experience anything similar, and how did you go about fixing it?

any help will be much appreciated!

layout

  return html.Div([
            dcc.RadioItems(
                id="area-radio",
                options=[
                    {"label": "Områder i produktion", "value": "oip"},
                    {"label": "Alle områder", "value": "ao"},
                ],
                value="oip",
                labelStyle={"display": "inline-block"},
            ),

   html.Div(
                [
                    dcc.DatePickerRange(
                        id="my-date-picker-range",  # ID to be used for callback
                        calendar_orientation="horizontal",  # vertical or horizontal
                        day_size=39,  # size of calendar image. Default is 39
                        end_date_placeholder_text="Return",  # text that appears when no end date chosen
                        with_portal=False,  # if True calendar will open in a full screen overlay portal
                        first_day_of_week=0,  # Display of calendar when open (0 = Sunday)
                        reopen_calendar_on_clear=True,
                        is_RTL=False,  # True or False for direction of calendar
                        clearable=True,  # whether or not the user can clear the dropdown
                        number_of_months_shown=1,  # number of months shown when calendar is open
                        min_date_allowed=df.ml_log_date.astype("datetime64[ns]").min().date() - timedelta(days=1),  # minimum date allowed on the DatePickerRange component
                        max_date_allowed=df.ml_log_date.astype("datetime64[ns]").max().date() + timedelta(days=1),  # maximum date allowed on the DatePickerRange component
                        initial_visible_month=df.ml_log_date.astype("datetime64[ns]").max().date(),  # the month initially presented when the user opens the calendar
                        start_date=df.ml_log_date.astype("datetime64[ns]").min().date(),
                        end_date=df.ml_log_date.astype("datetime64[ns]").max().date(),
                        display_format="D-M-Y",  # how selected dates are displayed in the DatePickerRange component.
                        month_format="MMMM, YYYY",  # how calendar headers are displayed when the calendar is opened.
                        minimum_nights=1,  # minimum number of days between start and end date
                        persistence=True,
                        persisted_props=["start_date"],
                        persistence_type="session",  # session, local, or memory. Default is 'local'
                        updatemode="singledate",  # singledate or bothdates. Determines when callback is triggered
                    ),
                   dcc.RadioItems(
                        id="timeperiod-radio",
                        options=[
                            {"label": "uge", "value": "uge"},
                            {"label": "dag", "value": "dag"},
                        ],
                        value="dag",
                        labelStyle={"display": "inline-block"},
                    ),
                    dcc.Graph(id="timeseries_predicted_count"),
                    html.Br(),
                    dcc.Graph(id="timeseries_change_share"),
                    html.Br(),
                    dcc.Graph(id="timeseries_predicted_timediff"),
                    html.Br(),
                    dcc.Graph(id="timeseries_blanks_selected"),
                ]
            ),
        ]
)

Callbacks

 @dash_app.callback(
        Output("timeseries_predicted_count", "figure"),
        [
            Input("my-date-picker-range", "start_date"),
            Input("my-date-picker-range", "end_date"),
            Input("area-radio", "value"),
            Input("timeperiod-radio", "value"),
        ],
    )
    def handled_pr_area(
        start_date, end_date, data_filter_value, date_filter_value
    ):
        if check_pickle():
            dff = prepare_pickle_df(df, start_date, end_date)
        if data_filter_value == "oip":
            dff = dff.loc[dff.ml_prediction != "empty_list"]
        else:
            dff = df.copy()
        periode = toggle_week_day(date_filter_value)
        dff = dff.groupby([periode, "Område"]).sum()
        dff.reset_index(inplace=True)
        dff_pivot = (
            dff.loc[:, [periode, "Område", "count"]]
            .pivot_table(
                index=periode, columns="Område", values=["count"], fill_value=0
            )
            .reset_index()
        ).round(1)
        dff_pivot.columns = [periode] + list(dff.Område.unique())
        print(dff_pivot.dtypes, dff_pivot)
        fig = px.line(
            dff_pivot,
            x=periode,
            y=dff_pivot.columns,
            labels=dict(value="antal", variable="område", date="tid", week_year="tid"),
            title="Håndterede emails",
        )
        fig.update_xaxes(dtick="D1", tickformat="%Y-%d-%m")

        return fig

    @dash_app.callback(
        Output("timeseries_change_share", "figure"),
        [
            Input("my-date-picker-range", "start_date"),
            Input("my-date-picker-range", "end_date"),
            Input("area-radio", "value"),
            Input("timeperiod-radio", "value"),
        ],
    )
    def handled_pr_area_change(
        start_date, end_date, data_filter_value, date_filter_value
    ):
        if check_pickle():
            dff = prepare_pickle_df(df, start_date, end_date)
        if data_filter_value == "oip":
            dff = dff.loc[dff.ml_prediction != "empty_list"]
        else:
            dff = df.copy()
        periode = toggle_week_day(date_filter_value)
        dff = dff.groupby([periode, "Område"]).sum()
        dff["andel"] = dff["Antal ændrede Email templates"] / dff["count"] * 100
        dff.reset_index(inplace=True)
        dff_pivot = (
            dff.loc[:, [periode, "Område", "andel"]]
            .pivot_table(
                index=periode, columns="Område", values=["andel"], fill_value=0
            )
            .reset_index()
        ).round(1)
        dff_pivot.columns = [periode] + list(dff.Område.unique())
        print(dff_pivot.dtypes, dff_pivot)
        fig = px.line(
            dff_pivot,
            x=periode,
            y=dff_pivot.columns,
            labels=dict(value="%", variable="område", date="tid", week_year="tid"),
            title="Email templates der er blevet ændret - andel",
        )
        fig.update_xaxes(dtick="D1", tickformat="%Y-%d-%m")

        return fig

    @dash_app.callback(
        Output("timeseries_predicted_timediff", "figure"),
        [
            Input("my-date-picker-range", "start_date"),
            Input("my-date-picker-range", "end_date"),
            Input("area-radio", "value"),
            Input("timeperiod-radio", "value"),
        ],
    )
    def time_avg_pr_area(
        start_date, end_date, data_filter_value, date_filter_value
    ):
        if check_pickle():
            dff = prepare_pickle_df(df, start_date, end_date)
        if data_filter_value == "oip":
            dff = dff.loc[dff.ml_prediction != "empty_list"]
        else:
            dff = df.copy()
        periode = toggle_week_day(date_filter_value)
        dff = dff.groupby([periode, "Område"]).mean().reset_index()
        dff_pivot = pd.pivot_table(
            dff,
            index=periode,
            columns="Område",
            values=["timediff_seconds"],
            fill_value=0,
        ).reset_index().round(1)
        dff_pivot.columns = [periode] + list(dff.Område.unique())
        print(dff_pivot.dtypes, dff_pivot)
        fig = px.line(
            dff_pivot,
            x=periode,
            y=dff_pivot.columns,
            labels=dict(
                value="sekunder", variable="område", date="tid", week_year="tid"
            ),
            title="Email håndtering - sekunder",
        )
        fig.update_xaxes(dtick="D1", tickformat="%Y-%d-%m")

        return fig

    @dash_app.callback(
        Output("timeseries_blanks_selected", "figure"),
        [
            Input("my-date-picker-range", "start_date"),
            Input("my-date-picker-range", "end_date"),
            Input("area-radio", "value"),
            Input("timeperiod-radio", "value"),
        ],
    )
    def blank_selected_pr_area_share(
        start_date, end_date, data_filter_value, date_filter_value
    ):
        if check_pickle():
            dff = prepare_pickle_df(df, start_date, end_date)
        if data_filter_value == "oip":
            dff = dff.loc[dff.ml_prediction != "empty_list"]
        else:
            dff = df.copy()
        periode = toggle_week_day(date_filter_value)
        dff_tom = dff.loc[dff.selectedTemplate == "tom"]
        dff_tom = dff_tom.groupby([periode, "Område"]).sum()
        dff_tom.reset_index(inplace=True)
        dff_pivot_tom = dff_tom.loc[:, [periode, "Område", "count"]].pivot_table(
            index=periode, columns="Område", values=["count"], fill_value=0
        )
        dff_alle = dff.copy()
        dff_alle = dff_alle.groupby([periode, "Område"]).sum()
        dff_alle.reset_index(inplace=True)
        dff_pivot_alle = dff_alle.loc[:, [periode, "Område", "count"]].pivot_table(
            index=periode, columns="Område", values=["count"], fill_value=0
        )
        dff_kombineret = ((dff_pivot_tom / dff_pivot_alle) * 100).fillna(0)
        dff_kombineret = dff_kombineret.reset_index().round(1)
        dff_kombineret.columns = [periode] + list(
            dff_alle.Område.sort_values().unique()
        )
        print(dff_kombineret.dtypes, dff_kombineret)
        fig = px.line(
            dff_kombineret,
            x=periode,
            y=dff_kombineret.columns,
            labels=dict(value="%", variable="område", date="tid", week_year="tid"),
            title="Blanke templates - andel",
        )

        fig.update_xaxes(dtick="D1", tickformat="%Y-%d-%m")

        return fig

you can use the prevent_initial_call flag to prevent the callbacks from triggering on page_load.

 @dash_app.callback(
        Output("timeseries_blanks_selected", "figure"),
        [
            Input("my-date-picker-range", "start_date"),
            Input("my-date-picker-range", "end_date"),
            Input("area-radio", "value"),
            Input("timeperiod-radio", "value"),
        ],
        prevent_initial_call = True
    )

Hi Matthijs

Thank you for the suggestion. The thing is i WANT the callbacks to be triggered on page load :slight_smile:

kind regards

So i figured out a solution. Seems like my problem was in the hierarchy of when callbacks was triggered. the 4 callbacks / graphs were i had the error (only 1 would show) appear to have been triggered simultaneously and for some reason that messed up the graphs.

I solved it by have 4 hidden divs:

            html.Div(id='hidden-value1', style={'display': 'none'}),
            html.Div(id='hidden-value2', style={'display': 'none'}),
            html.Div(id='hidden-value3', style={'display': 'none'}),
            html.Div(id='hidden-value4', style={'display': 'none'}),

And then have each callback output to the next as such:

   @dash_app.callback(
        Output("timediff_prediction", "figure"),
        Output('hidden-value1','children'),
        [
            Input("my-date-picker-range", "start_date"),
            Input("my-date-picker-range", "end_date"),
            Input("area-time-radio", "value"),
            Input("area-radio", "value"),
        ],
    )
def function():

 @dash_app.callback(
        Output("timeseries_predicted_count", "figure"),
        Output("hidden-value2", "children"),
        [
            Input("my-date-picker-range", "start_date"),
            Input("my-date-picker-range", "end_date"),
            Input("area-radio", "value"),
            Input("timeperiod-tab-one-radio", "value"),
            Input('hidden-value1','children')
        ],
    )
def function():

@dash_app.callback(
        Output("timeseries_change_share", "figure"),
        Output("hidden-value3", "children"),
        [
            Input("my-date-picker-range", "start_date"),
            Input("my-date-picker-range", "end_date"),
            Input("area-radio", "value"),
            Input("timeperiod-tab-two-radio", "value"),
            Input("hidden-value2", "children")
        ],
    )
def function():

... and so on

That created a much nicer sequence of callbacks being triggered than.