What input event can I fire when someone clicks on a custom button?

Viewed 128

I have this plot-

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"
                ),
                dict(
                    args=["type", "heatmap"],
                    label="Heatmap",
                    method="restyle"
                )
            ]),
            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()

I want to update the chart whenever someone clicks on either of the two buttons. I need to connect the update code to some event. If it was changing the x-axis, for example, I could use the relayout_data event. Is there any event that gets fired when someone clicks a custom button that I can tie to an Input() callback?

1 Answers

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.

Related