DS DevShelfHub Projects · AI tools
Cheatsheets / Matplotlib
Cheatsheet · AI frameworks

Matplotlib Cheatsheet: Figure, Axes and Plot Types Reference

By DevShelfHub

Figure/Axes OO API, plot types, styling, subplots, ticks, colormaps, save formats — the explicit interface you actually use.

104 items 8 min Figure Axes pyplot

Start hereQuick start · 6 you’ll reach for daily

Figure + Axesfig, ax = plt.subplots()
Line plotax.plot(x, y)
Title + labelsax.set(title=..., xlabel=..., ylabel=...)
Grid layoutplt.subplots(2, 2, layout="constrained")
Savefig.savefig("f.pdf", bbox_inches="tight")
Inline (Jupyter)%matplotlib inline

Target versions · paceVersions

Targets: matplotlib ≥ 3.9 numpy ≥ 1.26 python ≥ 3.10

Default to the object-oriented API (fig, ax = plt.subplots()), not the implicit plt.* globals — the OO API is what every modern matplotlib doc page assumes. layout="constrained" supersedes tight_layout and handles colorbars + suptitles correctly. Style sheets named seaborn-* are now seaborn-v0_8-*. This sheet pins to matplotlib 3.9+.

Install · backendSetup

bash
# Install — core + bundled deps (numpy comes along)
pip install "matplotlib>=3.9"

# Optional but useful
pip install ipympl                          # interactive backend for Jupyter
pip install seaborn                         # statistical wrapper on top
pip install pillow                          # for image I/O via imread

# Conda — picks a sane backend for your OS
conda install -c conda-forge matplotlib

# Pick a backend before importing pyplot (only if the default fights you)
import matplotlib
matplotlib.use("Agg")                       # non-GUI: scripts, CI, servers
# matplotlib.use("TkAgg")                   # native window
# matplotlib.use("module://matplotlib_inline.backend_inline")  # Jupyter

# In Jupyter — choose one per notebook
# %matplotlib inline       # static PNGs (default)
# %matplotlib widget       # interactive (needs ipympl)

# Confirm
python -c "import matplotlib as m; print(m.__version__, m.get_backend())"

Where things liveCommon imports

import matplotlib.pyplot as pltCanonical alias. Entry point for figure / axes creation.
import matplotlib as mplTop-level — rcParams, backend, version.
import matplotlib.dates as mdatesDate tick locators / formatters.
import matplotlib.ticker as mtickerCustom number tick formatters (FuncFormatter, PercentFormatter).
import matplotlib.patches as mpatchesRectangle, Circle, FancyArrowPatch — for annotations.
from matplotlib.colors import LogNorm, Normalize, ListedColormapColor scales for heatmaps / images.
from matplotlib.gridspec import GridSpecFine-grained subplot layouts.

Explicit > implicitAPI style: OO vs pyplot

fig, ax = plt.subplots()Make a figure + one axes. Preferred OO entry point.
fig, axes = plt.subplots(2, 3, figsize=(10, 6))Grid — axes is a 2x3 ndarray of Axes.
ax.plot(x, y) / ax.scatter(x, y)Call methods on the axes. Composable.
plt.plot(x, y) / plt.title("...")Legacy implicit API. Operates on "current" figure.
fig.suptitle("Figure title")Title above the entire figure.
ax.set(title="...", xlabel="...", ylabel="...", xlim=(0,10))One-shot setter — tidier than four lines.
fig.tight_layout() vs layout="constrained"Auto-fix overlap. constrained handles colorbars correctly.
plt.close(fig) / plt.close("all")Free figures in scripts / loops. Otherwise they leak.
plt.show()Render + block (GUI backends). No-op in Jupyter / Agg.

line · scatter · hist · ...Plot types

ax.plot(x, y, "o-", label="...")Line. Format string = marker + linestyle.
ax.scatter(x, y, s=size, c=value, cmap="viridis", alpha=0.6)Scatter with size + color encoding.
ax.bar(cats, heights, yerr=errs) / ax.barh(...)Vertical / horizontal bars.
ax.hist(x, bins=30, density=True, range=(0,1))Histogram. density=True integrates to 1.
ax.hist2d(x, y, bins=50, cmap="magma")2-D histogram. Heatmap of counts.
ax.boxplot([a, b, c], labels=["a","b","c"])Boxplot.
ax.violinplot([a, b, c], showmedians=True)Distribution shape per group.
ax.errorbar(x, y, yerr=err, fmt="o", capsize=3)Mean + error bars.
ax.fill_between(x, lo, hi, alpha=0.2)Confidence band.
ax.step(x, y, where="post")Step plot — cumulative / staircase data.
ax.imshow(M, cmap="gray", aspect="auto", origin="lower")Matrix / image heatmap.
ax.contour(X, Y, Z, levels=10) / ax.contourf(...)Iso-line / filled contours.
ax.quiver(X, Y, U, V) / ax.streamplot(X, Y, U, V)Vector / streamline fields.

