Clean history#24
Closed
CSSFrancis wants to merge 215 commits into
Closed
Conversation
Add initial README.md with project overview and usage examples.
…r, and line geometries
…segments, points, polygons, rectangles, and vertical lines with live updates in anyplotlib
…ime updates in Anyplotlib
…per workflows in Anyplotlib
…rt for vertical and horizontal orientations
…tangles, squares, polygons, texts, points, horizontal lines, and vertical lines for enhanced plotting capabilities
…age examples for plotting functionality
…grid cell allocation, removing aspect-locking logic. Update tests to verify correct canvas dimensions for various image aspect ratios and configurations. Add documentation for new layout behavior and introduce a navigator for `figure_esm.js`.
- Introduce `on_key` callback API for registering key-press handlers. - Implement functionality to add various shapes (rectangles, circles, annuli) at cursor position. - Allow deletion of the last clicked widget using the Delete key. - Update documentation to reflect new key binding features and usage examples.
…2D methods and ensure top-level imports are accessible.
…; create new RST files for callbacks, figure plots, markers, and widgets
…tailed parameter descriptions, examples, and return values
This reverts commit a77ee38.
…n for consistency
…rameter descriptions for subplot specification
…ay and improve scaling logic for interactive widgets
Co-Authored-By: Carter Francis <cartsfrancis@gmail.com>
Refactor/project reorganization
Adds a _PYODIDE_MOCK_PACKAGES = [...] variable that examples can declare to register packages unavailable in Pyodide as micropip mock packages before the wheel install, preventing dep-resolution failures. Changes: - _scraper.py: parse _PYODIDE_MOCK_PACKAGES_RE, emit data-pyodide-mock-packages attribute on the <script type="text/x-python"> tag - _directive.py: same parsing for the anywidget-figure directive - anywidget_bridge.js: collect data-pyodide-mock-packages from all script tags BEFORE the wheel install step (step 6) and register them as mock packages; also carry mockPackages through the srcGroups map for per-example use in step 9
…ss examples and documentation
…ts; implement batching for panel updates
Major additions across rendering, embedding, and performance: Rendering & plot types - True-colour RGB imshow ((H,W,3|4) arrays) - 3D voxels geometry + draggable PlaneWidget slice selectors - scatter3d per-point colours, set_highlight, reference sphere, bounds - Turntable 3D camera (matplotlib azim/elev; reaches any viewpoint) - Mini-TeX label formatting (superscripts/Greek/symbols) + fontsize control - IPF and voxel-grain explorer examples; label-formatting example Embedding (no Jupyter required) - anyplotlib.embed: figure_state/to_html/save_html/esm_path + FigureBridge - figure_esm.js mount() API for Electron / plain web pages - docs/embedding.rst guide WebGPU (progressive enhancement, canvas fallback) - Instanced points + voxels on the GPU via gpu="auto"; airtight Canvas2D fallback (no navigator.gpu / null adapter / device loss). No JS deps. - Hardware-verified on NVIDIA Pascal; WEBGPU_PLAN.md tracks phases. Performance (smoothness under Pyodide) - Figure.batch() coalesces panel pushes (one per panel per frame) - Geometry channel: heavy buffers ride a separate hash-gated trait, re-sent only on change — view updates (highlight/camera/planes) no longer retransmit geometry. Voxel explorer per-frame traffic -65%. - Voxel sprite cache + typed-array projection; RGB imshow skips LUT rebuild Packaging - LICENSE (MIT), wheel excludes tests/baselines, py3.13 classifier - RELEASE_PLAN.md; committed uv.lock Tests: +~90 across labels, embed, RGB, voxels/planes, GPU fallback, batch, geometry channel. Full suite 1323 passed / 58 skipped.
The default 11px bold 2D title was drawn strip-tight (12px strip, centered), leaving ~0px top margin. macOS Chromium hints fonts ~1px taller than the Windows dev box, so on macOS CI the title ascender reached row 0 of the title canvas and test_no_clipping flagged it as clipped. Clamp the drawn title size to padT-4 (reserve ~4px total vertical margin). Since _padT grows for large/TeX titles, this only affects the 12px default strip, capping an 11px title to ~8px — a sub-pixel change well within the visual-regression tolerance (all 24 baselines still match). Now ~2px top and bottom margin on the dev box, which absorbs the cross-platform hinting variance.
Self-review of the geometry channel found two cleanups: - Figure._push hashed (md5 of a 346 KB json.dumps) the geometry blob on EVERY push to detect change — ~1.4 ms of pure CPU per view-only frame just to discover nothing changed, partly undercutting the transfer win. Replace with a direct equality check against the last-sent geom dict: the b64 strings are the same objects when unchanged, so equality short-circuits (~0.05 ms/call, 28x faster) and no serialise happens until geometry really changes. Drops the now-unused hashlib import; renames _geom_hash -> _geom_last. - _applyGeom had a dead if/else (both branches identical). Collapse to a single "apply cache when present" with a clearer comment. No behaviour change; full suite 1323 passed / 58 skipped.
A touch-to-mouse bridge (_attachTouch, attached per panel in _attachPanelEvents) translates touch gestures into the existing mouse/wheel handlers via real MouseEvent/WheelEvent dispatch, so every panel kind and every example becomes touch-capable with no handler rewrites: - 1 finger drag -> pan / orbit / drag a widget, ROI, marker or plane - 2 fingers pinch -> zoom (wheel), centred on the gesture midpoint - double-tap -> dblclick -> the panel's double_click event move/up dispatch to document (handlers listen there for off-canvas drags); down/wheel/dblclick to the overlay canvas. Overlay canvases set touch-action:none so the browser yields gestures to the plot rather than scrolling the page. Tests: Playwright touch emulation (has_touch=True) across 2D/3D/1D drag, pinch zoom, and double-tap; 416 existing mouse-interaction tests unchanged. Full suite 1330 passed / 58 skipped.
Add touch input
Two issues made the voxel explorer's slice selector feel broken: 1. Snap-back (underlying code): Plot3D.to_state_dict() returned dict(_state) without refreshing overlay_widgets from the live widgets, so any view-only push that goes through the targeted-fields path (set_highlight / set_view) re-serialised a stale plane position and overwrote the in-progress drag in JS. The drag would jump back to the last Python-known integer position. Fix: to_state_dict() now always rebuilds overlay_widgets from the live _widgets, so every push path carries the current plane positions. 2. Faulty / jumpy highlight (example): the explorer snapped the highlight to integer voxel indices, so the marker jumped while the plane glided. Now it tracks smooth float positions (fx,fy,fz) for the highlight and the planes that follow, keeping integer indices only for slicing the 2D images. Adds 3 regression tests (TestPlaneDragNoSnapBack) asserting set_highlight / set_view preserve a live plane position. 3D suite + example execution green.
The voxel highlight appeared to land on random voxels in large (256³) volumes because the example rendered a sparse random subsample of the whole volume — the highlight projected to the correct (ix,iy,iz) but almost never coincided with a displayed cube, so it floated in empty space. Render the voxels lying ON the three slice planes instead, re-cut live on each drag. The marker is now always anchored on a real cube at the slice intersection, the volume shows actual slice contents, and the on-plane count is ~3·(N/step)² regardless of N, so it stays fast at 256³. - Plot3D.set_point_colors: accept voxels panels, not just scatter, so slabs can be recoloured live each drag. - Add the voxel grain explorer to the example smoke tests. - Add a set_point_colors-on-voxels regression test.
A 256³ grain explorer produced ~8112 slab voxels, which crossed the GPU_VOXEL_THRESHOLD (8000) and switched the panel to the Phase-1 WebGPU voxel path. That path is not hardware-verified in CI — headless Chromium exposes no WebGPU adapter, so the canvas fallback is what every test exercised — and on real GPUs it rendered a sparse, see-through volume (cubes appearing to float / vanish) instead of solid slice slabs. - Raise GPU_VOXEL_THRESHOLD 8000 -> 20000 so mid-size volumes stay on the depth-sorted, visual-regression-tested Canvas2D renderer. The grain explorer (~8k cubes) now renders its full slabs (verified at N=256). - GPU voxel pipeline: cullMode 'none'. The MVP swaps rows (r0,r2,r1) and negates depth, so cube winding can't be relied on for back-face culling; culling dropped the wrong faces. Translucent cubes need all faces anyway. - GPU geometry cache now keys on point_colors_b64 too, so set_point_colors recolours voxels live instead of reusing a stale colour buffer. - Add a browser regression test that a ~8k-voxel volume renders dense slabs on the canvas path (gpuCanvas hidden).
Correct the diagnosis from the previous commit. Large voxel volumes rendered 'empty' (only plane widgets + highlight, no cubes) in WebGPU-enabled browsers (PyCharm JCEF = Chrome 137). It was NOT the shader or back-face culling. Real cause: the 3D panel stacks gpuCanvas (z-index 0, WebGPU voxels) under plotCanvas (z-index 1, decorations). Activating the GPU path cleared the plotCanvas bitmap but left its opaque CSS background, so the element painted over every GPU-drawn voxel. - plotCanvas background -> transparent while GPU active; restored on fallback, device loss, and state-no-longer-wants-GPU. - Revert earlier workarounds now shown unnecessary: GPU_VOXEL_THRESHOLD back to 8000, cullMode back to 'back' for voxels. - Verified the voxel WGSL + _gpuMatrix on real hardware (NVIDIA TITAN X via native wgpu): three solid slabs; cull 'back' == 'none'. - Retarget regression test to the DOM layering invariant; the active-GPU transparent swap is hardware-verified separately (no WebGPU adapter in CI).
Safari's experimental WebGPU can activate, render correctly for a while, then throw mid-draw or lose the device. The GPU path makes the decoration plotCanvas transparent and takes GPU-only branches (proj==null etc.), so a mid-draw throw left the frame half-built: voxels AND axes vanished, and only a window resize (forcing a full redraw) brought them back. The catch block now disposes the GPU panel, restores the opaque plotCanvas background, and re-renders the whole panel once on the canvas path in the same frame (guarded against re-entry via p._gpuFellBack, reset per draw3d call). Verified by simulating an adapter+device that activates then throws on createCommandEncoder: no page errors, gpuCanvas hidden, plotCanvas opaque, voxels + axes rendered. Note: the kernel 'Task was destroyed but it is pending' warnings are ipykernel shutdown noise, unrelated to this.
Fix/plane drag snapback
The docs ⚡ bridge dispatched every interaction event with pyodide.runPythonAsync(code-string), which parses + compiles a fresh Python code string per event — ~1.2 ms/event in WASM, the dominant per-frame cost of the Pyodide interaction path on a drag (30-60 events/sec). That's why 3D orbit / plane-drag felt sluggish in the docs vs a live Jupyter kernel. Define a pre-compiled `_awi_dispatch(fig_id, data)` function once at boot and call its PyProxy directly from the message handler (~0.02 ms/event, ~50x faster, measured end-to-end in Pyodide). The proxy is fetched lazily and cached (robust to boot-step ordering) with a runPythonAsync fallback. The Python->JS return path was already efficient (a compiled observer closure calling js.window._anywidgetPush), so no change needed there. All 12 bridge-boot tests pass.
Speed up Pyodide interaction dispatch ~50x (
… plots A blank coordinate canvas in the matplotlib transData + PathCollection model: set_xlim/ylim (+ aspect) and draw scatter/plot/fill/text as collection markers in DATA coords. Reuses the 1-D data->canvas transform (kind='1d', hidden curve), so no renderer change is needed for native data-coord drawing. Foundation for an orix plotting backend (stereographic / IPF / pole figures). See ORIX_BACKEND_PLAN.md.
…[...]) PlotXY.scatter(c=[...]) now renders a per-offset colour gradient (matplotlib PathCollection parity) instead of a single fill colour. Clarify in the orix- backend plan that stereographic/IPF plotting stays in orix; anyplotlib only provides the generic data-coord 2-D surface.
_plotRect1d now takes the panel and, when state.aspect==='equal', shrinks and centres the box so one data unit spans equal pixels on x and y — the matplotlib apply_aspect model. Baked into the shared rect helper so draw1d / drawMarkers1d / overlay / hit-test all derive from the identical adjusted box (matplotlib's transData is relative to the axes box). A wide-panel IPF triangle now renders undistorted instead of stretched. Tests: test_aspect_equal_renders_square vs test_aspect_auto_fills_panel. Closes both renderer gaps the PlotXY demo exposed (per-point scatter colours landed in 4f4e780); anyplotlib is now a complete-enough generic 2-D backend. The stereographic/IPF projection stays in orix.
PlotXY.pcolormesh(x, y, c) draws a matplotlib-style quad mesh in data coords: (N+1,M+1) corner grids + an (N,M) scalar field (mapped through a colorcet/matplotlib LUT between vmin/vmax) or colour-string array. Masked / non-finite cells are skipped, so an orix pole_density_function histogram (masked outside the fundamental sector) renders natively as an inverse pole density-function heatmap — no matplotlib raster. Renderer: the 1-D polygons path now honours per-polygon fill_color / color arrays (matplotlib PathCollection), the same change made earlier for points; edges default to the face colour for a seamless heatmap. Tests: test_pcolormesh_builds_polygon_mesh (structure + masked-cell drop) and test_pcolormesh_renders_gradient (chromatic gradient render).
A marker group (wired through add_polygons and PlotXY.pcolormesh) accepts a clip_path: a (K,2) data-coord polygon the group is clipped to, matplotlib's set_clip_path. drawMarkers1d applies it as a canvas clip inside the per-set save()/restore(). Use it to keep a pcolormesh mesh inside a curved boundary (an IPF fundamental sector) so the coarse edge cells don't overflow. Test: test_pcolormesh_clip_path_clips_mesh (a triangle clip drops ~half a square mesh's cells).
The 1-D dblclick handler already computed xdata; add ydata by inverting the linear y transform. draw1d caches the rendered y bounds (p._1dDMin/_1dDMax) so the inverse uses exactly what was drawn — for a PlotXY coordinate axis the y range is y_range, not the hidden zero-curve's data_min/max. Matches the 2-D image path so a coordinate axis can be picked in data space. Test: test_double_click_reports_data_coords (centre click → centre of x/y range).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Remove AI agent commits from history.