Skip to content

Commit 0c8d0a9

Browse files
authored
Merge pull request #23 from CSSFrancis/release/v0.1.0
Release/v0.1.0
2 parents 5f48233 + b2e5cc9 commit 0c8d0a9

23 files changed

Lines changed: 219 additions & 192 deletions

‎CHANGELOG.rst‎

Lines changed: 206 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,211 @@ Fragment files in ``upcoming_changes/`` are assembled into this file by
1010

1111
.. towncrier release notes start
1212
13-
v0.1.0 (2026-04-12)
14-
====================
13+
0.1.0 (2026-06-24)
14+
==================
1515

16-
Initial release. Includes ``Figure``, ``Axes``, ``GridSpec``, ``subplots``,
17-
``Plot1D``, ``Plot2D``, ``PlotMesh``, ``Plot3D``, ``PlotBar``, a full marker
18-
system, interactive overlay widgets, and a two-tier callback registry.
16+
Initial release. Provides ``Figure``, ``Axes``, ``GridSpec``, ``subplots``,
17+
``Plot1D``, ``Plot2D``, ``PlotMesh``, ``Plot3D``, ``PlotBar`` and ``PlotXY``, a
18+
full marker system, interactive overlay widgets, and a two-tier callback
19+
registry, plus the additions below.
1920

