Plotly Cheatsheet

A visual guide to Plotly covering plotly.express one-liners, the Figure and graph_objects model, add_trace and update_layout, hover/zoom/pan interactivity, facets and subplots, color/size/animation encodings, HTML and static-image export, and a quick dashboard.

python
plotly
cheatsheet
Author

James Balamuta

Published

July 24, 2026

Plotly is the library for interactive figures in Python: every chart it builds is a live HTML widget you can hover, zoom, pan, and animate, and that ships to a browser or notebook with no extra code. You meet it through two layers. plotly.express (imported as px) is the fast path, where one function call over a tidy DataFrame returns a finished figure, and plotly.graph_objects (imported as go) is the object model underneath, where every figure is a single Figure holding a data list of traces plus a layout. The recurring mental model in this sheet is one picture: a tidy DataFrame flows into an indigo Figure object (always data + layout), which renders into a live browser canvas showing the chrome that makes Plotly Plotly: a hover tooltip, a zoom-box, a mode-bar, a clickable legend, a play button. Where this overlaps the static plotnine and seaborn sheets, the contrast is the point: those freeze a mark, while Plotly keeps it interactive. The conventional imports are import plotly.express as px, import plotly.graph_objects as go, and import plotly.io as pio, and every example runs on the bundled px.data.* datasets so nothing here needs an external file.

Complete Plotly cheatsheet (light mode): eight panels covering plotly.express one-liners, the Figure object, add_trace and update_layout, color/size/animation encodings, facets and subplots, hover/zoom/pan interactivity, a quick dashboard, and HTML and static-image export.

Complete Plotly cheatsheet (dark mode): eight panels covering plotly.express one-liners, the Figure object, add_trace and update_layout, color/size/animation encodings, facets and subplots, hover/zoom/pan interactivity, a quick dashboard, and HTML and static-image export.

Download the full cheatsheet

All eight panels in a single, printable SVG.

Light SVG Dark SVG

plotly.express One-Liners

plotly.express (imported as px) is the fast path: pass a tidy DataFrame plus the column names you want on each axis and as color, and a single function call returns a fully interactive figure. Reach for px.scatter, px.line, px.bar, px.histogram, and px.box first, and use the bundled px.data.* datasets (iris, gapminder, tips, stocks) so any example runs without external files.

Plotly express panel: interactive scatter from a DataFrame, line and time series, aggregated bar chart, histogram distribution, box plot spread, and the built-in px.data demo datasets.

One function call, tidy DataFrame in, interactive Figure out.

Plotly express panel: interactive scatter from a DataFrame, line and time series, aggregated bar chart, histogram distribution, box plot spread, and the built-in px.data demo datasets.

One function call, tidy DataFrame in, interactive Figure out.
import plotly.express as px

df = px.data.gapminder()                                   # built-in demo data, no files
px.scatter(df, x="gdpPercap", y="lifeExp", color="continent")  # interactive scatter
px.line(px.data.stocks(), x="date", y="GOOG")              # line / time series
px.bar(px.data.tips(), x="day", y="total_bill", color="sex")   # aggregated bars
px.histogram(px.data.tips(), x="total_bill", nbins=30)     # distribution
px.box(px.data.tips(), x="day", y="total_bill")            # categorical spread

See Plotly Express. Every px.* call returns one graph_objects.Figure.

The Figure Object

Whatever you call, you always get back one plotly.graph_objects.Figure, and that object is just two things: a data list of traces (each trace is one chart type, like go.Scatter or go.Bar) and a layout dict of everything else (titles, axes, legend). px is a convenience layer that assembles this same object for you, so fig.to_dict() reveals that every figure is really a chunk of JSON you can inspect and edit.

Plotly figure panel: build a Figure by hand, a Figure has data and layout, every chart type is a trace, inspect it as a plain dict, px returns the same object, and render it with fig.show.

Every figure is one object: a list of traces plus a layout.

Plotly figure panel: build a Figure by hand, a Figure has data and layout, every chart type is a trace, inspect it as a plain dict, px returns the same object, and render it with fig.show.

Every figure is one object: a list of traces plus a layout.
import plotly.graph_objects as go

