A high-performance, zero-dependency WebGL2 graph visualization library with optional React bindings.
npm install @godu/trapimport { Renderer } from "@godu/trap";
const canvas = document.querySelector("canvas");
const renderer = new Renderer({
canvas,
nodes: [
{ id: "a", x: 0, y: 0, r: 1, g: 0, b: 0, a: 1, s: 10, z: 0, i: 0, l: "Node A" },
{ id: "b", x: 100, y: 50, r: 0, g: 0, b: 1, a: 1, s: 8, z: 0, i: 0, l: "Node B" },
],
edges: [
{ id: "e1", src: "a", tgt: "b", r: 0.5, g: 0.5, b: 0.5, a: 1, s: 1, z: 0 },
],
});
renderer.fitToNodes(0);
renderer.render();import { Graph } from "@godu/trap/react";
import { useRef } from "react";
import type { GraphRef } from "@godu/trap/react";
function App() {
const ref = useRef<GraphRef>(null);
return (
<div style={{ width: "100%", height: "100vh" }}>
<Graph
ref={ref}
nodes={nodes}
edges={edges}
fitKey="initial"
onNodeClick={(e) => console.log(e.nodeId)}
/>
</div>
);
}The <Graph> component fills its parent container, manages the renderer lifecycle, and provides built-in label rendering.
All fields are required.
| Field | Type | Description |
|---|---|---|
id |
string |
Unique identifier |
x |
number |
World x position |
y |
number |
World y position |
r |
number |
Red channel (0-1) |
g |
number |
Green channel (0-1) |
b |
number |
Blue channel (0-1) |
a |
number |
Alpha (0-1) |
s |
number |
Radius in world units |
z |
number |
Z-index for draw order (lower draws first) |
i |
number |
Icon index (1-based into atlas, 0 = no icon) |
l |
string |
Text label displayed near the node ("" = no label) |
All fields are required.
| Field | Type | Description |
|---|---|---|
id |
string |
Unique identifier |
src |
string |
Source node id |
tgt |
string |
Target node id |
r |
number |
Red channel (0-1) |
g |
number |
Green channel (0-1) |
b |
number |
Blue channel (0-1) |
a |
number |
Alpha (0-1) |
s |
number |
Width in world units |
z |
number |
Z-index for draw order (lower draws first) |
Creates a WebGL2 renderer attached to the given canvas.
| Method | Signature | Description |
|---|---|---|
setNodes |
(nodes: Node[]) => void |
Update node data. Triggers animation if animationDuration > 0. |
setEdges |
(edges: Edge[]) => void |
Update edge data from an array. |
setEdges |
(buffer: ArrayBufferView, count: number) => void |
Update edges from a raw binary buffer. |
setCurvature |
(amount: number) => void |
Set edge curvature (0 = straight). |
fitToNodes |
(duration?: number) => void |
Animate camera to frame all nodes. Default 300 ms, pass 0 for instant. |
render |
() => void |
Start the render loop. |
destroy |
() => void |
Clean up WebGL resources and event listeners. |
getCameraState |
() => CameraState |
Returns the current camera state. Object is reused across calls. |
getNodes |
() => readonly Node[] |
Returns nodes with interpolated positions during animation. |
setIcons |
(svgStrings: string[], cellSize?: number) => Promise<void> |
Build icon atlas from SVG strings and upload to GPU. Default cell size is 64 px. |
setIconAtlas |
(source: TexImageSource, columns: number, rows: number) => void |
Upload a pre-built icon atlas texture. |
resize |
() => void |
Recalculate canvas dimensions (called automatically on ResizeObserver changes). |
setData |
(nodes: Node[], edges?: Edge[]) => void |
Update nodes and edges in one batch. Avoids redundant edge reprocessing. |
setNodeColors |
(colors: Uint8Array) => void |
Update only node colors. 4 bytes (RGBA 0-255) per node in input order. |
setEdgeColors |
(colors: Uint8Array) => void |
Update only edge colors. 4 bytes (RGBA 0-255) per edge in input order. |
| Option | Type | Default | Description |
|---|---|---|---|
canvas |
HTMLCanvasElement |
required | Target canvas element |
nodes |
Node[] |
required | Initial node data |
edges |
Edge[] |
undefined |
Initial edge data |
animationDuration |
number |
300 |
Transition duration in ms |
animationEasing |
(t: number) => number |
easeOutCubic | Easing function |
minScreenRadius |
number |
2 |
Minimum node radius in CSS pixels |
maxScreenRadius |
number |
40 |
Maximum node radius in CSS pixels |
onNodeClick |
(e: NodeEvent) => void |
||
onNodeDblClick |
(e: NodeEvent) => void |
||
onNodeHoverEnter |
(e: NodeEvent) => void |
||
onNodeHoverLeave |
(e: NodeEvent) => void |
||
onEdgeClick |
(e: EdgeEvent) => void |
||
onEdgeDblClick |
(e: EdgeEvent) => void |
||
onEdgeHoverEnter |
(e: EdgeEvent) => void |
||
onEdgeHoverLeave |
(e: EdgeEvent) => void |
||
onBackgroundClick |
(e: BackgroundEvent) => void |
||
onBackgroundDblClick |
(e: BackgroundEvent) => void |
||
onRender |
() => void |
Called after each rendered frame |
| Field | Type | Description |
|---|---|---|
type |
string |
"click", "dblclick", "hoverEnter", "hoverLeave" |
nodeId |
string |
Node id |
node |
Node |
Node object |
worldX |
number |
World-space x coordinate |
worldY |
number |
World-space y coordinate |
originalEvent |
MouseEvent | TouchEvent |
Original DOM event |
| Field | Type | Description |
|---|---|---|
type |
string |
Event type |
edgeId |
string |
Edge id |
edge |
Edge |
Edge object |
worldX |
number |
World-space x coordinate |
worldY |
number |
World-space y coordinate |
originalEvent |
MouseEvent | TouchEvent |
Original DOM event |
| Field | Type | Description |
|---|---|---|
type |
string |
Event type |
worldX |
number |
World-space x coordinate |
worldY |
number |
World-space y coordinate |
originalEvent |
MouseEvent | TouchEvent |
Original DOM event |
Props for the <Graph> React component.
| Prop | Type | Default | Description |
|---|---|---|---|
nodes |
Node[] |
required | Node data |
edges |
Edge[] |
undefined |
Edge data |
nodeColors |
Uint8Array |
Fast-path RGBA color override (4 bytes per node, 0-255) | |
edgeColors |
Uint8Array |
Fast-path RGBA color override (4 bytes per edge, 0-255) | |
animationDuration |
number |
300 |
Transition duration in ms |
animationEasing |
(t: number) => number |
easeOutCubic | Easing function |
curvature |
number |
0.4 |
Edge curvature (0 = straight) |
minScreenRadius |
number |
2 |
Min node radius in CSS px |
maxScreenRadius |
number |
40 |
Max node radius in CSS px |
icons |
string[] |
SVG strings for icon atlas | |
iconCellSize |
number |
64 |
Icon atlas cell size in px |
fitKey |
string | number |
Triggers fitToNodes when changed |
|
labelClass |
string |
CSS class for label elements | |
minLabelRadius |
number |
8 |
Only show labels when screen radius >= this |
maxLabels |
number |
200 |
Maximum visible labels |
onNodeClick |
(e: NodeEvent) => void |
||
onNodeDblClick |
(e: NodeEvent) => void |
||
onNodeHoverEnter |
(e: NodeEvent) => void |
||
onNodeHoverLeave |
(e: NodeEvent) => void |
||
onEdgeClick |
(e: EdgeEvent) => void |
||
onEdgeDblClick |
(e: EdgeEvent) => void |
||
onEdgeHoverEnter |
(e: EdgeEvent) => void |
||
onEdgeHoverLeave |
(e: EdgeEvent) => void |
||
onBackgroundClick |
(e: BackgroundEvent) => void |
||
onBackgroundDblClick |
(e: BackgroundEvent) => void |
||
onRender |
() => void |
Called after each rendered frame | |
children |
ReactNode |
Overlay content (rendered with pointer-events: none) |
Imperative handle exposed via ref on <Graph>.
| Method | Signature | Description |
|---|---|---|
fitToNodes |
(duration?: number) => void |
Animate camera to frame all nodes |
setCurvature |
(amount: number) => void |
Set edge curvature |
getRenderer |
() => Renderer | null |
Access the underlying renderer |
getCameraState |
() => CameraState | undefined |
Get current camera state |
setNodeColors |
(colors: Uint8Array) => void |
Update only node colors |
setEdgeColors |
(colors: Uint8Array) => void |
Update only edge colors |
setData |
(nodes: Node[], edges?: Edge[]) => void |
Batch update nodes and edges |
Standalone DOM label overlay for use without React.
import { LabelOverlay } from "@godu/trap";
const labels = new LabelOverlay({
container: document.getElementById("graph-container"),
minScreenRadius: 8,
maxLabels: 200,
labelClass: "my-label",
});
// Call after each render
renderer.onRender = () => {
labels.update(renderer.getNodes(), renderer.getCameraState());
};
// Cleanup
labels.destroy();| Option | Type | Default | Description |
|---|---|---|---|
container |
HTMLElement |
required | Parent element (typically the canvas's parent) |
minScreenRadius |
number |
8 |
Only show labels when node screen radius >= this |
maxLabels |
number |
200 |
Maximum visible labels |
labelClass |
string |
"" |
CSS class added to each label element |
| Field | Type | Description |
|---|---|---|
centerX |
number |
World-space center x |
centerY |
number |
World-space center y |
halfW |
number |
Half-width of view in world units |
halfH |
number |
Half-height of view in world units |
clientWidth |
number |
Canvas CSS width in pixels |
clientHeight |
number |
Canvas CSS height in pixels |
minScreenRadius |
number |
Min node screen radius in CSS px |
maxScreenRadius |
number |
Max node screen radius in CSS px |
| Field | Type | Description |
|---|---|---|
centerX |
number |
World-space center x |
centerY |
number |
World-space center y |
halfW |
number |
Half-width in world units |
halfH |
number |
Half-height in world units |
| Field | Type | Description |
|---|---|---|
minX |
number |
Minimum x (including node radius) |
maxX |
number |
Maximum x (including node radius) |
minY |
number |
Minimum y (including node radius) |
maxY |
number |
Maximum y (including node radius) |
computeBounds(nodes: Node[]): BoundsCompute the axis-aligned bounding box of all nodes (including radius).
computeFitView(bounds: Bounds, canvasWidth: number, canvasHeight: number): CameraViewCompute a camera view that fits the bounds with 10% padding, maintaining aspect ratio.
createProjectionFromView(view: CameraView): Float32ArrayCreate a 4x4 orthographic projection matrix (column-major) from a camera view.
createProjectionMatrix(bounds: Bounds, canvasWidth: number, canvasHeight: number): Float32ArrayShorthand for createProjectionFromView(computeFitView(bounds, w, h)).
MIT