Annotations & Text

Reviewed & published by Brayan K

Matplotlib is a Python library for creating charts and visualizations — and annotations let you write labels directly on a chart and draw arrows pointing at the exact data points that matter.

Part of the free Matplotlib course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll add free-floating text with plt.text(), point arrows at a peak with ax.annotate(), and wrap labels in a styled box.

What You'll Learn in This Lesson

1 Adding Plain Text with plt.text()

The simplest way to write on a chart is plt.text(x, y, "label"). The first two arguments are data coordinates — where on the chart the text begins — and the third is the string to show.

import matplotlib.pyplot as plt

x = [0, 1, 2, 3, 4, 5]
y = [0, 1, 4, 9, 16, 25]

plt.plot(x, y, marker='o')

# Place a label at the data point (3, 9)
plt.text(3, 9, "  x squared", fontsize=11)

plt.title("Labelling a point")
plt.show()
The chart this code draws: Write a label at a data coordinate with plt.text()
What this code draws, rendered with Matplotlib.

What you'll see: the rising curve with the words "x squared" sitting just beside the point (3, 9). The text floats at that data location with no arrow — that's the difference from annotate().

2 Pointing an Arrow with ax.annotate()

To draw an arrow from a label to a target point, use ax.annotate(). The xy argument is the point the arrow points to; xytext is where the label sits; and arrowprops describes the arrow itself.

import matplotlib.pyplot as plt

x = [1, 2, 3, 4, 5]
y = [10, 25, 15, 30, 20]

fig, ax = plt.subplots()
ax.plot(x, y, marker='o')

# Arrow points TO (4, 30); text sits at (2.5, 32)
ax.annotate(
    "Big jump",
    xy=(4, 30),
    xytext=(2.5, 32),
    arrowprops=dict(arrowstyle="->")
)

ax.set_title("Annotating with an arrow")
plt.show()
The chart this code draws: Point an arrow from a label to a data point
What this code draws, rendered with Matplotlib.

What you'll see: the zig-zag line with the text "Big jump" floating at (2.5, 32) and a thin arrow stretching from those words down to the high point at (4, 30) — drawing the eye straight to it.

A common use is highlighting the maximum. You can find the peak with Python's max() and feed its coordinates into annotate().

import matplotlib.pyplot as plt

x = [1, 2, 3, 4, 5, 6]
y = [3, 8, 5, 12, 9, 6]

fig, ax = plt.subplots()
ax.plot(x, y, marker='o')

# Find the highest point
peak_y = max(y)
peak_x = x[y.index(peak_y)]

ax.annotate(
    f"Max = {peak_y}",
    xy=(peak_x, peak_y),
    xytext=(peak_x - 1.5, peak_y + 1),
    arrowprops=dict(arrowstyle="->", color="red")
)

ax.set_title("Highlighting the maximum")
plt.show()
The chart this code draws: Compute the peak and point an arrow at it
What this code draws, rendered with Matplotlib.

What you'll see: the line chart with a red arrow pointing at the tallest dot (x=4, y=12) and the label "Max = 12" beside it — computed automatically from the data, not hard-coded.

3 Boxing Text with bbox

To make a label stand out, draw a box behind it with the bbox argument. Pass a dictionary that sets the box shape (boxstyle), fill color (facecolor), and transparency (alpha).

import matplotlib.pyplot as plt

x = [0, 1, 2, 3, 4]
y = [1, 3, 2, 5, 4]

plt.plot(x, y, marker='o')

# Text wrapped in a rounded yellow box
plt.text(
    1, 4, "Important!",
    fontsize=12,
    bbox=dict(boxstyle="round", facecolor="yellow", alpha=0.6)
)

plt.title("Text inside a box")
plt.show()
The chart this code draws: Wrap a text label in a styled bbox
What this code draws, rendered with Matplotlib.

What you'll see: the line chart with the word "Important!" sitting inside a soft, rounded, semi-transparent yellow box near (1, 4) — far more eye-catching than bare text.

🎯 Your Turn: Fill in the Blanks

Replace each ___ to point an arrow at the data point (3, 9).

import matplotlib.pyplot as plt

x = [1, 2, 3, 4]
y = [1, 4, 9, 16]

fig, ax = plt.subplots()
ax.plot(x, y, marker='o')

ax.annotate(
    "Look here",
    ___=(3, 9),          # the point the arrow points to
    xytext=(1.5, 11),    # where the text sits
    arrowprops=dict(arrowstyle="___")   # a simple arrow
)

plt.___()

📋 Common Errors & Quick Reference

❌ My text shows but there's no arrow

You left out arrowprops. Add arrowprops=dict(arrowstyle="->") to draw the arrow.

❌ The label is off the chart

xy and xytext use data coordinates. Pick values inside your axis ranges, not pixels.

CallWhat It Does
plt.text(x, y, "s")Place plain text at a point
ax.annotate("s", xy=...)Text plus optional arrow
arrowprops=dict(arrowstyle="->")Draw the arrow
bbox=dict(boxstyle="round", ...)Box around the text

🏆 Mini Challenge: Label the Peak

Plot monthly visitors, then point a boxed arrow at the busiest month.

import matplotlib.pyplot as plt