limits · ticks · logAxes, scales, ticks

ax.set_xlim(0, 10) / ax.set_ylim(-1, 1)Numeric limits.
ax.set_xscale("log") / ax.set_yscale("symlog")Log / symlog axes.
ax.set_xticks([0, 1, 2], labels=["a","b","c"])Custom tick positions + labels.
ax.xaxis.set_major_locator(mticker.MultipleLocator(5))Tick every N units.
ax.xaxis.set_major_formatter("{x:,.0f}")f-string formatter shortcut.
ax.tick_params(axis="x", labelrotation=45, labelsize=9)Bulk tweak tick style.
ax.invert_yaxis() / ax.invert_xaxis()Flip an axis direction.
ax.spines[["top","right"]].set_visible(False)Hide axis spines — cleaner look.
ax2 = ax.twinx() / twiny()Secondary y / x sharing the other axis.
ax.axhline(y, color="grey", linestyle="--")Horizontal reference line.
ax.axvspan(start, end, alpha=0.2)Vertical band — mark a date range.
ax.grid(True, which="major", alpha=0.3)Grid toggling.

Grids · mosaic · gridspecSubplots & layout

plt.subplots(2, 3, figsize=(12, 6), sharex=True)Grid with shared x.
plt.subplots(..., layout="constrained")Auto-fix overlap. Preferred over tight_layout.
plt.subplot_mosaic([["a","b"], ["a","c"]])ASCII layout: a spans the left column, b + c right.
fig.add_gridspec(3, 3) + fig.add_subplot(gs[:2, :2])Slice-based subplot placement.
ax.inset_axes([0.6, 0.6, 0.35, 0.35])Inset axes in figure-fraction coords.
fig.subplots_adjust(left=..., right=..., wspace=0.3, hspace=0.4)Manual padding when auto-layout isn’t enough.
fig.colorbar(im, ax=ax, fraction=0.046, pad=0.04)Attach a colorbar to a specific axes.
fig.legend(handles, labels, loc="upper center", ncols=4)Figure-level legend (vs per-axes).
python
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2 * np.pi, 200)

# Single figure, 2x2 grid — shared y, constrained layout
fig, axes = plt.subplots(
    nrows=2, ncols=2, figsize=(8, 6),
    sharex=True, sharey="row",                # share within rows
    layout="constrained",                     # auto-fix overlap; replaces tight_layout
)

axes[0, 0].plot(x, np.sin(x));  axes[0, 0].set_title("sin")
axes[0, 1].plot(x, np.cos(x));  axes[0, 1].set_title("cos")
axes[1, 0].plot(x, np.tan(x));  axes[1, 0].set_title("tan"); axes[1, 0].set_ylim(-5, 5)
axes[1, 1].plot(x, x**2);       axes[1, 1].set_title("x^2")

# Uneven layout — subplot_mosaic. ASCII map is the layout.
fig, axd = plt.subplot_mosaic(
    [["top", "top"],
     ["left", "right"]],
    figsize=(8, 6),
    layout="constrained",
)
axd["top"].plot(x, np.sin(x))
axd["left"].hist(np.random.default_rng(0).standard_normal(1000), bins=30)
axd["right"].scatter(x, np.sin(x) + np.random.default_rng(0).normal(0, 0.1, 200))

# GridSpec — manual control when mosaic isn't enough
fig = plt.figure(figsize=(8, 6), layout="constrained")
gs  = fig.add_gridspec(3, 3)
ax_big   = fig.add_subplot(gs[:2, :2])
ax_right = fig.add_subplot(gs[:2, 2])
ax_bot   = fig.add_subplot(gs[2,  :])

Styles · rcParams · colorsStyling

plt.style.use("seaborn-v0_8-whitegrid")Builtin style sheet.
plt.style.availableList installed styles.
plt.style.use(["seaborn-v0_8-paper", "mystyle.mplstyle"])Stack styles — last wins.
with plt.style.context("dark_background"): ...Temporary, scoped style.
plt.rcParams["font.size"] = 11Tweak any default.
mpl.rc("axes", titlesize=14, labelsize=12)Group setter for an rcParam family.
color="C0" / color="#ff6b6b" / color=(0.2, 0.4, 0.6, 0.8)Cycle slot / hex / RGBA.
linewidth, linestyle, marker, markersize, alpha, zorderPer-line aesthetics.
cycler import cycler → ax.set_prop_cycle(...)Custom color / linestyle cycle.
ax.annotate("peak", xy=(x, y), xytext=(...), arrowprops=...)Annotations with arrows.
python
import matplotlib.pyplot as plt
import numpy as np

