Skip to content

Core Concepts

Strongly-typed graph data model in pure TypeScript with zero dependencies.

Graph Model

TypeScript
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

PropertyTypeDescription
idstringUnique identifier
dataRecord<string, any>Arbitrary user data
position{ x, y }Position (set by layout or manually)
styleNodeStyleVisual properties

NodeStyle

PropertyTypeDefaultDescription
shape'circle' | 'rect' | 'diamond' | 'hexagon' | 'triangle''circle'Node shape
fillstringTheme defaultFill color
strokestringTheme defaultBorder color
radiusnumber20Node radius
labelstringundefinedDisplay label
badgesNodeBadge[][]Badge indicators
opacitynumber1Opacity (0-1)
fontSizenumber12Label font size
fontColorstringTheme defaultLabel color
iconstringundefinedEmoji or character in node center
TypeScript
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

PropertyTypeDescription
idstringUnique edge identifier
sourcestringSource node ID
targetstringTarget node ID
dataRecord<string, any>Arbitrary user data
styleEdgeStyleVisual properties

EdgeStyle

PropertyTypeDefaultDescription
type'line' | 'curve' | 'step''line'Edge path type
strokestringTheme defaultEdge color
widthnumber1.5Stroke width
arrowbooleantrueShow arrowhead
dashedbooleanfalseDashed line
animatedbooleanfalseAnimate dashes
labelstringundefinedMidpoint label
opacitynumber1Opacity (0-1)
TypeScript
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.

EventPayloadFired 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
TypeScript
// 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.

TypeScript
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.

ThemeBackgroundBest For
lightWhiteDocumentation, print
darkDark slateDev tools, dashboards
corporateLight grayBusiness presentations
neonBlackMonitoring tools
pastelSoft whiteEducational apps
monochromeWhiteMinimal, academic
oceanDeep blueData dashboards
TypeScript
// Built-in theme
const app = create(container, { theme: 'neon', nodes: [...], edges: [...] });

// Switch at runtime
app.setTheme('ocean');

Custom Themes

TypeScript
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