Why Prevent_initial_call does not stop the initial call?

Question:

I have the written code below. I have two dropdown menus that work based on chained callbacks. the first dropdown menu gets the datasets and reads the columns’ names and updates the options in the second dropdown menu. Then, the parameters can be plotted on the chart.
my dataframes look like this:

df={'col1':[12,15,25,33,26,33,39,17,28,25],
    'col2':[35,33,37,36,36,26,31,21,15,29],
    'col3':['A','A','A','A','B','B','B','B','B','B'],
    'col4':[1,2,3,4,5,6,7,8,9,10]

I want to highlight the chart background depending on the categories in col3. I don’t understand why when I select the dataset from the first dropdown menu the background color for col3 appears on the chart (before selecting the parameters). I have used Prevent_initial_call = True, but the second callback still triggers.

import dash
from dash import Dash, html, dcc, Output, Input, State, MATCH, ALL
import plotly.express as px
import pandas as pd
import numpy as np
import dash_bootstrap_components as dbc

app = Dash(__name__)

app.layout = html.Div([
    html.Div(children=[
        html.Button('add Chart', id='add-chart', n_clicks=0)
    ]),
    html.Div(id='container', children=[])
])

@app.callback(
    Output('container', 'children'),
    [Input('add-chart', 'n_clicks'),
    Input({'type': 'remove-btn', 'index': ALL}, 'n_clicks')],
    [State('container', 'children')],
    prevent_initial_call=True
)
def display_graphs(n_clicks, n, div_children):

    ctx = dash.callback_context
    triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]

    elm_in_div = len(div_children)

    if triggered_id == 'add-chart':
        new_child = html.Div(
            id={'type': 'div-num', 'index': elm_in_div},
            style={'width': '25%',
                   'display': 'inline-block',
                   'outline': 'none',
                   'padding': 5},
            children=[
                dbc.Container([
                    dbc.Row([
                        dbc.Col([dcc.Dropdown(id={'type': 'dataset-choice', 'index': n_clicks},
                                        options=['dataset1'],
                                        clearable=True,
                                        value=[]
                                )], width=6),
                        dbc.Col([dcc.Dropdown(id={'type': 'feature-choice', 'index': n_clicks},
                                              options=[],
                                              multi=True,
                                              clearable=True,
                                              value=[]
                                )], width=6)
                    ]),
                    dbc.Row([
                        dbc.Col([dcc.Graph(id={'type': 'dynamic-graph','index': n_clicks},
                                           figure={}
                                )])
                    ]),
                    dbc.Row([
                        dbc.Col([html.Button("Remove", id={'type': 'remove-btn', 'index': elm_in_div})
                        ])
                    ]),
                ])
            ]
        )
        div_children.append(new_child)
        return div_children

    if triggered_id != 'add-chart':
        for idx, val in enumerate(n):
            if val is not None:
                del div_children[idx]
                return div_children


@app.callback(
    Output({'type': 'feature-choice', 'index': MATCH}, 'options'),
    [Input({'type': 'dataset-choice', 'index': MATCH}, 'value')],
    prevent_initial_call=True
)
def set_dataset_options(chosen_dataset):
    if chosen_dataset is None:
        return dash.no_update
    else:
        path = 'C:/Users/pymnb/OneDrive/Desktop/test/'
        df = pd.read_csv(path + chosen_dataset+'.csv')
        features = df.columns.values[0:2]
        return features


@app.callback(
    Output({'type': 'dynamic-graph', 'index': MATCH}, 'figure'),
    [Input({'type': 'dataset-choice', 'index': MATCH}, 'value'),
     Input({'type': 'feature-choice', 'index': MATCH}, 'value')],
    prevent_initial_call=True
)
def update_graph(chosen_dataset1, chosen_feature):
    if chosen_feature is None:
        return dash.no_update
    if chosen_dataset1 is None:
        return dash.no_update

    path = 'C:/Users/pymnb/OneDrive/Desktop/test/'
    df = pd.read_csv(path + chosen_dataset1+'.csv')

    Xmin = df[chosen_feature].min().min()
    print(Xmin)
    Xmax = df[chosen_feature].max().max()

    # to find the height of y-axis(col4)
    col4_max = df['col4'].max()
    col4_min = df['col4'].min()

    fig1 = px.line(df, x=chosen_feature, y='col4')
    fig1.update_layout({'height': 600,
                'legend': {'title': '', 'x': 0, 'y': 1.06, 'orientation': 'h'},
                'margin': {'l': 0, 'r': 20, 't': 50, 'b': 0},
                'paper_bgcolor': 'black',
                'plot_bgcolor': 'white',
                }
    )

    fig1.update_yaxes(range=[col4_max, col4_min], showgrid=False)
    fig1.update_xaxes(showgrid=False)

    categ_col3 = df.col3.dropna().unique()
    colors = ['#54FF9F', '#87CEFF']

    for (i,j) in zip(categ_col3, colors):

        index_min = df.loc[df.col3 == i].index[0]
        index_max = df.loc[df.col3 == i].index[-1]
        if index_min == 0:
            cat_min = df['col4'][index_min]
        else:
            cat_min = df['col4'][index_min-1]
        cat_max = df['col4'][index_max]

        fig1.add_shape(type="rect", x0=Xmin, y0=cat_min, x1=Xmax, y1=cat_max,
                       fillcolor=j, layer='below', opacity=0.5,
                       )
    return fig1

if __name__ == '__main__':
    app.run_server(debug=True)
Asked By: pymn

||

Answers:

You can fix it by modifying your code to the following:

@app.callback(
    Output({'type': 'dynamic-graph', 'index': MATCH}, 'figure'),
    [Input({'type': 'dataset-choice', 'index': MATCH}, 'value'),
     Input({'type': 'feature-choice', 'index': MATCH}, 'value')],
    prevent_initial_call=True
)
def update_graph(chosen_dataset1, chosen_feature):
    if (chosen_feature == []) or (chosen_dataset1 is None): #<--- correct the condition
        return dash.no_update
    else:  #<---- add the else condition to prevent any update
        Xmin = df[chosen_feature].min().min()
        Xmax = df[chosen_feature].max().max()

The reason behind that because all the elements are created on fly and they are not within the app.layout. Please read the following from the documentation:

In other words, if the output of the callback is already present in
the app layout before its input is inserted into the layout,
prevent_initial_call will not prevent its execution when the input is
first inserted into the layout.

Answered By: Hamzah
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.