Update yaxis range with updatemenus button

Is there a way to update the yaxis range on button click? I am using buttons to toggle source data on a 2y axis chart, and need to manually define the axis ranges for each data series (due to the tick alignment issue that emerges in the default axis range when using 2y axis charts).

For a single 2y chart (i.e., no buttons) I’ve employed the code below, which works fine. Curious if there is a way to update these fields on click. I’ve tried flowing them into buttons.args, but I do not believe (based on external documentation) that these specific attributes are supported.

#Y-axis1 format
fig.update_yaxes(title_text='First yaxis title', tickformat=y1_label_format, range=[y1_min,y1_max], tickvals=y1_tickvals, secondary_y=False)
    
#Y-axis2 format
fig.update_yaxes(title_text='Second yaxis Title', tickformat=y2_label_format, range=[y2_min,y2_max], tickvals=y2_tickvals, secondary_y=True)

I’ve also considered making multiple superimposed, independent charts with buttons to show only the selected chart (and hide the others), although I have not seen any reference on successfully hiding entire charts with buttons (rather than single traces / elements).

Any guidance would be much appreciated…

Hi @petership,

Welcome to the community!

You can look here for an example on how to adjust the layout/data sections of a graph using “updatemenus”.

Otherwise you could create your own buttons and callbacks if you need more flexibility - like to selectively show/hide entire charts. I could elaborate on this if necessary.

I hope I’ve understood your question. :sweat_smile:

Hi @suiroc - thanks for the response. I had seen this page. Let me try to be more specific. In order to make the axes align when I update the underlying plot data, I need to be able to update the following fields on button click: range (y1 axis), range (y2 axis), tickvals (y1 axis), tickvals (y2 axis). These four + the visible field make 5 attributes that need to be updated on button click. I am trying to understand the appropriate way to flow these arguments into updatemenus.buttons. I have tried using a dict similar to the annotations example on the page you have shared, but it was not working. I am not sure whether this is a syntax issue, or whether these arguments are simply not supported with buttons.

Hi @petership,

The updates to be performed are layout updates. This notebook https://chart-studio.plotly.com/~empet/15608 explains how relayout works, with a few detailed examples, including updating yaxis1 and yaxis2.

2 Likes

@empet From example 2, I see that with sliders you can call ‘xaxis.range’. Is that also possible with buttons? For example, can I provide a yaxis range that depends on the data provided in different dropdown options?

Something like {‘yaxis.range’ : [something, something]}

@LOVESN,

Yes you may!!! But if you are referring to the already discussed updates, then you must call the update methd, not relayout, because you are performing both data and layout updates.

I’m facing sort of a similar issue.
I have two subplots and I’m trying to individually provide axis titles for each using buttons. But they seem to be using a shared axis and hence the plots are overlapping.

Being able to provide range to the axis would ideally solve the overlap of subplots while using buttons issue.

Thanks for the help thus far @empet! I now have a semi-related issue with changing format fields on button click.

I have a figure with three subplots - one row and three columns to be exact. Is there a way to update the xaxis title for each individual subplot on button click?

I found this page Changing the xy-Axis Titles with Dropdown Menu which shows how to update axis title of a single plot w/ buttons. Unfortunately I cannot figure out how to translate it to work for multiple subplots. Thanks!

@petership

You didn’t give details on the initial xaxes title before update, for each subplot. That’s why I created this example you can modify acccording to your needs:

import plotly.graph_objects as go
from plotly.subplots import make_subplots

fig = make_subplots(rows=1, cols=3)

print(fig.layout)

fig.add_trace(go.Scatter(x=[1,2], y=[3,4]), 1, 1)
fig.add_trace(go.Scatter(x=[1,2], y=[3,4]), 1, 2)
fig.add_trace(go.Scatter(x=[1,2], y=[3,4]), 1, 3)
fig.update_xaxes(title='x')

button1 = dict(method = "relayout",
               args = [{'xaxis.title': 'first x',
                        'xaxis2.title': 'x',
                        'xaxis3.title': 'x'},
                          ],
               label = 'xaxis1')

button2 = dict(method = "relayout",
               args = [{'xaxis.title': 'x',
                        'xaxis2.title': 'second x',
                        'xaxis3.title': 'x'
                           } 
                          ],
               label = 'xaxis2')
button3 = dict(method = "relayout",
               args = [{ 'xaxis.title': 'x',
                         'xaxis2.title': 'x',
                         'xaxis3.title': 'third x'} 
                          ],
               label = 'xaxis3')