# Style sheet — applies to all plots in this process
plt.style.use("seaborn-v0_8-whitegrid")     # builtin; many others available
# plt.style.available  → list them

# Or one-off rcParams overrides
plt.rcParams.update({
    "figure.dpi":      120,
    "savefig.dpi":     200,
    "font.size":       11,
    "axes.titlesize":  13,
    "axes.spines.top": False,
    "axes.spines.right": False,
    "legend.frameon":  False,
})

# Context manager — temporary style (doesn't leak)
with plt.style.context("dark_background"):
    fig, ax = plt.subplots()
    ax.plot([0, 1, 2], [0, 1, 4])

# Colors: cycle, named, hex, rgb, alpha
rng = np.random.default_rng(0)
fig, ax = plt.subplots()
for i in range(4):
    ax.plot(rng.standard_normal(50).cumsum(),
            color=f"C{i}",                  # cycle slot (C0..C9)
            linewidth=1.5, alpha=0.85,
            label=f"series {i}")
ax.legend(loc="upper left", ncols=2)

# Per-line cosmetics — short codes still work
ax.plot([0,1,2], [0,1,4], "o--", color="#ff6b6b", markersize=6, markeredgewidth=0)

# Annotate a point with an arrow
ax.annotate("peak", xy=(2, 4), xytext=(1, 3.5),
            arrowprops=dict(arrowstyle="->", color="grey"))

cmap · normColors & colormaps

cmap="viridis"Default sequential. Perceptually uniform.
cmap="magma" / "inferno" / "cividis" / "plasma"Other sequential cmaps. All perceptually uniform.
cmap="coolwarm" / "RdBu_r" / "bwr"Diverging — for signed values around a center.
cmap="tab10" / "tab20"Qualitative — for unordered categories. Never use a sequential cmap here.
norm=Normalize(vmin=0, vmax=1) / LogNorm(vmin=1e-3, vmax=1)Linear / log color mapping.
norm=TwoSlopeNorm(vmin=-1, vcenter=0, vmax=2)Asymmetric diverging map around a center.
fig.colorbar(im, ax=ax, label="...", extend="both")Attach a colorbar; extend shows out-of-range arrows.
mpl.colormaps["viridis"].resampled(8)Discretize a continuous cmap into N bins.

PNG · PDF · SVGSaving figures

fig.savefig("plot.png", dpi=200, bbox_inches="tight")Raster output. bbox_inches="tight" trims whitespace.
fig.savefig("plot.pdf", bbox_inches="tight")Vector PDF — Preferred for print / publications.
fig.savefig("plot.svg")Vector SVG — for web.
fig.savefig(..., transparent=True)No background — useful over branded slides.
fig.savefig(..., metadata={"Author":"...", "Title":"..."})Embed metadata (PDF/PNG only).
with PdfPages("report.pdf") as pdf: pdf.savefig(fig)Multi-page PDF — one figure per page.
plt.rcParams["savefig.dpi"] = 200Default DPI for every save call.
plt.close(fig) / plt.close("all")Always close after saving in a script — figures leak otherwise.

df.plot(ax=ax, ...)Pandas integration

df.plot(ax=ax, kind="line", x="ts", y=["a","b"])Pass an Axes to render into your figure.
df["x"].hist(ax=ax, bins=30)Series shortcut for histograms.
df.plot.scatter(x="a", y="b", c="cat", colormap="viridis")Color by a column.
df.plot(kind="bar", stacked=True)Stacked categorical bars.
df.boxplot(column=["a","b"], by="group")Boxplot per group.
from pandas.plotting import scatter_matrix; scatter_matrix(df, diagonal="kde")Quick pairs plot.
Pandas' .plot returns the underlying Axes — capture it with ax = df.plot(...) and continue styling with matplotlib’s OO API.

mplot3d · FuncAnimation3D & animation

fig.add_subplot(projection="3d")3D axes. From mpl_toolkits.mplot3d — auto-loaded.
ax3.plot_surface(X, Y, Z, cmap="viridis", alpha=0.8)Surface plot.
ax3.scatter(xs, ys, zs, c=values)3D scatter.
ax3.view_init(elev=20, azim=-60)Camera angle.
from matplotlib.animation import FuncAnimationFrame-based animations.
FuncAnimation(fig, update, frames=range(100), interval=50)Calls update(i) each frame.
ani.save("a.mp4", writer="ffmpeg", fps=30)Needs ffmpeg installed.
ani.save("a.gif", writer="pillow")GIF output via Pillow.

