Usage

Figures, not globals

Everything hangs off a Figure. plotpress.subplots() returns (fig, axes) just like matplotlib’s plt.subplots() – but there is no global “current figure/axes” and no global rcParams.

import plotpress
import numpy as np

fig, axes = plotpress.subplots(1, 2, figsize=(10, 4))
x = np.linspace(0, 10, 200)
axes[0].plot(x, np.sin(x)); axes[0].set_title("sin")
axes[1].scatter(x, np.cos(x), s=8); axes[1].set_title("cos")
fig.tight_layout()

How close is the matplotlib API?

The object-oriented core is deliberately matplotlib-shaped: Figure / Axes with plot, scatter, bar, hist, pcolormesh, set_xlabel / set_ylabel / set_title, set_xlim / set_ylim, grid, legend, colorbar, twinx / twiny, log scales and the "C0"..``”CN”`` colour cycle all behave as you would expect. Code written against fig, ax = plt.subplots() usually ports by changing the import.

It is a shaped API, not a drop-in one. There is no pyplot state machine (plt.plot / plt.gca / plt.savefig), no rcParams, and the long tail of matplotlib keyword arguments and plot types is not all present. Treat the gallery as the compatibility surface: if a call appears there, it works the same way; if it doesn’t, assume it needs adapting rather than a straight copy. Known gaps and trade-offs are catalogued under Limitations.

Output surfaces

One scene, many targets:

Call

Result

fig.save("x.svg")

static vector SVG

fig.save("x.png") / fig.savefig(...)

raster PNG

fig.save("x.pdf")

vector PDF

fig.save("x.gif")

looping GIF (plot_frames())

fig.save("x.html", interactive=True)

interactive HTML (JS toolbar)

fig._repr_svg_()

inline SVG in Jupyter

fig.show()

native pop-up window ([gui])

Interactive figures

Interactive HTML and pop-up output carry a self-contained JavaScript toolbar (no external requests – works under strict CSPs like Jupyter). Nothing is active until a tool is selected:

The toolbar is a single menu bar. Pan/Zoom, Home, and Fit Width sit standalone at the far left – the whole-figure tool reached for most, and the two resets that undo it; everything else groups into four menus by what its buttons do: Axes, Point Picking, Annotate, and File.

Pan/Zoom, Home, and Fit Width:

  • Pan/Zoom – a plain wheel zooms the whole figure, centered on the cursor, regardless of which axes (if any) is under it – the gesture that actually helps on a figure with many small axes, where the cursor is only ever over one tiny panel at a time. Drag pans that same whole-figure view. A plain wheel with no tool selected is left alone to scroll the page, exactly as it would over any other content:

    Wheel-zooming toward the cursor across a 30-panel pcolormesh grid

    It works just as directly on a single-axes figure:

    Wheel-zooming toward the cursor on a single-axes line plot
  • Home – restores whole-figure magnification to its natural size; leaves every axes’ own pan/zoom and every pin/annotation untouched.

  • Fit Width – a one-shot snap to whatever magnification makes the figure exactly as wide as the current browser window – worth reaching for on a large figsize opened as standalone HTML, which otherwise opens at its natural (often much larger than any window) pixel size, with only a portion visible until you zoom out or scroll. An embedded figure (e.g. inside a Report) already CSS-scales to its container, so this only matters for standalone HTML/pop-up output. Doesn’t keep re-fitting on a later window resize, the same as Home.

Axis Span to pan, Axis Zoom to zoom (ctrl+wheel or box-drag), Point Picking to read a value -- the scatter series also carries a phase value, surfaced when a marker is picked.

Axes menu:

  • Axis Span – drag to pan a single plot’s data window.

  • Axis Zoom – two distinct gestures. Box-drag zooms one axes in data space (ticks recompute). Ctrl+wheel (or a trackpad pinch) zooms the whole figure instead, the same gesture Pan/Zoom’s plain wheel does.

  • Reset All Axes – restores every axes’ own pan/zoom to its original view; leaves whole-figure magnification and every pin/annotation untouched.

Point Picking menu:

  • Point Picking – click to read the nearest data value; arrow keys step along the series; right-click deletes. Reports extra dims (z, c, …).

  • Hide Points – a standalone toggle, not a mode: hides every Point Picking pin without deleting any of them. Toggling it back to “Show Points” restores them exactly as they were, text included.

  • Clear Points – removes every Point Picking pin at once, leaving any annotation notes untouched.

  • Extract – copy/download every Point Picking pin as CSV/JSON (or hand them back to the kernel with fig.show(wait_for_extract=True)).

Annotate Arrow drops a user-written note anywhere on the figure, including outside the axes -- try the peak, or the margin.

