svg · state

Graph Visualizer

A tiny but complete example of rendering data structures interactively. A graph is stored as an adjacency list (each node maps to its neighbours), drawn with SVG, and made interactive with a single piece of state: which node is selected. Click a node below to light up its neighbours.

Live demo
1234
Click any node to select it.
selectedneighbourother
Click any circle to select it; its direct neighbours turn blue and the connecting edges highlight. Click reset to clear.
How it works

Adjacency list

The whole graph is just an object mapping each node id to an array of the nodes it connects to. Looking up a node’s neighbours is an O(1) object access — no scanning required. A separate positions map says where to draw each node, keeping structure separate from layout.

Derived state, not stored state

We store only one thing: selected. The set of highlighted neighbours and every node’s colour are derived from it during render. There is no second “highlighted” array to keep in sync — a key habit for bug-free UI: store the minimum, compute the rest.

Rendering with SVG

Edges are <line> elements and nodes are <circle> elements, mapped straight from the data. Each undirected edge is drawn once (by only drawing when a < b), and an onClick on each circle updates the selection, which re-renders the colours.

TSX
1
const graph = {            // adjacency list: node -> neighbours
2
  1: [2, 4], 2: [1, 3],
3
  3: [2, 4], 4: [1, 3],
4
};
5
 
6
const positions = {        // where to draw each node
7
  1: { x: 90,  y: 80  }, 2: { x: 290, y: 80  },
8
  3: { x: 290, y: 240 }, 4: { x: 90,  y: 240 },
9
};
10
 
11
function GraphVisualizer() {
12
  const [selected, setSelected] = useState(null);
13
  const neighbors = selected != null ? graph[selected] : [];
14
 
15
  return (
16
    <svg viewBox="0 0 380 320">
17
      {/* edges */}
18
      {Object.entries(graph).map(([node, nbrs]) =>
19
        nbrs.map((nbr) => Number(node) < nbr && (
20
          <line key={node + '-' + nbr}
21
                x1={positions[node].x} y1={positions[node].y}
22
                x2={positions[nbr].x}  y2={positions[nbr].y} />
23
        ))
24
      )}
25
      {/* nodes — fill depends on selection (derived, not stored) */}
26
      {Object.keys(graph).map((node) => {
27
        const id = Number(node);
28
        const fill = selected === id ? 'signal'
29
                   : neighbors.includes(id) ? 'trace'
30
                   : 'paper';
31
        return (
32
          <circle key={id} cx={positions[id].x} cy={positions[id].y} r={26}
33
                  fill={fill} onClick={() => setSelected(id)} />
34
        );
35
      })}
36
    </svg>
37
  );
38
}