Black Lives Matter. Please consider donating to Black Girls Code today.

Callbacks not being called [Help please]

Hi,
I am trying to create an app in which I display 3 dropdowns, and 3 datatables. The first dropdown represents an Area of knowledge, then each area has different Groups (2nd dropdown) and each group has different Subgroups (3rd dropdown). Then, the Subgroups contain a list of Indicators (1st datatable) which have fields as Years and Territories that I use to create Filters (2nd and 3rd datatables).

The initial state of the app is showing only the 3 dropdowns and the behavior should be that the user selects the Area, which fills up the options available in Group and when a Group is selected, the selection fills up the options in the Subgroup. Then, when the Subgroup dropdown has a value different to “False” the Indicators’ datatable is showed, then if a row in that table is selected the Years and Territories datatable are shown.

I tried to program the callbacks to be on cascade to reset and/or hide the dropdowns and datatables when the component of higher hierarchy is changed. For instance, if an Area is changed the Group will be set to “False”, due to it that should put to “False” the Subgroup dropdown to “False” which would make the div of the Indicators datatable hide, which, consecuently, would make that the div of the Years’ and Territories’ datatable hide. If the value of the Subgroup is changed, the Indicators table would be regenerated and the filters would hide.

The problem

The problem I found is that whenever I change an Area the cascade works fine until the moment of hiding the Filters datatables, when that callback should be called, it simply it isn’t. When I change Group, it happens the same as with Area, BUT when I change the Subgroup, the callback to hide the filters is called after hiding the Indicators datatable.

Any ideas to fix this?

The anomaly
I found an anomaly that when I only have the callbacks with the methods called “reset_group” and “reset_subgrup”, when the Area is changed, “reset_group” is called to set the Group to False, and as the Group’s value is changed the “reset_subgroup” is called as it is to be expected. However, when I have all the callbacks coded, the behavour changes and when I do the same action “reset_group” is called but then “reset_subgroup” is not.

Code
Here I leave my code:

import pandas as pd
import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
import dash_table_experiments as dte
import MySQLdb
import os
import glob
import flask
from flask import send_from_directory
import gettext
from operator import itemgetter


example_data = [
    {"area":"area1","group":"group1","subgroup":"subgroup1","indicator":"A","year":2010,"territory":"London"},
    {"area":"area1","group":"group1","subgroup":"subgroup1","indicator":"B","year":2011,"territory":"Warsaw"},
    {"area": "area1", "group": "group1", "subgroup": "subgroup2", "indicator": "C", "year": 2010,
     "territory": "Dublin"},
    {"area": "area1", "group": "group1", "subgroup": "subgroup2", "indicator": "D", "year": 2011,
     "territory": "Warsaw"},
    {"area": "area1", "group": "group2", "subgroup": "subgroup3", "indicator": "E", "year": 2010,
     "territory": "Dublin"},
    {"area": "area1", "group": "group2", "subgroup": "subgroup3", "indicator": "F", "year": 2011,
     "territory": "Warsaw"},
    {"area": "area1", "group": "group2", "subgroup": "subgroup4", "indicator": "G", "year": 2010,
     "territory": "Madrid"},
    {"area": "area1", "group": "group2", "subgroup": "subgroup4", "indicator": "H", "year": 2014,
     "territory": "Moscu"},
    {"area": "area2", "group": "group3", "subgroup": "subgroup5", "indicator": "I", "year": 2010,
     "territory": "Berlin"},
    {"area": "area2", "group": "group3", "subgroup": "subgroup5", "indicator": "J", "year": 2011,
     "territory": "Warsaw"},
    {"area": "area2", "group": "group3", "subgroup": "subgroup6", "indicator": "K", "year": 2013,
     "territory": "Dublin"},
    {"area": "area2", "group": "group3", "subgroup": "subgroup6", "indicator": "L", "year": 2011,
     "territory": "Moscu"},    
    {"area": "area2", "group": "group4", "subgroup": "subgroup7", "indicator": "M", "year": 2010,
     "territory": "New York"},
    {"area": "area2", "group": "group4", "subgroup": "subgroup7", "indicator": "N", "year": 2014,
     "territory": "Warsaw"},
    {"area": "area2", "group": "group4", "subgroup": "subgroup8", "indicator": "O", "year": 2010,
     "territory": "York"},
    {"area": "area2", "group": "group4", "subgroup": "subgroup8", "indicator": "P", "year": 2031,
     "territory": "Malmo"},
]
df_values = pd.DataFrame(example_data)
df_values = df_values.sort_values("area", ascending=False)
area_list = [{'label': el, 'value': el} for el in df_values["area"].unique()]