fig = go.Figure(data=go.Scatter(x=[1, 2, 3], y=[4, 5, 6], mode="markers"))
fig.data                       # the list of traces (the chart series)
fig.layout                     # titles, axes, legend, template
go.Scatter, go.Bar, go.Heatmap, go.Box   # trace types you put in data
fig.to_dict()                  # the whole figure as plain JSON
type(px.scatter(df, x="a", y="b")).__name__   # 'Figure'  -> px returns this too
fig.show()                     # render to browser / notebook

See Graph objects. px and go produce the same go.Figure; one is a shortcut for the other.

add_trace and update_layout

Build up a figure by layering traces with fig.add_trace(...), then style the whole thing with the update_ family: update_layout for titles, legend, and figure-wide options, update_traces (often with a selector) to restyle matching series, and update_xaxes / update_yaxes for a single axis. Helpers like add_hline, add_vline, and add_annotation drop reference lines and labels onto the canvas.

Plotly traces and layout panel: start an empty figure, add a trace to layer a series, set title and axis labels, restyle matching traces with a selector, tweak one axis, and drop a reference line.

Layer more traces, then style the whole figure with update_ methods.

Plotly traces and layout panel: start an empty figure, add a trace to layer a series, set title and axis labels, restyle matching traces with a selector, tweak one axis, and drop a reference line.

Layer more traces, then style the whole figure with update_ methods.
fig = go.Figure()                                           # empty figure, ready to fill
fig.add_trace(go.Scatter(x=x, y=y, name="obs"))             # layer a series
fig.update_layout(title="Sales", xaxis_title="Month", yaxis_title="USD")
fig.update_traces(marker_color="#0d6efd", selector=dict(type="bar"))  # restyle matches
fig.update_xaxes(range=[0, 100], showgrid=False)            # tweak one axis
fig.add_hline(y=50, line_dash="dash", annotation_text="target")       # reference line

See Creating and updating figures. Use a selector to target only the traces you mean.

Hover, Zoom, and Pan

Interactivity is the reason to choose Plotly over a static plot: every figure ships with hover tooltips, drag-to-zoom, pan, and a mode-bar, all for free. Enrich tooltips with hover_data and hover_name, align them across traces with hovermode="x unified", take full control with a hovertemplate, switch the drag behavior with dragmode, and add a rangeslider for time series.

Plotly interactivity panel: show extra fields on hover, bold a hover title, align tooltips across a row with hovermode x unified, fully custom hovertemplate text, switch drag from zoom to pan, and add a range slider.

The payoff over static plots: tooltips, zoom-box, pan, and the mode-bar.

Plotly interactivity panel: show extra fields on hover, bold a hover title, align tooltips across a row with hovermode x unified, fully custom hovertemplate text, switch drag from zoom to pan, and add a range slider.

The payoff over static plots: tooltips, zoom-box, pan, and the mode-bar.
px.scatter(df, x="a", y="b", hover_data=["petal_length", "petal_width"])  # extra fields
px.scatter(df, x="a", y="b", hover_name="country")          # bold tooltip header
fig.update_layout(hovermode="x unified")                    # align tooltips across traces
fig.update_traces(hovertemplate="%{x}: %{y:.1f}<extra></extra>")   # custom tooltip text
fig.update_layout(dragmode="pan")                           # default drag is zoom
fig.update_xaxes(rangeslider_visible=True)                  # draggable mini-timeline

See Hover text and formatting. <extra></extra> hides the default trace-name box.

Color, Size, and Animation

Encode extra variables by mapping columns to visual channels: color (discrete categories or a continuous gradient with color_continuous_scale), marker size for bubbles, and symbol for shape. The channel unique to Plotly is time: animation_frame turns a column into a playable slider, animation_group keeps objects identified across frames, and fixing range_x / range_y keeps the frames comparable.

Plotly encodings panel: color by a category with a discrete map, color by a number with a continuous scale, size by a value for bubbles, distinguish points with symbols, animate over a frame variable, and lock axes so frames compare.

Map extra columns to color, marker size, symbol, and time.

Plotly encodings panel: color by a category with a discrete map, color by a number with a continuous scale, size by a value for bubbles, distinguish points with symbols, animate over a frame variable, and lock axes so frames compare.

Map extra columns to color, marker size, symbol, and time.
px.scatter(df, x="a", y="b", color="species",
           color_discrete_map={"setosa": "#198754"})        # color by category
px.scatter(df, x="a", y="b", color="petal_length",
           color_continuous_scale="Viridis")                # color by number (gradient)
