Plotly
Plotly builds interactive, web-ready charts with hover, zoom, and pan - export HTML or embed in Dash apps.
Recipe
Quick-reference recipe card - copy-paste ready.
import plotly.express as px
import pandas as pd
df = pd.read_csv("sales.csv", parse_dates=["ordered_at"])
fig = px.line(df, x="ordered_at", y="revenue", color="region", title="Revenue trend")
fig.update_layout(hovermode="x unified")
fig.write_html("revenue.html", include_plotlyjs="cdn")When to reach for this:
- Dashboards and stakeholder self-serve exploration
- Dense scatter needing hover detail
- Linked brushing across facets (Dash)
- Embedding charts in internal web tools
Working Example
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
rng = np.random.default_rng(42)
df = pd.DataFrame(
{
"region": np.repeat(["East", "West"], 100),
"month": np.tile(np.arange(1, 13).repeat(8)[:100], 2),
"revenue": rng.integers(80, 200, 200),
"units": rng.integers(5, 50, 200),
}
)
# Express scatter with custom hover
fig_scatter = px.scatter(
df,
x="units",
y="revenue",
color="region",
hover_data=["month"],
opacity=0.7,
title="Units vs revenue",
)
fig_scatter.update_traces(marker=dict(size=8))
# Graph objects for dual-axis combo
monthly = df.groupby(["month", "region"], observed=True)["revenue"].sum().reset_index()
fig_combo = go.Figure()
for region, sub in monthly.groupby("region"):
fig_combo.add_trace(
go.Scatter(x=sub["month"], y=sub["revenue"], mode="lines+markers", name=region)
)
fig_combo.update_layout(
xaxis_title="Month",
yaxis_title="Revenue",
legend_title="Region",
template="plotly_white",
)
fig_scatter.write_html("scatter.html", include_plotlyjs="cdn")
fig_combo.write_html("combo.html", include_plotlyjs="cdn")What this demonstrates:
- Plotly Express for rapid scatter with
hover_data - Graph Objects for multi-trace figures
templatefor consistent light theme- CDN-hosted JS for smaller HTML files
Deep Dive
How It Works
- Figures are JSON-serializable dicts rendered by plotly.js in the browser.
- Express wraps graph_objects with sensible defaults from DataFrame columns.
- Traces hold data; layout controls axes, legends, and annotations.
- Dash builds reactive apps sharing the same figure model.
Express vs Graph Objects
| Layer | Best for |
|---|---|
plotly.express | Standard charts from tidy data |
plotly.graph_objects | Custom layouts, dual axes, annotations |
Python Notes
import plotly.io as pio
# Static image export needs kaleido
pio.write_image(fig, "chart.png", scale=2)Gotchas
- Plotting millions of points - browser hangs. Fix: aggregate, hexbin, or datashader first.
- Huge HTML from inline JS -
include_plotlyjs=Truebloats files. Fix: use"cdn"or"directory". - Categorical x treated as numeric - lines connect wrongly. Fix:
astype(str)orcategory_orders. - Timezone-naive datetimes - hover shows wrong local offset. Fix: UTC at source, format in
hovertemplate. - Kaleido missing for PNG -
write_imagefails in CI. Fix:uv pip install kaleidoin export jobs.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| matplotlib | Print/PDF static pipelines | Need hover tooltips |
| Altair | Vega-Lite declarative specs | Need 3-D WebGL traces |
| Bokeh | Large streaming datasets | Team already on Plotly/Dash |
| Observable Plot | Web-native grammar | Python-only batch jobs |
FAQs
Express or graph_objects?
- Start Express; drop to GO for dual axes and custom annotations.
- GO is verbose but unlimited.
How do I customize hover text?
fig.update_traces(hovertemplate="Revenue: %{y:$,.0f}<extra></extra>")Can I use Plotly in Jupyter?
fig.show()renders inline with nbformat support.- Prefer HTML export for sharing outside notebook.
How do I fix legend order?
fig.update_layout(legend_traceorder="reversed")- Or sort traces before adding.
How do I add horizontal reference line?
fig.add_hline(y=150, line_dash="dash", annotation_text="target")Does Plotly work with Polars?
- Pass
pl.DataFramewhere supported or.to_pandas()first.
How do I dark theme?
fig.update_layout(template="plotly_dark")How do I embed in Flask?
- Return
fig.to_html(full_html=False)in templates. - Load plotly.js once in base layout.
What is Dash?
- Plotly's reactive app framework - see dashboards page.
- Shares figure model with Express/GO.
How do I animate?
px.scatter(..., animation_frame="year")for small datasets.- Avoid animation on huge frames.
Related
- Dashboards (Streamlit / Dash / Gradio) - Dash apps
- Plotting Large Data - downsampling
- Altair - declarative alternative
- Visualization Basics - when interactivity helps
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.