Matplotlib cheat sheet

A scannable Matplotlib reference: 22 short snippets across 11 topics, each linking back to the lesson it came from.

At a glance

TopicWhat it covers
Figures, axes and your first plotA Figure is the whole image or window; an Axes is one plotting area inside it. Confusing the two is why people fightlesson
Styling, subplots and savingShare axes (sharex, sharey) whenever panels use the same units — it removes duplicated tick labels and makes comparisonlesson
Backends and environment setupMatplotlib separates the plot description from the thing that draws it. The backend is the renderer: an interactive onelesson
Chart types: bar, scatter, histogram, pie and boxThe everyday chart functions, the arguments that matter, and the specific ways each chart can mislead a readerlesson
Titles, legends, annotations and textDirect labelling beats a legend for two or three series: the reader never has to match a colour to an entry. A legendlesson
Scales, ticks and date axesLog and symmetric-log scales, tick locators and formatters, and getting date and category axes to read the way youlesson
Colormaps, colour mapping and accessibilityA colour map alone does nothing: you also need a norm that maps data values to the 0-1 range the map expects. Scatterlesson
Images and 3D: imshow, contour and mplot3dimshow treats an array as pixels and is the fastest way to view a matrix. pcolormesh accepts explicit coordinate arrayslesson
Animations and interactive figuresFuncAnimation calls your update function once per frame. With blit=True it redraws only the artists you return, whichlesson
Publication quality: DPI, vector formats and layoutfigsize is in inches and dpi is dots per inch, so pixel dimensions are simply the product. A single-column journallesson
Debugging plots: empty axes, missing data and overlapMost empty-plot problems have one of four causes: NaN in the data, values outside the limits, a log scale rejectinglesson

Quick snippets

Figures, axes and your first plot

Figure versus Axes

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(7, 4))     # the explicit (object) style
ax.plot([1, 2, 3, 4], [1, 4, 9, 16], marker="o", label="y = x²")
ax.set_title("Growth")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.legend()
ax.grid(True, alpha=0.3)
fig.tight_layout()
fig.savefig("plot.png", dpi=150)

The four plots that cover most work

ax.plot(x, y)                 # line: trends over a continuous axis
ax.scatter(x, y, s=20, alpha=0.6)   # scatter: relationship between two measures
ax.bar(labels, values)        # bar: comparison across categories
ax.hist(values, bins=30)      # histogram: distribution of one variable

ax.barh(labels, values)       # horizontal bar - better for long labels
ax.boxplot(values)            # spread and outliers per group
ax.imshow(matrix)             # heatmap-style matrix

Making a chart readable

ax.set_xlim(0, 10)
ax.set_ylim(bottom=0)
ax.tick_params(axis="x", rotation=45)
ax.annotate("peak", xy=(4, 16), xytext=(5, 12),
            arrowprops={"arrowstyle": "->"})

ax.legend(loc="upper left", frameon=False)
fig.suptitle("Quarterly revenue")

Full lesson: Figures, axes and your first plot →

Styling, subplots and saving

Themes and colours

plt.style.available[:5]
plt.style.use("seaborn-v0_8-whitegrid")     # process-wide default

fig, ax = plt.subplots()
ax.plot(x, y, color="#4f46e5", linewidth=2, linestyle="--")
ax.plot(x, y2, marker="s", markersize=5, alpha=0.8)

cmap = plt.get_cmap("viridis")              # perceptually uniform

Multiple panels

fig, axes = plt.subplots(1, 2, figsize=(10, 4), sharey=True)
axes[0].plot(x, y)
axes[0].set_title("A")
axes[1].scatter(x, y2)
axes[1].set_title("B")
fig.tight_layout()

# grids with mixed sizes
fig = plt.figure(figsize=(10, 6))
ax1 = fig.add_subplot(2, 2, 1)
ax2 = fig.add_subplot(2, 2, (2, 3))         # spans two cells
ax3 = fig.add_subplot(2, 2, 4)

Saving and embedding

fig.savefig("chart.png", dpi=150, bbox_inches="tight")
fig.savefig("chart.svg")                    # vector: scales cleanly
fig.savefig("chart.pdf", transparent=True)  # for LaTeX / print

# close when generating many figures in a loop, or memory grows
plt.close(fig)

Full lesson: Styling, subplots and saving →

Backends and environment setup

What a backend is

import matplotlib
matplotlib.use("Agg")          # must run BEFORE pyplot is imported
import matplotlib.pyplot as plt

matplotlib.get_backend()       # what is active right now

# inside a Jupyter notebook, instead of a backend call
# %matplotlib inline           # static PNG in the output cell
# %matplotlib widget           # interactive canvas, needs ipympl installed

Choosing a backend per environment

