This question is basically an addendum to this previously asked question:
Properly setting up callbacks for dynamic dropdowns plotly dash
Now, I want to add a second trace to my plots which would be on a secondary y-axis. The data for the plot on the secondary y-axis would come from a similarly structured dict and dataframe, with similar naming conventions as well. Here is what I have.
app = JupyterDash(external_stylesheets=[dbc.themes.SLATE])
index1= [1,2,3,4]
columns1 =['time', '2m_temp_prod' , 'total_precip_prod']
index2= [1,2,3,4]
columns2 = ['time', '2m_temp_area', 'total_precip_area']
df_vals_prod = {'corn': pd.DataFrame(index=index1, columns = columns1,
data= np.random.randn(len(index1),len(columns1))).cumsum(),
'soybeans' : pd.DataFrame(index=index1, columns = columns1,
data= np.random.randn(len(index1),len(columns1))).cumsum()}
df_vals_area= {'corn': pd.DataFrame(index=index2, columns = columns2,
data= np.random.randn(len(index2),len(columns2))).cumsum(),
'soybeans' : pd.DataFrame(index=index2, columns = columns2,
data= np.random.randn(len(index2),len(columns2))).cumsum()}
# mimic data properties of your real world data
df_vals_prod['corn']['time'] = [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'),
Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_vals_prod['corn'].set_index('time', inplace = True)
df_vals_prod['soybeans']['time'] = [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'),
Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_vals_prod['soybeans'].set_index('time', inplace = True)
df_vals_area['corn']['time'] = [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'),
Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_vals_area['corn'].set_index('time', inplace = True)
df_vals_area['soybeans']['time'] = [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'),
Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_vals_area['soybeans'].set_index('time', inplace = True)
index3= [1,2,3,4]
columns3 =['time', '2m_temp_24hdelta_prod' , 'total_precip_24hdelta_prod']
index4= [1,2,3,4]
columns4 = ['time', '2m_temp_24hdelta_area', 'total_precip_24hdelta_area']
df_deltas_prod = {'corn': pd.DataFrame(index=index3, columns = columns3,
data= np.random.randn(len(index3),len(columns3))).cumsum(),
'soybeans' : pd.DataFrame(index=index3, columns = columns3,
data= np.random.randn(len(index3),len(columns3))).cumsum()}
df_deltas_area= {'corn': pd.DataFrame(index=index4, columns = columns4,
data= np.random.randn(len(index4),len(columns4))).cumsum(),
'soybeans' : pd.DataFrame(index=index4, columns = columns4,
data= np.random.randn(len(index4),len(columns4))).cumsum()}
# mimic data properties of your real world data
df_deltas_prod['corn']['time'] = [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'),
Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_deltas_prod['corn'].set_index('time', inplace = True)
df_deltas_prod['soybeans']['time'] = [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'),
Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_deltas_prod['soybeans'].set_index('time', inplace = True)
df_deltas_area['corn']['time'] = [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'),
Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_deltas_area['corn'].set_index('time', inplace = True)
df_deltas_area['soybeans']['time'] = [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'),
Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_deltas_area['soybeans'].set_index('time', inplace = True)
# weighting
all_options = {
'Production': list(df_vals_prod[list(df_vals_prod.keys())[0]].columns[1:]),
'Area': list(df_vals_area[list(df_vals_prod.keys())[0]].columns[1:])
}
controls = dbc.Card(
[ dbc.FormGroup(
[
dbc.Label("Crop"),
dcc.Dropdown(
id='crop_dd',
options=[{'label': k.title(), 'value': k} for k in list(df_vals_prod.keys())],
value=list(df_vals_prod.keys())[0],
clearable=False,
),
]
),
dbc.FormGroup(
[
dbc.Label("Weighting"),
dcc.Dropdown(
id='weight_dd',
options=[{'label': k, 'value': k} for k in all_options.keys()],
value='Area',
clearable=False,
),
]
),
dbc.FormGroup(
[
dbc.Label("Forecast Variable"),
dcc.Dropdown(
id='columns_dd',
clearable=False,
),
]
),
],
body=True,
)
app.layout = dbc.Container(
[
html.Hr(),
dbc.Row([
dbc.Col([
dbc.Row([
dbc.Col(controls)
], align="start"),
dbc.Row([
dbc.Col([
html.Br(),
dbc.Row([
dbc.Col([html.Div(id = 'txt1')
])
]),
html.Br(),
dbc.Row([
dbc.Col([html.Div(id = 'txt2')])
])
])
])
],xs = 2)
,
dbc.Col([
dbc.Row([
dbc.Col([html.Div(id = 'plot_title')],)
]),
dbc.Row([
dbc.Col(dcc.Graph(id="crop-graph")),
#dbc.Col(dcc.Graph(id="cluster-graph"))
])
])
],),
],
fluid=True,
)
# Callbacks #####################################################################
# Weighting selection.
@app.callback( # Dataframe PROD or AREA
Output('columns_dd', 'options'),
# layout element: dcc.RadioItems(id='weight_dd'...)
[Input('weight_dd', 'value')])
def set_columns_options(weight):
varz = [{'label': i, 'value': i} for i in all_options[weight]]
return [{'label': i, 'value': i} for i in all_options[weight]]
# Columns selection
@app.callback(
Output('columns_dd', 'value'),
[Input('columns_dd', 'options')])
def set_columns(available_options):
return available_options[1]['value']
# Crop selection
@app.callback(
Output('crop_dd', 'value'),
[Input('crop_dd', 'options')])
def set_crops(available_crops):
return available_crops[0]['value']
# Make a figure based on the selections
@app.callback( # Columns 2m_temp_prod, or....
Output('crop-graph', 'figure'),
[Input('weight_dd', 'value'),
Input('crop_dd', 'value'),
Input('columns_dd', 'value')])
def make_graph(weight, available_crops, vals):
# data source / weighting
if weight == 'Production':
dfv = df_vals_prod
#dfd = df_deltas_prod
if weight == 'Area':
dfv = df_vals_area
#dfd= df_deltas_area
# plotly figure
fig = make_subplots(specs=[[{"secondary_y": True}]])
if 'precip' in vals:
fig.add_trace(go.Scatter(x=df_vals_prod[available_crops]['time'], y=round((dfv[available_crops][vals].cumsum()/25.4),2),
mode = 'lines', line=dict(color='lime', width=4),
hovertemplate='Date: %{x|%d %b %H%M} UTC<br>Precip: %{y:.2f} in<extra></extra>'), secondary_y=False)
else:
fig.add_trace(go.Scatter(x=df_vals_prod[available_crops]['time'], y=round(((dfv[available_crops][vals]-273.15)*(9/5))+32,2),
mode = 'lines', line=dict(color='red', width=4),
hovertemplate='Date: %{x|%d %b %H%M} UTC<br>Temp: %{y:.2f} F<extra></extra>'), secondary_y=False)
#fig.add_trace(go.Bar(x=dfd[available_crops].index, y=dfd[available_crops][deltas]), secondary_y=True)
fig.update_layout(title=dict(text='Crop: ' + available_crops.title() + ', Weight: ' +weight+ ', Variable: '+ vals))
fig.update_layout(yaxis2_showgrid=False,showlegend=False,
width=1500,height=800,yaxis_zeroline=False, yaxis2_zeroline=False)
fig.update_layout(template="plotly_dark", plot_bgcolor='#272B30', paper_bgcolor='#272B30')
fig.update_yaxes(automargin=True)
return(fig)
app.run_server(mode='external', port = 8099)
Notice how the dict names and the dataframe columns within the dicts have similar names. I would like those to stay together on the plot.
For example, the user selects Weighting: Production, Crop: Corn, Forecast Variable: 2m_temp_prod. This should plot a line plot. Now, I want to add a secondary y-axis, where 2m_temp_24hdelta_prod is plotted (comes from df_deltas_prod['corn']['2m_temp_24hdelta_prod']. Notice though that I don't want a dropdown for this, I just want it to be plotted based on the other dropdown selections. Finally, if the user switches to Weighting: Area, Crop: Corn, Forecast Variable: 2m_temp_area, the secondary y-axis would have plotted df_deltas_area['corn']['2m_temp_24hdelta_area']. Hope this is clear.