app = dash.Dash()
# app.css.append_css({"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})
app.css.config.serve_locally = True

app.layout = html.Div(id='global', className='container', children=[
    html.Div(className='row place-holder', children=[
        html.Link(href='static/css/skeleton.css', rel='stylesheet'),
    ]),
    html.Div(id='elements', className='row', style={"display": "flex"}, children=[
        html.Div(id="left_column", style={"width": "45%"}, children=[
            html.Div(className='row', children=[
                html.Label(id='area_label', style={"margin-top": "2%"}, children="Area:"),
            ]),
            html.Div(className='row', children=[
                dcc.Dropdown(
                    id='area_dropdown',
                    options=area_list,
                    placeholder="Seleccione un Área",
                    className='u-full-width',
                    disabled=False,
                    clearable=False
                ),
            ]),
            html.Div(className='row', children=[
                html.Label(id='group_label', style={"margin-top": "2%"}, children="Group:"),
            ]),
            html.Div(className='row', children=[
                dcc.Dropdown(
                    id='group_dropdown',
                    options=[],
                    placeholder="Seleccione un Grupo",
                    disabled=False,
                    clearable=False
                ),
            ]),
            html.Div(className='row', children=[
                html.Label(id='subgroup_label', style={"margin-top": "2%"}, children="Subgroup:"),
            ]),
            html.Div(className='row', children=[
                dcc.Dropdown(
                    id='subgroup_dropdown',
                    options=[],
                    placeholder="Seleccione un Subgrupo",
                    disabled=False,
                    clearable=False
                ),
            ]),
            html.Div(className='row', children=[
                html.Label(id='indicators_label', style={"margin-top": "2%"}, children="Indicators:"),
            ]),

            # place-holder until the Indicators table is created
            html.Div(id="indicators_place_holder_div", className='place-holder',
                     style={"margin-top": "2%", "padding": "25% 20%"},
                     children=[
                         "Select an Area, a Group and a Subgroup" +
                         "to display the Indicators"
                     ]),

            # Aqui poner solo indicadores
            html.Div(className='row', children=[
                html.Div(id='indicators_div', style={"display": "none"}, children=
                dte.DataTable(
                    rows=[{}],
                    row_selectable=True,
                    selected_row_indices=[],
                    filterable=False,
                    # min_width=500,
                    column_widths=[600],
                    id='indicators_table'
                ),
                ),
            ]),

            html.Div(className='row', children=[
                html.Div(id='indicators_es_div', style={'display': 'none'}, className='twelve columns', children=[
                    dte.DataTable(
                        rows=[{}],
                        row_selectable=True,
                        selected_row_indices=[],
                        id='indicators_table_es'
                    ),
                ]),
            ]),
        ]),
        html.Div(id="right_column", style={"width": "55%", "margin-left": "3%"}, children=[
            html.Div(className='row', children=[
                html.Label(id='filters_label', style={"margin-top": "2%"}, children="Filters:"),
                ]),
            html.Div(id="filters_place_holder_div", className='place-holder',
                     style={"margin-top": "2%", "padding": "25% 20%"},
                     children=[
                         "Select an Area, a Group, a Subgroup and one or more Indicators" +
                         "to display the available Filters"
                     ]),
            html.Div(id="filters_div", style={'display': 'none'}, className='row', children=[
                html.Div(id='intermediate_div', style={'display': 'flex'}, children=[
                    html.Div(id='years_div', style={'width': '30%'}, children=[
                        dte.DataTable(
                            rows=[{}],
                            row_selectable=True,
                            selected_row_indices=[],
                            id='years_table',
                        ),
                    ]),
                    html.Div(id='territories_div', style={"margin-left": "5%", 'width': '65%'}, children=[
                        dte.DataTable(
                            rows=[{}],
                            row_selectable=True,
                            selected_row_indices=[],
                            id='territories_table',
                            column_widths=[300]
                        ),
                    ]),
                ]),
            ]),
        ]),
    ]),
    html.Div(id='df_store_div', className='row', style={'display': 'none'}),
])


