Skip to main content

🧩 FigurePatch

Compose multi-panel Matplotlib figures with | and / operators.

PyPI Python 3.10+ Matplotlib License: MIT

FigurePatch brings intuitive layout operators to Matplotlib. Wrap your existing plotting functions into panels, then compose them effortlessly using | (side-by-side) and / (stacked).

Powered by Matplotlib's native constrained_layout engine — automatically prevents text overlap, clips nothing, and reserves proper margins for labels, titles, and colorbars.


The Mental Model in 5 Seconds

Operator Meaning Layout
`p1 p2` Side-by-side
p1 / p2 Stacked Vertical split
`(p1 p2) / p3` Nested spanning
`(p1 p2 p3) / (p4

Quick Start

pip install figurepatch
import matplotlib.pyplot as plt
import figurepatch as fp

@fp.panel
def panel_a(ax):
    ax.plot([1, 2, 3], [2, 4, 3])
    ax.set_xlabel("Time (s)")
    ax.set_ylabel("Signal")

@fp.panel
def panel_b(ax):
    ax.scatter([1, 2, 3], [3, 1, 2])
    ax.set_xlabel("Time (s)")
    ax.set_ylabel("Noise")

# Compose side-by-side with automatic A, B labels
fig = (panel_a | panel_b).render()
fig.savefig("figure.pdf")

1. Side-by-Side — panel_a | panel_b

Two panels arranged horizontally.

fig = (panel_a | panel_b).render(figsize=(7.2, 3.2))

Basic composition


2. Nested Spanning — (panel_a | panel_b) / panel_c

panel_c spans the entire bottom row automatically without manual colspan.

fig = ((panel_a | panel_b) / panel_c).render(figsize=(6.5, 5.0))

Nested layout


3. 2×3 Grid — (A | B | C) / (D | E | F)

Six panels with equal column widths across two rows.

fig = (panel_a | panel_b | panel_c) / (panel_d | panel_e | panel_f)
fig = fig.render(figsize=(8.0, 5.0))

2x3 grid


4. 3×3 Grid — 9 Panels

Complete multi-panel display with mixed plot types (lines, scatter, bars, boxplots, heatmaps, pie charts).

fig = (
    (panel_a | panel_b | panel_c)
    / (panel_d | panel_e | panel_f)
    / (panel_g | panel_h | panel_i)
).render(figsize=(8.5, 8.5))

3x3 grid


5. Single Row — A | B | C | D | E | F

Six panels chained in a single horizontal strip.

fig = (p1 | p2 | p3 | p4 | p5 | p6).render(figsize=(13.0, 2.5))

1x6 row


6. Irregular Rows — (A | B | C) / (D | E) / (F | G | H)

Row 1 has 3 panels, Row 2 has 2 wider panels (with colorbar), Row 3 has 3 panels.

fig = (
    (panel_a | panel_b | panel_c)
    / (panel_d | panel_e)
    / (panel_f | panel_g | panel_h)
).render(figsize=(8.5, 7.5))

Irregular 3+2+3


7. Compose Existing Figures — fp.compose(fig1, fig2, fig3)

Have existing Figure objects from disparate scripts or libraries? Compose them without rewriting your plotting code.

fig = fp.compose(fig1, fig2, fig3, direction="h", figsize=(9.0, 3.0))

Figure compose


Why FigurePatch?

Before: Matplotlib GridSpec Boilerplate

fig = plt.figure(figsize=(10, 8))
gs = fig.add_gridspec(2, 2)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1])
ax3 = fig.add_subplot(gs[1, :])  # manual spanning

ax1.plot(x, y1)
ax1.set_xlabel("Time")
ax2.scatter(x, y2)
ax2.set_xlabel("Feature")
ax3.bar(x, y3)
ax3.set_xlabel("Group")

fig.tight_layout()
fig.savefig("figure.pdf")

After: FigurePatch

fig = ((panel_a | panel_b) / panel_c).render()
fig.savefig("figure.pdf")

How It Works

FigurePatch compiles arbitrary composition trees into an exact 2D mosaic matrix, rendered through Matplotlib's native layout="constrained" engine:

  1. Automatic Area Alignment: Chained horizontal or vertical panels automatically find their common least multiple and expand cleanly into grid cells.
  2. Zero Text Collision: Dynamically measures bounding boxes for every tick mark, axis label, title, and colorbar to guarantee no overlapping text.
  3. No Edge Truncation: Automatically reserves perimeter margins so negative tick values (e.g. -1.00) and titles are never cropped off.
  4. Bold Panel Labels: Adds A, B, C... labels at the top-left of each axes that coexist harmoniously with centered plot titles.

API Reference

@fp.panel

Decorator converting a plotting function func(ax) into a composable Panel:

@fp.panel
def my_plot(ax):
    ax.plot(x, y)

Operators

  • panel_a | panel_b: Places panels side-by-side.
  • panel_a / panel_b: Stacks panels vertically.

.render(figsize=None, labels=True)

Renders the composition tree into a matplotlib.figure.Figure.

  • figsize: (width, height) in inches. Auto-estimated when omitted.
  • labels: True for bold A, B, C... labels; a string (e.g. "S") for prefixed labels (S1, S2...); or False to disable.

fp.compose(*items, direction="h", figsize=None, labels=True)

Post-hoc composition for existing Figure or Axes objects.


Compatibility

FigurePatch receives standard matplotlib.axes.Axes, making it 100% compatible with any library that plots on an existing axes:

Library Usage Status
Matplotlib Direct plotting on ax Supported
Seaborn Pass ax=ax (e.g., sns.lineplot(..., ax=ax)) Supported
pandas Pass ax=ax (e.g., df.plot(..., ax=ax)) Supported
Scanpy Pass ax=ax (e.g., sc.pl.umap(..., ax=ax)) Supported

Development & Examples

git clone https://github.com/zh3li/FigurePatch.git
cd FigurePatch
uv sync

# Run the test suite
uv run pytest

# Run any gallery example
uv run python examples/basic_compose.py
uv run python examples/complex_layout.py
uv run python examples/grid_2x3.py
uv run python examples/grid_3x3.py
uv run python examples/grid_1x6.py
uv run python examples/irregular_3_2_3.py
uv run python examples/figure_compose.py

License

FigurePatch is available under the MIT License.

Release files for figurepatch 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for figurepatch 0.1.0
File Size Uploaded
figurepatch-0.1.0.tar.gz 998.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for figurepatch 0.1.0
File Interpreter ABI Platform
figurepatch-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.0 MB

Release files / figurepatch-0.1.0.tar.gz

Download URL figurepatch-0.1.0.tar.gz
Size 998.7 kB
Tags Source
SHA-256 checksum
How to use checksums
e092c4101b70a3de9c2a7f0f580a8c48349446aa40623fb233bf07e1cd792ded
BLAKE2b-256 checksum
How to use checksums
6c886adacc9af92ae3c4c9089611e41651892ea6234db2c8f6ae9a35e65d9809
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / figurepatch-0.1.0-py3-none-any.whl

Download URL figurepatch-0.1.0-py3-none-any.whl
Size 12.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1f6f14495c29a5a6d50a2a3502e0693b3c1f75a3f277ad03756ecbee2a939a17
BLAKE2b-256 checksum
How to use checksums
13a76b32948c00fb575c9584ae013ca46119cc1663606ce2dbd9457c0c18bf76
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

0.2.0

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page