paperplot-quantum 0.1.0__py3-none-any.whl
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/__init__.py +77 -0
- paperplot/core.py +228 -0
- paperplot/data/fonts/GUST-FONT-LICENSE.txt +29 -0
- paperplot/data/fonts/LICENSE.md +22 -0
- paperplot/data/fonts/texgyreheros-bold.otf +0 -0
- paperplot/data/fonts/texgyreheros-bolditalic.otf +0 -0
- paperplot/data/fonts/texgyreheros-italic.otf +0 -0
- paperplot/data/fonts/texgyreheros-regular.otf +0 -0
- paperplot/data/journals.toml +123 -0
- paperplot/fonts.py +45 -0
- paperplot/journals.py +57 -0
- paperplot/layout.py +133 -0
- paperplot/lint.py +156 -0
- paperplot/mplstyle.py +103 -0
- paperplot/palettes.py +190 -0
- paperplot/plots.py +310 -0
- paperplot/preview.py +250 -0
- paperplot/registry.py +111 -0
- paperplot/save.py +99 -0
- paperplot/style.py +194 -0
- paperplot_quantum-0.1.0.dist-info/METADATA +281 -0
- paperplot_quantum-0.1.0.dist-info/RECORD +27 -0
- paperplot_quantum-0.1.0.dist-info/WHEEL +5 -0
- paperplot_quantum-0.1.0.dist-info/licenses/LICENSE +21 -0
- paperplot_quantum-0.1.0.dist-info/licenses/paperplot/data/fonts/GUST-FONT-LICENSE.txt +29 -0
- paperplot_quantum-0.1.0.dist-info/licenses/paperplot/data/fonts/LICENSE.md +22 -0
- paperplot_quantum-0.1.0.dist-info/top_level.txt +1 -0
paperplot/save.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Saving figures with publication-correct defaults.
|
|
2
|
+
|
|
3
|
+
PDF default, EPS first-class. Fonts embedded (Type-42, no Type-3) for pdf+ps.
|
|
4
|
+
Runs preflight() and surfaces warnings. Warns on alpha in EPS (matplotlib
|
|
5
|
+
rasterizes it) and stamps the spec revision into PDF metadata.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import importlib
|
|
11
|
+
import warnings
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import matplotlib as mpl
|
|
15
|
+
|
|
16
|
+
# pp.style() shadows the style submodule on the package; reach it via sys.modules.
|
|
17
|
+
_style = importlib.import_module("paperplot.style")
|
|
18
|
+
from .lint import preflight as _preflight
|
|
19
|
+
|
|
20
|
+
_VECTOR = {"pdf", "eps", "svg", "ps"}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _color_has_alpha(artist) -> bool:
|
|
24
|
+
"""True if any face/edge color on the artist is RGBA with alpha < 1."""
|
|
25
|
+
for getter in ("get_facecolor", "get_edgecolor"):
|
|
26
|
+
fn = getattr(artist, getter, None)
|
|
27
|
+
if fn is None:
|
|
28
|
+
continue
|
|
29
|
+
try:
|
|
30
|
+
col = fn()
|
|
31
|
+
except Exception:
|
|
32
|
+
continue
|
|
33
|
+
import numpy as np
|
|
34
|
+
arr = np.atleast_2d(np.asarray(col, dtype=float))
|
|
35
|
+
if arr.ndim == 2 and arr.shape[1] == 4 and (arr[:, 3] < 1.0).any():
|
|
36
|
+
return True
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _has_alpha(fig) -> bool:
|
|
41
|
+
# Scan figure-level artists + every axes child for transparency, via the
|
|
42
|
+
# alpha attribute *and* RGBA face/edge colors (fill_between, scatter, etc.).
|
|
43
|
+
artists = [fig.patch, *getattr(fig, "legends", [])]
|
|
44
|
+
for ax in fig.axes:
|
|
45
|
+
artists.extend(ax.get_children())
|
|
46
|
+
for artist in artists:
|
|
47
|
+
a = getattr(artist, "get_alpha", lambda: None)()
|
|
48
|
+
if a is not None and a < 1.0:
|
|
49
|
+
return True
|
|
50
|
+
if _color_has_alpha(artist):
|
|
51
|
+
return True
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def save(fig, path, *, dpi=None, run_preflight=True, journal=None, **savefig_kw):
|
|
56
|
+
"""Save ``fig`` to ``path`` with journal-correct export settings.
|
|
57
|
+
|
|
58
|
+
Format is taken from the extension; no extension defaults to ``.pdf``.
|
|
59
|
+
Returns the preflight :class:`~paperplot.lint.Report` (or None if skipped).
|
|
60
|
+
"""
|
|
61
|
+
spec = _style.resolve_spec(journal)
|
|
62
|
+
p = Path(path)
|
|
63
|
+
fmt = p.suffix.lower().lstrip(".")
|
|
64
|
+
if not fmt:
|
|
65
|
+
p = p.with_suffix(".pdf")
|
|
66
|
+
fmt = "pdf"
|
|
67
|
+
|
|
68
|
+
if fmt in ("eps", "ps") and _has_alpha(fig):
|
|
69
|
+
warnings.warn(
|
|
70
|
+
f"{fmt.upper()} target: figure has alpha<1 artists; matplotlib "
|
|
71
|
+
f"rasterizes transparency in {fmt.upper()}. Use PDF to keep them vector.",
|
|
72
|
+
stacklevel=2,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
report = None
|
|
76
|
+
if run_preflight:
|
|
77
|
+
report = _preflight(fig, spec)
|
|
78
|
+
for f in report.warnings:
|
|
79
|
+
warnings.warn(f"preflight: {f.message}", stacklevel=2)
|
|
80
|
+
|
|
81
|
+
if dpi is None and fmt not in _VECTOR:
|
|
82
|
+
dpi = spec.rasterize_dpi.get("line", 600)
|
|
83
|
+
|
|
84
|
+
metadata = None
|
|
85
|
+
if fmt == "pdf":
|
|
86
|
+
metadata = {"Creator": f"paperplot ({spec.name} {spec.revision})"}
|
|
87
|
+
|
|
88
|
+
kw = dict(facecolor="white")
|
|
89
|
+
if dpi is not None:
|
|
90
|
+
kw["dpi"] = dpi
|
|
91
|
+
if metadata is not None:
|
|
92
|
+
kw["metadata"] = metadata
|
|
93
|
+
kw.update(savefig_kw)
|
|
94
|
+
|
|
95
|
+
# Embed fonts as Type-42 (never Type-3) scoped to this save — don't mutate
|
|
96
|
+
# the caller's global rcParams.
|
|
97
|
+
with mpl.rc_context({"pdf.fonttype": 42, "ps.fonttype": 42}):
|
|
98
|
+
fig.savefig(p, **kw)
|
|
99
|
+
return report
|
paperplot/style.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""rcParams construction and the use/style/reset lifecycle.
|
|
2
|
+
|
|
3
|
+
``rcparams(spec, ...)`` is the core translation layer (spec -> matplotlib rc).
|
|
4
|
+
``use()`` sets a sticky global journal; ``style()`` is a scoped context manager
|
|
5
|
+
(full save/restore); ``reset()`` undoes ``use()``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import contextlib
|
|
11
|
+
import copy
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
import matplotlib as mpl
|
|
15
|
+
from cycler import cycler
|
|
16
|
+
|
|
17
|
+
from . import fonts, palettes
|
|
18
|
+
from .journals import JournalSpec
|
|
19
|
+
from .registry import get_spec
|
|
20
|
+
|
|
21
|
+
_active: Optional[JournalSpec] = None
|
|
22
|
+
_snapshot: Optional[dict] = None
|
|
23
|
+
|
|
24
|
+
# The styling options last set by use(); figure()/style() inherit these unless a
|
|
25
|
+
# call overrides them (pass the kwarg). reset() restores the defaults.
|
|
26
|
+
_DEFAULT_OPTS = {"serif": False, "palette": None, "usetex": False,
|
|
27
|
+
"math": "cm", "font_scale": 1.0}
|
|
28
|
+
_active_opts: dict = dict(_DEFAULT_OPTS)
|
|
29
|
+
|
|
30
|
+
# math= names -> matplotlib mathtext.fontset. Default "cm" gives the Computer
|
|
31
|
+
# Modern look physics figures expect, even with sans-serif text labels.
|
|
32
|
+
_MATH_FONTSET = {
|
|
33
|
+
"cm": "cm", # Computer Modern (LaTeX look) — default
|
|
34
|
+
"stix": "stix", # Times-like math, pairs with serif=True
|
|
35
|
+
"stixsans": "stixsans",
|
|
36
|
+
"sans": "stixsans", # clean sans math that pairs with Arial/Helvetica
|
|
37
|
+
"dejavusans": "dejavusans",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _mathset(math: str, serif: bool) -> str:
|
|
42
|
+
if math == "auto":
|
|
43
|
+
return "cm" if serif else "stixsans"
|
|
44
|
+
return _MATH_FONTSET.get(math, math)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _effective(serif=None, palette=None, usetex=None, math=None,
|
|
48
|
+
font_scale=None) -> dict:
|
|
49
|
+
"""Fill any unspecified (None) option from the active use() options."""
|
|
50
|
+
o = _active_opts
|
|
51
|
+
return {
|
|
52
|
+
"serif": o["serif"] if serif is None else serif,
|
|
53
|
+
"palette": o["palette"] if palette is None else palette,
|
|
54
|
+
"usetex": o["usetex"] if usetex is None else usetex,
|
|
55
|
+
"math": o["math"] if math is None else math,
|
|
56
|
+
"font_scale": o["font_scale"] if font_scale is None else font_scale,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def resolve_spec(journal) -> JournalSpec:
|
|
61
|
+
"""Resolve a journal arg (str / JournalSpec / None) to a spec."""
|
|
62
|
+
if isinstance(journal, JournalSpec):
|
|
63
|
+
return journal
|
|
64
|
+
if journal is not None:
|
|
65
|
+
return get_spec(journal)
|
|
66
|
+
if _active is not None:
|
|
67
|
+
return _active
|
|
68
|
+
raise RuntimeError(
|
|
69
|
+
"No active journal. Call pp.use('aps') first, or pass journal=..."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def rcparams(spec: JournalSpec, *, serif: bool = False, usetex: bool = False,
|
|
74
|
+
palette=None, math: str = "cm", font_scale: float = 1.0) -> dict:
|
|
75
|
+
"""Build the matplotlib rcParams dict for a spec.
|
|
76
|
+
|
|
77
|
+
``math`` selects the mathtext font set (``"cm"``/``"sans"``/``"stix"``/
|
|
78
|
+
``"auto"``); ``font_scale`` multiplies every spec point size (a deliberate
|
|
79
|
+
nudge — preflight still warns below the journal's absolute minimum).
|
|
80
|
+
"""
|
|
81
|
+
fonts.register_bundled()
|
|
82
|
+
fp = spec.font_pt
|
|
83
|
+
s = float(font_scale)
|
|
84
|
+
lw = spec.min_linewidth_pt
|
|
85
|
+
cycle_colors = palettes.resolve_cycle(palette)
|
|
86
|
+
return {
|
|
87
|
+
# fonts
|
|
88
|
+
"font.family": "serif" if serif else "sans-serif",
|
|
89
|
+
"font.sans-serif": list(spec.font_family),
|
|
90
|
+
"font.serif": ["Times", "Nimbus Roman", "DejaVu Serif"],
|
|
91
|
+
"font.size": fp.base * s,
|
|
92
|
+
"axes.labelsize": fp.base * s,
|
|
93
|
+
"axes.titlesize": fp.base * s,
|
|
94
|
+
"xtick.labelsize": fp.tick * s,
|
|
95
|
+
"ytick.labelsize": fp.tick * s,
|
|
96
|
+
"legend.fontsize": fp.legend * s,
|
|
97
|
+
"legend.title_fontsize": fp.base * s,
|
|
98
|
+
"figure.titlesize": fp.base * s,
|
|
99
|
+
"mathtext.fontset": _mathset(math, serif),
|
|
100
|
+
"text.usetex": usetex,
|
|
101
|
+
"axes.unicode_minus": not usetex,
|
|
102
|
+
"axes.formatter.use_mathtext": True,
|
|
103
|
+
# lines / ticks / spines (>= min line weight). Plot lines scale with the
|
|
104
|
+
# journal's min weight so talk targets get thicker strokes for projection
|
|
105
|
+
# while APS/Nature (min 0.5) keep the 1.0 pt publication default.
|
|
106
|
+
"axes.linewidth": max(0.6, lw),
|
|
107
|
+
"lines.linewidth": max(1.0, 2.0 * lw),
|
|
108
|
+
"lines.markersize": max(3.0, 3.0 * lw / 0.5),
|
|
109
|
+
"lines.markeredgewidth": 0.5,
|
|
110
|
+
"patch.linewidth": 0.6,
|
|
111
|
+
"xtick.direction": "in",
|
|
112
|
+
"ytick.direction": "in",
|
|
113
|
+
"xtick.major.width": lw,
|
|
114
|
+
"ytick.major.width": lw,
|
|
115
|
+
"xtick.minor.width": 0.4,
|
|
116
|
+
"ytick.minor.width": 0.4,
|
|
117
|
+
"xtick.major.size": 3.0,
|
|
118
|
+
"ytick.major.size": 3.0,
|
|
119
|
+
"xtick.minor.size": 1.5,
|
|
120
|
+
"ytick.minor.size": 1.5,
|
|
121
|
+
"xtick.major.pad": 3,
|
|
122
|
+
"ytick.major.pad": 3,
|
|
123
|
+
"axes.titlepad": 3,
|
|
124
|
+
"axes.labelpad": 2,
|
|
125
|
+
"axes.xmargin": 0.02,
|
|
126
|
+
"axes.ymargin": 0.02,
|
|
127
|
+
# full box (APS); pp.despine() removes top/right on demand
|
|
128
|
+
"axes.spines.top": True,
|
|
129
|
+
"axes.spines.right": True,
|
|
130
|
+
# color cycle
|
|
131
|
+
"axes.prop_cycle": cycler(color=cycle_colors),
|
|
132
|
+
# figure / save
|
|
133
|
+
"figure.figsize": list(spec.figsize("single")),
|
|
134
|
+
"figure.facecolor": "white",
|
|
135
|
+
"figure.dpi": 150,
|
|
136
|
+
"savefig.dpi": spec.rasterize_dpi.get("line", 600),
|
|
137
|
+
"savefig.bbox": "standard",
|
|
138
|
+
"savefig.pad_inches": 0.01,
|
|
139
|
+
"savefig.transparent": False,
|
|
140
|
+
"pdf.fonttype": 42,
|
|
141
|
+
"ps.fonttype": 42,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def apply(spec: JournalSpec, *, serif=None, palette=None, usetex=None,
|
|
146
|
+
math=None, font_scale=None) -> None:
|
|
147
|
+
"""Update the global rcParams in place (used by use() and figure()).
|
|
148
|
+
|
|
149
|
+
Unspecified (None) options fall back to whatever ``use()`` last set, so
|
|
150
|
+
per-figure calls inherit the active style instead of resetting it.
|
|
151
|
+
"""
|
|
152
|
+
eff = _effective(serif, palette, usetex, math, font_scale)
|
|
153
|
+
mpl.rcParams.update(rcparams(spec, **eff))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def use(journal, *, serif: bool = False, palette=None, usetex: bool = False,
|
|
157
|
+
math: str = "cm", font_scale: float = 1.0) -> JournalSpec:
|
|
158
|
+
"""Set the sticky active journal + style options and apply them globally."""
|
|
159
|
+
global _active, _snapshot, _active_opts
|
|
160
|
+
spec = resolve_spec(journal)
|
|
161
|
+
if _snapshot is None: # remember the pre-paperplot state for reset()
|
|
162
|
+
_snapshot = copy.deepcopy(dict(mpl.rcParams))
|
|
163
|
+
_active_opts = {"serif": serif, "palette": palette, "usetex": usetex,
|
|
164
|
+
"math": math, "font_scale": font_scale}
|
|
165
|
+
apply(spec) # uses the just-stored active options
|
|
166
|
+
_active = spec
|
|
167
|
+
return spec
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def active() -> Optional[JournalSpec]:
|
|
171
|
+
return _active
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def reset() -> None:
|
|
175
|
+
"""Restore rcParams to the state before the first use(), and clear active."""
|
|
176
|
+
global _active, _snapshot, _active_opts
|
|
177
|
+
if _snapshot is not None:
|
|
178
|
+
mpl.rcParams.update(_snapshot)
|
|
179
|
+
_snapshot = None
|
|
180
|
+
_active = None
|
|
181
|
+
_active_opts = dict(_DEFAULT_OPTS)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@contextlib.contextmanager
|
|
185
|
+
def style(journal=None, *, serif=None, palette=None, usetex=None,
|
|
186
|
+
math=None, font_scale=None):
|
|
187
|
+
"""Scoped styling: applies a spec's rcParams, restores fully on exit.
|
|
188
|
+
|
|
189
|
+
Unspecified options inherit the active ``use()`` style (or the defaults).
|
|
190
|
+
"""
|
|
191
|
+
spec = resolve_spec(journal)
|
|
192
|
+
eff = _effective(serif, palette, usetex, math, font_scale)
|
|
193
|
+
with mpl.rc_context(rcparams(spec, **eff)):
|
|
194
|
+
yield spec
|
|
@@ -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,27 @@
|
|
|
1
|
+
paperplot/__init__.py,sha256=g6mN9_2FB48JVAt90sFm0_F1nMP8enEI0ggkdN9Bfv4,2117
|
|
2
|
+
paperplot/core.py,sha256=Br6v9f0n1JgJL3fLKygAeVoKMQsFbVgSxq8wu1zE2FE,8778
|
|
3
|
+
paperplot/fonts.py,sha256=iusWnMj8Mw_lYigF7tR33vBpj2XrR90053z-WCT0RMg,1406
|
|
4
|
+
paperplot/journals.py,sha256=dip0ve29t_w4i9DI3ZC6TpuyFZiJKi8hg78xkAfgdHw,2019
|
|
5
|
+
paperplot/layout.py,sha256=EInObtV5sujqTwBJrMhIzHInvzWpMbo7aLcW_IYo_To,4205
|
|
6
|
+
paperplot/lint.py,sha256=_7Tz_YMTCgzIAhXAAwk42MvPiQLwKy-R1kICOODBxZA,6191
|
|
7
|
+
paperplot/mplstyle.py,sha256=JOdpiqen0eTwaLK4ZG9LIYWX2YzcneVsW7ngkv2UPkc,4330
|
|
8
|
+
paperplot/palettes.py,sha256=Baku2fIvCvJPV8NR01P8h8bpjG3RSI_qdtTHHyrT0iQ,7126
|
|
9
|
+
paperplot/plots.py,sha256=8_y24FVFGzENiaaENdsRjSrTeZo-2c2Xgel24ohozpk,10515
|
|
10
|
+
paperplot/preview.py,sha256=pXg22V9RpqcVo_fvVU9RIgr752GfiC2mrerbVN5Pi-Y,10082
|
|
11
|
+
paperplot/registry.py,sha256=pnuoxDubGe4SGyNDYMtMh5p7VdKrrOLLkNvr413GYVs,3696
|
|
12
|
+
paperplot/save.py,sha256=mBcYIDTiubR3xU7Q-P2zSgDr7Vodt2L1m0siK01jPUk,3241
|
|
13
|
+
paperplot/style.py,sha256=TaWtFQfVR7QMm3oTgFjjTmif4JzAx_UtzYAxYbPJv0w,7217
|
|
14
|
+
paperplot/data/journals.toml,sha256=94wgnEz-j2r9CpJO34midxyWFo-sJCWNxwT1RplJRWw,3802
|
|
15
|
+
paperplot/data/fonts/GUST-FONT-LICENSE.txt,sha256=XrYbuDa7GEXvZocXyxWzgumXdIziYp5DiMxeTD-k5DM,1417
|
|
16
|
+
paperplot/data/fonts/LICENSE.md,sha256=y5G2z6rQEdok7kObwDETdRtExyQD0G5fd-ldtujuMu8,1022
|
|
17
|
+
paperplot/data/fonts/texgyreheros-bold.otf,sha256=sXAWKDX078KIiG3UIxQG3Efhm2FM9EFoNmNVmdRKfWA,135204
|
|
18
|
+
paperplot/data/fonts/texgyreheros-bolditalic.otf,sha256=Fm_G0GjZyZdCgVVcs9cwNlU3qbZ2qyabtRY_WnVJZQU,135264
|
|
19
|
+
paperplot/data/fonts/texgyreheros-italic.otf,sha256=ZHPff6EHs_tL44lzcQr-IrBkDCrAdtUzfPEmvtmqEIw,139208
|
|
20
|
+
paperplot/data/fonts/texgyreheros-regular.otf,sha256=auGgnVqUA2e3qqqR7ovYosMzv-GT5wluI_kxNX1iCB8,133600
|
|
21
|
+
paperplot_quantum-0.1.0.dist-info/licenses/LICENSE,sha256=2bWBItGck5o3t8rMicBPLU6OKklTEG_Af-i3oMiI9SU,1078
|
|
22
|
+
paperplot_quantum-0.1.0.dist-info/licenses/paperplot/data/fonts/GUST-FONT-LICENSE.txt,sha256=XrYbuDa7GEXvZocXyxWzgumXdIziYp5DiMxeTD-k5DM,1417
|
|
23
|
+
paperplot_quantum-0.1.0.dist-info/licenses/paperplot/data/fonts/LICENSE.md,sha256=y5G2z6rQEdok7kObwDETdRtExyQD0G5fd-ldtujuMu8,1022
|
|
24
|
+
paperplot_quantum-0.1.0.dist-info/METADATA,sha256=-SeSEWfyy02WCcaVJke6BGdp4Yu9IGMDj59MBoQ6PkA,12636
|
|
25
|
+
paperplot_quantum-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
26
|
+
paperplot_quantum-0.1.0.dist-info/top_level.txt,sha256=wdNpTgIMXQh8QkN3Cd_oXGMKMYK2ye3vWcowAMfWrCk,10
|
|
27
|
+
paperplot_quantum-0.1.0.dist-info/RECORD,,
|
|
@@ -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,29 @@
|
|
|
1
|
+
% This is version 1.0, dated 22 June 2009, of the GUST Font License.
|
|
2
|
+
% (GUST is the Polish TeX Users Group, https://www.gust.org.pl)
|
|
3
|
+
%
|
|
4
|
+
% For the most recent version of this license see
|
|
5
|
+
% https://www.gust.org.pl/fonts/licenses/GUST-FONT-LICENSE.txt
|
|
6
|
+
% or
|
|
7
|
+
% https://tug.org/fonts/licenses/GUST-FONT-LICENSE.txt
|
|
8
|
+
%
|
|
9
|
+
% This work may be distributed and/or modified under the conditions
|
|
10
|
+
% of the LaTeX Project Public License, either version 1.3c of this
|
|
11
|
+
% license or (at your option) any later version.
|
|
12
|
+
%
|
|
13
|
+
% Please also observe the following clause:
|
|
14
|
+
% 1) it is requested, but not legally required, that derived works be
|
|
15
|
+
% distributed only after changing the names of the fonts comprising this
|
|
16
|
+
% work and given in an accompanying "manifest", and that the
|
|
17
|
+
% files comprising the Work, as listed in the manifest, also be given
|
|
18
|
+
% new names. Any exceptions to this request are also given in the
|
|
19
|
+
% manifest.
|
|
20
|
+
%
|
|
21
|
+
% We recommend the manifest be given in a separate file named
|
|
22
|
+
% MANIFEST-<fontid>.txt, where <fontid> is some unique identification
|
|
23
|
+
% of the font family. If a separate "readme" file accompanies the Work,
|
|
24
|
+
% we recommend a name of the form README-<fontid>.txt.
|
|
25
|
+
%
|
|
26
|
+
% The latest version of the LaTeX Project Public License is in
|
|
27
|
+
% https://www.latex-project.org/lppl.txt and version 1.3c or later
|
|
28
|
+
% is part of all distributions of LaTeX version 2006/05/20 or later.
|
|
29
|
+
|