neuron-explorer
Steering-first neuron explorer for Qwen3.5-4B: push every MLP neuron, keep every rollout, browse the results
翻译准备中
一个快速、依赖轻量的绘图软件库,能够将单个图形渲染为 SVG、PNG、PDF、Vega/Vega‑Lite,以及带完整平移/缩放、点选取和注释工具栏的自包含交互式 HTML。
AI 翻译
A fast, dependency-light plotting library that renders one figure to SVG, PNG, PDF, Vega/Vega-Lite, and self-contained interactive HTML (with a full pan/zoom, point-picking, and annotation toolbar) th
开发工具Python · JavaScriptGitHub @jrvannucci在线运营者未确认
试用过了吗? 点一下就好,无需登录。
一个快速、依赖轻量的绘图库,可通过类似 matplotlib 的 API 将单个图形渲染为 SVG、PNG、PDF、Vega/Vega‑Lite 以及自包含的交互式 HTML(具备完整的平移/缩放、点选取和标注工具栏),无需全局状态,也不需编译扩展。
AI 翻译
A fast, dependency-light plotting library that renders one figure to SVG, PNG, PDF, Vega/Vega-Lite, and self-contained interactive HTML (with a full pan/zoom, point-picking, and annotation toolbar) through a matplotlib-shaped API — no global state, no compiled extension
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
Plot once. Share anywhere. Explore everywhere.
A fast, dependency-light plotting library for scientific computing, with
a matplotlib-shaped API and no compiled extension — install it
anywhere Python does, from notebooks to CI pipelines to offline environments.
It renders one figure to SVG, PNG, PDF, Vega/Vega-Lite, and self-contained
interactive HTML — the HTML carrying a toolbar for pan/zoom, point-picking,
annotation, and slicing a heatmap. No global state, either — a Figure
owns its own axes and its own Style.查看完整 README ↗Plot once. Share anywhere. Explore everywhere.
A fast, dependency-light plotting library for scientific computing, with
a matplotlib-shaped API and no compiled extension — install it
anywhere Python does, from notebooks to CI pipelines to offline environments.
It renders one figure to SVG, PNG, PDF, Vega/Vega-Lite, and self-contained
interactive HTML — the HTML carrying a toolbar for pan/zoom, point-picking,
annotation, and slicing a heatmap. No global state, either — a Figure
owns its own axes and its own Style.
📖 Documentation · User guide · API reference · Example gallery · Real applications
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", linestyle="--")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.legend()
fig.save("out.svg") # static vector SVG
fig.save("out.png") # raster export
fig.save("out.pdf") # vector export
fig.save("out.html", interactive=True) # interactive: zoom / pick / annotate / slice
fig.show() # native pop-up window
out.html above is a real, self-contained page — no server, no external JS —
with a toolbar over every axes in the figure at once. Self-contained means
genuinely shareable: the plotted data and the toolbar's own JS are both
inlined into that one file, so anyone can open and interact with it with
nothing installed on their end — no Python, no plotpress, no internet
connection, just a browser. Email it, drop it in a chat, put it on a USB
stick — it still works. Send someone a file, not a service they have to
install.
A README on PyPI or GitHub can't run that page's script, so the GIFs below are recordings of it rather than the real thing. For the live version, open a saved file yourself or visit the real-applications gallery, where every figure is embedded exactly this way.
Pan / zoom, working the same over every axes, not just the one under the cursor:
Point picking, reading a value off any axes — a mesh's z, not just a
line's x/y — then extracting every picked point as CSV/JSON:
Annotation, a free-form note whose label box drags independently of the point it's pinned to:
Slicing a heatmap, reading a row of a pcolormesh as a profile in a strip
carved out of the same axes — the companion view of the opt-in Slice tool. One
slider drives every mesh sharing the grid, so both sections stay on the same
row as it sweeps down; the profile's value axis is pinned to the colorbar's
range, so the line moves through the field instead of rescaling each step:
At scale, every gesture above still works the same way on a figure with
hundreds of axes — zoom from the full grid down into a handful of panels, pan,
pick a value off a mesh cell, walk it to another with the arrow keys, drag its
label clear of the dot it stays pinned to, then hand that one panel to Slice by
clicking it on the figure, read the profile through it, and project the pin
onto that profile so the same point is marked on both. The other 499 panels are
left alone throughout (the figure is
docs/figure_layout/grouping/plot_13_full_scale_demo.py —
500 pcolormesh panels, 250 groups, each with its own colorbar):
The same Figure built once from the matplotlib-shaped API renders to every
format below — no separate figure per output, 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 from
scientific Python to web-native visualization, for handing a figure to an
existing Vega-based dashboard or notebook without rebuilding the plot from
scratch. See the
architecture docs
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. The interactive HTML above isn't a one-way trip: it embeds the plotted data and the figure's own structure and styling as JSON alongside the SVG, so a later process — with none of the Python objects that built it still around — can read a figure back out and rebuild it. The figure becomes a portable representation of the data it displays, not just a picture of it:
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
A freeform Figure.add_axes() rect has no grid cell to rebuild from — its
index is listed in template["omitted_axes"] instead of silently
vanishing. See the
full API and worked examples
for the round trip end to end.
For the common case of a uniform grid — every axes its own single
pcolormesh or line series, all the same shape — plotpress.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
plotpress.figure_from_template(). See the
data round-trip example
for both paths worked through end to end.
"template" above is the exact same dict Figure.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, with
no plotted data in it at all — so a layout worth building once can be
reused across many future plots via plotpress.load_template()/
plotpress.figure_from_template(), the identical function this data
round-trip itself uses. See the
templates example.
A plotpress figure can leave behind a recoverable artifact rather than an image: it stores its plotted data, layout, and styling together in the same file.
plotpress is designed around the way scientific figures actually get used:
Explore ──► Analyze ──► Visualize ──► Share ──► Publish ──► Archive ──► Reuse
▲ │
└───────────────────────────────────────────────────────────────────────┘
plotpress.load_data(), recover the
data, and analyze or replot it — the cycle starts again from Explore.plotpress isn't just a handful of basic plotting primitives — it covers the kinds of figures scientists actually build. See Supported plot types below for the full method list; the categories:
pcolormesh panels, 250 groups, each with its own
colorbar).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, a handful of bundled font-metric families, no 3-D, and its polar axes project onto the 2-D core rather than a dedicated pipeline — see Supported plot types below). It aims at a narrower, underserved spot: plotting where matplotlib's install footprint or global state gets in the way. Scientific software doesn't always run on a developer laptop.
Reach for plotpress when you want to:
rcParams.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.
Two galleries in the docs, on separate pages: a plot-type reference with one figure per method, and real applications — over 160 worked figures built from the data real measurements produce, grouped by field, each explaining the axis, scale and colour choices the data forces. Every application figure is embedded live, with the interactive toolbar.
pip install plotpress # SVG + interactive HTML + PNG/PDF export
pip install plotpress[full] # + the viewers (gui, qt, jupyter) and xarray
pip install plotpress[contrib] # + everything a contributor needs (dev, browser, bench, docs)
The standard install covers all file output — SVG, interactive HTML, PNG
and vector PDF — with pure-wheel dependencies that install everywhere (servers,
CI, notebooks). Reach for [full] as soon as you want anything beyond that.
Its pieces also install individually, or in combination
(pip install plotpress[gui,xarray]): [gui], [qt], [jupyter],
[xarray], or all three viewers at once via [viewers]. That matters because
each pulls its own stack — [gui] brings a desktop webview for the native
fig.show() window, which a [qt]-only or [jupyter]-only install has no
reason to carry. See Installation
for the full extras reference.
| Call | Result |
|---|---|
fig.save("x.svg") | static vector SVG |
fig.save("x.png") / fig.savefig(...) | raster PNG (supersampled Pillow backend, dpi metadata included) |
fig.save("x.jpg") / fig.save("x.webp") | raster, lossy — smaller for dense mesh figures than PNG |
fig.save("x.pdf") / fig.save("x.eps") | vector PDF / EPS (svglib + reportlab) |
fig.save("x.html", interactive=True) | interactive HTML (self-contained JS toolbar) |
fig.to_svg() / fig.to_html() | string, for embedding |
fig._repr_svg_() | inline SVG in Jupyter |
fig.show() | native pop-up window (pywebview, [gui] extra; falls back to browser) |
fig.show_qt() | embed in a PyQt/PySide app (plotpress.qt, [qt] extra) |
Interactive HTML and pop-up output carry a self-contained vanilla-JS toolbar (no external requests, so it works under strict CSPs like Jupyter and sandboxed webviews). Nothing is active until you pick a tool:
Pan/Zoom, Home and Fit Width sit standalone on the toolbar's left; everything else is grouped into Axes, Point Picking, Annotate, and File menus:
z, c, …). Click a pin, or use
Clear Points, to remove it. Its label box (connected to the marker by a
leader arrow) is draggable while Point Picking is active. Hide
Points toggles every pin's visibility without deleting them, and
Extract copies/downloads them all as CSV/JSON, or hands them back to
the kernel (fig.show(wait_for_extract=True)).That toolbar is what every interactive figure gets. Anything beyond it is
opt-in through options=, so nothing new changes an existing figure unless
you ask for it:
fig.save("out.html", interactive=True, options=["slice"])
pcolormesh or imshow, scrub any row or column as a
1-D profile with a play/step slider. The profile can sit in a strip beside
the heatmap, replace it, or stay hidden behind just a cursor line; its value
axis can follow each slice, the colorbar's range, or bounds you set. Point
Picking works on the profile itself, and every mesh sharing a grid can be
driven from one slider — which is what makes it usable on a figure with
hundreds of panels. Pass settings instead of a bare name to open in a chosen
state: options={"slice": {"enabled": True, "view": "companion"}}.fig.to_html()/fig.save(..., interactive=True) also accept extra_js — a
raw JS string inlined as its own <script>, for adding a custom tool to the
same toolbar menu — plus pick_precision (decimal places embedded per
value) and pick_max_mesh_cells/pick_max_points (a hard cap on how much of
each mesh/series is embedded for picking, per artist) to bound the
interactive payload for mesh- or point-heavy figures.
Per axes: ax.set_pickable(False) excludes that axes from Point Picking
(Axis Span/Zoom/Annotation still work everywhere), and
ax.set_pick_context(**kwargs) attaches extra key/value context — e.g. a
panel's spine color — that rides along on every record picked from it. Every
picked record also always carries axes_title (falling back to a generated
name when the axes has no title) plus xlabel/ylabel and zlabel (the
title of any colorbar attached to that axes, shared or not), so a value
pulled out of context still says what it means.
A stack of 2-D frames via ax.plot_frames(...)/ax.pcolormesh_frames(...)
adds a slider (play/pause/step) over the extra dimension. Multiple sliders
can be linked by a shared index.
plotpress covers the core of matplotlib's "Plot types" reference grid, plus the axis, layout and color machinery a real figure needs. Each table below is one grouping.
plot (lines) | scatter (+ c/cmap) | bar / barh |
hist | step | fill_between |
stem | errorbar (x/y err + caps) | imshow |
pcolormesh | pie | plot_frames (slider) |
boxplot | violinplot (KDE) | eventplot |
quiver | contour (marching squares) | hist2d |
stackplot | contourf (filled) | hexbin |
matshow | spy | broken_barh |
stairs | axline | barbs |
ecdfplot | kdeplot | pcolormesh_frames (slider) |
Pure-NumPy Welch estimators — no SciPy required.
psd | csd | cohere |
magnitude_spectrum | angle_spectrum | phase_spectrum |
specgram | xcorr | acorr |
Created with projection="polar", projected onto the 2-D core rather than a
dedicated pipeline — see the
limitations docs
for the caveats.
plot | scatter | fill |
set_rmax / set_rlim | set_rticks | set_thetagrids |
axhline / axvline | axhspan / axvspan | hlines / vlines |
fill | fill_between | fill_betweenx |
| Call | What it does |
|---|---|
set_xscale / set_yscale / loglog / semilogx / semilogy | log scales |
set_aspect("equal") | equal data aspect |
set_xlim / set_ylim | limits |
set_xticks / set_yticks | fixed tick locations |
set_xticklabels / set_yticklabels | fixed tick labels |
invert_xaxis / invert_yaxis | reversed direction |
margins / grid / set_axis_off | padding, gridlines, hiding the frame |
tick_params | per-axes, per-axis tick styling |
| Call | What it does |
|---|---|
subplots(sharex=…, sharey=…) | shared limits across a grid |
ax.sharex(other) / ax.sharey(other) | the same, applied after the fact |
twinx / twiny | an overlaid axes with a second y/x axis |
secondary_xaxis / secondary_yaxis | a mirrored, unit-converted second axis |
inset_axes | a nested axes inside another |
| Call | What it does |
|---|---|
fig.tight_layout() | auto-margins, so labels never overflow |
fig.subplots_adjust(...) | direct margin control |
GridSpec (plotpress.figure) | row/column spans |
ax.spines | per-side visible / color / linewidth |
align_xlabels / align_ylabels | line labels up across panels |
| Call | What it does |
|---|---|
ax.text / ax.annotate | text, with optional arrows |
suptitle / supxlabel / supylabel | figure-level titles and labels |
legend(loc=…, ncol=…, title=…) | per-axes legend |
fig.colorbar(...) | one colorbar, or one shared across a list of axes |
27 built-in, plus any _r reversed variant, named colors ("red", "k", …)
and the matplotlib "C0".."CN" cycle. plotpress.make_cmap() /
register_cmap() build a custom one from any list of colors.
| Family | Colormaps |
|---|---|
| Perceptually uniform | viridis, plasma, inferno, magma, cividis |
| Diverging | coolwarm, RdBu, Spectral, PiYG, BrBG, seismic |
| Sequential | Blues, Greens, Oranges, Reds, Purples, YlOrRd, gray, hot, cool |
| Cyclic | twilight |
| Rainbow | jet, turbo |
| Qualitative | tab10, tab20, Set1, Dark2 — for class labels with no natural ordering |
| Class | Scaling |
|---|---|
Normalize | linear |
LogNorm | logarithmic |
PowerNorm | gamma |
SymLogNorm | log, through zero |
TwoSlopeNorm | diverging, midpoint pinned to a real center value |
BoundaryNorm | discrete bins |
python examples/plot_types.py # plot / scatter / bar / hist / pie / imshow / ...
python examples/plot_types_2.py # boxplot / violin / quiver / contour / hist2d / ...
python examples/gallery.py # line/scatter/pcolormesh/subplots
streamplot, triangulation (tri*), and geographic / map projections — each
would need new primitives. These are the main remaining plot-type gaps against
matplotlib's full gallery.
pip install plotpress[dev] # pytest
python -m pytest # fast unit + output tests (~1 min)
python -m pytest -m perf -s # timing tests + speedup report (needs matplotlib)
A plain pytest run deselects the browser tests below (they need a Chromium
download); -m perf selects the timing ones, which a plain run also skips.
The suite covers the no-global-state invariants, plotting/autoscale logic, transforms/tickers/colors, a lossless PNG round-trip, SVG/HTML well-formedness and structure, the interactive JS's tick labels against the Python ones that drew them, and performance (regression guards + a comparative claim vs matplotlib).
Point picking runs in JavaScript inside the interactive HTML, so it is tested
end-to-end in a real browser: each case clicks the pixel where the renderer drew
a known datum and asserts the marker reports that datum, across every pickable
plot type (line, scatter, bar, stem, errorbar, quiver, eventplot, boxplot,
violin, fill, pcolormesh, imshow, pie) and awkward axes (log, inverted,
set_aspect, multi-subplot).
These need a browser, so they are deselected by default and skip cleanly when it is missing:
pip install plotpress[browser] && playwright install chromium
python -m pytest -m browser
pip install plotpress[bench] # matplotlib, for comparison
python benchmarks/benchmark.py # plotpress vs matplotlib, plot build + SVG output
Representative run (best of 3, one machine — build and serialize to SVG, both using the object-oriented API):
| scenario | plotpress | matplotlib | speedup |
|---|---|---|---|
| pcolormesh 300×300 | ~16 ms | ~6400 ms | ~400× |
| many axes (8×8 grid) | ~40 ms | ~1600 ms | ~40× |
| scatter, 5k points | ~15 ms | ~220 ms | ~14× |
| single line, 100k points | ~9 ms | ~48 ms | ~5.6× |
Where the win comes from: avoiding matplotlib's per-Artist Python
overhead (many axes), and rasterizing meshes to one <image> instead of tens
of thousands of vector cells (pcolormesh). The single huge polyline
case used to be a loss (pure-Python float→string serialization of 100k points);
it's now a win via min/max path decimation — a monotonic-x line is reduced
to first/last/min/max per pixel column before serializing, which is visually
lossless (spikes preserved), keeps the output vector, and needs no compiled
backend. Coordinate formatting itself is already vectorized with numpy.char.
Pure Python, and staying that way. Every number above comes from NumPy, not from native code — plotpress has no compiled extension and is not going to grow one. Curvilinear and Gouraud meshes scan-convert in NumPy for the same reason: where a faster path would need a C extension, the NumPy one is what gets written. Installing everywhere pip does, with no build toolchain and no per-platform wheels, is a first-class feature here rather than a trade-off made to get it.
src/plotpress/ layout (a "src" layout: the package lives under src/, not
at the repo root, so an accidental import plotpress can't silently resolve
to the working tree instead of the installed package). Four subpackages group
what used to sit loose at the top level: backends/ (every format a Figure
renders itself to), core/ (the shared scene/geometry layer every backend
reads from), style/ (how a value becomes appearance -- Style, colormaps,
tick locating/formatting), and gui/ (optional viewers). None re-export
except style/__init__.py (it is Style); import the submodule you need
directly (from plotpress.backends.svg import figure_to_svg):
| Module | Responsibility |
|---|---|
figure/ | Figure, subplots(), layout, save/show/_repr_* -- split by concern (_core, _layout, _html_options, _template, _report, _io) |
axes.py | Axes: plotting methods, limits, autoscale |
polar.py | PolarAxes: (θ, r) projection + polar frame, built from existing artists |
_spectral.py | pure-NumPy Welch spectral estimators (psd/csd/cohere/specgram/…) |
core/artists.py | data-only scene primitives (Line2D, ScatterCollection, QuadMesh) |
style/__init__.py | per-figure Style (replaces global rcParams) |
core/transform.py | vectorized data→pixel transforms (linear + log scales) |
style/colors.py | Normalize, colormap LUTs, colormap application |
style/ticker.py | "nice number" + log tick locations, label formatting |
style/dates.py | datetime axis tick locations + label formatting |
backends/svg/ | the renderer: scene → SVG string (+ per-axes metadata) -- split by concern (_render, _ticks_and_frame, _legend, _group_layout, _metadata, …) |
core/primitives.py | backend-agnostic pixel-space primitives + one artist→primitive converter |
backends/png.py | stdlib-only PNG encoder for mesh/image layers |
backends/raster.py | Pillow raster backend for PNG export; svglib/reportlab for PDF |
backends/vega.py / backends/vega_lite.py | Figure → Vega / Vega-Lite v5 JSON specifications |
fonts/ |
Artists never render themselves — they just hold arrays. The geometry of each
artist is computed once in core/primitives.py; backends/svg/ and
backends/raster.py are thin emitters over that shared primitive vocabulary,
so an artist is defined in one place, not per backend.
Fonts. A figure is laid out before anything draws its glyphs — SVG emits
<text> and lets the viewer rasterize — so plotpress has to predict text width
from bundled metric tables. That keeps layout identical on every machine with no
font-file dependency. Bundled are the base-14 metric families — Helvetica,
Times and Courier, each in regular / bold / italic / bold-italic — plus
DejaVu Sans, which covers the metric-compatible clones too (Arial and
Liberation Sans are Helvetica, Liberation Serif is Times, Liberation Mono is
Courier). Families outside those groups — Verdana, Tahoma, Arial Black, Arial
Narrow — have proprietary metrics, so they render but are measured as Helvetica
and need hand-tuned figsize; Style(measure_installed_fonts=True) opts into
measuring the real file on this machine instead, trading cross-machine
reproducibility for fidelity. PNG export picks a matching face, falling back to
Pillow's built-in font where the system has none.
| bundled width tables + the family registry (layout only; no glyph rasterization) |
backends/_interactive.py + backends/_js/ | the toolbar's vanilla JS (pan/zoom, picking, annotate, Slice, sliders, export), split into one .js file per tool under _js/ and assembled by _interactive.py |
gui/qt.py | optional PyQt/PySide WebEngine widget + window (fig.show_qt(), [qt] extra) |