Toolmingo
Guides10 min read

Matplotlib Cheat Sheet: The Complete Quick Reference

A complete matplotlib cheat sheet — figure setup, plot types, styling, subplots, axes customization, annotations, saving figures, and the most common pitfalls.

The matplotlib operations you look up every time — from basic line plots to subplots, custom styles, annotations, and saving publication-quality figures. This reference covers the full plotting workflow.

Quick reference

The 25 patterns that cover 90% of daily matplotlib work.

Pattern Code
Basic line plot plt.plot(x, y)
Scatter plot plt.scatter(x, y)
Bar chart plt.bar(x, height)
Histogram plt.hist(data, bins=20)
Box plot plt.boxplot(data)
Set title plt.title("My Title")
Axis labels plt.xlabel("X") ; plt.ylabel("Y")
Legend plt.legend(["Series A", "Series B"])
Show figure plt.show()
Save figure plt.savefig("fig.png", dpi=150, bbox_inches="tight")
Figure size plt.figure(figsize=(10, 6))
Subplots fig, axes = plt.subplots(2, 2, figsize=(10, 8))
Plot on axis ax.plot(x, y)
Set x limits ax.set_xlim(0, 100)
Set y limits ax.set_ylim(-1, 1)
Grid ax.grid(True, alpha=0.3)
Tick labels ax.set_xticklabels(labels, rotation=45)
Twin axis ax2 = ax.twinx()
Colormap plt.scatter(x, y, c=values, cmap="viridis")
Colorbar plt.colorbar(label="Value")
Tight layout plt.tight_layout()
Close figure plt.close()
Style sheet plt.style.use("seaborn-v0_8")
Annotate point ax.annotate("label", xy=(x, y), xytext=(x+1, y+1), arrowprops=dict(arrowstyle="->"))
Fill between ax.fill_between(x, y1, y2, alpha=0.3)

Setup and imports

import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np

# Check version
print(mpl.__version__)  # 3.8+

# Inline in Jupyter
%matplotlib inline
# Or for interactive plots in Jupyter:
%matplotlib widget

Basic plot types

Line plot

x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x)

plt.figure(figsize=(8, 4))
plt.plot(x, y, color="steelblue", linewidth=2, linestyle="--", label="sin(x)")
plt.plot(x, np.cos(x), color="coral", linewidth=2, label="cos(x)")
plt.title("Trigonometric Functions")
plt.xlabel("x (radians)")
plt.ylabel("Amplitude")
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("trig.png", dpi=150, bbox_inches="tight")
plt.show()

Scatter plot

np.random.seed(42)
x = np.random.randn(200)
y = 0.7 * x + np.random.randn(200) * 0.5

plt.figure(figsize=(6, 6))
plt.scatter(x, y, c=np.abs(x), cmap="plasma", alpha=0.7, edgecolors="white", linewidths=0.5, s=50)
plt.colorbar(label="|x|")
plt.title("Scatter Plot with Colormap")
plt.xlabel("x")
plt.ylabel("y")
plt.tight_layout()
plt.show()

Bar chart

categories = ["A", "B", "C", "D", "E"]
values = [23, 45, 12, 67, 34]
errors = [2, 4, 1, 5, 3]

fig, ax = plt.subplots(figsize=(7, 4))
bars = ax.bar(categories, values, yerr=errors, capsize=5,
              color="steelblue", alpha=0.8, edgecolor="white")

# Add value labels on bars
for bar, val in zip(bars, values):
    ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.5,
            str(val), ha="center", va="bottom", fontsize=10)

ax.set_title("Bar Chart with Error Bars")
ax.set_ylabel("Value")
ax.grid(axis="y", alpha=0.3)
plt.tight_layout()
plt.show()

Grouped bar chart

x = np.arange(4)
width = 0.35

fig, ax = plt.subplots(figsize=(8, 5))
ax.bar(x - width / 2, [20, 35, 30, 25], width, label="Group A", color="steelblue")
ax.bar(x + width / 2, [25, 32, 34, 20], width, label="Group B", color="coral")

ax.set_xticks(x)
ax.set_xticklabels(["Q1", "Q2", "Q3", "Q4"])
ax.legend()
ax.set_title("Grouped Bar Chart")
plt.tight_layout()
plt.show()

Histogram

data = np.random.normal(50, 10, 1000)

