Dynamically updating dropdown options with 2 Inputs in callback Dash

Viewed 1691

I am stuck in a part where I want to update the Batch dropdown in 2 ways. If there is nothing selected in Material dropdown, the batch dropdown should show all the Batches, but as soon as Material is selected, it should change to only show Batches for that Material type.

This is what the relevant code looks like right now:

import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output 
import plotly.express as px
import pandas as pd
import re

app = dash.Dash(__name__)

# Convert PARQUET to CSV


df = pd.read_csv("release_cln.csv")

# Store all unique values from Basis column

df_dis = df[df['Analysis_Name_CLN'].str.contains("Dissolution").fillna(False)]

df_dis['Batch'] = df_dis['Batch'].fillna(9999)
df_dis['Basis'] = df_dis['Batch'].fillna(9999)
df_dis['Material'] = df['Material'].fillna(9999)

batch = df_dis['Batch'].unique()
material = df_dis['Material'].unique()
basis = df_dis['Basis'].unique()

#[{'label': i, 'value': i} for i in material]

app.layout = html.Div([
    html.H1(children='Comparing Batches'),
    html.H2('Dissolution vs Timepoint'),

    # Batch 1 Dropdowns
    html.Div([
        html.Div(
            html.H3(children='''DP Batch''')
        ),
        html.Div([
            html.Div(children='''Batch: '''),
            dcc.Dropdown(
                    id='batch_dd',
                    multi=False,
                    clearable=True,
                    disabled=False
                ),
            ],style={'width': '20%','display': 'inline-block'}
        ),
        html.Div([
            html.Div(children='''Material: '''),
            dcc.Dropdown(
                    id='material_dd',
                    multi=False,
                    clearable=True,
                    disabled=False
                ),
            ],style = {'width': '15%','display': 'inline-block'}
        ),
        html.Div([
            html.Div(children='''Basis: '''),
            dcc.Dropdown(
                    id='basis_dd',
                    multi=False,
                    clearable=True,
                    disabled=False
                ),
            ],style = {'width': '15%','display': 'inline-block'}
        ),
        html.Div([
            html.Div(children='''Variant: '''),
            dcc.Dropdown(
                    id='variant_dd',
                    multi=False,
                    clearable=True,
                    disabled=False
                ),
            ],style = {'width': '10%','display': 'inline-block'}
        ),
        html.Div([
            html.Div(children='''Analysis: '''),
            dcc.Dropdown(
                    id='analysis_name_dd',
                    multi=False,
                    clearable=True,
                    disabled=False,
                    options=[]
                ),
            ],style = {'width': '40%','display': 'inline-block'}
        )
        ],style = {'width': '90%', 'display': 'inline-block'}
    ),

    html.Br(),

    # Batch 2 Dropdowns
    html.Div([
        html.Div(
            html.H3(children='''DS Batch''')
        ),
        html.Div([
            html.Div(children='''Batch: '''),
            dcc.Dropdown(
                    id='batch_dd_1',
                    multi=False,
                    clearable=True,
                    disabled=False,
                    options=[{'label': i, 'value': i} for i in batch]
                ),
            ],style={'width': '20%','display': 'inline-block'}
        ),
        html.Div([
            html.Div(children='''Material: '''),
            dcc.Dropdown(
                    id='material_dd_1',
                    multi=False,
                    clearable=True,
                    disabled=False,
                    options=[],
                ),
            ],style = {'width': '15%','display': 'inline-block'}
        ),
        html.Div([
            html.Div(children='''Basis: '''),
            dcc.Dropdown(
                    id='basis_dd_1',
                    multi=False,
                    clearable=True,
                    disabled=False,
                    options=[]
                ),
            ],style = {'width': '15%','display': 'inline-block'}
        ),
        html.Div([
            html.Div(children='''Variant: '''),
            dcc.Dropdown(
                    id='variant_dd_1',
                    multi=False,
                    clearable=True,
                    disabled=False,
                    options=[]
                ),
            ],style = {'width': '10%','display': 'inline-block'}
        ),
        html.Div([
            html.Div(children='''Analysis: '''),
            dcc.Dropdown(
                    id='analysis_name_dd_1',
                    multi=False,
                    clearable=True,
                    disabled=False,
                    options=[]
                ),
            ],style = {'width': '40%','display': 'inline-block'}
        )
        ],style = {'width': '90%', 'display': 'inline-block'}
    ),

    html.Br(),
    dcc.Graph(id='dissol_tp')
])

