Hi there,
I’ve been attempting to make a non-Dash interactive HTML plot with dual dropdowns, one for data filtering and another for variable selection. I’ve used this example as a basic template and tweaked it to fit the format I want. This seems to work as intended with just one plot displayed at a time, but when I incorporate a second table subplot I start getting unexpected results and the wrong traces are displayed. I’ve included code for a toy example reproduction below.
For 2 subplots with N traces each, and only one pair of traces visible at a time, it seems as though my list of visible traces should be 2*N long and for a given variable at index i, the subplots set to be visible would be at index 2*i and 2*i+1. This does work with the initialized data. But after changing data with the ‘group’ dropdown, the traces are displayed out of order.
Through experimentation I found I can somehow get the right traces to show up after group selection if I use a visible list of length of only N with variable number i set to True, although the legend labels and initialized traces aren’t ordered correctly in this case, and it doesn’t make sense to me why this would work. Furthermore, this only works if I have an odd number of variables to plot. if the number is even, it breaks.
So my current workaround is: make sure to use an odd number of variables, initialize the charts with blank data to force a group selection, use a visibility parameter list in my restyle dropdown of length N, and hide the legend. But all of this seems very hack-y and I’m wondering if I’m missing something. If anyone can give me some guidance, I’d appreciate it - please let me know if I can explain anything better.
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import pandas as pd
df1 = pd.DataFrame({'group_name': ['group A', 'group A', 'group A'], 'Submission Date': ['2023-01-01', '2023-01-02', '2023-01-03'], 'var1': [1, 1, 1], 'var2': [2, 2, 2], 'var3': [3, 3, 3]})
df2 = pd.DataFrame({'group_name': ['group B', 'group B', 'group B'], 'Submission Date': ['2023-01-04', '2023-01-05', '2023-01-06'], 'var1': [11, 11, 11], 'var2': [12, 12, 12], 'var3': [13, 13, 13]})
df = pd.concat([df1, df2])
df['Submission Date'] = pd.to_datetime(df['Submission Date'])
# dummy extra variable for testing - behavior changes when the number of variables is odd vs even(??)
#df['extravar'] = [4, 4, 4, 14, 14, 14]
# Data for the tables
tabdf1 = pd.DataFrame({'group_name': ['group A', 'group A', 'group A'], 'Submission Date': ['2023-01-01', '2023-02-11', '2023-03-05'], 'variable': ['var1', 'var2', 'var3'], 'value': ['A var1 val', 'A var2 val', 'A var3 val']})
tabdf2 = pd.DataFrame({'group_name': ['group B', 'group B', 'group B'], 'Submission Date': ['2022-06-01', '2022-07-11', '2022-08-01'], 'variable': ['var1', 'var2', 'var3'], 'value': ['B var1 val', 'B var2 val', 'B var3 val']})
table_df = pd.concat([tabdf1, tabdf2])
# fns for building data update dropdown. x is the same every time so doesn't need a function
def updateTimePlotY(group_df, var):
return group_df[var]
def updateCells(tab_group_df, var):
cells_output = []
use = tab_group_df.loc[tab_group_df['variable'] == var]
for col in use.columns:
cells_output.append(use[col].tolist())
return cells_output
availableGroups = df['group_name'].unique().tolist()
columns = list(df.columns)
# drop columns not used for iteration
columns.pop(columns.index('Submission Date'))
columns.pop(columns.index('group_name'))
# list for the data update dropdown
dropdown_group_selector = []
# Fill out rest of dropdown with x/y/cell data for each group
for group_choice in availableGroups:
# subset data for table cells
table_df_group = table_df.loc[table_df['group_name'] == group_choice]
# subset data for scatterplot
group_subset_df = df.loc[df['group_name'] == group_choice]
# scatter x is same for each variable
x_data = group_subset_df['Submission Date']
# lists used for update method
xargs = []
yargs = []
cells = []
for i, c in enumerate(columns):
# x and y data for scatter
xargs.append(x_data)
yargs.append(updateTimePlotY(group_subset_df, c))
# table data
celldata = updateCells(table_df_group, c)
cells.append(dict(values=celldata))
dropdown_group_selector.append(dict(
args=[{'x': xargs,
'y': yargs,
'cells': cells,
#'header': header # header doesn't need to change
}],
label=group_choice,
method='update'
))
# Create dropdown for variable selection
dropdown_var_selector = []
# This is where things are weird. Before group selection, the figure has 2*ncol traces as initialized, and visible list
# should be true for [2*i, 2*i+1] to select the adjacent 2 subplots that correspond to a single variable.
# But after group selection data update, using 1*ncol and setting just [i] to True shows the correct data, although the
# legend is wrong. For some reason, this workaround only seems to work when there are an odd number of columns to plot.
for i, c in enumerate(columns):
vis = ([False] * (len(columns)))
vis_indices = [i]
#vis = ([False] * 2 * len(columns))
#vis_indices = [2*i, 2*i+1]
for v in vis_indices:
vis[v] = True
dropdown_var_selector.append(dict(
args=[{'visible': vis}],
label=c,
method='restyle'
))
# data to initialize the traces
group = availableGroups[0]
usedf = df.loc[df['group_name'] == group]
usedf_tab = table_df.loc[table_df['group_name'] == group]
fig = make_subplots(rows=2, cols=1,
specs=[[{'type': 'scatter'}],
[{'type': 'table'}]])
# Add traces for each column
for i, c in enumerate(columns):
vis = False
if i == 0:
vis = True
# Create scatter trace
trace = go.Scatter(x=usedf['Submission Date'], y=usedf[c],
mode='markers',
opacity=1,
marker_color='blue',
showlegend=True,
hovertemplate='Date: %{x}<br>Number: %{y}<extra></extra>',
visible=vis,
name=c
)
# Add that trace to the figure
fig.add_trace(trace, row=1, col=1)
# get data for table trace
initial_cells = updateCells(usedf_tab, c)
# Create and add second trace for data table
trace2 = go.Table(header=dict(values=['group', 'submission date', 'variable', 'value']),
cells=dict(values=initial_cells),
visible=vis,
name='table' + str(i)
)
fig.add_trace(trace2, row=2, col=1)
# update a few parameters for the axes
fig.update_xaxes(title='Date')
fig.update_yaxes(title='value', rangemode='nonnegative') # , fixedrange = True)
fig.update_layout(
title_text='double dropdown issue',
# hovermode = 'x',
height=1000,
width=850,
title_y=0.99,
margin=dict(t=140)
)
# Add the two dropdowns
fig.update_layout(
updatemenus=[
# Dropdown menu for choosing the group
dict(
buttons=dropdown_group_selector,
direction='down',
showactive=True,
x=0.0,
xanchor='left',
y=1.11,
yanchor='top'
),
# and for the variables
dict(
buttons=dropdown_var_selector,
direction='down',
showactive=True,
x=0.0,
xanchor='left',
y=1.06,
yanchor='top'
)
]
)
fig.show()