fig.update_layout(width=800, height=400,
                 updatemenus=[dict(active=0,
                                   x= -0.25, y=1, 
                                   xanchor='left', 
                                   yanchor='top',
                                   buttons=[button1, button2, button3])
                              ]) 
1 Like

Solved it! Thanks again @empet!

Hello, @empet
I’m facing a similar issue.

I want to design the Dash app layout in such a way, that two trend/line charts are arranged in the same container/figure and I want to switch it with a button to display one chart at a time, the x-axis is the same for both charts.

I have tried with the below code but it doesn’t update the label of the y-axis and title of hover text according to charts.
Your guidance would be helpful.

    dfx = pd.DataFrame({'months':['apr','may','jun','jul','aug','sep','oct','nov','dec','jan','feb','mar']
          ,'imported_qty':[25,35,45,30,35,45,50,25,30,35,45,40]
          ,'unit_price':[1.80,1.75,2.10,2.08,2.25,2.20,2.35,2.38,2.28,2.32,2.38,2.51]})
    
    

    fig = px.line(dfx, x='months', y=dfx['imported_qty'])
    
    fig.update_traces(mode="lines")

    fig.update_layout(yaxis={'showgrid': False}
                     ,xaxis={'showgrid': False}
                     ,template='plotly_dark'
                     ,hovermode="x"
                     ,legend=dict(y=1, x=1, font=dict(size=8))
                     ,height=350
                     ,font=dict(size=10, color='gray')
                     ,title={'text': "Import Qty. Trend"
                             ,'y':0.95
                             ,'x':0.5
                             ,'xanchor': 'center'
                             ,'yanchor': 'top'
                             ,'font_size':15
                             ,'font_color':'white'}
                     ,updatemenus=[
                                dict(
                                    type="buttons",
                                    direction="right",
                                    x=0.7,
                                    y=1,
                                    showactive=True,
                                    buttons=list(
                                        [
                                            dict(
                                                label="Qty",
                                                method="update",
                                                args=[{"y": [dfx['imported_qty']]}
                                                     ,{'title':'Import Qty. Trend'}
                                                     ,{'yaxis.title':'Import qty.'}],
                                            ),
                                            dict(
                                                label="Price",
                                                method="update",
                                                args=[{"y": [dfx['unit_price']]}
                                                     ,{'title':'Unit Price Trend'}
                                                     ,{'yaxis.title':'Unit Price'}],
                                            ),
                                        ]
                                    ),
                                )
                            ]
                     )

trend chart qty and unit price

HI @Chirag
If I understand correctly, you want one chart with the price trend line and one chart with the Qty trend line inside a Dash app layout. But you only want one chart to appear at a time, based on a button (maybe a bolean Switch) clicked by the user.

Did I get that right?

@adamschroeder
Yes, you got it right.
Thanks for the quick reply.

Hi @Chirag
Here’s an example of a Boolean switch that allows user to shift between graphs. In this example, we’re just switching between a graph with markers and a graph with lines, but you can update that to show different graphs.

from dash import Dash, html, dcc, Output, Input
import plotly.express as px
import pandas as pd
import dash_daq as daq

app = Dash(__name__)

dfx = pd.DataFrame({'months': ['apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec', 'jan', 'feb', 'mar']
                       , 'imported_qty': [25, 35, 45, 30, 35, 45, 50, 25, 30, 35, 45, 40]
                       , 'unit_price': [1.80, 1.75, 2.10, 2.08, 2.25, 2.20, 2.35, 2.38, 2.28, 2.32, 2.38, 2.51]})

app.layout =html.Div([
    html.Div(children=[], id='my-graph'),
    daq.BooleanSwitch(
        id='switch-graph',
        on=True,
        label="Choose Graph",
        labelPosition="top"
    )
])

@app.callback(
    Output(component_id='my-graph', component_property='children'),
    Input(component_id='switch-graph', component_property='on')
)
def update_output_div(power):
    if power is True:
        fig = px.line(dfx, x='months', y=dfx['imported_qty'])
        fig.update_traces(mode="lines")
        return dcc.Graph(figure=fig)
    else:
        fig = px.line(dfx, x='months', y=dfx['imported_qty'])
        fig.update_traces(mode="markers")
        return dcc.Graph(figure=fig)


if __name__ == '__main__':
    app.run_server(debug=False)
2 Likes

Hello @adamschroeder
Thank you very much.
I was unaware of the dash DAQ controls.
You are a great teacher, I learn so much from your Youtube channel.

1 Like

Hello @adamschroeder
I have added one more input to the callback and edit update function. but the output doesn’t show the chart, only dropdown and switch are there.

