@demo-charts/renderer-3d
v0.1.34
Published
Standalone 3D renderer and chart primitives
Readme
3D chart (@demo-charts/renderer-3d)
This package implements the phase-resolved 3D bar (“PRPD-style”) chart: vertical spikes over a phase × cycle floor, with an optional reference curve on the back wall, orbit camera, and tooltips.
High-level architecture
App / React
│
▼
RmChart3D ← thin facade: setSeries, updateOptions, resize, destroy
│
▼
ChartController3D ← DOM, input, camera state, tooltip, series transitions
│
▼
Canvas3DRenderer ← single HTML canvas, 2D context: full scene draw + hit-test
│
▼
PhaseGraph3DSeries ← normalized spike + wall-curve data, styles, bounds, interpolationExported public API: RmChart3D, ChartController3D, PhaseGraph3DSeries, and types from ./types.
Scene graph, coordinates, and WebGL background passes are described in PRPD_WEBGL_SCENE_ARCHITECTURE.md.
The React wrapper (@demo-charts/react → RmChart3DComponent) mounts a div, constructs RmChart3D({ ...options, container }), and calls setSeries when the series instance changes.
Data model: PhaseGraph3DSeries
A series is defined by parallel arrays of equal length:
| Field | Role (typical PRPD mapping) |
|--------|-----------------------------|
| phase | X on the floor (e.g. phase angle 0…360°) |
| cycle | Z on the floor (e.g. cycle index) |
| amplitude | Height of the bar (Y) |
Optional wallCurve: phase[] + amplitude[] — a polyline on the back vertical wall (phase vs amplitude), e.g. a sine reference.
Style (PhaseGraph3DSeriesStyle): bar color, stroke, wall curve color, spike width / depth, spike radius (0 = rectangular pillar, higher = rounder cap toward “cylinder”), wall line width.
The constructor normalizes inputs to number[] and truncates to the common length. getBounds() computes min/max over spikes (and wall curve for phase/amplitude) for axis scaling.
PhaseGraph3DSeries.interpolate(from, to, t) blends two series for smooth transitions when ChartController3D animates a data update.
Controller: ChartController3D
Responsibilities:
Container — Resolves
containerfrom a CSS selector orHTMLElement, positions itrelative, injects:- One
canvas(full size of container). - One
tooltipdiv(absolute, non-interactive, high z-index).
- One
Camera — Orbital parameters (degrees / distance):
yaw,pitch— updated by drag (pointer delta → clamped angles).distance— dollies in/out with the mouse wheel (clamped tominDistance/maxDistancefrom options).
Interaction (see
Chart3DInteractionOptions):- Rotate: pointer drag on canvas (unless
interaction.rotate === false). - Zoom: wheel (unless
interaction.zoom === false). - Double-click: resets camera toward options defaults (
camera.yaw,pitch,distance). - Tooltip: pointer move runs hit-test; if a spike is hit, shows phase / amplitude / cycle (unless
interaction.tooltip === false).
- Rotate: pointer drag on canvas (unless
Pointer wiring — Uses
pointerdown/pointermove/pointerup/pointerleaveon the canvas;pointermove/pointerupare registered onwindowso dragging continues outside the element.Resize —
ResizeObserveron the container callsresize()→ matches canvas backing store (with devicePixelRatio cap) and re-renders.Series updates —
setSeries(series):- First series: immediate assign + render.
- Later updates: starts a short transition (~220 ms) interpolating from the previous series to the new one via
PhaseGraph3DSeries.interpolate, driven byrequestAnimationFrame.
Rendering: Canvas3DRenderer
Uses getContext('2d') only for the shipped path: no WebGL in this pipeline.
render(series, options, state, hoverHit?) pipeline (order matters for painter’s algorithm):
- Clear and fill background (from theme /
darkMode). buildProjection— Builds a simple perspective state from canvas size +yaw/pitch/distance(focal length, camera Z, center on screen). This is custom projection math (not a full scene graph).drawFloor— Floor plane with grid, striped cells (light/dark), clipped to the chart volume.drawHoverFloorGuide— On spike hover: L-shaped floor guides(phaseₘᵢₙ,cycle)→(phase,cycle)→(phase,cycleₘᵢₙ). Colourtheme.hoverFloorGuideColor(default#ff6a14inresolveChart3DTheme). Stroke thickness follows pillars (~half the narrower pillar semi-axis →pillarGraphHalfExtents). WebGL:renderHoverFloorCrosshairuses ribbon quads; samehoverHitas the tooltip.drawWall— Back wall quads + grid (behind the data volume).drawWallCurve— The reference polyline on the wall fromseries.getWallCurve().drawSpikes— For each spike, project base + top to screen; sort by depth (back to front); draw each spike as a filled trapezoid (body) + strokes + optional rounded cap at the top (pillar vs rounded style fromspikeRadius).drawAxesAndLabels— Axis titles, ticks, numeric labels (fromxAxis/yAxis/zAxisin options), using resolved theme and typography.
HiDPI: The canvas bitmap is scaled by dpr (capped at 2); drawing uses logical coordinates after ctx.scale(dpr, dpr).
Hit-test (hitTest) — Rebuilds the same projected spike segments as buildSpikeItems, iterates front to back (reverse of draw order), finds the closest spike whose screen-space distance from the pointer to the base–top segment is within a tolerance. Returns Chart3DHitResult: phase, cycle, amplitude, index, and screen point at the top.
Options (Chart3DOptions)
container— Selector string or element (required).width/height— Optional explicit size; else container client size on resize.darkMode+theme— Background, floor checkerboard tiles (floorStripeA/floorStripeB), wall amplitude bands (wallStripeA/wallStripeB;wallFillstill overrides stripe A in the WebGL adapter only), grid (floorGrid/wallGrid), axis and default spike colors.xAxis,yAxis,zAxis— Labels, min/max, tick count or tick values,formatLabel, per-axis typography colors/sizes/fonts.camera— Initial / reset yaw, pitch, distance, min/max distance for zoom.interaction— Toggle rotate, zoom, tooltip.tooltip— Colors, font.
Axis mapping in the 3D scene is handled inside the renderer (phase/cycle/amplitude bounds vs. the fixed “graph box” in projection space); see resolveBounds / projection helpers in Canvas3DRenderer.ts.
Optional / internal GPU path
The default rendererBackend is auto, which prefers WebGL2 instanced pillars + a transparent Canvas2D overlay for axis labels. Set rendererBackend: 'canvas2d' to force the legacy full Canvas2D path.
Internals: WebGLPhaseSpikeRenderer, WebGLChart3DAdapter, GPU picking, chunking, renderPassGraph.ts (explicit background → pillar passes), gpuGraphProjection.ts, interleave worker (gpuInterleaveWorkerSource.ts), optional prpdAggregationLod (phase×cycle bins), data-prep worker string (prpdDataPrepWorkerSource.ts), and WebGPUComputeBackend (stub for future compute).
Package entry
src/index.ts exports:
./types./PhaseGraph3DSeries./ChartController3D./Canvas3DRenderer./RmChart3D
Typical usage (vanilla)
import { RmChart3D, PhaseGraph3DSeries } from '@demo-charts/renderer-3d';
const chart = new RmChart3D({
container: '#chart-3d',
xAxis: { label: 'Phase Angle (deg)', min: 0, max: 360, /* ... */ },
yAxis: { label: 'Amplitude (mVp)', min: 0, max: 2000, /* ... */ },
zAxis: { label: 'Cycle', min: 0, max: 50, /* ... */ },
camera: { yaw: -50, pitch: 32, distance: 3.65, maxDistance: 28 },
interaction: { rotate: true, zoom: true, tooltip: true },
});
chart.setSeries(
new PhaseGraph3DSeries({
name: '3D Phase Distribution',
spikes: { phase, cycle, amplitude },
wallCurve: { phase: wallPhase, amplitude: wallAmp },
style: { spikeColor: '#42a5f5', spikeStroke: '#1565c0', /* ... */ },
})
);Optional camera tuning: orbitRadiusBase overrides the additive orbit radius base, orbitTarget sets a graph-space look-at pivot (ignored when autoFit: 'spikes'), and autoFit: 'spikes' recenters on sampled spike centroids with a gentle zoom scale for tight clouds.
Files (reference)
| File | Role |
|------|------|
| RmChart3D.ts | Public API entry |
| ChartController3D.ts | Layout, events, camera, tooltip, transitions |
| Canvas3DRenderer.ts | 2D canvas drawing + projection + hit-test |
| PhaseGraph3DSeries.ts | Data + styles + bounds + interpolation |
| math.ts | Angles, color shading helpers |
| types.ts | Options and data typings |
| WebGLPhaseSpikeRenderer.ts | Optional WebGL instanced pillars (not in default bundle export) |
