Dynamic dropdown filtering subordinate dropdown also created by pattern-matching callback

Hi Community,

my app should give to user option to add arbitrary number of dropdown sets to filter dataframe. Single set of dropdowns consists of main dropdown - module (A, B) and its subordinate dropdown to help select submodule (A1…or B1…). Except of first dropdown set (see example gif bellow), all other sets are added by clicking Add Filter through pattern-matching callback. I need to modify code so selected values persist each time I click Add Filter.


Please, run code to reproduce described behaviour. Many thanks to all who will participate !

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

my_dict ={
    'module': ['A', 'A', 'A', 'B', 'B', 'B'],
    'submodule': ['A1', 'A2', 'A3', 'B1', 'B2', 'B3']}

df = pd.DataFrame(my_dict)

app = dash.Dash(__name__, suppress_callback_exceptions=True)

app.layout = html.Div([
    html.Div([html.Button("Add Filter", id="dynamic-add-filter", n_clicks=0)], style={"padding-bottom": 25}),
    html.Div([dcc.Dropdown(id='basic-dropdown',
                           options=[{'label': i, 'value': i} for i in df['module'].unique()],
                           value='A'
              )], style={"padding-bottom": 25}),
    html.Div(id='submodule-basic-container'),
    html.Div(id='dynamic-dropdown-container', children=[]),
])

@app.callback(
    Output('dynamic-dropdown-container', 'children'),
    [Input('dynamic-add-filter', 'n_clicks')],
    [State('dynamic-dropdown-container', 'children')])
def display_dropdowns(n_clicks, children):
    new_element = html.Div([
        dcc.Dropdown(
            id={
                'type': 'dynamic-dropdown',
                'index': n_clicks
            },
            options=[{'label': i, 'value': i} for i in df['module'].unique()],
            value='A'
        ),
        html.Div(
            id={
                'type': 'dynamic-output',
                'index': n_clicks
            }
        )
    ], style={"padding-top": 25})
    children.append(new_element)
    return children

# interaction module vs submodule
@app.callback(
    Output({'type': 'dynamic-output', 'index': MATCH}, 'children'),
    [Input({'type': 'dynamic-dropdown', 'index': MATCH}, 'value')],
)
def display_output(value):
    dff = df[df['module'] == value]
    if value == None:
        return dash.no_update
    else:
        return html.Div([(dcc.Dropdown(id="submodule-dynamic",
                                       options=[{'label': i, 'value': i} for i in dff['submodule'].unique()]))],
                style={"padding-top": 25})

# interaction layout dropdown vs layout subdropdown
@app.callback(
    Output("submodule-basic-container", "children"),
    [Input("basic-dropdown", "value")]
)
def bf(bvalue):
    dff = df[df['module'] == bvalue]
    return html.Div(dcc.Dropdown(id='submodule-basic', options=[{'label': i, 'value': i} for i in dff['submodule'].unique()], value='A1'))

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

Hi - did you ever solve this problem? I am having the same issue.

Hi emvy03,

here is update of code. Compared to first version:

  • each new_element has both dropdown and sub-dropdown
  • persistence=True is set for dropdowns
  • callback output is set ("# interaction module vs submodule") to provide ‘options’ for sub-dropdown
my_dict ={
    'module': ['A', 'A', 'A', 'B', 'B', 'B'],
    'submodule': ['A1', 'A2', 'A3', 'B1', 'B2', 'B3']}

df = pd.DataFrame(my_dict)

app = dash.Dash(__name__, suppress_callback_exceptions=True)

app.layout = \
    html.Div([
        html.Div([html.Button("Add Filter", id="dynamic-add-filter", n_clicks=0)], style={"padding-bottom": 25}),
        html.Div([dcc.Dropdown(id='basic-dropdown',
                               options=[{'label': i, 'value': i} for i in df['module'].unique()],
                               value='A'
                  )], style={"padding-bottom": 25}),
        html.Div(id='submodule-basic-container'),
        html.Div(id='dynamic-dropdown-container', children=[]),
])

@app.callback(
    Output('dynamic-dropdown-container', 'children'),
    [Input('dynamic-add-filter', 'n_clicks')],
    [State('dynamic-dropdown-container', 'children')])
def display_dropdowns(n_clicks, children):
    new_element = html.Div([
        dcc.Dropdown(
            id={
                'type': 'dynamic-dropdown',
                'index': n_clicks
            },
            options=[{'label': i, 'value': i} for i in df['module'].unique()],
            value='A',
            persistence=True
        ),
        dcc.Dropdown(id={'type': 'submodule-dynamic', 'index': n_clicks},
                     persistence=True, persistence_type="session"
                     )
    ], style={"padding-top": 25})
    children.append(new_element)
    return children

# interaction module vs submodule
@app.callback(
    Output({'type': 'submodule-dynamic', 'index': MATCH}, 'options'),
    [Input({'type': 'dynamic-dropdown', 'index': MATCH}, 'value')],
)
def display_output(value):
    dff = df[df['module'] == value]
    if value == None:
        return dash.no_update
    else:
        return \
            [{'label': i, 'value': i} for i in dff['submodule'].unique()]

# interaction layout dropdown vs layout subdropdown
@app.callback(
    Output("submodule-basic-container", "children"),
    [Input("basic-dropdown", "value")]
)
def bf(bvalue):
    dff = df[df['module'] == bvalue]
    return \
        html.Div(
            dcc.Dropdown(id='submodule-basic',
                            options=[{'label': i, 'value': i} for i in dff['submodule'].unique()],
                            value='A1',
                            persistence=True)
        )

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

Values in dropdowns persist after clicking “Add Filter” with this adjustment.

Cheers,
opas

Amazing. Thank you, Opas. I will try this now.