Creating dropdown with multiple options in loop

@LOVESN

I think I gave you all details to succeed yourself in defining the new Figure version.

1 Like

How would I update the title of the plot to reflect the column name each time a new dropdown is selected? Basically the dropdown and title name would be the same.

@LOVESN

To update the plot title, you should first asssign a title to layout.title.text, and then to perform a title update in each button arg. In this case since you are updating both data and layout the restyle method, must be replaced by update:

import pandas as pd
import numpy as np
import plotly.graph_objects as go
from  plotly.subplots import make_subplots

df = pd.DataFrame({'xdata': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
                   'A': np.random.randint(23, 51, 10),
                   'B': np.random.randint(30, 65, 10),
                   'C': np.random.randint(27, 63, 10),
                   'D': np.random.randint(40, 78, 10)})


fig = make_subplots(specs=[[{"secondary_y": True}]])

fig.add_trace(go.Scatter(x=df['xdata'], y=df['A'], mode='lines', line_color='RoyalBlue'), secondary_y=False)

y_bar = np.random.randint(3, 10, 5)
fig.add_trace(go.Bar(x= [2, 4, 6, 8, 10], y= y_bar, width=0.5,  marker_color='#E9967A'), secondary_y=True)

cols = df.columns[1:]
linecolors = ['RoyalBlue', 'red', 'green', 'magenta']
barcolors = ['#E9967A', '#8FBC8F', '#483D8B','#2F4F4F']


y_barupdate = [y_bar, np.random.randint(3, 8, 5), np.random.randint(2, 10, 5), np.random.randint(4, 11, 5)]
my_buttons = [dict(method = "update",
                
                   args = [{'y': [df[c],   y_barupdate[k]],
                           'line.color': linecolors[k],
                           'marker.color': barcolors[k]}, 
                           {'title.text': f'Column: {c}'}, [0,1]],
                   label = c) for k, c in enumerate(cols)]

fig.update_layout(title_text = f'Column: {cols[0]}',
                  title_x =0.5,
                  width=800, height=400,
                  updatemenus=[dict(active=0,
                                   x= -0.2, y=1, 
                                   xanchor='left', 
                                   yanchor='top',
                                   buttons=my_buttons)
                              ]) 

@empet When I add in the yaxis.range calls the plot does not update when selecting a new dropdown button. What might be causing this?

my_buttons = [dict(method = "update",
                
                   args = [{'y': [df[c],   y_barupdate[k]],
                           'line.color': linecolors[k],
                           'marker.color': barcolors[k],
                           'yaxis.range': [np.min(df[c]),np.max(df[c])],
                           'yaxis2.range': [np.min(y_barupdate[k]), np.max(y_barupdate[k])]}, 
                           {'title.text': f'Column: {c}'}, [0,1]],
                   label = c) for k, c in enumerate(cols)]

@LOVESN

The updates for layout must be inserted in a new dict, not in the same with data,
i.e.

my_buttons = [dict(method = "update",
                
                   args = [{'y': [df[c],   y_barupdate[k]],
                           'line.color': linecolors[k],
                           'marker.color': barcolors[k]},
                          { 'yaxis.range': [np.min(df[c]),np.max(df[c])],
                           'yaxis2.range': [np.min(y_barupdate[k]), np.max(y_barupdate[k])], 
                           'title.text': f'Column: {c}'}, [0,1]],
                      label = c) for k, c in enumerate(cols)]

I gave you a few links. If you are reading the notebook destinated to update method https://chart-studio.plotly.com/~empet/15605/update-method-called-within-an-update/#/ you can see the following rule:

The args to be passed to the update method consist in two dicts and a list.
The first dict defines updates for data and data attributes,
the second dict updates layout attributes, while the list is a list of trace indices (indices of elements in the list fig.data, that are updated within the first dict).

