Skip to content

Multi-panel Figures

Plots combine into multi-panel figures in two ways. Both produce the same backend-agnostic grid:

  • Operators: a | b puts panels side by side and a / b stacks them. This is the quickest way, and it nests.
  • Figure: an explicit grid with add(row=, col=, rowspan=, colspan=), for full control.

Composing with operators

| and / accept any plot or figure, including the result of an earlier composition. The result is a figure that you can .render(), .show() or .save() like any other.

Side by side and stacked

a | b places panels in a row. Chaining | gives one flat row rather than nested pairs:

a / b stacks panels in a column:

Nesting and automatic spans

Use parentheses to nest groups. A single panel next to a group stretches to cover it, so you never need to work out spans yourself:

┌──────┬──────┐
│  p1  │  p2  │
├──────┴──────┤
│   pk_fig    │   pk_fig spans both columns
└─────────────┘

┌──────┬────────┐
│  p1  │        │
├──────┤ pk_fig │   pk_fig spans both rows
│  p2  │        │
└──────┴────────┘

Columns with different numbers of rows

Groups don't need to line up. Here a column of two sits beside a column of three. cplots merges the row boundaries (halves and thirds) into one grid, so each panel keeps exactly its share of the height:

┌──────┬────────┐
│      │   p3   │
│  p1  ├────────┤
│      │        │
├──────┤ pk_fig │   4 grid rows, height ratios 2:1:1:2
│      │        │
│  p2  ├────────┤
│      │ hz_fig │
└──────┴────────┘

Relative sizes

cplots.Row and cplots.Col are the classes behind | and /. Call them directly to weight a group's children:

A weighted group nested in a larger layout keeps its internal split. In Row(p1, p2, widths=[3, 1]) | pk_fig, the inner row takes half the width (split 3:1) and pk_fig takes the other half, which gives column ratios 3:1:4:

.layout(widths=, heights=) works differently: it sets the ratios of the final grid directly, with one value per grid column or row:

Figure-level settings

A composition is a figure, so the figure methods chain on it:

  • .layout(sharex=, sharey=, hspace=, wspace=) for axis sharing and spacing
  • .size(w, h) and .paper("PRD-2col") for the size
  • .style(...) for a style that applies to every panel (Formatting and Styles)

On matplotlib, figures use the constrained layout engine by default, so axis labels, titles and colourbars don't overlap. Setting hspace or wspace asks for exact spacing and turns the engine off; .layout(engine="tight") or .layout(engine=None) choose another engine or none at all.

A model and its residuals, with a shared x-axis and a tall top panel:

Per-plot formatting survives composition, so each panel keeps its own limits, legend and style:

Mixing with Figure

Operators also accept a Figure, which keeps its own grid, ratios and style inside the larger layout:

The same layout, three ways

These three expressions build the same figure: two panels on top and one wide panel below.

(p1 | p2) / pk_fig                                   # operators

cplots.Figure.grid([[p1, p2], [pk_fig]])             # grid of rows

(cplots.Figure(nrows=2, ncols=2)                     # explicit cells
    .add(p1, row=0, col=0)
    .add(p2, row=0, col=1)
    .add(pk_fig, row=1, col=0, colspan=2))

Operators are usually the shortest. Figure.add gives exact control over each cell, for layouts that don't break down into rows and columns.

Simple grids

Figure.grid() is the fastest way to build a uniform layout:

Rules:

  • Each inner list is a row.
  • Rows with fewer plots than the maximum width are auto-spanned evenly.
  • The number of plots per row must evenly divide the maximum row length.

Explicit spanning

Use Figure(...).add(...) for irregular layouts:

add() returns self, so calls can be chained.

Shared axes

Sizing and styles

The figure's style applies to every panel. A panel's own style (p1.style(...)) is layered on top of it.

Colourbars

colorbar={...} sets colourbar fields (loc, label_loc, pad, scale, ...) on every colour-mapped panel (e.g. XYZColored); panels without a colour scale are left alone:

cplots.Figure.grid([[xyz_panel, plain_panel]], colorbar={"loc": "bottom"})

For one colourbar shared by several colour-coded panels, use XYZColoredGrid(..., shared_colorbar=True).

Backend support

matplotlib and plotly support every layout feature. Altair sizes panels by colspan and width/height ratios. It cannot share axes, and it raises NotImplementedError for rowspan > 1. Anything a backend ignores triggers one cplots.UnsupportedFeatureWarning.

Not a panel

A TrianglePlot cannot be a Figure panel: its engine draws a figure of its own. 3D PhasePortraits cannot be panels either. Both raise NotImplementedError; render them on their own.