@app.server.route('/static/<path:path>')
def static_file(path):
    """
    Enables the load of local css
    """
    static_folder = os.path.join(os.getcwd(), 'static')
    return send_from_directory(static_folder, path)

# ----------Dropdowns----------
@app.callback(Output(component_id='group_dropdown', component_property='value'),
              [Input(component_id='area_dropdown', component_property='value')])
def reset_group_value(area_value):
    if area_value:
        return False


@app.callback(Output(component_id='subgroup_dropdown', component_property='value'),
              [Input(component_id='group_dropdown', component_property='value')])
def reset_subgroup_value(group_dropdown):
    if group_dropdown:
        return False


@app.callback(Output(component_id='group_dropdown', component_property='options'),
              [Input(component_id='area_dropdown', component_property='value')])
def update_group_dropdown(area_value):
    df_area = df_values[df_values["area"] == area_value]
    group_list = [{'label': el, 'value': el} for el in df_area["group"].unique()]
    return group_list


@app.callback(Output(component_id='subgroup_dropdown', component_property='options'),
              [Input(component_id='group_dropdown', component_property='value')],
              [State(component_id='area_dropdown', component_property='value')])
def update_subgroup_dropdown(group_value, area_value):
    print("update_subgroup_options")
    df_area = df_values[df_values["area"] == area_value]
    df_group = df_area[df_area["group"] == group_value]
    subgroup_list = [{'label': el, 'value': el} for el in df_group["subgroup"].unique()]
    return subgroup_list


# -----Indicators----
@app.callback(Output(component_id='indicators_place_holder_div', component_property='style'),
              [Input(component_id='subgroup_dropdown', component_property='value')])
def display_hide_indicators_place_holder(subgroup_value):
    if subgroup_value:
        return {'display': 'none'}
    else:
        return {"margin-top": "2%", "padding": "25% 20%"}


@app.callback(Output(component_id='indicators_div', component_property='style'),
              [Input(component_id='subgroup_dropdown', component_property='value')])
def display_hide_indicators_table(subgroup_value):
    if subgroup_value:
        return {"margin-top": "2%", "margin-bottom": "5%", 'display': 'block'}
    else:
        return {'display': 'none'}


@app.callback(Output(component_id='indicators_table', component_property='selected_row_indices'),
              [Input(component_id='subgroup_dropdown', component_property='value')])
def reset_indicators_table(subgroup_value):
    return []


@app.callback(Output(component_id='indicators_table', component_property='rows'),
              [Input(component_id='subgroup_dropdown', component_property='value')],
              [State(component_id='area_dropdown', component_property='value'),
           State(component_id='group_dropdown', component_property='value'), ])
def update_indicators_table(subgroup_value, area_value, group_value):
    if subgroup_value:
        df_area = df_values[df_values["area"] == area_value]
        df_group = df_area[df_area["group"] == group_value]
        df_subgroup = df_area[df_area["subgroup"] == subgroup_value]
        indicator_list = [{"Indicators": el} for el in df_subgroup["indicator"].unique()]
        return indicator_list


@app.callback(Output(component_id='indicators_table_es', component_property='rows'),
              [Input(component_id='subgroup_dropdown', component_property='value')],
              [State(component_id='area_dropdown', component_property='value'),
               State(component_id='group_dropdown', component_property='value'), ])