px.scatter(df, x="gdpPercap", y="lifeExp", size="pop", size_max=60)   # bubbles
px.scatter(df, x="a", y="b", symbol="species")              # distinguish with shapes
px.scatter(df, x="gdpPercap", y="lifeExp", size="pop", color="continent",
           animation_frame="year", animation_group="country")   # play button + slider
px.scatter(df, animation_frame="year", range_x=[100, 100000],
           range_y=[25, 90], log_x=True)                    # lock axes so frames compare

See Animations and Colorscales. There is no cmap=; pass a named scale string.

Facets and Subplots

For small multiples of the same chart, let px facet for you with facet_row, facet_col, and facet_col_wrap; the panels share axes by default, and update_yaxes(matches=None) frees them. When you need different chart types in one grid, build the scaffold with make_subplots(rows, cols, ...) and place each trace with add_trace(..., row=, col=).

Plotly facets and subplots panel: split into a row of panels, grid by two variables, wrap many facets, free each panel's axis, build a mixed-chart grid with make_subplots, and place a trace in a specific cell.

Small multiples with px facets; mixed grids with make_subplots.

Plotly facets and subplots panel: split into a row of panels, grid by two variables, wrap many facets, free each panel's axis, build a mixed-chart grid with make_subplots, and place a trace in a specific cell.

Small multiples with px facets; mixed grids with make_subplots.
from plotly.subplots import make_subplots

px.scatter(df, x="a", y="b", facet_col="species")           # a row of panels
px.scatter(df, x="total_bill", y="tip", facet_row="sex", facet_col="time")  # 2x2 grid
px.scatter(df, x="gdpPercap", y="lifeExp",
           facet_col="continent", facet_col_wrap=3)         # wrap many facets
fig.update_yaxes(matches=None)                              # free each panel's axis
fig = make_subplots(rows=2, cols=1, subplot_titles=("Price", "Volume"))  # mixed grid
fig.add_trace(go.Bar(x=x, y=v), row=2, col=1)               # place a trace in a cell

See Facet plots and Subplots. Facets share axes by default; matches=None frees them.

Build a Quick Dashboard

You can get surprisingly far without a server: set a global look with pio.templates.default, polish each figure’s layout, add updatemenus buttons, and concatenate several figures’ to_html(full_html=False) fragments into one HTML page (load plotly.js once via include_plotlyjs="cdn" on the first fragment). For a live notebook widget use go.FigureWidget (which now needs anywidget); when you need real inputs and callbacks, graduate to Dash, Plotly’s web-app framework.

Plotly dashboard panel: pick a global theme, polish the layout, add buttons and dropdowns, a live FigureWidget in a notebook, stitch figures into one page, and graduate to Dash for callbacks.

A theme, a notebook widget, and several figures on one HTML page.

Plotly dashboard panel: pick a global theme, polish the layout, add buttons and dropdowns, a live FigureWidget in a notebook, stitch figures into one page, and graduate to Dash for callbacks.

A theme, a notebook widget, and several figures on one HTML page.
import plotly.io as pio

pio.templates.default = "plotly_white"                      # global theme
fig.update_layout(legend=dict(orientation="h"),
                  margin=dict(l=40, r=20, t=40, b=40))      # polish the layout
fig.update_layout(updatemenus=[dict(buttons=[...])])        # buttons / dropdowns
fw = go.FigureWidget(fig)                                   # live notebook widget (anywidget)
open("dash.html", "w").write("".join(                       # stitch figures onto one page
    f.to_html(full_html=False, include_plotlyjs=("cdn" if i == 0 else False))
    for i, f in enumerate(figs)))
# pip install dash  ->  app.layout = [dcc.Graph(figure=fig)]   # for inputs and callbacks

See Templates and FigureWidget. For real callbacks, reach for Dash.

Export to HTML and Static Image

There are two export paths. write_html (and to_html) produces a standalone, still-interactive web page; use include_plotlyjs="cdn" to keep the file small, or full_html=False to embed a <div> in a larger page. write_image freezes a static PNG, SVG, or PDF for papers and slides, but it needs the kaleido engine installed (HTML export needs nothing extra).

Plotly export panel: save a standalone interactive page, slim the page with a CDN script, embed a div in an existing page, freeze a raster image, vector and print output, and the kaleido engine note.

