Quick answer: Matplotlib annotate() creates a callout attached to a coordinate. xy is the target point and xytext is the text position; arrowprops controls the connector. Keep coordinate systems, clipping, layout, and the final export size in mind so an annotation that looks correct interactively remains visible in a saved figure. A good annotation explains one decision or feature, not every point in a chart, so use labels selectively and keep the visual hierarchy readable.

matplotlib.annotate() adds explanatory text to a plot and can draw an arrow from that text to a specific data point. Use it when a chart needs a callout, peak label, threshold note, outlier explanation, or any label that should point to a plotted value. Annotations and legends label different chart elements; Fix No Handles With Labels Found in Legend fixes the case where legend() cannot find labeled artists.
In most object-oriented Matplotlib code, call ax.annotate(). The pyplot shortcut plt.annotate() works too, but using the Axes method is clearer when a figure has more than one subplot.
Basic Syntax
ax.annotate(
text,
xy,
xytext=None,
xycoords="data",
textcoords=None,
arrowprops=None,
annotation_clip=None,
**kwargs,
)
| Argument | Purpose |
|---|---|
text |
The annotation label. |
xy |
The point being annotated. This is where the arrow points. |
xytext |
The text position. If omitted, the text is placed at xy. |
xycoords |
Coordinate system for xy. The default is data. |
textcoords |
Coordinate system for xytext. |
arrowprops |
Dictionary of arrow styling options. If omitted, no arrow is drawn. |
Simple Annotation Example
This example labels the highest point of a sine curve. The annotation point uses data coordinates, while the text is offset from that point by 25 points horizontally and 35 points vertically.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 10, 100)
y = np.sin(x)
peak_index = np.argmax(y)
fig, ax = plt.subplots()
ax.plot(x, y)
ax.annotate(
"peak",
xy=(x[peak_index], y[peak_index]),
xytext=(25, 35),
textcoords="offset points",
arrowprops={"arrowstyle": "->", "color": "crimson"},
)
plt.show()
The key detail is the difference between xy and xytext. xy identifies the data point. xytext controls where the label appears.

Add a Box Around the Label
Because annotate() accepts text styling keyword arguments, you can add a background box with bbox and align the label with ha and va.
ax.annotate(
"maximum value",
xy=(x[peak_index], y[peak_index]),
xytext=(30, 40),
textcoords="offset points",
ha="left",
va="bottom",
bbox={"boxstyle": "round,pad=0.3", "fc": "white", "ec": "crimson"},
arrowprops={"arrowstyle": "->", "color": "crimson"},
)
This pattern is useful when the plotted line or background would make plain text hard to read.
Common Coordinate Systems
Matplotlib annotations are flexible because the arrow target and text can use different coordinate systems.
data: Uses the plotted data coordinates. This is the default forxy.offset points: Places the text a fixed point offset fromxy. This is common for labels that should follow a data point without covering it.axes fraction: Uses the Axes box, where(0, 0)is bottom-left and(1, 1)is top-right.figure fraction: Uses the full figure instead of a single Axes.
Annotation With Axes-Fraction Text
Sometimes the arrow should point to data, but the text should stay in a stable corner of the plot. Use xycoords="data" and textcoords="axes fraction".
ax.annotate(
"important point",
xy=(4, np.sin(4)),
xycoords="data",
xytext=(0.05, 0.95),
textcoords="axes fraction",
ha="left",
va="top",
arrowprops={"arrowstyle": "->", "connectionstyle": "arc3,rad=0.2"},
)
This keeps the label near the top-left of the Axes even if the data limits change.

