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.FigureWidgetin Jupyter (requires theanywidgetpackage) supportstrace.on_click(callback)andon_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 neededRemember
- 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.