# Photon — full reference for LLMs # # Human-facing documentation (guides, live demos, API reference): https://coredumpdev.github.io/photon/docs/ GPU-accelerated (WebGL2) charting for the web. 40+ chart types across 2D, 3D, polar, finance, diagrams, and ML. Zero runtime dependencies, TypeScript-first. A single shared WebGL2 context backs every chart, so one page can hold dozens of live charts at 60fps. Packages (npm scope `@photonviz`): - `@photonviz/core` — the framework-agnostic engine. Everything below lives here. - `@photonviz/react`, `@photonviz/vue`, `@photonviz/svelte`, `@photonviz/solid`, `@photonviz/gea` — thin framework wrappers. - `@photonviz/wc` — framework-free Web Components (`` etc.). Requirements: WebGL2 (all evergreen browsers). ESM only. TypeScript types + dense JSDoc ship with every package — read the types to discover options. ## Install ```bash npm i @photonviz/core # optional framework bindings: npm i @photonviz/react # or @photonviz/vue / @photonviz/svelte / @photonviz/solid / @photonviz/gea / @photonviz/wc ``` ## Core concepts - A **Plot** owns a container element and a set of **layers**. `new Plot(el, options)` creates it; `plot.addLine({ x, y })` etc. add layers; the plot auto-fits and renders. - **Coordinates**: pass `x`/`y` as `number[]` or `Float64Array`. Data is uploaded to GPU buffers offset by the first point (`xRef`/`yRef`) for float32 precision. - **Streaming**: create a layer with `renderType: "dynamic"`, then call `layer.setData(x, y)` (or type-specific setters) and `plot.render()`. - **Composed charts** (finance, diagrams, ML) are free functions, not methods: `addBollinger(plot, opts)`, `addTreemap(plot, opts)`, `addConfusionMatrix(plot, opts)`. Import them from `@photonviz/core`. - **One shared GL context**: dozens of plots on a page are fine. Never keep a reference to internal GPU programs; call `plot.destroy()` when unmounting. ## Quick start ```ts import { Plot } from "@photonviz/core"; const plot = new Plot(document.getElementById("chart")!, { theme: "dark" }); const x = Float64Array.from({ length: 500 }, (_, i) => i / 20); const y = x.map((v) => Math.sin(v) + 0.3 * Math.sin(v * 3)); plot.addLine({ x, y, color: "#60a5fa", width: 2, name: "signal" }); plot.render(); ``` ## PlotOptions (common) `new Plot(container, options)`: - `theme`: `"light" | "dark"` (or a Theme object). - `scales`: `{ x?: AxisScaleOptions, y?: AxisScaleOptions }` — see Scales. - `axes`: `{ x?: AxisConfig, y?: AxisConfig }` — titles, tick format, grid. - `title`: string or `{ text, ... }`. - `legend`: `true` or a `LegendOptions` object — shows **named** series only. Entries are clickable by default (`legend: { interactive: false }` to opt out); hiding a series re-fits the auto axes. API: `plot.toggleLayer(layer)`, `setLayerVisible`, `isLayerVisible`, `onVisibilityChange`. - `colorbar`: `true` (default) / `false` / `ColorbarOptions` — see Colorbar. - `equalAspect`: keep data-units-per-pixel equal on both axes so nothing shears when the container's aspect changes. Toggle at runtime with `plot.setEqualAspect(on)` / `plot.isEqualAspect()`. - `interactive`: wheel-zoom + drag (default true). `mode`: `"pan"` (default) or box-zoom. - `hover`: crosshair + tooltip (default true). `pick`: `"x" | "y" | "xy"` (use `"xy"` for scatter/maps). - `showToolbar`: home + pan/box/X/Y-zoom buttons (default true). - `drawingTools`: add trendline/hline/ray/fib/rect drawing tools (default false). - `ariaLabel`: accessible label (auto-summarized if omitted). - `equalAspect`, `boundedPan`, `background`, `margin`, `hoverReadout`. ## 2D layers (methods on Plot) Every layer accepts `renderType?: "static" | "dynamic"`, `name?`, `yAxis?`. - `plot.addLine({ x, y, color?, width?, step?, join?, dash?, decimate?, name? })` - `plot.addScatter({ x, y, size?, marker?, color?, colorBy?: { values, colormap?, domain? }, labels? })` — marker: `"circle"|"square"|"triangle"|"diamond"|"cross"|"plus"`. - `plot.addBar({ x, y, base?, width?, orientation?: "v"|"h", color?, colors? })` - `plot.addGroupedBars(opts)`, `plot.addStackedBars(opts)` - `plot.addArea({ x, y, base?, color? })`, `plot.addStackedArea(opts)` - `plot.addHeatmap({ values, cols, rows, extent: { x, y }, colormap?, domain? })` — row-major, row 0 at bottom. - `plot.addContour({ values, cols, rows, extent, levels?, colormap? })` - `plot.addHexbin({ x, y, radius?, colormap? })` - `plot.addBox({ groups, width? })` — Tukey box plot from `BoxGroup[]`. - `plot.addErrorBar({ x, y, yerr?/xerr?, ... })`, `plot.addStem({ x, y })` - `plot.addQuiver({ x, y, u, v, ... })` — vector field. - `plot.addCandlestick({ x, open, high, low, close, upColor?, downColor? })`, `plot.addOhlc(...)` - `plot.addPie({ values, colormap? })` - `plot.addPatches({ patches })` — arbitrary colored polygons. - `plot.addImage({ source, extent })` - `plot.addGraph({ nodes, edges, ... })` — force-directed graph (`forceLayout` exported). - `plot.addAnnotation(a)` where `a` is a `span | band | box | label | line | ray | fib`. A `label` also takes `dx`/`dy` (screen px, applied after projection) and `baseline`, so stacked lines keep their spacing at any zoom. - `plot.addHistogram(values, opts)` (uses `histogram` from stats). Streaming: `const l = plot.addLine({ x, y, renderType: "dynamic" }); l.setData(x2, y2); plot.render();` ## Scales Set per axis via `scales: { x: { type, domain?, factors?, times? } }`: - `"linear"` (default), `"log"`, `"time"` (ms epoch), `"categorical"` (`factors: string[]`), `"ordinal-time"` (finance session axis: plots at integer indices, collapses market gaps, ticks snap to calendar dates — pass `times: number[]`). ## Colormaps + palettes Continuous (`colormap` option) — sequential: `viridis`, `plasma`, `inferno`, `magma`, `cividis`, `turbo`, `grayscale`; diverging: `coolwarm`, `RdBu`, `BrBG`, `spectral`; cyclic: `twilight`. `COLORMAP_KIND[name]` tells you which family. - `colormap(spec)` → `(t)=>[r,g,b]`; `colormapLUT(spec)` → Float32Array (256×3). - `spec` is a name **or** inline anchor colours: `colormap(["#000", "#0af"])`. - `registerColormap("brand", stops)` makes `"brand"` usable anywhere a name is. - `reverseColormap(spec)`, `discreteColormap(spec, steps)`, `colormapFromStops(stops)`. - `symmetricDomain(values, center?)` → a domain centred for a diverging map. Categorical (series/class colours) — `tableau10` (default), `okabe-ito` (colour-vision-deficiency safe), `set2`, `bright`. - `palette(spec)` → colours; `paletteColor(i, spec)` cycles by index. - `spec` is a name or the colours themselves; `registerPalette("brand", colors)`. - Builders take it too: `addTreemap(p, { colors: "okabe-ito" })`, `addEmbedding(p, { palette: ["#f00", "#0f0"] })`. ## Colorbar Any layer that maps values to colours reports `colorInfo()`, and the plot draws a bar per scale in the right margin — **on by default**. Turn it off or restyle it with `colorbar: false` / `colorbar: { position, label, width, ticks, format }`. Layers that report one: heatmap, hexbin, contour, choropleth patches, and `colorBy` scatter/quiver (plus the 3D surface/bar/quiver/volume). ## 3D — Plot3D ```ts import { Plot3D } from "@photonviz/core"; const p3 = new Plot3D(el, { axisLabels: { x, y, z }, autoRotate?, lightControls? }); p3.addSurface({ values, cols, rows, extentX, extentZ, colormap?, wireframe? }); ``` Layers: `addSurface`, `addPointCloud({ x, y, z, colorBy? })`, `addLine3D`, `addBar3D`, `addBoxes3D({ boxes: [{ x, y, z, w, h, d, color?, label? }] })` (independently sized lit cuboids), `addQuiver3D`, `addContour3D`, `addIsosurface({ values, dims, isoLevel })`, `addVolume({ values, dims, colormap?, density? })` (GPU raymarch). Orbit: drag; zoom: wheel. `marchingCubes(values, dims, isoLevel)` is exported (pure). Camera/framing options: `aspectMode: "cube" | "data"` ("data" keeps true proportions — use it for long scenes), `projection: "perspective" | "orthographic"`, `showAxes: false` (hide box + ticks, for diagrams). ## Polar — PolarPlot ```ts import { PolarPlot } from "@photonviz/core"; const pp = new PolarPlot(el, { maxRadius? }); pp.addLine({ theta, r, closed? }); pp.addScatter({ theta, r }); ``` ## Finance Indicators (pure array→array): `sma, ema, wma, rollingStd, bollinger, rsi, macd, vwap, atr, trueRange, stochastic, keltner, obv, ichimoku, adx, superTrend, fibRetracements, firstFinite, cci, mfi, williamsR, aroon, donchian, parabolicSar, pivotPoints`. Transforms: `heikinAshi, renko, lineBreak, pointAndFigure, volumeProfile, depth`, `resampleOhlc(time, ohlc, bucketMs, volume?)` (roll bars up to a coarser timeframe), `drawdown(equity)` → `{ values, peak, maxDrawdown, troughIndex, peakIndex }`. Builders (compose layers on a Plot): `addHeikinAshi(plot, opts)`, `addRenko`, `addVolumeProfile`, `addBollinger`, `addDepth`, `addDrawdown(plot, { equity })`. Use an `ordinal-time` x scale for gap-free session charts. ## Diagrams (composed builders) `addTreemap(plot, { items })`, `addFunnel`, `addSunburst`, `addGauge`, `addSankey`, `addChord`, `addParallelCoordinates`. Pure `*Layout` functions are exported too. ## ML / deep-learning Pure metrics: `confusionMatrix, rocCurve (AUC), prCurve (AP), calibrationCurve (ECE), emaSmooth`. Reducers: `pca(data, n, d, k?)`, `standardize`. Builders (compose layers on a Plot): - `addConfusionMatrix(plot, { yTrue, yPred, classes?, colormap?, normalize? })` - `addRocCurve(plot, { scores, labels, fill? })`, `addPrCurve`, `addCalibration` - `addEmbedding(plot, { x, y, labels?, classNames?, colorBy? })` — colored by class - `addDecisionBoundary(plot, { values, cols, rows, extent, points? })` - `addFeatureImportance(plot, { names, values, top? })` - `addShapBeeswarm(plot, { values, featureValues?, names })` (+ `beeswarmLayout`) - `addPartialDependence(plot, { x, pd, ice? })` - `addAttentionMap(plot, { weights, colormap? })` - `addTrainingCurves(plot, { series, smoothing?, best? })` — EMA smoothing + best epoch - `addRidgeline(plot, { groups, overlap? })` — distributions over time - `addPredVsActual(plot, { yTrue, yPred })` — with the y=x reference and R² - `addResiduals(plot, { yTrue, yPred, against?: "predicted"|"index" })` - `addLiftCurve(plot, { scores, labels, mode?: "gain"|"lift" })` - `addLearningCurve(plot, { sizes, train, validation, trainStd?, validationStd? })` More pure metrics: `mse`, `rmse`, `mae`, `r2`, `logLoss`, `brierScore`, `classificationReport(yTrue, yPred, classes?)` (per-class precision/recall/f1 + macro/weighted), `liftCurve`, `rocCurveOvR(scores, labels, classes)` (one-vs-rest with macro/micro AUC). ## Model architecture graphs A `ModelGraph` is `{ name?, nodes: [{ id, name?, type, shape?, params?, flops?, group? }], edges: [{ from, to }] }` — `shape` is the output tensor with the batch dim dropped. Build one with an adapter (all pure): - `sequentialModel(layers, name?)` — ordered list, edges added automatically - `modelGraphFromTorchFx(nodes, opts?)` — `torch.fx` trace (keeps skip connections) - `modelGraphFromKeras(config, { shapes?, params? })` — `json.loads(model.to_json())`, Sequential and functional (Keras 2 + Keras 3 `inbound_nodes`) - `modelGraphFromSklearn(step)` — Pipeline / ColumnTransformer / FeatureUnion tree (`mode: "sequential" | "parallel"`); `mlpModel(layerSizes)` for an MLPClassifier - `modelGraphFromOnnx(graph, { shapes?, paramCounts? })` — `MessageToDict(model.graph)` Render it either way, from the same graph: - `addModelGraph(plot, { graph, direction?: "vertical" | "horizontal", sizeBy?: "params" | "flops", labels?, colors?, theme? })` — layered DAG of rounded boxes with orthogonal connectors; skip edges route around the trunk. Returns `{ layout, nodes, edges, nodeAt(x, y), destroy() }`. Blanks the axes and adds its own hover tooltip; pair with `new Plot(el, { hover: false })`. - `addModelGraph3D(plot3d, { graph, sizeScale?, rankSpacing?, branchSpacing? })` — one cuboid per layer, the visible face from the last two shape dims and the thickness from the rest, so a CNN's feature maps shrink as depth grows. Pair with `new Plot3D(el, { aspectMode: "data", showAxes: false, projection: "orthographic" })`. Slices — draw a layer as its channels instead of one shape, in either dimension: `slices: "none" | "channels" | number` (default "none"), plus `"voxels"` in 3D, which subdivides all three axes into a real channels × height × width grid of cubes. `maxSlices` caps each axis (default 12); `maxVoxels` caps the product (default 20 000, and the axes shrink together when it is exceeded, so the proportions survive). `sliceSpread` sets the 2D card fan-out (default 0.3), `sliceGap` the 3D gap per cell (default 0.35). Neither view shifts: in 2D the front card stays where the box was and keeps the labels, in 3D the stack fills exactly the space the solid cuboid occupied. `addModelGraph` locks the plot's aspect (`equalAspect`, on by default) because box proportions, corner radii and arrowheads are all in data units — a free aspect shears every one of them when the container changes shape. Its labels are positioned with pixel offsets, so the text block stays tight at any zoom. Pure helpers: `modelLayout(graph, opts)` → `{ nodes, edges, extent, ranks }`, `modelBoxDims(nodes, opts)`, `layerCategory(type)`, `LAYER_COLORS`, `formatCount`, `formatShape`, `tensorMetrics(shape)` -> [channels, height, width]. ## Signal + statistics Signal (pure): `windowFunction(name, n)` (`hann`/`hamming`/`blackman`/`bartlett`/ `rectangular`), `welch(signal, { segment?, overlap?, window?, sampleRate? })` → `{ frequencies, power }`, `savitzkyGolay(values, window?, order?)` (peak-preserving smoothing), `crossCorrelate(a, b, maxLag?, normalize?)` → `{ lags, values }`. Fits + summaries (pure): `linearRegression(x, y)` → `{ slope, intercept, r2, stderr, predict }`, `linearTrend(x, y, { points?, band? })`, `loess(x, y, { bandwidth?, points? })`, `ecdf(values)`, `zscore(values)`, `correlation(a, b)`, `corrMatrix(columns)`. Builders: `addRegression(plot, { x, y, method?: "ols"|"loess", band? })`, `addEcdf(plot, { values })`, `addCorrMatrix(plot, { columns, names? })` (diverging, locked to ±1), `addPsd(plot, { signal, sampleRate?, window? })`. ## Data + stats `parseCSV(text, opts)` → `{ columns, rows }`. `lttb(x, y, threshold)` — downsample. `histogram, boxStats, quantileSorted, kde, fft, spectrogram` (pure). ## Framework wrappers Two shapes: 1. **Component-based** (react/vue/solid) — one component per layer inside a ``: ```tsx // React import { Plot, Line, Scatter, YAxis } from "@photonviz/react"; ``` Imperative escape hatch to the core `Plot`: React `usePlot()`; Vue/Solid/Gea `onReady={(plot) => …}`. Use it to call composed builders (finance/diagram/ML) and anything not exposed as a component. 2. **Series-spec based** (svelte/gea/wc) — a `series` array of `{ type, ...opts }`: ```svelte
``` Web Components: `document.querySelector("photon-plot").series = [{ type: "line", x, y }]`; reach the core Plot via the element's `.plot` getter. All wrappers re-export the pure functions + composed builders (indicators, ML metrics, `pca`, etc.) so you can compute/compose imperatively. ## Gotchas - **WebGL2 is required.** No canvas-2D fallback. - **ESM only**; internal imports use `.js` specifiers even for `.ts` sources. - The **legend shows only explicitly `name`d series** — unnamed helper layers are hidden. - Heatmap `values` are **row-major with row 0 at the bottom**. - Call **`plot.destroy()`** (or the wrapper's unmount) to free GPU resources. - For scatter/graph hover, set `pick: "xy"` on the plot. - Streaming needs `renderType: "dynamic"` + `setData(...)` + `plot.render()`.