matplotvite 0.6.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 (79) hide show
  1. matplotvite-0.6.0/.github/dependabot.yml +21 -0
  2. matplotvite-0.6.0/.github/requirements-ci.txt +13 -0
  3. matplotvite-0.6.0/.github/workflows/ci.yml +366 -0
  4. matplotvite-0.6.0/.github/workflows/pages.yml +84 -0
  5. matplotvite-0.6.0/.github/workflows/publish.yml +146 -0
  6. matplotvite-0.6.0/.gitignore +21 -0
  7. matplotvite-0.6.0/.jules/bolt.md +5 -0
  8. matplotvite-0.6.0/.jules/sentinel.md +6 -0
  9. matplotvite-0.6.0/Cargo.lock +1778 -0
  10. matplotvite-0.6.0/Cargo.toml +36 -0
  11. matplotvite-0.6.0/LICENSE +12 -0
  12. matplotvite-0.6.0/LICENSE-APACHE +201 -0
  13. matplotvite-0.6.0/LICENSE-MIT +21 -0
  14. matplotvite-0.6.0/PKG-INFO +477 -0
  15. matplotvite-0.6.0/README.md +443 -0
  16. matplotvite-0.6.0/ROADMAP.md +306 -0
  17. matplotvite-0.6.0/TRACE.md +222 -0
  18. matplotvite-0.6.0/architecture.md +219 -0
  19. matplotvite-0.6.0/assets/fonts/DejaVuSans-Bold.ttf +0 -0
  20. matplotvite-0.6.0/assets/fonts/DejaVuSans.ttf +0 -0
  21. matplotvite-0.6.0/assets/fonts/Geist-Regular.ttf +0 -0
  22. matplotvite-0.6.0/assets/fonts/GeistMono-Regular.ttf +0 -0
  23. matplotvite-0.6.0/assets/fonts/GeistSerif-Regular.ttf +0 -0
  24. matplotvite-0.6.0/assets/fonts/LICENSE_DEJAVU.txt +78 -0
  25. matplotvite-0.6.0/assets/fonts/LICENSE_GEIST_OFL.txt +93 -0
  26. matplotvite-0.6.0/benches/criterion_baseline.json +25 -0
  27. matplotvite-0.6.0/benches/render.rs +250 -0
  28. matplotvite-0.6.0/examples/__init__.py +1 -0
  29. matplotvite-0.6.0/examples/samples.py +890 -0
  30. matplotvite-0.6.0/prek.toml +38 -0
  31. matplotvite-0.6.0/pyproject.toml +59 -0
  32. matplotvite-0.6.0/python/matplotvite/__init__.py +8013 -0
  33. matplotvite-0.6.0/python/matplotvite/__init__.pyi +377 -0
  34. matplotvite-0.6.0/python/matplotvite/_cmaps.py +3657 -0
  35. matplotvite-0.6.0/python/matplotvite/_colors.py +152 -0
  36. matplotvite-0.6.0/python/matplotvite/mpl_toolkits/__init__.py +5 -0
  37. matplotvite-0.6.0/python/matplotvite/mpl_toolkits/axes_grid1/__init__.py +5 -0
  38. matplotvite-0.6.0/python/matplotvite/mpl_toolkits/axes_grid1/inset_locator.py +165 -0
  39. matplotvite-0.6.0/python/matplotvite/py.typed +0 -0
  40. matplotvite-0.6.0/review.md +260 -0
  41. matplotvite-0.6.0/src/bench.rs +34 -0
  42. matplotvite-0.6.0/src/cmap_data.rs +21193 -0
  43. matplotvite-0.6.0/src/color.rs +81 -0
  44. matplotvite-0.6.0/src/draw.rs +3162 -0
  45. matplotvite-0.6.0/src/export.rs +709 -0
  46. matplotvite-0.6.0/src/fonts.rs +105 -0
  47. matplotvite-0.6.0/src/image.rs +210 -0
  48. matplotvite-0.6.0/src/layout.rs +328 -0
  49. matplotvite-0.6.0/src/lib.rs +279 -0
  50. matplotvite-0.6.0/src/model.rs +646 -0
  51. matplotvite-0.6.0/src/raster.rs +343 -0
  52. matplotvite-0.6.0/src/scale.rs +211 -0
  53. matplotvite-0.6.0/tests/conftest.py +26 -0
  54. matplotvite-0.6.0/tests/png_utils.py +50 -0
  55. matplotvite-0.6.0/tests/test_benchmark.py +143 -0
  56. matplotvite-0.6.0/tests/test_commit_message.py +38 -0
  57. matplotvite-0.6.0/tests/test_gallery_smoke.py +71 -0
  58. matplotvite-0.6.0/tests/test_gallery_summary.py +43 -0
  59. matplotvite-0.6.0/tests/test_harvest_matplotlib.py +433 -0
  60. matplotvite-0.6.0/tests/test_harvest_seaborn.py +79 -0
  61. matplotvite-0.6.0/tests/test_plt.py +3794 -0
  62. matplotvite-0.6.0/tests/test_samples.py +148 -0
  63. matplotvite-0.6.0/tests/test_visual_compare.py +92 -0
  64. matplotvite-0.6.0/tests/test_wheel.py +83 -0
  65. matplotvite-0.6.0/tools/benchmark.py +622 -0
  66. matplotvite-0.6.0/tools/benchmark_baseline.json +212 -0
  67. matplotvite-0.6.0/tools/benchmark_guard.py +86 -0
  68. matplotvite-0.6.0/tools/benchmarks_data.json +212 -0
  69. matplotvite-0.6.0/tools/build_pages.py +1978 -0
  70. matplotvite-0.6.0/tools/check_commit_message.py +45 -0
  71. matplotvite-0.6.0/tools/criterion_guard.py +85 -0
  72. matplotvite-0.6.0/tools/gallery_summary.py +71 -0
  73. matplotvite-0.6.0/tools/generate_colormaps.py +111 -0
  74. matplotvite-0.6.0/tools/harvest_matplotlib.py +904 -0
  75. matplotvite-0.6.0/tools/harvest_seaborn.py +206 -0
  76. matplotvite-0.6.0/tools/security_audit.py +55 -0
  77. matplotvite-0.6.0/tools/trace_baseline.json +364 -0
  78. matplotvite-0.6.0/tools/trace_unsupported.py +209 -0
  79. matplotvite-0.6.0/tools/visual_compare.py +263 -0
