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.
- paperplot_quantum-0.1.0/LICENSE +21 -0
- paperplot_quantum-0.1.0/PKG-INFO +281 -0
- paperplot_quantum-0.1.0/README.md +237 -0
- paperplot_quantum-0.1.0/paperplot/__init__.py +77 -0
- paperplot_quantum-0.1.0/paperplot/core.py +228 -0
- paperplot_quantum-0.1.0/paperplot/data/fonts/GUST-FONT-LICENSE.txt +29 -0
- paperplot_quantum-0.1.0/paperplot/data/fonts/LICENSE.md +22 -0
- paperplot_quantum-0.1.0/paperplot/data/fonts/texgyreheros-bold.otf +0 -0
- paperplot_quantum-0.1.0/paperplot/data/fonts/texgyreheros-bolditalic.otf +0 -0
- paperplot_quantum-0.1.0/paperplot/data/fonts/texgyreheros-italic.otf +0 -0
- paperplot_quantum-0.1.0/paperplot/data/fonts/texgyreheros-regular.otf +0 -0
- paperplot_quantum-0.1.0/paperplot/data/journals.toml +123 -0
- paperplot_quantum-0.1.0/paperplot/fonts.py +45 -0
- paperplot_quantum-0.1.0/paperplot/journals.py +57 -0
- paperplot_quantum-0.1.0/paperplot/layout.py +133 -0
- paperplot_quantum-0.1.0/paperplot/lint.py +156 -0
- paperplot_quantum-0.1.0/paperplot/mplstyle.py +103 -0
- paperplot_quantum-0.1.0/paperplot/palettes.py +190 -0
- paperplot_quantum-0.1.0/paperplot/plots.py +310 -0
- paperplot_quantum-0.1.0/paperplot/preview.py +250 -0
- paperplot_quantum-0.1.0/paperplot/registry.py +111 -0
- paperplot_quantum-0.1.0/paperplot/save.py +99 -0
- paperplot_quantum-0.1.0/paperplot/style.py +194 -0
- paperplot_quantum-0.1.0/paperplot_quantum.egg-info/PKG-INFO +281 -0
- paperplot_quantum-0.1.0/paperplot_quantum.egg-info/SOURCES.txt +32 -0
- paperplot_quantum-0.1.0/paperplot_quantum.egg-info/dependency_links.txt +1 -0
- paperplot_quantum-0.1.0/paperplot_quantum.egg-info/requires.txt +21 -0
- paperplot_quantum-0.1.0/paperplot_quantum.egg-info/top_level.txt +1 -0
- paperplot_quantum-0.1.0/pyproject.toml +61 -0
- paperplot_quantum-0.1.0/setup.cfg +4 -0
- paperplot_quantum-0.1.0/tests/test_fonts.py +35 -0
- paperplot_quantum-0.1.0/tests/test_paperplot.py +307 -0
- paperplot_quantum-0.1.0/tests/test_plots.py +154 -0
- 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
|
+
[](https://github.com/zlatko-minev/paperplot/actions/workflows/ci.yml)
|
|
48
|
+
[](https://zlatko-minev.github.io/paperplot/)
|
|
49
|
+
[](https://www.python.org/)
|
|
50
|
+
[](LICENSE)
|
|
51
|
+
[](https://colab.research.google.com/github/zlatko-minev/paperplot/blob/main/docs/showcase.ipynb)
|
|
52
|
+
[](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
|
+
[](https://github.com/zlatko-minev/paperplot/actions/workflows/ci.yml)
|
|
4
|
+
[](https://zlatko-minev.github.io/paperplot/)
|
|
5
|
+
[](https://www.python.org/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://colab.research.google.com/github/zlatko-minev/paperplot/blob/main/docs/showcase.ipynb)
|
|
8
|
+
[](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
|
+
]
|