fig, ax = plt.subplots(figsize=(7, 4))
n, bins, patches = ax.hist(data, bins=30, color="steelblue", alpha=0.7, edgecolor="white", density=True)

# Overlay normal distribution curve
from scipy.stats import norm
mu, sigma = norm.fit(data)
x = np.linspace(data.min(), data.max(), 200)
ax.plot(x, norm.pdf(x, mu, sigma), "r-", linewidth=2, label=f"Normal fit (μ={mu:.1f}, σ={sigma:.1f})")

ax.set_title("Histogram with Fit")
ax.set_xlabel("Value")
ax.set_ylabel("Density")
ax.legend()
plt.tight_layout()
plt.show()

Box plot

data = [np.random.normal(0, 1, 100),
        np.random.normal(1, 1.5, 100),
        np.random.normal(-1, 0.5, 100)]

fig, ax = plt.subplots(figsize=(6, 5))
bp = ax.boxplot(data, labels=["A", "B", "C"], patch_artist=True,
                medianprops=dict(color="white", linewidth=2))

colors = ["steelblue", "coral", "seagreen"]
for patch, color in zip(bp["boxes"], colors):
    patch.set_facecolor(color)
    patch.set_alpha(0.7)

ax.set_title("Box Plot")
ax.set_ylabel("Value")
ax.grid(axis="y", alpha=0.3)
plt.tight_layout()
plt.show()

Pie chart

sizes = [35, 25, 20, 15, 5]
labels = ["Python", "JavaScript", "Java", "C++", "Other"]
explode = (0.05, 0, 0, 0, 0)  # emphasize first slice

fig, ax = plt.subplots(figsize=(7, 7))
wedges, texts, autotexts = ax.pie(
    sizes, labels=labels, explode=explode, autopct="%1.1f%%",
    startangle=140, colors=["steelblue", "coral", "seagreen", "gold", "orchid"]
)
for text in autotexts:
    text.set_fontsize(9)
ax.set_title("Language Distribution")
plt.tight_layout()
plt.show()

Heatmap

data = np.random.rand(8, 8)

fig, ax = plt.subplots(figsize=(7, 6))
im = ax.imshow(data, cmap="YlOrRd", aspect="auto")
plt.colorbar(im, ax=ax, label="Value")

# Add text annotations
for i in range(data.shape[0]):
    for j in range(data.shape[1]):
        ax.text(j, i, f"{data[i, j]:.2f}", ha="center", va="center", fontsize=7)

ax.set_title("Heatmap")
ax.set_xticks(range(8))
ax.set_yticks(range(8))
plt.tight_layout()
plt.show()

Subplots

Grid of subplots

fig, axes = plt.subplots(2, 2, figsize=(10, 8))

x = np.linspace(0, 10, 100)

axes[0, 0].plot(x, np.sin(x), color="steelblue")
axes[0, 0].set_title("Sine")

axes[0, 1].plot(x, np.cos(x), color="coral")
axes[0, 1].set_title("Cosine")

axes[1, 0].plot(x, np.exp(-x / 5) * np.sin(x), color="seagreen")
axes[1, 0].set_title("Damped Sine")

axes[1, 1].scatter(np.random.randn(100), np.random.randn(100), alpha=0.5, color="orchid")
axes[1, 1].set_title("Scatter")

for ax in axes.flat:
    ax.grid(True, alpha=0.3)

plt.suptitle("2×2 Subplot Grid", fontsize=14, y=1.02)
plt.tight_layout()
plt.show()

Unequal subplot sizes with GridSpec

from matplotlib.gridspec import GridSpec

fig = plt.figure(figsize=(10, 6))
gs = GridSpec(2, 3, figure=fig, hspace=0.4, wspace=0.3)

ax1 = fig.add_subplot(gs[0, :])   # top row, all columns
ax2 = fig.add_subplot(gs[1, 0])   # bottom-left
ax3 = fig.add_subplot(gs[1, 1])   # bottom-middle
ax4 = fig.add_subplot(gs[1, 2])   # bottom-right

x = np.linspace(0, 10, 100)
ax1.plot(x, np.sin(x))
ax1.set_title("Wide plot (full row)")

for ax, title in zip([ax2, ax3, ax4], ["A", "B", "C"]):
    ax.plot(np.random.randn(50).cumsum())
    ax.set_title(title)