## Batch 1 Dropdowns
#Call back to update Batch options if user searches for Batches
@app.callback(
    Output('batch_dd','options'),
    [Input('batch_dd','search_value'),
    Input('material_dd','value')]
)
def batch_options(search_value, material_dd):
    if material_dd is None:
        return [{'label': i, 'value': i} for i in batch]
    else:
        batch_df = df[df['Material']==material_dd]
        return [{'label':i,'value':i} for i in batch_df['Batch'].fillna('None').unique()]


@app.callback(
    Output('material_dd','options'),
    Input('material_dd','search_value'))
def material_options(search_value):
    return [{'label': i, 'value': i} for i in material]

# Callback to select default value in basis list
@app.callback(
    Output('basis_dd','options'),
    Input('basis_dd','search_value'))
def variant_default(batch_dd):
    return [k['value'] for k in batch_dd]

# Chained callback to select variants with the selected batch
@app.callback(
    Output('variant_dd','options'),
    Input('batch_dd','value'))
def get_variant(batch_dd):
    batch_df = df[df['Batch']==batch_dd]
    return [{'label':i,'value':i} for i in batch_df['Variant'].fillna('None').unique()]


What's happening currently is batch dropdown shows all the batch values even when a material value is selected and doesn't change dynamically

1 Answers

Here's a demonstration, based off of your code, just showing how to get the batches dropdown to dynamically depend on whatever is selected in the material dropdown.

import random
import dash
import numpy as np
import pandas as pd

from dash import dcc
from dash import html
from dash.dependencies import Input
from dash.dependencies import Output


app = dash.Dash(__name__)

# Convert PARQUET to CSV
# df = pd.read_csv("release_cln.csv")

n = 50
df_dis = pd.DataFrame(
    {
        "Batch": np.random.randint(1, 2000, n),
        "Material": random.choices(
            ["wood", "metal", "fire", "water", "ice", "rock"], k=n
        ),
    }
)

batches = {
    m: list(df_dis[df_dis.Material == m].Batch.values)
    for m in df_dis.Material
}


batch = df_dis["Batch"].unique()
material = df_dis["Material"].unique()


app.layout = html.Div(
    [
        html.H1(children="Comparing Batches"),
        html.H2("Dissolution vs Timepoint"),
        # Batch 1 Dropdowns
        html.Div(
            [
                html.Div(html.H3(children="""DP Batch""")),
                html.Div(
                    [
                        html.Div(children="""Material: """),
                        dcc.Dropdown(
                            id="material_dd",
                            multi=False,
                            clearable=True,
                            disabled=False,
                        ),
                    ],
                    style={"width": "15%", "display": "inline-block"},
                ),
                html.Div(
                    [
                        html.Div(children="""Batch: """),
                        dcc.Dropdown(
                            id="batch_dd",
                            multi=False,
                            clearable=True,
                            disabled=False,
                        ),
                    ],
                    style={"width": "20%", "display": "inline-block"},
                ),
            ]
        ),
    ]
)

## Batch 1 Dropdowns
# Call back to update Batch options if user searches for Batches
@app.callback(
    Output("batch_dd", "options"),
    [Input("batch_dd", "search_value"), Input("material_dd", "value")],
)
def batch_options(search_value, material_dd):
    if material_dd is None:
        return [{"label": i, "value": i} for i in batch]
    else:
        return [{"label": i, "value": i} for i in batches[material_dd]]


@app.callback(
    Output("material_dd", "options"), Input("material_dd", "search_value")
)
def material_options(search_value):
    return [{"label": i, "value": i} for i in material]


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

demo

Here's what I did:

  • Create a dictionary, where each unique material from the dataframe form the keys, and each one has a corresponding list (assuming all material possible values have >1 row in df), specifically of the Batches which have the selected material type.
  • Likewise you would proceed for further nesting of dropdowns
Related