# headless machine: force a file backend before Python starts
export MPLBACKEND=Agg

# one-off script
MPLBACKEND=Agg python make_charts.py

# a GUI toolkit must be installed for interactive backends
pip install matplotlib PyQt6      # or use tkinter from the standard library
pip install ipympl                # for the widget backend in Jupyter

Configuration and matplotlibrc

matplotlib.matplotlib_fname()   # the config file actually in use
matplotlib.get_configdir()      # where your user config lives

# a matplotlibrc file, applied to every script on this machine
# backend: Agg
# figure.figsize: 7, 4.5
# figure.dpi: 120
# savefig.dpi: 200
# font.size: 10

Full lesson: Backends and environment setup →

Chart types: bar, scatter, histogram, pie and box

When a chart misleads

# a pie with ten slices communicates nothing
ax.pie(np.array([30, 20, 12, 9, 8, 7, 6, 4, 3, 1]),
       labels=[f"cat{i}" for i in range(10)], autopct="%1.1f%%")

# three bars and a line on one axis: two different scales, zero information
ax.bar(labels, [3, 4, 5, 6])                  # counts in hundreds
ax2 = ax.twinx()
ax2.plot(labels, [0.31, 0.42, 0.55, 0.61])    # a rate between 0 and 1

# the honest version: two panels with separate axes
fig, (top, bottom) = plt.subplots(2, 1, sharex=True, figsize=(7, 5))

Full lesson: Chart types: bar, scatter, histogram, pie and box →

Titles, legends, annotations and text

Titles and axis labels

fig, ax = plt.subplots(figsize=(7, 4))

ax.set_title("Revenue by quarter", loc="left", fontsize=13)
ax.set_xlabel("Quarter")
ax.set_ylabel("Revenue (GBP millions)")
ax.set_xticks(range(4), ["Q1", "Q2", "Q3", "Q4"])

fig.suptitle("Annual report", fontsize=15)      # the whole figure
fig.supxlabel("Fiscal year 2026", fontsize=9)
fig.supylabel("All units in GBP", fontsize=9)

Annotations and text coordinates

ax.annotate("launch", xy=(3, 40), xytext=(4, 25),
            arrowprops=dict(arrowstyle="->", color="grey", lw=0.8))

# transData: the default, positions follow the data
ax.text(2, 30, "peak", fontsize=9)

# transAxes: 0-1 across the panel, ignores the data range
ax.text(0.02, 0.95, "provisional", transform=ax.transAxes, va="top")

# transFigure: 0-1 across the whole figure
fig.text(0.5, 0.01, "Source: internal data", ha="center", fontsize=8)

Full lesson: Titles, legends, annotations and text →

Scales, ticks and date axes

Scales and limits

ax.set_yscale("log")                       # orders of magnitude
ax.set_xscale("symlog", linthresh=1.0)    # log scale that tolerates zero
ax.set_xscale("logit")                    # proportions between 0 and 1

ax.set_xlim(0, 100)
ax.set_ylim(bottom=0)                     # keep the top automatic
ax.invert_yaxis()                         # depth, rank and lat/lon charts

ax.margins(x=0.02, y=0.05)                # breathing room around the data
ax.autoscale(enable=True, axis="y", tight=False)

Tick locators and formatters

from matplotlib.ticker import (MultipleLocator, MaxNLocator,
                               PercentFormatter, FuncFormatter)

ax.xaxis.set_major_locator(MultipleLocator(10))
ax.yaxis.set_major_locator(MaxNLocator(nbins=6))
ax.yaxis.set_major_formatter(PercentFormatter(decimals=0))

ax.yaxis.set_major_formatter(FuncFormatter(lambda v, pos: f"{v/1000:.0f}k"))
ax.tick_params(axis="x", rotation=45, labelsize=9, length=3)
ax.minorticks_on()
ax.grid(True, which="major", alpha=0.25)

Full lesson: Scales, ticks and date axes →

Colormaps, colour mapping and accessibility

Choosing a palette

plt.get_cmap("cividis")     # designed for colour-blind readers
plt.get_cmap("RdBu_r")      # reversed, so high values read as cold or hot
plt.colormaps()             # everything available

# a cyclic map needs both ends to meet
plt.get_cmap("twilight")

# qualitative palettes for categories, never for magnitude
cmap = plt.get_cmap("tab10")
colors = cmap(np.linspace(0, 1, 10))

Colourbar and accessibility

cb = fig.colorbar(sc, ax=ax, orientation="vertical", pad=0.02)
cb.set_label("Response time (ms)")
cb.set_ticks([0, 25, 50, 75, 100])
cb.ax.tick_params(labelsize=8)