def update_indicators_table_es(subgroup_value, area_value, group_value):
    """
    I update an auxiliary table for future features
    """
    if subgroup_value:
        df_area = df_values[df_values["area"] == area_value]
        df_group = df_area[df_area["group"] == group_value]
        df_subgroup = df_area[df_area["subgroup"] == subgroup_value]
        indicator_list = [{"Indicators": el} for el in df_subgroup["indicator"].unique()]
        return indicator_list


# -----Filters----
@app.callback(Output(component_id='filters_place_holder_div', component_property='style'),
              [Input(component_id='indicators_table', component_property='selected_row_indices')])
def display_hide_filter_place_holder(indicators_selected_row_indices):
    if indicators_selected_row_indices:
        return {'display': 'none'}
    else:
        return {"margin-top": "2%", "padding": "25% 20%"}


@app.callback(Output(component_id='filters_div', component_property='style'),
              [Input(component_id='indicators_table', component_property='selected_row_indices')])
def display_hide_filter_tables(indicators_selected_indices):
    if indicators_selected_indices:
        return {"margin-top": "2%", "margin-bottom": "5%"}
    else:
        return {'display': 'none'}

    # ----Filter Years---
@app.callback(Output('years_table', 'rows'),
              [Input('indicators_table', 'selected_row_indices')],
              [State('df_store_div', 'children'), State('indicators_table_es', 'rows')])
def update_years_table(indicators_selected_row_indices, df_subgroup_json, indicator_rows):
    if len(indicators_selected_row_indices) > 0:
        indicator_list = [indicator_rows[index]["Indicators"]
                          for index in indicators_selected_row_indices]
        df_subgroup = pd.read_json(df_subgroup_json)
        df_indicator = df_subgroup[df_subgroup['indicator'].isin(indicator_list)]

        year_list = []
        for indicator in indicator_list:
            df_fraction = df_indicator[df_indicator["indicator"] == indicator]
            for year in df_fraction["year"].unique():
                year_dict = {"Years": year}
                if year_dict not in year_list:
                    year_list.append(year_dict)
        return sorted(year_list, key=itemgetter('Years'))

    # ----Filter Territories----
@app.callback(Output('territories_table', 'rows'),
              [Input('indicators_table', 'selected_row_indices')],
              [State('df_store_div', 'children'), State('indicators_table_es', 'rows')])
def update_territories_table(indicators_selected_row_indices, df_subgroup_json, indicator_rows):
    if len(indicators_selected_row_indices) > 0:
        indicator_list = [indicator_rows[index]["Indicators"]
                          for index in indicators_selected_row_indices]
        df_subgroup = pd.read_json(df_subgroup_json)
        df_indicator = df_subgroup[df_subgroup['indicator'].isin(indicator_list)]

        territory_list = []
        for indicator in indicator_list:
                df_fraction = df_indicator[df_indicator["indicator"] == indicator]
            for territory in df_fraction["territory"].unique():
                territory_dict = {"Territories": territory}
                if territory_dict not in territory_list:
                    territory_list.append(territory_dict)
        return sorted(territory_list, key=itemgetter('Territories'))


# ------------Miscelanea------
@app.callback(Output('df_store_div', 'children'),
              [Input('subgroup_dropdown', 'value')],
              [State('area_dropdown', 'value'), State('group_dropdown', 'value')])
def store_final_df(subgroup_value, area_value, group_value):
    """
    I store the remaining data from the selected Area, Group and Subgroup
    """
    if (subgroup_value != None):
        df_area = df_values[df_values["area"] == area_value]
        df_group = df_area[df_area["group"] == group_value]
        df_subgroup = df_area[df_area["subgroup"] == subgroup_value]
        return df_subgroup.to_json()



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

@chriddyp could you give me a hand? Thank you in advance.

Could you simplify your example until it demonstrates the issue with minimal extra code?

Here it is the necessary code to see the issue:

import pandas as pd
import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
import dash_table_experiments as dte

