Skip to content

zlatko-minev/paperplot

Repository files navigation

paperplot

CI Docs Python License: MIT Open in Colab Binder

Publication-correct matplotlib figures, journal by journal.

Most matplotlib styling focuses on the look. paperplot does that too — but it also handles the parts that decide whether a journal accepts your figure:

  • sizes it to the journal's real column width (8.6 cm, 17.8 cm, …), not a generic default;
  • embeds fonts correctly (Type-42, no Type-3);
  • preflights it for the rules figures actually get rejected over — font type, line weight, lettering height, and grayscale legibility.

Ships Physical Review (APS) (incl. PRL/PRX/PRB), Nature, and IEEE, plus a presentation target for slides. matplotlib-only core; seaborn/IPython are optional extras.

Just want the look? pp.register_mplstyles() then plt.style.use("paperplot-aps") — no API to learn. You give up only the parts a style sheet can't do (true column sizing, font embedding, preflight); pp.figure()/pp.save() add those back. See Drop-in style sheets.

Install

pip install paperplot-quantum                 # core (matplotlib + numpy)
pip install "paperplot-quantum[notebook]"     # + IPython for pp.show()

The PyPI package is paperplot-quantum, but you still import paperplot. Python ≥ 3.10. Latest main from GitHub: pip install "git+https://github.com/zlatko-minev/paperplot.git". Full matrix on the Install page.

30-second quickstart

import numpy as np
import paperplot as pp

pp.use("aps")                          # or "nature" / "ieee" / "prl" / "talk" — set once

fig, ax = pp.figure(width="single")    # 8.6 cm wide, golden ratio, styled
ax.plot(np.linspace(0, 10, 200), np.sin(np.linspace(0, 10, 200)))
ax.set_xlabel(r"delay $\tau$ (ns)")
ax.set_ylabel(r"population $\langle n \rangle$")

pp.save(fig, "fig1.pdf")               # embeds fonts, runs preflight()

Gallery

Same three-curve plot, same data — stock matplotlib versus one pp.use("aps"). paperplot fixes the column width, type scale, color cycle, ticks, and line weights in a single call.

matplotlib defaults
paperplot — pp.use("aps")

One figure, many panels

A custom GridSpec, four panel letters: (a) $T_1$ relaxation and (b) Ramsey fringes — each a fit with a flush residual strip — plus (c) outlined $T_1$ distributions and (d) a chevron map. Journal styling carries through a hand-built grid.

See it on the page before you submit

The part most styling packages skip: pp.preview_in_page(fig) drops your figure into a true-to-scale mock journal page with real body text — so you see exactly how big it lands in the column and whether the lettering still reads, before you submit. More in the gallery.


single column

histograms, in context

double column, across the page

Every image is generated from examples/showcase.py by docs/generate_gallery.py and regenerated by CI on each docs build (published to GitHub Pages). Build locally:

pip install -e ".[docs,notebook]"
python docs/generate_gallery.py && mkdocs serve

What you get

True journal sizing width="single"/"onehalf"/"double"/"full_page" → exact column widths in inches.
Correct export PDF default, EPS first-class; fonts embedded as Type-42, no Type-3; RGB; revision stamped in metadata.
Preflight linter pp.preflight(fig) → structured Report: flags sub-spec fonts/lines and grayscale-ambiguous colors. Warn, never block.
Colorblind-safe by default Okabe-Ito cycle; sequential/diverging maps kept separate (pp.cmap("BuGn")); custom via pp.register_palette. Fill/stroke convention: pp.fills() (muted) under pp.strokes() (bright). pp.show_palettes() shows every palette tagged for colorblind-safety.
Plot helpers pp.hist_outline (translucent fill + crisp staircase outline), pp.hist_filled, pp.data_fit_band (markers + fit + CI band), pp.swatches.
See it on the page pp.preview_in_page(fig) drops the figure into a true-to-scale mock journal page with real body text — catch sizing/legibility before you submit (a paperplot original). Plus pp.show(fig, zoom=2) and pp.grayscale_proof(fig).
Helpers pp.panel_labels(axes), pp.despine(ax), pp.clean_shared_axes(fig).

A bit more

# multi-panel, double column
fig, axes = pp.figure(width="double", nrows=2, ncols=2, sharex=True)
pp.panel_labels(axes, fmt="({})")          # (a) (b) (c) (d)
pp.clean_shared_axes(fig)

# sans-serif labels with Computer Modern math is the default (the physics look);
# math="sans" pairs Arial-style math instead, font_scale nudges every size
pp.use("aps", math="cm", font_scale=1.1)    # sticky: pp.figure() inherits both

# scoped style (doesn't leak), serif to match REVTeX Times
with pp.style("aps", serif=True):
    fig, ax = pp.figure(width="full_page")

# per-figure overrides
fig, ax = pp.figure("single", journal="prl", palette="mylab")

# overlapping histograms, fig-3 style: muted fills, crisp outlines
fig, ax = pp.figure("single")
for s, c in zip((sample_a, sample_b), pp.fills()):
    pp.hist_outline(s, ax, bins=60, rescale=True, color=c)
ax.axvline(ideal, color=pp.strokes()[8], ls="--", lw=1.2, zorder=100)

# data + fit + ±1σ band (e.g. lmfit result.eval / eval_uncertainty)
pp.data_fit_band(ax, x, y, yerr=yerr, x_fit=xf, y_fit=yf, y_fit_err=yf_err)

# pre-submission check
report = pp.preflight(fig)
if not report:                              # truthy == clean
    print(report)                           # aligned table of issues

Targets

pp.use(...) / pp.figure(journal=...) accept any of:

key target single / double width
aps (+ prl prx prb) Physical Review 8.6 cm / 17.8 cm
nature Nature 8.9 cm / 18.3 cm
ieee IEEE Transactions / conference 8.9 cm (3.5″) / 18.2 cm (7.16″)
talk slides / presentation larger type, thicker lines (scale with the target)

pp.available() lists them all. New journals are pure data — add a table to data/journals.toml; no code.

Drop-in style sheets

The product is pp.figure()/pp.save() — they size to the real column, embed fonts, and preflight, which a style sheet cannot. But the look is just rcParams, and rcParams travel as .mplstyle files, so you can adopt it with zero buy-in and graduate later:

import paperplot as pp, matplotlib.pyplot as plt

pp.register_mplstyles()                 # registers paperplot-aps/-nature/-ieee/-talk
plt.style.use("paperplot-aps")          # now use plain matplotlib
# composable, matplotlib-native: layer a built-in modifier on top
plt.style.use(["paperplot-nature", "ggplot"])

# or write a file to commit / share / drop into ~/.config/matplotlib/stylelib/
pp.export_mplstyle("ieee", "ieee.mplstyle", serif=True)

paperplot never touches matplotlib's global style library on import — registration is an explicit opt-in. A style sheet carries the target's default (single-column) size; reach for pp.figure(width=...) when you need the column-true figure.

Fonts

Journals want figure lettering in Helvetica or Arial (Nature requires it), but those are proprietary and can't be redistributed. So paperplot ships TeX Gyre Heros — a free, metric-compatible Helvetica clone — and registers it automatically. The font preference is Arial → Helvetica → TeX Gyre Heros → DejaVu Sans: if you have the real thing installed it's used, otherwise the bundled clone gives the same Helvetica look on every machine (and embeds a proper Type-42 font in the PDF, instead of silently falling back to matplotlib's DejaVu Sans). This is why the figures look identical on your laptop, a collaborator's box, and CI. Bundled under the free GUST Font License (paperplot/data/fonts/LICENSE.md).

Design

See paperplot_DESIGN.md for the full design — journal specs as data (data/journals.toml), the rcParams mapping, and the registry/versioning model.

Layout

paperplot/
  journals.py   registry.py   data/journals.toml   layout.py
  style.py      lint.py       palettes.py          preview.py
  fonts.py      save.py       core.py              plots.py
  mplstyle.py

Develop

pip install -e ".[dev]"
pytest                              # tests
python examples/run_all.py          # run every example -> examples/out/render/*.png

Releasing

Published to PyPI as paperplot-quantum via Trusted Publishing (OIDC — no stored tokens). To cut a release: bump version in pyproject.toml, push, then create a GitHub Release with tag vX.Y.Z. The release.yml workflow builds, runs twine check, and publishes. (One-time: register the trusted publisher on PyPI — repo zlatko-minev/paperplot, workflow release.yml, environment pypi.)

About

Publication-ready matplotlib figures, sized & styled per journal (APS, Nature, IEEE)

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages