@@ -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 >`_)
0 commit comments