Coordinate systems and transforms

Matplotlib · Expert · 15 min

Placing elements precisely, such as insets, labels at fixed offsets or lines spanning panels, requires choosing the right coordinate system.

Every artist has a transform mapping its coordinates to display pixels. The four you use most:

  • ax.transData: data coordinates, the default for plotted data.
  • ax.transAxes: (0, 0) is the Axes' bottom-left, (1, 1) its top-right.
  • fig.transFigure: the same for the whole figure.
  • fig.dpi_scale_trans: inches from the figure's bottom-left.

Pass any of them as transform= to text, patches or lines. Blended transforms mix systems per axis: ax.get_xaxis_transform() uses data x and axes y, so a span from y=0 to 1 always covers the full height at a given x (this is how axvline works).

Transforms compose with +. transData itself is transScale + transLimits + transAxes, which is how log scales and limits feed into pixels. ScaledTranslation(dx, dy, fig.dpi_scale_trans) adds an offset in inches, so ax.transData + offset places something a fixed physical distance from a data point regardless of zoom.

transform.transform(points) converts to display pixels and transform.inverted() goes back, for example to convert a mouse position or to compute where a label landed.

Display coordinates depend on figure size, dpi and limits, so compute them only after the layout is final (after a draw), and never store pixel positions across resizes. ax.inset_axes([x, y, w, h]) uses axes coordinates by default, making insets resolution-independent.

Example

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.transforms as mtransforms

fig, ax = plt.subplots(figsize=(4, 3), dpi=100)
ax.set_xlim(0, 10); ax.set_ylim(0, 100)

# data -> display pixels and back
px = ax.transData.transform((5, 50))
print(ax.transData.inverted().transform(px))   # [ 5. 50.]

# axes coordinates: centre of the Axes, whatever the limits
print((ax.transAxes.transform((0.5, 0.5)) == px).all())  # True here

# blended: x in data, y in axes -> full-height marker at x=7
trans = ax.get_xaxis_transform()
ax.plot([7, 7], [0, 1], transform=trans, color="red")

# fixed 10-point offset to the right of a data point
offset = mtransforms.ScaledTranslation(10 / 72, 0, fig.dpi_scale_trans)
ax.text(5, 50, "label", transform=ax.transData + offset)
print(ax.transData.contains_branch(ax.transAxes))   # True
fig.savefig("transforms.png")

Remember

  • transData, transAxes, transFigure and dpi_scale_trans cover most needs
  • Blended transforms mix data and axes coordinates per axis
  • Compose with +; inverted() maps pixels back to data

Watch out for

  • Caching display coordinates before the final layout or dpi is applied.

Check yourself

  1. You want a vertical marker at x = 3 (data) spanning the full height of the Axes whatever the y limits. Which transform fits?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: matplotlib.org/stable/users/explain/artists/transforms_tutorial.html

Builds on

Unlocks

Nothing builds on this yet.