Matplotlib Annotations and Text: Call Out Insights Clearly
Updated on
Unlabeled peaks and bars force readers to guess what matters. If text overlaps points or floats far from the data, the story gets lost. Clear annotations solve this: short labels, well-placed arrows, and consistent fonts that guide attention without clutter.
Choose the right text tool
| Task | Tool | Why |
|---|---|---|
| Mark a specific point with an arrow | annotate | Couples text and arrow in one call |
| Add free text in axes coords | text | Titles, inline notes, panel labels |
| Label bars automatically | bar_label | Anchors labels to bars with offsets |
| Keep labels inside bounds | clip_on=True, offsets | Prevents labels from bleeding outside axes |
| Wrap or format text | textwrap.fill, f-strings | Keeps labels short and readable |
Annotate peaks with arrows
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 300)
y = np.sin(x) * np.exp(-x / 5)
fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(x, y, color="tab:blue")
peak_idx = np.argmax(y)
peak_x, peak_y = x[peak_idx], y[peak_idx]
ax.annotate(
"Peak signal",
xy=(peak_x, peak_y),
xytext=(peak_x + 1.2, peak_y + 0.2),
arrowprops=dict(arrowstyle="->", color="tab:red", lw=1.5),
bbox=dict(boxstyle="round,pad=0.3", fc="white", ec="tab:red", alpha=0.9),
fontsize=10,
color="tab:red",
)
ax.set_xlabel("Time (s)")
ax.set_ylabel("Amplitude")
ax.grid(True, linestyle="--", alpha=0.3)
plt.tight_layout()
plt.show()Tips:
- Offset text (
xytext) so arrows stay short and readable. - Use a light background box for contrast on busy plots.
- Keep phrases brief (2–4 words) to avoid covering data.
Label bars without overlap
import matplotlib.pyplot as plt
items = ["A", "B", "C", "D"]
values = [28, 35, 30, 22]
fig, ax = plt.subplots(figsize=(6, 4))
bars = ax.bar(items, values, color="tab:purple", alpha=0.9)
ax.bar_label(bars, labels=[f"{v}k" for v in values], padding=3)
ax.set_ylabel("Units sold")
ax.set_ylim(0, 40)
plt.tight_layout()
plt.show()Tips:
- Use
paddingto control distance from bar tops; set negative padding to place labels inside. - For stacked bars, pass
label_type="center"to place labels in the middle of each stack. - When values are small, switch text color to white and place labels inside for contrast.
Inline notes and safe placement
ax.text(
0.02, 0.95, "Experiment 3\nn=120",
transform=ax.transAxes,
ha="left", va="top",
fontsize=9,
bbox=dict(boxstyle="round", fc="white", ec="0.7", alpha=0.8)
)Guidelines:
- Use
transform=ax.transAxesto position text in axis coordinates (0–1) so it stays in the same corner after resizes. - Set
clip_on=Truewhen labeling inside plots that may go out of bounds (e.g.,ax.text(..., clip_on=True)). - Align text with
ha/vato avoid edge collisions; pair with short line breaks for clarity.
Quick troubleshooting
- Overlapping labels? Reduce font size, add
padding, or rotate text slightly. - Arrow missing? Increase
zorderonannotateor draw after filled areas. - Labels outside the figure? Use
constrained_layout=Trueor trim long text withtextwrap.fill.