Annotate menu:

  • Annotate – drop a plain text box anywhere on the figure: a caption, not a callout. No dot, no leader arrow, always pinned to a fixed figure position.

  • Annotate Arrow – drop a user-written note anywhere on the figure, not locked to any datum but pointing at wherever it was dropped (a dot, a leader arrow) – including the margins or the gap between subplots.

  • Annotate Point – like Point Picking, but prompts for text and locks a note to that datum instead of the auto-generated readout; never appears in Extract’s output.

  • Hide Annotations – the mirror of Hide Points: hides every annotation note, plus every boxed callout the figure itself drew, without deleting any of them.

  • Clear Annotations – the mirror of Clear Points: removes every annotation note at once, leaving any Point Picking pins untouched.

A note dropped by any of the three Annotate tools can be dragged to reposition it under any of the three – not just the one that created it.

Point Picking a few values along the curve, then click Hide All -- every pin disappears without being deleted; click it again (now labeled Show All) to bring them all back exactly as they were.

File menu:

  • Save/Save As – downloads the current page – pan/zoom, every pin/annotation, hidden-legend-series toggles, and Hide Points/Hide Annotations – as a new, equally self-contained HTML file; Save tries to overwrite the file the page was opened from instead, where the browser allows it.

3-D data via ax.plot_frames(...) adds a play/pause/step slider over the extra dimension.

plot_frames adds a play/pause/step slider over an extra dimension.

Every picked/extracted record carries axes and axes_title – the axes’ own title, or a generated "axes N" when it has none, so a multi-panel export always names its source panel. set_pick_context() attaches further per-axes key/value context (e.g. a panel’s own color) that rides along on every record picked from it, and set_pickable() excludes an axes from Point Picking entirely (Annotate Point resolves the same way, so it respects this too) – Axis Span, Axis Zoom, Pan/Zoom, Annotate, and Annotate Arrow are unaffected. See Interactive figures for the full picture, and A different spine color per axes for set_pick_context used to surface a per-panel spine color.

Point Picking the left panel: its axes_title ("Sensor A") and set_pick_context() keys (edge_color, unit) ride along on the marker. The right panel has set_pickable(False), so clicking it does nothing -- and were it pickable, its axes_title would fall back to a generated "axes 1" since it has no title of its own.

Combining figures into a report

Report combines several figures into one self-contained HTML file. Each figure keeps its own independent interactivity – its own toolbar, pan/zoom, point-picking, annotations – because it is embedded in its own <iframe> rather than spliced directly into the page: an interactive figure’s JS assumes it owns the page (fixed element ids, a document-level toolbar), so several sharing one page directly would collide. Add figures with add(), in the order they should appear, with an optional title and details for each:

report = plotpress.Report(title="Weekly QA sweep",
                          description="Four sensor batches, one figure each.")
report.add(fig_a, title="Batch A", details="Baseline run, no anomalies.")
report.add(fig_b, title="Batch B", details="Elevated noise floor after 14:00.")
report.save("qa_sweep.html")

Every entry is collapsible – click its “Figure N”/title header to hide just that figure, or the Collapse All/Expand All button above the first entry to do the same for all of them at once – so a report with many figures still reads as a scannable outline, not a wall of plots. report.save("qa_sweep.html", collapsed=True) starts every entry collapsed instead of open, worth it once a report has enough figures that opening it fully expanded would be slow: a collapsed figure’s document isn’t parsed or rendered at all until a reader actually expands it.

Below: four figures, each its own 5x10 grid of independent pcolormesh panels – every panel keeping its own title, axes, ticks, labels, and colorbar – combined into a single scrollable report. Try collapsing one, then Collapse All.

Four figures, each a 5x10 grid of independent pcolormesh panels, combined into one file with plotpress.Report -- every panel in every figure keeps its own toolbar-driven interactivity.

Reading data back out of a saved HTML

load_data() reads the plotted data straight back out of an interactive=True HTML file – the original Python objects that built it don’t need to still be around. It returns a dict keyed by each figure’s own title (one entry for a bare figure’s HTML, one per embedded figure for a Report’s – a generated "Figure N" for one with no title of its own), each mapping its own axes’ titles to that panel’s series/mesh data plus its labels, limits, and scale:

data = plotpress.load_data("qa_sweep.html")
mesh = data["Batch A"]["axes"]["axes 0"]["meshes"][0]
mesh["x"], mesh["y"]   # 1-D cell-center coordinates
mesh["z"]              # 2-D array, shape (ny, nx)

Title keys are convenient but not guaranteed unique – pass by_index=True when two figures or two axes share a title, or a positional key is simply more useful: this returns a plain list of per-figure dicts instead, each with its axes keyed by integer index rather than title.

See Reload a mesh grid and replot one slice per panel as a line and Reload a mesh grid and FFT every panel for two worked examples: reloading a 30-panel pcolormesh grid and replotting one x-slice per panel as a line, and reloading the same grid to run a 2-D FFT over every panel.

Log scales, aspect, layout

ax.set_xscale("log"); ax.set_yscale("log")   # or ax.loglog(x, y)
ax.set_aspect("equal")                        # circles look circular
fig.tight_layout()                            # auto-margins, no overflow
ax.annotate("peak", xy=(x0, y0), xytext=(x1, y1), arrowprops={})
fig.suptitle("Overview")