Publication-style figures for papers, from single plots to panel-first Figma assembly.
pubfig is a Matplotlib-native plotting library for researchers who want figures that already look close to the final paper figure. It combines common scientific plot families, journal-aware export, and a panel-first Figma workflow for assembling large multi-panel figures without rebuilding artwork by hand.
Project links: PyPI · GitHub · Examples
- Paper-Ready Defaults, Not a Blank Canvas — Titles, legends, fonts, line widths, and spacing start from a more publication-like baseline.
- Common Scientific Plot Families in One API — Statistical plots, distributions, trends, dimensionality reduction, evaluation curves, heatmaps, and flow plots live in one consistent surface.
- Journal-Aware Export Without Boilerplate —
save_figure(...)andbatch_export(...)handle explicit output suffixes, column widths, DPI, trimming, and export-time relayout directly. - Panel-First Workflow for Composite Figures — Export clean subplot assets, then assemble and refresh full figures in Figma instead of re-drawing panels manually.
- Matplotlib-Native and Script-Friendly — Plot functions return standard Matplotlib
Figureobjects, so they drop into existing analysis code easily.
- 2026-04-10:
pubfig 0.2.3makesbatch_export(...)follow the same publication-size resize / relayout path assave_figure(...), so multi-format export now keeps export-time layout consistent. - 2026-03-31:
pubfig 0.2.2adds ECDF, QQ, Bland–Altman, Calibration, and UpSet, and brings them into the homepage showcase and full gallery. - 2026-03-30:
pubfig 0.2.1refreshed the PyPI release with stronger polar/composition coverage and fuller gallery examples. - 2026-03-29:
pubfig 0.2.0landed on PyPI withpip install pubfig, suffix-based export, and the documented panel-first Figma workflow.
View older changelog
- 2026-03-25: Panel-first Figma loop polish — panel export now defaults to title-free assets for cleaner Figma assembly,
pubfig-syncnow keeps shared title / legend placeholders off by default, and bridge/watch flows now surface bundle provenance plus the exact manual-fallback bundle path. - 2026-03-20: Local bridge automation for Figma sync — added a bridge-backed
pubfig figma bridge|sync|watchworkflow, upgradedpubfig-syncwith bridge connection mode, and enabled CLI-triggered vector import/refresh after one-time plugin connection. - 2026-03-20: Figma plugin v2 workflow polish — added
auto/hero_toprelayout presets, upgraded shared title / legend placeholders, and improved refresh behavior so manual Figma positioning is preserved more reliably unless relayout is requested. - 2026-03-20: CLI + Figma plugin workflow — added
pubfig figma package|validate|inspect, introduced a single-file Figma bundle JSON format for exported panels, and scaffolded thefigma-plugin/pubfig-syncplugin for node-level import and refresh. - 2026-03-20: Figma-first panel export workflow — added
export_panel(...)andexport_panels(...)for stable subplot asset export, introduced a minimalpanel-index.jsonsync index, and documented the Codex + Figma MCP refinement path for multi-panel figures. - 2026-03-20: README alignment with pubtab style and homepage refresh — reorganized the README into a pubtab-style homepage with centered badges, language switch, highlights, dated recent news, showcase examples, and an embedded gallery hero.
- 2026-03-20: Default full install and metadata simplification — changed
pip install pubfigto install the full plotting stack by default, removed user-facing extras from the main install path, and aligned package metadata, GitHub About, and README wording. - 2026-03-19: Raincloud plot support and gallery refresh — added
raincloud(...), tuned its default styling, integrated it into the gallery, and regenerated the exported figure set. - 2026-03-19: PCA biplot and radar default updates — expanded
pca_biplot(...)with loading panel modes and group ellipses, refreshed radar defaults, unified font handling, and re-exported the gallery.
Full Gallery
The full gallery below also includes the newest diagnostics, evaluation, and set/composition families such as ECDF, QQ, Bland–Altman, Calibration, and UpSet.
pip install pubfigStart with the fewest possible parameters first:
import numpy as np
import pubfig as pf
rng = np.random.default_rng(0)
means = np.array([
[0.78, 0.96],
[0.88, 1.08],
[0.84, 1.00],
], dtype=float)
data = rng.normal(loc=means[..., None], scale=0.08, size=(3, 2, 18))
data = np.clip(data, 0.0, None)
fig = pf.bar_scatter(data)
pf.save_figure(fig, "figure1.pdf")This is enough to get your first figure out. You do not need to understand layout, export, or publication-specific parameters before the first run.
Once the minimal example works, these are usually the first parameters worth adding:
fig = pf.bar_scatter(
data,
category_names=["Condition A", "Condition B", "Condition C"],
series_names=["Ctrl", "Treatment"],
title="Bar + Scatter",
)
pf.save_figure(fig, "figure1.pdf", spec="nature", width="single")category_names: names on the x-axisseries_names: names in the legendtitle: figure titlespec/width: journal-style export presets
Only add parameters like aspect_ratio or trim when you already know why you
need them.
If you want to understand a specific plot in more detail, start here:
help(pf.bar_scatter)
help(pf.line)
help(pf.heatmap)You can also inspect runnable examples under examples/.
save_figure(...) now expects an explicit filename suffix:
pf.save_figure(fig, "figure1.pdf")→ write PDFpf.save_figure(fig, "figure1.svg")→ write SVGpf.save_figure(fig, "figure1.png")→ write PNGpf.save_figure(fig, "figure1.jpg")→ write JPG
If you want multiple outputs, use batch_export(...) instead:
pf.batch_export(fig, "figure1", formats=("pdf", "svg", "png", "jpg"))batch_export(...) now follows the same publication-size export path as
save_figure(...): it resizes to the requested output size first, reruns
layout/post-layout hooks, and then writes each format.
These rows are the shortest useful plotting calls. When you want to export one,
reuse pf.save_figure(fig, "name.pdf") from Quick Start.
Each row assumes:
import numpy as np
import pubfig as pfThe polar defaults for circular_stacked_bar(...) and circular_grouped_bar(...) now follow the denser publication-style settings used in the gallery: tighter group/item spacing, shorter single-line inner labels, and built-in warm/cool palettes. circular_grouped_bar(...) now renders true grouped polar bars with multiple side-by-side series inside each item slot.
For bar_scatter(...), significance spacing parameters now follow explicit orientation-based names:
fig = pf.bar_scatter(
data,
show_statistics=True,
significance_ns_label_offset_ratio_vertical=0.08,
significance_stars_label_offset_ratio_vertical=-0.12,
significance_label_offset_ratio_vertical=0.07,
)pubfig exports clean panel artwork, and Figma stays the place where you
assemble and finish the whole publication figure.
For day-to-day use, the main command is pubfig figma push.
- Install
pubfig-syncin Figma Desktop the first time: go to Plugins → Development → Import plugin from manifest..., then selectfigma-plugin/pubfig-sync/manifest.jsonfrom this repo. After that, reopen it from Plugins → Development → pubfig-sync. - Click Connect Bridge once in the plugin.
- Export your panels from Python.
- Run
pubfig figma push <panel_dir> --figure-id <id>from the terminal. - If the bridge path fails, load the written bundle in the plugin and use the manual buttons.
pubfig figma push panels --figure-id figure-01Panel export now defaults to clean, title-free art so subplot titles can be
handled at the Figma assembly layer. If you explicitly want embedded panel
headers, pass include_title=True.
import numpy as np
import pubfig as pf
rng = np.random.default_rng(0)
panels = {
"a": pf.bar(rng.uniform(0.4, 0.9, size=3), category_names=["A", "B", "C"]),
"b": pf.scatter(rng.normal(size=40), rng.normal(size=40)),
}
pf.export_panels(panels, "panels", overwrite=True) # title-free art by defaultpubfig figma push panels --figure-id figure-01This writes panel assets such as a.svg, b.svg, and panel-index.json, then
uses push as the primary panel-first handoff into Figma.
- Keep the same
figure_idto refresh the existing figure in place. - Use a new
figure_idto import a separate figure.
What does Connect Bridge do?
It links the open Figma plugin to your local terminal workflow so later push
commands know which live session to refresh.
What does pubfig figma push do automatically?
It is the primary agent-first command. It ensures the local bridge is available,
selects the latest connected session, writes the bundle, and then syncs or
refreshes the figure.
What is the .pubfig-figma.json file?
It is the exact Figma handoff bundle for one figure. Keep it around for manual
import, refresh, debugging, or recovery.
How do I do manual fallback?
If bridge refresh stalls, load the latest written .pubfig-figma.json bundle in
pubfig-sync, then use Import as New, Manual Refresh, or Refresh + Relayout.
When should I use pubfig figma package?
Use it as the secondary path when you only want to write a standalone
bundle without pushing immediately.
pubfig figma package panels --figure-id figure-01Where are the advanced commands?
Use these only for finer control or debugging after the normal push path:
pubfig figma sync figure-01.pubfig-figma.json --session latest
pubfig figma watch figure-01.pubfig-figma.json --session latest
pubfig figma bridge statusIf you use Codex locally, the companion skill pubfig-figma-workflow can still
orchestrate the panel export → Figma import → MCP review loop.
| Function | Description | Recipe |
|---|---|---|
bar |
Simple bar chart and grouped bar chart | recipe |
bar_scatter |
Grouped bar chart with raw points and significance annotations | recipe |
stacked_bar |
Horizontal stacked bar chart | recipe |
paired |
Paired dot plot | recipe |
dumbbell |
Connected paired comparison plot | recipe |
forest_plot |
Effect-size plot with confidence intervals | recipe |
| Function | Description | Recipe |
|---|---|---|
grouped_scatter |
Dense grouped scatter / strip benchmark panel | recipe |
donut |
Publication-style donut chart | recipe |
stacked_ratio_barh |
100 percent horizontal ratio bar chart | recipe |
radial_hierarchy |
Two-level radial hierarchy / sunburst-style chart | recipe |
circular_stacked_bar |
Dense circular stacked bar chart with inner group ring | recipe |
circular_grouped_bar |
Dense circular grouped bar chart with inner group ring | recipe |
| Function | Description | Recipe |
|---|---|---|
box |
Box plot | recipe |
violin |
Violin plot | recipe |
strip |
Strip plot | recipe |
raincloud |
Half-violin + box + raw-point raincloud plot | recipe |
density |
Density plot with KDE | recipe |
histogram |
Histogram with optional KDE | recipe |
ridgeline |
Ridgeline plot | recipe |
| Function | Description | Recipe |
|---|---|---|
line |
Line chart with optional CI | recipe |
area |
Stacked area chart | recipe |
scatter |
Scatter plot with optional grouped workflow | recipe |
bubble |
Bubble chart | recipe |
contour2d |
2D contour plot with marginals | recipe |
hexbin |
Dense-scatter hexbin plot | recipe |
radar |
Radar chart | recipe |
| Function | Description | Recipe |
|---|---|---|
heatmap |
Heatmap | recipe |
corr_matrix |
Correlation heatmap | recipe |
clustermap |
Clustered heatmap | recipe |
dimreduce |
Dimensionality-reduction scatter plot | recipe |
pca_biplot |
PCA biplot with optional loadings and group ellipses | recipe |
parallel_coordinates |
Parallel coordinates plot | recipe |
| Function | Description | Recipe |
|---|---|---|
roc |
ROC curve with AUC | recipe |
pr_curve |
Precision-Recall curve with AP | recipe |
volcano |
Volcano plot for effect size vs significance | recipe |
sankey |
Sankey diagram | recipe |
pubfig currently ships with these themes:
defaultnaturesciencecell
pf.set_default_theme("science")For export, save_figure(...) uses named figure specs:
naturesciencecell
Width can be specified as:
"single""double"- numeric millimeters such as
120 - string millimeters such as
"120mm"
Built-in palettes include:
DEFAULTNATURESCIENCELANCETJAMA
from pubfig import NATURE, show_palette
show_palette(NATURE).show()You can also fetch palettes by name:
palette = pf.get_palette("science")
palette = pf.get_palette("carto_blugrn")These journal-style palettes are inspired palettes, not official journal standards. In pubfig, the NATURE, SCIENCE, LANCET, and JAMA cards are derived from widely used ggsci-derived community palettes rather than publisher-mandated color specifications.
Source note: ggsci documents these palettes as inspired by NPG / Nature Publishing Group, AAAS / Science, Lancet journals, and JAMA figures. See pal_npg, pal_aaas, pal_lancet, and pal_jama.
For a visual preview of all currently available palettes, see docs/palette-gallery.md.
Most files under examples/ are either:
- runnable example scripts, or
- rendered assets used by this README and the palette docs.
If you only want the main entry points, start here:
examples/gallery.py— quick visual walkthrough of supported plotsexamples/export_gallery.py— exports the gallery tooutput_figures/examples/export_composite_showcases_panels.py— exports the panel-first composite showcases that are assembled in Figmaexamples/figma_workflow_demo.md— panel-first pubfig → Figma workflow guideexamples/generate_palette_gallery.py— regenerates the palette preview sheets and gallery docsexamples/README.md— keep/remove inventory for this folderhelp(pubfig.circular_stacked_bar)/help(pubfig.circular_grouped_bar)— inspect the fixed dense polar defaults directly from Python
Advanced / secondary:
examples/export_gallery_mpl.py— focused Matplotlib export examplesfigma-plugin/pubfig-sync/— Figma plugin scaffold for panel import and refreshdocs/palette-gallery.md— visual palette gallery for built-in and Plotly-derived palettes
pip install -e .[dev]pytestruff check src tests examplespython examples/export_gallery.pyMIT
























