✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
⚡️ Concerned about the grid? Kyle Baranko teaches how to predicting peak loads using XGBoost. Register for the August webinar!

passing options with patter-matching callbacks

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)`