plotastro 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. plotastro-1.0.0/.github/workflows/ci.yml +48 -0
  2. plotastro-1.0.0/.github/workflows/publish.yml +44 -0
  3. plotastro-1.0.0/.gitignore +8 -0
  4. plotastro-1.0.0/CHANGELOG.md +48 -0
  5. plotastro-1.0.0/LICENSE +23 -0
  6. plotastro-1.0.0/PKG-INFO +432 -0
  7. plotastro-1.0.0/README.md +408 -0
  8. plotastro-1.0.0/examples/authors_example.csv +7 -0
  9. plotastro-1.0.0/examples/figures/cvd_check.png +0 -0
  10. plotastro-1.0.0/examples/figures/example_column.png +0 -0
  11. plotastro-1.0.0/examples/figures/example_full.png +0 -0
  12. plotastro-1.0.0/examples/figures/linestyles.png +0 -0
  13. plotastro-1.0.0/examples/figures/markers.png +0 -0
  14. plotastro-1.0.0/examples/figures/palette.png +0 -0
  15. plotastro-1.0.0/examples/figures/palette_okabe_ito.png +0 -0
  16. plotastro-1.0.0/examples/figures/redundant_encoding.png +0 -0
  17. plotastro-1.0.0/examples/make_reference_figures.py +90 -0
  18. plotastro-1.0.0/examples/tutorial.ipynb +1281 -0
  19. plotastro-1.0.0/pyproject.toml +42 -0
  20. plotastro-1.0.0/requirements-dev.txt +5 -0
  21. plotastro-1.0.0/requirements.txt +3 -0
  22. plotastro-1.0.0/src/plotastro/__init__.py +68 -0
  23. plotastro-1.0.0/src/plotastro/_authors.py +302 -0
  24. plotastro-1.0.0/src/plotastro/_colors.py +227 -0
  25. plotastro-1.0.0/src/plotastro/_core.py +256 -0
  26. plotastro-1.0.0/src/plotastro/_extras.py +187 -0
  27. plotastro-1.0.0/src/plotastro/styles/aanda.mplstyle +101 -0
  28. plotastro-1.0.0/src/plotastro/styles/apj.mplstyle +101 -0
  29. plotastro-1.0.0/src/plotastro/styles/jcap.mplstyle +100 -0
  30. plotastro-1.0.0/src/plotastro/styles/mnras.mplstyle +101 -0
  31. plotastro-1.0.0/src/plotastro/styles/natastro.mplstyle +102 -0
  32. plotastro-1.0.0/src/plotastro/styles/oja.mplstyle +102 -0
  33. plotastro-1.0.0/src/plotastro/styles/prd.mplstyle +101 -0
  34. plotastro-1.0.0/src/plotastro/styles/rasti.mplstyle +101 -0
  35. plotastro-1.0.0/tests/conftest.py +13 -0
  36. plotastro-1.0.0/tests/test_authors.py +144 -0
  37. plotastro-1.0.0/tests/test_colors.py +64 -0
  38. plotastro-1.0.0/tests/test_extras.py +55 -0
  39. plotastro-1.0.0/tests/test_sizing.py +74 -0
  40. plotastro-1.0.0/tests/test_styles.py +69 -0
  41. plotastro-1.0.0/tools/generate_styles.py +202 -0
