
phoria2 is a vanilla JavaScript WebGL 2 library for generating interactive 3D charts, diagrams, floor plans, and product viewers.
It creates charts from data specifications and renders custom 3D scenes through a compact scene graph.
Each view uses a regular canvas. Dashboard views share WebGL contexts after the first four and redraw as their contents change.
Features
- Physically based materials, shadows, and HDR environment lighting.
- 6 chart marks for 3D data visualization.
- HTML labels with overlap and occlusion controls.
- Mouse picking, hover highlights, tooltips, and object dragging.
- Instanced geometry for repeated objects and data points.
- glTF 2.0 models and animations.
- Particle emitters, motion timelines, and scene editing tools.
- Keyboard navigation, screen reader descriptions, and reduced-motion controls.
- JSON scene descriptions with schema validation.
- Animated data updates and configurable chart legends.
How to Use It
Installation
Install phoria2 in your ES module project:
npm install phoria2
Import the core and whichever modules your application uses.
import { View, Mesh, box, DirectionalLight } from 'phoria2';
import { chart } from 'phoria2/charts';
import { loadGLTF } from 'phoria2/gltf';
For browser use, use an import map.
<script type="importmap">
{
"imports": {
"phoria2": "https://cdn.jsdelivr.net/npm/[email protected]/dist/phoria2.js",
"phoria2/charts": "https://cdn.jsdelivr.net/npm/[email protected]/dist/charts.js",
"phoria2/particles": "https://cdn.jsdelivr.net/npm/[email protected]/dist/particles.js",
"phoria2/gltf": "https://cdn.jsdelivr.net/npm/[email protected]/dist/gltf.js",
"phoria2/motion": "https://cdn.jsdelivr.net/npm/[email protected]/dist/motion.js",
"phoria2/edit": "https://cdn.jsdelivr.net/npm/[email protected]/dist/edit.js"
}
}
</script>
Basic Usage
The following example uses the import map above. Paste the canvas and module script into your HTML page.
<canvas id="phoria-demo" style="display:block;width:100%;height:360px"></canvas>
<script type="module">
import { View, Mesh, box, DirectionalLight } from 'phoria2';
const view = new View({
canvas: '#phoria-demo',
background: '#191d25',
orbit: true
});
const cube = view.scene.add(new Mesh({
geometry: box(2),
material: { color: '#e69b43', roughness: 0.35 }
}));
view.scene.add(new DirectionalLight({
direction: [-1, -2, -1]
}));
view.start((dt) => cube.rotateY(dt * 0.5));
</script>
Create a 3D Bar Chart
The charts module accepts an array of objects. The x and z channels group categories, and y sets each value.
<canvas id="chart-canvas" style="display:block;width:100%;height:360px"></canvas>
import { View } from 'phoria2';
import { chart } from 'phoria2/charts';
const rows = [
{ quarter: 'Q1', team: 'North', revenue: 32 },
{ quarter: 'Q1', team: 'South', revenue: 24 },
{ quarter: 'Q2', team: 'North', revenue: 41 },
{ quarter: 'Q2', team: 'South', revenue: 29 }
];
const view = new View({ canvas: '#chart-canvas', orbit: true });
const sales = view.scene.add(chart({
mark: 'bar',
data: rows,
x: 'quarter',
z: 'team',
y: 'revenue',
color: 'team',
tooltip: true,
title: 'Quarterly revenue'
}));
view.frame(sales);
Handle Clicks and Dragging
A picking result contains the object and its associated data row. Click handlers receive null for empty canvas positions.
<canvas id="scene-canvas" style="display:block;width:100%;height:320px"></canvas>
import { View, Mesh, box } from 'phoria2';
const view = new View({
canvas: '#scene-canvas',
orbit: true,
highlight: '#ffcc00',
tooltip: (hit) => hit.datum?.name ? { title: hit.datum.name } : null
});
const chair = view.scene.add(new Mesh({
geometry: box(1),
material: { color: '#46a6a0' },
data: { name: 'Movable object' }
}));
view.on('click', (hit) => {
if (hit) console.log(hit.node, hit.instance, hit.datum);
});
let initialPosition;
chair.onDrag = (drag) => {
if (drag.phase === 'start') initialPosition = chair.position.clone();
else if (drag.point) chair.position.copy(initialPosition).add(drag.delta);
};
Load a glTF Model
loadGLTF() loads .glb and .gltf models. Host the file on your web server and use a View already initialized as in Basic Usage. Your models can include meshes, cameras, and animations.
import { loadGLTF } from 'phoria2/gltf';
const model = await loadGLTF('./models/chair.glb');
view.scene.add(model);
view.frame(model);
if (model.animations.length) model.play();
API Reference
View Initialization and Options
| Option | Description |
|---|---|
canvas | Canvas element or selector. Defaults to a newly created full-window canvas. |
scene | Existing Scene instance. Defaults to a new scene. To load a JSON description, first call create(). |
camera | 'perspective' (default), 'isometric', 'top', 'front', 'side', an existing camera, or camera options { position, target, fov }. |
orbit | false by default. Enable with true or an object containing autoRotate, inertia, minDistance, maxDistance, minPitch, maxPitch, pan, zoom, and mode. |
background | Background color, panorama texture, texture options, or null. Transparent by default. |
highlight | Color applied to the object beneath the pointer. Disabled by default. |
tooltip | (hit) => content callback for a tooltip. Disabled by default. |
framing | 'height' (default), 'width', 'both', or { keep, aspect } for resize framing. |
renderMode | 'auto' (default), 'always', or 'manual'. |
toneMapping | 'neutral' (default), 'aces', or 'none'. |
exposure | Tone mapping exposure. Default 1. |
bloom | false (default), true, or { strength, radius, threshold }. |
autoLight | Automatically light scenes with no lights. Default true. |
pixelRatio | Canvas pixel ratio. Default Math.min(devicePixelRatio, 2). |
context | 'auto' (default), 'own', or 'shared'. Automatic mode assigns independent contexts to the first four views, then uses a shared context. |
antialias | Antialiasing, default true. |
maxDelta | Maximum frame delta in seconds, default 0.1, to limit jumps after backgrounding. |
keyboard | false (default), true, or { label: (hit) => text } for keyboard exploration. |
description | Accessible canvas description. Derived from the scene when keyboard navigation is active. |
reducedMotion | 'auto' (default), true, or false. |
outlineAntialias | Antialias outline edges, defaulting to the antialias setting. false uses hard pixel edges. |
The orbit object’s mode is 'orbit' (default) or 'map' for pan-only controls. autoRotate accepts true or a speed in radians per second. Distance limits apply to the camera target or orthographic view height.
View members include canvas, scene, camera, controls, renderer, width, height, time, renderMode, maxDelta, autoLight, description, reducedMotion, and bloom. When orbit is enabled, controls.spherical exposes yaw, pitch, and distance, and controls.enabled, controls.mode, and controls.autoRotate are writable.
View Methods and Events
view.start((dt, time) => {}); // Start a frame callback; dt and time are seconds.
view.frame(node, { padding: 0.2, direction: 'front', duration: 0.6 });
// Frame a node or the whole scene.
const unsubscribe = view.on('click', (hit, event) => {});
const unsubscribeHover = view.on('hover', (hit, event) => {});
// Listen for picking changes or clicks.
view.tooltip((hit) => hit?.datum?.name ?? null); // Set the tooltip callback.
view.pick(x, y); // Find the nearest hit at canvas coordinates.
view.pick(event); // Pick using a pointer event.
view.pickAll(event); // Return all hits, nearest first.
view.rayAt(x, y); // Return a world-space Ray through a canvas point.
view.rayAt(event); // Return a Ray for a pointer event.
view.framing(node, { padding: 0.2 }); // Calculate a framing position without moving the camera.
await view.toImage(); // Return a PNG Blob, including HTML labels.
await view.explain(); // Return scene diagnostics as text.
view.render(); // Draw a frame.
view.invalidate(); // Request a redraw after an untracked data mutation.
view.controls.cancel(); // Cancel the current orbit gesture.
view.dispose(); // Release the view and its resources.
Scene Graph and Node Options
| Option | Description |
|---|---|
name | Identifier for scene lookup. |
position | [x, y, z] or an equivalent vector object. |
rotation | Euler XYZ rotation in radians. |
quaternion | [x, y, z, w] or a matching object. |
scale | Uniform number or [x, y, z]. |
visible | Controls node visibility. |
pickable | Controls pointer picking. Default true. |
matrixAutoUpdate | Recompose the local matrix from position, rotation, and scale every frame. Default true. |
userData | Application metadata. |
children | Child nodes. |
showFrom | (camera) => boolean callback that determines which cameras display this subtree. |
onUpdate | Frame update callback. |
clip | { normal, offset, cap } section plane for the subtree. |
tooltip | (hit) => content callback for this node and its subtree. |
highlight | Node-level pointer highlight or false. |
highlightWhole | Highlight the entire subtree as one item, default false. |
onHover, onClick | Pointer interaction callbacks. |
onDrag | Drag callback receiving a drag object. |
outline | Color or { color, width } for a silhouette outline. |
Scene also accepts background, environment ('studio', 'none', or an HDR URL), environmentIntensity, backgroundBlur, and fog. Fog is { color, density } or { color, near, far }. A clip applies one cutting plane to meshes in its subtree; cap fills closed cut faces.
Use the #scene-canvas element from the interaction example. Vector properties accept .set(), .copy(), and per-axis assignments; bounds expose .min, .max, .getCenter(), and .getSize().
import { View, Group, Mesh, box } from 'phoria2';
const view = new View({ canvas: '#scene-canvas', orbit: true });
const group = view.scene.add(new Group({ name: 'display' }));
const cube = group.add(new Mesh({ geometry: box(1) }));
const copy = cube.clone(); // Clone a node.
group.add(copy); // Insert the clone.
group.remove(copy); // Remove the clone.
cube.translate(1, 0, 0); // Change local position.
cube.rotate([0, 1, 0], Math.PI / 4); // Rotate around an arbitrary axis.
cube.rotateX(Math.PI / 4); // Rotate around X (also rotateY, rotateZ).
cube.lookAt([0, 0, 0]); // Face a point.
cube.onUpdate((node, dt, t) => {}); // Per-frame update callback.
const off = cube.on('click', (hit) => console.log(hit));
view.scene.find('display'); // Find by name.
view.scene.findAll((node) => node.visible);
view.scene.traverse((node) => {});
cube.updateWorld(); // Update world transforms before inspection.
cube.updateWorldTree(); // Update descendants when the parent is current.
cube.bounds; // Current world-space Box3.
cube.boundsFrom(view.camera); // Geometry drawn by this camera.
cube.worldPosition; // Read-only world position.
cube.worldQuaternion; // Read-only world rotation.
cube.worldScale; // Read-only world scale.
cube.matrix; // Local transform matrix.
cube.worldMatrix; // World transform matrix.
cube.parent; // Parent node, or null.
cube.root; // Root node.
cube.children; // Child nodes.
cube.toJSON(); // Serialize the node hierarchy.
cube.removeFromParent(); // Detach the node.
cube.dispose(); // Release node resources.
off(); // Unsubscribe from the click.
Drawables, Instancing, and Labels
Meshrenders solid geometry.Pointsrenders points, with materialsizeandsizeAttenuation.Linesrenders pairs of vertices as segments, with materialwidthin pixels.InstancedMeshdraws repeated geometry with per-instance placement and colors. Its constructor acceptscount,instances,instanceMatrices,instanceColors, andlabels;countdefaults toinstances.lengthordata.lengthwhen available.
instanceMatrices holds 16 column-major numbers per instance, and instanceColors holds three linear RGB values each. Call needsUpdate() after direct array edits.
The example uses the earlier #scene-canvas element.
import { View, InstancedMesh, box } from 'phoria2';
const view = new View({ canvas: '#scene-canvas', orbit: true });
const instances = view.scene.add(new InstancedMesh({
geometry: box(1),
material: { color: 'white' },
data: [{ name: 'A' }, { name: 'B' }],
labels: { text: (row) => row.name, place: 'above' }
}));
instances.setInstance(0, { position: [-1, 0, 0], scale: [1, 2, 1], color: '#ed6a5a' });
instances.setInstance(1, { position: [1, 0, 0], quaternion: [0, 0, 0, 1], color: '#51b8a4' });
instances.setColor(0, '#f0bb77');
instances.instanceMatrices[0] = 0.8;
instances.needsUpdate(); // Required after changing a matrix array directly.
For per-instance or per-point labels, labels accepts one configuration or an array. text(datum, index) is required, priority may be a number or function, and at: 'top' | 'center' controls the instance anchor (default 'top').
| Label option | Description |
|---|---|
text | Required string or number on Label; required (datum, index) => text callback for labels on instances and points. |
position | 3D anchor position. |
className | CSS class attached to the generated HTML label. |
at | For instance labels: 'top' (default) or 'center'. |
place | 'center' (default), 'above', 'below', 'left', or 'right'. |
offset | Pixel offset [x, y]. |
priority | Overlap priority (default 0); higher values win. |
overlap | 'hide' (default), 'dim', or 'show'. |
occlude | Consider geometry occlusion; default false. |
leader | Draw a leader line and point marker; default false. Style its line via .phoria-leader. |
edge | 'left' or 'right' canvas-edge callout placement. |
minScale | Minimum on-screen pixels per world unit before showing a label. Default 0. |
onClick, onHover | Interactive label callbacks. |
Geometry and Outlines
| Generator | Arguments |
|---|---|
box() | Size number or { width, height, depth }. |
sphere() | { radius, segments, rings }. |
plane() | Size number or { width, depth }; lies in the XZ plane. |
cylinder() | { radius, radiusTop, height, segments, axis }. |
torus() | { radius, tube }. |
icosphere() | { radius, detail }. |
lathe() | { points, segments, axis } for a rotated profile. |
extrude() | { shape, holes, depth, axis } for a 2D outline. |
grid() | { size, divisions }. |
edges() | Geometry and { angle }; default angle is 1 degree. |
The axis option on cylinders, lathes, and extrusions accepts 'x', 'y', or 'z'. Public outline generators are outline.circle(radius | { radius, segments, center }), outline.rect({ width, height, radius, center }), outline.polygon({ radius, sides, angle }), outline.arc({ radius, from, to, center }), and outline.gear({ radius, teeth, depth }).
Custom geometry uses new Geometry({ positions, normals, uvs, uvs2, colors, indices }). Update vertex arrays and call geometry.needsUpdate() for geometry animation. Colors are RGB values from 0 to 1, and uvs2 supplies the second UV set.
Materials and Textures
A material is accepted as an options object or a Material instance.
| Property | Description |
|---|---|
name | Material identifier. |
color | Base surface color, default white. |
opacity | Transparency; 1 is opaque (default). |
transparent | Force blending at any opacity. By default enabled for opacity below 1. |
roughness | Microsurface roughness, default 0.5. |
metalness | Metallic response, default 0. |
emissive, emissiveIntensity | Emission color and brightness. |
unlit | Draw base color independently of lighting. |
flatShading | Flat-shaded normals. |
doubleSided | Render both sides of a surface. |
vertexColors | Multiply material color by geometry vertex colors. |
fog | Apply scene fog, default true. |
depthWrite, depthTest | Enable writing and testing the depth buffer, both true by default. |
blending | 'normal', 'additive', or 'multiply'. |
alphaTest | Discard low-alpha texture pixels. |
map | Base color texture. |
normalMap, normalScale | Normal texture and strength. |
bumpMap, bumpScale | Height texture and bump strength. |
roughnessMap, metalnessMap | Surface data textures. |
aoMap | Ambient occlusion texture. |
aoMapIntensity | Ambient occlusion strength, default 1. |
emissiveMap | Emission texture. |
lightMap, lightMapIntensity | Baked lighting map; intensity defaults to 1. |
environmentIntensity | Per-material environment strength, default 1. |
transmission | Light-through-surface amount from 0 to 1; no refraction. |
clearcoat, clearcoatRoughness | Clear glossy surface layer and its roughness. |
sheen, sheenColor, sheenRoughness | Cloth-like grazing-angle reflection; sheen roughness defaults to 0.5. |
ior | Index of refraction for surface reflectance; default 1.5. |
specularIntensity, specularColor | Nonmetallic reflection strength and tint; intensity defaults to 1. |
anisotropy, anisotropyRotation | Brushed-metal reflection strength and direction. |
translucency, translucencyColor | Thin-surface backlighting strength and tint. |
matcap | Matcap texture for baked view-relative shading. |
toon | Number of discrete tone steps; 0 is smooth. |
triplanar | Texture projection without mesh UVs; default 0. |
size, sizeAttenuation | Point diameter and distance scaling. |
width | Line width in pixels. |
new Texture(options) accepts { image } or { url }; a URL string also works. Images may be an HTMLImageElement, canvas, video, ImageBitmap, OffscreenCanvas, or raw pixel data. Call texture.needsUpdate() after drawing onto a canvas texture. texture.ready resolves after loading, and texture.dispose() releases GPU resources. Browser autoplay policies can keep a video texture paused until a click calls texture.image.play().
| Texture option | Description |
|---|---|
image | Image, canvas, video, bitmap, or raw pixel data. |
url | URL for an image, .hdr panorama, or video. |
colorSpace | 'srgb' (default) or 'linear'. |
wrap | 'repeat' (default), 'clamp', or 'mirror'. |
repeat, offset | UV pairs, default [1, 1] and [0, 0]. |
rotation, center | Angle in radians and center UV; defaults 0 and [0, 0]. |
uvSet | 0 (default) or 1 for the second UV set. |
filter | 'linear' (default) or 'nearest'. |
mipmaps | Generate mipmaps (default true; default false for video). |
anisotropy | Texture filtering level, default 8, subject to the GPU limit. 1 disables it. |
flipY | Flip pixels on upload, default false. |
Lights, Shadows, and Cameras
| Class | Public options |
|---|---|
AmbientLight | color, intensity. |
HemisphereLight | sky, ground, intensity. |
DirectionalLight | color, intensity, direction, castShadow, shadowBounds. |
PointLight | color, intensity, position, range, castShadow. |
SpotLight | Point light options plus direction, angle, and penumbra. |
PerspectiveCamera | fov, near, far, position, target. |
OrthographicCamera | height, near, far, position, target. |
The renderer uses at most two directional lights and four point/spot lights together. Shadow support includes one directional light, up to four spot lights, and one point light. Set castShadow on the light and participating meshes; receivers use receiveShadow.
Picking, Events, and Drag Callbacks
view.on('click' | 'hover', handler) and node.on('click' | 'hover', handler) are public event subscriptions. Node options onClick, onHover, and onDrag register callbacks; .on() registers additional hover and click listeners.
A hit has { node, instance, datum, point, distance, normal, face, uv }. The onDrag argument has { phase, hit, node, ray, plane, start, point, delta, event }, with phase set to 'start', 'move', or 'end'.
new Raycaster() exposes ray, camera, near (default 0), far (default Infinity), and threshold (default 0.1 world units). setFromCamera([x, y], camera) aims the ray using normalized device coordinates, and intersect(nodeOrNodes) returns ordered hits. A Ray provides origin, direction, intersectPlane(normal, point), and at(distance).
Tooltips accept plain text, an HTML element, or { title, rows }; return null to hide. HTML tooltip elements use the .phoria-tooltip CSS class. Labels accept your className for custom styles.
Charts API (`phoria2/charts`)
Create a chart with chart(spec) and insert the returned node into a scene. Channel names accept a field string, a constant, { value }, or { field, type, title, format, scale, axis, priority, legend }.
| Chart property | Description |
|---|---|
mark | 'bar', 'point', 'line', 'area', 'surface', or 'network'; also accepts Vega-Lite-style { type, tooltip }. |
data | Array of rows or { nodes, links } for networks. Vega-Lite-style { values } is also accepted. |
$schema | Optional JSON Schema identifier included in the specification. |
x, y, z | Spatial channels with fields, constants, or channel definitions. |
color, size, opacity, label | Mark encoding and text channels. |
key | Data row identity for animation, default id. |
detail | Additional data grouping channel. |
tooltip | true, false, a field, a list of fields or { field, title, format } entries, or (row, hit) => content. |
box | [width, height, depth] in world units; approximately [10, 6, 6] by default. |
stack | Stack compatible bar or area series. |
width | Fractional bar width from 0 to 1. |
legend | false or { position, container, ticks }. |
theme | 'dark' or 'light'. |
title, description | Chart title and accessible description. |
transition | false or { duration, ease } for animated updates. |
overlap | 'dim', 'hide', or 'show' for chart labels. |
highlight | Highlight color, false, 'node', or 'neighbours' for networks. |
link | Network link encodings including width, color, opacity, flow. |
layout | Network layout 'force' (default), 'given', or { tiers, order }. |
steps, seed | Positive integer force-layout iterations and numeric random seed for network placement. |
flat | true constrains network node positions to the XZ plane. |
source, target | Custom data field names for network link endpoints. |
encoding | Vega-Lite-style channel object as an alternative to top-level channels. |
Channel type accepts 'quantitative', 'nominal', 'ordinal', or 'temporal'. A channel scale accepts domain, range, scheme, zero, nice, and type: 'linear' | 'log'. Color schemes are 'category', 'viridis', 'blues', 'heat', 'cool', and 'redblue'. Axis configuration is false or { title, format, ticks }. Format accepts Intl.NumberFormat or Intl.DateTimeFormat options or a function. Chart specs require preloaded data; data: { url } does not load remote files.
legend.position accepts 'top-right', 'top-left', 'bottom-right', and 'bottom-left'; container places the HTML legend in a chosen element. Chart specs also accept Node options such as position. These methods continue the sales example above.
sales.update(rows); // Replace data and animate changes.
sales.update({ color: 'team' }); // Update a specification property.
sales.filter((row) => row.revenue > 0); // Restrict displayed rows.
sales.filter(null); // Remove the filter.
sales.summary(); // Chart description for assistive technology.
sales.explain(); // Report plotted rows and scale information.
sales.scales.y(50); // Convert a data value to a chart coordinate.
sales.marks; // Nodes rendering the chart.
sales.data; // Original chart data.
sales.spec; // Normalized chart specification.
For network charts, give each node an id and each link source/target identifiers. Surface charts need one row per grid point with x, z, and height in y. The schema is available as phoria2/charts/schema.json.
glTF API (`phoria2/gltf`)
loadGLTF(url) returns a model node with meshes, cameras, and animations. Use model.play(name?) to play the first or a specified animation. Each animation exposes play(), pause(), stop(), loop, and speed.
model.toJSON() serializes the geometry and node hierarchy. Embedded binary textures and animation tracks are not retained in that JSON representation.
Curves and Motion API (`phoria2/motion`)
The core module exports curve(points, { closed, tension }) and ellipse({ plane, radius, radiusX, radiusY, radiusZ, center, tilt }). Curves expose length, at(t), tangentAt(t), points(count), line({ segments, dash }), and tube({ radius, segments, sides }). Their normalized t values follow distance along the curve.
The optional motion module exports Timeline, follow(), face(), and reveal().
| API | Description |
|---|---|
new Timeline({ loop, speed }) | Create a sequence of timed steps. |
timeline.add(seconds, run, { label, ease }) | Add a step whose callback receives progress from 0 to 1. |
timeline.play() | Play the sequence. |
timeline.pause() | Pause playback. |
timeline.seek(seconds) | Move to a time in seconds. |
timeline.seekStep(indexOrLabel) | Move to a step. |
timeline.onStep(fn) | Register a step-change callback. |
timeline.update(dt) | Advance the timeline by elapsed seconds. |
follow(node, curve, t, { orient, forward, up }) | Move an object along a curve and optionally orient it. |
face(node, direction, { forward, up }) | Turn an object toward a direction. |
reveal(lines, t) | Show the first fraction of a curve line. |
ease accepts 'linear' (default), 'in', 'out', 'inOut', or a function. Timeline members include duration, time, step, label, steps, and playing. The forward option identifies the model’s forward axis, default '-z'.
Particles API (`phoria2/particles`)
The Emitter class extends Points and runs its particle updates as part of the scene.
| Emitter option | Description |
|---|---|
max | Maximum living particles, default 1000. |
rate | Emission per second, default 100. |
origin, originSpread | Spawn point and radius of variation. |
velocity, velocitySpread | Starting velocity and scalar or per-axis random variation. |
lifetime | Lifetime in seconds or a [min, max] range. |
color | Single color or sequence of colors over a particle’s life. |
gravity, drag | Acceleration and velocity loss per second. |
follow | Node used as the spawn position. |
onParticle | Per-particle callback; return false to remove a particle. |
Emitters also accept Node and material options. Public methods are burst(count, spawnOptions), clear(), and set(options); count reports living particles.
new Body({ velocity, gravity, drag }) updates node motion. Attach body.update with node.onUpdate(); body.impulse([x, y, z]) applies a velocity change.
Editing API (`phoria2/edit`)
TransformHandle renders a drag-and-rotation control for a selected node. History manages undo and redo.
| TransformHandle option | Description |
|---|---|
view | Required View instance. |
target | Selected node; assign handle.target = null to hide the handle. |
move | 'xz' (default), 'xyz', or false. |
turn | Enable the rotation ring. Default true. |
snap | { move, turn } snap increments, in world units and radians. |
size | Ring diameter in pixels, default 120. |
dragTarget | Allow dragging the node itself, default true. |
history | History instance for tracking edits. |
constrain | `(placement, target) => placement |
onChange | (target, phase) callback for editor updates. |
Use handle.dispose() to remove the editing overlay.
new History({ limit, onChange }) accepts a maximum record count (100 by default) and a (history) => {} callback. The public reference is:
history.push({ label, undo, redo }); // Record an already completed change.
history.undo(); // Undo; returns false when unavailable.
history.redo(); // Redo; returns false when unavailable.
history.clear(); // Clear the undo and redo stacks.
const done = history.transform(nodes, label); // Capture node transforms.
done(label); // Commit the change after moving nodes.
history.canUndo;
history.canRedo;
history.undoCount;
history.redoCount;
history.undoLabel;
history.redoLabel;
JSON Scenes and Headless Rendering
create(description) constructs a scene node from a JSON description. Its type can be 'scene', 'group', 'mesh', 'instanced-mesh', 'points', 'lines', 'label', a camera or light type, and 'chart' when the charts module is loaded. node.toJSON() produces a serializable description; handlers and function-based labels do not serialize. The scene schema is exported as phoria2/schema.json.
Use registerType(CustomNodeClass) to register custom scene node types. A custom class supplies static type, extends the parent’s static OPTIONS, and can override toJSON() for extra serializable fields. scene.ready resolves after URL-based texture assets have loaded.
For scene calculations in Node.js, use Scene, PerspectiveCamera, Raycaster, and NullRenderer. renderer.render(scene, camera) processes a frame; renderer.frameCount counts rendered frames. scene.update(dt, time) runs registered update callbacks. NullRenderer.lastFrame is a debugging detail outside the stable public contract.
Styling, Accessibility, and Browser Support
Labels are HTML elements. Use className for label styling, .phoria-tooltip for tooltips, and .phoria-leader for label callout lines:
.rack-label {
color: #fff;
font: 600 12px/1.4 system-ui;
text-shadow: 0 1px 3px #000;
}
.phoria-tooltip {
font: 13px/1.5 system-ui;
}
Set keyboard: true to place the canvas in the tab order. Arrow keys, Home, End, PageUp, and PageDown navigate interactive marks; Enter or Space invokes a click, and Escape leaves the selection. Use keyboard: { label: (hit) => text } and description for screen reader announcements. reducedMotion: 'auto' follows the system preference for camera glides, chart transitions, and auto-rotation.
phoria2 requires WebGL 2 and supports current Chrome, Edge, Firefox, and Safari, including Safari on iOS 15 and newer.
Alternatives and Related Resources
- 2D/3D Data Visualization with Ray Tracing in JavaScript – MorphCharts
- GPU-Powered WebGL Canvas Chart Library for JavaScript – WebGL Chart
- High-Performance WebGPU Charting Library for Large Datasets – ChartGPU
FAQs
Q: Why is my phoria2 canvas blank?
A: Give the canvas a nonzero CSS height and confirm WebGL 2 is available. Call console.log(await view.explain()) to check for an empty view, camera framing, and missing lights.
Q: Can a chart load its data directly from a URL?
A: Fetch or transform the rows in your application first, then pass them to chart({ data: rows, ... }). The chart spec does not fetch data: { url } for you.
Q: How do I keep dashboard charts from redrawing continuously?
A: Leave renderMode set to 'auto'. A View schedules a new frame when its scene changes; use view.invalidate() after modifying raw arrays the View cannot track.
Q: Does phoria2 support custom shaders or refractive glass?
A: No. The renderer provides predefined materials and transmission, but it has no custom shader hook or refraction effect. Choose a more extensible rendering engine for those requirements.
Q: How should I dispose of a View in React or another component framework?
A: Create the View once the canvas has mounted, update its scene when props change, and call view.dispose() during unmount cleanup.







