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)