Matplotlib Arrows and Annotations Guide

Matplotlib arrows are useful for pointing to peaks, showing direction, connecting two points, or marking a change on a plot. The important update is that matplotlib.pyplot.arrow() still exists, but the Matplotlib documentation discourages it for many plots because axis limits and aspect ratio can distort the arrow. For most chart annotations, use annotate() with arrowprops.

Which Matplotlib arrow API should you use?

  • Use ax.annotate() when you want an arrow with a label.
  • Use ax.annotate("", ...) when you want only an arrow and no text.
  • Use FancyArrowPatch when you need a patch object with more control over style, connection, or reuse.
  • Use plt.arrow() only for simple cases where you understand the aspect-ratio limitation.

Draw an arrow with annotate()

The most common pattern is to point from label text to a data point. The xy argument is the point being annotated, and xytext is where the label should appear.

import matplotlib.pyplot as plt

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

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

ax.annotate(
    "Peak",
    xy=(2, 6),
    xytext=(2.35, 7.2),
    arrowprops=dict(arrowstyle="->", linewidth=2, color="tab:blue"),
)

plt.show()

This approach is usually better than plt.arrow() because the arrow is drawn through the annotation system and supports text placement, coordinate choices, clipping behavior, and patch styling.

Use data and offset coordinates correctly

By default, xy and xytext use data coordinates. That means xy=(2, 6) points to the data location where x is 2 and y is 6. This is usually what you want when labeling a point on a line chart, scatter plot, or bar chart.

For labels that should sit a fixed distance away from the point, use offset coordinates. The example below places the text 25 points above and 15 points to the right of the selected point, while the arrow still points to the data location.

ax.annotate(
    "Peak",
    xy=(2, 6),
    xytext=(15, 25),
    textcoords="offset points",
    arrowprops=dict(arrowstyle="->"),
)

This is often easier to maintain than hard-coding another data coordinate for the label position, especially when the data scale changes.

Draw an arrow without text

If you only need an arrow, pass an empty string as the annotation text. This is the Matplotlib documentation’s recommended replacement pattern for many pyplot.arrow() examples.

fig, ax = plt.subplots()

ax.annotate(
    "",
    xy=(0.8, 0.8),
    xytext=(0.2, 0.2),
    arrowprops=dict(arrowstyle="->", linewidth=2),
)

ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
plt.show()

Style arrows with arrowprops

The arrowprops dictionary controls the arrow that connects xytext to xy. Common options include arrowstyle, color, linewidth, mutation_scale, and connectionstyle.

ax.annotate(
    "Change",
    xy=(4, 10),
    xytext=(2.8, 12),
    arrowprops=dict(
        arrowstyle="-|>",
        color="tab:green",
        linewidth=2,
        mutation_scale=18,
        connectionstyle="arc3,rad=0.2",
    ),
)

Use arrowstyle="->" for a simple arrow, "<-" to reverse the head, "<->" for two heads, and "-|>" for a filled head. Curved arrows are controlled through connectionstyle.

Use FancyArrowPatch for custom arrows

FancyArrowPatch is the lower-level patch class behind many annotation arrows. It is useful when you want to create an arrow object directly and add it to an axes.

from matplotlib.patches import FancyArrowPatch

fig, ax = plt.subplots()

arrow = FancyArrowPatch(
    (0.15, 0.25),
    (0.85, 0.75),
    arrowstyle="-|>",
    mutation_scale=24,
    linewidth=2,
    color="tab:green",
)

ax.add_patch(arrow)
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
plt.show()

For most everyday plots, start with annotate(). Move to FancyArrowPatch when you need patch-level control.

What about plt.arrow()?

The basic syntax is still:

plt.arrow(x, y, dx, dy, width=0.01)

Here, x and y are the arrow base, while dx and dy are the change in x and y direction. The problem is that the arrow shape can look wrong after aspect-ratio changes, zooming, or axis-limit changes. That is why current Matplotlib guidance points users toward annotate() for reliable arrows.

Common mistakes

  • Using plt.arrow() for publication plots without checking the final aspect ratio.
  • Confusing xy and xytext. The arrow points from the text position toward xy.
  • Forgetting to set axis limits when an arrow patch is added outside the current data range.
  • Using color and edgecolor inconsistently on patch-based arrows.
  • Adding arrows before deciding the final Matplotlib aspect ratio.

Related Matplotlib guides

For more plot annotation work, read our guides on Matplotlib annotate(), Matplotlib zorder, Matplotlib vertical lines, Matplotlib tight_layout(), and Matplotlib gca().

Official references

Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted