There are good news and bad news:
The bad news: There is no straightforward tweak for this and you might need to use javascript too.
The good news: Here are two options to tackle the requirement:
Option#1: javascript
import plotly.graph_objects as go
import pandas as pd
# load dataset
df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/volcano.csv")
# create figure
fig = go.Figure()
# Add surface trace
fig.add_trace(go.Surface(z=df.values.tolist(), colorscale="Viridis"))
# Update plot sizing
fig.update_layout(
width=800,
height=900,
autosize=False,
margin=dict(t=0, b=0, l=0, r=0),
template="plotly_white",
)
# Update 3D scene options
fig.update_scenes(
aspectratio=dict(x=1, y=1, z=0.7),
aspectmode="manual"
)
# Add dropdown
fig.update_layout(
updatemenus=[
dict(
type = "buttons",
direction = "left",
buttons=list([
dict(
args=["type", "surface"],
label="3D Surface",
method="restyle",
execute=False
),
dict(
args=["type", "heatmap"],
label="Heatmap",
method="restyle",
execute=False
)
]),
pad={"r": 10, "t": 10},
showactive=True,
x=0.11,
xanchor="left",
y=1.1,
yanchor="top"
),
]
)
# Add annotation
fig.update_layout(
annotations=[
dict(text="Trace type:", showarrow=False,
x=0, y=1.08, yref="paper", align="left")
]
)
fig.show()
Note the part where we add execute=False. This allows the JS function plotly_buttonclicked to act as a customizable callback function for the component's click.
As per this documentation:
property execute When true, the API method is executed. When false,
all other behaviors are the same and command execution is skipped.
This may be useful when hooking into, for example, the
plotly_buttonclicked method and executing the API command manually
without losing the benefit of the updatemenu automatically binding to
the state of the plot through the specification of method and args.
The execute property must be specified as a bool (either True, or
False)
Returns Return type bool
Option #2: residing to dash:
Since plotly is built on dash, you can go back and forth between them. Here is how you should do it:
from dash import Dash, dcc, html, ctx
from dash.dependencies import Input, Output
from dash.exceptions import PreventUpdate
import plotly.graph_objects as go
import pandas as pd
# load dataset
df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/volcano.csv")
# create figure
fig = go.Figure()
# Add surface trace
fig.add_trace(go.Surface(z=df.values.tolist(), colorscale="Viridis"))
# Update plot sizing
fig.update_layout(
width=800,
height=900,
autosize=False,
margin=dict(t=0, b=0, l=0, r=0),
template="plotly_white",
)
# Update 3D scene options
fig.update_scenes(
aspectratio=dict(x=1, y=1, z=0.7),
aspectmode="manual"
)
# Add dropdown
fig.update_layout(
updatemenus=[
dict(
type = "buttons",
direction = "left",
buttons=list([
dict(
args=["type", "surface"],
label="3D Surface",
method="restyle",
execute=False
),
dict(
args=["type", "heatmap"],
label="Heatmap",
method="restyle",
execute=False
)
]),
pad={"r": 10, "t": 10},
showactive=True,
x=0.11,
xanchor="left",
y=1.1,
yanchor="top"
),
]
)
# Add annotation
fig.update_layout(
annotations=[
dict(text="Trace type:", showarrow=False,
x=0, y=1.08, yref="paper", align="left")
]
)
# Instead of loading the figure relying on `plotly`'s engine, how about embedding it as a `dash` object:
#fig.show()
dash_fig = dcc.Graph(figure=fig)
app = Dash(__name__)
app.layout = html.Div([
html.Label('Trace Type:', id='ttype'),
html.Button('Surface', id='surf', n_clicks=0),
html.Button('Heatmap', id='heat', n_clicks=0),
dash_fig])
@app.callback(
Output('ttype', 'children'),
Input(component_id='surf', component_property='n_clicks'),
Input(component_id='heat', component_property='n_clicks'))
def click_callback(n1,n2):
return f'{ctx.triggered_id}:{n1}:{n2}'
if __name__ == '__main__':
app.run_server(debug=True)
Note how now we have the callback function click_callback.
Remains the task to reimplement the graph change code since it has to be done manually now.