21+
New Features
22+
------------
23+
24+
- Added :class:`~anyplotlib.InsetAxes` — floating overlay sub-plots that sit
25+
above the main figure grid, created via :meth:`~anyplotlib.Figure.add_inset`
26+
and supporting all plot types (:meth:`~anyplotlib.Axes.imshow`,
27+
:meth:`~anyplotlib.Axes.plot`, :meth:`~anyplotlib.Axes.pcolormesh`, etc.)
28+
as well as interactive minimise, maximise, and restore states. (`#6 <https://github.com/CSSFrancis/anyplotlib/pull/6>`_)
29+
- Added ``anyplotlib.sphinx_anywidget`` Sphinx extension for interactive,
30+
Pyodide-powered figures in documentation (``.. anywidget-figure::`` directive,
31+
automatic wheel building, Sphinx Gallery integration), plus several supporting
32+
improvements (`#9 <https://github.com/CSSFrancis/anyplotlib/pull/9>`_):
33+
34+
* Improved widget–parent page postMessage communication bridge.
35+
* Made colormap LUT construction more robust against unknown colormap names.
36+
* Subplot panels now use deterministic IDs.
37+
* Added an end-to-end test for the Playwright thumbnail scraper.
38+
- 3-D ``scatter3d`` and ``voxels`` now render on the GPU via WebGPU when
39+
available, as a transparent progressive enhancement: a ``gpu="auto"`` kwarg
40+
(default) uses instanced WebGPU rendering above ~20k points / ~8k voxels and
41+
falls back to Canvas2D otherwise or whenever a GPU is unavailable (no
42+
``navigator.gpu``, null adapter, or device loss) — query the actual path via
43+
``plot.gpu_active``. Voxel slice emphasis and per-face shading are GPU
44+
uniforms, so dragging a ``PlaneWidget`` re-renders without re-uploading
45+
geometry. Decorations (axes, labels, sphere, planes, highlight) always
46+
render on the 2-D canvas, so visuals are identical to the fallback. No new
47+
JavaScript dependencies (raw WebGPU + inline WGSL).
48+
- :meth:`PlotXY.pcolormesh` now renders a **regular, uniformly spaced scalar
49+
mesh** as a single stretched RGBA raster instead of one polygon per cell — the
50+
fast path for dense orientation-density / IPF heatmaps. Irregular meshes,
51+
colour-string ``c``, or an explicit ``edgecolor`` keep the per-cell polygon
52+
path. The win is twofold: the image is encoded once and travels on the deduped
53+
geometry channel (a view-only pan/zoom never re-transmits it), and the renderer
54+
blits it in a single ``drawImage`` whose cost is independent of cell count —
55+
so a 256×256 heatmap draws as fast as a 32×32 one.
56+
57+
The underlying primitive is exposed directly as :meth:`PlotXY.add_raster`
58+
(also on :class:`Plot1D`): an RGBA image drawn between data-coordinate
59+
``extent`` corners, with an optional ``clip_path`` polygon (e.g. the curved
60+
fundamental-sector boundary). Image bytes ride the geometry channel
61+
(``Plot1D._GEOM_KEYS``) and the decoded bitmap is cached on the marker set.
62+
Pass ``smooth=True`` (on either ``add_raster`` or ``pcolormesh``) to bilinearly
63+
interpolate the raster for a continuous heat field; the default keeps crisp
64+
nearest-neighbour cells.
65+
66+
New example ``Examples/Interactive/plot_ipf_density_map.py`` — a linked IPF
67+
orientation map + density heat map where the modal (peak-density) bin is the
68+
"best-fit" orientation, ringed on the IPF and highlighted on the map.
69+
- Added :meth:`Axes.axes2d` / :class:`PlotXY` — a blank **data-coordinate
70+
2-D axis** (matplotlib ``transData`` + ``PathCollection`` model). Set
71+
``xlim``/``ylim`` (+ ``aspect="equal"``) and draw ``scatter``/``plot``/``fill``/
72+
``text`` as collection-style artists in data coords — the surface needed for
73+
stereographic / IPF / pole-figure plots (e.g. an orix plotting backend).
74+
``scatter(c=[...])`` honours per-point face/edge colours, and ``aspect="equal"``
75+
applies matplotlib's ``apply_aspect`` in the renderer (the panel box is shrunk
76+
and centred so one data unit spans equal pixels on x and y).
77+
:meth:`PlotXY.pcolormesh` draws a data-coord quad mesh (per-cell colours via a
78+
polygon ``PathCollection``); masked / non-finite cells are skipped, so an
79+
``orix`` pole-density histogram renders natively as an IPF density heatmap. A
80+
marker group (and ``pcolormesh``) accepts a ``clip_path`` — a data-coord polygon
81+
the group is clipped to (matplotlib ``set_clip_path``), e.g. the curved sector
82+
boundary so the mesh's edge cells don't overflow it.
83+
- Axis labels, titles, and colorbar labels now accept a ``fontsize`` keyword
84+
(``set_xlabel("...", fontsize=14)``), and a new ``set_tick_label_size()``
85+
controls tick-number size. Label strings support a mini-TeX subset inside
86+
``$...$`` — superscripts (``$10^{-3}$``), subscripts (``$E_F$``), Greek
87+
letters, and common symbols (``\times``, ``\AA``, ``\degree``) — rendered
88+
natively on the canvas. Logarithmic tick labels now draw true superscripts.
89+
90+
Text is never clipped: the 2D title strip grows to fit large or TeX titles,
91+
the colorbar (strip + label) now reserves real layout space instead of
92+
overflowing the panel edge, rotated y-labels stay inside their gutter at any
93+
size, and edge tick labels are nudged inward rather than cut off.
94+
- Heavy plot geometry now travels on a separate sync channel and is
95+
re-transmitted only when it actually changes. ``Plot2D`` and ``Plot3D``
96+
panels split their large, slow-changing state (vertex/face/image buffers,
97+
per-point colours, colormap LUTs) into a ``panel_<id>_geom`` trait keyed by
98+
a content hash; the light view payload references it by revision and the JS
99+
renderer splices the cached geometry back in. Consequently view-only
100+
updates — ``set_highlight``, ``set_view``, ``set_zoom``, plane-widget drags,
101+
titles — no longer re-send the panel's geometry. Combined with
102+
``Figure.batch()`` coalescing, the voxel grain explorer's per-crosshair
103+
wire traffic drops ~65% (1155 -> 400 KB/frame at 192-cubed), the main
104+
source of Pyodide lag. Plots that declare no geometry keys (e.g. ``Plot1D``)
105+
keep the prior single-trait behaviour unchanged.
106+
- New ``Axes.voxels()`` 3-D geometry renders volumes as shaded translucent
107+
cubes (per-voxel colours, global ``alpha``), and 3-D panels gained their
108+
first interactive widget: ``add_widget("plane", axis=..., position=...)``
109+
adds a draggable :class:`PlaneWidget` slice selector — drag it along its
110+
normal in the browser and ``pointer_move``/``pointer_up`` callbacks fire in
111+
Python. Voxels lying on a plane render more opaque
112+
(``voxel_slice_alpha``), so selected slices glow inside the volume. The
113+
voxel grain explorer example now uses all of this: three plane widgets
114+
bidirectionally linked with three orthoslice crosshairs and the 3-D IPF.
115+
- Plots are now usable on touch devices (iPad / iPhone) and trackpads. A touch
116+
bridge in the renderer translates gestures into the existing interaction
117+
handlers, so every panel type and every example becomes touch-capable with no
118+
API change: one-finger drag pans / orbits / moves a widget, ROI, marker or
119+
slice plane (whatever is under the finger); two-finger pinch zooms; and
120+
double-tap fires the panel's ``double_click`` event. Overlay canvases set
121+
``touch-action: none`` so the browser hands gestures to the plot instead of
122+
scrolling the page.
123+
- The ``double_click`` event on a 1-D / :class:`PlotXY` panel now reports
124+
``ydata`` alongside ``xdata`` (data coordinates), matching the 2-D image path —
125+
so a coordinate axis can be picked in data space (e.g. an IPF / pole-figure mask).
126+
- Voxel rendering is ~2–3× faster: cubes render once per (colour, emphasis)
127+
into sprites and are blitted per voxel with typed-array projection and
128+
integer-snapped draws; camera-static redraws (plane-widget drags) reuse a
129+
cached projection/depth-sort. 3-D interaction no longer double-draws —
130+
self-originated model writes skip the panel-listener echo. New voxel
131+
benchmarks (``test_bench_voxels_orbit`` / ``_reblit``) guard the budget
132+
(~3–6 µs/cube), and ``voxels()`` warns above ~20k cubes with downsampling
133+
guidance for large volumes (e.g. 512×512×300 tomograms). Local docs builds
134+
now rebuild the Pyodide wheel when sources are newer, so the ⚡ interactive
135+
mode never runs stale code.
136+
- ``Figure.batch()`` coalesces panel pushes: every plot mutation inside the
137+
``with fig.batch():`` block is serialised and transferred at most once per
138+
panel when the block exits, instead of once per mutation. Linked-view
139+
handlers (e.g. the voxel grain explorer's crosshairs, which touch 5 panels
140+
per mouse event) drop from ~8 full-state pushes per frame to one per changed
141+
panel — a large reduction in comm traffic that removes most of the lag under
142+
Pyodide and remote kernels. ``set_highlight`` / ``set_view`` / ``set_zoom``
143+
on 3-D panels now route through this coalescing path so re-aiming the camera
144+
or moving the highlight never re-transmits the panel's (potentially hundreds
145+
of KB) unchanged geometry. RGB ``imshow`` updates also skip the unused
146+
colormap-LUT rebuild.
147+
- ``imshow`` now renders ``(H, W, 3|4)`` arrays as true-colour RGB(A) images
148+
(previously the extra channels were silently dropped). ``scatter3d`` gained
149+
per-point ``colors=`` and a ``bounds=`` override for origin-true geometry
150+
(e.g. unit vectors on a sphere), ``Plot3D.set_highlight()`` marks a
151+
single emphasised point, and ``Plot3D.set_sphere()`` draws a shaded,
152+
wireframed reference sphere behind the data (far-side points dimmed). The 3-D camera is now a proper turntable
153+
(matplotlib ``azim``/``elev`` semantics — azimuth spins about the data
154+
z-axis): the previous camera could not aim at arbitrary directions, which
155+
blocked rotate-to-face interactions. A new gallery example,
156+
*Inverse Pole Figure (IPF) Explorer*, combines all of these: an IPF-RGB
157+
orientation map whose crosshair rotates a reduced 3-D IPF sphere to face
158+
the selected grain's crystal direction.
159+
- anyplotlib figures can now be embedded outside Jupyter — e.g. in Electron
160+
apps, MDI sub-windows, or plain web pages — with no anywidget runtime.
161+
``fig.save_html()`` / ``fig.to_html()`` export a self-contained interactive
162+
page; ``figure_esm.js`` now exports a ``mount(el, state, opts)`` entry point
163+
for direct JS embedding (with ``onEvent`` interaction callbacks, live
164+
``setPanelState`` updates, ``resize``, and ``dispose``); and the new
165+
``anyplotlib.embed`` module provides ``figure_state()``, ``esm_path()``, and
166+
a transport-agnostic ``FigureBridge`` for live two-way Python sync over any
167+
pipe (WebSocket, IPC, stdio) with full event-callback support.
168+
169+
170+
Bug Fixes
171+
---------
172+
173+
- Fixed 3-D plane-widget drags snapping back instead of moving smoothly.
174+
``Plot3D.to_state_dict()`` now always serialises the live overlay widgets, so
175+
a view-only push on the same panel (``set_highlight`` / ``set_view``) no
176+
longer re-sends a stale plane position and clobbers an in-progress drag. The
177+
voxel grain explorer also tracks smooth (float) positions for the highlight
178+
marker so it glides with the planes instead of jumping by whole voxels.
179+
- Fixed a 3-D GPU panel breaking — voxels and axes both vanishing after
180+
rendering correctly — when the WebGPU device throws mid-draw or is lost,
181+
as Safari's experimental WebGPU does after working for a while. The GPU
182+
path makes the decoration ``plotCanvas`` transparent and takes GPU-only
183+
branches, so a mid-draw failure left the frame half-built and only a window
184+
resize (which forces a full redraw) restored it. The fallback now disposes
185+
the GPU panel, restores the opaque background, and re-renders the whole panel
186+
once on the Canvas2D path in the same frame, so it self-heals without a
187+
resize.
188+
- Fixed large voxel volumes (e.g. a 256³ grain explorer) rendering "empty" —
189+
only the plane widgets and highlight marker visible, with no cubes — in
190+
WebGPU-enabled browsers such as PyCharm's embedded JCEF. The WebGPU voxel
191+
path draws cubes on a ``gpuCanvas`` beneath the ``plotCanvas`` that carries
192+
the axes/planes/highlight; activating the GPU path cleared the plotCanvas
193+
bitmap but left its opaque CSS ``background``, so the element painted over
194+
every GPU-drawn voxel. The plotCanvas background is now set transparent
195+
while the GPU path is active (and restored on fallback / device loss). The
196+
voxel shader itself was verified correct on real hardware (NVIDIA TITAN X via
197+
native wgpu). The GPU geometry cache also keys on ``point_colors_b64`` now,
198+
so ``set_point_colors`` recolours voxels live.
199+
- Fixed the 3-D voxel highlight appearing to "float" or land on random voxels
200+
in large grain volumes. ``Plot3D.set_point_colors`` now accepts ``voxels``
201+
panels (not just ``scatter``), so the orthoslice explorer can re-colour voxels
202+
live. The voxel grain explorer now renders the voxels that lie *on* the three
203+
slice planes (instead of a sparse random subsample of the whole volume), so the
204+
highlight marker is always anchored on a real cube at the slice intersection.
205+
The on-plane voxel count is ~3·(N/step)² regardless of N, so this stays fast
206+
even for a 256³ volume.
207+
- Interactive (⚡) documentation figures are much smoother under Pyodide. Each
208+
user interaction event was dispatched with ``pyodide.runPythonAsync`` on a
209+
freshly-built code string, which recompiles Python source every frame
210+
(~1.2 ms/event in WASM — the dominant per-frame cost on a drag). The bridge
211+
now calls a pre-compiled dispatcher proxy directly (~50× faster, ~0.02 ms),
212+
so panning, orbiting, and dragging widgets / slice planes in the docs keep up
213+
with the gesture.
214+
215+
216+
Maintenance
217+
-----------
218+
219+
- Refactored the test suite. Moved to a new directory, combined like
220+
tests into single files, added a couple new tests and removed some redundant tests. (`#11 <https://github.com/CSSFrancis/anyplotlib/pull/11>`_)

