plotpress
A fast, dependency-light plotting library for scientific computing, with a matplotlib-shaped API and no compiled extension – so it installs everywhere Python does, from notebooks and scientific applications to CI pipelines and offline environments.
import plotpress
import numpy as np
fig, ax = plotpress.subplots()
x = np.linspace(0, 4 * np.pi, 400)
ax.plot(x, np.sin(x), label="sin")
ax.plot(x, np.cos(x), "--", label="cos")
ax.set_xlabel("x")
ax.set_ylabel("amplitude")
ax.legend()
fig.save("figure.svg") # static vector SVG
fig.save("figure.png") # raster PNG
fig.save("figure.html", interactive=True) # self-contained interactive toolbar
One figure. Many destinations.
Build the Figure once from the matplotlib-shaped API, then choose the
output when you need it – no separate figure per format, no plugin to
install:
one Figure object
│
┌───────────────┬───────────────┬───────┴───────┬───────────────┬───────────────┐
▼ ▼ ▼ ▼ ▼ ▼
.svg .png .pdf .html Vega Vega-Lite
(vector, (raster, (vector, (SVG + JS (v5 JSON, (v5 JSON, a
the core Pillow) svglib + inlined -- real pixel- stricter, more
format) reportlab) no server space marks) declarative
round trip) grammar)
fig.save(path, ...) dispatches on the file extension for the first
four; fig.to_vega() / fig.to_vega_lite() return a JSON
specification as a plain dict for a separate Vega/Vega-Lite runtime to
render – a bridge to web-native visualization for handing a figure to an
existing Vega-based dashboard or notebook, without rebuilding the plot from
scratch. See How it works for exactly how much of the
rendering pipeline each of these six actually shares, and where a format
gets its own dedicated path instead.
A plot doesn’t have to be a dead image.
A PNG tells someone what your data looked like. An interactive plotpress figure lets them explore what was plotted – and recover the data behind it.
When you save an interactive HTML figure, plotpress embeds the plotted data alongside the figure itself. The result is one self-contained file: open it in a browser, attach it to a paper, or archive it – no server, no plotpress, no Python needed to view it. Months later, the same file can still be read back with Python:
a saved .html (Figure.save(path, interactive=True))
embeds <script id="plotpress-pick"> and
id="plotpress-layout"> per figure
│
▼
plotpress.load_data(path)
parses that embedded JSON back out
│
┌──────────────┴──────────────┐
▼ ▼
"template" "axes"
(grid shape, spines, (recovered series/
tick overrides, groups, mesh/pie data per axes,
overlays, Style, ...) keyed by title)
│
▼
plotpress.figure_from_template(template)
rebuilds the grid, every axes' own
decorations/styling, and its overlays --
not the plotted data itself
│
▼
a new, already-styled Figure -- ready for
the caller to replot the recovered "axes"
data back into
The figure becomes more than an image – it becomes a portable
representation of the data it displays, ready to inspect, transform, or
replot. A freeform add_axes() rect has no
grid cell to rebuild from – its index is listed in
template["omitted_axes"] instead of silently vanishing. See
Reading data back out of a saved HTML for the full API and
Reloading data from a saved HTML for worked examples.
For the common case of a uniform grid – every axes its own single
pcolormesh or line series, all the same shape –
load_data_xarray() skips the title-keyed dict above
entirely and reads the same file straight into one xarray.Dataset
indexed by row/column instead, with the recovered template still
available under ds.attrs["template"] for
figure_from_template(). See
Reloading data from a saved HTML for both paths worked through
end to end.
"template" above is the exact same dict
to_template()/save_template produce
directly, with no HTML export or plotted data involved at all: a figure’s
grid, group boxes, spine colors, tick overrides, ids, twin/secondary/inset
overlays, and its own Style, snapshotted with
no plotted data in it at all – so a layout worth building once
(derived from a dataset’s own metadata, say) can be reused across many
future plots via load_template()/
figure_from_template(), the identical function this data
round-trip itself uses. See Reusable, data-free figure templates for a
worked example.
Built for scientific Python
plotpress gives you a familiar, matplotlib-shaped interface without requiring matplotlib itself:
Familiar API – create figures and axes, plot data, configure labels and ticks, build legends, arrange subplots, and style figures the way scientific Python users already expect.
No pyplot, no globals – a
Figureowns its own axes and its ownStyle. There is no process-wide “current figure” and no globalrcParamsto get in the way of applications, libraries, or concurrent workflows.plotpress.subplots()returns(fig, axes)just likeplt.subplots()– but touches no global state.Dependency-light – built around Python and NumPy, with no compiled extension. Hot paths are vectorized (coordinate formatting, min/max decimation for huge lines), so it installs, and runs, cleanly in environments where a large visualization stack isn’t practical.
It is shaped, not drop-in: there’s no pyplot state machine and not every
matplotlib keyword is present – treat the gallery as the compatibility
surface.
Interactive when you need it
Static figures are great for papers. Interactive figures are great for everything else. plotpress can turn a figure into a self-contained HTML document with a toolbar for:
Pan and zoom, over every axes at once or one at a time
Point picking, with extraction to CSV/JSON
Annotations, with a draggable label independent of the point it’s pinned to
Frame and data sliders over a stack of 2-D frames (
plot_frames())Interactive meshes – picking works cell-by-cell on a
pcolormesh, not just a line’sx/yCustom JavaScript tools, added to the same toolbar via
extra_js
Tools beyond that core set are opt-in through options=, so nothing newer
changes an existing figure unless you ask for it. options=["slice"] adds
Slice: for a pcolormesh or imshow, scrub any row or column as a
1-D profile with a play/step slider, shown in a strip beside the heatmap, in
its place, or behind just a cursor line – and drive every mesh sharing a grid
from one slider, which is what makes it work on a figure with hundreds of
panels.
There is no server to run and no external application required – the HTML travels with its data and opens directly in a browser. Send someone a file, not a service they have to install. See Interactive figures for the full toolbar reference.
Scientific visualization, end to end
plotpress is designed around the way scientific figures actually get used:
Explore ──► Analyze ──► Visualize ──► Share ──► Publish ──► Archive ──► Reuse
▲ │
└───────────────────────────────────────────────────────────────────────┘
Explore – interact with a measurement, simulation, image, or spectrum while building an experiment or analysis (pan/zoom, point-picking).
Analyze – the same figure workflow for signal processing, statistics, and multidimensional data.
Visualize – build and style the figure: plot, arrange subplots, apply colormaps and normalization.
Share – hand an interactive HTML figure to a collaborator – no server, nothing to install on their end.
Publish – export a publication figure as SVG/PNG/PDF, or publish a self-contained HTML figure that carries its own data.
Archive – keep the figure and its plotted data together in one portable file.
Reuse – load the figure back with
plotpress.load_data(), recover the data, and analyze or replot it – the cycle starts again from Explore.
Made for real scientific workloads
plotpress isn’t just a handful of basic plotting primitives – it covers the kinds of figures scientists actually build:
Signal processing – power spectral density, cross-spectral density, coherence, spectrograms, autocorrelation, cross-correlation, and magnitude/angle/phase spectra (pure-NumPy Welch estimators).
2-D and gridded data – images, meshes (including curvilinear grids), contours, vector fields, and logarithmic/power/symlog normalization for large scientific fields.
Statistical visualization – histograms, 2-D histograms, box plots, violin plots, ECDFs, KDEs, event rasters, error bars, and hexbins.
Complex figures – subplot grids, shared axes, colorbars (including one shared across several axes), secondary axes, inset axes, grouped panels, figure-level titles/labels, and mixed layouts.
Animation – animated lines and meshes with frame sliders, exportable as self-contained looping GIFs.
Large figures – built to keep object and output-node counts under control, so a very large multi-panel figure stays practical; see the 500 panels, 250 groups, each with its own colorbar example (500
pcolormeshpanels across 250 grouped, individually colorbar’d pairs).
See the example gallery for a figure per plot type, figure layout for assembling the figure itself – sizing, margins, spans, and grouping panels – large-scale figures for build-time and file-size comparisons against matplotlib, live streaming for watching data update in a Qt window as it’s collected, and real applications for over 160 figures built from the data real measurements produce, grouped by field.
Designed for constrained environments
Scientific software doesn’t always run on a developer laptop. plotpress targets environments where a conventional plotting stack can be hard to deploy: locked-down networks, offline systems, minimal containers, CI, scientific and web applications, Pyodide/WASM, library and server code, and shared computing environments. No GUI is required to create a figure, no global plotting state is required, and interactive HTML needs no running server.
plotpress is not a matplotlib replacement, and it does not try to match matplotlib’s twenty years of breadth (no geographic projections or triangulated grids, no 3-D, a handful of bundled font-metric families, and its polar axes project onto the 2-D core rather than a dedicated pipeline – see Limitations). It aims at a narrower, underserved spot: plotting where matplotlib’s install footprint or global state gets in the way.
Reach for matplotlib (or seaborn, Plotly) when you need publication-grade typography across arbitrary fonts, the full plot-type gallery, 3-D, or the deep ecosystem that pandas, seaborn and scikit-learn plot into. The matplotlib-shaped API means moving between them is mostly mechanical.
One API. One figure. Many representations.
The figure should be independent of where you eventually use it:
┌─────────── SVG
│
├─────────── PNG
│
Scientific Python ──► Figure ─────── PDF
│
├─────────── HTML ──► explore
│ share
│ recover data
│
├─────────── Vega
│
└─────────── Vega-Lite
Create the visualization once, choose how to render it later, and keep the data available for whenever you need it again.
Start plotting
pip install plotpress
import plotpress
fig, ax = plotpress.subplots()
ax.plot(x, y)
fig.save("figure.html", interactive=True)
Explore it in a browser. Share the file. Archive it. Load it again.
Getting started
User guide
Examples
- Example gallery
- Pairwise data
- Statistical distributions
- Gridded data
- Multi-axes layout and annotation
- Animation
- Axes & figure manipulation
- Secondary and inset axes
- Building a figure across processes
- Custom interactive JS
- Seaborn-style distribution plots
- Limitations
- Figure layout
- Grouping axes
- Large-scale figures
Live plotting
Real applications
- Real applications
- Earth, ocean and atmosphere
- Astronomy and space science
- Medical imaging and clinical data
- Biology, genomics and epidemiology
- Chemistry and spectroscopy
- Materials, surfaces and microscopy
- Spectroscopy and wavefunctions
- Readout and device maps
- Coherence and noise
- Gate calibration
- Benchmarking and multi-qubit
- Semiconductors and electronic test
- Fluids, heat transfer and mechanical engineering
- Acoustics, vibration and sonar
- Energy and power systems
- Transport, robotics and navigation
- Manufacturing, reliability and quality
- Computing, networks and machine learning
- Finance, economics and risk
Limitations
- Limitations
- Only bundled metric families are measured accurately
- Text width is an estimate, not a measurement
- PNG is a second renderer, not a rasterized SVG
- Density estimates are approximate for large samples
- Polar is projected onto the 2-D core
- A callable tick formatter doesn’t survive interactive zoom
- A plain number on an already date- or category-flavored axis is undefined
- Not implemented