Buttons, sliders, range selectors and events

Plotly · Advanced · 14 min

Self-contained HTML charts can still let readers switch views, filter and zoom without any server.

Plotly figures can carry their own controls, which work in exported HTML with no Python running.

Updatemenus add buttons or dropdowns. Each button calls a plotly.js method with arguments:

  • "restyle" changes trace properties, for example visibility: args=[{"visible": [True, False]}];
  • "relayout" changes layout, such as a title or axis type;
  • "update" does both; "animate" plays frames.

Sliders work the same way with steps instead of buttons.

For time series, fig.update_xaxes(rangeslider_visible=True) adds a miniature overview to drag, and rangeselector=dict(buttons=[dict(count=1, label="1m", step="month", stepmode="backward"), dict(step="all")]) adds quick-range buttons.

To run Python code in response to interaction, you need a live kernel or server:

  • go.FigureWidget in Jupyter (requires the anywidget package) supports trace.on_click(callback) and on_selection, and updates when you change its properties from Python;
  • Dash callbacks (clickData, selectedData, relayoutData) in web apps;
  • Streamlit's st.plotly_chart(fig, on_select="rerun") returns selected points.

Choose in-figure controls for simple view switching that must survive as a static file; choose widgets or apps when interaction requires computing new data.

Example

import plotly.graph_objects as go
import plotly.express as px

df = px.data.stocks()
fig = go.Figure([go.Scatter(x=df.date, y=df[t], name=t) for t in ["GOOG", "AAPL", "AMZN"]])

buttons = [dict(label="All", method="update",
                args=[{"visible": [True, True, True]}, {"title.text": "All"}])]
for i, t in enumerate(["GOOG", "AAPL", "AMZN"]):
    vis = [j == i for j in range(3)]
    buttons.append(dict(label=t, method="update", args=[{"visible": vis}, {"title.text": t}]))

fig.update_layout(updatemenus=[dict(type="dropdown", buttons=buttons, x=0, y=1.15)])
fig.update_xaxes(rangeslider_visible=True, rangeselector=dict(buttons=[
    dict(count=6, label="6m", step="month", stepmode="backward"),
    dict(count=1, label="1y", step="year", stepmode="backward"),
    dict(step="all")]))

print([b.label for b in fig.layout.updatemenus[0].buttons])  # ['All', 'GOOG', 'AAPL', 'AMZN']
print(fig.layout.xaxis.rangeslider.visible)                   # True
fig.write_html("controls.html")    # works offline, no Python needed

Remember

  • updatemenus and sliders call restyle, relayout, update or animate in the browser
  • Range sliders and selectors make time series navigable
  • Python callbacks need FigureWidget, Dash or Streamlit, not a static file

Watch out for

  • Expecting a button in an exported HTML file to run Python code; it can only change the existing figure.

Check yourself

  1. A dropdown should show one of three traces at a time. Which method and args fit?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: plotly.com/python/custom-buttons/