dmc.MultiSelect options dependent on results of previous MultiSelect

I’m trying to create a cascading set of selections, where each subsequent MultiSelect is dependent on the results of the previous select.

My current attempt is to create a GLOBAL DataFrame, which - after making the first selection - is updated based on the selection.

The second MultiSelect options (data=) is then calculated form the GLOBAL DataFrame. However, the options for the second selection are not changing - perhaps because there’s nothing to trigger it to refresh?

IS it possible to populate the data field with the contents of dcc.Store(id='resultset_ids', storage_type='session')

app = Dash(__name__, use_pages=True)

app.title="Main Sidebar"

global selections_df
global resultset_names

def get_selections_df():
    # Returns a large DataFrame with columns "resultset_name" and "injection_id"
    return query_selections_df(connection())
    
def sidebar():
    global selections_df
    global resultset_names
    selections_df = get_selections_df()
    
    return html.Div([
                html.Div([
                    dcc.Location(id="url"), 
                    html.Div([
                        dcc.Store(id='resultset_names',     storage_type='session'),
                        dcc.Store(id='injection_ids',       storage_type='session'),

                        html.H2("Sidebar", className="display-4"), # Classname is the CSS class
                        
                        html.Div([
                                    dmc.MultiSelect(
                                        label="Select one or more resultset names",
                                        placeholder="",
                                        id="set-resultset-name",  
                                        value=[],
                                        data = sorted([name for name in selections_df.resultset_name.unique() if isinstance(name, str)]), # To prevent None getting in there
                                        persistence =  True,
                                        style={"marginBottom": 10}),
                                    
                                    dmc.MultiSelect(
                                        label="Select one or more resultset IDs",
                                        placeholder="",
                                        id="set-resultset-ids",  
                                        value="All",
                                        data = sorted([ID for ID in selections_df.injection_id.unique() if isinstance(ID, str)]),
                                        persistence =  True,
                                        style={"marginBottom": 10}),
                                    
                                ]
                            ),

                    ],
                    style=SIDEBAR_STYLE, # Css formatting saved in variable above
                    ), 
                ]),                 
])
app.layout = html.Div([
    sidebar(),
])

# Resultset_name will be the primary filter=====================================
#===============================================================================
@callback(Output('resultset_names', 'data'),
          Input('set-resultset-name', 'value'),
          State('resultset_names', 'data'))
def set_resultset_name(value, data):
    global selections_df
    if data is None:
        data = {"resultset_names":value}
    else:
        data["resultset_names"] = value         

    selections_df = selections_df[selections_df.resultset_name.isin(value)]
    return data

@callback(Output('injection_ids', 'data'),
          Input('set-resultset-ids', 'value'),
          State('injection_ids', 'data'))
def set_injection_ids(value, data):
    global selections_df

    if value == "All":
        value = sorted([ID for ID in selections_df.injection_id.unique() if isinstance(ID, str)])
    if data is None:
        data = {"injection_ids":value}
    else:
        data["injection_ids"] = value         

    selections_df = selections_df[selections_df.injection_id.isin(value)]
    return data

app.scripts.config.serve_locally = True

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

Hi @rightmirem I think changing a global variable in a callback is not a good idea. Have a read here:

Hi AIMPED,

Thanks for the reply. I have been experimenting with dcc.store. But how can I use the contents of a dcc.store (like dcc.Store(id='resultset_ids', storage_type='session')) to populate the contents of the data= field in the multiselect?

You can create a secodn callback to update the multiselect options. The Input() of this callback would be Input('resultset_ids', 'data')

df → filter → store → multiselect options

1 Like

Do you have a link to some example code on how to do this?

OK. I figured out how to do it. It’s not so bad, once you get your head wrapped around it.

Here’s a code snippet for any others running into this problem…

from dash import callback, Dash, Input, Output, dcc, html, State, ctx
# import dash_bootstrap_components as dbc
import dash_mantine_components as dmc
import pandas as pd
import psycopg2

app = Dash(__name__)

app.title="Multiselect app"

conn = psycopg2.connect(host = "***.com",
                        port = ****,
                        dbname = "***",
                        user = "***",
                        password = "***"
                        )

_sql = """
SELECT 
    MAINPEAK_ID,
    INJCOMPOUND_NAME,
    RESULTSET_NAME,
    PEAK_ID, 
    <other data columns>,
FROM COMPOUNDS
LEFT JOIN PEAKS ON MAINPEAK_ID = PEAK_ID 
"""

global df
df = pd.read_sql(_sql, conn)

app.layout = html.Div([
                        html.Div("Filter resultset names"),
                        # Actual multiselect for resultset_names
                        html.Div([
                                        dmc.MultiSelect(
                                            label="Select Result set names",
                                            placeholder="Result set names",
                                            id="set-resultset-name",  
                                            value=[],
                                            data = df.resultset_name.unique(),
                                            searchable=True,
                                            clearable = True, 
                                            maxDropdownHeight = 200, 
                                            persistence =  True,
                                            style={"marginBottom": 10}),
                                    ]
                                ),

                        html.Hr(),
                        # The following line displays the multiselect returned 
                        #  from select_compounds(resultsets)
                        html.Div(id = "compound_names_multiselect")
                        # The id = "set-compound-name" from the returned multiselect can then
                        #  be used as input into subsequent callbacks
                    ]) 

@callback(
    Output('compound_names_multiselect', 'children'),
     Input('set-resultset-name', 'value'),
          )

def select_compounds(resultsets):
    global df
    return html.Div([
                    dmc.MultiSelect(
                        label="Select compound names",
                        placeholder="Select compound names",
                        id="set-compound-name",  
                        value=[],
                        data = df[df.resultset_name.isin(resultsets)].injcompound_name.unique(),
                        searchable=True,
                        clearable = True, 
                        maxDropdownHeight = 200, 
                        persistence =  True,
                        style={"marginBottom": 10}),
                ]
            ),    

app.scripts.config.serve_locally = True

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

1 Like