plotpress 0.23.2__tar.gz → 0.25.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.
- {plotpress-0.23.2 → plotpress-0.25.0}/PKG-INFO +228 -41
- {plotpress-0.23.2 → plotpress-0.25.0}/README.md +212 -37
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/__init__.py +4 -2
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/_interactive.py +22 -10
- plotpress-0.25.0/plotpress/_version.py +1 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/artists.py +24 -14
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/axes.py +76 -50
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/colors.py +31 -7
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/figure.py +28 -5
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/primitives.py +74 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/qt.py +9 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/raster.py +34 -54
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/style.py +27 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/svg.py +9 -17
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/ticker.py +18 -2
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/vega.py +90 -27
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/vega_lite.py +130 -47
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress.egg-info/PKG-INFO +228 -41
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress.egg-info/SOURCES.txt +3 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress.egg-info/requires.txt +15 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/pyproject.toml +26 -5
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_axes_api_audit.py +116 -5
- plotpress-0.25.0/tests/test_colors.py +79 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_input_validation.py +61 -8
- plotpress-0.25.0/tests/test_primitives.py +177 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_qt.py +28 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_render_all.py +3 -4
- plotpress-0.25.0/tests/test_style.py +92 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_svg_output.py +77 -6
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_vega_lite_output.py +39 -2
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_vega_output.py +100 -2
- plotpress-0.23.2/plotpress/_version.py +0 -1
- {plotpress-0.23.2 → plotpress-0.25.0}/LICENSE +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/_spectral.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/fonts/__init__.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/fonts/families.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/fonts/installed.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/fonts/metrics.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/png.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/polar.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress/transform.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress.egg-info/dependency_links.txt +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/plotpress.egg-info/top_level.txt +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/setup.cfg +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_adopt_axes.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_axes.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_distributions.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_fonts.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_load_data_xarray.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_matplotlib_gaps.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_matplotlib_parity.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_no_global_state.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_performance.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_pick_interactive.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_png.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_polar.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_python_compat.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_spectral.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_summary.py +0 -0
- {plotpress-0.23.2 → plotpress-0.25.0}/tests/test_units.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plotpress
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: A fast, figure-centric
|
|
3
|
+
Version: 0.25.0
|
|
4
|
+
Summary: A fast, figure-centric plotting library with a matplotlib-like API, no global state, and one figure exportable to SVG, PNG, PDF, interactive HTML, or Vega/Vega-Lite.
|
|
5
5
|
Author: plotpress contributors
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/jrvannucci/plotpress
|
|
@@ -20,10 +20,17 @@ Requires-Dist: pywebview>=4.0; extra == "gui"
|
|
|
20
20
|
Provides-Extra: qt
|
|
21
21
|
Requires-Dist: PyQt6>=6.4; extra == "qt"
|
|
22
22
|
Requires-Dist: PyQt6-WebEngine>=6.4; extra == "qt"
|
|
23
|
-
Provides-Extra: xarray
|
|
24
|
-
Requires-Dist: xarray>=2023.1; extra == "xarray"
|
|
25
23
|
Provides-Extra: jupyter
|
|
26
24
|
Requires-Dist: ipython>=7; extra == "jupyter"
|
|
25
|
+
Provides-Extra: viewers
|
|
26
|
+
Requires-Dist: plotpress[gui]; extra == "viewers"
|
|
27
|
+
Requires-Dist: plotpress[qt]; extra == "viewers"
|
|
28
|
+
Requires-Dist: plotpress[jupyter]; extra == "viewers"
|
|
29
|
+
Provides-Extra: xarray
|
|
30
|
+
Requires-Dist: xarray>=2023.1; extra == "xarray"
|
|
31
|
+
Provides-Extra: full
|
|
32
|
+
Requires-Dist: plotpress[viewers]; extra == "full"
|
|
33
|
+
Requires-Dist: plotpress[xarray]; extra == "full"
|
|
27
34
|
Provides-Extra: dev
|
|
28
35
|
Requires-Dist: pytest>=7; extra == "dev"
|
|
29
36
|
Provides-Extra: browser
|
|
@@ -42,14 +49,24 @@ Requires-Dist: polars>=1.0; extra == "docs"
|
|
|
42
49
|
Requires-Dist: adaptive>=1.0; extra == "docs"
|
|
43
50
|
Requires-Dist: joblib>=1.3; extra == "docs"
|
|
44
51
|
Requires-Dist: xarray>=2023.1; extra == "docs"
|
|
52
|
+
Provides-Extra: contrib
|
|
53
|
+
Requires-Dist: plotpress[dev]; extra == "contrib"
|
|
54
|
+
Requires-Dist: plotpress[browser]; extra == "contrib"
|
|
55
|
+
Requires-Dist: plotpress[bench]; extra == "contrib"
|
|
56
|
+
Requires-Dist: plotpress[docs]; extra == "contrib"
|
|
45
57
|
Dynamic: license-file
|
|
46
58
|
|
|
47
59
|
# plotpress
|
|
48
60
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
61
|
+
**Scientific plots you can explore, share, and reuse.**
|
|
62
|
+
|
|
63
|
+
A **fast, dependency-light** plotting library for scientific computing, with
|
|
64
|
+
a **matplotlib-shaped** API and **no compiled extension** — install it
|
|
65
|
+
anywhere Python does, from notebooks to CI pipelines to offline environments.
|
|
66
|
+
It renders one figure to **SVG, PNG, PDF, Vega/Vega-Lite, and self-contained
|
|
67
|
+
interactive HTML** — the HTML carrying a full pan/zoom, point-picking, and
|
|
68
|
+
annotation toolbar. **No global state**, either — a `Figure` owns its own
|
|
69
|
+
axes and its own `Style`.
|
|
53
70
|
|
|
54
71
|
```python
|
|
55
72
|
import plotpress
|
|
@@ -67,6 +84,187 @@ fig.save("out.html", interactive=True) # interactive toolbar: zoom / pick / a
|
|
|
67
84
|
fig.show() # native pop-up window
|
|
68
85
|
```
|
|
69
86
|
|
|
87
|
+
`out.html` above is a real, self-contained page — no server, no external JS —
|
|
88
|
+
with a toolbar over every axes in the figure at once. Self-contained means
|
|
89
|
+
genuinely shareable: the plotted data and the toolbar's own JS are both
|
|
90
|
+
inlined into that one file, so anyone can open and interact with it with
|
|
91
|
+
nothing installed on their end — no Python, no plotpress, no internet
|
|
92
|
+
connection, just a browser. Email it, drop it in a chat, put it on a USB
|
|
93
|
+
stick — it still works. Send someone a file, not a service they have to
|
|
94
|
+
install. PyPI/GitHub READMEs can't run the page's own script, so the four
|
|
95
|
+
GIFs below stand in for it; open one yourself (or click through to the
|
|
96
|
+
[real-applications gallery](https://jrvannucci.github.io/plotpress/auto_applications/index.html),
|
|
97
|
+
embedded exactly this way) and it's fully live.
|
|
98
|
+
|
|
99
|
+
**Pan / zoom**, working the same over every axes, not just the one under the cursor:
|
|
100
|
+
|
|
101
|
+

|
|
102
|
+
|
|
103
|
+
**Point picking**, reading a value off any axes — a mesh's `z`, not just a
|
|
104
|
+
line's `x`/`y` — then extracting every picked point as CSV/JSON:
|
|
105
|
+
|
|
106
|
+

|
|
107
|
+
|
|
108
|
+
**Annotation**, a free-form note whose label box drags independently of the
|
|
109
|
+
point it's pinned to:
|
|
110
|
+
|
|
111
|
+

|
|
112
|
+
|
|
113
|
+
**At scale**, every gesture above still works the same way on a figure with
|
|
114
|
+
hundreds of axes — zoom from the full grid into a handful of panels, pan,
|
|
115
|
+
pick a value, remove it, pick again and drag its label, pan to a distant
|
|
116
|
+
group, annotate, then back Home (the figure is
|
|
117
|
+
[`docs/examples/grouping/plot_13_full_scale_demo.py`](https://jrvannucci.github.io/plotpress/auto_examples/grouping/plot_13_full_scale_demo.html) —
|
|
118
|
+
500 `pcolormesh` panels, 250 groups, each with its own colorbar):
|
|
119
|
+
|
|
120
|
+

|
|
121
|
+
|
|
122
|
+
## One figure. Many destinations.
|
|
123
|
+
|
|
124
|
+
The same `Figure` built once from the matplotlib-shaped API renders to every
|
|
125
|
+
format below — no separate figure per output, no plugin to install:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
one Figure object
|
|
129
|
+
|
|
|
130
|
+
+---------------+---------------+-------+-------+---------------+---------------+
|
|
131
|
+
▼ ▼ ▼ ▼ ▼ ▼
|
|
132
|
+
.svg .png .pdf .html Vega Vega-Lite
|
|
133
|
+
(vector, (raster, (vector, (SVG + JS (v5 JSON, (v5 JSON, a
|
|
134
|
+
the core Pillow) svglib + inlined -- real pixel- stricter, more
|
|
135
|
+
format) reportlab) no server space marks) declarative
|
|
136
|
+
round trip) grammar)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`fig.save(path, ...)` dispatches on the file extension for the first four;
|
|
140
|
+
`fig.to_vega()` / `fig.to_vega_lite()` return a JSON specification as a plain
|
|
141
|
+
`dict` for a separate Vega/Vega-Lite runtime to render — a bridge from
|
|
142
|
+
scientific Python to web-native visualization, for handing a figure to an
|
|
143
|
+
existing Vega-based dashboard or notebook without rebuilding the plot from
|
|
144
|
+
scratch. See the
|
|
145
|
+
[architecture docs](https://jrvannucci.github.io/plotpress/user_guide/architecture.html)
|
|
146
|
+
for exactly how much of the rendering pipeline each of these six actually
|
|
147
|
+
shares, and where a format gets its own dedicated path instead.
|
|
148
|
+
|
|
149
|
+
## Reading a figure back out of HTML
|
|
150
|
+
|
|
151
|
+
A plot doesn't have to be a dead image. The interactive HTML above isn't a
|
|
152
|
+
one-way trip: it embeds the plotted data and the figure's own layout as JSON
|
|
153
|
+
alongside the SVG, so a later process — with none of the Python objects that
|
|
154
|
+
built it still around — can read a figure back out and rebuild it. The
|
|
155
|
+
figure becomes a portable representation of the data it displays, not just a
|
|
156
|
+
picture of it:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
a saved .html (Figure.save(path, interactive=True))
|
|
160
|
+
embeds <script id="plotpress-pick"> and
|
|
161
|
+
id="plotpress-layout"> per figure
|
|
162
|
+
|
|
|
163
|
+
▼
|
|
164
|
+
plotpress.load_data(path)
|
|
165
|
+
parses that embedded JSON back out
|
|
166
|
+
|
|
|
167
|
+
+--------------+--------------+
|
|
168
|
+
▼ ▼
|
|
169
|
+
"layout" "axes"
|
|
170
|
+
(grid shape, each (recovered series/
|
|
171
|
+
axes' own decorations, mesh/pie data per axes,
|
|
172
|
+
groups, sup-title) keyed by title)
|
|
173
|
+
|
|
174
|
+
|
|
|
175
|
+
▼
|
|
176
|
+
plotpress.subplots_from_layout(layout)
|
|
177
|
+
rebuilds the grid and every axes' own
|
|
178
|
+
decorations -- not the plotted data itself
|
|
179
|
+
|
|
|
180
|
+
▼
|
|
181
|
+
a new, already-labeled Figure -- ready for
|
|
182
|
+
the caller to replot the recovered "axes"
|
|
183
|
+
data back into
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
A freeform `Figure.add_axes()` rect, an inset, or a colorbar axes has no grid
|
|
187
|
+
cell to rebuild from — its index is listed in `layout["omitted_axes"]`
|
|
188
|
+
instead of silently vanishing. See the
|
|
189
|
+
[full API and worked examples](https://jrvannucci.github.io/plotpress/usage.html#reading-html-data)
|
|
190
|
+
for the round trip end to end.
|
|
191
|
+
|
|
192
|
+
For the common case of a *uniform* grid — every axes its own single
|
|
193
|
+
`pcolormesh` or line series, all the same shape — `plotpress.load_data_xarray()`
|
|
194
|
+
skips the title-keyed dict above entirely and reads the same file straight
|
|
195
|
+
into one `xarray.Dataset` indexed by row/column instead, with the recovered
|
|
196
|
+
layout still available under `ds.attrs["layout"]` for
|
|
197
|
+
`plotpress.subplots_from_layout()`. See the
|
|
198
|
+
[data round-trip example](https://jrvannucci.github.io/plotpress/auto_examples/data_roundtrip/index.html)
|
|
199
|
+
for both paths worked through end to end.
|
|
200
|
+
|
|
201
|
+
## What makes it different
|
|
202
|
+
|
|
203
|
+
1. **No `pyplot`, no globals.** There is no "current figure/axes" and no global
|
|
204
|
+
`rcParams`. A `Figure` owns its axes and its own `Style`; two figures never
|
|
205
|
+
share mutable state. `plotpress.subplots()` returns `(fig, axes)` just like
|
|
206
|
+
`plt.subplots()` — but touches no global state.
|
|
207
|
+
2. **matplotlib-*shaped* API** so moving code either direction is mostly
|
|
208
|
+
mechanical: `Figure`/`Axes`, `plot`, `scatter`, `pcolormesh`,
|
|
209
|
+
`set_xlabel/ylabel/title`, `set_xlim/ylim`, `grid`, `legend`, `colorbar`. It
|
|
210
|
+
is shaped, not drop-in — there's no `pyplot` state machine and not every
|
|
211
|
+
matplotlib keyword is present; treat the gallery as the compatibility surface.
|
|
212
|
+
3. **SVG-first + built for speed.** Output is vector SVG; only mesh/image layers
|
|
213
|
+
are rasterized (as a single embedded `<image>`, not thousands of rects). Each
|
|
214
|
+
series is one `<path>`. It's **pure Python + NumPy** — vectorized coordinate
|
|
215
|
+
formatting, min/max-decimated huge lines — with **no compiled extension**, so
|
|
216
|
+
it installs everywhere pip does.
|
|
217
|
+
|
|
218
|
+
## Scientific visualization, end to end
|
|
219
|
+
|
|
220
|
+
plotpress is designed around the way scientific figures actually get used:
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
Explore ──► Analyze ──► Visualize ──► Share ──► Publish ──► Archive ──► Reuse
|
|
224
|
+
▲ │
|
|
225
|
+
└───────────────────────────────────────────────────────────────────────┘
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
- **Explore** — interact with a measurement, simulation, image, or spectrum
|
|
229
|
+
while building an experiment or analysis (pan/zoom, point-picking).
|
|
230
|
+
- **Analyze** — the same figure workflow for signal processing, statistics,
|
|
231
|
+
and multidimensional data.
|
|
232
|
+
- **Visualize** — build and style the figure: plot, arrange subplots, apply
|
|
233
|
+
colormaps and normalization.
|
|
234
|
+
- **Share** — hand an interactive HTML figure to a collaborator — no server,
|
|
235
|
+
nothing to install on their end.
|
|
236
|
+
- **Publish** — export a publication figure as SVG/PNG/PDF, or publish a
|
|
237
|
+
self-contained HTML figure that carries its own data.
|
|
238
|
+
- **Archive** — keep the figure and its plotted data together in one
|
|
239
|
+
portable file.
|
|
240
|
+
- **Reuse** — load the figure back with `plotpress.load_data()`, recover the
|
|
241
|
+
data, and analyze or replot it — the cycle starts again from Explore.
|
|
242
|
+
|
|
243
|
+
## Made for real scientific workloads
|
|
244
|
+
|
|
245
|
+
plotpress isn't just a handful of basic plotting primitives — it covers the
|
|
246
|
+
kinds of figures scientists actually build. See
|
|
247
|
+
[Supported plot types](#supported-plot-types) below for the full method
|
|
248
|
+
list; the categories:
|
|
249
|
+
|
|
250
|
+
- **Signal processing** — power spectral density, cross-spectral density,
|
|
251
|
+
coherence, spectrograms, autocorrelation, cross-correlation, and
|
|
252
|
+
magnitude/angle/phase spectra (pure-NumPy Welch estimators).
|
|
253
|
+
- **2-D and gridded data** — images, meshes (including curvilinear grids),
|
|
254
|
+
contours, vector fields, and logarithmic/power/symlog normalization for
|
|
255
|
+
large scientific fields.
|
|
256
|
+
- **Statistical visualization** — histograms, 2-D histograms, box plots,
|
|
257
|
+
violin plots, ECDFs, KDEs, event rasters, error bars, and hexbins.
|
|
258
|
+
- **Complex figures** — subplot grids, shared axes, colorbars (including one
|
|
259
|
+
shared across several axes), secondary axes, inset axes, grouped panels,
|
|
260
|
+
figure-level titles/labels, and mixed layouts.
|
|
261
|
+
- **Animation** — animated lines and meshes with frame sliders, exportable
|
|
262
|
+
as self-contained looping GIFs.
|
|
263
|
+
- **Large figures** — built to keep object and output-node counts under
|
|
264
|
+
control, so a very large multi-panel figure stays practical; see the "At
|
|
265
|
+
scale" GIF above (500 `pcolormesh` panels, 250 groups, each with its own
|
|
266
|
+
colorbar).
|
|
267
|
+
|
|
70
268
|
## What it is for
|
|
71
269
|
|
|
72
270
|
plotpress is **not a matplotlib replacement**, and it does not try to match
|
|
@@ -74,13 +272,15 @@ matplotlib's twenty years of breadth (no geographic projections or triangulated
|
|
|
74
272
|
grids, one font-metric family, no 3-D, and its polar axes project onto the 2-D
|
|
75
273
|
core rather than a dedicated pipeline — see [Supported plot types](#supported-plot-types)
|
|
76
274
|
below). It aims at a narrower, underserved spot: plotting where matplotlib's
|
|
77
|
-
install footprint or global state gets in the way
|
|
275
|
+
install footprint or global state gets in the way — scientific software
|
|
276
|
+
doesn't always run on a developer laptop.
|
|
78
277
|
|
|
79
278
|
**Reach for plotpress when you want to:**
|
|
80
279
|
|
|
81
|
-
- **Ship plots from a constrained runtime** — locked-down
|
|
82
|
-
containers, Pyodide/WASM,
|
|
83
|
-
|
|
280
|
+
- **Ship plots from a constrained runtime** — locked-down networks, offline
|
|
281
|
+
systems, minimal containers, Pyodide/WASM, CI, or shared computing
|
|
282
|
+
environments — where a pure-Python + NumPy install with no build toolchain
|
|
283
|
+
and no per-platform wheels matters.
|
|
84
284
|
- **Embed in web apps or notebooks** as SVG or self-contained interactive HTML
|
|
85
285
|
whose JS makes no external requests (works under strict CSPs like Jupyter).
|
|
86
286
|
- **Write library or server code** that should never touch a global "current
|
|
@@ -98,38 +298,24 @@ with one figure per method, and
|
|
|
98
298
|
grouped by field, each explaining the axis, scale and colour choices the data
|
|
99
299
|
forces. Every application figure is embedded live, with the interactive toolbar.
|
|
100
300
|
|
|
101
|
-
## What makes it different
|
|
102
|
-
|
|
103
|
-
1. **No `pyplot`, no globals.** There is no "current figure/axes" and no global
|
|
104
|
-
`rcParams`. A `Figure` owns its axes and its own `Style`; two figures never
|
|
105
|
-
share mutable state. `plotpress.subplots()` returns `(fig, axes)` just like
|
|
106
|
-
`plt.subplots()` — but touches no global state.
|
|
107
|
-
2. **matplotlib-*shaped* API** so moving code either direction is mostly
|
|
108
|
-
mechanical: `Figure`/`Axes`, `plot`, `scatter`, `pcolormesh`,
|
|
109
|
-
`set_xlabel/ylabel/title`, `set_xlim/ylim`, `grid`, `legend`, `colorbar`. It
|
|
110
|
-
is shaped, not drop-in — there's no `pyplot` state machine and not every
|
|
111
|
-
matplotlib keyword is present; treat the gallery as the compatibility surface.
|
|
112
|
-
3. **SVG-first + built for speed.** Output is vector SVG; only mesh/image layers
|
|
113
|
-
are rasterized (as a single embedded `<image>`, not thousands of rects). Each
|
|
114
|
-
series is one `<path>`. It's **pure Python + NumPy** — vectorized coordinate
|
|
115
|
-
formatting, min/max-decimated huge lines — with **no compiled extension**, so
|
|
116
|
-
it installs everywhere pip does.
|
|
117
|
-
|
|
118
301
|
## Install
|
|
119
302
|
|
|
120
303
|
```bash
|
|
121
304
|
pip install plotpress # SVG + interactive HTML + PNG/PDF export
|
|
122
|
-
pip install plotpress[
|
|
123
|
-
pip install plotpress[
|
|
124
|
-
pip install plotpress[dev] # + pytest (contributors)
|
|
125
|
-
pip install plotpress[bench] # + matplotlib (benchmark comparison)
|
|
305
|
+
pip install plotpress[full] # + every real end-user feature: viewers (gui, qt, jupyter) + xarray
|
|
306
|
+
pip install plotpress[contrib] # + everything a contributor needs (dev, browser, bench, docs)
|
|
126
307
|
```
|
|
127
308
|
|
|
128
309
|
The standard install covers **all file output** -- SVG, interactive HTML, PNG and
|
|
129
310
|
vector PDF -- with pure-wheel dependencies that install everywhere (servers, CI,
|
|
130
|
-
notebooks).
|
|
131
|
-
|
|
132
|
-
|
|
311
|
+
notebooks). `[full]` is likely what you want if you're reaching for more than
|
|
312
|
+
that at all; each of its pieces (`[gui]`, `[qt]`, `[jupyter]`, `[xarray]`, or all
|
|
313
|
+
three viewers via `[viewers]`) also installs on its own, or combined in one
|
|
314
|
+
command (`pip install plotpress[gui,xarray]`), for anyone who wants less than
|
|
315
|
+
the full bundle -- `[gui]`, for instance, pulls a desktop webview stack for the
|
|
316
|
+
native `fig.show()` window that a `[qt]`-only or `[jupyter]`-only install has
|
|
317
|
+
no reason to carry. See [Installation](https://jrvannucci.github.io/plotpress/installation.html)
|
|
318
|
+
for the full extras reference.
|
|
133
319
|
|
|
134
320
|
## Output surfaces (one scene, many targets)
|
|
135
321
|
|
|
@@ -180,11 +366,12 @@ grouped into Axes, Point Picking, Annotate, and File menus:
|
|
|
180
366
|
- **Save**/**Save As** — persist pan/zoom, every pin/annotation, and every
|
|
181
367
|
toggle above to a new (or the same) self-contained HTML file.
|
|
182
368
|
|
|
183
|
-
`fig.to_html()`/`fig.save(..., interactive=True)` accept `
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
369
|
+
`fig.to_html()`/`fig.save(..., interactive=True)` also accept `extra_js` — a
|
|
370
|
+
raw JS string inlined as its own `<script>`, for adding a custom tool to the
|
|
371
|
+
same toolbar menu — plus `pick_precision` (decimal places embedded per
|
|
372
|
+
value) and `pick_max_mesh_cells`/`pick_max_points` (a hard cap on how much of
|
|
373
|
+
each mesh/series is embedded for picking, per artist) to bound the
|
|
374
|
+
interactive payload for mesh- or point-heavy figures.
|
|
188
375
|
|
|
189
376
|
Per axes: `ax.set_pickable(False)` excludes that axes from Point Picking
|
|
190
377
|
(Axis Span/Zoom/Annotation still work everywhere), and
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
# plotpress
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
**Scientific plots you can explore, share, and reuse.**
|
|
4
|
+
|
|
5
|
+
A **fast, dependency-light** plotting library for scientific computing, with
|
|
6
|
+
a **matplotlib-shaped** API and **no compiled extension** — install it
|
|
7
|
+
anywhere Python does, from notebooks to CI pipelines to offline environments.
|
|
8
|
+
It renders one figure to **SVG, PNG, PDF, Vega/Vega-Lite, and self-contained
|
|
9
|
+
interactive HTML** — the HTML carrying a full pan/zoom, point-picking, and
|
|
10
|
+
annotation toolbar. **No global state**, either — a `Figure` owns its own
|
|
11
|
+
axes and its own `Style`.
|
|
7
12
|
|
|
8
13
|
```python
|
|
9
14
|
import plotpress
|
|
@@ -21,6 +26,187 @@ fig.save("out.html", interactive=True) # interactive toolbar: zoom / pick / a
|
|
|
21
26
|
fig.show() # native pop-up window
|
|
22
27
|
```
|
|
23
28
|
|
|
29
|
+
`out.html` above is a real, self-contained page — no server, no external JS —
|
|
30
|
+
with a toolbar over every axes in the figure at once. Self-contained means
|
|
31
|
+
genuinely shareable: the plotted data and the toolbar's own JS are both
|
|
32
|
+
inlined into that one file, so anyone can open and interact with it with
|
|
33
|
+
nothing installed on their end — no Python, no plotpress, no internet
|
|
34
|
+
connection, just a browser. Email it, drop it in a chat, put it on a USB
|
|
35
|
+
stick — it still works. Send someone a file, not a service they have to
|
|
36
|
+
install. PyPI/GitHub READMEs can't run the page's own script, so the four
|
|
37
|
+
GIFs below stand in for it; open one yourself (or click through to the
|
|
38
|
+
[real-applications gallery](https://jrvannucci.github.io/plotpress/auto_applications/index.html),
|
|
39
|
+
embedded exactly this way) and it's fully live.
|
|
40
|
+
|
|
41
|
+
**Pan / zoom**, working the same over every axes, not just the one under the cursor:
|
|
42
|
+
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
**Point picking**, reading a value off any axes — a mesh's `z`, not just a
|
|
46
|
+
line's `x`/`y` — then extracting every picked point as CSV/JSON:
|
|
47
|
+
|
|
48
|
+

|
|
49
|
+
|
|
50
|
+
**Annotation**, a free-form note whose label box drags independently of the
|
|
51
|
+
point it's pinned to:
|
|
52
|
+
|
|
53
|
+

|
|
54
|
+
|
|
55
|
+
**At scale**, every gesture above still works the same way on a figure with
|
|
56
|
+
hundreds of axes — zoom from the full grid into a handful of panels, pan,
|
|
57
|
+
pick a value, remove it, pick again and drag its label, pan to a distant
|
|
58
|
+
group, annotate, then back Home (the figure is
|
|
59
|
+
[`docs/examples/grouping/plot_13_full_scale_demo.py`](https://jrvannucci.github.io/plotpress/auto_examples/grouping/plot_13_full_scale_demo.html) —
|
|
60
|
+
500 `pcolormesh` panels, 250 groups, each with its own colorbar):
|
|
61
|
+
|
|
62
|
+

|
|
63
|
+
|
|
64
|
+
## One figure. Many destinations.
|
|
65
|
+
|
|
66
|
+
The same `Figure` built once from the matplotlib-shaped API renders to every
|
|
67
|
+
format below — no separate figure per output, no plugin to install:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
one Figure object
|
|
71
|
+
|
|
|
72
|
+
+---------------+---------------+-------+-------+---------------+---------------+
|
|
73
|
+
▼ ▼ ▼ ▼ ▼ ▼
|
|
74
|
+
.svg .png .pdf .html Vega Vega-Lite
|
|
75
|
+
(vector, (raster, (vector, (SVG + JS (v5 JSON, (v5 JSON, a
|
|
76
|
+
the core Pillow) svglib + inlined -- real pixel- stricter, more
|
|
77
|
+
format) reportlab) no server space marks) declarative
|
|
78
|
+
round trip) grammar)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`fig.save(path, ...)` dispatches on the file extension for the first four;
|
|
82
|
+
`fig.to_vega()` / `fig.to_vega_lite()` return a JSON specification as a plain
|
|
83
|
+
`dict` for a separate Vega/Vega-Lite runtime to render — a bridge from
|
|
84
|
+
scientific Python to web-native visualization, for handing a figure to an
|
|
85
|
+
existing Vega-based dashboard or notebook without rebuilding the plot from
|
|
86
|
+
scratch. See the
|
|
87
|
+
[architecture docs](https://jrvannucci.github.io/plotpress/user_guide/architecture.html)
|
|
88
|
+
for exactly how much of the rendering pipeline each of these six actually
|
|
89
|
+
shares, and where a format gets its own dedicated path instead.
|
|
90
|
+
|
|
91
|
+
## Reading a figure back out of HTML
|
|
92
|
+
|
|
93
|
+
A plot doesn't have to be a dead image. The interactive HTML above isn't a
|
|
94
|
+
one-way trip: it embeds the plotted data and the figure's own layout as JSON
|
|
95
|
+
alongside the SVG, so a later process — with none of the Python objects that
|
|
96
|
+
built it still around — can read a figure back out and rebuild it. The
|
|
97
|
+
figure becomes a portable representation of the data it displays, not just a
|
|
98
|
+
picture of it:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
a saved .html (Figure.save(path, interactive=True))
|
|
102
|
+
embeds <script id="plotpress-pick"> and
|
|
103
|
+
id="plotpress-layout"> per figure
|
|
104
|
+
|
|
|
105
|
+
▼
|
|
106
|
+
plotpress.load_data(path)
|
|
107
|
+
parses that embedded JSON back out
|
|
108
|
+
|
|
|
109
|
+
+--------------+--------------+
|
|
110
|
+
▼ ▼
|
|
111
|
+
"layout" "axes"
|
|
112
|
+
(grid shape, each (recovered series/
|
|
113
|
+
axes' own decorations, mesh/pie data per axes,
|
|
114
|
+
groups, sup-title) keyed by title)
|
|
115
|
+
|
|
116
|
+
|
|
|
117
|
+
▼
|
|
118
|
+
plotpress.subplots_from_layout(layout)
|
|
119
|
+
rebuilds the grid and every axes' own
|
|
120
|
+
decorations -- not the plotted data itself
|
|
121
|
+
|
|
|
122
|
+
▼
|
|
123
|
+
a new, already-labeled Figure -- ready for
|
|
124
|
+
the caller to replot the recovered "axes"
|
|
125
|
+
data back into
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
A freeform `Figure.add_axes()` rect, an inset, or a colorbar axes has no grid
|
|
129
|
+
cell to rebuild from — its index is listed in `layout["omitted_axes"]`
|
|
130
|
+
instead of silently vanishing. See the
|
|
131
|
+
[full API and worked examples](https://jrvannucci.github.io/plotpress/usage.html#reading-html-data)
|
|
132
|
+
for the round trip end to end.
|
|
133
|
+
|
|
134
|
+
For the common case of a *uniform* grid — every axes its own single
|
|
135
|
+
`pcolormesh` or line series, all the same shape — `plotpress.load_data_xarray()`
|
|
136
|
+
skips the title-keyed dict above entirely and reads the same file straight
|
|
137
|
+
into one `xarray.Dataset` indexed by row/column instead, with the recovered
|
|
138
|
+
layout still available under `ds.attrs["layout"]` for
|
|
139
|
+
`plotpress.subplots_from_layout()`. See the
|
|
140
|
+
[data round-trip example](https://jrvannucci.github.io/plotpress/auto_examples/data_roundtrip/index.html)
|
|
141
|
+
for both paths worked through end to end.
|
|
142
|
+
|
|
143
|
+
## What makes it different
|
|
144
|
+
|
|
145
|
+
1. **No `pyplot`, no globals.** There is no "current figure/axes" and no global
|
|
146
|
+
`rcParams`. A `Figure` owns its axes and its own `Style`; two figures never
|
|
147
|
+
share mutable state. `plotpress.subplots()` returns `(fig, axes)` just like
|
|
148
|
+
`plt.subplots()` — but touches no global state.
|
|
149
|
+
2. **matplotlib-*shaped* API** so moving code either direction is mostly
|
|
150
|
+
mechanical: `Figure`/`Axes`, `plot`, `scatter`, `pcolormesh`,
|
|
151
|
+
`set_xlabel/ylabel/title`, `set_xlim/ylim`, `grid`, `legend`, `colorbar`. It
|
|
152
|
+
is shaped, not drop-in — there's no `pyplot` state machine and not every
|
|
153
|
+
matplotlib keyword is present; treat the gallery as the compatibility surface.
|
|
154
|
+
3. **SVG-first + built for speed.** Output is vector SVG; only mesh/image layers
|
|
155
|
+
are rasterized (as a single embedded `<image>`, not thousands of rects). Each
|
|
156
|
+
series is one `<path>`. It's **pure Python + NumPy** — vectorized coordinate
|
|
157
|
+
formatting, min/max-decimated huge lines — with **no compiled extension**, so
|
|
158
|
+
it installs everywhere pip does.
|
|
159
|
+
|
|
160
|
+
## Scientific visualization, end to end
|
|
161
|
+
|
|
162
|
+
plotpress is designed around the way scientific figures actually get used:
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
Explore ──► Analyze ──► Visualize ──► Share ──► Publish ──► Archive ──► Reuse
|
|
166
|
+
▲ │
|
|
167
|
+
└───────────────────────────────────────────────────────────────────────┘
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
- **Explore** — interact with a measurement, simulation, image, or spectrum
|
|
171
|
+
while building an experiment or analysis (pan/zoom, point-picking).
|
|
172
|
+
- **Analyze** — the same figure workflow for signal processing, statistics,
|
|
173
|
+
and multidimensional data.
|
|
174
|
+
- **Visualize** — build and style the figure: plot, arrange subplots, apply
|
|
175
|
+
colormaps and normalization.
|
|
176
|
+
- **Share** — hand an interactive HTML figure to a collaborator — no server,
|
|
177
|
+
nothing to install on their end.
|
|
178
|
+
- **Publish** — export a publication figure as SVG/PNG/PDF, or publish a
|
|
179
|
+
self-contained HTML figure that carries its own data.
|
|
180
|
+
- **Archive** — keep the figure and its plotted data together in one
|
|
181
|
+
portable file.
|
|
182
|
+
- **Reuse** — load the figure back with `plotpress.load_data()`, recover the
|
|
183
|
+
data, and analyze or replot it — the cycle starts again from Explore.
|
|
184
|
+
|
|
185
|
+
## Made for real scientific workloads
|
|
186
|
+
|
|
187
|
+
plotpress isn't just a handful of basic plotting primitives — it covers the
|
|
188
|
+
kinds of figures scientists actually build. See
|
|
189
|
+
[Supported plot types](#supported-plot-types) below for the full method
|
|
190
|
+
list; the categories:
|
|
191
|
+
|
|
192
|
+
- **Signal processing** — power spectral density, cross-spectral density,
|
|
193
|
+
coherence, spectrograms, autocorrelation, cross-correlation, and
|
|
194
|
+
magnitude/angle/phase spectra (pure-NumPy Welch estimators).
|
|
195
|
+
- **2-D and gridded data** — images, meshes (including curvilinear grids),
|
|
196
|
+
contours, vector fields, and logarithmic/power/symlog normalization for
|
|
197
|
+
large scientific fields.
|
|
198
|
+
- **Statistical visualization** — histograms, 2-D histograms, box plots,
|
|
199
|
+
violin plots, ECDFs, KDEs, event rasters, error bars, and hexbins.
|
|
200
|
+
- **Complex figures** — subplot grids, shared axes, colorbars (including one
|
|
201
|
+
shared across several axes), secondary axes, inset axes, grouped panels,
|
|
202
|
+
figure-level titles/labels, and mixed layouts.
|
|
203
|
+
- **Animation** — animated lines and meshes with frame sliders, exportable
|
|
204
|
+
as self-contained looping GIFs.
|
|
205
|
+
- **Large figures** — built to keep object and output-node counts under
|
|
206
|
+
control, so a very large multi-panel figure stays practical; see the "At
|
|
207
|
+
scale" GIF above (500 `pcolormesh` panels, 250 groups, each with its own
|
|
208
|
+
colorbar).
|
|
209
|
+
|
|
24
210
|
## What it is for
|
|
25
211
|
|
|
26
212
|
plotpress is **not a matplotlib replacement**, and it does not try to match
|
|
@@ -28,13 +214,15 @@ matplotlib's twenty years of breadth (no geographic projections or triangulated
|
|
|
28
214
|
grids, one font-metric family, no 3-D, and its polar axes project onto the 2-D
|
|
29
215
|
core rather than a dedicated pipeline — see [Supported plot types](#supported-plot-types)
|
|
30
216
|
below). It aims at a narrower, underserved spot: plotting where matplotlib's
|
|
31
|
-
install footprint or global state gets in the way
|
|
217
|
+
install footprint or global state gets in the way — scientific software
|
|
218
|
+
doesn't always run on a developer laptop.
|
|
32
219
|
|
|
33
220
|
**Reach for plotpress when you want to:**
|
|
34
221
|
|
|
35
|
-
- **Ship plots from a constrained runtime** — locked-down
|
|
36
|
-
containers, Pyodide/WASM,
|
|
37
|
-
|
|
222
|
+
- **Ship plots from a constrained runtime** — locked-down networks, offline
|
|
223
|
+
systems, minimal containers, Pyodide/WASM, CI, or shared computing
|
|
224
|
+
environments — where a pure-Python + NumPy install with no build toolchain
|
|
225
|
+
and no per-platform wheels matters.
|
|
38
226
|
- **Embed in web apps or notebooks** as SVG or self-contained interactive HTML
|
|
39
227
|
whose JS makes no external requests (works under strict CSPs like Jupyter).
|
|
40
228
|
- **Write library or server code** that should never touch a global "current
|
|
@@ -52,38 +240,24 @@ with one figure per method, and
|
|
|
52
240
|
grouped by field, each explaining the axis, scale and colour choices the data
|
|
53
241
|
forces. Every application figure is embedded live, with the interactive toolbar.
|
|
54
242
|
|
|
55
|
-
## What makes it different
|
|
56
|
-
|
|
57
|
-
1. **No `pyplot`, no globals.** There is no "current figure/axes" and no global
|
|
58
|
-
`rcParams`. A `Figure` owns its axes and its own `Style`; two figures never
|
|
59
|
-
share mutable state. `plotpress.subplots()` returns `(fig, axes)` just like
|
|
60
|
-
`plt.subplots()` — but touches no global state.
|
|
61
|
-
2. **matplotlib-*shaped* API** so moving code either direction is mostly
|
|
62
|
-
mechanical: `Figure`/`Axes`, `plot`, `scatter`, `pcolormesh`,
|
|
63
|
-
`set_xlabel/ylabel/title`, `set_xlim/ylim`, `grid`, `legend`, `colorbar`. It
|
|
64
|
-
is shaped, not drop-in — there's no `pyplot` state machine and not every
|
|
65
|
-
matplotlib keyword is present; treat the gallery as the compatibility surface.
|
|
66
|
-
3. **SVG-first + built for speed.** Output is vector SVG; only mesh/image layers
|
|
67
|
-
are rasterized (as a single embedded `<image>`, not thousands of rects). Each
|
|
68
|
-
series is one `<path>`. It's **pure Python + NumPy** — vectorized coordinate
|
|
69
|
-
formatting, min/max-decimated huge lines — with **no compiled extension**, so
|
|
70
|
-
it installs everywhere pip does.
|
|
71
|
-
|
|
72
243
|
## Install
|
|
73
244
|
|
|
74
245
|
```bash
|
|
75
246
|
pip install plotpress # SVG + interactive HTML + PNG/PDF export
|
|
76
|
-
pip install plotpress[
|
|
77
|
-
pip install plotpress[
|
|
78
|
-
pip install plotpress[dev] # + pytest (contributors)
|
|
79
|
-
pip install plotpress[bench] # + matplotlib (benchmark comparison)
|
|
247
|
+
pip install plotpress[full] # + every real end-user feature: viewers (gui, qt, jupyter) + xarray
|
|
248
|
+
pip install plotpress[contrib] # + everything a contributor needs (dev, browser, bench, docs)
|
|
80
249
|
```
|
|
81
250
|
|
|
82
251
|
The standard install covers **all file output** -- SVG, interactive HTML, PNG and
|
|
83
252
|
vector PDF -- with pure-wheel dependencies that install everywhere (servers, CI,
|
|
84
|
-
notebooks).
|
|
85
|
-
|
|
86
|
-
|
|
253
|
+
notebooks). `[full]` is likely what you want if you're reaching for more than
|
|
254
|
+
that at all; each of its pieces (`[gui]`, `[qt]`, `[jupyter]`, `[xarray]`, or all
|
|
255
|
+
three viewers via `[viewers]`) also installs on its own, or combined in one
|
|
256
|
+
command (`pip install plotpress[gui,xarray]`), for anyone who wants less than
|
|
257
|
+
the full bundle -- `[gui]`, for instance, pulls a desktop webview stack for the
|
|
258
|
+
native `fig.show()` window that a `[qt]`-only or `[jupyter]`-only install has
|
|
259
|
+
no reason to carry. See [Installation](https://jrvannucci.github.io/plotpress/installation.html)
|
|
260
|
+
for the full extras reference.
|
|
87
261
|
|
|
88
262
|
## Output surfaces (one scene, many targets)
|
|
89
263
|
|
|
@@ -134,11 +308,12 @@ grouped into Axes, Point Picking, Annotate, and File menus:
|
|
|
134
308
|
- **Save**/**Save As** — persist pan/zoom, every pin/annotation, and every
|
|
135
309
|
toggle above to a new (or the same) self-contained HTML file.
|
|
136
310
|
|
|
137
|
-
`fig.to_html()`/`fig.save(..., interactive=True)` accept `
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
311
|
+
`fig.to_html()`/`fig.save(..., interactive=True)` also accept `extra_js` — a
|
|
312
|
+
raw JS string inlined as its own `<script>`, for adding a custom tool to the
|
|
313
|
+
same toolbar menu — plus `pick_precision` (decimal places embedded per
|
|
314
|
+
value) and `pick_max_mesh_cells`/`pick_max_points` (a hard cap on how much of
|
|
315
|
+
each mesh/series is embedded for picking, per artist) to bound the
|
|
316
|
+
interactive payload for mesh- or point-heavy figures.
|
|
142
317
|
|
|
143
318
|
Per axes: `ax.set_pickable(False)` excludes that axes from Point Picking
|
|
144
319
|
(Axis Span/Zoom/Annotation still work everywhere), and
|
|
@@ -10,8 +10,10 @@ Distinct from matplotlib in three ways:
|
|
|
10
10
|
``scatter``, ``pcolormesh``, ``set_xlabel``, ``legend`` mirror matplotlib so
|
|
11
11
|
existing code is easy to port. ``plotpress.subplots(...)`` returns
|
|
12
12
|
``(fig, axes)`` just like ``plt.subplots(...)`` -- minus the globals.
|
|
13
|
-
3. **SVG-first
|
|
14
|
-
mesh/image layers)
|
|
13
|
+
3. **SVG-first, fast, and multi-format.** Output is vector SVG (with embedded
|
|
14
|
+
raster only for mesh/image layers) -- and from that same figure, also PNG,
|
|
15
|
+
PDF, self-contained interactive HTML (a full pan/zoom/pick/annotate
|
|
16
|
+
toolbar), or a Vega/Vega-Lite JSON spec. The hot paths are vectorized in
|
|
15
17
|
NumPy and huge lines are decimated, so it is fast in **pure Python** -- no
|
|
16
18
|
compiled extension, installs everywhere pip does.
|
|
17
19
|
|