agentsclimarketplace

Multipanel figures

Skill BioTender-max/awesome-bio-agent-skills/skills/bioskills/multipanel-figures

A curated collection of AI agent skills for biomedical research, covering genomics, proteomics, single-cell analysis, clinical AI, and protein design.

Install
npx -y skills add BioTender-max/awesome-bio-agent-skills --skill multipanel-figures

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.

What its author says it does

Copied from the file, not written here

Compose multi-panel publication figures with patchwork, cowplot, gridExtra (R), or matplotlib GridSpec/subfigures (Python) including shared axes/legends/guides collection, panel labels in Nature/Cell convention, and journal-spec sizing. Covers patchwork ≥1.2.0 axes='collect' feature, Type-42 font embedding, and the cairo_pdf save path. Use when composing 2+ subpanels into a single figure for journal submission.

SKILL.md

11.9 KB, as published. Nobody here has run it

Version Compatibility

Reference examples tested with: patchwork 1.2+ (axes='collect' requires this version, released 2024-01-05), cowplot 1.1+, ggplot2 3.5+, matplotlib 3.8+ (subfigures stable since 3.4).

Before using code patterns, verify installed versions match. If versions differ:

  • R: packageVersion('<pkg>') then ?function_name
  • Python: pip show <package> then help(module.function)

If code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example to match the actual API rather than retrying.

Multi-Panel Figures

"Combine plots into a multi-panel figure" -> Arrange individual plots into a single composed figure with consistent sizing, shared legends/axes, and panel labels (a, b, c) in the Nature/Cell convention. The decision space: which composition library (patchwork most modern in R; matplotlib subfigures in Python), how to share legends and axes, and how to size at journal specifications.

  • R: patchwork (modern; supports axes/guides collection since 1.2), cowplot (older; align_plots), gridExtra (basic grid arrange)
  • Python: matplotlib.gridspec.GridSpec, fig.subfigures() (matplotlib 3.4+)

The Single Most Important Modern Insight -- Axes Collection Requires patchwork ≥ 1.2.0

patchwork 1.2.0 (released 2024-01-05) added axes = 'collect' and axis_titles = 'collect' to plot_layout(). These collect repeated axes / titles across subplots into a single shared axis label — the same way guides = 'collect' (available since patchwork 1.0) collects legends.

Without this, multi-panel figures with shared axes show redundant labels on every subplot (visually cluttered AND non-Nature compliant). Verify patchwork version is ≥ 1.2.0; older versions silently ignore the axes argument.

patchwork -- Modern R Composition

Goal: Compose 4 ggplot objects into a 2×2 panel figure with shared legend, collected axes, and bold panel labels (a, b, c, d) in upper-left of each subplot.

Approach: Combine plots with +, /, | operators; apply plot_layout(guides='collect', axes='collect') for shared elements; add plot_annotation(tag_levels='a') for Nature-style panel labels.

library(patchwork)
library(ggplot2)

p1 <- ggplot(df, aes(x, y)) + geom_point() + theme_classic()
p2 <- ggplot(df, aes(group, value)) + geom_boxplot() + theme_classic()
p3 <- ggplot(df, aes(x)) + geom_histogram() + theme_classic()
p4 <- ggplot(df, aes(x, y, color = group)) + geom_point() + theme_classic()

# 2x2 grid
fig <- (p1 + p2) / (p3 + p4) +
    plot_annotation(tag_levels = 'a',
                    theme = theme(plot.tag = element_text(face = 'bold', size = 10))) +
    plot_layout(guides = 'collect',         # share legends
                axes = 'collect',           # share axes (patchwork >= 1.2.0)
                axis_titles = 'collect')

ggsave('figure1.pdf', fig, width = 180, height = 140, units = 'mm', device = cairo_pdf)

patchwork Operators

p1 + p2                                     # side-by-side
p1 / p2                                     # vertical stack
(p1 | p2) / p3                              # mixed: top row two, bottom one
p1 + p2 + p3 + plot_layout(ncol = 3)
p1 + p2 + plot_layout(widths = c(2, 1))     # 2:1 width ratio

# Complex grid via design string
design <- "
AAB
AAB
CCC
"
p1 + p2 + p3 + plot_layout(design = design)

