publiplots.subplots

publiplots.subplots(nrows=1, ncols=1, *, axes_size=None, width_ratios=None, height_ratios=None, sharex=False, sharey=False, title_space=None, xlabel_space=None, ylabel_space=None, right=None, hspace=None, wspace=None, outer_pad=None, label_outer=True, **fig_kw)[source]

Create a figure and a grid of axes with deterministic axes dimensions.

publiplots’ flagship 0.10 layout API. Every axes in the grid has exactly axes_size millimeters as its spine bounding box — this is the inviolate quantity. The figure canvas is sized to fit the grid plus auto-measured decorations (titles, axis labels, tick labels), which are remeasured on first draw. Any per-side reservation passed explicitly is locked and never remeasured.

This is the opposite mental model from matplotlib.pyplot.subplots(), which fixes the figure and lets axes shrink. When comparing to matplotlib/seaborn code, note that axes_size is in mm (not inches) and figsize= is rejected — publiplots owns the figure geometry. See reject_figsize() for the rationale.

Parameters:
  • nrows (int, default 1) – Grid shape (must be >= 1).

  • ncols (int, default 1) – Grid shape (must be >= 1).

  • axes_size ((float, float) or float, in mm, optional) – Declared per-cell axes bounding box, in millimeters. A scalar is coerced to (s, s). If None, falls back to pp.rcParams["subplots.axes_size"] — the baseline publication default is (70, 50) mm. This is the per-cell budget; total grid-width budget is axes_size[0] * ncols and total grid- height budget is axes_size[1] * nrows. Asymmetric grids (see width_ratios / height_ratios) renormalize that budget across columns/rows without changing the total.

  • width_ratios (sequence of float, optional) – Per-column width weights, matching matplotlib.pyplot.subplots() in spirit. Length must equal ncols. Derived mm widths are ratios[c] / sum(ratios) * (axes_size[0] * ncols) — so the total grid-width budget is preserved and each cell’s width is its ratio-share of that budget. Equal ratios recover the uniform-grid behavior. Use for JointGrid-style layouts (e.g., width_ratios=[7, 2] for a big main panel + thin right marginal).

  • height_ratios (sequence of float, optional) – Per-row height weights. Same semantics as width_ratios but along the row axis.

  • sharex (bool or {"all", "row", "col", "none"}, default False) – Axis-sharing semantics, matching matplotlib.pyplot.subplots().

  • sharey (bool or {"all", "row", "col", "none"}, default False) – Axis-sharing semantics, matching matplotlib.pyplot.subplots().

  • title_space (float | sequence, optional) –

    Per-cell reservations, in millimeters. Accepts:

    • None (default) — use pp.rcParams["subplots.<name>"] as the initial value and auto-measure every position on first draw.

    • A float — broadcast across the whole side AND lock it; no position is auto-measured.

    • A sequence of length nrows (for title_space / xlabel_space) or ncols (for ylabel_space / right). Each element is either a float (lock that position) or None (leave it auto-measured, initial value from rcParams). Mixed sequences such as (0.0, None) let you pin one row/column while the other auto-grows — this is how pp.JointGrid clamps the inside-facing joint↔marginal edges to 0 mm without disabling label auto-measurement on the outer edges.

  • xlabel_space (float | sequence, optional) –

    Per-cell reservations, in millimeters. Accepts:

    • None (default) — use pp.rcParams["subplots.<name>"] as the initial value and auto-measure every position on first draw.

    • A float — broadcast across the whole side AND lock it; no position is auto-measured.

    • A sequence of length nrows (for title_space / xlabel_space) or ncols (for ylabel_space / right). Each element is either a float (lock that position) or None (leave it auto-measured, initial value from rcParams). Mixed sequences such as (0.0, None) let you pin one row/column while the other auto-grows — this is how pp.JointGrid clamps the inside-facing joint↔marginal edges to 0 mm without disabling label auto-measurement on the outer edges.

  • ylabel_space (float | sequence, optional) –

    Per-cell reservations, in millimeters. Accepts:

    • None (default) — use pp.rcParams["subplots.<name>"] as the initial value and auto-measure every position on first draw.

    • A float — broadcast across the whole side AND lock it; no position is auto-measured.

    • A sequence of length nrows (for title_space / xlabel_space) or ncols (for ylabel_space / right). Each element is either a float (lock that position) or None (leave it auto-measured, initial value from rcParams). Mixed sequences such as (0.0, None) let you pin one row/column while the other auto-grows — this is how pp.JointGrid clamps the inside-facing joint↔marginal edges to 0 mm without disabling label auto-measurement on the outer edges.

  • right (float | sequence, optional) –

    Per-cell reservations, in millimeters. Accepts:

    • None (default) — use pp.rcParams["subplots.<name>"] as the initial value and auto-measure every position on first draw.

    • A float — broadcast across the whole side AND lock it; no position is auto-measured.

    • A sequence of length nrows (for title_space / xlabel_space) or ncols (for ylabel_space / right). Each element is either a float (lock that position) or None (leave it auto-measured, initial value from rcParams). Mixed sequences such as (0.0, None) let you pin one row/column while the other auto-grows — this is how pp.JointGrid clamps the inside-facing joint↔marginal edges to 0 mm without disabling label auto-measurement on the outer edges.

  • hspace (float, optional) – Gaps between rows/cols and outer margin, in millimeters. None falls back to pp.rcParams["subplots.<name>"]. These are never auto-measured.

  • wspace (float, optional) – Gaps between rows/cols and outer margin, in millimeters. None falls back to pp.rcParams["subplots.<name>"]. These are never auto-measured.

  • outer_pad (float, optional) – Gaps between rows/cols and outer margin, in millimeters. None falls back to pp.rcParams["subplots.<name>"]. These are never auto-measured.

  • **fig_kw – Forwarded to matplotlib.pyplot.figure(). figsize= is rejected (see reject_figsize()); matplotlib layout-engine kwargs (layout, constrained_layout, tight_layout) are ignored with a UserWarning.

  • label_outer (bool or {"all"}, default True) – When True and sharex / sharey is active, hide interior tick labels, offset text, and axis labels — leaving labels only on the bottom row (x) and left column (y), matching seaborn’s FacetGrid. False or "all" draws every label (the pre-0.x behavior). With no sharing, True is a no-op. See publiplots.label_outer() to re-apply this after late set_xlabel / set_ylabel calls.

