paperplot-quantum 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. paperplot_quantum-0.1.0/LICENSE +21 -0
  2. paperplot_quantum-0.1.0/PKG-INFO +281 -0
  3. paperplot_quantum-0.1.0/README.md +237 -0
  4. paperplot_quantum-0.1.0/paperplot/__init__.py +77 -0
  5. paperplot_quantum-0.1.0/paperplot/core.py +228 -0
  6. paperplot_quantum-0.1.0/paperplot/data/fonts/GUST-FONT-LICENSE.txt +29 -0
  7. paperplot_quantum-0.1.0/paperplot/data/fonts/LICENSE.md +22 -0
  8. paperplot_quantum-0.1.0/paperplot/data/fonts/texgyreheros-bold.otf +0 -0
  9. paperplot_quantum-0.1.0/paperplot/data/fonts/texgyreheros-bolditalic.otf +0 -0
  10. paperplot_quantum-0.1.0/paperplot/data/fonts/texgyreheros-italic.otf +0 -0
  11. paperplot_quantum-0.1.0/paperplot/data/fonts/texgyreheros-regular.otf +0 -0
  12. paperplot_quantum-0.1.0/paperplot/data/journals.toml +123 -0
  13. paperplot_quantum-0.1.0/paperplot/fonts.py +45 -0
  14. paperplot_quantum-0.1.0/paperplot/journals.py +57 -0
  15. paperplot_quantum-0.1.0/paperplot/layout.py +133 -0
  16. paperplot_quantum-0.1.0/paperplot/lint.py +156 -0
  17. paperplot_quantum-0.1.0/paperplot/mplstyle.py +103 -0
  18. paperplot_quantum-0.1.0/paperplot/palettes.py +190 -0
  19. paperplot_quantum-0.1.0/paperplot/plots.py +310 -0
  20. paperplot_quantum-0.1.0/paperplot/preview.py +250 -0
  21. paperplot_quantum-0.1.0/paperplot/registry.py +111 -0
  22. paperplot_quantum-0.1.0/paperplot/save.py +99 -0
  23. paperplot_quantum-0.1.0/paperplot/style.py +194 -0
  24. paperplot_quantum-0.1.0/paperplot_quantum.egg-info/PKG-INFO +281 -0
  25. paperplot_quantum-0.1.0/paperplot_quantum.egg-info/SOURCES.txt +32 -0
  26. paperplot_quantum-0.1.0/paperplot_quantum.egg-info/dependency_links.txt +1 -0
  27. paperplot_quantum-0.1.0/paperplot_quantum.egg-info/requires.txt +21 -0
  28. paperplot_quantum-0.1.0/paperplot_quantum.egg-info/top_level.txt +1 -0
  29. paperplot_quantum-0.1.0/pyproject.toml +61 -0
  30. paperplot_quantum-0.1.0/setup.cfg +4 -0
  31. paperplot_quantum-0.1.0/tests/test_fonts.py +35 -0
  32. paperplot_quantum-0.1.0/tests/test_paperplot.py +307 -0
  33. paperplot_quantum-0.1.0/tests/test_plots.py +154 -0
  34. paperplot_quantum-0.1.0/tests/test_robustness.py +172 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 The paperplot authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,281 @@