# centre the diverging scale on the real midpoint
norm = mpl.colors.TwoSlopeNorm(vcenter=0.0, vmin=-3.0, vmax=3.0)
ax2.scatter(x2, y2, c=values2, cmap="coolwarm", norm=norm)

Full lesson: Colormaps, colour mapping and accessibility →

Images and 3D: imshow, contour and mplot3d

Contour lines

x = np.linspace(-3, 3, 200)
y = np.linspace(-3, 3, 200)
X, Y = np.meshgrid(x, y)
Z = np.exp(-(X ** 2 + Y ** 2)) + 0.4 * np.exp(-((X - 1.5) ** 2 + Y ** 2))

cs = ax.contourf(X, Y, Z, levels=20, cmap="viridis")
ax.contour(X, Y, Z, levels=8, colors="white", linewidths=0.5, alpha=0.5)
ax.clabel(cs, inline=True, fontsize=7, fmt="%.2f")
fig.colorbar(cs, ax=ax, label="Density")

A first mplot3d surface

fig = plt.figure(figsize=(7, 5))
ax3 = fig.add_subplot(projection="3d")

ax3.plot_surface(X, Y, Z, cmap="viridis", linewidth=0, antialiased=True)
ax3.contour(X, Y, Z, zdir="z", offset=Z.min(), cmap="viridis", alpha=0.6)
ax3.set_xlabel("x")
ax3.set_zlabel("density")
ax3.view_init(elev=30, azim=-60)      # camera angles in degrees

# sometimes the 2-D view is simply better
fig, ax = plt.subplots()
ax.contourf(X, Y, Z, levels=20, cmap="viridis")

Full lesson: Images and 3D: imshow, contour and mplot3d →

Animations and interactive figures

Saving to video or GIF

anim.save("wave.mp4", fps=30, dpi=150)        # requires ffmpeg on PATH
anim.save("wave.gif", writer="pillow", fps=15) # no ffmpeg needed

from matplotlib.animation import PillowWriter, FFMpegWriter
anim.save("wave.mp4", writer=FFMpegWriter(fps=30, bitrate=1800))
anim.save("wave.gif", writer=PillowWriter(fps=12))

# in a notebook, render a playable clip inline
from matplotlib.animation import HTMLWriter
anim.save("wave.html", writer=HTMLWriter(fps=30))

Full lesson: Animations and interactive figures →

Publication quality: DPI, vector formats and layout

Size and DPI arithmetic

fig, ax = plt.subplots(figsize=(3.5, 2.5))   # inches

fig.savefig("fig1.pdf")                       # vector: resolution is irrelevant
fig.savefig("fig1.png", dpi=600)              # 2100 x 1500 pixels
fig.savefig("fig1.tiff", dpi=300, pil_kwargs={"compression": "tiff_lzw"})

print(fig.get_size_inches())                  # confirm the physical size
# rule of thumb: line charts 300 dpi, raster images 600 dpi, always prefer vector

Multi-panel figures with labels

fig, axes = plt.subplots(1, 2, figsize=(7, 3), constrained_layout=True)

for label, ax in zip("ab", axes):
    ax.plot(x, np.sin(x))
    ax.text(-0.16, 1.06, label, transform=ax.transAxes,
            fontsize=11, fontweight="bold", va="top", ha="left")

fig.supxlabel("Time (s)", fontsize=9)
fig.savefig("figure1.pdf", bbox_inches="tight", pad_inches=0.02)

Full lesson: Publication quality: DPI, vector formats and layout →

Debugging plots: empty axes, missing data and overlap

Overlapping labels and layout

fig, ax = plt.subplots(layout="constrained")   # modern replacement for tight_layout
ax.tick_params(axis="x", rotation=45, labelsize=9)
fig.align_labels()                            # line up shared axis labels

ax.legend(loc="upper left", bbox_to_anchor=(1.0, 1.0))   # outside the data
fig.savefig("chart.png", bbox_inches="tight")             # include what overflows

# long tick labels: shorten the strings instead of rotating further
ax.set_xticks(range(len(names)), [n[:18] for n in names], ha="right", rotation=30)

Full lesson: Debugging plots: empty axes, missing data and overlap →

FAQ

Is this Matplotlib cheat sheet free to use?
Yes. No sign-up and no tracking: the page is static, every example is on the page itself, and you can print it or save it as a one-page reference.
Where do the examples come from?
Every snippet is taken from the 11 lessons of the Matplotlib course on this site, and each section links back to the lesson it was pulled from.
How do I go deeper than a cheat sheet?
Open the full Matplotlib course — it carries the worked explanations, the edge cases and the exercises behind every line here.

Python 3 NumPy pandas Jupyter Notebook Flask FastAPI

Last refreshed 2026-09-27.