Skip to content

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

TaskToolWhy
Mark a specific point with an arrowannotateCouples text and arrow in one call
Add free text in axes coordstextTitles, inline notes, panel labels
Label bars automaticallybar_labelAnchors labels to bars with offsets
Keep labels inside boundsclip_on=True, offsetsPrevents labels from bleeding outside axes
Wrap or format texttextwrap.fill, f-stringsKeeps 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 padding to 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.transAxes to position text in axis coordinates (0–1) so it stays in the same corner after resizes.
  • Set clip_on=True when labeling inside plots that may go out of bounds (e.g., ax.text(..., clip_on=True)).
  • Align text with ha/va to 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 zorder on annotate or draw after filled areas.
  • Labels outside the figure? Use constrained_layout=True or trim long text with textwrap.fill.