Returns:

  • fig (matplotlib.figure.Figure) – The created figure.

  • axes (matplotlib.axes.Axes or numpy.ndarray of Axes) – Shape matches matplotlib.pyplot.subplots() with squeeze=True.

Notes

Auto-growing canvas. The figure starts at the size computed from axes_size plus initial rcParams reservations. On first draw, decorations (titles, axis labels, tick labels) are measured and the reservations for unlocked sides are expanded to fit. The axes bbox itself never changes — only the canvas around it grows.

Legend overhangs. If you attach a publiplots.legend() band with side="right"/"bottom"/"left"/"top", the extra space for the legend is auto-computed on first draw from the rendered group’s width/height. There is no legend_column kwarg to set by hand.

Examples

Single axes (defaults):

>>> import publiplots as pp
>>> fig, ax = pp.subplots()
>>> pp.barplot(data=df, x='category', y='value', ax=ax)

Explicit mm axes size:

>>> fig, ax = pp.subplots(axes_size=(80, 50))  # 80 mm x 50 mm

Grid of axes with row-shared y-axis:

>>> fig, axes = pp.subplots(2, 3, axes_size=(40, 30), sharey='row')

Locked title reservation (no auto-measurement for this side):

>>> fig, ax = pp.subplots(axes_size=(60, 40), title_space=8)

Asymmetric 2×2 grid — a JointGrid-shaped layout with a large main panel and thin marginal strips. axes_size sets the per-cell budget that width_ratios / height_ratios renormalize:

>>> fig, axes = pp.subplots(2, 2, axes_size=(45, 35),
...                         width_ratios=[7, 2], height_ratios=[2, 5])

With axes_size=(45, 35) and ncols=2 the total grid-width budget is 90 mm; the ratio [7, 2] splits it into 70 and 20 mm. Same for rows: total 70 mm → 20 and 50 mm.

See also

publiplots.label_outer

Hide interior labels on an existing shared grid.

reject_figsize

Guard used by plot functions to reject figsize=.

publiplots.rcParams

Where the subplots.* defaults live.