months = ['Jan', 'Feb', 'Mar', 'Apr', 'May']
visitors = [120, 200, 150, 320, 260]

fig, ax = plt.subplots()
ax.plot(months, visitors, marker='o')

# 1. Find the busiest month
peak = max(visitors)
i = visitors.index(peak)

# 2. Annotate it with an arrow and a box
ax.annotate(
    f"Peak: {peak}",
    xy=(i, peak),
    xytext=(i - 1.5, peak + 10),
    arrowprops=dict(arrowstyle="->", color="green"),
    bbox=dict(boxstyle="round", facecolor="lightgreen", alpha=0.6)
)

ax.set_title("Monthly visitors")
plt.show()
The chart this code draws: Annotate the busiest month with a boxed arrow
What this code draws, rendered with Matplotlib.
import matplotlib.pyplot as plt

x = [1, 2, 3, 4, 5]
y = [2, 5, 3, 8, 6]

fig, ax = plt.subplots()
ax.plot(x, y, marker="o")

peak = y.index(max(y))
ann = ax.annotate("peak", xy=(x[peak], y[peak]), xytext=(x[peak] - 1.5, y[peak]),
                  arrowprops=dict(arrowstyle="->"))
note = ax.text(1, 7, "hand-placed note")

print("Annotations on the axes:", len(ax.texts))
print("Annotation text:", ann.get_text())
print("Points at:", [int(v) for v in ann.xy])
print("Label sits left of the point:", ann.xyann[0] < ann.xy[0])
print("Plain text:", note.get_text())
print("Peak value:", max(y), "at x =", x[peak])

# ✅ Expected output:
# Annotations on the axes: 2
# Annotation text: peak
# Points at: [4, 8]
# Label sits left of the point: True
# Plain text: hand-placed note
# Peak value: 8 at x = 4
The chart this code draws: Where the arrow points, where the label sits, and how many text objects the axes ended up with
What this code draws, rendered with Matplotlib.

❓ Frequently Asked Questions

Lesson complete — your charts can talk now!

You placed plain text with plt.text(), pointed arrows at a peak with ax.annotate(), and boxed a label with a bbox dictionary.

Practice quiz

What does plt.text(x, y, 'label') do?

  • Places plain text at a data coordinate
  • Draws an arrow
  • Adds a legend
  • Sets the axis limits

Answer: Places plain text at a data coordinate. plt.text places a plain string at the given data point with no arrow.

Which function can draw an arrow from a label to a target point?

  • plt.text()
  • ax.annotate()
  • ax.legend()
  • plt.grid()

Answer: ax.annotate(). ax.annotate() can draw an arrow using xy, xytext, and arrowprops.

In ax.annotate(), what does the xy argument specify?

  • The figure size
  • The text color
  • The point the arrow points TO
  • Where the text sits

Answer: The point the arrow points TO. xy is the data point being highlighted — where the arrow points.

In ax.annotate(), what does xytext specify?

  • The arrow style
  • The marker shape
  • The axis label
  • Where the text label sits

Answer: Where the text label sits. xytext is the location of the text label itself.

Which argument actually draws the arrow in annotate()?

  • arrowprops
  • xytext
  • bbox
  • fontsize

Answer: arrowprops. Without arrowprops, annotate places text but draws no arrow.

What units do xy and xytext use by default?

  • Pixels
  • Data coordinates
  • Inches
  • Points

Answer: Data coordinates. By default xy and xytext are in data coordinates of the axes.

How do you draw a box behind a text label?

  • color=
  • marker=
  • bbox=dict(...)
  • alpha=

Answer: bbox=dict(...). The bbox argument takes a dict with boxstyle, facecolor, and alpha.

What does arrowstyle='->' produce in arrowprops?

  • A filled rectangle
  • A dashed grid
  • No arrow
  • A simple line arrow with a head

Answer: A simple line arrow with a head. arrowstyle='->' gives a basic line arrow pointing at xy.

If you omit xytext in annotate(), what happens?

  • The text is placed at xy with no arrow
  • An error is raised
  • The arrow doubles in size
  • The text disappears

Answer: The text is placed at xy with no arrow. Without xytext the text sits right at xy and no arrow is drawn.

Why might a label land off the chart?

  • fontsize is too large
  • The arrow is missing
  • xy/xytext used values outside the axis ranges
  • alpha is set to 1

Answer: xy/xytext used values outside the axis ranges. xy and xytext are data coordinates; values outside the ranges place text off-chart.

Continue this course

Frequently asked questions

What is the difference between plt.text() and ax.annotate()?

plt.text(x, y, 'label') just places text at a point. ax.annotate() does that too, but can also draw an arrow from the text to a target point using the xy and xytext arguments — perfect for pointing at a peak or outlier.

What do xy and xytext mean in annotate()?

xy is the data point the arrow points TO (the thing you're highlighting). xytext is where the text label sits. If you omit xytext, the text is placed right at xy with no arrow.

How do I draw the arrow?

Pass an arrowprops dictionary, for example arrowprops=dict(arrowstyle='->'). Without arrowprops, annotate() places text but draws no arrow at all.

How do I put a box around my text?

Use the bbox argument with a dict, like bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.5). It works with both plt.text() and ax.annotate().