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()
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'}]
}]