Ship the live widget as HTML, or freeze a PNG/SVG/PDF.

Plotly export panel: save a standalone interactive page, slim the page with a CDN script, embed a div in an existing page, freeze a raster image, vector and print output, and the kaleido engine note.

Ship the live widget as HTML, or freeze a PNG/SVG/PDF.
fig.write_html("chart.html")                                # standalone interactive page
fig.write_html("chart.html", include_plotlyjs="cdn")        # smaller file (JS from CDN)
html = fig.to_html(full_html=False, include_plotlyjs=False) # embeddable <div> fragment
fig.write_image("chart.png", scale=2, width=900, height=500)  # static raster (retina)
fig.write_image("chart.svg")                                # crisp vector
fig.write_image("chart.pdf")                                # pip install kaleido first

See Static image export and Interactive HTML export. HTML needs nothing; write_image needs kaleido.

Quick Reference

Common plotly.express functions.
Call Chart Notes
px.scatter(df, x=, y=, color=) Points Add size=, symbol=, hover_data=
px.line(df, x=, y=) Line / time series markers=True for dots on the line
px.bar(df, x=, y=, color=) Bars barmode="group" to unstack
px.histogram(df, x=, nbins=) Distribution histnorm="probability" for density
px.box(df, x=, y=) / px.violin Spread points="all" to show raw points
px.imshow(matrix) Heatmap / image Good for correlation matrices
px.scatter_map(df, lat=, lon=) Map Tile maps (replaces the old mapbox names)
The Figure model.
Piece What it is
go.Figure The whole figure (data + layout)
fig.data List of traces (the chart series)
fig.layout Titles, axes, legend, template
go.Scatter / go.Bar / go.Box / go.Heatmap Trace types you put in data
fig.to_dict() / fig.to_json() The figure as plain JSON
fig.show() Render to browser / notebook
Refining a figure.
Method Styles
fig.update_layout(...) Figure-wide: title, legend, margins, template
fig.update_traces(..., selector=) Matching traces (markers, lines, names)
fig.update_xaxes(...) / update_yaxes(...) A single axis (range, type, grid, title)
fig.add_trace(go..., row=, col=) Layer a series (optionally into a subplot cell)
fig.add_hline / add_vline / add_annotation Reference lines and text labels
px encoding arguments.
Argument Maps a column to
color= Hue (discrete) or gradient (continuous)
size= Marker area (bubbles)
symbol= Marker shape
facet_row= / facet_col= Small-multiple panels
animation_frame= A playable time slider
hover_data= / hover_name= Tooltip fields
Exporting and theming.
Call Output Needs
fig.write_html("f.html") Standalone interactive page nothing extra
fig.to_html(full_html=False) Embeddable <div> fragment nothing extra
include_plotlyjs="cdn" Smaller file (loads JS from CDN) nothing extra
fig.write_image("f.png") Static PNG (also SVG, PDF, WebP) kaleido + Chrome
pio.templates.default = "plotly_white" Global theme nothing extra
Modern Plotly spellings.
Deprecated spelling (avoid) Current spelling
plotly.offline / iplot / plotly.plotly fig.show() / fig.write_html()
px.scatter_mapbox / *_mapbox / layout.mapbox px.scatter_map / *_map / layout.map
cmap= (that is Matplotlib) color_continuous_scale="Viridis"
orca engine / kaleido==0.2.* kaleido v1 (drives Chrome)
inner class styles pio.templates.default = "plotly_white"

Appendix: Sample Code

Express one-liner to Figure to show

import plotly.express as px

df = px.data.iris()
fig = px.scatter(
    df,
    x="sepal_width",
    y="sepal_length",
    color="species",
    size="petal_length",
    hover_data=["petal_width"],
    title="Iris sepals",
)
fig.show()          # opens an interactive figure in browser / notebook
type(fig).__name__  # 'Figure'  -> px returns a graph_objects.Figure

The same figure built from graph_objects

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

df = px.data.iris()

fig = go.Figure()
for name, grp in df.groupby("species"):
    fig.add_trace(go.Scatter(
        x=grp["sepal_width"], y=grp["sepal_length"],
        mode="markers", name=name,
    ))
fig.update_layout(
    title="Iris sepals",
    xaxis_title="sepal_width",
    yaxis_title="sepal_length",
    legend_title="species",
)
fig.update_traces(marker=dict(size=8, line=dict(width=0.5, color="white")))
# fig.data  -> a list of 3 Scatter traces; fig.layout -> the styling

