I`m building complex Dash app. Main feature is highly customizable dashboard.
For this purpose I need to make filters for dataset which will pass to plotly.px object.
The goal I currently trying to achieve is delete option point from all other dropdowns if it was chosen in any other dropdown. The problem covered in comments in the code provided (lines 150-190)
How to make this last callback working as I expected?
`# -- coding: utf-8 --
import json
import numpy as np
import pandas as pd
import dash
import dash_core_components as dcc
import dash_html_components as html_c
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State, ALL, MATCH
sample DataFrame
df = pd.DataFrame({‘field_1’: [‘A’, ‘B’, ‘C’], ‘field_2’: [‘1’, ‘2’, ‘3’], ‘field_3’: [‘I’, ‘II’, ‘III’]})
dict for conver pd.DataFrame columns to objects dash can working with
col_to_label=
{‘field_1’:‘associated_human_readable_val_1’,
‘field_2’:‘associated_human_readable_val_2’,
‘field_3’:‘associated_human_readable_val_3’}
reverse dict
label_to_col = {v: k for k, v in col_to_label.items()}
make app
app = dash.Dash(name, external_stylesheets=[dbc.themes.BOOTSTRAP])
build init layout for adding filters
app.layout = html_c.Div(
[
dbc.Button(‘add filter’,
id = {‘type’: ‘add_filter_button’, ‘index’: ‘custom_bar_1_sidebar’},
n_clicks=0, className=“mar-1”, color=“warning”, size=“sm”, style={“margin”:“5px”}),
html_c.Div(
[
# content will passed here from callback
], id=“filter_container_custom_bar_1_sidebar”)
])
callback for dynamically filters generation
# working well
@app.callback(
Output(‘filter_container_custom_bar_1_sidebar’, ‘children’),
[Input({‘type’: ‘add_filter_button’, ‘index’: ‘custom_bar_1_sidebar’}, ‘n_clicks’),
Input({‘type’: ‘remove_filter_btt’, ‘index’: ALL}, ‘n_clicks’)],
[State(‘filter_container_custom_bar_1_sidebar’, ‘children’)]
, prevent_initial_call=True)
def make_filter(n_clicks_add, n_clicks_remove, children):
# print('\nn_clicks_remove: ', n_clicks_remove)
# print('make_filter n_clicks_add: ', n_clicks_add)
# print('make_filter callback_context.triggered: ', dash.callback_context.triggered)
# print('make_filter callback_context.inputs: ', dash.callback_context.inputs, end='\n\n')
triggered = [t["prop_id"] for t in dash.callback_context.triggered]
remove = len([1 for i in triggered if "remove_filter_btt" in i]) # check if 'remove' btt was pressed
if not remove:
# if it`s not removing it can be only adding
# so we generating children
new_dropdown = \
dbc.Row(
[
dbc.Col( # field dropdown (columns in df)
[
dcc.Dropdown(
id = {'type': 'filter_dropdown_custom_bar_1_sidebar_key', 'index': n_clicks_add},
multi = False
,optionHeight=40
,style = {'cursor':'pointer'}
,options = [{'label': k, 'value': v} for v, k in col_to_label.items()]
)
], className="col-md-4 col-lg-4", style = {'padding':'0', 'margin-left':'20px'}),
dbc.Col( # values associated with field (unique values in df column)
[
dcc.Dropdown(
id = {'type': 'filter_dropdown_custom_bar_1_sidebar_val', 'index': n_clicks_add},
multi = True
,optionHeight=40
,style = {'padding':'0', 'cursor':'pointer'})
], className="col-md-4 col-lg-4", style = {'padding':'0', 'margin-left':'5px'}),
dbc.Col( # "romove filter" button
[
dbc.Button(
'X',
id = {'type': 'remove_filter_btt', 'index': n_clicks_add},
className="mar-1",
color="danger",
size="sm"
, style={'padding':'5px', 'padding-left':'8px', 'padding-right':'8px', 'margin-up': '3px', 'margin-right': '3px'}
)
], className="col-md-2 col-lg-2", style={'margin-right':'16px', 'margin-top':'2px', 'padding-left':'13px'})
,html_c.Div(id = 'technical_div')
,html_c.Div(id = {'type': 'another_technical_div', 'index': n_clicks_add})
], id = {'type': 'filter_row', 'index': n_clicks_add})
children.append(new_dropdown)
else:
# deletion of arbitrary filter
children_ind = 0
callback_ind_to_find = json.loads(triggered[0].split('.')[0])['index'] # id of triggered button
for row_i in range(len(children)): # looking for that id in current children
if int(children[row_i]['props']['children'][2]['props']['children'][0]['props']['id']['index']) == int(callback_ind_to_find):
ind_to_del = children_ind
break # if founded - pass to ind_to_del and end for loop
children_ind += 1
del children[ind_to_del] # deleting particular children obj by index
return children
pass options associated with chosen filter
# working perfectly fine
@app.callback(
[Output({‘type’: ‘filter_dropdown_custom_bar_1_sidebar_val’, ‘index’: MATCH}, ‘options’)],
[Input({‘type’: ‘filter_dropdown_custom_bar_1_sidebar_key’, ‘index’: MATCH}, ‘value’)],
[State({‘type’: ‘filter_row’, ‘index’: MATCH}, ‘children’)]
, prevent_initial_call=True
)
def make_filter_work_again(key_to_filter, current_row):
# print('make_filter_work_again callback_context.inputs: ', dash.callback_context.inputs, end='\n\n')
# print('\ncurrent_row', current_row, '\n')
options_for_val = [[{'label': i, 'value': i} for i in sorted(df[key_to_filter].unique())]]
return options_for_val
pass current dbc.Row to global variable so we can compare it in next callback
# stupid workaround
@app.callback(
[Output(‘technical_div’, ‘n_clicks’)], # div with id ‘technical_div’ initializing arbitrary number of times so it`s causing errors, which is not the case. in this callback I need only data from Input
[Input({‘type’: ‘filter_dropdown_custom_bar_1_sidebar_key’, ‘index’: ALL}, ‘value’)],
[State({‘type’: ‘filter_row’, ‘index’: ALL}, ‘children’)]
, prevent_initial_call=True
)
def pass_keys_to_other_filters(ff, current_row):
global all_rows
all_rows = current_row
return [np.random.randint(100000000)] # we dont care about what to pass in output
update options when field chosed
# NOT WORKING AT ALL
@app.callback(
[Output({‘type’: ‘another_technical_div’, ‘index’: MATCH}, ‘n_clicks’)],
[Input({‘type’: ‘filter_dropdown_custom_bar_1_sidebar_key’, ‘index’: MATCH}, ‘value’)],
[State({‘type’: ‘filter_row’, ‘index’: MATCH}, ‘children’)]
, prevent_initial_call=True
)
def otday_moy_row(key_to_filter, current_row, initial_options=label_to_col):
print('\notday_moy_row all_rows: ', all_rows, end='\n')
# print('\notday_moy_row fired in: ', datetime.datetime.now(), '\n')
# print('\notday_moy_row callback_context.inputs: ', dash.callback_context.inputs, '\n')
# print('\notday_moy_row callback_context.states: ', dash.callback_context.states, '\n')
triggered_index = json.loads(list(dash.callback_context.states.keys())[0].split('.')[0])['index'] - 1
# print('\ntriggered_index', triggered_index, '\n')
val_to_exclude = all_rows[triggered_index][0]['props']['children'][0]['props']['value']
indxes_to_change = [i for i in range(len(all_rows)) if i != triggered_index]
options_to_pass = [{'label': k, 'value': v} for v, k in col_to_label.items() if v != val_to_exclude]
# print('\noptions_to_pass', options_to_pass, '\n')
def do_something_cat(triggered_index=triggered_index, val_to_exclude=val_to_exclude):
# print('\ndo_something_cat: ', triggered_index, type(triggered_index), val_to_exclude, type(val_to_exclude))
return [options_to_pass]
# I tryed to pass otions in for loop by index
# but there is no filter_dropdown_custom_bar_1_sidebar_key fiering event
# so its not working
for idx in indxes_to_change:
app.callback(
[Output({'type': 'filter_dropdown_custom_bar_1_sidebar_key', 'index': idx}, 'optinons')],
[Input({'type': 'filter_dropdown_custom_bar_1_sidebar_key', 'index': ALL}, 'value')]
,prevent_initial_call=True)(do_something_cat())
return [1] # here we dont mind about output too. ACTUAL Output() in above (lines 186-190)
if name == ‘main’:
app.run_server(debug=True)`
1 post - 1 participant