I’m working on a dash app in which I need to include row dropdowns in a DataTable where the content of the dropdown is specific to the row. However, because I’m dealing with a large amount of data, I’m only identifying the contents of the DataTable (and the dropdown options) after a few selections have been made by the user.
Issue: I can’t get the dropdowns to populate with options unless the options are defined before the app.layout section.
Below is a simplified example with the issue. The data describes products offered to different customers where each customer has certain colors they can choose for each product (colors offered are not always the same for each product / customer). For simplicity of the example, the data used does not change based on the A/B/C dropdown, but in my real use case the available data does change.
I’ve only successfully populated the dropdowns when the data manipulation is done before the app is initiated. Is it possible to have the dropdown options defined dynamically while the app is operating?
import pandas as pd
from dash import Dash, html, dash_table, callback, Output, Input, dcc
# Create a dictionary to map row_id to the list of colors for each row
rowid_colors = {}
app = Dash(__name__)
app.layout = html.Div(
children = [
dcc.Dropdown(
id = 'election_dropdown',
options=['A','B','C'],
),
dash_table.DataTable(
id="dropdown_per_row",
columns=[
{"id": "customer_id", "name": "Customer ID"},
{"id": "product_id", "name": "Product ID"},
{"id": "row_id", "name": "Row ID"},
{"id": "product_colors", "name": "Product Colors", "presentation": "dropdown"}
],
hidden_columns=["row_id"],
editable=True,
dropdown_conditional=[
{
"if": {
"column_id": "product_colors",
"filter_query": "{{row_id}} eq {}".format(rowid),
},
"options": [{"label": i, "value": i} for i in colors],
}
for rowid, colors in rowid_colors.items()
],
),
html.Div(id="dropdown_per_row_container"),
]
)
@callback(
Output("dropdown_per_row", "data")
, Input("election_dropdown", "value")
)
def define_dropdown(election_value):
df = pd.DataFrame()
df['customer_id'] = [305, 305, 305, 305, 305, 402, 402]
df['product_id'] = [101, 101, 101, 109, 109, 101, 101]
df['cust_prod_colors'] = ['Blue', 'Green', 'Yellow', 'Blue', 'Orange', 'Blue', 'Red']
agg_colors_df = df.groupby(['customer_id', 'product_id'])['cust_prod_colors'].apply(list).reset_index()
# Add a row_id column to the aggregated positions DataFrame using the index as a string
agg_colors_df['row_id'] = agg_colors_df.index.astype(str)
# Define the colors for each row's dropdown
global rowid_colors
rowid_colors.clear()
for i in range(len(agg_colors_df)):
# for the record with row_id = i, get the position list
pos_list = agg_colors_df.at[i, 'cust_prod_colors']
row_id = agg_colors_df.at[i, 'row_id']
rowid_colors[row_id] = pos_list
# Remove the cust_prod_colors from agg_colors_df
agg_colors_df = agg_colors_df.drop(columns=['cust_prod_colors'])
return agg_colors_df.to_dict('records')
if __name__ == "__main__":
app.run(debug=True)