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.
Files changed (84) hide show
  1. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/CHANGELOG.md +0 -2
  2. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/PKG-INFO +108 -32
  3. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/README.md +104 -28
  4. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/pyproject.toml +3 -3
  5. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/__init__.py +11 -12
  6. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/_version.py +2 -2
  7. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/accessibility.py +6 -4
  8. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/grayscale.py +2 -1
  9. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/palettes.py +6 -6
  10. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/core/export.py +4 -6
  11. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/core/figure.py +12 -4
  12. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/core/migrate.py +26 -19
  13. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/core/style.py +4 -5
  14. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/integrations/seaborn.py +1 -1
  15. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/preview/gallery.py +1 -1
  16. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/__init__.py +1 -1
  17. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/schema.py +10 -7
  18. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/validation/__init__.py +11 -9
  19. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/validation/checks/dimensions.py +1 -1
  20. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/validation/checks/export.py +17 -0
  21. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/validation/report.py +31 -24
  22. plotstyle-0.1.0a2/tests/test_cli/test_main.py +628 -0
  23. plotstyle-0.1.0a2/tests/test_color/test_accessibility.py +448 -0
  24. plotstyle-0.1.0a2/tests/test_color/test_grayscale.py +536 -0
  25. plotstyle-0.1.0a2/tests/test_color/test_palettes.py +658 -0
  26. plotstyle-0.1.0a2/tests/test_color/test_rendering.py +215 -0
  27. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_core/test_export.py +419 -39
  28. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_core/test_figure.py +313 -70
  29. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_core/test_migrate.py +328 -24
  30. plotstyle-0.1.0a2/tests/test_core/test_style.py +764 -0
  31. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_engine/test_fonts.py +7 -1
  32. plotstyle-0.1.0a2/tests/test_preview/test_gallery.py +897 -0
  33. plotstyle-0.1.0a2/tests/test_preview/test_print_size.py +764 -0
  34. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_specs/test_registry.py +6 -2
  35. plotstyle-0.1.0a2/tests/test_utils/test_io.py +333 -0
  36. plotstyle-0.1.0a2/tests/test_utils/test_warnings.py +235 -0
  37. plotstyle-0.1.0a2/tests/test_validation/test_checks.py +1348 -0
  38. plotstyle-0.1.0a2/tests/test_validation/test_report.py +675 -0
  39. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/.gitignore +0 -0
  40. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/LICENSE +0 -0
  41. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/_utils/__init__.py +0 -0
  42. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/_utils/io.py +0 -0
  43. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/_utils/warnings.py +0 -0
  44. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/cli/__init__.py +0 -0
  45. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/cli/main.py +0 -0
  46. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/__init__.py +0 -0
  47. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/_rendering.py +0 -0
  48. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/data/okabe_ito.json +0 -0
  49. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/data/safe_grayscale.json +0 -0
  50. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/data/tol_bright.json +0 -0
  51. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/data/tol_muted.json +0 -0
  52. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/color/data/tol_vibrant.json +0 -0
  53. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/core/__init__.py +0 -0
  54. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/engine/__init__.py +0 -0
  55. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/engine/fonts.py +0 -0
  56. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/engine/latex.py +0 -0
  57. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/engine/rcparams.py +0 -0
  58. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/integrations/__init__.py +0 -0
  59. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/preview/__init__.py +0 -0
  60. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/preview/print_size.py +0 -0
  61. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/py.typed +0 -0
  62. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/_templates.toml +0 -0
  63. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/acs.toml +0 -0
  64. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/cell.toml +0 -0
  65. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/elsevier.toml +0 -0
  66. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/ieee.toml +0 -0
  67. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/nature.toml +0 -0
  68. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/plos.toml +0 -0
  69. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/prl.toml +0 -0
  70. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/science.toml +0 -0
  71. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/springer.toml +0 -0
  72. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/units.py +0 -0
  73. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/specs/wiley.toml +0 -0
  74. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/validation/checks/__init__.py +0 -0
  75. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/validation/checks/_base.py +0 -0
  76. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/validation/checks/colors.py +0 -0
  77. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/validation/checks/lines.py +0 -0
  78. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/src/plotstyle/validation/checks/typography.py +0 -0
  79. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/conftest.py +0 -0
  80. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_engine/test_latex.py +0 -0
  81. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_engine/test_rcparams.py +0 -0
  82. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_integrations/test_seaborn.py +0 -0
  83. {plotstyle-0.1.0a1 → plotstyle-0.1.0a2}/tests/test_specs/test_schema.py +0 -0
  84. {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.0a1
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 :: 5 - Production/Stable
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.9.19; extra == 'docs'
47
- Requires-Dist: myst-parser<5,>=4.0; extra == 'docs'
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 scientific journals — out of the box.
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 via Matplotlib's `rcParams`.
98
- - **Correctly-sized figures** — `plotstyle.figure()` / `plotstyle.subplots()` create figures at the exact column width and max height specified by each journal.
99
- - **Auto panel labels** — multi-panel figures get **(a)**, **(b)**, **(c)**, … labels placed according to the journal's style rules.
100
- - **Colorblind-safe palettes** — built-in Okabe–Ito, Tol Bright/Vibrant/Muted, and grayscale-safe palettes with optional markers and linestyles.
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** — batch-export to all formats a journal accepts (PDF, EPS, TIFF, …) with font embedding and DPI enforcement.
104
- - **Spec diffing & migration** — compare two journal specs side-by-side; migrate a figure from one journal to another.
105
- - **Seaborn compatibility** — a monkey-patch layer ensures PlotStyle's `rcParams` survive `sns.set_theme()` calls.
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
- ### Extras
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
- `plotstyle.use()` also works as a context manager — `rcParams` are automatically restored on exit:
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
- with plotstyle.use("ieee"):
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
- # rcParams are back to normal here
200
+ finally:
201
+ style.restore() # always restore, even on error
191
202
  ```
192
203
 
193
204
  ---
194
205
 
195
206
  ## Usage
196
207
 
197
- **Multi-panel figures** with auto labels:
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
- fig, axes = plotstyle.subplots("science", nrows=2, ncols=2, columns=2)
201
- # Axes get (a), (b), (c), (d) labels per journal style
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
- **Colorblind-safe palettes** (Okabe–Ito, Tol Bright/Vibrant/Muted, Safe Grayscale):
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
- styled = plotstyle.palette("ieee", n=3, with_markers=True) # [(color, linestyle, marker), ...]
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
- **Validation** — check dimensions, fonts, line weights, colors, and export settings:
240
+ ### Validation
212
241
 
213
242
  ```python
214
243
  report = plotstyle.validate(fig, journal="nature")
215
- print(report.passed) # True / False
216
- print(report.failures) # with fix suggestions
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
- **Submission export** — batch-export to all formats a journal accepts:
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(fig, "figure1", journal="ieee",
223
- author_surname="Kaushal",
224
- output_dir="submission_ieee")
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
- **Accessibility previews**, **spec diffing & migration**, and **Seaborn integration** are also available — see the [`examples/`](examples/) directory for full usage.
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 scientific journals — out of the box.
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 via Matplotlib's `rcParams`.
41
- - **Correctly-sized figures** — `plotstyle.figure()` / `plotstyle.subplots()` create figures at the exact column width and max height specified by each journal.
42
- - **Auto panel labels** — multi-panel figures get **(a)**, **(b)**, **(c)**, … labels placed according to the journal's style rules.
43
- - **Colorblind-safe palettes** — built-in Okabe–Ito, Tol Bright/Vibrant/Muted, and grayscale-safe palettes with optional markers and linestyles.
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** — batch-export to all formats a journal accepts (PDF, EPS, TIFF, …) with font embedding and DPI enforcement.
47
- - **Spec diffing & migration** — compare two journal specs side-by-side; migrate a figure from one journal to another.
48
- - **Seaborn compatibility** — a monkey-patch layer ensures PlotStyle's `rcParams` survive `sns.set_theme()` calls.
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
- ### Extras
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
- `plotstyle.use()` also works as a context manager — `rcParams` are automatically restored on exit:
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
- with plotstyle.use("ieee"):
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
- # rcParams are back to normal here
143
+ finally:
144
+ style.restore() # always restore, even on error
134
145
  ```
135
146
 
136
147
  ---
137
148
 
138
149
  ## Usage
139
150
 
140
- **Multi-panel figures** with auto labels:
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
- fig, axes = plotstyle.subplots("science", nrows=2, ncols=2, columns=2)
144
- # Axes get (a), (b), (c), (d) labels per journal style
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
- **Colorblind-safe palettes** (Okabe–Ito, Tol Bright/Vibrant/Muted, Safe Grayscale):
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
- styled = plotstyle.palette("ieee", n=3, with_markers=True) # [(color, linestyle, marker), ...]
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
- **Validation** — check dimensions, fonts, line weights, colors, and export settings:
183
+ ### Validation
155
184
 
156
185
  ```python
157
186
  report = plotstyle.validate(fig, journal="nature")
158
- print(report.passed) # True / False
159
- print(report.failures) # with fix suggestions
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
- **Submission export** — batch-export to all formats a journal accepts:
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(fig, "figure1", journal="ieee",
166
- author_surname="Kaushal",
167
- output_dir="submission_ieee")
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
- **Accessibility previews**, **spec diffing & migration**, and **Seaborn integration** are also available — see the [`examples/`](examples/) directory for full usage.
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 :: 5 - Production/Stable",
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.9.19",
94
- "myst-parser>=4.0,<5",
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
- >>> fig, ax = plotstyle.subplots(columns=1)
17
- >>> ax.plot([0, 1, 2], [0.2, 0.8, 0.4], color=plotstyle.palette("nature")[0])
18
- >>> ax.set_xlabel("Time (s)")
19
- >>> ax.set_ylabel("Signal (a.u.)")
20
- >>>
21
- >>> report = plotstyle.validate(fig, journal="nature")
22
- >>> print(report)
23
- >>>
24
- >>> plotstyle.savefig(fig, "figure1.pdf")
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.0a1'
22
- __version_tuple__ = version_tuple = (0, 1, 0, 'a1')
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 have exactly three channels
147
- (last dimension != 3) or if its number of dimensions is not 3.
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 :func:`simulate_cvd` if the
228
- rasterised image has an unexpected shape.
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
 
@@ -79,7 +79,8 @@ def rgb_to_luminance(r: float, g: float, b: float) -> float:
79
79
  -------
80
80
  Relative luminance in ``[0, 1]``.
81
81
 
82
- Example:
82
+ Examples
83
+ --------
83
84
  >>> rgb_to_luminance(1.0, 0.0, 0.0) # pure red
84
85
  0.2126
85
86
  >>> rgb_to_luminance(1.0, 1.0, 1.0) # white
@@ -49,9 +49,9 @@ from pathlib import Path
49
49
 
50
50
  _DATA_DIR: Path = Path(__file__).parent / "data"
51
51
 
52
- # Maps lowercase journal identifiers to the palette name whose JSON file lives
53
- # in _DATA_DIR. New journals can be added here without touching any other
54
- # logic in this module.
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 :data:`ColorList` (``list[str]``) of hex colour strings when
204
- *with_markers* is ``False``, or a :data:`StyledColorList`
205
- (``list[tuple[str, str, str]]``) of ``(colour, linestyle, marker)``
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
- # Maps canonical format names to their standard file extensions. Kept as a
52
- # module-level constant so callers can introspect supported formats without
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
- :mod:`plotstyle.specs`. When given, the journal's ``min_dpi``
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
- :mod:`plotstyle.specs`. Used to resolve default formats, apply
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+ produce two-character labels
175
- # (aa, ab, ..., az, ba, ...) to support figures with more than 26 panels.
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
- else:
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: