500 panels, 250 groups, each with its own colorbar

The flagship case for everything the grouping gallery covers at once: 25 columns of 10 stacked row-pairs (500 pcolormesh panels total), each pair its own group, each panel its own colorbar, and each group’s box cycling through four accent colors so a reader can tell neighboring groups apart at a glance without reading every title.

Built with GroupLayout: a 10x25 outer arrangement of groups, each one a plain 2x1 (two rows, one column) inner shape – the row-pair itself. That’s the whole layout description; nothing here computes which of the 500 flat-grid cells a given pair actually occupies the way indexing into one big axes[r][col] array used to.

This is also what motivated group()’s max()-not-+= outer-margin fix: 25 of these 250 groups (one per column) share the grid’s own top edge, all title-facing. They still need only one correctly-sized top margin between them – not one that grows with how many columns happen to reach that edge.

Every panel keeps its own tick numbers rather than hiding the inner ones the way a sharex/sharey grid would – a figure-level supxlabel()/ supylabel() still names the shared axis once, but each panel’s own 0/0.5/1 stays readable at a glance rather than only appearing at the true grid edges. Building all 500 panels, their colorbars, and every group box still takes well under a second – see the scale gallery for what happens well past this size.

500 panels each carrying a title, tick numbers, and its own colorbar need real room per panel to stay legible, not just per-group separation – an SVG never loses resolution zooming in, but the figure should still read cleanly at a normal glance, not just under a magnifier. figsize scales generously per panel (not the tighter ratio a plainer grid could get away with) and group_spacing reserves visible, uncrowded gaps between groups rather than the bare minimum tight_layout() would allow.

Finally, get_group() finds one specific group by its title alone, and its own get_ax() reaches one particular panel inside it by inner position – not by remembering which of this figure’s 1000 total axes (500 panels + 500 colorbars) it is, or which (row, col) of the shared super-grid GroupLayout resolved that pair to. The magenta-bordered panel below was never touched during the build loop above; it is found and styled afterward, from nothing but the string "Group 137". See Finding a group or axes again: title, id, or position for this lookup API gathered in one dedicated example.

plot 13 full scale demo

This figure has 500 panels — too many to usefully embed at a fixed size.

Open the full interactive example in a new page to pick a tool, then zoom, pan, point-pick or annotate any panel.

View this figure’s Vega export ↗ — the raw JSON spec, rendered live by a real Vega engine.

View this figure’s Vega-Lite export ↗ — the raw JSON spec(s), rendered live by a real Vega-Lite engine.

Group 137's top panel is 'panel 14–13' -- confirming the magenta-bordered one above is the one actually found by title.

import numpy as np
import plotpress

GROUP_COLORS = ["#1f77b4", "#d62728", "#2ca02c", "#9467bd"]  # blue/red/green/purple

NROWS, NCOLS = 20, 25     # 500 panels, 250 top/bottom groups
N_PAIRS = NROWS // 2      # 10 row-pairs per column -> the outer grid's own rows
MESH_N = 20               # 20x20 mesh per panel

layout = plotpress.GroupLayout(N_PAIRS, NCOLS)
for col in range(NCOLS):
    for row_pair in range(N_PAIRS):
        group_idx = col * N_PAIRS + row_pair
        color = GROUP_COLORS[group_idx % len(GROUP_COLORS)]
        layout.add_group(row_pair, col, 2, 1, title=f"Group {group_idx}",
                        color=color, linewidth=1.0, fontsize=5)

# subplot_size= rather than a hand-multiplied figsize: at this size what is
# actually known is how big one panel has to be to stay readable, and
# figsize=(NCOLS * 1.6, NROWS * 1.6) does not deliver 1.6in panels -- the
# ticks, titles, 500 colorbars, 250 group boxes and the sup-labels all come
# out of that total. Stating the panel size gets the panels; the room for
# everything else is measured and added.
fig, axes = plotpress.subplots_from_groups(layout, subplot_size=(1.35, 1.35))

x_edges = np.linspace(0, 1, MESH_N + 1)
# A smooth Gaussian-plus-ripple field per panel (the same shape as the
# gridded_data gallery's own gouraud-shading example), each with a shifted
# center and frequency so every panel looks distinct without being pure
# noise -- real structure to read a colorbar value off of.
g = np.linspace(-3, 3, MESH_N)
Xc, Yc = np.meshgrid(g, g)

for col in range(NCOLS):
    for row_pair in range(N_PAIRS):
        top_row, bot_row = row_pair * 2, row_pair * 2 + 1
        for within_pair, r in enumerate((top_row, bot_row)):
            ax = axes[row_pair, col][within_pair]
            cx = 1.6 * np.sin(0.6 * col + 0.3 * r)
            cy = 1.6 * np.cos(0.5 * row_pair - 0.4 * col)
            freq = 1.3 + 0.15 * (r % 3)
            z = (np.exp(-((Xc - cx) ** 2 + (Yc - cy) ** 2) / 4)
                 + 0.3 * np.sin(freq * Xc) * np.cos(freq * Yc))
            mesh = ax.pcolormesh(x_edges, x_edges, z, cmap="viridis")
            ax.set_title(f"panel {r}{col}", fontsize=6)
            ax.tick_params(labelsize=5)
            fig.colorbar(mesh, ax=ax, fraction=0.08)

fig.group_spacing(wspace=60, hspace=44)
fig.suptitle("500 grouped pcolormesh panels")
fig.supxlabel("global x")
fig.supylabel("global y")
fig.tight_layout()

# Address one axes within a group by its group's title, not its global
# position: fig.get_group() finds any group by the title given when it
# was created, and its own get_ax(row=, col=) addresses one of its axes by
# *inner* position -- (0, 0) is simply "this pair's top panel," the same
# indexing already used to plot into it above, without knowing which of
# the figure's 1000 axes it actually is.
top_panel = fig.get_group(title="Group 137").get_ax(row=0, col=0)
for side in top_panel.spines:
    top_panel.spines[side].set_color("#e91e8c")
    top_panel.spines[side].set_linewidth(2.5)
print(f"Group 137's top panel is {top_panel.get_title()!r} -- confirming the "
     "magenta-bordered one above is the one actually found by title.")

Total running time of the script: (0 minutes 12.163 seconds)

Gallery generated by Sphinx-Gallery