after the switch, I want to introduce dropdown as a list of importers.

here is the code

from dash import Dash, html, dcc, Output, Input
import plotly.express as px
import pandas as pd
import dash_daq as daq

app = Dash(__name__)

dfx = pd.DataFrame({'months': ['apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec', 'jan', 'feb', 'mar']
                    , 'imported_qty': [25, 35, 45, 30, 35, 45, 50, 25, 30, 35, 45, 40]
                    , 'unit_price': [1.80, 1.75, 2.10, 2.08, 2.25, 2.20, 2.35, 2.38, 2.28, 2.32, 2.38, 2.51]
                    ,'importer_name': ['a','a','a','a','a','a','b','b','b','b','b','b']})

importer_list = dcc.Dropdown(id='importer_list'
                             ,options=[{'label':x, 'value':x} for x in dfx['importer_name'].unique()]
                             ,value=['a']
                             ,persistence='string'
                             ,persistence_type='session'
                             ,multi=True)

app.layout =html.Div([
    html.Div(children=[], id='my-graph'),
    daq.BooleanSwitch(
        id='switch-graph',
        on=True,
        label="Choose Graph",
        labelPosition="top"
    )
    ,importer_list
])

@app.callback(
    Output(component_id='my-graph', component_property='children'),
    [Input(component_id='switch-graph', component_property='on')
     ,Input(component_id='importer_name', component_property='value')])


def update_output_div(importer_name, power):
    dfx = dfx[dfx.importer_name==importer_name]
    if power is True:
        fig = px.line(dfx, x='months', y=dfx['imported_qty'])
        fig.update_traces(mode="lines")
        return dcc.Graph(figure=fig)
    else:
        fig = px.line(dfx, x='months', y=dfx['unit_price'])
        fig.update_traces(mode="lines")
        return dcc.Graph(figure=fig)


if __name__ == '__main__':
    app.run_server(debug=False)

Hey @Chirag
You have a few small mistakes.
1.

@app.callback(
    Output(component_id='my-graph', component_property='children'),
    [Input(component_id='switch-graph', component_property='on')
     ,Input(component_id='importer_name', component_property='value')])

importer_name is not a component id; it is a column. You need to assign a component id.

def update_output_div(importer_name, power):

the first argument inside the callback function refers to the first input. In this case, they don’t align. They need to be switched.

dfx = dfx[dfx.importer_name==importer_name]

when Dropdown multi prop is True, the value type is a list. As a result, importer_name is a list, so the way you are slicing your data in pandas will not work.

  1. Always make a copy of the global data you are updating inside a callback. It’s not best practice to modify the global data. You can do something like:
    dfxx = dfx[dfx.importer_name.isin(value)]

Final Code:

from dash import Dash, html, dcc, Output, Input
import plotly.express as px
import pandas as pd
import dash_daq as daq

app = Dash(__name__)

dfx = pd.DataFrame({'months': ['apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec', 'jan', 'feb', 'mar']
                    , 'imported_qty': [25, 35, 45, 30, 35, 45, 50, 25, 30, 35, 45, 40]
                    , 'unit_price': [1.80, 1.75, 2.10, 2.08, 2.25, 2.20, 2.35, 2.38, 2.28, 2.32, 2.38, 2.51]
                    ,'importer_name': ['a','a','a','a','a','a','b','b','b','b','b','b']})

importer_list = dcc.Dropdown(id='importer_list'
                             ,options=[{'label':x, 'value':x} for x in dfx['importer_name'].unique()]
                             ,value=['a']
                             ,persistence='string'
                             ,persistence_type='session'
                             ,multi=True)

app.layout =html.Div([
    html.Div(children=[], id='my-graph'),
    daq.BooleanSwitch(
        id='switch-graph',
        on=True,
        label="Choose Graph",
        labelPosition="top"
    )
    ,importer_list
])

@app.callback(
    Output(component_id='my-graph', component_property='children'),
    [Input(component_id='switch-graph', component_property='on')
     ,Input(component_id='importer_list', component_property='value')])


def update_output_div(power, value):
    dfxx = dfx[dfx.importer_name.isin(value)]
    if power is True:
        fig = px.line(dfxx, x='months', y=dfxx['imported_qty'])
        fig.update_traces(mode="lines")
        return dcc.Graph(figure=fig)
    else:
        fig = px.line(dfxx, x='months', y=dfxx['unit_price'])
        fig.update_traces(mode="lines")
        return dcc.Graph(figure=fig)


if __name__ == '__main__':
    app.run_server(debug=False)
1 Like