@@ -0,0 +1,48 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, master]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest]
16
+ python-version: ["3.9", "3.11", "3.13"]
17
+ numpy: [""]
18
+ include:
19
+ - os: macos-latest
20
+ python-version: "3.12"
21
+ - os: windows-latest
22
+ python-version: "3.12"
23
+ # plotastro supports both NumPy major versions — test each explicitly
24
+ - os: ubuntu-latest
25
+ python-version: "3.11"
26
+ numpy: "numpy<2"
27
+ - os: ubuntu-latest
28
+ python-version: "3.12"
29
+ numpy: "numpy>=2"
30
+ steps:
31
+ - uses: actions/checkout@v4
32
+ - uses: actions/setup-python@v5
33
+ with:
34
+ python-version: ${{ matrix.python-version }}
35
+ - name: Install package with test dependencies
36
+ shell: bash
37
+ run: |
38
+ python -m pip install ".[dev]"
39
+ if [ -n "${{ matrix.numpy }}" ]; then
40
+ python -m pip install "${{ matrix.numpy }}"
41
+ fi
42
+ - name: Run tests
43
+ run: pytest -v
44
+ - name: Check style files are in sync with the generator
45
+ if: runner.os == 'Linux'
46
+ run: |
47
+ python tools/generate_styles.py
48
+ git diff --exit-code src/plotastro/styles/
@@ -0,0 +1,44 @@
1
+ # Publishes to PyPI when you push a version tag, e.g.:
2
+ # git tag v1.0.0 && git push origin v1.0.0
3
+ #
4
+ # One-time setup (no secrets needed — uses PyPI "trusted publishing"):
5
+ # 1. Create the project on https://pypi.org/manage/account/publishing/
6
+ # ("Add a new pending publisher"): project name `plotastro`,
7
+ # this repository, workflow file `publish.yml`, environment `pypi`.
8
+ # 2. In the GitHub repo settings, create an environment named `pypi`.
9
+
10
+ name: Publish to PyPI
11
+
12
+ on:
13
+ push:
14
+ tags: ["v*"]
15
+
16
+ jobs:
17
+ build:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - uses: actions/setup-python@v5
22
+ with:
23
+ python-version: "3.12"
24
+ - name: Build sdist and wheel
25
+ run: |
26
+ python -m pip install build
27
+ python -m build
28
+ - uses: actions/upload-artifact@v4
29
+ with:
30
+ name: dist
31
+ path: dist/
32
+
33
+ publish:
34
+ needs: build
35
+ runs-on: ubuntu-latest
36
+ environment: pypi
37
+ permissions:
38
+ id-token: write # required for PyPI trusted publishing
39
+ steps:
40
+ - uses: actions/download-artifact@v4
41
+ with:
42
+ name: dist
43
+ path: dist/
44
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .ipynb_checkpoints/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .venv/
@@ -0,0 +1,48 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0 — 2026-09-01
4
+
5
+ First release as an installable package, `plotastro`.
6
+
7
+ ### Added
8
+ - `pip install plotastro`; importing it registers all styles with
9
+ matplotlib, so `plt.style.use("mnras")` works everywhere.
10
+ - Journal styles: **MNRAS**, **RASTI**, **A&A**, **ApJ/ApJL (AASTeX)**,
11
+ **Open Journal of Astrophysics**, **PRD/PRL (REVTeX)**, **JCAP**, and
12
+ **Nature Astronomy** (sans-serif variant), plus `thesis`/`beamer`
13
+ width presets. All generated from one template
14
+ (`tools/generate_styles.py`) so they stay consistent.
15
+ - Helpers: `set_style`, `figsize`, `subplots`, `savefig`,
16
+ `style_cycler`, `lighten`/`darken`, `label_panels` (journal-style
17
+ (a)/(b)/(c) panel labels), reference charts
18
+ (`show_colors`/`show_markers`/`show_linestyles`).
19
+ - Colour-vision-deficiency checking: `simulate_cvd`, `check_colors`,
20
+ and `check_figure` (Machado et al. 2009 model; no extra dependencies).
21
+ - Palettes: the default colour-blind-friendly cycle (`COLORS`), Okabe &
22
+ Ito (`OKABE_ITO`), Petroff-10 (`PETROFF10`) and light/dark pairs
23
+ (`PAIRED`).
24
+ - Author-list generator: `authorlist("authors.csv", journal=...)` and the
25
+ `plotastro-authors` command-line tool turn a CSV of names/affiliations/
26
+ ORCIDs/emails into the journal's LaTeX author block (MNRAS, A&A,
27
+ AASTeX, REVTeX, JCAP and generic formats), with automatic affiliation
28
+ numbering and sharing. Reads real collaboration lists as-is
29
+ (`Authorname`/`Firstname`+`Lastname` columns, one row per affiliation,
30
+ embedded LaTeX accents, extra columns ignored).
31
+ - Zero-learning-curve mode: importing plotastro registers every style
32
+ with matplotlib, so `plt.style.use("mnras")` + ordinary matplotlib is
33
+ the entire integration (`pa.use(...)` is an alias of `set_style`).
34
+ - `requirements.txt` / `requirements-dev.txt` for pip users; NumPy 1.x
35
+ and 2.x both supported and tested in CI.
36
+ - Tests (pytest), CI and PyPI-publishing GitHub Actions workflows,
37
+ executed tutorial notebook, MIT license.
38
+
39
+ ### Changed
40
+ - Styles no longer require LaTeX: portable STIX mathtext by default,
41
+ with `set_style(..., usetex=True)` to opt in (newtx fonts).
42
+ - Submission-safe saving defaults: PDF, tight bbox, 450 dpi,
43
+ TrueType font embedding (`pdf.fonttype 42`).
44
+
45
+ ### Migration from the original repo
46
+ - `MNRAS_Style.mplstyle` → `plt.style.use("mnras")` (after `import plotastro`).
47
+ - `myfigsize.set_size(...)` → `plotastro.set_size(...)` still works, but
48
+ prefer `plotastro.figsize(...)` / `plotastro.subplots(...)`.
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Behnood Bandi
4
+
5
+ Based on mplstyle_for_MNRAS by M. Knabenhans (2018).
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
@@ -0,0 +1,432 @@
1
+ Metadata-Version: 2.5
2
+ Name: plotastro
3
+ Version: 1.0.0
4
+ Summary: Publication-quality matplotlib styles and helpers for astronomy journals (MNRAS, A&A, ApJ, OJA, PRD, JCAP, Nature Astronomy)
5
+ Project-URL: Homepage, https://github.com/BehnoodBandi/plotastro
6
+ Project-URL: Issues, https://github.com/BehnoodBandi/plotastro/issues
7
+ Author-email: Behnood Bandi <b.bandi@sussex.ac.uk>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: aanda,apj,astronomy,colorblind,figures,matplotlib,mnras,plotting,publication
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Framework :: Matplotlib
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
17
+ Classifier: Topic :: Scientific/Engineering :: Visualization
18
+ Requires-Python: >=3.9
19
+ Requires-Dist: matplotlib>=3.5
20
+ Provides-Extra: dev
21
+ Requires-Dist: numpy; extra == 'dev'
22
+ Requires-Dist: pytest; extra == 'dev'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # plotastro
26
+
27
+ **Publication-quality matplotlib figures for astronomy journals.**
28
+
29
+ One `pip install` gives you journal-matched styles for **MNRAS**, **RASTI**,
30
+ **A&A**, **ApJ/ApJL**, the **Open Journal of Astrophysics**, **PRD/PRL**,
31
+ **JCAP** and **Nature Astronomy** — figures at exactly the right physical
32
+ size, a colour-blind-friendly palette, and helpers that make the tedious
33
+ parts (sizing, panel labels, accessibility checks, saving) one-liners.
34
+
35
+ ```bash
36
+ pip install plotastro
37
+ ```
38
+
39
+ **Simplest usage — no new API to learn.** Importing plotastro registers the
40
+ styles with matplotlib itself; after one `plt.style.use` line you write
41
+ ordinary matplotlib, and the default figure size is already the journal's
42
+ column width:
43
+
44
+ ```python
45
+ import matplotlib.pyplot as plt
46
+ import plotastro # just to register the styles
47
+
48
+ plt.style.use("mnras") # or "aanda", "apj", "oja", "prd", ...
49
+ fig, ax = plt.subplots() # plain matplotlib from here on
50
+ ```
51
+
52
+ **With the helpers** (optional, but they make the tedious parts one-liners):
53
+
54
+ ```python
55
+ import plotastro as pa
56
+
57
+ pa.set_style("mnras")
58
+ fig, ax = pa.subplots() # one-column figure, golden-ratio height
59
+ ax.plot(x, y, label="model")
60
+ ax.set_xlabel("$x$")
61
+ ax.legend()
62
+ pa.savefig("myplot") # -> myplot.pdf, ready for \includegraphics
63
+ ```
64
+
65
+ | One column | Full width |
66
+ |---|---|
67
+ | ![single-column example](examples/figures/example_column.png) | ![full-width example](examples/figures/example_full.png) |
68
+
69
+ **Start with the [tutorial notebook](examples/tutorial.ipynb)** — it walks
70
+ through every feature with runnable examples.
71
+
72
+ ## Why this exists
73
+
74
+ Two problems ruin most paper figures:
75
+
76
+ 1. **Wrong physical size.** If you hand LaTeX a 6-inch figure and it squeezes
77
+ it into an 84 mm column, every label shrinks by ~50 % and becomes
78
+ unreadable. The fix: build the figure at its final printed width, then
79
+ include it with a plain `\includegraphics{fig.pdf}` — no `[width=...]`.
80
+ 2. **Inaccessible colours.** ~5 % of male readers have a colour-vision
81
+ deficiency, and MNRAS's
82
+ [author guidelines](https://academic.oup.com/mnras/pages/general_instructions)
83
+ explicitly ask for colour-blind-friendly figures. The default matplotlib
84
+ cycle is not; the one here is — and `pa.check_figure()` lets you verify it.
85
+
86
+ The styles share one visual language — Times-like serif fonts at ~9 pt with
87
+ ~8 pt tick lettering, inward ticks on all four sides with minors, a subtle
88
+ grid, frameless legends — and differ only in figure width (plus the
89
+ sans-serif fonts Nature requires), so your plots stay **consistent between
90
+ papers** no matter where you submit.
91
+
92
+ ## Supported journals
93
+
94
+ `pa.set_style(...)`, `pa.figsize(...)` and `plt.style.use(...)` accept
95
+ (aliases in parentheses):
96
+
97
+ | key | journal | one column | full width |
98
+ |---|---|---|---|
99
+ | `mnras` | Monthly Notices of the RAS | 240.0 pt = 3.32 in | 504.0 pt = 6.97 in |
100
+ | `rasti` | RAS Techniques & Instruments | 240.0 pt = 3.32 in | 504.0 pt = 6.97 in |
101
+ | `aanda` (`a&a`, `aa`) | Astronomy & Astrophysics | 250.4 pt = 3.46 in (88 mm) | 512.2 pt = 7.09 in (180 mm) |
102
+ | `apj` (`apjl`, `aastex`) | The Astrophysical Journal | 242.3 pt = 3.35 in | 513.1 pt = 7.10 in |
103
+ | `oja` | Open Journal of Astrophysics | ≈245.3 pt = 3.39 in | ≈508 pt = 7.03 in |
104
+ | `prd` (`prl`, `revtex`) | Physical Review D | 246.0 pt = 3.40 in | 510.0 pt = 7.06 in |
105
+ | `jcap` | J. Cosmology & Astroparticle Phys. | single-column ≈455 pt = 6.30 in | — |
106
+ | `natastro` (`nature`) | Nature Astronomy (sans-serif!) | 253.2 pt = 3.50 in (89 mm) | 520.7 pt = 7.20 in (183 mm) |
107
+ | `thesis` | A4 thesis text width | 426.8 pt = 5.91 in | — |
108
+ | `beamer` | Beamer slide text width | 307.3 pt = 4.25 in | — |
109
+
110
+ Widths come from each journal's LaTeX class / author guide. For a custom
111
+ document, put `\the\columnwidth` or `\the\textwidth` in your `.tex` body,
112
+ compile, read the value off the page, and pass it directly:
113
+ `pa.figsize(width=345.0)`.
114
+
115
+ The only hard dependency is matplotlib; plotastro works with both NumPy 1.x
116
+ and 2.x (CI tests each). Running the examples from a clone?
117
+ `pip install -r requirements-dev.txt`.
118
+
119
+ ## Tutorial
120
+
121
+ ### Figure sizing
122
+
123
+ ```python
124
+ pa.figsize("column") # one column, golden-ratio height
125
+ pa.figsize("full") # full text width
126
+ pa.figsize("column", fraction=0.5) # half a column
127
+ pa.figsize("column", aspect=1) # square panel (aspect = height/width)
128
+ pa.figsize("column", journal="aanda") # size for a specific journal
129
+ pa.figsize(345.0) # any width in LaTeX points
130
+ ```
131
+
132
+ `pa.subplots()` takes the same arguments *plus* everything `plt.subplots`
133
+ accepts, and scales the height with the grid so each panel keeps its aspect:
134
+
135
+ ```python
136
+ fig, ax = pa.subplots() # 1 panel, one column
137
+ fig, axes = pa.subplots(2, 2, width="full") # 2x2 grid, full width
138
+ fig, axes = pa.subplots(1, 2, width="full", aspect=0.75, sharey=True)
139
+ ```
140
+
141
+ ### The colour palette
142
+
143
+ ![default palette](examples/figures/palette.png)
144
+
145
+ The default cycle has 12 colours, all accessible by name via `pa.COLORS`
146
+ (e.g. `pa.COLORS["blue"]`), or as matplotlib's `"C0"`…`"C11"` shorthands:
147
+
148
+ - **C0–C8** are a colour-blind-safe re-ordering of the
149
+ [ColorBrewer](https://colorbrewer2.org) *Set1* qualitative palette
150
+ (popularised by [Thøger Rivera-Thorsen's CBcycle](https://gist.github.com/thriveth/8560036)).
151
+ Consecutive colours differ in **lightness as well as hue**, so adjacent
152
+ lines stay distinguishable under the common deficiencies (deuteranopia,
153
+ protanopia) *and* in greyscale print; the notorious red–green pair is
154
+ pushed far apart in the cycle (green is C2, red is C7), so plots with a
155
+ handful of lines never rely on it.
156
+ - **C9–C11** are light companions (from Tableau's *Color Blind 10*): use them
157
+ for uncertainty bands, reference curves, or de-emphasised data underneath a
158
+ saturated line of the same hue.
159
+
160
+ Matched shades without transparency (better for print and EPS):
161
+
162
+ ```python
163
+ ax.plot(x, y, color=pa.COLORS["blue"])
164
+ ax.fill_between(x, lo, hi, color=pa.lighten(pa.COLORS["blue"], 0.7))
165
+ pa.darken(pa.COLORS["orange"], 0.3) # the other direction
166
+ ```
167
+
168
+ Three more palettes ship with the package:
169
+
170
+ - `pa.OKABE_ITO` — [Okabe & Ito (2008)](https://jfly.uni-koeln.de/color/),
171
+ *the* classic CVD-safe recommendation for categorical colours in science;
172
+ - `pa.PETROFF10` — [Petroff (2021)](https://arxiv.org/abs/2107.02270), the
173
+ CVD-optimised 10-colour cycle used across particle physics;
174
+ - `pa.PAIRED` — light/dark pairs for data/model or before/after comparisons:
175
+ `pa.PAIRED["blue"]` → `("#a6cee3", "#1f78b4")`.
176
+
177
+ **Colormaps:** the styles default to `viridis` (perceptually uniform,
178
+ CVD-safe). Good picks: `viridis`/`magma`/`cividis` for sequential data,
179
+ `RdBu_r` or `coolwarm` for diverging data (red–*blue*, not red–green). Avoid
180
+ `jet`/`rainbow`. For more astro-friendly maps see
181
+ [cmasher](https://cmasher.readthedocs.io) and [cmocean](https://matplotlib.org/cmocean/).
182
+
183
+ ### Checking accessibility yourself
184
+
185
+ ![CVD check](examples/figures/cvd_check.png)
186
+
187
+ Don't take the palette's word for it — simulate it
188
+ (Machado et al. 2009 model, no extra dependencies):
189
+
190
+ ```python
191
+ pa.check_colors() # any palette under deuteranopia/protanopia/greyscale
192
+ pa.check_colors(pa.PAIRED) # works on your own colour lists/dicts too
193
+ pa.check_figure(fig) # simulate a whole rendered figure — the
194
+ # final check before submission
195
+ pa.simulate_cvd("#e41a1c", "deuteranopia") # the raw transform
196
+ ```
197
+
198
+ If two lines merge in any panel, add markers or dash patterns (below), or
199
+ pick colours further apart in the cycle. MNRAS recommends
200
+ [Color Oracle](https://colororacle.org) and ColorBrewer for exactly this;
201
+ now it's built in.
202
+
203
+ ### Markers
204
+
205
+ ![markers](examples/figures/markers.png)
206
+
207
+ `pa.MARKERS = ["o", "s", "^", "D", "v", "p", "*", "X"]` — filled shapes that
208
+ survive shrinking to 4 pt. Conventions worth knowing:
209
+
210
+ | marker | typical use in astro figures |
211
+ |---|---|
212
+ | `"o"` `"s"` `"D"` | primary data series |
213
+ | `"^"` / `"v"` | **lower / upper limits** (readers expect this) |
214
+ | `"*"` `"p"` | highlight special objects (the Sun, a best-fit point) |
215
+ | `"x"` `"+"` | thin crosses — dense scatter plots, since they don't occlude |
216
+ | `"."` | huge point clouds (use `ms=1`–`2`, or better, rasterized hexbin) |
217
+
218
+ Useful tricks: `markevery=7` thins markers on dense curves;
219
+ `mfc="none"` (hollow markers) keeps overlapping datasets readable;
220
+ `ms=` and `mew=` control size and edge width.
221
+
222
+ ### Line styles
223
+
224
+ ![line styles](examples/figures/linestyles.png)
225
+
226
+ Beyond matplotlib's `"-"`, `"--"`, `":"`, `"-."`, the dict `pa.LINESTYLES`
227
+ provides named dash tuples of the form `(offset, (on, off, ...))` in points:
228
+
229
+ ```python
230
+ ax.plot(x, y, ls=pa.LINESTYLES["long dash"]) # (0, (9, 3))
231
+ ax.plot(x, y, ls=(0, (4, 1, 1, 1))) # or roll your own
232
+ ```
233
+
234
+ Guidelines: keep to ≤ 4 distinct dash patterns per panel (more becomes
235
+ noise); use solid for data / the headline result and dashes/dots for models
236
+ and references; MNRAS explicitly warns against triple-dot-dashed lines.
237
+
238
+ ### Redundant encoding — the cycler
239
+
240
+ Colour should never be the *only* difference between curves. `pa.style_cycler`
241
+ advances colour, marker and/or line style **in step**, so every series is
242
+ unique in two or three channels at once (and survives greyscale printing):
243
+
244
+ ![redundant encoding](examples/figures/redundant_encoding.png)
245
+
246
+ ```python
247
+ ax.set_prop_cycle(pa.style_cycler(markers=True)) # one axes
248
+ ax.set_prop_cycle(pa.style_cycler(linestyles=True, markers=True))
249
+ plt.rc("axes", prop_cycle=pa.style_cycler(markers=True)) # everywhere
250
+ ```
251
+
252
+ ### Panel labels
253
+
254
+ Journals want multi-panel figures labelled (a), (b), (c)…:
255
+
256
+ ```python
257
+ fig, axes = pa.subplots(2, 2, width="full")
258
+ pa.label_panels(axes) # (a) (b) (c) (d)
259
+ pa.label_panels(axes, loc="outside", fmt="{}", fontweight="bold") # Nature style
260
+ pa.label_panels(axes, uppercase=True, loc="lower right") # (A) ... bottom-right
261
+ ```
262
+
263
+ ### LaTeX text rendering
264
+
265
+ By default the styles use matplotlib **mathtext** with STIX fonts:
266
+ Times-compatible maths, zero dependencies. For pixel-perfect agreement with
267
+ your manuscript (custom macros, real kerning):
268
+
269
+ ```python
270
+ pa.set_style("mnras", usetex=True) # needs latex + dvipng + ghostscript
271
+ ```
272
+
273
+ This loads the `newtx` Times fonts (matching the MNRAS/A&A house font), or
274
+ Helvetica for Nature Astronomy. Develop with `usetex=False`, flip it on for
275
+ the final version — LaTeX rendering is slow.
276
+
277
+ ### Saving figures
278
+
279
+ The styles bake in submission-friendly defaults: **PDF** output, 450 dpi for
280
+ rasterised elements (journals want ≥ 300–400), tight bounding box, and
281
+ TrueType font embedding (`pdf.fonttype: 42`, so no Type-3 font rejections).
282
+
283
+ ```python
284
+ pa.savefig("figure1") # figure1.pdf
285
+ pa.savefig("figure1", formats=("pdf", "png")) # + a PNG for slides/Slack
286
+ pa.savefig("figure1", fig=fig, dpi=600) # extra options pass through
287
+ ```
288
+
289
+ If a journal insists on EPS, note EPS has **no transparency** — replace
290
+ `alpha=` with `pa.lighten()` shades (a good habit anyway).
291
+
292
+ ### Author lists from a CSV
293
+
294
+ Assembling the author/affiliation block by hand is error-prone on long
295
+ collaborations. Feed plotastro the author CSV your collaboration already
296
+ maintains — it works with real-world lists exactly as they are
297
+ (this is [examples/authors_example.csv](examples/authors_example.csv)):
298
+
299
+ ```csv
300
+ Lastname,Firstname,Authorname,Email,JoinedAsBuilder,Affiliation,ORCID,
301
+ Bandi,Behnood,Behnood Bandi, b.bandi@sussex.ac.uk, False,"Astronomy Centre, University of Sussex, Falmer, Brighton BN1 9QH, UK",0000-0001-5838-3903,
302
+ Rocher,Antoine,Antoine Rocher,antoine.rocher@epfl.ch,False,"EPFL, \'{E}cole polytechnique f\'{e}d\'{e}rale de Lausanne, Chemin des Maillettes, 51, 1290 Versoix, Switzerland",0000-0003-4349-6424,
303
+ Verdier,Aur\'{e}lien,Aur\'{e}lien Verdier,aurelien.verdier@epfl.ch,False,"EPFL, \'{E}cole polytechnique f\'{e}d\'{e}rale de Lausanne, Chemin des Maillettes, 51, 1290 Versoix, Switzerland",,
304
+ Richard,Johan,Johan Richard,johan.richard@univ-lyon1.fr,False,"CRAL, Centre de Recherche Astrophysique de Lyon, Universit\'{e} de Lyon, 9 avenue Charles Andr\'{e}, 69230 Saint-Genis-Laval, France",0000-0001-5492-1049,
305
+ Loveday,Jon ,Jon Loveday, j.loveday@sussex.ac.uk, False,"Astronomy Centre, University of Sussex, Falmer, Brighton BN1 9QH, UK",0000-0001-5290-8940,
306
+ Brown,Michael,Michael Brown,michael.brown@monash.edu,False,"Monash, School of Physics and Astronomy, Monash University, Wellington Road, Clayton, VIC 3800, Australia",0000-0002-1207-9137,
307
+ ```
308
+
309
+ It recognises `Authorname` (or `name`, or `Firstname`+`Lastname`),
310
+ `Affiliation`/`affiliations` (several separated by `;`, or one row per
311
+ affiliation — repeated author rows are merged), and optional `ORCID` and
312
+ `Email`; **every other column is ignored** (`JoinedAsBuilder`, ...), stray
313
+ spaces are stripped, and LaTeX already in the file (accents like `\'{e}`)
314
+ passes through untouched. Affiliations are numbered in order of first
315
+ appearance and shared between authors automatically; the first author with
316
+ an email becomes the corresponding author.
317
+
318
+ ```python
319
+ print(pa.authorlist("authors_example.csv", journal="mnras"))
320
+ ```
321
+
322
+ ```latex
323
+ \author[B. Bandi et al.]{
324
+ Behnood Bandi,$^{1}$\thanks{E-mail: b.bandi@sussex.ac.uk}
325
+ Antoine Rocher,$^{2}$
326
+ Aur\'{e}lien Verdier,$^{2}$
327
+ Johan Richard,$^{3}$
328
+ Jon Loveday$^{1}$
329
+ and Michael Brown$^{4}$
330
+ \\
331
+ % List of institutions
332
+ $^{1}$Astronomy Centre, University of Sussex, Falmer, Brighton BN1 9QH, UK\\
333
+ $^{2}$EPFL, \'{E}cole polytechnique f\'{e}d\'{e}rale de Lausanne, Chemin des Maillettes, 51, 1290 Versoix, Switzerland\\
334
+ $^{3}$CRAL, Centre de Recherche Astrophysique de Lyon, Universit\'{e} de Lyon, 9 avenue Charles Andr\'{e}, 69230 Saint-Genis-Laval, France\\
335
+ $^{4}$Monash, School of Physics and Astronomy, Monash University, Wellington Road, Clayton, VIC 3800, Australia
336
+ }
337
+ ```
338
+
339
+ The same CSV works for every journal: `mnras`/`rasti`, `aanda`
340
+ (`\inst`/`\institute`), `apj`/`oja` (AASTeX `\author`/`\affiliation` with
341
+ ORCIDs), `prd` (REVTeX), `jcap` (lettered `\affiliation[a]`), or `generic`
342
+ for a plain numbered block. A command-line tool ships with the package, so
343
+ co-authors who don't use Python can run it too:
344
+
345
+ ```bash
346
+ plotastro-authors authors.csv --journal aanda
347
+ plotastro-authors authors.csv -j apj -o authors.tex
348
+ ```
349
+
350
+ See [examples/authors_example.csv](examples/authors_example.csv) for a
351
+ complete example.
352
+
353
+ ## API summary
354
+
355
+ | | |
356
+ |---|---|
357
+ | `set_style(journal, usetex=, grid=, **rc)` | activate a journal's style (alias: `use`) |
358
+ | `authorlist(csv, journal=)` | LaTeX author/affiliation block from a CSV (CLI: `plotastro-authors`) |
359
+ | `figsize(width, journal=, fraction=, aspect=, ...)` | journal-correct figure dimensions |
360
+ | `subplots(...)` | `plt.subplots` with the size computed for you |
361
+ | `savefig(name, formats=("pdf",))` | save one figure in several formats |
362
+ | `label_panels(axes, ...)` | (a), (b), (c) panel labels |
363
+ | `style_cycler(markers=, linestyles=)` | redundant-encoding property cycle |
364
+ | `COLORS`, `CYCLE`, `OKABE_ITO`, `PETROFF10`, `PAIRED` | palettes |
365
+ | `lighten(c, f)`, `darken(c, f)` | matched shades without transparency |
366
+ | `simulate_cvd`, `check_colors`, `check_figure` | colour-vision-deficiency checks |
367
+ | `MARKERS`, `LINESTYLES` | curated marker / dash-pattern sequences |
368
+ | `show_colors()`, `show_markers()`, `show_linestyles()` | reference charts |
369
+ | `current_journal()`, `JOURNALS`, `GOLDEN` | introspection |
370
+ | `set_size(...)` | deprecated alias for the original `myfigsize` API |
371
+
372
+ ## Tweaks and FAQ
373
+
374
+ - **Turn the grid off:** `pa.set_style("mnras", grid=False)`, or per-axes
375
+ `ax.grid(False)`.
376
+ - **Override anything:** `pa.set_style("mnras", **{"font.size": 10})`, or
377
+ `plt.rcParams[...] = ...` after `set_style`.
378
+ - **"Times New Roman not found" warning:** the font list falls back through
379
+ Times → Nimbus Roman → STIX → DejaVu automatically; install
380
+ `mscorefonts`/STIX to silence it, or ignore it.
381
+ - **Labels getting cut off?** They shouldn't be — the styles enable
382
+ `constrained_layout`. If you manage layout manually, disable it with
383
+ `plt.rcParams["figure.constrained_layout.use"] = False`.
384
+ - **Astronomical images:** use `origin="lower"` in `imshow` (or uncomment
385
+ `image.origin: lower` in the style file), and `ax.grid(False)`.
386
+ - **Figures look huge/small on screen:** that's just `figure.dpi: 150` for
387
+ display; the saved size is exact.
388
+ - **Styles without Python helpers:** after `import plotastro` once,
389
+ `plt.style.use("mnras")` works in any code; or copy the `.mplstyle` files
390
+ from `src/plotastro/styles/` into `matplotlib.get_configdir()/stylelib/`.
391
+ - **Old API:** `plotastro.set_size(...)` reproduces the original
392
+ `myfigsize.set_size()`; the old `MNRAS_Style.mplstyle` is now
393
+ `plt.style.use("mnras")`.
394
+
395
+ ## Development
396
+
397
+ ```bash
398
+ git clone <this repo> && cd <repo>
399
+ pip install -e ".[dev]"
400
+ pytest # run the test suite
401
+ python tools/generate_styles.py # regenerate styles/ after editing the template
402
+ python examples/make_reference_figures.py # regenerate README figures
403
+ ```
404
+
405
+ The `.mplstyle` files are generated from a single template in
406
+ [tools/generate_styles.py](tools/generate_styles.py) — edit that, not the
407
+ files (CI checks they stay in sync). Releases: bump the version in
408
+ `pyproject.toml` and `CHANGELOG.md`, then push a `v*` tag — the
409
+ [publish workflow](.github/workflows/publish.yml) builds and uploads to PyPI
410
+ (see the one-time trusted-publishing setup notes in that file).
411
+
412
+ ## Credits
413
+
414
+ - Original MNRAS style this grew from:
415
+ [M. Knabenhans' mplstyle_for_MNRAS](https://github.com/mischakn/mplstyle_for_MNRAS)
416
+ - Colour-blind-friendly Set1 ordering:
417
+ [Thøger Rivera-Thorsen](https://gist.github.com/thriveth/8560036);
418
+ light colours from Tableau *Color Blind 10*
419
+ - Palettes: [Okabe & Ito](https://jfly.uni-koeln.de/color/),
420
+ [Petroff (2021)](https://arxiv.org/abs/2107.02270), ColorBrewer *Paired*
421
+ - CVD model: Machado, Oliveira & Fernandes (2009), IEEE TVCG 15(6)
422
+ - Figure-size approach after
423
+ [Jack Walton's guide](https://jwalton.info/Embed-Publication-Matplotlib-Latex/)
424
+ - Journal guidelines:
425
+ [MNRAS](https://academic.oup.com/mnras/pages/general_instructions) ·
426
+ [A&A](https://www.aanda.org/for-authors) ·
427
+ [AAS Journals](https://journals.aas.org/graphics-guide/) ·
428
+ [OJA](https://astro.theoj.org/site/instructions) ·
429
+ [APS](https://journals.aps.org/authors) ·
430
+ [Nature](https://www.nature.com/nature/for-authors/formatting-guide)
431
+
432
+ MIT licensed — see [LICENSE](LICENSE).