2 Identical Dropdowns Instead of Multiselect

Hello, I am working on a project that analyzes text spoken by characters in a tv show. One part of the project involves letting the user select two characters to compare dialogues. I want to have a list of characters to select from for the first character and the second character.

Here is some example code of the dashboard layout:

#Import libraries
import pandas as pd
import numpy as np
import dash

import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State

#Set parameter
choose_character = ['Mark', 'Mike', 'Derrick', 'Jane', 'Stephanie', 'Lisa']

#Define app layout
app = dash.Dash()
server = app.server
app.layout = html.Div([
    dbc.Row([
        dbc.Col(
            dcc.Dropdown(
                id='dropdown1',
                options=[{'label': i, 'value': i} for i in choose_character],
                value=choose_character[0]
            ), width=6
        ),
        dbc.Col(
            dcc.Dropdown(
                id='dropdown2',
                options=[{'label': i, 'value': i} for i in choose_character],
                value=choose_character[1]
            ), width=6
        )

    ])
])

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

What I want to be able to do is to have the selected item from the first dropdown menu not be available in the second dropdown menu. I imagine I need to set up a dictionary with all of the character names and then set up a chained callback like the one here in this link: Part 3. Basic Callbacks | Dash for Python Documentation | Plotly

Am I on the right path? Does anyone know of an easy way to get this done? Any help would be appreciated! Thank you!

hi John @plotme1
That’s a good question. Yes, you were on the right track with chained callbacks. Here’s an example of how you could do it:

import dash

import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State

#Set parameter
choose_character = ['Mark', 'Mike', 'Derrick', 'Jane', 'Stephanie', 'Lisa']

#Define app layout
app = dash.Dash()
server = app.server
app.layout = html.Div([
    dbc.Row([
        dbc.Col(
            dcc.Dropdown(
                id='dropdown1',
                options=[{'label': i, 'value': i} for i in choose_character],
                value=choose_character[0]
            ), width=6
        ),
        dbc.Col(
            dcc.Dropdown(
                id='dropdown2',
                options=None,
                value=None,
            ), width=6
        )

    ])
])

@app.callback(
    Output('dropdown2','options'),
    Input('dropdown1','value')
)
def update_second_dropdown(value):
    print(value)
    updated_choose_character = choose_character.copy()
    updated_choose_character.remove(value)
    print(updated_choose_character)
    return [{'label': i, 'value': i} for i in updated_choose_character]


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

This worked beautifully! Thank you so much!

Hey @adamschroeder ! Thanks again for your solution! I have a new problem for you:

I want to use that callback in tandem with another callback that selects characters by season they appear.

Here is my code including the snippet you helped me with:


import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State

#Set parameter
choose_season = ['Season 1', 'Season 2', 'Season 3','Season 4']
choose_character = ['Mark', 'Mike', 'Derrick', 'Jane', 'Stephanie', 'Lisa']

#Create a season-character dictionary
season_character_dict = {
    'Season 1': ['Mark', 'Mike','Jane', 'Stephanie','Lisa'],
    'Season 2': ['Mark', 'Mike','Jane', 'Stephanie'],
    'Season 3': ['Mark', 'Mike','Jane', 'Stephanie', 'Derrick', 'Lisa'],
    'Season 4': ['Mark', 'Mike','Jane', 'Stephanie', 'Derrick']

}

#Define app layout
app = dash.Dash()
server = app.server
app.layout = html.Div([
    dbc.Row([
        dbc.Col(
            dcc.Dropdown(
                id='dropdown0',
                options=[{'label': i, 'value': i} for i in choose_season],
                value=choose_season[0]
            ), width=4
        ),
        dbc.Col(
            dcc.Dropdown(
                id='dropdown1',
                options=[{'label': i, 'value': i} for i in choose_character],
                value=choose_character[0]
            ), width=4
        ),
        dbc.Col(
            dcc.Dropdown(
                id='dropdown2',
                options=[{'label': i, 'value': i} for i in choose_character],
                value=choose_character[0]
            ), width=4
        )

    ])
])



@app.callback(
    Output('dropdown2', 'options'),#-----Filters the character options
    Output('dropdown2', 'value'),
    Input('dropdown0', 'value') #----- Select the season
)
def set_character_options(selected_season):
    return [{'label': i, 'value': i} for i in season_character_dict[selected_season]], season_character_dict[selected_season][0]


@app.callback(
    Output('dropdown1', 'options'),#-----Filters the character options
    Output('dropdown1', 'value'),
    Input('dropdown0', 'value') #----- Select the season
)
def set_character_options(selected_season):
    return [{'label': i, 'value': i} for i in season_character_dict[selected_season]], season_character_dict[selected_season][1]



@app.callback(
    Output('dropdown2','options'),
    Input('dropdown1','value')
)
def update_second_dropdown(value):
    updated_choose_character = choose_character.copy()
    updated_choose_character.remove(value)
    return [{'label': i, 'value': i} for i in updated_choose_character]

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

It seems when I include the callback to filter the character dropdown menus by season - it screws up the update_second_dropdown callback. I don’t get an error, so I’m not sure how to go about fixing this.

Is there a better way I can rewrite the callbacks or combine them somehow?

Hi @plotme1 ,
can you please clarify what you’re trying to do. You have 3 dropdowns. Once the user selects a season from the first dropdown, you would like what to happen?

I would like the season selection dropdown to filter both character options first. But then also have the update_second_dropdown callback remove the selected character in the 1st character dropdown from the 2nd character dropdown.

Hi @plotme1 ,
here you go. I chose to remove the dropdowns from the layout, and put them inside the divs, through the callback.

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State

#Set parameter
choose_season = ['Season 1', 'Season 2', 'Season 3','Season 4']
choose_character = ['Mark', 'Mike', 'Derrick', 'Jane', 'Stephanie', 'Lisa']
#Create a season-character dictionary
season_character_dict = {
    'Season 1': ['Mark', 'Mike','Jane', 'Stephanie','Lisa'],
    'Season 2': ['Mark', 'Mike','Jane', 'Stephanie'],
    'Season 3': ['Mark', 'Mike','Jane', 'Stephanie', 'Derrick', 'Lisa'],
    'Season 4': ['Mark', 'Mike','Jane', 'Stephanie', 'Derrick']

}

#Define app layout
app = dash.Dash(__name__, suppress_callback_exceptions=True)
server = app.server
app.layout = html.Div([
    dbc.Row([
        dbc.Col(
            dcc.Dropdown(
                id='dropdown0',
                options=[{'label': i, 'value': i} for i in choose_season],
                value=choose_season[0]
            ), width=4
        ),
        dbc.Col(
            html.Div(id='first-dropdown'), width=4
        ),
        dbc.Col(
            html.Div(id='second-dropdown'), width=4
        )

    ])
])



@app.callback(
    Output('first-dropdown', 'children'),
    Input('dropdown0', 'value') #----- Select the season
)
def set_character_options(selected_season):
    return dcc.Dropdown(
        id='dropdown1',
        options=[{'label': i, 'value': i} for i in season_character_dict[selected_season]],
        value=season_character_dict[selected_season][1]
        )


@app.callback(
    Output('second-dropdown','children'),
    Input('dropdown1','value'),
)
def update_second_dropdown(value):
    print(value)
    updated_choose_character = choose_character.copy()
    updated_choose_character.remove(value)
    return dcc.Dropdown(
        id='dropdown2',
        options=[{'label': i, 'value': i} for i in updated_choose_character],
        # value=
    )

if __name__=='__main__':
    app.run_server(port=8002, debug=True)
1 Like

Thanks again @adamschroeder !