Figure and axes
A figure is the whole image. An axes is one plot inside it, and almost every call below is a method on ax.
| Task | Code |
|---|
| Import pyplot | import matplotlib.pyplot as plt |
| One figure with one plot | fig, ax = plt.subplots() |
| Set the size in inches | fig, ax = plt.subplots(figsize=(8, 4)) |
| Draw on the axes | ax.plot(x, y) |
| Show the figure | plt.show() |
| Save as a sharp PNG | fig.savefig("chart.png", dpi=150, bbox_inches="tight") |
| Save as vector graphics | fig.savefig("chart.svg"), fig.savefig("chart.pdf") |
| Transparent background | fig.savefig("chart.png", transparent=True) |
| Close a figure to free memory | plt.close(fig) |
| Quick pyplot style for one chart | plt.plot(x, y)
plt.title("Quick look")
plt.show() |
Plot types
| Task | Code |
|---|
| Line chart | ax.plot(x, y) |
| Scatter plot | ax.scatter(x, y, s=20, alpha=0.7) |
| Scatter colored by value, with a color bar | points = ax.scatter(x, y, c=y, cmap="viridis")
fig.colorbar(points, ax=ax) |
| Bar chart | ax.bar(cats, a) |
| Horizontal bar chart | ax.barh(cats, a) |
| Stacked bars | ax.bar(cats, a, label="2025")
ax.bar(cats, b, bottom=a, label="2026") |
| Grouped bars | pos = np.arange(len(cats))
ax.bar(pos - 0.2, a, width=0.4)
ax.bar(pos + 0.2, b, width=0.4)
ax.set_xticks(pos, labels=cats) |
| Histogram | ax.hist(data, bins=30) |
| Box plot | ax.boxplot([data, data * 2], tick_labels=["A", "B"]) |
| Violin plot | ax.violinplot([data, data * 2]) |
| Pie chart | ax.pie([40, 35, 25], labels=cats, autopct="%1.0f%%") |
| Band between two curves | ax.fill_between(x, y - 0.2, y + 0.2, alpha=0.3) |
| Error bars | ax.errorbar([1, 2, 3], [4, 5, 3], yerr=[0.5, 0.3, 0.6], fmt="o", capsize=4) |
| Step chart | ax.step(x, y, where="mid") |
| Heatmap of a 2D array | image = ax.imshow(np.random.default_rng(0).random((5, 5)), cmap="magma")
fig.colorbar(image, ax=ax) |
| Filled contour plot | X, Y = np.meshgrid(x, x)
ax.contourf(X, Y, np.sin(X) * np.cos(Y), levels=20) |
| Density of many points | ax.hexbin(data, data + np.random.default_rng(1).normal(size=200), gridsize=20) |
| Horizontal or vertical reference line | ax.axhline(0, color="gray", linestyle="--"), ax.axvline(5) |
| Shade a range | ax.axvspan(2, 4, alpha=0.2) |
Lines, markers and colors
| Task | Code |
|---|
| Color, width and line style | ax.plot(x, y, color="tab:red", linewidth=2, linestyle="--") |
| Markers on the points | ax.plot(x, y, marker="o", markersize=4) |
| Short format string | ax.plot(x, y, "g--o") |
| Transparency | ax.plot(x, y, alpha=0.5) |
| Line styles | "-", "--", "-.", ":" |
| Marker codes | "o", "s", "^", "x", ".", "*" |
| Ways to name a color | "tab:blue", "C1", "#1f77b4", (0.2, 0.4, 0.6) |
| n colors from a colormap | colors = plt.colormaps["viridis"](np.linspace(0, 1, 5)) |
| Your own color cycle | ax.set_prop_cycle(color=["#0b7285", "#e8590c", "#5f3dc4"]) |
| Outlined and hatched bars | ax.bar(cats, a, color="white", edgecolor="black", hatch="//") |
Titles, labels and legends
| Task | Code |
|---|
| Title and axis labels | ax.set_title("Sales"), ax.set_xlabel("Month"), ax.set_ylabel("EUR") |
| All of them in one call | ax.set(title="Sales", xlabel="Month", ylabel="EUR") |
| Legend from line labels | ax.plot(x, y, label="sin")
ax.legend() |
| Legend position and columns | ax.legend(loc="upper right", ncols=2, frameon=False) |
| Legend outside the plot | ax.legend(loc="center left", bbox_to_anchor=(1, 0.5)) |
| Text at a data position | ax.text(5, 0.5, "note") |
| Annotation with an arrow | ax.annotate("peak", xy=(1.57, 1), xytext=(3, 1.2), arrowprops=dict(arrowstyle="->")) |
| Value labels on bars | bars = ax.bar(cats, a)
ax.bar_label(bars) |
| Title for the whole figure | fig.suptitle("Q1 report") |
| Math in labels | ax.set_xlabel(r"$\alpha$ (rad)") |
Axes, ticks and scales
| Task | Code |
|---|
| Axis limits | ax.set_xlim(0, 10), ax.set_ylim(bottom=0) |
| Log scale | ax.set_yscale("log") |
| Choose ticks and their labels | ax.set_xticks([0, 5, 10], labels=["start", "middle", "end"]) |
| Rotate tick labels | ax.tick_params(axis="x", labelrotation=45) |
| Grid lines | ax.grid(True, alpha=0.3) |
| Percent axis | from matplotlib.ticker import PercentFormatter
ax.yaxis.set_major_formatter(PercentFormatter(1.0)) |
| Thousands separator | ax.yaxis.set_major_formatter("{x:,.0f}") |
| Hide the top and right border | ax.spines[["top", "right"]].set_visible(False) |
| Second y-axis on the right | ax2 = ax.twinx()
ax2.plot(x, y * 100, color="tab:orange") |
| Same scale on both axes | ax.set_aspect("equal") |
| Flip an axis | ax.invert_yaxis() |
| Hide the axes completely | ax.axis("off") |
| Readable date labels | fig.autofmt_xdate() |
Subplots and layout
plt.subplots(rows, cols) returns a NumPy array of axes. Index it like an array.
| Task | Code |
|---|
| Grid of plots | fig, axs = plt.subplots(2, 2, figsize=(8, 6)) |
| Draw on one of them | axs[0, 1].plot(x, y) |
| Loop over all of them | for ax in axs.flat:
ax.grid(True) |
| Share an axis between plots | fig, axs = plt.subplots(1, 3, sharey=True) |
| Spacing that avoids overlaps | fig, axs = plt.subplots(2, 2, layout="constrained") |
| Fix overlaps after drawing | fig.tight_layout() |
| Columns of different widths | fig, axs = plt.subplots(1, 2, width_ratios=[3, 1]) |
| Named layout with a wide panel | fig, axd = plt.subplot_mosaic([["top", "top"], ["left", "right"]])
axd["top"].plot(x, y) |
| Small plot inside a plot | inset = ax.inset_axes([0.6, 0.6, 0.35, 0.35]) |
| Hide an empty panel | axs[0].axis("off") |
Styles and defaults
| Task | Code |
|---|
| Use a built-in style | plt.style.use("ggplot") |
| List the styles | plt.style.available |
| Seaborn look without seaborn | plt.style.use("seaborn-v0_8-whitegrid") |
| Style for one figure only | with plt.style.context("dark_background"):
fig, ax = plt.subplots() |
| Default font size | plt.rcParams["font.size"] = 12 |
| Default figure size and resolution | plt.rcParams["figure.figsize"] = (8, 4), plt.rcParams["figure.dpi"] = 120 |
| Back to the defaults | plt.style.use("default") |
| Common colormaps: sequential, diverging, gray | "viridis", "coolwarm", "Greys" |
Plot from pandas
pandas plots with Matplotlib. Pass ax= to draw into your own axes, or keep the ax that pandas returns.
| Task | Code |
|---|
| DataFrame columns into your axes | df.plot(x="month", y="sales", ax=ax) |
| Bar chart of a group summary | df.groupby("city")["sales"].sum().plot.bar(ax=ax) |
| Keep styling what pandas drew | ax = df.plot(x="month", y="sales")
ax.set_title("Sales") |
| Columns straight into Matplotlib | ax.plot(df["month"], df["sales"]) |
Matplotlib 3.11: old forms and what to write now
Code copied from older tutorials can fail or warn in Matplotlib 3.11. Write the forms in the Matplotlib 3.11 column instead.
| Task | Matplotlib 3.11 | Old form |
|---|
| Box plot labels | ax.boxplot([data, data * 2], tick_labels=["A", "B"]) | labels=["A", "B"] |
| Horizontal box or violin plot | ax.boxplot(data, orientation="horizontal") | vert=False |
| Get a colormap | plt.colormaps["viridis"] | plt.cm.get_cmap("viridis") |
| Seaborn style | plt.style.use("seaborn-v0_8") | plt.style.use("seaborn") |
The Matplotlib code you need to turn data into a clear chart, grouped by task: figure and axes, plot types, lines and colors, titles and legends, axes and ticks, subplots and layout, styles, and plotting from pandas. Each row is a short snippet you can paste, plus one line on what it does.
Everything here uses the object-oriented style (fig, ax = plt.subplots(), then methods on ax) and is written for Matplotlib 3.11, the version Coddy runs. Older forms such as labels= in boxplot appear only in the last table, next to what to write instead. The examples use x = np.linspace(0, 10, 50), y = np.sin(x) and a sample of normal values called data. New to Python itself? Start with the Python cheat sheet, and keep the pandas, NumPy and scikit-learn cheat sheets next to this one.