@@ -0,0 +1,21 @@
1
+ # PDF output assembles svg2pdf/usvg chunks by hand. When either crate changes,
2
+ # run the tight-PDF tests in tests/test_plt.py before merging the update.
3
+ version: 2
4
+ updates:
5
+ - package-ecosystem: "cargo"
6
+ directory: "/"
7
+ schedule:
8
+ interval: "weekly"
9
+ ignore:
10
+ # svg2pdf pins pdf-writer's major version; update them together.
11
+ - dependency-name: "pdf-writer"
12
+
13
+ - package-ecosystem: "pip"
14
+ directory: "/"
15
+ schedule:
16
+ interval: "weekly"
17
+
18
+ - package-ecosystem: "github-actions"
19
+ directory: "/"
20
+ schedule:
21
+ interval: "weekly"
@@ -0,0 +1,13 @@
1
+ # Constraints for CI's ad-hoc pip installs. Keep these exact-version pins in
2
+ # sync with the supported Python matrix (3.9 through 3.14).
3
+ maturin==1.16.0
4
+ numpy==2.0.2; python_version < "3.12"
5
+ numpy==2.5.4; python_version >= "3.12"
6
+ pandas==2.2.3; python_version < "3.12"
7
+ pandas==3.1.1; python_version >= "3.12"
8
+ pytest==8.4.2
9
+ matplotlib==3.11.2
10
+ bandit==1.9.4
11
+ pip-audit==2.10.1
12
+ pypdfium2==4.30.0
13
+ cairosvg==2.7.1
@@ -0,0 +1,366 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ concurrency:
11
+ group: ${{ github.workflow }}-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ prek:
16
+ name: prek checks
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
20
+ - uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master
21
+ with:
22
+ toolchain: stable
23
+ components: rustfmt, clippy
24
+ - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
25
+ - uses: j178/prek-action@44924fe9ef4a6f88253c4b6b1bf11c689d3ce53d # v3.1.0
26
+
27
+ lint:
28
+ name: python lint
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
32
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
33
+ with:
34
+ python-version: "3.13"
35
+ - run: pip install ruff==0.17.4
36
+ - run: ruff check python tests tools examples
37
+
38
+ security:
39
+ name: security scan
40
+ runs-on: ubuntu-latest
41
+ steps:
42
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
43
+ - uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master
44
+ with:
45
+ toolchain: stable
46
+ - uses: taiki-e/install-action@3a3daafd3b7f23b8f65d9f5ba2db203fb59b79e2 # v2.90.2
47
+ with:
48
+ tool: cargo-audit
49
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
50
+ with:
51
+ python-version: "3.13"
52
+ - name: Run Bandit static analysis
53
+ run: |
54
+ python -m pip install -c .github/requirements-ci.txt "bandit>=1.9" numpy
55
+ python -m bandit -q -r python/matplotvite
56
+
57
+ - name: Run dependency and static security checks
58
+ run: |
59
+ python -m pip install -c .github/requirements-ci.txt "pip-audit>=2.9"
60
+ python tools/security_audit.py
61
+
62
+ test:
63
+ name: test (python ${{ matrix.python }})
64
+ runs-on: ${{ matrix.os }}
65
+ strategy:
66
+ fail-fast: false
67
+ matrix:
68
+ os: [ubuntu-latest]
69
+ python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
70
+ include:
71
+ # ubuntu-latest (26.04) has no python-versions build for 3.9 yet, so the
72
+ # oldest supported interpreter runs on 22.04 instead.
73
+ - os: ubuntu-22.04
74
+ python: "3.9"
75
+ steps:
76
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
77
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
78
+ with:
79
+ python-version: ${{ matrix.python }}
80
+ - uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master
81
+ with:
82
+ toolchain: stable
83
+ - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
84
+ - name: Build & Test
85
+ run: |
86
+ python -m venv .venv
87
+ . .venv/bin/activate
88
+ pip install -c .github/requirements-ci.txt maturin numpy pandas pytest
89
+ maturin develop --release
90
+ pytest -q
91
+
92
+ wheels:
93
+ name: wheel (${{ matrix.name }})
94
+ runs-on: ${{ matrix.os }}
95
+ strategy:
96
+ fail-fast: false
97
+ matrix:
98
+ include:
99
+ - name: linux-x86_64
100
+ os: ubuntu-latest
101
+ target: x86_64-unknown-linux-gnu
102
+ manylinux: auto
103
+ interpreter: python3.13
104
+ - name: linux-aarch64
105
+ os: ubuntu-24.04-arm
106
+ target: aarch64-unknown-linux-gnu
107
+ manylinux: auto
108
+ interpreter: python3.13
109
+ - name: macos-arm64
110
+ os: macos-latest
111
+ target: aarch64-apple-darwin
112
+ interpreter: python
113
+ - name: macos-x86_64
114
+ os: macos-15-intel
115
+ target: x86_64-apple-darwin
116
+ interpreter: python
117
+ - name: windows-x86_64
118
+ os: windows-latest
119
+ target: x86_64-pc-windows-msvc
120
+ interpreter: python
121
+ - name: windows-arm64
122
+ os: windows-11-arm
123
+ target: aarch64-pc-windows-msvc
124
+ interpreter: python
125
+ steps:
126
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
127
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
128
+ with:
129
+ python-version: "3.13"
130
+ - uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
131
+ with:
132
+ target: ${{ matrix.target }}
133
+ manylinux: ${{ matrix.manylinux }}
134
+ # The manylinux container does not see setup-python's interpreter, so
135
+ # select the matching container Python explicitly. The wheel is built
136
+ # against the stable ABI (cp39-abi3) and works on every version above 3.9.
137
+ args: --release --out dist --interpreter ${{ matrix.interpreter }}
138
+ - name: Build the sdist
139
+ # One matrix entry builds the source distribution: the tarball is
140
+ # platform independent, so every platform would only duplicate it.
141
+ if: matrix.name == 'linux-x86_64'
142
+ uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
143
+ with:
144
+ command: sdist
145
+ args: --out dist
146
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
147
+ with:
148
+ name: wheel-${{ matrix.name }}
149
+ path: dist/*.whl
150
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
151
+ if: matrix.name == 'linux-x86_64'
152
+ with:
153
+ name: sdist
154
+ path: dist/*.tar.gz
155
+
156
+ wheel-tests:
157
+ name: wheel test (py${{ matrix.python }}, ${{ matrix.os }})
158
+ needs: wheels
159
+ runs-on: ${{ matrix.os }}
160
+ # The wheel is stable-ABI, so the per-interpreter coverage comes from the
161
+ # Linux matrix. Hosted macOS runners are scarce: asking for one per Python
162
+ # version regularly left jobs queued until GitHub cancelled them ~15 minutes
163
+ # later, so macOS checks the newest interpreter only. No timeout setting
164
+ # covers that queue wait; reducing the number of macOS jobs is the fix.
165
+ timeout-minutes: 30
166
+ strategy:
167
+ fail-fast: false
168
+ matrix:
169
+ os: [ubuntu-latest, macos-latest, windows-latest]
170
+ python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
171
+ exclude:
172
+ # Python 3.9 and 3.10 are no longer available on current hosted
173
+ # macOS/Windows images; the stable-ABI wheel still supports them.
174
+ - os: macos-latest
175
+ python: "3.10"
176
+ - os: macos-latest
177
+ python: "3.11"
178
+ - os: macos-latest
179
+ python: "3.12"
180
+ - os: macos-latest
181
+ python: "3.13"
182
+ - os: windows-latest
183
+ python: "3.10"
184
+ include:
185
+ - os: ubuntu-22.04
186
+ python: "3.9"
187
+ steps:
188
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
189
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
190
+ with:
191
+ python-version: ${{ matrix.python }}
192
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
193
+ with:
194
+ pattern: wheel-*
195
+ path: dist
196
+ merge-multiple: true
197
+ - name: Install the built wheel
198
+ run: |
199
+ python -m pip install -c .github/requirements-ci.txt numpy pandas pytest
200
+ python -m pip install --no-index --find-links dist matplotvite
201
+ - name: Run the test suite against the installed wheel
202
+ # The repo root is not on sys.path ahead of site-packages, so these
203
+ # imports resolve to the installed wheel rather than the source tree.
204
+ # Keep this the same suite the `test` job runs, so a wheel-only break
205
+ # cannot hide behind a `maturin develop` build.
206
+ run: python -m pytest -q
207
+
208
+ sdist-wheel:
209
+ name: wheel from sdist
210
+ needs: wheels
211
+ runs-on: ubuntu-latest
212
+ steps:
213
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
214
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
215
+ with:
216
+ python-version: "3.13"
217
+ - uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master
218
+ with:
219
+ toolchain: stable
220
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
221
+ with:
222
+ name: sdist
223
+ path: dist
224
+ - name: Build a wheel from the sdist
225
+ # Same PEP 517 path a user gets from `pip install matplotvite` on a
226
+ # platform without a wheel, using the pinned maturin instead of an
227
+ # isolated resolution.
228
+ run: |
229
+ python -m pip install -c .github/requirements-ci.txt maturin
230
+ python -m pip wheel --no-deps --no-build-isolation --wheel-dir wheel dist/*.tar.gz
231
+ - name: Check the wheel built from the sdist
232
+ run: |
233
+ python - <<'PY'
234
+ import glob
235
+ import pathlib
236
+
237
+ wheels = glob.glob("wheel/*.whl")
238
+ assert len(wheels) == 1, wheels
239
+ # The sdist must keep the abi3 configuration, or every interpreter
240
+ # above 3.9 would lose its wheel.
241
+ assert "cp39-abi3" in pathlib.Path(wheels[0]).name, wheels[0]
242
+ print(f"built {wheels[0]}")
243
+ PY
244
+ - name: Install and smoke-test the sdist wheel
245
+ run: |
246
+ python -m pip install -c .github/requirements-ci.txt numpy pytest
247
+ python -m pip install --no-deps wheel/*.whl
248
+ python -m pytest -q tests/test_wheel.py
249
+
250
+ benchmarks:
251
+ name: benchmark regression guard
252
+ runs-on: ubuntu-latest
253
+ # Performance budgets are advisory: benchmark noise should never block CI.
254
+ continue-on-error: true
255
+ steps:
256
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
257
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
258
+ with:
259
+ python-version: "3.13"
260
+ - uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master
261
+ with:
262
+ toolchain: stable
263
+ - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
264
+ - name: Run benchmarks and report the regression budget
265
+ env:
266
+ MPLBACKEND: Agg
267
+ run: |
268
+ python -m venv .venv
269
+ . .venv/bin/activate
270
+ pip install -c .github/requirements-ci.txt maturin numpy "matplotlib==3.11.2"
271
+ maturin develop --release
272
+ python tools/benchmark.py --quick --out dist/benchmarks.json
273
+ python tools/benchmark_guard.py --warn-only dist/benchmarks.json tools/benchmark_baseline.json
274
+ python tools/visual_compare.py dist/visual --json dist/visual_diff.json
275
+ - name: Upload the visual mismatch metrics
276
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
277
+ with:
278
+ name: visual-diff-${{ github.sha }}
279
+ path: |
280
+ dist/visual
281
+ dist/visual_diff.json
282
+ retention-days: 90
283
+
284
+ galleries:
285
+ name: comparison galleries
286
+ runs-on: ubuntu-latest
287
+ steps:
288
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
289
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
290
+ with:
291
+ python-version: "3.13"
292
+ - uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master
293
+ with:
294
+ toolchain: stable
295
+ - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
296
+ - name: Build
297
+ run: |
298
+ python -m venv .venv
299
+ . .venv/bin/activate
300
+ pip install -c .github/requirements-ci.txt maturin numpy "matplotlib==3.11.2" pypdfium2 cairosvg
301
+ maturin develop --release
302
+ - name: Fetch the harvested sources
303
+ run: |
304
+ pip download --no-binary :all: --no-deps "matplotlib==3.11.2" -d dist/sdist
305
+ mkdir -p dist/mpl-sdist
306
+ tar -xzf dist/sdist/matplotlib-3.11.2.tar.gz -C dist/mpl-sdist
307
+ git clone --depth 1 --filter=blob:none --sparse -b v3.11.2 \
308
+ https://github.com/matplotlib/matplotlib dist/mpl-src
309
+ git -C dist/mpl-src sparse-checkout set galleries/examples
310
+ git clone --depth 1 --filter=blob:none --sparse -b v0.13.2 \
311
+ https://github.com/mwaskom/seaborn dist/sns-src
312
+ git -C dist/sns-src sparse-checkout set examples
313
+ - name: Regenerate the galleries
314
+ env:
315
+ MPLBACKEND: Agg
316
+ run: |
317
+ . .venv/bin/activate
318
+ python tools/harvest_matplotlib.py --out dist/mpl-corpus
319
+ python tools/harvest_matplotlib.py --examples dist/mpl-src --out dist/mpl-examples
320
+ python tools/harvest_seaborn.py --out dist/seaborn-gallery
321
+ - name: Summarise the galleries
322
+ run: |
323
+ . .venv/bin/activate
324
+ python tools/gallery_summary.py --out dist >> "$GITHUB_STEP_SUMMARY"
325
+
326
+ criterion:
327
+ name: criterion output benchmarks
328
+ runs-on: ubuntu-latest
329
+ steps:
330
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
331
+ - uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master
332
+ with:
333
+ toolchain: stable
334
+ - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
335
+ - name: Run the output-stage Criterion suite
336
+ run: cargo bench --bench render -- --quick
337
+ - name: Report the Criterion budget
338
+ run: python tools/criterion_guard.py --warn-only target/criterion benches/criterion_baseline.json
339
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
340
+ with:
341
+ name: criterion-${{ github.sha }}
342
+ path: target/criterion
343
+ retention-days: 90
344
+
345
+ trace:
346
+ name: coverage trace
347
+ runs-on: ubuntu-latest
348
+ steps:
349
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
350
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
351
+ with:
352
+ python-version: "3.13"
353
+ - uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master
354
+ with:
355
+ toolchain: stable
356
+ - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
357
+ - name: Check coverage against baseline
358
+ env:
359
+ MPLBACKEND: Agg
360
+ run: |
361
+ python -m venv .venv
362
+ . .venv/bin/activate
363
+ pip install -c .github/requirements-ci.txt maturin numpy "matplotlib==3.11.2"
364
+ maturin develop --release
365
+ TRACE_DIR=$(python -c "import matplotlib, pathlib; print(pathlib.Path(matplotlib.__file__).parent / 'tests')")
366
+ python tools/trace_unsupported.py --check "$TRACE_DIR"
@@ -0,0 +1,84 @@
1
+ name: Pages
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: pages
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ build:
17
+ name: Build Example Gallery
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
21
+
22
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
23
+ with:
24
+ python-version: "3.12"
25
+
26
+ - uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master
27
+ with:
28
+ toolchain: stable
29
+
30
+ - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
31
+
32
+ - name: Build matplotvite
33
+ run: |
34
+ python -m venv .venv
35
+ . .venv/bin/activate
36
+ pip install -c .github/requirements-ci.txt maturin numpy pandas "matplotlib==3.11.2" pypdfium2 cairosvg
37
+ maturin develop --release
38
+
39
+ - name: Fetch the harvested sources
40
+ run: |
41
+ . .venv/bin/activate
42
+ mkdir -p /tmp/mpl-sdist /tmp/sdist
43
+ pip download --no-binary :all: --no-deps "matplotlib==3.11.2" -d /tmp/sdist
44
+ tar -xzf /tmp/sdist/matplotlib-3.11.2.tar.gz -C /tmp/mpl-sdist
45
+ git clone --depth 1 --filter=blob:none --sparse -b v3.11.2 \
46
+ https://github.com/matplotlib/matplotlib /tmp/mpl-src
47
+ git -C /tmp/mpl-src sparse-checkout set galleries/examples
48
+ git clone --depth 1 --filter=blob:none --sparse -b v0.13.2 \
49
+ https://github.com/mwaskom/seaborn /tmp/sns-src
50
+ git -C /tmp/sns-src sparse-checkout set examples
51
+
52
+ - name: Generate comparison galleries
53
+ env:
54
+ MPLBACKEND: Agg
55
+ run: |
56
+ . .venv/bin/activate
57
+ python tools/harvest_matplotlib.py --source /tmp/mpl-sdist --out dist/mpl-corpus
58
+ python tools/harvest_matplotlib.py --examples /tmp/mpl-src --out dist/mpl-examples
59
+ python tools/harvest_seaborn.py --source /tmp/sns-src --out dist/seaborn-gallery
60
+ python tools/visual_compare.py dist/visual-svg --format svg
61
+ # Built after the galleries so the coverage section can summarise them.
62
+ python tools/build_pages.py --out dist
63
+
64
+ - name: Setup Pages
65
+ uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
66
+
67
+ - name: Upload artifact
68
+ uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
69
+ with:
70
+ path: dist
71
+
72
+ deploy:
73
+ name: Deploy to GitHub Pages
74
+ needs: build
75
+ runs-on: ubuntu-latest
76
+ permissions:
77
+ pages: write
78
+ id-token: write
79
+ environment:
80
+ name: github-pages
81
+ url: ${{ steps.deployment.outputs.page_url }}
82
+ steps:
83
+ - id: deployment
84
+ uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
@@ -0,0 +1,146 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ # Re-run an interrupted upload for an existing tag without tagging again.
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ check-version:
15
+ name: tag matches package version
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
19
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
20
+ with:
21
+ python-version: "3.13"
22
+ - name: Compare the tag with the version in pyproject.toml, Cargo.toml and Cargo.lock
23
+ # PyPI rejects a duplicate version, so a mistyped tag would build every
24
+ # wheel and only fail at the upload. Stop before the matrix starts, and
25
+ # make sure the three manifests agree so the published metadata is not
26
+ # split between different versions.
27
+ if: github.ref_type == 'tag'
28
+ run: |
29
+ python - <<'PY'
30
+ import os
31
+ import pathlib
32
+ import sys
33
+ import tomllib
34
+
35
+ def load(path):
36
+ return tomllib.loads(pathlib.Path(path).read_text())
37
+
38
+ tag = os.environ["GITHUB_REF_NAME"]
39
+ wanted = tag[1:] if tag.startswith("v") else tag
40
+ cargo = load("Cargo.toml")["package"]
41
+ locked = next(
42
+ (item["version"] for item in load("Cargo.lock")["package"] if item["name"] == cargo["name"]),
43
+ None,
44
+ )
45
+ if locked is None:
46
+ sys.exit(f"{cargo['name']} is missing from Cargo.lock")
47
+ versions = {
48
+ "pyproject.toml": load("pyproject.toml")["project"]["version"],
49
+ "Cargo.toml": cargo["version"],
50
+ "Cargo.lock": locked,
51
+ }
52
+ mismatched = {path: version for path, version in versions.items() if version != wanted}
53
+ if mismatched:
54
+ found = ", ".join(f"{path} says {version}" for path, version in mismatched.items())
55
+ sys.exit(f"tag {tag} does not match: {found}")
56
+ print(f"tag {tag} matches " + ", ".join(f"{path} {version}" for path, version in versions.items()))
57
+ PY
58
+
59
+ wheels:
60
+ name: wheel (${{ matrix.name }})
61
+ needs: check-version
62
+ runs-on: ${{ matrix.os }}
63
+ strategy:
64
+ fail-fast: false
65
+ matrix:
66
+ include:
67
+ - name: linux-x86_64
68
+ os: ubuntu-latest
69
+ target: x86_64-unknown-linux-gnu
70
+ manylinux: auto
71
+ interpreter: python3.13
72
+ - name: linux-aarch64
73
+ os: ubuntu-24.04-arm
74
+ target: aarch64-unknown-linux-gnu
75
+ manylinux: auto
76
+ interpreter: python3.13
77
+ - name: macos-arm64
78
+ os: macos-latest
79
+ target: aarch64-apple-darwin
80
+ interpreter: python
81
+ - name: macos-x86_64
82
+ os: macos-15-intel
83
+ target: x86_64-apple-darwin
84
+ interpreter: python
85
+ - name: windows-x86_64
86
+ os: windows-latest
87
+ target: x86_64-pc-windows-msvc
88
+ interpreter: python
89
+ - name: windows-arm64
90
+ os: windows-11-arm
91
+ target: aarch64-pc-windows-msvc
92
+ interpreter: python
93
+ steps:
94
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
95
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
96
+ with:
97
+ python-version: "3.13"
98
+ - uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
99
+ with:
100
+ target: ${{ matrix.target }}
101
+ manylinux: ${{ matrix.manylinux }}
102
+ # Same stable-ABI wheel as CI: one cp39-abi3 build serves every
103
+ # interpreter above 3.9, so the matrix only varies by platform.
104
+ args: --release --out dist --interpreter ${{ matrix.interpreter }}
105
+ - name: Build the sdist
106
+ # The tarball is platform independent, so exactly one job builds it.
107
+ if: matrix.name == 'linux-x86_64'
108
+ uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
109
+ with:
110
+ command: sdist
111
+ args: --out dist
112
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
113
+ with:
114
+ name: wheel-${{ matrix.name }}
115
+ path: dist/*.whl
116
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
117
+ if: matrix.name == 'linux-x86_64'
118
+ with:
119
+ name: sdist
120
+ path: dist/*.tar.gz
121
+
122
+ publish:
123
+ name: Publish to PyPI
124
+ needs: wheels
125
+ runs-on: ubuntu-latest
126
+ # Must match the environment name registered in the PyPI trusted publisher.
127
+ environment: pypi
128
+ permissions:
129
+ # PyPI trusted publishing exchanges this OIDC token for a short-lived key.
130
+ id-token: write
131
+ steps:
132
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
133
+ with:
134
+ pattern: wheel-*
135
+ path: dist
136
+ merge-multiple: true
137
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
138
+ with:
139
+ name: sdist
140
+ path: dist
141
+ - name: Upload the wheels and sdist to PyPI
142
+ uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
143
+ with:
144
+ command: upload
145
+ # Re-runs after a partial upload keep the files PyPI already has.
146
+ args: --skip-existing --non-interactive dist/*
@@ -0,0 +1,21 @@
1
+ # Rust
2
+ target/
3
+
4
+ # Python
5
+ __pycache__/
6
+ *.py[cod]
7
+ *$py.class
8
+ *.so
9
+ *.pyd
10
+ *.dylib
11
+ .pytest_cache/
12
+ .mypy_cache/
13
+ .ruff_cache/
14
+ dist/
15
+ wheels/
16
+ *.egg-info/
17
+ .venv/
18
+ env/
19
+
20
+ # Output
21
+ visual/
@@ -0,0 +1,5 @@
1
+ # Bolt's Journal - Critical Performance Learnings
2
+
3
+ ## 2026-03-30 - Python-side Data Extent Calculation Bottleneck on Dense Data
4
+ **Learning:** Even though rendering itself is written in compiled Rust, Python-side figure preparation can become the dominant bottleneck when plotting dense datasets (e.g. 50k–100k points). Specifically, `Axes._extent_x` and `_extent_y` previously flattened all coordinate arrays into a giant Python list using `list.extend(np.asarray(arr).ravel())` before feeding it to `_span(xs)`, which converted the list back into a NumPy array. For 100k points, constructing and boxing 100,000 Python float objects twice (for X and Y limits) took ~24 ms, whereas the entire Rust rendering pass took ~12 ms.
5
+ **Action:** When computing extents across heterogeneous graphical primitives in Python, accumulate running `(min, max)` tuples directly using vectorized `fin = arr[np.isfinite(arr)]; (min(ext[0], fin.min()), max(ext[1], fin.max()))` per array rather than concatenating arrays into a Python list. This cut 100k line plot time by more than half (speedup vs matplotlib jumped from 5.0× to 10.2× on PNG and 5.0× to 27.9× on SVG).
@@ -0,0 +1,6 @@
1
+ # Sentinel's Journal - Critical Security Learnings
2
+
3
+ ## 2026-03-30 - Unbounded Canvas Dimensions Causing Out-Of-Memory / Integer Overflows
4
+ **Vulnerability:** Figure size (`figsize`) and resolution (`dpi`) were accepted without positive-finite validation or maximum bounds checking in both Python and Rust layers. Unchecked parameters allowed non-positive or excessive numbers, leading to integer overflow conversions or multi-terabyte buffer allocations in Rust rendering engines that crashed or aborted the host process (Denial of Service).
5
+ **Learning:** PyO3 bindings converting Python floats directly into integer pixel counts (`(round(w * dpi), round(h * dpi))`) without range validation can cause `OverflowError` on negative/infinite inputs and immediate OOM aborts on huge values in native rasterizers (`tiny-skia` / `resvg`).
6
+ **Prevention:** Validate dimension inputs at the Python API boundary (`_validate_dimension`) ensuring positive, finite numbers and reasonable upper bounds on pixel dimensions (32,768px) and total pixel count (2^28 px), and mirror the bounds checking defensively in Rust before memory allocations.