5.4.15 · D2Scientific Computing (Python)

Visual walkthrough — Matplotlib — figure - axes architecture

1,748 words8 min readBack to topic

We build it in seven steps. Each step: WHAT we do, WHY we do it, and WHAT IT LOOKS LIKE.


Step 1 — Start with literally nothing: a blank sheet

WHAT. We call one function and it hands us an empty canvas:

  • — the variable that now holds the Figure object. Everything else we build will live inside this.
  • — the factory: "make me one blank sheet and give me a handle to it."

WHY this and not just start plotting? Because every visible thing needs a container to live in. If you skip this, Matplotlib silently makes one for you (that hidden-state trap the parent warned about). We make it explicit so we always know which sheet we are on.

PICTURE. An empty bordered rectangle — the paper, no plot yet.

Figure — Matplotlib — figure - axes architecture

Step 2 — Tape a frame onto the sheet: the Axes

WHAT. We add one Axes to the Figure:

  • — the handle to this one plot region.
  • — asks the Figure to place a frame on itself. Note who we talk to: the Figure owns the Axes, so the Figure creates it.
  • — position in Figure-fraction units: [left, bottom, width, height]. So the frame starts in from the left and bottom and spans of the sheet each way.

WHY a separate box at all? Because you might want several plots on one sheet later. Separating "the sheet" (Figure) from "a plot on the sheet" (Axes) is the whole idea that makes multi-panel figures possible.

PICTURE. The same sheet, now with one framed region taped inside it, offset from the edges.

Figure — Matplotlib — figure - axes architecture

Step 3 — Give the frame its rulers: the Axis (singular)

WHAT. These come automatically with the Axes — we don't create them, we address them:

  • — the object controlling the numbers along the bottom.
  • — the same for the left edge.

WHY split Axes from Axis? Because "which plot" (Axes) and "which direction inside that plot" (Axis) are different questions. When you write ax.set_xlabel("x"), under the hood you are talking to ax.xaxis. Keeping them separate is exactly the trap the parent note flagged: Axe-S = a Subplot, Axi-S = a Single ruler.

PICTURE. Zoom into the frame: tick marks and numbers appear on the bottom edge (red = the x Axis) and the left edge (the y Axis).

Figure — Matplotlib — figure - axes architecture

Step 4 — Draw something: the first Artist

WHAT. We add data to the Axes and it returns a Line2D Artist:

  • — asks this Axes to draw a curve using its own rulers. Note who owns the drawing: the Axes, not the Figure.
  • — the data (NumPy arrays; see NumPy — arrays as plot data).
  • — the returned Line2D Artist. The trailing comma unpacks the one-element list ax.plot returns.

WHY does plot belong to ax and not fig? Because data is drawn in a coordinate system, and only the Axes has one. The Figure has no x/y rulers, so it cannot plot. This is why saving is a Figure job but plotting is an Axes job.

PICTURE. The red curve now sits inside the frame, measured against the ticks from Step 3.

Figure — Matplotlib — figure - axes architecture

Step 5 — The full nesting, in one exploded view

WHAT. We can now read the canonical one-liner as a shortcut for Steps 1–3 at once:

  • creates the Figure (Step 1),
  • creates one Axes inside it with its two Axis rulers (Steps 2–3),
  • returns both handles so you never rely on hidden state.

WHY prefer this line? One call, both boxes named, zero guessing about "which is current." Contrast with the pyplot style in Matplotlib — pyplot vs object-oriented API.

PICTURE. An exploded diagram: Figure at the back, Axes floating in front of it, Artists (line, title, ticks) floating in front of the Axes — the containment made literal.

Figure — Matplotlib — figure - axes architecture

Step 6 — Edge case A: many Axes, one Figure (the grid)

WHAT.

  • nrows, ncols: a grid of Axes.
  • — now a 2-D NumPy array; address a cell with (row , column ).

WHY the array? With four plots there is no sensible "current" Axes — so pyplot's hidden-state model breaks down. Indexing removes the ambiguity: axes[1][1].plot(...) means exactly the bottom-right frame.

Degenerate shapes (the parent's "returns" rule, made visual):

  • plt.subplots()one Axes (not an array).
  • plt.subplots(1, 3)1-D array axes[i].
  • plt.subplots(2, 2)2-D array axes[r][c].

PICTURE. One sheet, four framed regions, with the red one labelled axes[1][1].

Figure — Matplotlib — figure - axes architecture

Step 7 — Edge case B: the hidden "current" Axes (why blank plots happen)

WHAT / WHY. Two failure modes, both container confusion:

  1. Everything piles on one plot. You call plt.plot twice; both hit the same current Axes because you never made a new one.
  2. Blank figure, no error. You made fig, ax explicitly, then plotted with plt.plot(...) — which quietly created a different, hidden Axes and drew there. Your ax stays empty.

Fix (the whole point of the OO style): always talk to your named ax, and end with a Figure-level fig.savefig(...) (see Saving figures — dpi, formats, bbox).

PICTURE. Left: your named ax (empty). Right: the hidden gca() Axes where the red curve secretly went — the "blank plot" mystery solved.

Figure — Matplotlib — figure - axes architecture

The one-picture summary

Everything above compressed into a single nested diagram: the Figure wraps the Axes, which owns its two Axis rulers and holds the Artists; the pyplot API pokes at the box through a hidden "current" pointer, while the OO API holds the box handles directly.

Figure — Matplotlib — figure - axes architecture
Recall Feynman: the whole walkthrough in plain words

Start with a blank sheet of paper — that's the Figure, and it becomes your saved picture. On the sheet you tape a frame — that's an Axes, one little plot with its own left-right and up-down rulers. Those rulers, one per direction, are the Axis objects. Inside the frame you draw stuff — a line, a title, tick marks — and every drawn thing is an Artist. plt.subplots() just does "sheet + frame + rulers" in one go and hands you both handles. If you want many pictures on one sheet, ask for a grid and point at each frame by number, axes[row][col], so nothing is ambiguous. The danger is Matplotlib's habit of remembering "the last frame I touched": if you draw with plt.plot instead of ax.plot, your stuff can sneak into a hidden frame and your named one stays empty. Moral: name your boxes and talk to them by name.


Active recall

Connections

  • 5.4.15 Matplotlib — figure - axes architecture (Hinglish)
  • Matplotlib — pyplot vs object-oriented API
  • Matplotlib — subplots and GridSpec layout
  • Matplotlib — Artists, Line2D and styling
  • NumPy — arrays as plot data
  • Saving figures — dpi, formats, bbox
  • Scientific Computing (Python)