1
+ Metadata-Version: 2.4
2
+ Name: paperplot-quantum
3
+ Version: 0.1.0
4
+ Summary: Publication-correct matplotlib figures, journal by journal (APS, Nature, IEEE, + presentation).
5
+ Author: Zlatko Minev
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/zlatko-minev/paperplot
8
+ Project-URL: Documentation, https://zlatko-minev.github.io/paperplot/
9
+ Project-URL: Repository, https://github.com/zlatko-minev/paperplot
10
+ Project-URL: Issues, https://github.com/zlatko-minev/paperplot/issues
11
+ Keywords: matplotlib,publication,figures,physics,APS,Physical Review
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Visualization
22
+ Classifier: Framework :: Matplotlib
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ License-File: paperplot/data/fonts/LICENSE.md
27
+ License-File: paperplot/data/fonts/GUST-FONT-LICENSE.txt
28
+ Requires-Dist: matplotlib>=3.6
29
+ Requires-Dist: numpy>=1.21
30
+ Requires-Dist: tomli>=1.2; python_version < "3.11"
31
+ Provides-Extra: notebook
32
+ Requires-Dist: IPython>=7; extra == "notebook"
33
+ Provides-Extra: seaborn
34
+ Requires-Dist: seaborn>=0.12; extra == "seaborn"
35
+ Provides-Extra: dev
36
+ Requires-Dist: pytest>=7; extra == "dev"
37
+ Provides-Extra: docs
38
+ Requires-Dist: mkdocs-material>=9.5; extra == "docs"
39
+ Requires-Dist: mkdocs-jupyter>=0.24; extra == "docs"
40
+ Requires-Dist: jupytext>=1.16; extra == "docs"
41
+ Requires-Dist: nbconvert>=7; extra == "docs"
42
+ Requires-Dist: ipykernel>=6; extra == "docs"
43
+ Dynamic: license-file
44
+
45
+ # paperplot
46
+
47
+ [![CI](https://github.com/zlatko-minev/paperplot/actions/workflows/ci.yml/badge.svg)](https://github.com/zlatko-minev/paperplot/actions/workflows/ci.yml)
48
+ [![Docs](https://github.com/zlatko-minev/paperplot/actions/workflows/docs.yml/badge.svg)](https://zlatko-minev.github.io/paperplot/)
49
+ [![Python](https://img.shields.io/badge/python-%E2%89%A53.10-blue)](https://www.python.org/)
50
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
51
+ [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/zlatko-minev/paperplot/blob/main/docs/showcase.ipynb)
52
+ [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/zlatko-minev/paperplot/main?labpath=docs/showcase.ipynb)
53
+
54
+ **Publication-correct matplotlib figures, journal by journal.**
55
+
56
+ Most matplotlib styling focuses on the *look*. `paperplot` does that too — but it
57
+ also handles the parts that decide whether a journal accepts your figure:
58
+
59
+ - **sizes it to the journal's real column width** (8.6 cm, 17.8 cm, …), not a generic default;
60
+ - **embeds fonts correctly** (Type-42, no Type-3);
61
+ - **preflights it** for the rules figures actually get rejected over — font type,
62
+ line weight, lettering height, and grayscale legibility.
63
+
64
+ Ships **Physical Review (APS)** (incl. PRL/PRX/PRB), **Nature**, and **IEEE**, plus a
65
+ **presentation** target for slides. matplotlib-only core; seaborn/IPython are optional
66
+ extras.
67
+
68
+ > **Just want the look?** `pp.register_mplstyles()` then `plt.style.use("paperplot-aps")` —
69
+ > no API to learn. You give up only the parts a style sheet *can't* do (true column
70
+ > sizing, font embedding, preflight); `pp.figure()`/`pp.save()` add those back. See
71
+ > [Drop-in style sheets](#drop-in-style-sheets).
72
+
73
+ ## Install
74
+
75
+ ```bash
76
+ pip install paperplot-quantum # core (matplotlib + numpy)
77
+ pip install "paperplot-quantum[notebook]" # + IPython for pp.show()
78
+ ```
79
+
80
+ The PyPI package is **`paperplot-quantum`**, but you still **`import paperplot`**.
81
+ Python ≥ 3.10. Latest `main` from GitHub:
82
+ `pip install "git+https://github.com/zlatko-minev/paperplot.git"`. Full matrix on
83
+ the [Install page](https://zlatko-minev.github.io/paperplot/install/).
84
+
85
+ ## 30-second quickstart
86
+
87
+ ```python
88
+ import numpy as np
89
+ import paperplot as pp
90
+
91
+ pp.use("aps") # or "nature" / "ieee" / "prl" / "talk" — set once
92
+
93
+ fig, ax = pp.figure(width="single") # 8.6 cm wide, golden ratio, styled
94
+ ax.plot(np.linspace(0, 10, 200), np.sin(np.linspace(0, 10, 200)))
95
+ ax.set_xlabel(r"delay $\tau$ (ns)")
96
+ ax.set_ylabel(r"population $\langle n \rangle$")
97
+
98
+ pp.save(fig, "fig1.pdf") # embeds fonts, runs preflight()
99
+ ```
100
+
101
+ ## Gallery
102
+
103
+ Same three-curve plot, same data — stock matplotlib versus one `pp.use("aps")`.
104
+ paperplot fixes the column width, type scale, color cycle, ticks, and line weights
105
+ in a single call.
106
+
107
+ <table>
108
+ <tr>
109
+ <td align="center"><b>matplotlib defaults</b><br><img src="docs/assets/gallery/before.png" width="380"></td>
110
+ <td align="center"><b>paperplot — <code>pp.use("aps")</code></b><br><img src="docs/assets/gallery/after.png" width="380"></td>
111
+ </tr>
112
+ </table>
113
+
114
+ <table>
115
+ <tr>
116
+ <td><img src="docs/assets/gallery/aps_4panel_single.png" width="250"></td>
117
+ <td><img src="docs/assets/gallery/hist_overlap.png" width="250"></td>
118
+ <td><img src="docs/assets/gallery/data_fit_band.png" width="250"></td>
119
+ </tr>
120
+ </table>
121
+
122
+ ### One figure, many panels
123
+
124
+ A custom `GridSpec`, four panel letters: **(a)** $T_1$ relaxation and **(b)** Ramsey
125
+ fringes — each a fit with a **flush residual strip** — plus **(c)** outlined $T_1$
126
+ distributions and **(d)** a chevron map. Journal styling carries through a hand-built grid.
127
+
128
+ <p align="center"><img src="docs/assets/gallery/composite.png" width="720"></p>
129
+
130
+ ### See it on the page before you submit
131
+
132
+ The part most styling packages skip: `pp.preview_in_page(fig)` drops your figure
133
+ into a **true-to-scale mock journal page** with real body text — so you see exactly
134
+ how big it lands in the column and whether the lettering still reads, *before* you
135
+ submit. More in the [gallery](https://zlatko-minev.github.io/paperplot/gallery/#on-the-page).
136
+
137
+ <table>
138
+ <tr>
139
+ <td align="center"><img src="docs/assets/gallery/in_page_aps.png" width="250"><br>single column</td>
140
+ <td align="center"><img src="docs/assets/gallery/in_page_hist.png" width="250"><br>histograms, in context</td>
141
+ <td align="center"><img src="docs/assets/gallery/in_page_double.png" width="250"><br>double column, across the page</td>
142
+ </tr>
143
+ </table>
144
+
145
+ Every image is generated from [`examples/showcase.py`](examples/showcase.py) by
146
+ [`docs/generate_gallery.py`](docs/generate_gallery.py) and regenerated by CI on each
147
+ docs build (published to GitHub Pages). Build locally:
148
+
149
+ ```bash
150
+ pip install -e ".[docs,notebook]"
151
+ python docs/generate_gallery.py && mkdocs serve
152
+ ```
153
+
154
+ ## What you get
155
+
156
+ | | |
157
+ |---|---|
158
+ | **True journal sizing** | `width="single"/"onehalf"/"double"/"full_page"` → exact column widths in inches. |
159
+ | **Correct export** | PDF default, EPS first-class; fonts embedded as Type-42, no Type-3; RGB; revision stamped in metadata. |
160
+ | **Preflight linter** | `pp.preflight(fig)` → structured `Report`: flags sub-spec fonts/lines and grayscale-ambiguous colors. Warn, never block. |
161
+ | **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. |
162
+ | **Plot helpers** | `pp.hist_outline` (translucent fill + crisp staircase outline), `pp.hist_filled`, `pp.data_fit_band` (markers + fit + CI band), `pp.swatches`. |
163
+ | **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)`. |
164
+ | **Helpers** | `pp.panel_labels(axes)`, `pp.despine(ax)`, `pp.clean_shared_axes(fig)`. |
165
+
166
+ ## A bit more
167
+
168
+ ```python
169
+ # multi-panel, double column
170
+ fig, axes = pp.figure(width="double", nrows=2, ncols=2, sharex=True)
171
+ pp.panel_labels(axes, fmt="({})") # (a) (b) (c) (d)
172
+ pp.clean_shared_axes(fig)
173
+
174
+ # sans-serif labels with Computer Modern math is the default (the physics look);
175
+ # math="sans" pairs Arial-style math instead, font_scale nudges every size
176
+ pp.use("aps", math="cm", font_scale=1.1) # sticky: pp.figure() inherits both
177
+
178
+ # scoped style (doesn't leak), serif to match REVTeX Times
179
+ with pp.style("aps", serif=True):
180
+ fig, ax = pp.figure(width="full_page")
181
+
182
+ # per-figure overrides
183
+ fig, ax = pp.figure("single", journal="prl", palette="mylab")
184
+
185
+ # overlapping histograms, fig-3 style: muted fills, crisp outlines
186
+ fig, ax = pp.figure("single")
187
+ for s, c in zip((sample_a, sample_b), pp.fills()):
188
+ pp.hist_outline(s, ax, bins=60, rescale=True, color=c)
189
+ ax.axvline(ideal, color=pp.strokes()[8], ls="--", lw=1.2, zorder=100)
190
+
191
+ # data + fit + ±1σ band (e.g. lmfit result.eval / eval_uncertainty)
192
+ pp.data_fit_band(ax, x, y, yerr=yerr, x_fit=xf, y_fit=yf, y_fit_err=yf_err)
193
+
194
+ # pre-submission check
195
+ report = pp.preflight(fig)
196
+ if not report: # truthy == clean
197
+ print(report) # aligned table of issues
198
+ ```
199
+
200
+ ## Targets
201
+
202
+ `pp.use(...)` / `pp.figure(journal=...)` accept any of:
203
+
204
+ | key | target | single / double width |
205
+ |---|---|---|
206
+ | `aps` (+ `prl` `prx` `prb`) | Physical Review | 8.6 cm / 17.8 cm |
207
+ | `nature` | Nature | 8.9 cm / 18.3 cm |
208
+ | `ieee` | IEEE Transactions / conference | 8.9 cm (3.5″) / 18.2 cm (7.16″) |
209
+ | `talk` | slides / presentation | larger type, thicker lines (scale with the target) |
210
+
211
+ `pp.available()` lists them all. New journals are pure data — add a table to
212
+ [`data/journals.toml`](paperplot/data/journals.toml); no code.
213
+
214
+ ## Drop-in style sheets
215
+
216
+ The product is `pp.figure()`/`pp.save()` — they size to the real column, embed
217
+ fonts, and preflight, which a style sheet *cannot*. But the **look** is just
218
+ rcParams, and rcParams travel as `.mplstyle` files, so you can adopt it with zero
219
+ buy-in and graduate later:
220
+
221
+ ```python
222
+ import paperplot as pp, matplotlib.pyplot as plt
223
+
224
+ pp.register_mplstyles() # registers paperplot-aps/-nature/-ieee/-talk
225
+ plt.style.use("paperplot-aps") # now use plain matplotlib
226
+ # composable, matplotlib-native: layer a built-in modifier on top
227
+ plt.style.use(["paperplot-nature", "ggplot"])
228
+
229
+ # or write a file to commit / share / drop into ~/.config/matplotlib/stylelib/
230
+ pp.export_mplstyle("ieee", "ieee.mplstyle", serif=True)
231
+ ```
232
+
233
+ paperplot never touches matplotlib's global style library on import — registration
234
+ is an explicit opt-in. A style sheet carries the target's *default* (single-column)
235
+ size; reach for `pp.figure(width=...)` when you need the column-true figure.
236
+
237
+ ## Fonts
238
+
239
+ Journals want figure lettering in **Helvetica or Arial** (Nature requires it), but
240
+ those are proprietary and can't be redistributed. So paperplot ships **TeX Gyre
241
+ Heros** — a free, metric-compatible Helvetica clone — and registers it
242
+ automatically. The font preference is `Arial → Helvetica → TeX Gyre Heros →
243
+ DejaVu Sans`: if you have the real thing installed it's used, otherwise the
244
+ bundled clone gives the *same Helvetica look on every machine* (and embeds a
245
+ proper Type-42 font in the PDF, instead of silently falling back to matplotlib's
246
+ DejaVu Sans). This is why the figures look identical on your laptop, a
247
+ collaborator's box, and CI. Bundled under the free GUST Font License
248
+ ([`paperplot/data/fonts/LICENSE.md`](paperplot/data/fonts/LICENSE.md)).
249
+
250
+ ## Design
251
+
252
+ See [`paperplot_DESIGN.md`](paperplot_DESIGN.md) for the full design — journal specs
253
+ as data (`data/journals.toml`), the rcParams mapping, and the registry/versioning
254
+ model.
255
+
256
+ ## Layout
257
+
258
+ ```
259
+ paperplot/
260
+ journals.py registry.py data/journals.toml layout.py
261
+ style.py lint.py palettes.py preview.py
262
+ fonts.py save.py core.py plots.py
263
+ mplstyle.py
264
+ ```
265
+
266
+ ## Develop
267
+
268
+ ```bash
269
+ pip install -e ".[dev]"
270
+ pytest # tests
271
+ python examples/run_all.py # run every example -> examples/out/render/*.png
272
+ ```
273
+
274
+ ## Releasing
275
+
276
+ Published to PyPI as **`paperplot-quantum`** via **Trusted Publishing** (OIDC — no
277
+ stored tokens). To cut a release: bump `version` in `pyproject.toml`, push, then
278
+ create a GitHub **Release** with tag `vX.Y.Z`. The
279
+ [`release.yml`](.github/workflows/release.yml) workflow builds, runs `twine check`,
280
+ and publishes. (One-time: register the trusted publisher on PyPI — repo
281
+ `zlatko-minev/paperplot`, workflow `release.yml`, environment `pypi`.)
@@ -0,0 +1,237 @@
1
+ # paperplot
2
+
3
+ [![CI](https://github.com/zlatko-minev/paperplot/actions/workflows/ci.yml/badge.svg)](https://github.com/zlatko-minev/paperplot/actions/workflows/ci.yml)
4
+ [![Docs](https://github.com/zlatko-minev/paperplot/actions/workflows/docs.yml/badge.svg)](https://zlatko-minev.github.io/paperplot/)
5
+ [![Python](https://img.shields.io/badge/python-%E2%89%A53.10-blue)](https://www.python.org/)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
7
+ [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/zlatko-minev/paperplot/blob/main/docs/showcase.ipynb)
8
+ [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/zlatko-minev/paperplot/main?labpath=docs/showcase.ipynb)
9
+
10
+ **Publication-correct matplotlib figures, journal by journal.**
11
+
12
+ Most matplotlib styling focuses on the *look*. `paperplot` does that too — but it
13
+ also handles the parts that decide whether a journal accepts your figure:
14
+
15
+ - **sizes it to the journal's real column width** (8.6 cm, 17.8 cm, …), not a generic default;
16
+ - **embeds fonts correctly** (Type-42, no Type-3);
17
+ - **preflights it** for the rules figures actually get rejected over — font type,
18
+ line weight, lettering height, and grayscale legibility.
19
+
20
+ Ships **Physical Review (APS)** (incl. PRL/PRX/PRB), **Nature**, and **IEEE**, plus a
21
+ **presentation** target for slides. matplotlib-only core; seaborn/IPython are optional
22
+ extras.
23
+
24
+ > **Just want the look?** `pp.register_mplstyles()` then `plt.style.use("paperplot-aps")` —
25
+ > no API to learn. You give up only the parts a style sheet *can't* do (true column
26
+ > sizing, font embedding, preflight); `pp.figure()`/`pp.save()` add those back. See
27
+ > [Drop-in style sheets](#drop-in-style-sheets).
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ pip install paperplot-quantum # core (matplotlib + numpy)
33
+ pip install "paperplot-quantum[notebook]" # + IPython for pp.show()
34
+ ```
35
+
36
+ The PyPI package is **`paperplot-quantum`**, but you still **`import paperplot`**.
37
+ Python ≥ 3.10. Latest `main` from GitHub:
38
+ `pip install "git+https://github.com/zlatko-minev/paperplot.git"`. Full matrix on
39
+ the [Install page](https://zlatko-minev.github.io/paperplot/install/).
40
+
41
+ ## 30-second quickstart
42
+
43
+ ```python
44
+ import numpy as np
45
+ import paperplot as pp
46
+
47
+ pp.use("aps") # or "nature" / "ieee" / "prl" / "talk" — set once
48
+
49
+ fig, ax = pp.figure(width="single") # 8.6 cm wide, golden ratio, styled
50
+ ax.plot(np.linspace(0, 10, 200), np.sin(np.linspace(0, 10, 200)))
51
+ ax.set_xlabel(r"delay $\tau$ (ns)")
52
+ ax.set_ylabel(r"population $\langle n \rangle$")
53
+
54
+ pp.save(fig, "fig1.pdf") # embeds fonts, runs preflight()
55
+ ```
56
+
57
+ ## Gallery
58
+
59
+ Same three-curve plot, same data — stock matplotlib versus one `pp.use("aps")`.
60
+ paperplot fixes the column width, type scale, color cycle, ticks, and line weights
61
+ in a single call.
62
+
63
+ <table>
64
+ <tr>
65
+ <td align="center"><b>matplotlib defaults</b><br><img src="docs/assets/gallery/before.png" width="380"></td>
66
+ <td align="center"><b>paperplot — <code>pp.use("aps")</code></b><br><img src="docs/assets/gallery/after.png" width="380"></td>
67
+ </tr>
68
+ </table>
69
+
70
+ <table>
71
+ <tr>
72
+ <td><img src="docs/assets/gallery/aps_4panel_single.png" width="250"></td>
73
+ <td><img src="docs/assets/gallery/hist_overlap.png" width="250"></td>
74
+ <td><img src="docs/assets/gallery/data_fit_band.png" width="250"></td>
75
+ </tr>
76
+ </table>
77
+
78
+ ### One figure, many panels
79
+
80
+ A custom `GridSpec`, four panel letters: **(a)** $T_1$ relaxation and **(b)** Ramsey
81
+ fringes — each a fit with a **flush residual strip** — plus **(c)** outlined $T_1$
82
+ distributions and **(d)** a chevron map. Journal styling carries through a hand-built grid.
83
+
84
+ <p align="center"><img src="docs/assets/gallery/composite.png" width="720"></p>
85
+
86
+ ### See it on the page before you submit
87
+
88
+ The part most styling packages skip: `pp.preview_in_page(fig)` drops your figure
89
+ into a **true-to-scale mock journal page** with real body text — so you see exactly
90
+ how big it lands in the column and whether the lettering still reads, *before* you
91
+ submit. More in the [gallery](https://zlatko-minev.github.io/paperplot/gallery/#on-the-page).
92
+
93
+ <table>
94
+ <tr>
95
+ <td align="center"><img src="docs/assets/gallery/in_page_aps.png" width="250"><br>single column</td>
96
+ <td align="center"><img src="docs/assets/gallery/in_page_hist.png" width="250"><br>histograms, in context</td>
97
+ <td align="center"><img src="docs/assets/gallery/in_page_double.png" width="250"><br>double column, across the page</td>
98
+ </tr>
99
+ </table>
100
+
101
+ Every image is generated from [`examples/showcase.py`](examples/showcase.py) by
102
+ [`docs/generate_gallery.py`](docs/generate_gallery.py) and regenerated by CI on each
103
+ docs build (published to GitHub Pages). Build locally:
104
+
105
+ ```bash
106
+ pip install -e ".[docs,notebook]"
107
+ python docs/generate_gallery.py && mkdocs serve
108
+ ```
109
+
110
+ ## What you get
111
+
112
+ | | |
113
+ |---|---|
114
+ | **True journal sizing** | `width="single"/"onehalf"/"double"/"full_page"` → exact column widths in inches. |
115
+ | **Correct export** | PDF default, EPS first-class; fonts embedded as Type-42, no Type-3; RGB; revision stamped in metadata. |
116
+ | **Preflight linter** | `pp.preflight(fig)` → structured `Report`: flags sub-spec fonts/lines and grayscale-ambiguous colors. Warn, never block. |
117
+ | **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. |
118
+ | **Plot helpers** | `pp.hist_outline` (translucent fill + crisp staircase outline), `pp.hist_filled`, `pp.data_fit_band` (markers + fit + CI band), `pp.swatches`. |
119
+ | **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)`. |
120
+ | **Helpers** | `pp.panel_labels(axes)`, `pp.despine(ax)`, `pp.clean_shared_axes(fig)`. |
121
+
122
+ ## A bit more
123
+
124
+ ```python
125
+ # multi-panel, double column
126
+ fig, axes = pp.figure(width="double", nrows=2, ncols=2, sharex=True)
127
+ pp.panel_labels(axes, fmt="({})") # (a) (b) (c) (d)
128
+ pp.clean_shared_axes(fig)
129
+
130
+ # sans-serif labels with Computer Modern math is the default (the physics look);
131
+ # math="sans" pairs Arial-style math instead, font_scale nudges every size
132
+ pp.use("aps", math="cm", font_scale=1.1) # sticky: pp.figure() inherits both
133
+
134
+ # scoped style (doesn't leak), serif to match REVTeX Times
135
+ with pp.style("aps", serif=True):
136
+ fig, ax = pp.figure(width="full_page")
137
+
138
+ # per-figure overrides
139
+ fig, ax = pp.figure("single", journal="prl", palette="mylab")
140
+
141
+ # overlapping histograms, fig-3 style: muted fills, crisp outlines
142
+ fig, ax = pp.figure("single")
143
+ for s, c in zip((sample_a, sample_b), pp.fills()):
144
+ pp.hist_outline(s, ax, bins=60, rescale=True, color=c)
145
+ ax.axvline(ideal, color=pp.strokes()[8], ls="--", lw=1.2, zorder=100)
146
+
147
+ # data + fit + ±1σ band (e.g. lmfit result.eval / eval_uncertainty)
148
+ pp.data_fit_band(ax, x, y, yerr=yerr, x_fit=xf, y_fit=yf, y_fit_err=yf_err)
149
+
150
+ # pre-submission check
151
+ report = pp.preflight(fig)
152
+ if not report: # truthy == clean
153
+ print(report) # aligned table of issues
154
+ ```
155
+
156
+ ## Targets
157
+
158
+ `pp.use(...)` / `pp.figure(journal=...)` accept any of:
159
+
160
+ | key | target | single / double width |
161
+ |---|---|---|
162
+ | `aps` (+ `prl` `prx` `prb`) | Physical Review | 8.6 cm / 17.8 cm |
163
+ | `nature` | Nature | 8.9 cm / 18.3 cm |
164
+ | `ieee` | IEEE Transactions / conference | 8.9 cm (3.5″) / 18.2 cm (7.16″) |
165
+ | `talk` | slides / presentation | larger type, thicker lines (scale with the target) |
166
+
167
+ `pp.available()` lists them all. New journals are pure data — add a table to
168
+ [`data/journals.toml`](paperplot/data/journals.toml); no code.
169
+
170
+ ## Drop-in style sheets
171
+
172
+ The product is `pp.figure()`/`pp.save()` — they size to the real column, embed
173
+ fonts, and preflight, which a style sheet *cannot*. But the **look** is just
174
+ rcParams, and rcParams travel as `.mplstyle` files, so you can adopt it with zero
175
+ buy-in and graduate later:
176
+
177
+ ```python
178
+ import paperplot as pp, matplotlib.pyplot as plt
179
+
180
+ pp.register_mplstyles() # registers paperplot-aps/-nature/-ieee/-talk
181
+ plt.style.use("paperplot-aps") # now use plain matplotlib
182
+ # composable, matplotlib-native: layer a built-in modifier on top
183
+ plt.style.use(["paperplot-nature", "ggplot"])
184
+
185
+ # or write a file to commit / share / drop into ~/.config/matplotlib/stylelib/
186
+ pp.export_mplstyle("ieee", "ieee.mplstyle", serif=True)
187
+ ```
188
+
189
+ paperplot never touches matplotlib's global style library on import — registration
190
+ is an explicit opt-in. A style sheet carries the target's *default* (single-column)
191
+ size; reach for `pp.figure(width=...)` when you need the column-true figure.
192
+
193
+ ## Fonts
194
+
195
+ Journals want figure lettering in **Helvetica or Arial** (Nature requires it), but
196
+ those are proprietary and can't be redistributed. So paperplot ships **TeX Gyre
197
+ Heros** — a free, metric-compatible Helvetica clone — and registers it
198
+ automatically. The font preference is `Arial → Helvetica → TeX Gyre Heros →
199
+ DejaVu Sans`: if you have the real thing installed it's used, otherwise the
200
+ bundled clone gives the *same Helvetica look on every machine* (and embeds a
201
+ proper Type-42 font in the PDF, instead of silently falling back to matplotlib's
202
+ DejaVu Sans). This is why the figures look identical on your laptop, a
203
+ collaborator's box, and CI. Bundled under the free GUST Font License
204
+ ([`paperplot/data/fonts/LICENSE.md`](paperplot/data/fonts/LICENSE.md)).
205
+
206
+ ## Design
207
+
208
+ See [`paperplot_DESIGN.md`](paperplot_DESIGN.md) for the full design — journal specs
209
+ as data (`data/journals.toml`), the rcParams mapping, and the registry/versioning
210
+ model.
211
+
212
+ ## Layout
213
+
214
+ ```
215
+ paperplot/
216
+ journals.py registry.py data/journals.toml layout.py
217
+ style.py lint.py palettes.py preview.py
218
+ fonts.py save.py core.py plots.py
219
+ mplstyle.py
220
+ ```
221
+
222
+ ## Develop
223
+
224
+ ```bash
225
+ pip install -e ".[dev]"
226
+ pytest # tests
227
+ python examples/run_all.py # run every example -> examples/out/render/*.png
228
+ ```
229
+
230
+ ## Releasing
231
+
232
+ Published to PyPI as **`paperplot-quantum`** via **Trusted Publishing** (OIDC — no
233
+ stored tokens). To cut a release: bump `version` in `pyproject.toml`, push, then
234
+ create a GitHub **Release** with tag `vX.Y.Z`. The
235
+ [`release.yml`](.github/workflows/release.yml) workflow builds, runs `twine check`,
236
+ and publishes. (One-time: register the trusted publisher on PyPI — repo
237
+ `zlatko-minev/paperplot`, workflow `release.yml`, environment `pypi`.)
@@ -0,0 +1,77 @@
1
+ """paperplot — publication-correct matplotlib figures, journal by journal.
2
+
3
+ Quickstart::
4
+
5
+ import paperplot as pp
6
+
7
+ pp.use("aps") # set the journal once
8
+ fig, ax = pp.figure(width="single") # journal-sized, journal-styled
9
+ ax.plot(x, y)
10
+ pp.save(fig, "fig1.pdf") # embeds fonts, runs preflight()
11
+
12
+ Ships Physical Review (APS, incl. PRL/PRX/PRB), Nature, and IEEE, plus a "talk"
13
+ presentation target. matplotlib-only core; seaborn/IPython optional.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from .journals import FontScale, JournalSpec
19
+ from .layout import Width, Row, Grid
20
+ from .lint import Finding, Report, preflight
21
+ from .palettes import (
22
+ OKABE_ITO,
23
+ available_palettes,
24
+ cmap,
25
+ fills,
26
+ is_colorblind_safe,
27
+ palette,
28
+ register_palette,
29
+ strokes,
30
+ )
31
+ from .preview import grayscale_proof, preview_in_page, show
32
+ from .registry import available, get_spec
33
+ from .style import active, apply, rcparams, reset, style, use
34
+ from .core import (
35
+ clean_shared_axes,
36
+ despine,
37
+ figure,
38
+ panel_labels,
39
+ subplots,
40
+ composite,
41
+ )
42
+ from .plots import (
43
+ data_fit_band,
44
+ hist_filled,
45
+ hist_outline,
46
+ show_palettes,
47
+ swatches,
48
+ )
49
+ from .save import save
50
+ from .mplstyle import export_mplstyle, register_mplstyles, to_mplstyle_text
51
+
52
+ __version__ = "0.1.0"
53
+
54
+ __all__ = [
55
+ # types
56
+ "JournalSpec", "FontScale", "Width", "Report", "Finding",
57
+ "Row", "Grid",
58
+ # journal / style
59
+ "use", "style", "reset", "active", "apply", "rcparams",
60
+ "get_spec", "available",
61
+ # figures
62
+ "figure", "subplots", "composite", "save",
63
+ # helpers
64
+ "panel_labels", "despine", "clean_shared_axes",
65
+ # colors
66
+ "palette", "cmap", "register_palette", "OKABE_ITO", "fills", "strokes",
67
+ "available_palettes", "is_colorblind_safe",
68
+ # plot helpers
69
+ "hist_outline", "hist_filled", "data_fit_band", "swatches", "show_palettes",
70
+ # preview
71
+ "show", "preview_in_page", "grayscale_proof",
72
+ # mplstyle on-ramp
73
+ "export_mplstyle", "to_mplstyle_text", "register_mplstyles",
74
+ # lint
75
+ "preflight",
76
+ "__version__",
77
+ ]