Core Concepts
Strongly-typed graph data model in pure TypeScript with zero dependencies.
Graph Model
import { Graph } from '@topokit/core';
const graph = new Graph({ directed: true });
graph.addNode({ id: 'a', data: { name: 'Alice' } });
graph.addNode({ id: 'b', data: { name: 'Bob' } });
graph.addEdge({ id: 'e1', source: 'a', target: 'b', data: {} }); Set directed: false for undirected (symmetric) edges.
Nodes & Edges
Node Structure
| Property | Type | Description |
|---|---|---|
id | string | Unique identifier |
data | Record<string, any> | Arbitrary user data |
position | { x, y } | Position (set by layout or manually) |
style | NodeStyle | Visual properties |
NodeStyle
| Property | Type | Default | Description |
|---|---|---|---|
shape | 'circle' | 'rect' | 'diamond' | 'hexagon' | 'triangle' | 'circle' | Node shape |
fill | string | Theme default | Fill color |
stroke | string | Theme default | Border color |
radius | number | 20 | Node radius |
label | string | undefined | Display label |
badges | NodeBadge[] | [] | Badge indicators |
opacity | number | 1 | Opacity (0-1) |
fontSize | number | 12 | Label font size |
fontColor | string | Theme default | Label color |
icon | string | undefined | Emoji or character in node center |
graph.addNode({
id: 'server-1',
data: { type: 'server', status: 'healthy', cpu: 42 },
position: { x: 100, y: 200 },
style: {
shape: 'rect',
fill: '#1e40af',
stroke: '#3b82f6',
radius: 30,
label: 'Web Server',
fontSize: 14,
badges: [
{ text: 'OK', color: '#fff', background: '#10b981', position: 'top-right' }
],
},
}); Edge Structure
| Property | Type | Description |
|---|---|---|
id | string | Unique edge identifier |
source | string | Source node ID |
target | string | Target node ID |
data | Record<string, any> | Arbitrary user data |
style | EdgeStyle | Visual properties |
EdgeStyle
| Property | Type | Default | Description |
|---|---|---|---|
type | 'line' | 'curve' | 'step' | 'line' | Edge path type |
stroke | string | Theme default | Edge color |
width | number | 1.5 | Stroke width |
arrow | boolean | true | Show arrowhead |
dashed | boolean | false | Dashed line |
animated | boolean | false | Animate dashes |
label | string | undefined | Midpoint label |
opacity | number | 1 | Opacity (0-1) |
graph.addEdge({
id: 'data-flow',
source: 'server-1',
target: 'database',
data: { bandwidth: 1200 },
style: {
type: 'curve',
stroke: '#10b981',
width: 2,
animated: true,
label: '1.2 Gbps',
},
}); Events
Every structural change fires an event, keeping renderer, layout, and your code in sync.
| Event | Payload | Fired When |
|---|---|---|
node:add | { node } | Node added |
node:remove | { node } | Node removed |
node:update | { node, changes } | Node data/style updated |
edge:add | { edge } | Edge added |
edge:remove | { edge } | Edge removed |
edge:update | { edge, changes } | Edge data/style updated |
node:click | { node, event } | Node clicked |
node:dblclick | { node, event } | Node double-clicked |
node:hover | { node, event } | Mouse enters node |
node:leave | { node, event } | Mouse leaves node |
node:dragstart | { node, position } | Drag started |
node:drag | { node, position } | Node dragging |
node:dragend | { node, position } | Drag ended |
edge:click | { edge, event } | Edge clicked |
canvas:click | { event } | Empty canvas clicked |
selection:change | { nodes, edges } | Selection changed |
layout:start | { algorithm } | Layout begins |
layout:end | { algorithm, duration } | Layout finishes |
// Listen for node clicks
graph.on('node:click', ({ node, event }) => {
console.log('Clicked:', node.id, node.data);
});
// Unsubscribe
const unsub = graph.on('node:update', handler);
unsub(); Batch Operations
Wrap many changes in a batch to fire a single batch:end event instead of individual events.
graph.batch(() => {
for (let i = 0; i < 1000; i++) {
graph.addNode({ id: `node-${i}`, data: { index: i } });
}
for (let i = 0; i < 999; i++) {
graph.addEdge({
id: `edge-${i}`,
source: `node-${i}`,
target: `node-${i + 1}`,
data: {},
});
}
}); Combo Edge Synthesis
When a combo (parent) node is collapsed, every real edge crossing its
boundary is replaced with a synthetic aggregate edge whose id has the
form __combo_edge_<src>_<tgt>. The aggregate
represents the union of the underlying real edges between the two
visible endpoints (where each endpoint may itself be a collapsed
combo).
The cache key is order-insensitive, so the synthesized edge id stays
stable across renders even when source/target ordering flips. Edge
bundlers and other consumers can detect synthetic edges by checking
for the __combo_edge_ prefix — the built-in edge
bundler filters them automatically.
Synthesis can be disabled with comboEdgeAggregation: false
on the create config. Read the current synthetic set via
renderer.getSynthesizedComboEdges().
Orphans vs Combos
An orphan is a non-combo node with zero incident
visible edges in the currently-rendered graph — after
combo collapse and any active edge-type filter. The
hideOrphans option (default false) hides
these on each batch:end.
Combo containers are never orphans, even when their own visible incident edge count is zero. Combos exist to group, not to relate — treating an empty combo as an orphan would surprise-hide user-authored grouping. If you want a combo to disappear when empty, remove it from the graph rather than relying on the orphan rule.
Themes
Seven built-in themes control default colors for nodes, edges, canvas, selection, and text.
| Theme | Background | Best For |
|---|---|---|
light | White | Documentation, print |
dark | Dark slate | Dev tools, dashboards |
corporate | Light gray | Business presentations |
neon | Black | Monitoring tools |
pastel | Soft white | Educational apps |
monochrome | White | Minimal, academic |
ocean | Deep blue | Data dashboards |
// Built-in theme
const app = create(container, { theme: 'neon', nodes: [...], edges: [...] });
// Switch at runtime
app.setTheme('ocean'); Custom Themes
const app = create(container, {
theme: {
name: 'my-theme',
canvas: { background: '#0a0a0f' },
node: {
fill: '#6366f1',
stroke: '#818cf8',
labelColor: '#e0e7ff',
selectedStroke: '#f59e0b',
hoverStroke: '#a5b4fc',
},
edge: {
stroke: '#334155',
labelColor: '#94a3b8',
selectedStroke: '#f59e0b',
},
},
nodes: [...],
edges: [...],
}); Next Steps
- Layout Algorithms -- position nodes automatically
- Filtering & Highlighting -- focus on what matters
- Data Mapping -- import JSON data
- API Reference -- full method listing