Search code examples
pythonplotly

Plotly updatemenus - Only update specific parameters


I'm looking for a way to have two updatemenu buttons on a plotly figure. One changes the data and the y-axis title and one switches from linear to log scale. Broadly the code below works but I lose something depending on which method I use.

If the buttons method is update then when I switch parameters it defaults back to linear scale. If I use restyle the y-axis title stops updating. Is there a method to only partially update I've missed?

import plotly.graph_objects as go
import pandas as pd


def main():
    figure = go.Figure()

    df = pd.DataFrame([['sample 1', 1, 5, 10],
                       ['sample 2', 2, 20, 200],
                       ],
                      columns=['sample id', 'param1', 'param2', 'param3']
                      )
    options = ['param1', 'param2', 'param3']

    figure.add_trace(go.Bar(x=df['sample id'], y=df[options[0]]))
    figure.update_yaxes(title=options[0])

    buttons = []
    for option in options:
        buttons.append({'method': 'restyle',  ### this can be update or restyle, each has different issues
                        'label': option,
                        'args': [{'y': [df[option]],
                                  'name': [option]},
                                 {'yaxis': {'title': option}},
                                 [0]]
                        })
    scale_buttons = [{'method': 'relayout',
                      'label': 'Linear Scale',
                      'args': [{'yaxis': {'type': 'linear'}}]
                      },
                     {'method': 'relayout',
                      'label': 'Log Scale',
                      'args': [{'yaxis': {'type': 'log'}}]
                      }
                     ]
    figure.update_layout(yaxis_title=options[0],
                         updatemenus=[dict(buttons=buttons,
                                           direction='down',
                                           x=0,
                                           xanchor='left',
                                           y=1.2,
                                           ),
                                      dict(buttons=scale_buttons,
                                           direction='down',
                                           x=1,
                                           xanchor='right',
                                           y=1.2,
                                           )],
                         )
    figure.show()


if __name__ == '__main__':
    main()

Solution

  • You need to use the update method for the first dropdown (restyle only updates data, update updates both data and layout - nb. those methods refer to their respective Plotly.js function, see Plotly.relayout and Plotly.update).

    Now to fix the issue, the key is to use "attribute strings", that is, 'yaxis.title': option instead of 'yaxis': {'title': option} in the arguments, otherwise the update will reset the yaxis params other than title to their defaults (same applies for the yaxis type) :

    The term attribute strings is used to mean flattened (e.g., {marker: {color: 'red'}} vs. {'marker.color': red}). When you pass an attribute string to restyle inside the update object, it’s assumed to mean update only this attribute. Therefore, if you wish to replace and entire sub-object, you may simply specify one less level of nesting.

    buttons = []
    for option in options:
        buttons.append({
            'method': 'update',
            'label': option,
            'args': [{
                'y': [df[option]],
                'name': [option]
            }, {
                'yaxis.title': option
            }]
        })
    
    scale_buttons = [{
        'method': 'relayout',
        'label': 'Linear Scale',
        'args': [{'yaxis.type': 'linear'}],
    }, {
        'method': 'relayout',
        'label': 'Log Scale',   
        'args': [{'yaxis.type': 'log'}]
    }]