Update single dropdown options based on multiple inputs

Hi

I’ve been trying to figure out how I can update one dropdowns’ options based on multiple input variables.

I’m aware that Plotly doesn’t allow you to create multiple callbacks with the same output variable.

So far none of my attempts have been successful.

Hi,

You can use multiple inputs in a single callback. If for any reason you need to see which one of the inputs triggered the callback, you can always use the callback context, explained here.

Hi

Thanks for your reply.

I’ve read the documentation but haven’t seen any possible solutions to my issue.

To illustrate I’ve included my code below.

Code
import dash
from dash.dependencies import Input, Output
from dash import dcc
from dash import html
from dash import dash_table as dt
import plotly.express as px
import pandas as pd
import numpy as np

app = dash.Dash(__name__)

app.title = "Fantasy NBA dashboard: Understand multi-year player performance"

colors = {
    'background': '#111111',
    'text': '#7FDBFF'
}


def acquire_bbref_data():
    player_data = []
    season = list(reversed(range(2010, 2023)))
    for year in season:
        url = 'https://www.basketball-reference.com/leagues/NBA_' + str(year) + '_per_game.html'
        data = pd.read_html(url, header=0)
        df = data[0]
        df['Season'] = year
        year -= 1
        df = df.drop(['Rk'], axis=1)
        player_data.append(df)
    player_data = pd.concat(player_data)
    return player_data


season_data = acquire_bbref_data()


"""Creating the app"""
app = dash.Dash(__name__)
app.title = "NBA data"

app.layout = html.Div([
    html.H1(
        children="NBA data",
        className="header-title"
    ),
    html.P(
        children="Placeholder text ",
        className="header-description",
    ),
    html.Div([
        html.Div(children="Season", className="menu-title"),
        dcc.Dropdown(
            id="season-filter",
            options=[
                {"label": Season, "value": Season}
                for Season in np.sort(season_data.Season.unique())
            ],
            clearable=False,
            multi=True,
            className="dropdown",
            placeholder="Choose a season",
        ), html.Br(),
    ]
    ),

    html.Div([
        html.Div(children="Team", className="menu-title"),
        dcc.Dropdown(
            id="team-filter",
            options=[
                {"label": Tm, "value": Tm}
                for Tm in np.sort(season_data.Tm.unique())
            ],
            clearable=False,
            multi=True,
            className="dropdown",
            placeholder="Choose a team",
        ),
        html.Br(),

    ]
    ),

    html.Div([
        html.Div(children="Position", className="menu-title"),
        dcc.Dropdown(
            id="pos-filter",
            options=[
                {"label": Pos, "value": Pos}
                for Pos in np.sort(season_data.Pos.unique())
            ],
            clearable=False,
            multi=True,
            className="dropdown",
            placeholder="Choose a position",
        ),
        html.Br(),

    ]
    ),

    html.Div([
        html.Div(children="Player", className="menu-title"),
        dcc.Dropdown(
            id="player-filter",
            options=[
                {"label": Player, "value": Player}
                for Player in np.sort(season_data.Player.unique())
            ],
            clearable=False,
            multi=True,
            className="dropdown",
            placeholder="Choose a player",
        ),
        html.Br(),

    ]
    ),

    html.Div([
        dt.DataTable(
            id="player-table",
            columns=[{"name": i, "id": i} for i in season_data.columns],
            data=season_data.to_dict("records"),
            style_table={"overflowX": "auto"},
            style_cell={
                "textAlign": "left",
                "padding": "3px",
                "whiteSpace": "normal",
                "height": "auto",
            },
            style_header={
                "backgroundColor": "teal",
                "fontWeight": "bold",
            },
            page_current=0,
            page_size=10,
        ),
    ]
    ),

]
)

"""Call to update the player filter based on other filters """

#The callback below currently works with one input. If I try and extend this out to the others it doesn't work
# I've tried separating it into multiple callbacks but Plotly doesn't allow for this

@app.callback(
    Output("player-filter", "options"),
    [
        Input("season-filter", "value"),
        # Input("team-filter", "value"),
        # Input("pos-filter", "value")
    ])
def update_player_filter(d1): 
    if d1 is not None:
        dff = season_data[season_data["Season"].isin(d1)]
        return [{'label': i, 'value': i} for i in dff["Player"].unique()]
    else:
        return []  # returns an empty list as it's easier to check things are working

        # will have it return the below player list once the above is resolved
        # return [{'label': i, 'value': i} for i in season_data["Player"].unique()]


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

Hopefully the above makes it clearer what I’ve trying to achieve.

Yes, it is very clear. I imagine that season_data has “Team” and “Position” as columns, so what you want is something like:

@app.callback(
    Output("player-filter", "options"),
    [
        Input("season-filter", "value"),
        Input("team-filter", "value"),
        Input("pos-filter", "value")
    ])
def update_player_filter(season, team, position): 
     dff = season_data[season_data["Season"].isin(season)] if season else season_data
     dff = dff[dff["Team"].isin(team)] if team else dff
     dff = dff[dff["Position"].isin(position)] if position else dff
     return [{'label': i, 'value': i} for i in dff["Player"].unique()]

Here I am explicitly using that an empty selection should return all seasons/teams/positions, because otherwise the user would need to select all one by one (there is no “select all” options). Of course, you could in principle add a “All” option in each dropdown and handle this case separately.

Thanks for your response.

I’ll try this and see how it goes.

@jlfsjunior I’m guessing this should have been “pos” instead of “team”. As soon as I changed it the filters worked.

Many thanks for your help.

1 Like