args = [{'y': [y0_new, y_2_new],
         'marker_color': ['green', 'magenta'}, #dict for data updates
        {'yaxis.type': 'log'}, #dict for layout updates
        [0, 2] #list of trace indices
1 Like

Thanks for all your help with this code @empet . I now have (what I assume) a more difficult task of adding a second data source. For now, the plots I have constructed are all for temperatures. Now I want to add in precipitation data. The data works the same, i.e., there is a line plot and a second y-axis with bars. The times are also the same. How would I go about incorporating this into a new button. My current button has 4 options, denoting 4 different crop belts. I want the new button to have 2 options, one denoted Temperature and the other Precipitation. Thus, I could click “Corn” and “Precipitation”, or “Corn” and “Temperature” and the data would change accordingly.

 my_buttons = list([[dict(method = "update",
                       args = [{'y': [((df_temps_prod[c]-273)*(9/5)+32), round((df_temps_deltas_prod[c]*(9/5)),2)]},
                       {'title.text': 'Temp and 24-hour Temp Delta for: '+c.title(),
                       'yaxis.range': [myround(np.min(((df_temps_prod[c]-273)*(9/5)+32)-10)), myround(np.max(((df_temps_prod[c]-273)*(9/5)+32)+10))]},[0,1]],
                       label = c.title()) for k,c in enumerate(cols)],
                   dict(method = 'update',
                       args = [{'y': [(df_precip_prod[c]/25.4).cumsum(), round(df_precip_deltas_prod[c]/25.4,2)]},[0,1]],
                       label = c.title()) for k,c in enumerate(cols)])

I tried doing something like this, but it doesn’t seem to work. For reference, the column names are the same in both dataframes.

@LOVESN

Let us say you have already defined, buttonA, buttonB, buttonC, buttonD. For precipitation data you’ll define more 4 buttons: button1, button2, button3, button4. In this case the definition of updatemenus contains two dicts: one for the dropdown menu to update temeprature data, and the second one for precipitation data:

updatemenus=[dict(
                                   active=0,
                                   x= -0.2, y=1, 
                                   xanchor='left', 
                                   yanchor='top',
                                   buttons=[buttonA, buttonB, buttonC, buttonD]),
                              
                               dict(
                                   active=0,
                                   x= -0.2, y=0.75,  #The position of this second dropdown menu is assigned by trial and error
                                   xanchor='left', 
                                   yanchor='top',
                                   buttons=[button1, button2, button3, button4])
                              ])  

@empet This helps me understand how the buttons work but isn’t exactly what I am looking for. I want a dropdown with 4 different options, and the second dropdown to only have 2 options. Here are two images for what I am trying to do.

The data changes dynamically based on the selection. For example, I can click “corn” and “total_precip” or “corn” and “2m_temps”. Does that make sense? I want to do something similar with my dropdowns. When I click “corn” and then click “temperature”, I want my dropdown to display that specific data. However, now I want to display precipitation data (but still for corn), so I change menu 2 to "precipitation. Is that possible to do?

Hi @empet. Have you been able to take a look at the post above? Hope you can help as I am still stuck. Thanks!

Hi @LOVESN

I can look at your plots, but without data and code I cannot express any opinion. It’s difficult to understand what’s going on from a story about temperature and precipitation.

Let me get back to you. Trying something new here. If I cannot get it to work, I will reach out once more.

Okay, I am back to the same point I was before. Here is the situation:

I have a plot that has two buttons, each button having 4 options. The 4 options represent a crop, in this case corn, soybeans, winter wheat and spring wheat. The first button represents temperature and the second button represents precipitation. The plot looks like so:

However, this is not what I want. I still want to have 2 buttons, but I want the data to be different and can’t figure out how to make it work. In this case, I want button 1 to contain 4 options, with the options being the crops listed above. Then, I want the second button to have 2 options, being temperature and precipitation.

As a result, I could select ‘corn’ and ‘temperature’ and it would plot. Then, say I want to keep the first option the same, ‘corn’, but I want to look at ‘precipitation’ now instead. How can I create code that will dynamically switch these data? I essentially want to clean up the dropdowns to make them more clear. Is my question clear? @empet

Edit: I can share code for my data sources if need be.

So I made a little schematic to try and help inform what I am trying to do.

Each crop has two sets of data associated with it. One is temperature and the other is precipitation. The name of the crop is just an identifier for which temperature or precipitation dataset to plot. Hopefully this adds some value to what is happening. @empet

@LOVESN

I haven’t understood yet, because it’s not clear how is defined your fig.data, i.e. how many traces is contains.

Eventually take a look at this answer: Advanced Dropdown Menus with Plotly if you want to selectively reset or ignore certain properties when restyling. Besides 'undefined' there is the option 'null'. `null resets a property to the default one, whileundefined`` applies no change to the current state.

*In the above answer replace “two each” by “To each” :slight_smile:

Hi @empet. Thanks for continuing to take the time to reply. This still isn’t quite what I am trying to accomplish. The example you provided has two dropdowns that are independent of the other.

I will provide the data I am working with to try and help better explain.

I have two dictionaries, each with 4 keys. One dictionary, df_vals, represents the actual data over a specified time. The second dictionary, df_deltas, represents the change of those values in the last 24 hours. The data is updated every 24 hours with the latest and great data.

df_vals.keys()
dict_keys(['corn', 'soybeans', 'winterwheat', 'springwheat'])

df_deltas.keys()
dict_keys(['corn', 'soybeans', 'winterwheat', 'springwheat'])

Here is what the first key looks like for both:

df_vals['corn'].head()
time	2m_temp_prod	2m_temp_area	total_precip_prod	total_precip_area
0	2020-08-29 00:00:00	299.346777	299.799234	0.000000	0.000000
1	2020-08-29 06:00:00	294.039512	294.443352	0.191070	0.286952
2	2020-08-29 12:00:00	292.959274	293.182931	0.155765	0.216606
3	2020-08-29 18:00:00	301.318046	301.767516	0.421768	0.485691
4	2020-08-30 00:00:00	300.623567	300.979650	0.363572	0.501164
df_deltas['corn'].head()
time	2m_temp_24hdelta_prod	2m_temp_24hdelta_area	total_precip_24hdelta_prod	total_precip_24hdelta_area
0	2020-08-29	-0.330566	-0.294223	-1.441738	-0.896948
1	2020-08-30	-0.063527	-0.066953	-3.242770	-2.002193
2	2020-08-31	-0.276225	-0.238248	-1.954929	-1.326568
3	2020-09-01	-0.778811	-0.747444	1.080549	0.523297
4	2020-09-02	-0.121823	-0.008793	-2.857210	-1.974432

So we can see that we have two variables in each dictionary key that we want to plot. Temperature and precipitation. For this example, let’s focus on the columns that end in ‘prod’.

df_vals['corn']['time']
0    2020-08-29 00:00:00
1    2020-08-29 06:00:00
2    2020-08-29 12:00:00
3    2020-08-29 18:00:00
4    2020-08-30 00:00:00
             ...        
56   2020-09-12 00:00:00
57   2020-09-12 06:00:00
58   2020-09-12 12:00:00
59   2020-09-12 18:00:00
60   2020-09-13 00:00:00
Name: time, Length: 61, dtype: datetime64[ns]

df_deltas['corn']['time']
0    2020-08-29
1    2020-08-30
2    2020-08-31
3    2020-09-01
4    2020-09-02
5    2020-09-03
6    2020-09-04
7    2020-09-05
8    2020-09-06
9    2020-09-07
10   2020-09-08
11   2020-09-09
12   2020-09-10
13   2020-09-11
14   2020-09-12
15   2020-09-13

Now, I want to plot this data using a double y-axis plot, with the actual values as a line and the 24-hour changes as a bar. I can do that like so:

fig = make_subplots(specs=[[{"secondary_y": True}]])
fig.add_trace(go.Bar(x=df_deltas['corn']['time'], y=round((df_deltas['corn']['2m_temp_24hdelta_prod']*(9/5)),2), marker_color='black', opacity=0.6, hovertemplate='Date: %{x|%d %b}<br>Delta: %{y:.2i} F<extra></extra>'),secondary_y=True)
fig.add_trace(go.Scatter(x=df_vals['corn']['time'], y=((df_vals['corn']['2m_temp_prod']-273)*(9/5)+32),mode='lines', line=dict(color='red', width=4), hovertemplate='Date: %{x|%d %b %H%M} UTC<br>Temp: %{y:.2f} F<extra></extra>'))

Great! We have a plot that takes in one entry from one of the dictionary keys, creating a plot like this (the dates are wrong, as this is an older plot):

The precipitation plot looks similar except that it is a cumulative sum of the values over time instead of the instantaneous value at each 6 hour interval.

Now, we want to make it dynamic! Let’s add some dropdowns so the user can change the data around themselves. I understand how to add one dropdown, that isn’t too hard. However, now I want to add a second dropdown. We want the user to be able to select which crop they want to look at (corn, soybeans, winter wheat or spring wheat), but also what variable to plot (temperature or precipitation). As such, we want two dropdowns. The first would have 4 options: corn, soybeans, winter wheat and spring wheat. The second dropdown will have two options: temperature and precipitation.

So if it is set up properly, a user could select ‘corn’ from the first dropdown and ‘temperatures’ from the second dropdown. As such, the temperature data from the ‘corn’ key the dictionary will plot. However, if the user then selects ‘soybeans’, while leaving ‘temperature’ still selected, the data would also change because the data for ‘corn’ and ‘soybeans’ are not the same. How would I accomplish this?

Sorry for the novel, but hope this is more clear.

@LOVESN

I didn’t point out that example for its two different plots on which the buttons act, but to suggest that you have the option to perform updates/restyling on a trace and keep the other to the initial settings or don’t apply any update to its the previous state. In a few minutes you decided that my suggestion is not suitable for your case. However this is the solution. Please think at it!

1 Like

very sad you didn’t point out the examples and I wish you will get the nice examples

@empet I really appreciate all of the help you have provided me and hope you can help me figure out this issue. Unfortunately, I cannot figure out how the example you provided can help me attain my goal. I am still stuck, but have approached the problem in a different way now. I will try to summarize below and see if you can help.

I am working with two dictionaries:

df_vals.keys()

dict_keys(['corn', 'soybeans', 'winterwheat', 'springwheat'])

df_deltas.keys()

dict_keys(['corn', 'soybeans', 'winterwheat', 'springwheat'])

Sample of one of the keys:

df_vals['corn'].head()

time	2m_temp_prod	2m_temp_area	total_precip_prod	total_precip_area
0	2020-09-03 00:00:00	299.346777	299.799234	0.000000	0.000000
1	2020-09-03 06:00:00	294.039512	294.443352	0.191070	0.286952
2	2020-09-03 12:00:00	292.959274	293.182931	0.155765	0.216606
3	2020-09-03 18:00:00	301.318046	301.767516	0.421768	0.485691
4	2020-09-04 00:00:00	300.623567	300.979650	0.363572	0.501164 

df_deltas['corn'].head()

time	2m_temp_24hdelta_prod	2m_temp_24hdelta_area	total_precip_24hdelta_prod	total_precip_24hdelta_area
0	2020-09-03	-0.330566	-0.294223	-1.441738	-0.896948
1	2020-09-04	-0.063527	-0.066953	-3.242770	-2.002193
2	2020-09-05	-0.276225	-0.238248	-1.954929	-1.326568
3	2020-09-06	-0.778811	-0.747444	1.080549	0.523297
4	2020-09-07	-0.121823	-0.008793	-2.857210	-1.974432

Now, I go on to make the plot.

fig = make_subplots(specs=[[{"secondary_y": True}]])

time_vals=df_vals['corn']['time']
time_deltas=df_deltas['corn']['time']

temp_prod_vals=df_vals['corn']['2m_temp_prod']
temp_prod_deltas=df_deltas['corn']['2m_temp_24hdelta_prod']


fig.add_trace(go.Scatter(x=time_vals, y=((temp_prod_vals-273)*(9/5)+32),mode='lines', line=dict(color='red', width=4), yaxis='y1', hovertemplate='Date: %{x|%d %b %H%M} UTC<br>Temp: %{y:.2f} F<extra></extra>'),secondary_y=False)
fig.add_trace(go.Bar(x=time_deltas, y=round((temp_prod_deltas*(9/5)),2), marker_color='black', opacity=0.6, yaxis='y2', hovertemplate='Date: %{x|%d %b}<br>Delta: %{y:.2i} F<extra></extra>'),secondary_y=True)


button1= [dict(method= 'update',
                args= [{'y': [((df_vals[i]['2m_temp_prod']-273)*(9/5)+32), round((df_deltas[i]['2m_temp_24hdelta_prod']*(9/5)),2)]}],
                label=i) for i, j in list(zip(df_vals.keys(), df_deltas.keys()))]
button2= [dict(method= 'update',
                args= [{'y': [(df_vals[i]['total_precip_prod'].cumsum())/25.4, round((df_deltas[i]['total_precip_24hdelta_prod']/25.4),2)]}],
                label=i) for i, j in list(zip(df_vals.keys(), df_deltas.keys()))]

fig.update_layout(yaxis2_showgrid=False, title_text='Plot title',showlegend=False,
                  title_x=0.4,
                  width=850,
                  height=450,annotations=
                  [dict(text='Temps:', x=0.02, xref='paper', y=1.12, yref='paper', align='left', showarrow=False),
                  dict(text='Precip:', x=0.36, xref='paper', y=1.12, yref='paper', align='left', showarrow=False)],
                  updatemenus=[dict(active=0,
                                   x=0.1, y=1.18, 
                                   pad={"r": 10, "t": 10},
                                   xanchor='left', 
                                   yanchor='top',
                                   buttons=button1),
                               dict(active=0,
                                   x=0.40, y=1.18, 
                                   pad={"r": 10, "t": 10},
                                   xanchor='left', 
                                   yanchor='top',
                                   buttons=button2)
                              ])

This produces this plot:

This plot is fine and works as intended. However, since both of the dropdown columns have the same name, I think the plot would look more tidy if we combined the similar dropdown options into one button, i.e., button 1 would be (corn, soybeans, winter wheat and spring wheat). Button two then would be the text next to the button (the actual variable we are plotting), temps and precip.

For the life of me I cannot figure out how to code this up, as I have been stuck on this issue for weeks.

Have you had a chance to look at my most recent post? @empet I am still stuck. I will be moving onto trying Bokeh soon if I cannot figure this issue out. Thanks for the help!

I believe I have discovered what I am trying to do is actually impossible in the Python version of Plotly. This would make sense as to why it’s been so difficult. Can you confirm @empet ?