Use Range Slider to Filter Dropdown Box

I am working on creating a dashboard that uses a range slider to filter a dropdown box.

Here is my code so far:

#Import packages
import dash
from dash import dcc, html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output

letters = ['a','b','c','d','e','f','g','h','i','j']

dash_dict = {
    1:'a',
    2:'b',
    3:'c',
    4:'d',
    5:'e',
    6:'f',
    7:'g',
    8:'h',
    9:'i',
    10:'j'
}

app = dash.Dash()
server = app.server
app.layout = html.Div([
        dbc.Row([
            dbc.Col([
                dcc.RangeSlider(
                    id='range_slider',
                    min=1,
                    max=10,
                    step=1,
                    value=[6, 10],
                    allowCross=False,
                    pushable=2,
                    tooltip={"placement": "bottom", "always_visible": True},
                    marks={
                        1: '1',
                        2: '2',
                        3: '3',
                        4: '4',
                        5: '5',
                        6: '6',
                        7: '7',
                        8: '8',
                        9: '9',
                        10: '10'
                    }
                ),
            ],width=6),
            dbc.Col([
                dcc.Dropdown(
                    id='dropdown',
                    style={'color':'black'},
                    options=[{'label': i, 'value': i} for i in letters],                        
                )
            ],width=6)
        ])              
])      


#Configure dependent dropdown box from slider
@app.callback(
    Output('dropdown', 'options'), #--> filter letters
    Input('range_slider', 'value') #--> choose number range
)
def set_letter_options(selected_range):
    return [{'label': i, 'value': i} for i in dash_dict[selected_range]],



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

I am following the chained callback example found here and was able to get this to work properly for a normal one-sided slider. However, with the range slider, I get an error that reads:

TypeError: unhashable type: 'list'

which makes sense, I’m passing 2 values to slice and dice that dictionary up by. I am stuck on how to structure the callback to account for the dynamic range of the slider with the upper and lower bounds.

Can someone help point me in the right direction?
Thank you!

Hi,

There are many ways of doing it…I think you are looking for something like:

@app.callback(
    Output('dropdown', 'options'), #--> filter letters
    Input('range_slider', 'value') #--> choose number range
)
def set_letter_options(selected_range):
    return [{'label': dash_dict[i], 'value': dash:_dict[i]} for i in range(int(selected_range[0]), int(selected_range[1])+1],

This assumes that the end value in the range is included in the options, of course. You could also consider converting some int to str and vice-versa in the definition, so your code gets cleaner.

Hope this helps! :slightly_smiling_face:

1 Like

Thanks so much!

Hey @jlfsjunior , I have another question.

I tried to tweak the problem a little and account for having multiple values per key.

Here is an example of my revised dictionary:


dash_dict = {
    1:['a','b'],
    2:'c',
    3:'d',
    4:'e',
    5:'f',
    6:'g',
    7:'h',
    8:'i',
    9:'j',
    10:['k','l']
}

I tried using your solution with this dictionary and the β€˜a’ and β€˜b’ were smushed together as well as the β€˜k’ and the β€˜l’ as unique values to select in the dropdown box. How can I modify your solution to get separate unique values for a, b, k, and l when their corresponding numbers are selected?

Let me see if I understand: say that the selected range is [1, 3]. Would you want the dropdown choices to be ["a", "b", "c", "d"]?

1 Like

Yes, that is correct!

Then you need to flatten the list first, as some elements are str and others are list. Something like:

@app.callback(
    Output('dropdown', 'options'), #--> filter letters
    Input('range_slider', 'value') #--> choose number range
)
def set_letter_options(selected_range):
     values = [dash_dict[i] for i in range(int(selected_range[0]), int(selected_range[1])+1]
     
     new_values = []
     for val in values:
          if isinstance(val, list):
              for item in val:
                  new_values.append(item)
          else:
               new_values.append(val)
   
    return new_values

Note that you don’t need to use a list of dicts if label and value are the same. A simple list will do the job.

A good one-liner option is:

@app.callback(
    Output('dropdown', 'options'), #--> filter letters
    Input('range_slider', 'value') #--> choose number range
)
def set_letter_options(selected_range):
    values = [dash_dict[i] for i in range(int(selected_range[0]), int(selected_range[1])+1]
    return sum([i if isinstance(i, list) else [i] for i in values], [])

Hey @jlfsjunior , thanks for responding again. I ran your suggestion with the rest of the code, but now the dropdown box appears blank.

Nevermind, I got it! Just added this for the return line:

return [{'label':i,'value':i} for i in new_values]

Thanks for your help!

1 Like