v.plot()builds aPlotOptionsobject and wraps an inner_plotthat materializes virtual cubes, infers dims, and returns a configuredCubePlotwith geom/axes/theme set. The function returns either theVerbor the resultingCubePlot. 【F:src/cubedynamics/verbs/plot.py†L17-L108】CubePlot.to_html()prepares stats/annotations and calls_render_viewer, which constructs the viewer HTML viacube_from_dataarraywith styling and legend metadata._repr_html_simply delegates toto_html(). 【F:src/cubedynamics/plotting/cube_plot.py†L582-L804】【F:src/cubedynamics/plotting/cube_plot.py†L836-L839】_render_viewerhands off tocube_from_dataarrayincube_viewer.py, which renders faces/interior slices to base64 PNGs and assembles the WebGL viewer template. This template is the only HTML/JS builder on the Python side. 【F:src/cubedynamics/plotting/cube_plot.py†L629-L684】【F:src/cubedynamics/plotting/cube_viewer.py†L1-L120】
Running python tools/debug_viewer_pipeline.py prints:
CubePlot type: <class 'cubedynamics.plotting.cube_plot.CubePlot'>
v.plot return type: <class 'cubedynamics.plotting.cube_plot.CubePlot'>
So v.plot() still returns a CubePlot object. 【27a0d3†L1-L2】
- Added logging hooks:
v.plot()now logs the cube name/dims when invoked;CubePlot._repr_html_logs when the HTML repr is requested. 【F:src/cubedynamics/verbs/plot.py†L81-L84】【F:src/cubedynamics/plotting/cube_plot.py†L836-L839】 - Enable by configuring
logging.basicConfig(level=logging.INFO)in a notebook/kernel; watch stdout/stderr for calls when rendering.
notebooks/cube_viewer_perf.ipynbbuilds full/half/quarter-resolution NDVI cubes and renders them withv.plot(debug=True). Record a Performance trace in Chrome and confirm[CubeViewer debug] draw start/endlogs only appear while dragging or zooming.
- The viewer HTML is entirely generated in
cube_from_dataarrayand_render_cube_html. It currently uses a custom WebGL wireframe cube (canvas.getContext("webgl")plus manual shaders) rather than the previous Lexcube integration—no references to Lexcube remain. 【F:src/cubedynamics/plotting/cube_viewer.py†L90-L195】【F:src/cubedynamics/plotting/cube_viewer.py†L374-L520】 - The HTML builds a cube wrapper with
<canvas id="cube-canvas-{fig_id}">and overlays axis labels/legend, but the JS only draws a wireframe cube; face textures are never applied to the canvas. PNG faces are still generated, but_render_cube_htmlonly uses them as CSS backgrounds fordivplanes, which are missing in the current WebGL path. 【F:src/cubedynamics/plotting/cube_viewer.py†L30-L88】【F:src/cubedynamics/plotting/cube_viewer.py†L471-L520】 - No alternative templates are present; the current template is the single path invoked by
CubePlot.
- Added guard logging around the viewer script; browser consoles should now show
[CubeViewer] script startingand report[CubeViewer] top-level errorif the script throws early. 【F:src/cubedynamics/plotting/cube_viewer.py†L369-L520】 - To capture errors: open DevTools → Console after running
pipe(ndvi) | v.plot(); watch for those messages alongside any WebGL errors.
- Searches show no
IPython.display.displaycalls in plotting/verbs; rendering relies solely on theCubePlotreturn value and its_repr_html_. No evidence of double display. 【dbd202†L1-L1】
- Face PNGs and interior planes call
.valueson slices; this is necessary for encoding small 2D images. Bulk operations include downsampling viacoarsen(...).mean()for interior planes. Potentially heavy if the cube is large andthin_time_factoris small. 【F:src/cubedynamics/plotting/cube_viewer.py†L638-L718】【F:src/cubedynamics/plotting/cube_viewer.py†L828-L868】 - Coordinate metadata lookups use
coord.valuesbut are lightweight. 【F:src/cubedynamics/plotting/cube_plot.py†L446-L474】
- H1: Viewer shows blank center because the JS path now draws only a green wireframe cube; PNG face textures (data slices) are not being mapped anywhere in the WebGL canvas. The DOM also lacks the Lexcube/CSS 3D elements that previously displayed face images. 【F:src/cubedynamics/plotting/cube_viewer.py†L374-L520】【F:src/cubedynamics/plotting/cube_viewer.py†L30-L88】
- H2: Any WebGL init failure would now surface via
[CubeViewer] top-level error; if errors appear, the script may be failing before draw (e.g., shader issues), leaving a blank canvas. 【F:src/cubedynamics/plotting/cube_viewer.py†L369-L520】 - H3: Slowness likely comes from full-face
.valuesextraction and color mapping for each face plus interior downsampling; large cubes will still materialize multiple slices eagerly before rendering. 【F:src/cubedynamics/plotting/cube_viewer.py†L638-L718】【F:src/cubedynamics/plotting/cube_viewer.py†L828-L868】
- What changed? Drag setup was refactored to share pointer/mouse/touch start logic and attach move/end listeners to
windowso rotation keeps flowing even if the pointer leaves the drag surface. Pointer capture is attempted on the drag overlay but gracefully skipped when unsupported. Drag sessions now track the active pointer/touch identifier and clear any stale listeners before beginning a new drag to prevent cross-pointer interference. 【F:src/cubedynamics/plotting/cube_viewer.py†L353-L472】 - How to debug:
- Open DevTools and watch for
[CubeViewer] drag start/move/endconsole logs when interacting with the cube. Absence of logs suggests the event listeners are not attaching (e.g., scripts blocked) or the drag surface is not present. - Verify the transparent drag surface exists with
document.getElementById("cube-drag-surface-<fig_id>"); rotation depends on this element being on top of the cube. - Pointer capture failures are expected on some touch devices; the viewer falls back to window-level listeners. If moves stop mid-drag, confirm the move/end listeners are on
windowviagetEventListeners(window)(in Chromium-based devtools) or by addingwindow.addEventListenerbreakpoints. - If drag motion stutters on multi-touch devices, inspect
activePointerId/activeTouchIdin the embedded script to ensure the move handler is gating events to the current pointer ID; stale listeners are cleared at drag start, so seeing multiple active IDs usually means the drag surface never receivedpointerup/touchend. - Zoom uses the wheel handler on the drag surface; if scroll-to-zoom stops working, inspect whether the
wheellistener is blocked by the notebook or page-level scroll container.
- Open DevTools and watch for
- Rotation is applied around the cube’s center via
applyCubeRotation(); if you see skewing or off-center rotation, inspect the inline handler that updatesrotationX/rotationYduring drag gestures before the transform is applied. 【F:src/cubedynamics/plotting/cube_viewer.py†L590-L647】 - Zoom should bring the cube closer (larger on screen). In DevTools, watch the logged
zoomvalue in thewheelhandler; if the cube shrinks when you zoom in, confirm the exponential zoom factor is clamped betweenzoomMinandzoomMax. 【F:src/cubedynamics/plotting/cube_viewer.py†L723-L730】
- The root viewer element now carries deterministic IDs (
cube-figure-<id>) plusdata-debug/data-fig-idattributes so the inline script can always locate the DOM node, even when Jupyter wraps outputs. The debug flag enables[CubeViewer debug]console logs for pointer/mouse/touch/wheel events. 【F:src/cubedynamics/plotting/cube_viewer.py†L200-L236】【F:src/cubedynamics/plotting/cube_viewer.py†L529-L596】 - PointerEvents, mouse, and touch listeners are always attached to the drag surface; wheel zoom uses a non-passive handler to prevent default scrolling. Event logs emit pointer/touch identifiers to help debug Safari or embedded-notebook quirks. 【F:src/cubedynamics/plotting/cube_viewer.py†L597-L705】
_write_demo_html()emits a standalonecube_demo.htmlwith color blocks so developers can validate drag/zoom outside of notebooks before shipping changes. 【F:src/cubedynamics/plotting/cube_viewer.py†L1032-L1072】