plotstyle 0.1.0a1__tar.gz → 0.1.0a2__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.
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/CHANGELOG.md +0 -2
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/PKG-INFO +108 -32
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/README.md +104 -28
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/pyproject.toml +3 -3
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/__init__.py +11 -12
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/_version.py +2 -2
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/accessibility.py +6 -4
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/grayscale.py +2 -1
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/palettes.py +6 -6
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/core/export.py +4 -6
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/core/figure.py +12 -4
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/core/migrate.py +26 -19
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/core/style.py +4 -5
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/integrations/seaborn.py +1 -1
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/preview/gallery.py +1 -1
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/__init__.py +1 -1
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/schema.py +10 -7
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/validation/__init__.py +11 -9
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/validation/checks/dimensions.py +1 -1
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/validation/checks/export.py +17 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/validation/report.py +31 -24
- plotstyle-0.1.0a2/tests/test_cli/test_main.py +628 -0
- plotstyle-0.1.0a2/tests/test_color/test_accessibility.py +448 -0
- plotstyle-0.1.0a2/tests/test_color/test_grayscale.py +536 -0
- plotstyle-0.1.0a2/tests/test_color/test_palettes.py +658 -0
- plotstyle-0.1.0a2/tests/test_color/test_rendering.py +215 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_core/test_export.py +419 -39
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_core/test_figure.py +313 -70
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_core/test_migrate.py +328 -24
- plotstyle-0.1.0a2/tests/test_core/test_style.py +764 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_engine/test_fonts.py +7 -1
- plotstyle-0.1.0a2/tests/test_preview/test_gallery.py +897 -0
- plotstyle-0.1.0a2/tests/test_preview/test_print_size.py +764 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_specs/test_registry.py +6 -2
- plotstyle-0.1.0a2/tests/test_utils/test_io.py +333 -0
- plotstyle-0.1.0a2/tests/test_utils/test_warnings.py +235 -0
- plotstyle-0.1.0a2/tests/test_validation/test_checks.py +1348 -0
- plotstyle-0.1.0a2/tests/test_validation/test_report.py +675 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/.gitignore +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/LICENSE +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/_utils/__init__.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/_utils/io.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/_utils/warnings.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/cli/__init__.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/cli/main.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/__init__.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/_rendering.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/data/okabe_ito.json +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/data/safe_grayscale.json +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/data/tol_bright.json +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/data/tol_muted.json +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/data/tol_vibrant.json +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/core/__init__.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/engine/__init__.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/engine/fonts.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/engine/latex.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/engine/rcparams.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/integrations/__init__.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/preview/__init__.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/preview/print_size.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/py.typed +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/_templates.toml +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/acs.toml +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/cell.toml +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/elsevier.toml +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/ieee.toml +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/nature.toml +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/plos.toml +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/prl.toml +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/science.toml +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/springer.toml +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/units.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/wiley.toml +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/validation/checks/__init__.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/validation/checks/_base.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/validation/checks/colors.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/validation/checks/lines.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/validation/checks/typography.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/conftest.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_engine/test_latex.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_engine/test_rcparams.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_integrations/test_seaborn.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_specs/test_schema.py +0 -0
- {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_specs/test_units.py +0 -0
|
@@ -36,7 +36,5 @@ First public alpha release.
|
|
|
36
36
|
- **Dynamic versioning** — version derived from git tags via `hatch-vcs` and `importlib.metadata`.
|
|
37
37
|
- **CI/CD pipeline** — GitHub Actions workflows for lint, type-check, test matrix, and automated PyPI release via OIDC Trusted Publishing.
|
|
38
38
|
|
|
39
|
-
---
|
|
40
|
-
|
|
41
39
|
[Unreleased]: https://github.com/rahulkaushal04/plotstyle/compare/v0.1.0a1...HEAD
|
|
42
40
|
[0.1.0a1]: https://github.com/rahulkaushal04/plotstyle/releases/tag/v0.1.0a1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plotstyle
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a2
|
|
4
4
|
Summary: Matplotlib/seaborn style presets matching scientific journal requirements, with validation, export safety, and preview capabilities.
|
|
5
5
|
Project-URL: Homepage, https://github.com/rahulkaushal04/plotstyle
|
|
6
6
|
Project-URL: Documentation, https://plotstyle.readthedocs.io
|
|
@@ -11,7 +11,7 @@ Author: Rahul Kaushal
|
|
|
11
11
|
License: MIT
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
Keywords: figures,journal,matplotlib,plotting,publication,scientific,seaborn,style
|
|
14
|
-
Classifier: Development Status ::
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
15
|
Classifier: Intended Audience :: Science/Research
|
|
16
16
|
Classifier: License :: OSI Approved :: MIT License
|
|
17
17
|
Classifier: Natural Language :: English
|
|
@@ -43,8 +43,8 @@ Requires-Dist: pytest-mpl<1,>=0.18; extra == 'dev'
|
|
|
43
43
|
Requires-Dist: pytest<10,>=8.3; extra == 'dev'
|
|
44
44
|
Requires-Dist: ruff<1,>=0.15; extra == 'dev'
|
|
45
45
|
Provides-Extra: docs
|
|
46
|
-
Requires-Dist: furo>=2025.
|
|
47
|
-
Requires-Dist: myst-parser<5
|
|
46
|
+
Requires-Dist: furo>=2025.12.19; extra == 'docs'
|
|
47
|
+
Requires-Dist: myst-parser<6,>=5.0; extra == 'docs'
|
|
48
48
|
Requires-Dist: sphinx-autodoc-typehints<4,>=3.0; extra == 'docs'
|
|
49
49
|
Requires-Dist: sphinx-copybutton<1,>=0.5; extra == 'docs'
|
|
50
50
|
Requires-Dist: sphinx<10,>=9.0; extra == 'docs'
|
|
@@ -72,7 +72,9 @@ Description-Content-Type: text/markdown
|
|
|
72
72
|
|
|
73
73
|
---
|
|
74
74
|
|
|
75
|
-
**PlotStyle** configures [Matplotlib](https://matplotlib.org/) (and optionally [Seaborn](https://seaborn.pydata.org/)) so your figures match the typographic, dimensional, and export requirements of major
|
|
75
|
+
**PlotStyle** configures [Matplotlib](https://matplotlib.org/) (and optionally [Seaborn](https://seaborn.pydata.org/)) so your figures match the exact typographic, dimensional, and export requirements of major academic journals — out of the box.
|
|
76
|
+
|
|
77
|
+
Getting a figure accepted often means matching a journal's precise column width, font size range, line weight, DPI, and export format. PlotStyle encodes those requirements as TOML specs and applies them automatically, so you spend time on your science rather than your figure settings.
|
|
76
78
|
|
|
77
79
|
> **Current release:** `v0.1.0a1` (alpha)
|
|
78
80
|
|
|
@@ -94,16 +96,16 @@ Description-Content-Type: text/markdown
|
|
|
94
96
|
|
|
95
97
|
## Features
|
|
96
98
|
|
|
97
|
-
- **One-line journal presets** — `plotstyle.use("nature")` sets fonts, sizes, line widths, and export parameters
|
|
98
|
-
- **Correctly-sized figures** — `plotstyle.figure()`
|
|
99
|
-
- **Auto panel labels** — multi-panel figures get **(a)**, **(b)**, **(c)**, … labels placed according to
|
|
100
|
-
- **Colorblind-safe palettes** — built-in Okabe–Ito, Tol Bright/Vibrant/Muted, and grayscale-safe palettes
|
|
99
|
+
- **One-line journal presets** — `plotstyle.use("nature")` sets fonts, sizes, line widths, and export parameters in Matplotlib's `rcParams`. Wrap it in a `with` block and everything is restored automatically when the block exits.
|
|
100
|
+
- **Correctly-sized figures** — `plotstyle.figure()` and `plotstyle.subplots()` create figures at the exact column width and maximum height specified by each journal.
|
|
101
|
+
- **Auto panel labels** — multi-panel figures get **(a)**, **(b)**, **(c)**, … labels placed and styled according to each journal's conventions.
|
|
102
|
+
- **Colorblind-safe palettes** — built-in Okabe–Ito, Tol Bright/Vibrant/Muted, and grayscale-safe palettes via `plotstyle.palette()`.
|
|
101
103
|
- **Accessibility previews** — simulate deuteranopia, protanopia, and tritanopia; preview grayscale rendering.
|
|
102
|
-
- **Pre-submission validation** — check figure dimensions, font sizes, line weights, color accessibility, and export settings against the target journal's spec.
|
|
103
|
-
- **Submission-ready export** —
|
|
104
|
-
- **Spec diffing & migration** — compare two journal specs side-by-side;
|
|
105
|
-
- **Seaborn compatibility** — a
|
|
106
|
-
- **Typed, schema-validated specs** — journal requirements are stored as TOML files validated by immutable dataclasses.
|
|
104
|
+
- **Pre-submission validation** — check figure dimensions, font sizes, line weights, color accessibility, and export settings against the target journal's spec before you submit.
|
|
105
|
+
- **Submission-ready export** — `plotstyle.savefig()` enforces TrueType font embedding and minimum DPI; `plotstyle.export_submission()` batch-exports to every format the journal requires.
|
|
106
|
+
- **Spec diffing & migration** — compare two journal specs side-by-side; re-target a figure from one journal to another with `plotstyle.migrate()`.
|
|
107
|
+
- **Seaborn compatibility** — a patch layer ensures PlotStyle's `rcParams` survive `sns.set_theme()` calls.
|
|
108
|
+
- **Typed, schema-validated specs** — journal requirements are stored as TOML files validated by immutable typed dataclasses.
|
|
107
109
|
- **CLI** — `plotstyle list`, `plotstyle info`, `plotstyle validate`, and more — no Python script needed.
|
|
108
110
|
|
|
109
111
|
---
|
|
@@ -129,13 +131,13 @@ Description-Content-Type: text/markdown
|
|
|
129
131
|
|
|
130
132
|
## Installation
|
|
131
133
|
|
|
132
|
-
Requires **Python 3.10
|
|
134
|
+
Requires **Python 3.10+** and **Matplotlib ≥ 3.9**.
|
|
133
135
|
|
|
134
136
|
```bash
|
|
135
137
|
pip install plotstyle
|
|
136
138
|
```
|
|
137
139
|
|
|
138
|
-
###
|
|
140
|
+
### Optional extras
|
|
139
141
|
|
|
140
142
|
```bash
|
|
141
143
|
# Colorblind / grayscale preview (needs Pillow)
|
|
@@ -167,7 +169,11 @@ pip install -e ".[dev]"
|
|
|
167
169
|
import numpy as np
|
|
168
170
|
import plotstyle
|
|
169
171
|
|
|
172
|
+
# plotstyle.use() applies the journal's rcParams (fonts, sizes, line widths).
|
|
173
|
+
# The `with` block ensures they are restored automatically when plotting is done.
|
|
170
174
|
with plotstyle.use("nature"):
|
|
175
|
+
# Creates a figure at Nature's exact single-column width (89 mm).
|
|
176
|
+
# Use columns=2 for double-column (full text width).
|
|
171
177
|
fig, ax = plotstyle.figure("nature", columns=1)
|
|
172
178
|
|
|
173
179
|
x = np.linspace(0, 2 * np.pi, 200)
|
|
@@ -177,54 +183,124 @@ with plotstyle.use("nature"):
|
|
|
177
183
|
ax.set_ylabel("Amplitude (a.u.)")
|
|
178
184
|
ax.legend()
|
|
179
185
|
|
|
186
|
+
# Enforces Nature's minimum DPI (300) and embeds TrueType fonts.
|
|
180
187
|
plotstyle.savefig(fig, "quickstart_nature.pdf", journal="nature")
|
|
181
188
|
```
|
|
182
189
|
|
|
183
|
-
`
|
|
190
|
+
The `with` block is the recommended pattern — `rcParams` are always restored on exit, even if an exception occurs inside the block.
|
|
191
|
+
|
|
192
|
+
If you need to manage the style manually:
|
|
184
193
|
|
|
185
194
|
```python
|
|
186
|
-
|
|
195
|
+
style = plotstyle.use("ieee")
|
|
196
|
+
try:
|
|
187
197
|
fig, ax = plotstyle.figure("ieee", columns=1)
|
|
188
198
|
ax.plot([1, 2, 3])
|
|
189
199
|
plotstyle.savefig(fig, "fig_ieee.eps", journal="ieee")
|
|
190
|
-
|
|
200
|
+
finally:
|
|
201
|
+
style.restore() # always restore, even on error
|
|
191
202
|
```
|
|
192
203
|
|
|
193
204
|
---
|
|
194
205
|
|
|
195
206
|
## Usage
|
|
196
207
|
|
|
197
|
-
|
|
208
|
+
### Multi-panel figures
|
|
209
|
+
|
|
210
|
+
`plotstyle.subplots()` works like `plt.subplots()` but sizes the figure to the journal spec and adds panel labels automatically.
|
|
211
|
+
|
|
212
|
+
> **Note:** Unlike `plt.subplots()`, `plotstyle.subplots()` **always** returns a 2-D NumPy array of axes — even for a single panel. Use `axes[0, 0]` to access a single axes, or `axes.flat` to iterate over all panels.
|
|
198
213
|
|
|
199
214
|
```python
|
|
200
|
-
|
|
201
|
-
|
|
215
|
+
import plotstyle
|
|
216
|
+
|
|
217
|
+
with plotstyle.use("science"):
|
|
218
|
+
fig, axes = plotstyle.subplots("science", nrows=2, ncols=2, columns=2)
|
|
219
|
+
# axes has shape (2, 2); each panel is labelled (a), (b), (c), (d)
|
|
220
|
+
for ax in axes.flat:
|
|
221
|
+
ax.plot([1, 2, 3])
|
|
222
|
+
plotstyle.savefig(fig, "multipanel.pdf", journal="science")
|
|
202
223
|
```
|
|
203
224
|
|
|
204
|
-
|
|
225
|
+
Pass `panels=False` to suppress the automatic labels.
|
|
226
|
+
|
|
227
|
+
### Color palettes
|
|
205
228
|
|
|
206
229
|
```python
|
|
230
|
+
# A list of 4 hex color strings from Nature's recommended palette
|
|
207
231
|
colors = plotstyle.palette("nature", n=4)
|
|
208
|
-
|
|
232
|
+
|
|
233
|
+
# With linestyles and markers — useful for accessible line plots
|
|
234
|
+
styled = plotstyle.palette("ieee", n=3, with_markers=True)
|
|
235
|
+
# styled is a list of (color, linestyle, marker) tuples
|
|
236
|
+
for color, ls, marker in styled:
|
|
237
|
+
ax.plot(x, y, color=color, linestyle=ls, marker=marker)
|
|
209
238
|
```
|
|
210
239
|
|
|
211
|
-
|
|
240
|
+
### Validation
|
|
212
241
|
|
|
213
242
|
```python
|
|
214
243
|
report = plotstyle.validate(fig, journal="nature")
|
|
215
|
-
print(report
|
|
216
|
-
print(report.
|
|
244
|
+
print(report) # formatted table of all checks
|
|
245
|
+
print(report.passed) # True if no checks failed
|
|
246
|
+
|
|
247
|
+
for failure in report.failures:
|
|
248
|
+
print(failure.message) # what failed
|
|
249
|
+
print(failure.fix_suggestion) # how to fix it
|
|
217
250
|
```
|
|
218
251
|
|
|
219
|
-
|
|
252
|
+
### Submission export
|
|
253
|
+
|
|
254
|
+
`export_submission()` writes the figure in every format the journal requires (PDF, TIFF, EPS, etc.) and applies journal-specific naming conventions.
|
|
220
255
|
|
|
221
256
|
```python
|
|
222
|
-
plotstyle.export_submission(
|
|
223
|
-
|
|
224
|
-
|
|
257
|
+
paths = plotstyle.export_submission(
|
|
258
|
+
fig,
|
|
259
|
+
"figure1",
|
|
260
|
+
journal="ieee",
|
|
261
|
+
author_surname="Kaushal", # IEEE prepends the first 5 chars of the surname
|
|
262
|
+
output_dir="submission_ieee",
|
|
263
|
+
)
|
|
264
|
+
# Produces: submission_ieee/kaush_figure1.pdf (and any other IEEE-required formats)
|
|
225
265
|
```
|
|
226
266
|
|
|
227
|
-
|
|
267
|
+
### Spec diffing and migration
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
# Compare two journals — useful when retargeting a figure
|
|
271
|
+
result = plotstyle.diff("nature", "science")
|
|
272
|
+
print(result) # aligned two-column table of differences
|
|
273
|
+
|
|
274
|
+
# Re-target a figure to a different journal in place
|
|
275
|
+
plotstyle.migrate(fig, from_journal="nature", to_journal="science")
|
|
276
|
+
plotstyle.savefig(fig, "figure_science.pdf", journal="science")
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Accessibility previews
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
# Simulate how a figure looks under three types of color blindness
|
|
283
|
+
comp = plotstyle.preview_colorblind(fig)
|
|
284
|
+
comp.savefig("colorblind_check.png", dpi=150)
|
|
285
|
+
|
|
286
|
+
# Preview grayscale rendering
|
|
287
|
+
gs = plotstyle.preview_grayscale(fig)
|
|
288
|
+
gs.savefig("grayscale_check.png", dpi=150)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Seaborn integration
|
|
292
|
+
|
|
293
|
+
`sns.set_theme()` normally overwrites the rcParams that PlotStyle set. Pass `seaborn_compatible=True` to prevent that:
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
import seaborn as sns
|
|
297
|
+
import plotstyle
|
|
298
|
+
|
|
299
|
+
with plotstyle.use("nature", seaborn_compatible=True):
|
|
300
|
+
fig, ax = plotstyle.figure("nature", columns=1)
|
|
301
|
+
sns.lineplot(x=[1, 2, 3], y=[4, 5, 6], ax=ax)
|
|
302
|
+
plotstyle.savefig(fig, "seaborn_figure.pdf", journal="nature")
|
|
303
|
+
```
|
|
228
304
|
|
|
229
305
|
---
|
|
230
306
|
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
**PlotStyle** configures [Matplotlib](https://matplotlib.org/) (and optionally [Seaborn](https://seaborn.pydata.org/)) so your figures match the typographic, dimensional, and export requirements of major
|
|
18
|
+
**PlotStyle** configures [Matplotlib](https://matplotlib.org/) (and optionally [Seaborn](https://seaborn.pydata.org/)) so your figures match the exact typographic, dimensional, and export requirements of major academic journals — out of the box.
|
|
19
|
+
|
|
20
|
+
Getting a figure accepted often means matching a journal's precise column width, font size range, line weight, DPI, and export format. PlotStyle encodes those requirements as TOML specs and applies them automatically, so you spend time on your science rather than your figure settings.
|
|
19
21
|
|
|
20
22
|
> **Current release:** `v0.1.0a1` (alpha)
|
|
21
23
|
|
|
@@ -37,16 +39,16 @@
|
|
|
37
39
|
|
|
38
40
|
## Features
|
|
39
41
|
|
|
40
|
-
- **One-line journal presets** — `plotstyle.use("nature")` sets fonts, sizes, line widths, and export parameters
|
|
41
|
-
- **Correctly-sized figures** — `plotstyle.figure()`
|
|
42
|
-
- **Auto panel labels** — multi-panel figures get **(a)**, **(b)**, **(c)**, … labels placed according to
|
|
43
|
-
- **Colorblind-safe palettes** — built-in Okabe–Ito, Tol Bright/Vibrant/Muted, and grayscale-safe palettes
|
|
42
|
+
- **One-line journal presets** — `plotstyle.use("nature")` sets fonts, sizes, line widths, and export parameters in Matplotlib's `rcParams`. Wrap it in a `with` block and everything is restored automatically when the block exits.
|
|
43
|
+
- **Correctly-sized figures** — `plotstyle.figure()` and `plotstyle.subplots()` create figures at the exact column width and maximum height specified by each journal.
|
|
44
|
+
- **Auto panel labels** — multi-panel figures get **(a)**, **(b)**, **(c)**, … labels placed and styled according to each journal's conventions.
|
|
45
|
+
- **Colorblind-safe palettes** — built-in Okabe–Ito, Tol Bright/Vibrant/Muted, and grayscale-safe palettes via `plotstyle.palette()`.
|
|
44
46
|
- **Accessibility previews** — simulate deuteranopia, protanopia, and tritanopia; preview grayscale rendering.
|
|
45
|
-
- **Pre-submission validation** — check figure dimensions, font sizes, line weights, color accessibility, and export settings against the target journal's spec.
|
|
46
|
-
- **Submission-ready export** —
|
|
47
|
-
- **Spec diffing & migration** — compare two journal specs side-by-side;
|
|
48
|
-
- **Seaborn compatibility** — a
|
|
49
|
-
- **Typed, schema-validated specs** — journal requirements are stored as TOML files validated by immutable dataclasses.
|
|
47
|
+
- **Pre-submission validation** — check figure dimensions, font sizes, line weights, color accessibility, and export settings against the target journal's spec before you submit.
|
|
48
|
+
- **Submission-ready export** — `plotstyle.savefig()` enforces TrueType font embedding and minimum DPI; `plotstyle.export_submission()` batch-exports to every format the journal requires.
|
|
49
|
+
- **Spec diffing & migration** — compare two journal specs side-by-side; re-target a figure from one journal to another with `plotstyle.migrate()`.
|
|
50
|
+
- **Seaborn compatibility** — a patch layer ensures PlotStyle's `rcParams` survive `sns.set_theme()` calls.
|
|
51
|
+
- **Typed, schema-validated specs** — journal requirements are stored as TOML files validated by immutable typed dataclasses.
|
|
50
52
|
- **CLI** — `plotstyle list`, `plotstyle info`, `plotstyle validate`, and more — no Python script needed.
|
|
51
53
|
|
|
52
54
|
---
|
|
@@ -72,13 +74,13 @@
|
|
|
72
74
|
|
|
73
75
|
## Installation
|
|
74
76
|
|
|
75
|
-
Requires **Python 3.10
|
|
77
|
+
Requires **Python 3.10+** and **Matplotlib ≥ 3.9**.
|
|
76
78
|
|
|
77
79
|
```bash
|
|
78
80
|
pip install plotstyle
|
|
79
81
|
```
|
|
80
82
|
|
|
81
|
-
###
|
|
83
|
+
### Optional extras
|
|
82
84
|
|
|
83
85
|
```bash
|
|
84
86
|
# Colorblind / grayscale preview (needs Pillow)
|
|
@@ -110,7 +112,11 @@ pip install -e ".[dev]"
|
|
|
110
112
|
import numpy as np
|
|
111
113
|
import plotstyle
|
|
112
114
|
|
|
115
|
+
# plotstyle.use() applies the journal's rcParams (fonts, sizes, line widths).
|
|
116
|
+
# The `with` block ensures they are restored automatically when plotting is done.
|
|
113
117
|
with plotstyle.use("nature"):
|
|
118
|
+
# Creates a figure at Nature's exact single-column width (89 mm).
|
|
119
|
+
# Use columns=2 for double-column (full text width).
|
|
114
120
|
fig, ax = plotstyle.figure("nature", columns=1)
|
|
115
121
|
|
|
116
122
|
x = np.linspace(0, 2 * np.pi, 200)
|
|
@@ -120,54 +126,124 @@ with plotstyle.use("nature"):
|
|
|
120
126
|
ax.set_ylabel("Amplitude (a.u.)")
|
|
121
127
|
ax.legend()
|
|
122
128
|
|
|
129
|
+
# Enforces Nature's minimum DPI (300) and embeds TrueType fonts.
|
|
123
130
|
plotstyle.savefig(fig, "quickstart_nature.pdf", journal="nature")
|
|
124
131
|
```
|
|
125
132
|
|
|
126
|
-
`
|
|
133
|
+
The `with` block is the recommended pattern — `rcParams` are always restored on exit, even if an exception occurs inside the block.
|
|
134
|
+
|
|
135
|
+
If you need to manage the style manually:
|
|
127
136
|
|
|
128
137
|
```python
|
|
129
|
-
|
|
138
|
+
style = plotstyle.use("ieee")
|
|
139
|
+
try:
|
|
130
140
|
fig, ax = plotstyle.figure("ieee", columns=1)
|
|
131
141
|
ax.plot([1, 2, 3])
|
|
132
142
|
plotstyle.savefig(fig, "fig_ieee.eps", journal="ieee")
|
|
133
|
-
|
|
143
|
+
finally:
|
|
144
|
+
style.restore() # always restore, even on error
|
|
134
145
|
```
|
|
135
146
|
|
|
136
147
|
---
|
|
137
148
|
|
|
138
149
|
## Usage
|
|
139
150
|
|
|
140
|
-
|
|
151
|
+
### Multi-panel figures
|
|
152
|
+
|
|
153
|
+
`plotstyle.subplots()` works like `plt.subplots()` but sizes the figure to the journal spec and adds panel labels automatically.
|
|
154
|
+
|
|
155
|
+
> **Note:** Unlike `plt.subplots()`, `plotstyle.subplots()` **always** returns a 2-D NumPy array of axes — even for a single panel. Use `axes[0, 0]` to access a single axes, or `axes.flat` to iterate over all panels.
|
|
141
156
|
|
|
142
157
|
```python
|
|
143
|
-
|
|
144
|
-
|
|
158
|
+
import plotstyle
|
|
159
|
+
|
|
160
|
+
with plotstyle.use("science"):
|
|
161
|
+
fig, axes = plotstyle.subplots("science", nrows=2, ncols=2, columns=2)
|
|
162
|
+
# axes has shape (2, 2); each panel is labelled (a), (b), (c), (d)
|
|
163
|
+
for ax in axes.flat:
|
|
164
|
+
ax.plot([1, 2, 3])
|
|
165
|
+
plotstyle.savefig(fig, "multipanel.pdf", journal="science")
|
|
145
166
|
```
|
|
146
167
|
|
|
147
|
-
|
|
168
|
+
Pass `panels=False` to suppress the automatic labels.
|
|
169
|
+
|
|
170
|
+
### Color palettes
|
|
148
171
|
|
|
149
172
|
```python
|
|
173
|
+
# A list of 4 hex color strings from Nature's recommended palette
|
|
150
174
|
colors = plotstyle.palette("nature", n=4)
|
|
151
|
-
|
|
175
|
+
|
|
176
|
+
# With linestyles and markers — useful for accessible line plots
|
|
177
|
+
styled = plotstyle.palette("ieee", n=3, with_markers=True)
|
|
178
|
+
# styled is a list of (color, linestyle, marker) tuples
|
|
179
|
+
for color, ls, marker in styled:
|
|
180
|
+
ax.plot(x, y, color=color, linestyle=ls, marker=marker)
|
|
152
181
|
```
|
|
153
182
|
|
|
154
|
-
|
|
183
|
+
### Validation
|
|
155
184
|
|
|
156
185
|
```python
|
|
157
186
|
report = plotstyle.validate(fig, journal="nature")
|
|
158
|
-
print(report
|
|
159
|
-
print(report.
|
|
187
|
+
print(report) # formatted table of all checks
|
|
188
|
+
print(report.passed) # True if no checks failed
|
|
189
|
+
|
|
190
|
+
for failure in report.failures:
|
|
191
|
+
print(failure.message) # what failed
|
|
192
|
+
print(failure.fix_suggestion) # how to fix it
|
|
160
193
|
```
|
|
161
194
|
|
|
162
|
-
|
|
195
|
+
### Submission export
|
|
196
|
+
|
|
197
|
+
`export_submission()` writes the figure in every format the journal requires (PDF, TIFF, EPS, etc.) and applies journal-specific naming conventions.
|
|
163
198
|
|
|
164
199
|
```python
|
|
165
|
-
plotstyle.export_submission(
|
|
166
|
-
|
|
167
|
-
|
|
200
|
+
paths = plotstyle.export_submission(
|
|
201
|
+
fig,
|
|
202
|
+
"figure1",
|
|
203
|
+
journal="ieee",
|
|
204
|
+
author_surname="Kaushal", # IEEE prepends the first 5 chars of the surname
|
|
205
|
+
output_dir="submission_ieee",
|
|
206
|
+
)
|
|
207
|
+
# Produces: submission_ieee/kaush_figure1.pdf (and any other IEEE-required formats)
|
|
168
208
|
```
|
|
169
209
|
|
|
170
|
-
|
|
210
|
+
### Spec diffing and migration
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
# Compare two journals — useful when retargeting a figure
|
|
214
|
+
result = plotstyle.diff("nature", "science")
|
|
215
|
+
print(result) # aligned two-column table of differences
|
|
216
|
+
|
|
217
|
+
# Re-target a figure to a different journal in place
|
|
218
|
+
plotstyle.migrate(fig, from_journal="nature", to_journal="science")
|
|
219
|
+
plotstyle.savefig(fig, "figure_science.pdf", journal="science")
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Accessibility previews
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
# Simulate how a figure looks under three types of color blindness
|
|
226
|
+
comp = plotstyle.preview_colorblind(fig)
|
|
227
|
+
comp.savefig("colorblind_check.png", dpi=150)
|
|
228
|
+
|
|
229
|
+
# Preview grayscale rendering
|
|
230
|
+
gs = plotstyle.preview_grayscale(fig)
|
|
231
|
+
gs.savefig("grayscale_check.png", dpi=150)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Seaborn integration
|
|
235
|
+
|
|
236
|
+
`sns.set_theme()` normally overwrites the rcParams that PlotStyle set. Pass `seaborn_compatible=True` to prevent that:
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
import seaborn as sns
|
|
240
|
+
import plotstyle
|
|
241
|
+
|
|
242
|
+
with plotstyle.use("nature", seaborn_compatible=True):
|
|
243
|
+
fig, ax = plotstyle.figure("nature", columns=1)
|
|
244
|
+
sns.lineplot(x=[1, 2, 3], y=[4, 5, 6], ax=ax)
|
|
245
|
+
plotstyle.savefig(fig, "seaborn_figure.pdf", journal="nature")
|
|
246
|
+
```
|
|
171
247
|
|
|
172
248
|
---
|
|
173
249
|
|
|
@@ -33,7 +33,7 @@ keywords = [
|
|
|
33
33
|
"style",
|
|
34
34
|
]
|
|
35
35
|
classifiers = [
|
|
36
|
-
"Development Status ::
|
|
36
|
+
"Development Status :: 3 - Alpha",
|
|
37
37
|
"Intended Audience :: Science/Research",
|
|
38
38
|
"License :: OSI Approved :: MIT License",
|
|
39
39
|
"Natural Language :: English",
|
|
@@ -90,8 +90,8 @@ dev = [
|
|
|
90
90
|
# ── Documentation build ───────────────────────────────────────────────────────
|
|
91
91
|
docs = [
|
|
92
92
|
"sphinx>=9.0,<10",
|
|
93
|
-
"furo>=2025.
|
|
94
|
-
"myst-parser>=
|
|
93
|
+
"furo>=2025.12.19",
|
|
94
|
+
"myst-parser>=5.0,<6",
|
|
95
95
|
"sphinx-autodoc-typehints>=3.0,<4",
|
|
96
96
|
"sphinx-copybutton>=0.5,<1",
|
|
97
97
|
]
|
|
@@ -8,20 +8,19 @@ validation, and submission-ready export.
|
|
|
8
8
|
|
|
9
9
|
Quick start
|
|
10
10
|
-----------
|
|
11
|
-
>>> import matplotlib.pyplot as plt
|
|
12
11
|
>>> import plotstyle
|
|
13
12
|
>>>
|
|
14
|
-
>>> plotstyle.use("nature")
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
>>> with plotstyle.use("nature") as style:
|
|
14
|
+
... fig, ax = plotstyle.figure("nature", columns=1)
|
|
15
|
+
... ax.plot([0, 1, 2], [0.2, 0.8, 0.4], color=plotstyle.palette("nature")[0])
|
|
16
|
+
... ax.set_xlabel("Time (s)")
|
|
17
|
+
... ax.set_ylabel("Signal (a.u.)")
|
|
18
|
+
...
|
|
19
|
+
... report = plotstyle.validate(fig, journal="nature")
|
|
20
|
+
... print(report)
|
|
21
|
+
...
|
|
22
|
+
... plotstyle.savefig(fig, "figure1.pdf", journal="nature")
|
|
23
|
+
... # rcParams are restored automatically on exit
|
|
25
24
|
|
|
26
25
|
Package layout
|
|
27
26
|
--------------
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.1.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1, 0, '
|
|
21
|
+
__version__ = version = '0.1.0a2'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0, 'a2')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -143,8 +143,9 @@ def simulate_cvd(
|
|
|
143
143
|
|
|
144
144
|
Raises
|
|
145
145
|
------
|
|
146
|
-
CVDSimulationError: If *image* does not
|
|
147
|
-
(last dimension != 3) or if its number
|
|
146
|
+
plotstyle.color.accessibility.CVDSimulationError: If *image* does not
|
|
147
|
+
have exactly three channels (last dimension != 3) or if its number
|
|
148
|
+
of dimensions is not 3.
|
|
148
149
|
|
|
149
150
|
Example:
|
|
150
151
|
>>> import numpy as np
|
|
@@ -224,8 +225,9 @@ def preview_colorblind(
|
|
|
224
225
|
|
|
225
226
|
Raises
|
|
226
227
|
------
|
|
227
|
-
CVDSimulationError: Propagated from
|
|
228
|
-
rasterised image has an unexpected
|
|
228
|
+
plotstyle.color.accessibility.CVDSimulationError: Propagated from
|
|
229
|
+
:func:`simulate_cvd` if the rasterised image has an unexpected
|
|
230
|
+
shape.
|
|
229
231
|
AttributeError: If *fig*'s canvas does not support ``buffer_rgba``
|
|
230
232
|
(non-Agg backends).
|
|
231
233
|
|
|
@@ -49,9 +49,9 @@ from pathlib import Path
|
|
|
49
49
|
|
|
50
50
|
_DATA_DIR: Path = Path(__file__).parent / "data"
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
#: Maps lowercase journal identifiers to the palette name whose JSON file
|
|
53
|
+
#: lives in ``_DATA_DIR``. New journals can be added here without touching
|
|
54
|
+
#: any other logic in this module.
|
|
55
55
|
JOURNAL_PALETTE_MAP: dict[str, str] = {
|
|
56
56
|
"acs": "tol_bright",
|
|
57
57
|
"cell": "okabe_ito",
|
|
@@ -200,9 +200,9 @@ def palette(
|
|
|
200
200
|
|
|
201
201
|
Returns
|
|
202
202
|
-------
|
|
203
|
-
A
|
|
204
|
-
*with_markers* is ``False``, or a
|
|
205
|
-
|
|
203
|
+
A ``list[str]`` of hex colour strings when
|
|
204
|
+
*with_markers* is ``False``, or a ``list[tuple[str, str, str]]``
|
|
205
|
+
of ``(colour, linestyle, marker)``
|
|
206
206
|
tuples when *with_markers* is ``True``.
|
|
207
207
|
|
|
208
208
|
Raises
|
|
@@ -48,10 +48,8 @@ __all__: list[str] = [
|
|
|
48
48
|
# Module-level constants
|
|
49
49
|
# ---------------------------------------------------------------------------
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
# instantiating any objects. The dict is typed Final to signal that it must
|
|
54
|
-
# not be mutated at runtime.
|
|
51
|
+
#: Maps canonical format names to their standard file extensions.
|
|
52
|
+
#: The dict is typed ``Final`` to signal that it must not be mutated at runtime.
|
|
55
53
|
FORMAT_EXTENSIONS: Final[dict[str, str]] = {
|
|
56
54
|
"pdf": ".pdf",
|
|
57
55
|
"eps": ".eps",
|
|
@@ -237,7 +235,7 @@ def savefig(
|
|
|
237
235
|
path: Output file path. The file extension determines the format
|
|
238
236
|
unless *format* is also supplied via *kwargs*.
|
|
239
237
|
journal: Optional journal preset name registered with
|
|
240
|
-
|
|
238
|
+
the spec registry. When given, the journal's ``min_dpi``
|
|
241
239
|
overrides ``savefig.dpi`` for this call.
|
|
242
240
|
**kwargs: Additional keyword arguments forwarded verbatim to
|
|
243
241
|
:meth:`~matplotlib.figure.Figure.savefig`. ``bbox_inches``
|
|
@@ -335,7 +333,7 @@ def export_submission(
|
|
|
335
333
|
formats: Explicit list of output format keys (e.g. ``["pdf", "tiff"]``).
|
|
336
334
|
Overrides the journal spec's preferred formats when supplied.
|
|
337
335
|
journal: Optional journal preset name registered with
|
|
338
|
-
|
|
336
|
+
the spec registry. Used to resolve default formats, apply
|
|
339
337
|
DPI constraints, and select naming conventions.
|
|
340
338
|
output_dir: Directory into which all output files are written.
|
|
341
339
|
Created (including any missing parents) if it does not exist.
|
|
@@ -146,13 +146,18 @@ def _format_panel_label(index: int, spec: JournalSpec) -> str:
|
|
|
146
146
|
Args:
|
|
147
147
|
index: Zero-based panel index. Index ``0`` maps to the letter
|
|
148
148
|
``"a"`` (or its styled equivalent), index ``1`` to ``"b"``,
|
|
149
|
-
and so on.
|
|
149
|
+
and so on. Valid range is ``0`` to ``701`` inclusive.
|
|
150
150
|
spec: Journal specification containing panel label formatting rules.
|
|
151
151
|
|
|
152
152
|
Returns
|
|
153
153
|
-------
|
|
154
154
|
Formatted panel label string (e.g. ``"a"``, ``"(B)"``, ``"A"``).
|
|
155
155
|
|
|
156
|
+
Raises
|
|
157
|
+
------
|
|
158
|
+
ValueError: If *index* is >= 702 (beyond the two-character ``"zz"``
|
|
159
|
+
label).
|
|
160
|
+
|
|
156
161
|
Notes
|
|
157
162
|
-----
|
|
158
163
|
Supported ``panel_label_case`` values and their output:
|
|
@@ -171,13 +176,16 @@ def _format_panel_label(index: int, spec: JournalSpec) -> str:
|
|
|
171
176
|
Any unrecognised value falls back to ``"lower"``.
|
|
172
177
|
"""
|
|
173
178
|
# Derive the base lowercase letter(s) from the zero-based index.
|
|
174
|
-
# Indices 0-25 map to a-z; indices 26
|
|
175
|
-
# (aa, ab, ...,
|
|
179
|
+
# Indices 0-25 map to a-z; indices 26-701 produce two-character labels
|
|
180
|
+
# (aa, ab, ..., zz). Index 702+ would overflow beyond 'z' in the first
|
|
181
|
+
# character, so we reject it explicitly.
|
|
176
182
|
if index < 26:
|
|
177
183
|
letter: str = chr(ord("a") + index)
|
|
178
|
-
|
|
184
|
+
elif index < 702:
|
|
179
185
|
i = index - 26
|
|
180
186
|
letter = chr(ord("a") + i // 26) + chr(ord("a") + i % 26)
|
|
187
|
+
else:
|
|
188
|
+
raise ValueError(f"Panel index {index} exceeds the maximum supported label range (0-701).")
|
|
181
189
|
case: str = spec.typography.panel_label_case
|
|
182
190
|
|
|
183
191
|
match case:
|