example_data = [
    {"area":"area1","group":"group1","subgroup":"subgroup1","indicator":"A","year":2010,"territory":"London"},
    {"area":"area1","group":"group1","subgroup":"subgroup1","indicator":"B","year":2011,"territory":"Warsaw"},
    {"area": "area1", "group": "group1", "subgroup": "subgroup2", "indicator": "C", "year": 2010,
     "territory": "Dublin"},
    {"area": "area1", "group": "group1", "subgroup": "subgroup2", "indicator": "D", "year": 2011,
     "territory": "Warsaw"},
    {"area": "area1", "group": "group2", "subgroup": "subgroup3", "indicator": "E", "year": 2010,
     "territory": "Dublin"},
    {"area": "area1", "group": "group2", "subgroup": "subgroup3", "indicator": "F", "year": 2011,
     "territory": "Warsaw"},
    {"area": "area1", "group": "group2", "subgroup": "subgroup4", "indicator": "G", "year": 2010,
     "territory": "Madrid"},
    {"area": "area1", "group": "group2", "subgroup": "subgroup4", "indicator": "H", "year": 2014,
     "territory": "Moscu"},
    {"area": "area2", "group": "group3", "subgroup": "subgroup5", "indicator": "I", "year": 2010,
     "territory": "Berlin"},
    {"area": "area2", "group": "group3", "subgroup": "subgroup5", "indicator": "J", "year": 2011,
     "territory": "Warsaw"},
    {"area": "area2", "group": "group3", "subgroup": "subgroup6", "indicator": "K", "year": 2013,
     "territory": "Dublin"},
    {"area": "area2", "group": "group3", "subgroup": "subgroup6", "indicator": "L", "year": 2011,
     "territory": "Moscu"},
    {"area": "area2", "group": "group4", "subgroup": "subgroup7", "indicator": "M", "year": 2010,
     "territory": "New York"},
    {"area": "area2", "group": "group4", "subgroup": "subgroup7", "indicator": "N", "year": 2014,
     "territory": "Warsaw"},
    {"area": "area2", "group": "group4", "subgroup": "subgroup8", "indicator": "O", "year": 2010,
     "territory": "York"},
    {"area": "area2", "group": "group4", "subgroup": "subgroup8", "indicator": "P", "year": 2031,
     "territory": "Malmo"},
]
df_values = pd.DataFrame(example_data)
df_values = df_values.sort_values("area", ascending=False)
area_list = [{'label': el, 'value': el} for el in df_values["area"].unique()]

app = dash.Dash()

app.layout = html.Div(id='global', className='container', children=[
    html.Div(id='elements', className='row', style={"display": "flex"}, children=[
        html.Div(id="left_column", style={"width": "45%"}, children=[
            html.Div(className='row', children=[
                html.Label(id='area_label', style={"margin-top": "2%"}, children="Area:"),
            ]),
            html.Div(className='row', children=[
                dcc.Dropdown(
                    id='area_dropdown',
                    options=area_list,
                    placeholder="Select an Area",
                    className='u-full-width',
                    disabled=False,
                    clearable=False
                ),
            ]),
            html.Div(className='row', children=[
                html.Label(id='group_label', style={"margin-top": "2%"}, children="Group:"),
            ]),
            html.Div(className='row', children=[
                dcc.Dropdown(
                    id='group_dropdown',
                    options=[],
                    placeholder="Select a Group",
                    disabled=False,
                    clearable=False
                ),
            ]),
            html.Div(className='row', children=[
                html.Label(id='subgroup_label', style={"margin-top": "2%"}, children="Subgroup:"),
            ]),
            html.Div(className='row', children=[
                dcc.Dropdown(
                    id='subgroup_dropdown',
                    options=[],
                    placeholder="Select a Subgroup",
                    disabled=False,
                    clearable=False
                ),
            ]),
            html.Div(className='row', children=[
                html.Label(id='indicators_label', style={"margin-top": "2%"}, children="Indicators:"),
            ]),
            html.Div(className='row', children=[
                html.Div(id='indicators_div', style={"display": "none"}, children=
                dte.DataTable(
                    rows=[{}],
                    row_selectable=True,
                    selected_row_indices=[],
                    filterable=False,
                    column_widths=[600],
                    id='indicators_table'
                ),
                ),
            ]),
            html.Div(className='row', children=[
                html.Div(id='indicators_es_div', style={'display': 'none'}, className='twelve columns', children=[
                    dte.DataTable(
                        rows=[{}],
                        row_selectable=True,
                        selected_row_indices=[],
                        id='indicators_table_es'
                    ),
                ]),
            ]),
        ]),
        html.Div(id="right_column", style={"width": "55%", "margin-left": "3%"}, children=[
            html.Div(className='row', children=[
                html.Label(id='filters_label', style={"margin-top": "2%"}, children="Filters:"),
                ]),
            html.Div(id="filters_div", style={'display': 'none'}, className='row', children=[
                html.Div(id='intermediate_div', style={'display': 'flex'}, children=[
                    html.Div(id='years_div', style={'width': '30%'}, children=[
                        dte.DataTable(
                            rows=[{}],
                            row_selectable=True,
                            selected_row_indices=[],
                            id='years_table',
                        ),
                    ]),
                ]),
            ]),
        ]),
    ]),
])