Styling Annotation Arrows
For modern Matplotlib code, prefer the fancy-arrow style by setting arrowstyle. You can also use connectionstyle for curved arrows.
arrowprops = {
"arrowstyle": "->",
"color": "black",
"linewidth": 1.5,
"connectionstyle": "arc3,rad=-0.2",
}
If you need standalone arrows rather than text annotations, see the related Matplotlib arrow guide.
Why Matplotlib annotate Is Not Showing
- The point is outside the visible axis limits. Check
ax.set_xlim()andax.set_ylim(). The Matplotlib ylim guide covers y-axis limits in detail. - The text is clipped. Try moving
xytext, usingannotation_clip=False, or increasing the plot margins. - The arrow color blends into the chart. Set a visible
color,linewidth, orbbox. - You annotated the wrong Axes. In multi-plot figures, call
annotate()on the correctax. The Matplotlib gca guide explains current Axes behavior.

Related Matplotlib Guides
Annotations often pair well with shape and styling helpers. See how to draw circles in Matplotlib, how to change Matplotlib background color, and Matplotlib pcolormesh for more plotting examples.
Official References
- Matplotlib Axes.annotate documentation
- Matplotlib pyplot.annotate documentation
- Matplotlib annotations guide
Conclusion
Use ax.annotate() when a plot needs a clear callout. Set xy for the data point, xytext for the label position, textcoords for offsets, and arrowprops for arrows. These four options handle most annotation tasks cleanly.
Place Text At A Data Point
The simplest annotation uses data coordinates for both the target and text. This keeps the label tied to plotted values when the axes limits change. Use a short label and inspect crowded points before adding more callouts.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x = [1, 2, 3, 4]
y = [2, 5, 3, 7]
ax.plot(x, y, marker="o")
ax.annotate("peak", xy=(4, 7))
fig.savefig("annotated.png", dpi=160)
plt.close(fig)
Separate xy And xytext
xy identifies what the annotation explains, while xytext identifies where the label sits. Moving text away from the point prevents overlap. arrowprops can add a visible connector and use an arrow style that survives the output size.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.scatter([1, 2, 3], [2, 5, 3])
ax.annotate("highest value", xy=(2, 5), xytext=(2.4, 6), arrowprops={"arrowstyle": "->"})
fig.tight_layout()

Choose Coordinate Systems
By default, xy and xytext use data coordinates. axes fraction, figure fraction, offset points, and other coordinate systems are useful when a label should stay near a corner or use a fixed visual offset. Set the coordinate systems explicitly when their meanings differ.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([0, 1], [0, 1])
ax.annotate("axes note", xy=(0.8, 0.8), xycoords="data", xytext=(0.05, 0.95), textcoords="axes fraction", arrowprops={"arrowstyle": "->"})
Prevent Clipping And Overlap
An annotation can be clipped by the axes, hidden behind another artist, or cut off by the saved bounding box. Use annotation_clip deliberately, reserve layout space, and render the final PNG or PDF at its actual dimensions. A label should explain data without covering the point or neighboring labels.
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(6, 4), constrained_layout=True)
ax.plot([1, 2, 3], [1, 4, 2])
ax.annotate("callout", xy=(2, 4), xytext=(2.5, 4.5), annotation_clip=False)
fig.savefig("callout.png", dpi=160, bbox_inches="tight")
plt.close(fig)
Matplotlib’s official Axes.annotate() reference defines xy, xytext, coordinate systems, arrowprops, and clipping. Treat annotations as part of the figure layout, not as an afterthought added after export.
For related chart labeling and layout, compare legend handle fixes, current Axes inspection, and vertical plot markers when placing callouts that should remain readable.
Frequently Asked Questions
What does Matplotlib annotate() do?
It adds text at or near a plot location and can draw an arrow from the text to a target point.
What is the difference between xy and xytext?
xy identifies the point being annotated, while xytext identifies where the annotation text should be placed.
How do I add an arrow to an annotation?
Pass an arrowprops dictionary, usually with arrowstyle and connection settings, to draw a pointer between the text and target.
Why is a Matplotlib annotation not visible?
Check coordinate systems, axis limits, clipping, text color, layout, and whether the annotation is outside the saved figure bounds.