Matplotlib·Lesson 15·10 min·0/3 exercises

Making it fit

constrained layout, figure-level labels, and one legend for the lot

layout="constrained"fig.tight_layoutfig.suptitlefig.supxlabelfig.legendsubplots_adjust

Watch it happen

Play it through, or step back and forth yourself.

cupsday02550cupsday02550cupsday02550cupsday02550
labels collide with the neighbour
fig, axes = plt.subplots(2, 2)
# the default. it will overlap.

Put four panels in a figure and matplotlib's default spacing lets the y label of one collide with the ticks of its neighbour. Nothing is broken — the default was chosen long before anyone put labels on everything.

The idea

Put four labelled panels in one figure with matplotlib's default spacing and the y label of one will collide with the tick labels of its neighbour. Nothing is broken — the defaults predate the habit of labelling everything — but it does have to be fixed every single time.

constrained layout

fig, axes = plt.subplots(2, 2, layout="constrained")

That's the whole recommendation. It measures every label, title, tick and colourbar and solves for spacing that fits, and it keeps solving as the figure changes. Pass it at creation and stop thinking about spacing.

Why not tight_layout

fig.tight_layout() is the older answer and it does something subtly different: it adjusts once, at the moment you call it. Add a suptitle afterwards, or a colourbar, and the spacing you computed is stale — which is exactly the situation where people conclude matplotlib's layout is broken.

Concretely:

  • tight_layout: one-shot, must be called last, often misses colourbars, doesn't support outside legends.
  • constrained: continuous, set once at creation, handles colourbars and outside legends.

You can also set it after the fact with fig.set_layout_engine("constrained"), which is handy when a library handed you the figure.

Say it once

Four panels with the same y label means four copies of the same word. Promote shared meaning to the figure:

fig.suptitle("Chai sales, March", fontsize=14)
fig.supxlabel("day")
fig.supylabel("cups")

suptitle is a figure title, distinct from each Axes' own set_title — that difference from lesson 1 finally earns its keep here. Keep the per-panel titles for what varies (the category), and put what's shared at the top.

One legend for all

handles, labels = axes.flat[0].get_legend_handles_labels()
fig.legend(handles, labels, loc="outside upper right")

If every panel plots the same two series, one key serves all of them. The "outside …" positions place the legend beyond the panels rather than on top of the data — and they only work with constrained layout, which is one more reason to have it on.

get_legend_handles_labels() is the piece people miss: it hands you the artists and their labels from one Axes so you can hand them to the figure.

Manual spacing

fig.subplots_adjust(hspace=0.4, wspace=0.3, top=0.9, left=0.1)

Still there when you need exact control — a figure that has to match a template, say. Note that it conflicts with the layout engines: use both and one silently overrides the other, which is a confusing hour to spend. Pick one, and almost always pick constrained.

A checklist for a multi-panel figure

  1. layout="constrained" at creation.
  2. figsize scaled to the grid — roughly 3×2.2 inches per panel.
  3. Share the axis you're comparing on.
  4. Shared labels to the figure, distinguishing labels to the panels.
  5. One legend, outside.
  6. bbox_inches="tight" when you save, as a belt-and-braces crop.

See it run

The lesson's code, ready to run and to fiddle with.

Putting the kettle on…

Starting up…

Worked example

not graded

Already written and ready to go — press Run to see what it does, then change a number, a column name, anything, and run it again.

tryremoving layout="constrained" and watching the labels collide again.

Press Run — the output appears here.

Your turn

3 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.

Make a 2×2 grid using constrained layout, and return the engine's class name: fig.get_layout_engine().__class__.__name__.

your answer

Give a 1×2 figure the figure-level title "Chai sales" and the figure-level x label "day". Return the two texts as a list, in that order.

your answer

Plot a labelled line on the first panel of a 1×2 grid, then collect its handles with get_legend_handles_labels() and put one legend on the figure. Return len(fig.legends).

your answer