# ----------Dropdowns----------
@app.callback(Output(component_id='group_dropdown', component_property='value'),
              [Input(component_id='area_dropdown', component_property='value')])
def reset_group_value(area_value):
    if area_value:
        return False


@app.callback(Output(component_id='subgroup_dropdown', component_property='value'),
              [Input(component_id='area_dropdown', component_property='value'),
               Input(component_id='group_dropdown', component_property='value')])
def reset_subgroup_value(area_dropdown,group_dropdown):
    if group_dropdown or area_dropdown:
        return False


@app.callback(Output(component_id='group_dropdown', component_property='options'),
              [Input(component_id='area_dropdown', component_property='value')])
def update_group_dropdown(area_value):
    df_area = df_values[df_values["area"] == area_value]
    group_list = [{'label': el, 'value': el} for el in df_area["group"].unique()]
    return group_list


@app.callback(Output(component_id='subgroup_dropdown', component_property='options'),
              [Input(component_id='group_dropdown', component_property='value')],
              [State(component_id='area_dropdown', component_property='value')])
def update_subgroup_dropdown(group_value, area_value):
    print("update_subgroup_options")
    df_area = df_values[df_values["area"] == area_value]
    df_group = df_area[df_area["group"] == group_value]
    subgroup_list = [{'label': el, 'value': el} for el in df_group["subgroup"].unique()]
    return subgroup_list


# -----Indicators----
@app.callback(Output(component_id='indicators_div', component_property='style'),
              [Input(component_id='subgroup_dropdown', component_property='value')])
def display_hide_indicators_table(subgroup_value):
    if subgroup_value:
        return {"margin-top": "2%", "margin-bottom": "5%", 'display': 'block'}
    else:
        return {'display': 'none'}


@app.callback(Output(component_id='indicators_table', component_property='selected_row_indices'),
              [Input(component_id='subgroup_dropdown', component_property='value')])
def reset_indicators_table(subgroup_value):
    return []


@app.callback(Output(component_id='indicators_table', component_property='rows'),
              [Input(component_id='subgroup_dropdown', component_property='value')],
              [State(component_id='area_dropdown', component_property='value'),
           State(component_id='group_dropdown', component_property='value'), ])
def update_indicators_table(subgroup_value, area_value, group_value):
    if subgroup_value:
        df_area = df_values[df_values["area"] == area_value]
        df_group = df_area[df_area["group"] == group_value]
        df_subgroup = df_area[df_area["subgroup"] == subgroup_value]
        indicator_list = [{"Indicators": el} for el in df_subgroup["indicator"].unique()]
        return indicator_list


# -----Filters----
@app.callback(Output(component_id='filters_div', component_property='style'),
              [Input(component_id='indicators_table', component_property='selected_row_indices')])
def display_hide_filter_tables(indicators_selected_indices):
    if indicators_selected_indices:
        return {"margin-top": "2%", "margin-bottom": "5%"}
    else:
        return {'display': 'none'}



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

The main problem I see when putting breakpoints is that the last function (display_hide_filter_tables) is not being called once I change the “Area” or “Group” dropdowns and the Filter table is displayed.

Thank you for the help