Syncing component options using session state

I’m not sure if this has been figured out and posted elsewhere, but I’ve looked for a solution and haven’t found exactly what I was looking for.

I’ve been building a dashboard for drilling down into data and I wanted to sync the options for all the multiselect components such that you don’t see options that aren’t available. Meaning, when you select value from one multiselect, the options for the other multiselects update.

I found this post: Dynamic multiselect options tied to presence in a list/dataframe, which handles syncing options, but only downstream.

I made a solution that seems to work pretty well. It’s not exactly simple, but it does what I need to do.
Basically, you store each component’s value in session state and use that to generate new options each time the app refreshes.

import streamlit as st
import pandas as pd
from vega_datasets import data

df = data.cars()
df['Year'] = df['Year'].dt.year


#  The components like to update and choose new values when you apply these methods.
# Each time the dashboard updates, you need to set the reset the value and options for each component.
# To do that, store each components value in st.session_state.

# if there is a value in st.session_state for years 
if "years" in st.session_state and st.session_state['years']:
    # get the selected value. Use this to set the value of the year slider later
    years = st.session_state['years']
    # filter the dataframe for the selected years and grab the indices
    year_idx = df[(df['Year'] >= years[0]) & (df['Year'] <= years[1])].index
# if no value is selected or "years" hasn't been added to the session_state yet, set default values
else:
    years = [min(df['Year']), max(df['Year'])]
    year_idx = df['Year'].index

# Do the same for names
if 'name' in st.session_state and st.session_state['name']:
    names = st.session_state['name']
    name_value = names
    name_idx = df[df['Name'].isin(names)].index.unique()
else:
    name_value = []
    name_idx = df.index

# Do the same for Origin
if 'origin' in st.session_state and st.session_state['origin']:
    origins = st.session_state['origin']
    origin_value = origins
    origin_idx = df[df['Origin'].isin(origins)].index.unique()
else:
    origin_value = []
    origin_idx = df.index
    
# Find the interseciton of all the filtered indices. 
idx = list(set(year_idx).intersection(set(name_idx), set(origin_idx)))
# Filter the dataframe
dff = df.loc[idx]

# Make the year slider
years = st.slider("Years", min_value=min(df['Year']), max_value=max(df['Year']), value=years, key="years")

# Get the filtered origin options
origin_options = df.loc[list(set(year_idx).intersection(set(name_idx)))]['Origin'].sort_values().unique()

# Make the origin multiselect
origins = st.multiselect("Origin", origin_options, default=origin_value, key="origin")

# Show the number of Origin options (just sanity check that it updated)
st.write(len(dff['Origin'].unique()))

# Get the filtered name options
name_options = df.loc[list(set(year_idx).intersection(set(origin_idx)))]['Name'].sort_values().unique()

# Make the name multiselect
names = st.multiselect("Name", name_options, default=name_value, key="name")

# Show the number of Name options (just sanity check that it updated)
st.write(len(dff['Name'].unique()))

# Show the filtered dataframe
st.write(dff)

I don’t know if this is the easiest or fastest way to do this, but it’s a solution that seems to work. It’s a bit cumbersome if you have a lot of components you want synced, but worth it in my opinion.

1 Like

Hi @aboyher, here’s an example of dependencies between 2 multiselect widgets. If this is what you need, you can replicate it for more dependencies based upon your logic.

The example shows cities changing based on state.

Cheers

Thanks Shawn, but that is only one-way dependency. My code syncs both ways so that if you change the second multiselect, the first one’s options update to only show what is available.

Hi, can you illustrate your bi-directional dependencies with actual data examples?

Cheers

The code I posted illustrates the bi-directional syncing.

Hi - I thought this was interesting. It didn’t quite work for me. E.g., select a couple of regions and one car from the reduced set, then it falls over. Similarly, if you select two or more cars of different regions, then select one of the reduced set of regions, then it also falls over.

Yeah, maybe this isn’t the best example. One car (name) is going to have one origin country. Picking USA and Europe gives you car names from either USA or Europe. Then you pick a car name and it only has one origin, but the code is going to try to put USA and Europe both back into the value for the origin multiselect, but the options for the multiselect doesn’t include one of them. So it throws an error.

You have to tailor the code to your dataset, which I didn’t spend much time thinking about for this because I was just trying to show how you could do bi-directional syncing. It works a lot better with my use case at work, where there is a lot more overlap between options.

1 Like