Time series · dual y · PDFEnd-to-end · Price + volume chart

Object-oriented API end to end — twin y-axis, date tick formatter, merged legends, vector + raster save.

python
# Time-series chart, publication-ready — title, secondary y, formatted ticks, PDF save.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import pandas as pd

rng = np.random.default_rng(0)
idx = pd.date_range("2026-01-01", periods=180, freq="D")
price  = 100 + rng.standard_normal(180).cumsum()
volume = rng.integers(1_000, 5_000, 180)

fig, ax = plt.subplots(figsize=(9, 4.5), layout="constrained")
ax.plot(idx, price, color="C0", linewidth=1.5, label="Price")
ax.set_ylabel("Price (USD)")
ax.set_title("Daily price + volume, 2026 H1")

# Secondary y for volume — twinx shares the x axis
ax2 = ax.twinx()
ax2.bar(idx, volume, alpha=0.25, color="C1", label="Volume")
ax2.set_ylabel("Volume")

# Date ticks — major month, minor day
ax.xaxis.set_major_locator(mdates.MonthLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter("%b"))
ax.xaxis.set_minor_locator(mdates.DayLocator())

# Merge legends from both axes
h1, l1 = ax.get_legend_handles_labels()
h2, l2 = ax2.get_legend_handles_labels()
ax.legend(h1 + h2, l1 + l2, loc="upper left", frameon=False)

# Save — bbox_inches="tight" trims whitespace
fig.savefig("price_volume.pdf", bbox_inches="tight")
fig.savefig("price_volume.png", dpi=200, bbox_inches="tight")
plt.close(fig)

Best practiceGood to know

Stick to the OO API (fig, ax = plt.subplots()). The implicit plt.plot() / plt.title() globals operate on a hidden "current" figure — fine for quick REPL plots, painful in any function or notebook that builds multiple figures.
Use layout="constrained", not tight_layout(). Constrained layout handles colorbars, suptitles, and legends without the surprise overlaps tight_layout still produces.
Default to perceptually-uniform sequential maps (viridis, magma). jet and rainbow mislead readers and break for color-blind viewers. The default cmap exists for a reason — use it unless you have a specific reason not to.

Common trapsWatch out for

Forgetting plt.close(fig) in scripts leaks figures. Every plt.subplots() retains a reference until the process exits. In a loop over thousands of items, that’s a real memory leak. Always close after saving.
ax.imshow defaults to origin="upper". Pixel (0, 0) goes to the top-left, like an image — not the mathematical convention. Pass origin="lower" for heatmaps of matrices.
Old style names like seaborn-darkgrid error in 3.9+. They were renamed to seaborn-v0_8-darkgrid. Use plt.style.available to discover the current names.

Go deeperSee also

Matplotlib FAQ

What is Matplotlib used for?

Matplotlib is Python's foundational data visualization library. It creates static, interactive, and animated plots — line charts, scatter plots, bar charts, histograms, heatmaps, and more. It integrates with NumPy, pandas, and Jupyter notebooks, and is the rendering backbone for higher-level libraries like seaborn and pandas .plot().

What is the difference between plt.plot() and ax.plot() in Matplotlib?

plt.plot() uses the pyplot state machine interface — it implicitly creates a Figure and Axes if none exist, making it fast for quick scripts. ax.plot() uses the object-oriented interface: you explicitly create fig, ax = plt.subplots() and call methods on ax. The OO interface is recommended for anything beyond a single plot because it gives precise control over multiple subplots.

How do I create subplots in Matplotlib?

Use fig, axes = plt.subplots(nrows, ncols, layout='constrained') to create a grid. axes is a 2D NumPy array — index with axes[row, col]. layout='constrained' (or 'tight') automatically adjusts spacing. For irregular layouts, use plt.subplot_mosaic() with a string or list pattern.

How do I save a Matplotlib figure?

Call fig.savefig('output.png', dpi=150, bbox_inches='tight'). The file format is inferred from the extension — .png, .pdf, .svg, .eps are all supported. Always use bbox_inches='tight' to avoid cutting off axis labels. For Jupyter, use %matplotlib inline or %matplotlib widget to render inline.

What is the difference between Matplotlib and seaborn?

Seaborn is built on top of Matplotlib and adds statistical plot types (violin plots, pair plots, regression plots) with nicer default aesthetics. Seaborn is faster for exploratory data analysis; Matplotlib gives lower-level control for publication-quality customization. You can mix them: create a seaborn plot, then fine-tune with Matplotlib ax methods.

Is Matplotlib free and open source?

Yes. Matplotlib is BSD-licensed and maintained by the community. It is free for personal and commercial use. The Matplotlib Basemap and Cartopy extensions for geographic plots are also open source.