plt.suptitle("GridSpec Layout")
plt.show()

Twin axes (dual y-axis)

fig, ax1 = plt.subplots(figsize=(8, 5))

x = np.arange(12)
revenue = [100, 110, 130, 120, 150, 160, 145, 170, 185, 200, 190, 210]
growth_rate = [0, 10, 18, -8, 25, 7, -9, 17, 9, 8, -5, 10]

color1, color2 = "steelblue", "coral"

ax1.bar(x, revenue, color=color1, alpha=0.7, label="Revenue ($k)")
ax1.set_xlabel("Month")
ax1.set_ylabel("Revenue ($k)", color=color1)
ax1.tick_params(axis="y", labelcolor=color1)

ax2 = ax1.twinx()
ax2.plot(x, growth_rate, color=color2, marker="o", linewidth=2, label="Growth (%)")
ax2.set_ylabel("Growth Rate (%)", color=color2)
ax2.tick_params(axis="y", labelcolor=color2)
ax2.axhline(0, color="gray", linestyle="--", alpha=0.5)

lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines1 + lines2, labels1 + labels2, loc="upper left")

plt.title("Revenue and Growth Rate")
plt.tight_layout()
plt.show()

Styling and customization

Line styles, markers, and colors

fig, ax = plt.subplots(figsize=(8, 5))
x = np.linspace(0, 4 * np.pi, 100)

# line style + marker + color in format string: 'color marker linestyle'
ax.plot(x, np.sin(x),      "b-",   linewidth=2,   label="sin  (solid)")
ax.plot(x, np.sin(x + 1),  "r--",  linewidth=2,   label="sin+1 (dashed)")
ax.plot(x, np.sin(x + 2),  "g-.",  linewidth=2,   label="sin+2 (dash-dot)")
ax.plot(x, np.sin(x + 3),  "m:",   linewidth=2,   label="sin+3 (dotted)")
ax.plot(x[::10], np.sin(x + 4)[::10], "ko", markersize=8, label="sin+4 (dots only)")

ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Line styles: - solid, -- dashed, -. dash-dot, : dotted

Markers: o circle, s square, ^ triangle, D diamond, * star, + plus, x cross, . point

Colors: named ("steelblue"), hex ("#2196F3"), RGB tuple ((0.13, 0.59, 0.95)), single-letter ("b")

Style sheets

# List available styles
print(plt.style.available)

# Apply a style
plt.style.use("seaborn-v0_8-whitegrid")
# Other popular styles:
# "ggplot", "fivethirtyeight", "bmh", "dark_background", "tableau-colorblind10"

# Temporarily use a style
with plt.style.context("dark_background"):
    plt.plot([1, 2, 3], [4, 5, 6])
    plt.show()

Colormaps

# Sequential: "viridis", "plasma", "inferno", "magma", "cividis", "Blues", "Greens"
# Diverging:  "RdBu", "seismic", "coolwarm", "bwr", "PiYG"
# Qualitative: "Set1", "Set2", "tab10", "tab20", "Paired"
# Cyclic:     "hsv", "twilight"

# Usage in scatter/imshow
sc = plt.scatter(x, y, c=values, cmap="viridis", vmin=0, vmax=1)
plt.colorbar(sc, label="Value")

# Get colors from a colormap for bar/line plots
cmap = plt.get_cmap("tab10")
colors = [cmap(i) for i in range(5)]

Font and text customization

import matplotlib as mpl

# Global font settings
mpl.rcParams.update({
    "font.family": "sans-serif",
    "font.size": 12,
    "axes.titlesize": 14,
    "axes.labelsize": 12,
    "xtick.labelsize": 10,
    "ytick.labelsize": 10,
    "legend.fontsize": 10,
    "figure.titlesize": 16,
})

fig, ax = plt.subplots()
ax.set_title("Title", fontsize=16, fontweight="bold", pad=12)
ax.set_xlabel("X axis", fontsize=13)
ax.text(0.5, 0.5, "Center text", transform=ax.transAxes,
        ha="center", va="center", fontsize=14, color="gray")

Annotations

Annotate a data point

fig, ax = plt.subplots(figsize=(7, 5))
x = np.linspace(0, 10, 100)
y = np.sin(x) * np.exp(-x / 10)
ax.plot(x, y, color="steelblue", linewidth=2)

