A WebGPU-based 2D graph rendering framework with a React/TypeScript demo UI.
The project renders large node-edge graphs in the browser and supports visual customization, interaction, filtering, layouts, clustering, and comparison with D3/Sigma.js.
npm install
npm run devThe renderer is exposed through the React wrapper:
import WebGPURenderer from "./components/renderer";
<WebGPURenderer
graph={graph}
options={options}
onSelectionChange={setSelectedNodes}
onEdgeSelectionChange={setSelectedEdges}
onStats={setStats}
/>The graph data is stored in Graphology. Nodes and edges can define attributes such as:
graph.addNode("apple", {
x: 0,
y: 0,
label: "Apple",
color: [1, 0.2, 0.2],
size: 1.4,
borderWidth: 0.15,
});
graph.addEdge("apple", "banana", {
label: "related",
color: [1, 1, 1],
thickness: 0.08,
style: "solid",
});Main renderer customization is controlled by GraphRendererOptions.
{
showEdges: true,
showEdgeArrows: true,
edgeStyle: "solid", // solid | dashed | dotted | curved
nodeScale: 1,
nodeOpacity: 1,
edgeOpacity: 1,
defaultNodeSize: 1,
minNodeSize: 0.4,
maxNodeSize: 3,
defaultBorderWidth: 0.08,
defaultBorderColor: [1, 1, 1],
defaultEdgeColor: [1, 1, 1],
defaultEdgeThickness: 0.04,
antialias: true,
alphaCoverage: true,
}{
hoverEffects: true,
clickSelects: true,
edgeClickSelects: true,
multiSelect: true,
highlightConnectionsOnSelect: true,
includeSecondNeighbors: false,
dragToPan: true,
dragToMoveNodes: true,
zoomSpeed: 1.1,
minZoom: 0.05,
maxZoom: 40,
hitTestRadius: 12,
stateScales: {
hovered: 1.25,
selected: 1.4,
dimmed: 0.85,
},
stateOpacity: {
default: 1,
selected: 1,
dimmed: 0.18,
hidden: 0,
},
}edgeClickSelects enables click selection for edges (including multi-select with Ctrl/Cmd).
{
showLabels: true,
showNodeLabels: true,
showEdgeLabels: false,
}Labels are drawn as a canvas overlay and are limited by zoom and graph size to avoid clutter.
{
maxNodes: 100000,
maxEdges: 100000,
dynamicNodeSizing: true,
dynamicNodeBasePx: 14,
viewportCulling: true,
edgeLODThreshold: 50000,
edgeLODZoomFactor: 0.8,
depthTest: false,
batchSize: 50000,
powerPreference: "high-performance",
}Dynamic node sizing keeps nodes readable while zooming. Edge limits and edge LOD are important for very dense graphs because edges are usually the main rendering bottleneck.
{
autoGroupingOnZoomOut: false,
clusterEnabled: true,
clusterZoomThreshold: 1.25,
clusterCellPx: 42,
clusterMinNodes: 6,
communityEnabled: true,
communityZoomThreshold: 0.7,
communityMinNodes: 20,
communityShowInterEdges: true,
}Spatial clustering groups nearby nodes. Community clustering groups connected nodes using graph structure.
{
layoutMode: "force", // none | grid | circular | community | force
layoutAutoTune: true,
layoutPadding: 1,
layoutGridJitter: 0.08,
layoutIterations: 250,
layoutRepulsion: 0.03,
layoutLinkDistance: 1.2,
layoutLinkStrength: 0.02,
layoutDamping: 0.86,
layoutGravity: 0.002,
}Layouts are currently CPU-computed. The force layout runs in a Web Worker so the UI stays responsive.
The UI wraps the renderer and makes the framework easier to test.
It provides:
- Dataset selection: fruit graph, Reddit graph, generated graphs.
- Layout controls: none, grid, circular, community, force.
- Appearance controls: node size, node opacity, edge opacity, borders, labels, arrows, edge style, antialiasing.
- Interaction controls: hover, selection, multi-select, neighbor highlighting, second-neighbor toggle, drag node, zoom speed, hit radius.
- Edge selection controls: edge click selection toggle and selected edge count.
- Performance controls: node/edge limits, edge LOD, dynamic sizing, clustering, culling, GPU preference.
- Fit-to-view and graph stats.
- Comparison mode for D3 SVG and Sigma.js WebGL.
The renderer handles around 100k nodes and 100k edges interactively on the tested machine. Larger graphs are possible, but dense edge rendering and CPU layout become the main bottlenecks.
For very large graphs, the most useful options are:
- hide or limit edges,
- enable edge LOD,
- use clustering,
- use a good layout before rendering,
- keep dynamic node sizing enabled.