‎anyplotlib/__init__.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
from importlib.metadata import PackageNotFoundError, version as _pkg_version
2+
3+
try:
4+
__version__ = _pkg_version("anyplotlib")
5+
except PackageNotFoundError: # not installed (e.g. source tree on sys.path)
6+
__version__ = "0.0.0+unknown"
7+
18
from anyplotlib.figure import Figure, GridSpec, SubplotSpec, subplots
29
from anyplotlib.axes import Axes, InsetAxes
310
from anyplotlib.plot1d import Plot1D, PlotBar
@@ -42,4 +49,5 @@ def get_color_cycle() -> list[str]:
4249
"VLineWidget", "HLineWidget", "RangeWidget", "PlaneWidget",
4350
"show_help", "get_color_cycle",
4451
"embed",
52+
"__version__",
4553
]

‎docs/_root/switcher.json‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,9 @@
44
"version": "dev",
55
"url": "https://cssfrancis.github.io/anyplotlib/dev/"
66
},
7+
{
8+
"name": "v0.1.0 (stable)",
9+
"version": "v0.1.0",
10+
"url": "https://cssfrancis.github.io/anyplotlib/v0.1.0/"
11+
}
712
]
8-

‎upcoming_changes/+batch_perf.new_feature.rst‎

Lines changed: 0 additions & 11 deletions
This file was deleted.

‎upcoming_changes/+dblclick_1d_datacoords.new_feature.rst‎

Lines changed: 0 additions & 3 deletions
This file was deleted.

‎upcoming_changes/+embedding.new_feature.rst‎

Lines changed: 0 additions & 9 deletions
This file was deleted.

‎upcoming_changes/+geom_channel.new_feature.rst‎

Lines changed: 0 additions & 12 deletions
This file was deleted.

‎upcoming_changes/+gpu_draw_self_heal.bugfix.rst‎

Lines changed: 0 additions & 9 deletions
This file was deleted.

‎upcoming_changes/+ipf_3d.new_feature.rst‎

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)