# Find maximum
idx = np.argmax(y)
ax.annotate(
    f"Max ({x[idx]:.1f}, {y[idx]:.2f})",
    xy=(x[idx], y[idx]),
    xytext=(x[idx] + 2, y[idx] + 0.1),
    arrowprops=dict(arrowstyle="->", color="black"),
    fontsize=10,
    bbox=dict(boxstyle="round,pad=0.3", facecolor="lightyellow", edgecolor="gray"),
)

ax.set_title("Annotation Example")
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Shapes and spans

fig, ax = plt.subplots(figsize=(8, 4))
x = np.linspace(0, 10, 100)
ax.plot(x, np.sin(x), "steelblue")

# Horizontal/vertical lines
ax.axhline(0, color="black", linewidth=0.8)
ax.axvline(np.pi, color="red", linestyle="--", label="x=π")

# Shaded region
ax.axvspan(np.pi, 2 * np.pi, alpha=0.15, color="green", label="[π, 2π]")

# Rectangle patch
from matplotlib.patches import Rectangle
ax.add_patch(Rectangle((6, -0.5), 2, 1, facecolor="yellow", alpha=0.3, edgecolor="orange"))

ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Axes customization

fig, ax = plt.subplots(figsize=(8, 5))
x = np.linspace(0, 100, 50)
ax.plot(x, np.sqrt(x), "steelblue", linewidth=2)

# Limits
ax.set_xlim(0, 100)
ax.set_ylim(0, 12)

# Custom ticks
ax.set_xticks([0, 25, 50, 75, 100])
ax.set_xticklabels(["0%", "25%", "50%", "75%", "100%"])

# Minor ticks
from matplotlib.ticker import MultipleLocator, FormatStrFormatter
ax.xaxis.set_minor_locator(MultipleLocator(5))
ax.yaxis.set_major_formatter(FormatStrFormatter("%.1f"))

# Grid (major and minor)
ax.grid(which="major", alpha=0.4)
ax.grid(which="minor", alpha=0.15, linestyle=":")

# Logarithmic scale
# ax.set_xscale("log")
# ax.set_yscale("log")

# Remove spines
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)

ax.set_title("Axes Customization")
ax.set_xlabel("Percentage")
ax.set_ylabel("Square Root")
plt.tight_layout()
plt.show()

Saving figures

fig, ax = plt.subplots(figsize=(8, 5))
ax.plot([1, 2, 3], [4, 5, 6])

# PNG — raster, good for web
fig.savefig("chart.png", dpi=150, bbox_inches="tight", transparent=False)

# SVG — vector, perfect for print/slides
fig.savefig("chart.svg", bbox_inches="tight")

# PDF — vector, for LaTeX/reports
fig.savefig("chart.pdf", bbox_inches="tight")

# High-res PNG for print
fig.savefig("chart_print.png", dpi=300, bbox_inches="tight")

# Tight layout removes extra whitespace
plt.tight_layout()
plt.savefig("chart_tight.png", dpi=150, bbox_inches="tight")

plt.close(fig)  # free memory after saving

bbox_inches="tight" — trims whitespace around the figure. Always use it.

dpi — screen: 72–100; web: 150; print: 300.

Object-oriented vs pyplot API

Matplotlib has two styles. Always prefer the OO API for non-trivial plots.

# Pyplot API (OK for quick one-liners)
plt.figure(figsize=(6, 4))
plt.plot(x, y)
plt.title("Pyplot style")
plt.show()

# Object-oriented API (recommended)
fig, ax = plt.subplots(figsize=(6, 4))
ax.plot(x, y)
ax.set_title("OO style")
plt.show()

# OO with multiple subplots — each axis is explicit
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
ax1.plot(x, y)
ax2.scatter(x, y)
plt.tight_layout()
plt.show()

Common patterns

Time series

import pandas as pd

dates = pd.date_range("2024-01-01", periods=90, freq="D")
values = np.cumsum(np.random.randn(90)) + 100

fig, ax = plt.subplots(figsize=(10, 4))
ax.plot(dates, values, color="steelblue", linewidth=1.5)
ax.fill_between(dates, values, values.min(), alpha=0.15, color="steelblue")

# Rotate date labels
fig.autofmt_xdate(rotation=45)
ax.set_title("Time Series")
ax.set_ylabel("Price")
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Error bars