# Inset
p1 + inset_element(p2, left = 0.6, bottom = 0.6, right = 1, top = 1)

cowplot -- Alternative with Alignment Focus

library(cowplot)

# plot_grid is the workhorse
combined <- plot_grid(p1, p2, p3, p4,
                       ncol = 2, labels = 'AUTO',         # 'AUTO' = A, B, C, D
                       label_size = 12, label_fontface = 'bold',
                       align = 'hv',                       # align horizontally + vertically
                       rel_widths = c(1, 1), rel_heights = c(1, 1))

# Nested grids
top_row <- plot_grid(p1, p2, ncol = 2, labels = c('A', 'B'))
bottom <- plot_grid(p3, p4, ncol = 2, labels = c('C', 'D'))
combined <- plot_grid(top_row, bottom, nrow = 2, rel_heights = c(1, 1.2))

ggsave('figure.pdf', combined, width = 180, height = 140, units = 'mm', device = cairo_pdf)

cowplot is older but its alignment behavior is sometimes more reliable than patchwork on edge cases (axes-with-titles of different lengths).

matplotlib GridSpec (Python)

import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec

fig = plt.figure(figsize=(180/25.4, 120/25.4), constrained_layout=True)
gs = GridSpec(2, 3, figure=fig)

ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1:])         # top right, spans columns 1-2
ax3 = fig.add_subplot(gs[1, :])           # bottom row, spans all columns

ax1.scatter(x, y, s=4, rasterized=True)
ax2.plot(x, y)
ax3.bar(cats, vals)

# Panel labels at (-0.15, 1.05) of each axes
for ax, lbl in zip([ax1, ax2, ax3], 'abc'):
    ax.text(-0.15, 1.05, lbl, transform=ax.transAxes,
            fontsize=10, fontweight='bold', va='top')

fig.savefig('figure.pdf', dpi=300, bbox_inches='tight')

matplotlib Subfigures

fig = plt.figure(figsize=(180/25.4, 120/25.4), constrained_layout=True)
subfigs = fig.subfigures(1, 2, width_ratios=[2, 1])

# Left subfigure has 2 stacked panels
axs_left = subfigs[0].subplots(2, 1)
axs_left[0].plot(x, y)
axs_left[1].scatter(x, y, rasterized=True)

# Right subfigure has one panel
ax_right = subfigs[1].subplots(1, 1)
ax_right.imshow(matrix)
subfigs[1].colorbar(ax_right.images[0], ax=ax_right, shrink=0.5)

Subfigures are stronger than GridSpec for complex compositions because each subfigure has its own constrained_layout.

Journal Sizing

JournalSingle colDouble colMax height
Nature89 mm183 mm247 mm
Cell85 mm174 mm235 mm
Science55 mm120 mm220 mm
PNAS87 mm178 mm225 mm
eLife86 mm175 mm~240 mm

Always set explicit units in mm; default inches is the most common source of "figure too large" errors.

Panel Labels — Nature/Cell Convention

  • Nature: lowercase bold serif (a, b, c) in upper-left corner of each panel; 8 pt
  • Cell: uppercase bold sans-serif (A, B, C); placed flush left at panel top
  • Science: capital bold (A, B, C)
# patchwork tag_levels for lowercase (Nature)
plot_annotation(tag_levels = 'a',
                theme = theme(plot.tag = element_text(face = 'bold', size = 9)))
# 'A' for uppercase (Cell)
plot_annotation(tag_levels = 'A')
# 'i' for roman numerals (sometimes for sub-panels)
# cowplot
plot_grid(..., labels = 'AUTO')   # auto uppercase A, B, C
plot_grid(..., labels = 'auto')   # auto lowercase a, b, c

Per-Method Failure Modes

patchwork axes='collect' silently ignored

Trigger: Using plot_layout(axes='collect') with patchwork < 1.2.0.

Mechanism: Older versions silently accept the argument but don't act on it.

Symptom: Redundant axes on each subplot; no warning or error.

Fix: packageVersion('patchwork') must be ≥ 1.2.0. Update with install.packages('patchwork').

Default ggsave produces non-portable PDF

