Text, annotations and bar labels

Matplotlib · Practical · 10 min

Annotations turn a chart into an argument by telling readers exactly where to look.

ax.text(x, y, "label") places text at data coordinates. Align it with ha (horizontal: "left", "center", "right") and va. To place text relative to the Axes box rather than the data, for example in the top-left corner regardless of limits, add transform=ax.transAxes and use coordinates between 0 and 1.

ax.annotate draws text with an optional arrow:

ax.annotate("peak", xy=(x0, y0), xytext=(x0 + 1, y0 + 2),
            arrowprops=dict(arrowstyle="->"))

xy is the point being annotated and xytext where the text sits. textcoords="offset points" makes xytext an offset in points, which keeps the label a fixed visual distance away however the data is scaled.

Reference lines and spans mark thresholds and periods:

  • ax.axhline(y) and ax.axvline(x) span the whole Axes;
  • ax.axhspan(y0, y1) and ax.axvspan(x0, x1, alpha=0.2) shade a band;
  • ax.fill_between(x, low, high, alpha=0.3) shades between curves, ideal for confidence bands.

For bar charts, ax.bar_label(bars) writes each bar's value on or above it; fmt="{:.1f}" and padding=3 adjust the format and spacing, and label_type="center" puts labels inside stacked segments.

Restraint matters: one or two annotations make a point, ten create clutter.

Example

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 3.5), layout="constrained")

x = np.linspace(0, 10, 200)
y = np.sin(x) * np.exp(-x / 5)
i = y.argmax()
ax1.plot(x, y)
ax1.annotate("peak", xy=(x[i], y[i]), xytext=(20, -20),
             textcoords="offset points", arrowprops=dict(arrowstyle="->"))
ax1.axhline(0, color="gray", linewidth=0.8)
ax1.axvspan(6, 8, alpha=0.15, color="C1")
ax1.text(0.02, 0.95, "damped", transform=ax1.transAxes, va="top")

bars = ax2.bar(["Q1", "Q2", "Q3"], [12.5, 18.25, 9.0])
labels = ax2.bar_label(bars, fmt="{:.1f}", padding=3)
print([t.get_text() for t in labels])   # ['12.5', '18.2', '9.0']
fig.savefig("annotated.png")

Remember

  • ax.text places text; transform=ax.transAxes uses 0..1 Axes coordinates
  • ax.annotate(text, xy=point, xytext=..., arrowprops=...) points at data
  • axhline, axvspan, fill_between and bar_label cover most chart callouts

Watch out for

  • Positioning text in data coordinates that later fall outside the limits, so the label silently disappears.

Check yourself

  1. You want a note fixed in the top-right corner of the plot no matter how the data limits change. What do you use?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: matplotlib.org/stable/users/explain/text/annotations.html