x = np.arange(5)
y = np.array([2.1, 3.4, 2.8, 4.1, 3.7])
yerr = np.array([0.3, 0.4, 0.2, 0.5, 0.3])

fig, ax = plt.subplots(figsize=(6, 4))
ax.errorbar(x, y, yerr=yerr, fmt="o-", capsize=5,
            color="steelblue", ecolor="gray", linewidth=2, markersize=8)
ax.set_title("Error Bar Plot")
ax.set_xticks(x)
ax.set_xticklabels(["A", "B", "C", "D", "E"])
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Step plot (for digital signals, ECDF)

fig, ax = plt.subplots(figsize=(7, 4))
x = np.arange(10)
y = np.array([3, 5, 4, 7, 6, 8, 7, 9, 8, 10])

ax.step(x, y, where="post", color="steelblue", linewidth=2, label="Step (post)")
ax.plot(x, y, "o", color="steelblue", markersize=6)
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Saving multiple figures to one PDF

from matplotlib.backends.backend_pdf import PdfPages

with PdfPages("report.pdf") as pdf:
    for i in range(4):
        fig, ax = plt.subplots(figsize=(8, 5))
        ax.plot(np.random.randn(50).cumsum(), label=f"Series {i}")
        ax.legend()
        ax.set_title(f"Figure {i + 1}")
        pdf.savefig(fig, bbox_inches="tight")
        plt.close(fig)
print("Saved report.pdf")

Common mistakes

Mistake Problem Fix
plt.plot() after plt.show() show() resets state — new blank figure Call show() only at the very end
Mixing pyplot and OO on the same figure Confusing; pyplot modifies the current axes Pick one style per figure
plt.savefig() after plt.show() show() clears the figure first Save before show()
Missing plt.close() in loops Memory leak — figures accumulate Always plt.close(fig) in loops
figsize in inches, not pixels Figure comes out wrong size Use figsize=(w, h) in inches; set dpi for pixel size
Mutating data after plotting Plot doesn't update Re-plot or use set_data() on the line object
plt.xticks(labels) without positions Labels misaligned plt.xticks(ticks, labels) — positions first
ax.set_title vs plt.title on subplots plt.title targets the current axes only Use ax.set_title(...) for subplots; plt.suptitle(...) for the whole figure

Matplotlib vs alternatives

Library When to use Strength
Matplotlib Full control, publication figures, custom layouts Most flexible, widest ecosystem
Seaborn Statistical plots with nice defaults Prettier defaults, DataFrame-native
Plotly Interactive web charts HTML/JS output, zoom/hover
Bokeh Interactive data apps Streaming, dashboards
Altair Declarative grammar-of-graphics Clean API, Vega-Lite backend
Pandas .plot() Quick EDA Wraps matplotlib, one-liners

FAQ

Should I use plt.xxx() or fig, ax = plt.subplots(); ax.xxx()? Use fig, ax = plt.subplots() (OO API) for everything except throwaway one-liners. The OO API is explicit — you always know which axis you're modifying, and it scales to multiple subplots without confusion.

How do I make the figure bigger/smaller? plt.figure(figsize=(width_inches, height_inches)) or fig, ax = plt.subplots(figsize=(10, 6)). At 96 DPI, figsize=(10, 6) gives a 960×576 px image. For a different pixel size, also set dpi: savefig("out.png", dpi=150, bbox_inches="tight").

Why does plt.savefig() produce a blank image? You called plt.show() before plt.savefig(). show() renders and then clears the figure. Always save first: plt.savefig("out.png"); plt.show().

How do I plot a pandas DataFrame column? df["col"].plot(ax=ax) or ax.plot(df.index, df["col"]). For bar charts: df.plot(kind="bar", ax=ax). Pandas .plot() wraps matplotlib and passes ax directly.

How do I change the color cycle (default colors)? plt.rcParams["axes.prop_cycle"] = plt.cycler(color=["#E41A1C", "#377EB8", "#4DAF4A", "#984EA3"]). Or use a named color cycle: mpl.style.use("tableau-colorblind10").

How do I add a second x-axis or y-axis? For a second y-axis: ax2 = ax.twinx(). For a second x-axis: ax2 = ax.twiny(). Then plot on ax2 normally. Use ax.tick_params(axis="y", labelcolor=color) to colour each axis's tick labels to match its line.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools