Hypergraph Visualization
A hypergraph generalizes a graph: a hyperedge connects any number of
vertices, not just two. Nodus visualizes hypergraphs through the
nodus.hypergraph module, which implements a scalable rendering pipeline — set
data, simplify, lay out, and render hyperedges as regions around their vertices.
This guide walks the practical pipeline end to end. For the concepts behind it,
see Hypergraphs.
The pipeline is chainable:
setData(data) → simplify(opts?) → layout(opts?) → render(opts?)Try it live — the full set→simplify→layout→render pipeline running end to end:
1. Provide the data
Section titled “1. Provide the data”A hypergraph is a set of vertices and a set of hyperedges, where each
hyperedge lists the vertex ids it contains:
nodus.hypergraph.setData({ vertices: [ { id: 'alice' }, { id: 'bob' }, { id: 'carol' }, { id: 'dave' }, ], hyperedges: [ { id: 'paper-1', vertices: ['alice', 'bob', 'carol'] }, { id: 'paper-2', vertices: ['carol', 'dave'] }, ],});A vertex may optionally carry a nodeId (to tie it to a node in your main graph)
and arbitrary data; a hyperedge may carry data too.
From an existing graph
Section titled “From an existing graph”If you already have a graph loaded, build a hypergraph from it with
fromNodesAndEdges. Provide a groupBy function that returns, for each node, the
list of hyperedge ids it belongs to:
nodus.hypergraph.fromNodesAndEdges({ // Group nodes into hyperedges by some attribute of your data. groupBy: (node) => node.getData()?.communities ?? [],});Both setData and fromNodesAndEdges return the module, so you can chain.
2. Simplify
Section titled “2. Simplify”simplify(opts?) reduces visual clutter by merging and scaling the structure. It
returns { scales, operations }.
const { scales, operations } = nodus.hypergraph.simplify({ alpha: 1, beta: 1, gamma: 1, stopWhen: 'noForbidden',});simplify options
Section titled “simplify options”| Option | Meaning |
|---|---|
alpha, beta, gamma |
Weights that balance the simplification objective. |
adjacencyT |
Adjacency threshold used while simplifying. |
maxScales |
Cap on how many scales to produce. |
stopWhen |
Stopping rule: 'linear', 'noForbidden', 'targetVertices', 'targetHyperedges', or 'manual'. |
targetVertexCount |
Target vertex count when stopWhen: 'targetVertices'. |
targetHyperedgeCount |
Target hyperedge count when stopWhen: 'targetHyperedges'. |
forbiddenSubgraphs |
Subgraph patterns to avoid. |
betweennessRefreshEvery |
How often to refresh betweenness during simplification. |
// Simplify down to a target number of hyperedges.nodus.hypergraph.simplify({ stopWhen: 'targetHyperedges', targetHyperedgeCount: 12,});3. Lay out
Section titled “3. Lay out”layout(opts?) positions the vertices and hyperedge regions. It’s a Promise and
resolves with { totalEnergy, overlapCount } so you can judge quality.
const result = await nodus.hypergraph.layout({ weights: { regularity: 1, area: 1, separation: 1, intersection: 1, coordination: 1, }, separationIters: 200, regularityIters: 200, solver: 'lbfgs', onProgress: (info) => console.log('layout progress', info),});
console.log('energy', result.totalEnergy, 'overlaps', result.overlapCount);layout options
Section titled “layout options”| Option | Meaning |
|---|---|
weights |
Per-term weights: regularity, area, separation, intersection, coordination. |
separationIters |
Iterations spent pushing regions apart. |
regularityIters |
Iterations spent regularizing region shapes. |
solver |
'lbfgs' or 'adam'. |
optimizeDual |
Also optimize the dual layout. |
targetAreaPerCardinality(k) |
Target area for a hyperedge of cardinality k. |
bufferDistance |
Padding kept around regions. |
useWorker |
Run in a Web Worker (default true). |
progressEvery / onProgress(info) |
Progress reporting cadence and callback. |
4. Render
Section titled “4. Render”render(opts?) draws the result. It returns { primal?, dual? }.
nodus.hypergraph.render({ view: 'primal', palette: ['#0ea5e9', '#f97316', '#22c55e', '#a855f7'], showVertices: true, position: 'below', fillOpacity: 0.25, strokeOpacity: 0.8,});render options
Section titled “render options”| Option | Meaning |
|---|---|
view |
'primal', 'dual', 'both', or '2.5d'. |
palette |
Colors used for hyperedge regions. |
showVertices |
Draw the vertices themselves. |
showIncidenceEdges |
Draw vertex–hyperedge incidence edges. |
position |
Draw 'below' or 'above' the graph. |
layer |
Render on the 'canvas' or 'svg' layer. |
fillOpacity / strokeOpacity |
Region fill and stroke opacity. |
vertexRadius |
Radius of drawn vertices. |
interactive |
Enable hover/selection interaction. |
hoverFillOpacityScale / hoverStrokeWidth |
Hover emphasis. |
selectionStrokeColor / selectionStrokeWidth / selectionDash |
Selected-region styling. |
levelOf / levelSpacing / obliqueTilt |
Layout of the '2.5d' view. |
vertexTag |
Tagging used when drawing vertices. |
The '2.5d' view stacks hyperedges on tilted levels for a layered look:
nodus.hypergraph.render({ view: '2.5d', levelSpacing: 40, obliqueTilt: 0.5,});Try it live — switch between the primal and dual views of the same hypergraph:
Querying the result
Section titled “Querying the result”Read back positions and quality metrics:
const positions = nodus.hypergraph.getVertexPositions(); // Map<id, {x, y}>const xy = positions.get('alice');
const metrics = nodus.hypergraph.getQualityMetrics();// { overlapCount, overlapArea, avgRegularity, forbiddenSubgraphCount }console.log('regularity', metrics.avgRegularity);You can also get a hyperedge’s outline polygon to draw or hit-test yourself:
const polygon = nodus.hypergraph.getHyperedgePolygon('paper-1'); // {x, y}[]Selection and events
Section titled “Selection and events”When you render with interactive: true, the module tracks hovered and selected
hyperedges and vertices. Toggle selection programmatically, and read it back:
nodus.hypergraph.setSelected({ hyperedges: ['paper-1'], replace: true });nodus.hypergraph.toggleSelected({ vertex: 'carol' });
const selected = nodus.hypergraph.getSelectedHyperedges();nodus.hypergraph.clearSelection();Subscribe to interaction with on(event, cb):
nodus.hypergraph.on('hyperedgeClick', (e) => { console.log('clicked hyperedge', e);});
nodus.hypergraph.on('vertexHover', (v) => highlight(v));nodus.hypergraph.on('selectionChanged', () => updatePanel());The available events are simplifyStart, simplifyEnd, layoutStart,
layoutPhase, layoutIter, layoutEnd, render, viewChanged,
hyperedgeHover, hyperedgeOut, hyperedgeClick, vertexHover, vertexOut,
vertexClick, and selectionChanged. Remove a handler with off(cb).
Switching views and lifecycle
Section titled “Switching views and lifecycle”nodus.hypergraph.setActiveView('dual'); // 'primal' | 'dual' | 'both'nodus.hypergraph.refresh();nodus.hypergraph.hide();nodus.hypergraph.show();nodus.hypergraph.destroy();Putting it together
Section titled “Putting it together”import { Nodus } from '@kortexya/nodus';
const nodus = new Nodus({ container: document.getElementById('graph') });
await nodus.hypergraph .setData({ vertices: [{ id: 'a' }, { id: 'b' }, { id: 'c' }, { id: 'd' }], hyperedges: [ { id: 'h1', vertices: ['a', 'b', 'c'] }, { id: 'h2', vertices: ['c', 'd'] }, ], }) .simplify({ stopWhen: 'noForbidden' }) .layout({ solver: 'lbfgs', separationIters: 200, regularityIters: 200 });
nodus.hypergraph.render({ view: 'both', showVertices: true, palette: ['#0ea5e9', '#f97316'], interactive: true,});
nodus.hypergraph.on('hyperedgeClick', (e) => console.log('clicked', e));- Hypergraphs — the model and the ideas behind the pipeline.
- API: Hypergraph — the full module reference.
- Applying Layouts — ordinary graph layouts for the non-hypergraph case.