Hover, range slider, and a reference line

import plotly.express as px

stocks = px.data.stocks()
fig = px.line(stocks, x="date", y="GOOG", title="GOOG")
fig.update_layout(hovermode="x unified")
fig.update_traces(hovertemplate="%{x|%b %Y}: %{y:.2f}<extra></extra>")
fig.update_xaxes(rangeslider_visible=True)
fig.add_hline(y=1.0, line_dash="dash", annotation_text="baseline")
fig.show()

Facets, then a mixed subplot grid

import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots

# px facets: small multiples of the SAME chart
gap07 = px.data.gapminder().query("year == 2007")
px.scatter(
    gap07, x="gdpPercap", y="lifeExp",
    facet_col="continent", facet_col_wrap=3, log_x=True,
).update_yaxes(matches=None)

# make_subplots: DIFFERENT charts in one grid
fig = make_subplots(rows=2, cols=1, subplot_titles=("Price", "Volume"))
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[10, 12, 9], name="price"), row=1, col=1)
fig.add_trace(go.Bar(x=[1, 2, 3], y=[100, 80, 120], name="volume"), row=2, col=1)
fig.update_layout(showlegend=False)

The animated bubble chart (the classic Gapminder)

import plotly.express as px

gap = px.data.gapminder()
fig = px.scatter(
    gap, x="gdpPercap", y="lifeExp",
    size="pop", size_max=60, color="continent",
    hover_name="country",
    animation_frame="year", animation_group="country",
    log_x=True, range_x=[100, 100000], range_y=[25, 90],
    title="Life expectancy vs GDP per capita, 1952 to 2007",
)
fig.show()   # a play button and a year slider appear

Export: interactive HTML and a static PNG

import plotly.express as px

fig = px.scatter(px.data.iris(), x="sepal_width", y="sepal_length", color="species")

# 1. Standalone interactive page (small file: JS from the CDN)
fig.write_html("iris.html", include_plotlyjs="cdn")

# 2. An embeddable <div> fragment for an existing page
fragment = fig.to_html(full_html=False, include_plotlyjs=False)

# 3. A static raster for slides / papers (needs: pip install kaleido)
fig.write_image("iris.png", scale=2, width=900, height=500)
fig.write_image("iris.svg")   # crisp vector

A no-server “dashboard”: several figures on one page

import plotly.express as px

iris = px.data.iris()
figs = [
    px.scatter(iris, x="sepal_width", y="sepal_length", color="species"),
    px.histogram(iris, x="petal_length", color="species"),
    px.box(iris, x="species", y="sepal_length"),
]

with open("dashboard.html", "w") as out:
    out.write("<h1>Iris dashboard</h1>")
    for i, fig in enumerate(figs):
        fig.update_layout(template="plotly_white", height=350)
        # load plotly.js once (on the first figure), reuse it for the rest
        out.write(fig.to_html(
            full_html=False,
            include_plotlyjs=("cdn" if i == 0 else False),
        ))
# open dashboard.html -> three interactive charts stacked on one page

Behavior notes

  • px and go produce the same object. A px.scatter(...) call returns a plotly.graph_objects.Figure, the very thing you would build by hand with go.Figure(); px is a convenience layer that assembles the data and layout for you.
  • Import the three canonical namespaces. Use import plotly.express as px, import plotly.graph_objects as go, and import plotly.io as pio. The old plotly.offline / iplot / plotly.plotly entry points are retired; fig.show() and fig.write_html() replace them.
  • HTML export needs nothing; static export needs kaleido. write_html and to_html work out of the box, while write_image (PNG, SVG, PDF) needs pip install kaleido, which drives a real Chrome.
  • go.FigureWidget now requires anywidget. In plotly 6.x, constructing a FigureWidget without anywidget raises an ImportError; plain go.Figure and fig.show() need nothing extra.
  • Facets share axes by default. px small multiples lock their axes so panels compare; pass update_yaxes(matches=None) (or matches=None per axis) to free each panel’s range.
  • Color arguments are not Matplotlib’s. Use color_continuous_scale="Viridis" for gradients and color_discrete_map={...} for category-to-color maps; there is no cmap=.

References

Plotly Python documentation (current)

Project and related