Trigger: ggsave('out.pdf', fig) without device = cairo_pdf.

Mechanism: Default pdf() device produces fonts that journals reject on some systems.

Symptom: Submission rejected at automated check; "non-embedded fonts."

Fix: Always device = cairo_pdf.

Figure dimensions in inches when mm intended

Trigger: ggsave('out.pdf', fig, width = 180, height = 140).

Mechanism: Default units = 'in'.

Symptom: Figure file rejected for being 180 × 140 inches.

Fix: Explicit units = 'mm'.

Panel labels not aligned to panel content

Trigger: patchwork plot_annotation(tag_levels) with subplots of different y-axis label widths.

Mechanism: Tag is positioned relative to the plot canvas, including the y-axis label area.

Symptom: Labels are at different horizontal positions in each panel.

Fix: Either standardize y-label widths (pad with whitespace) OR move tags inside the plotting area: theme(plot.tag.position = c(0.02, 0.98)).

cowplot align='v' fails on plots of different widths

Trigger: plot_grid(p_wide, p_narrow, align = 'v').

Mechanism: Vertical alignment requires same x-axis widths.

Symptom: Plots align at the y-axis but x-axis labels are offset.

Fix: Use align = 'hv' if both alignments needed; otherwise patchwork's axes='collect' handles this more gracefully.

Shared legend lost in patchwork

Trigger: (p1 + p2) + plot_layout(guides = 'collect') but p1 and p2 use different scales.

Mechanism: guides='collect' merges identical guides; different scales produce duplicate (not merged) legends.

Symptom: Two legends still appear.

Fix: Standardize the scales across subplots (same scale_color_manual(values=...)); OR drop one legend via & theme(legend.position = 'none') on the redundant plot.

matplotlib GridSpec with constrained_layout=False

Trigger: Older code with plt.subplots no constrained_layout; tight_layout fails on colorbars.

Mechanism: tight_layout doesn't know about post-hoc colorbars.

Symptom: Colorbar overlaps adjacent subplot.

Fix: plt.figure(constrained_layout=True) and use fig.add_subplot(gs[...]). constrained_layout is the default-on choice in matplotlib 3.6+.

Reconciliation

PatternCauseAction
patchwork and cowplot align differentlyDifferent alignment algorithmsTry both; cowplot's align='hv' and patchwork's axes='collect' rarely produce identical results
Panel labels position differs between sessionsDifferent y-axis label widthsStandardize across panels
Shared legend duplicatedScales differ across subplotsUse identical scales OR drop legend from N-1 panels

Quantitative Thresholds

ThresholdValueSource
Nature single column89 mmNature figure guidelines
Nature double column183 mmNature figure guidelines
Body text size5-7 ptNature rejects outside range
Panel label size8 pt boldNature convention
patchwork axes='collect' minimum version1.2.0 (2024-01-05)patchwork release notes

Common Errors

Error / symptomCauseSolution
Redundant axis labels per panelpatchwork < 1.2.0 OR axes='collect' not setUpdate + add to plot_layout
Non-embedded font rejectionDefault ggsave devicedevice = cairo_pdf
Figure 180 in × 140 inDefault units = 'in'units = 'mm'
Panel tags misalignedDifferent y-label widthsStandardize or move tag inside
Cowplot vertical alignment failsDifferent x-axis widthsUse 'hv' OR switch to patchwork
Two legends instead of sharedScales differ across subplotsUnify scales
matplotlib colorbar overlaps subplotNo constrained_layoutconstrained_layout=True

References

  • Pedersen TL. 2024. patchwork: the composer of plots. CRAN package (v1.2.0 release notes).
  • Wilke CO. 2017. cowplot: streamlined plot theme and plot annotations for ggplot2. CRAN package.
  • Hunter JD. 2007. Matplotlib: A 2D graphics environment. Comput Sci Eng 9(3):90-95.

Related Skills

  • data-visualization/ggplot2-fundamentals - Individual ggplot objects
  • data-visualization/matplotlib-fundamentals - Python equivalent
  • reporting/figure-export - DPI / format / journal-spec compliance
  • data-visualization/color-palettes - Consistent palette across subpanels

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.