Skip to content

Hypergraph

nodus.hypergraph visualizes hypergraphs, where a single hyperedge may connect any number of vertices. It implements the scalable hypergraph visualization of Oliver et al. (TVCG 2023, arXiv:2308.05043). The pipeline is chainable:

nodus.hypergraph
.setData(data)
.simplify(/* opts */)
.layout(/* opts */)
.then(() => nodus.hypergraph.render(/* opts */));

For the concept and a walkthrough see Hypergraphs and the Hypergraph Visualization guide.

Data types

type HypergraphData = {
vertices: { id: string | number; nodeId?: string | number; data?: any }[];
hyperedges: { id: string | number; vertices: (string | number)[]; data?: any }[];
};

A Vertex carries an id, an optional nodeId linking it to a graph node, and optional data. A Hyperedge carries an id, the list of vertex ids it connects, and optional data.

Loading data

  • setData(data) -> this — set the HypergraphData directly.
  • fromNodesAndEdges({ groupBy: (node) => id[] }) -> this — build a hypergraph from the current graph, where groupBy returns the hyperedge ids each node belongs to.
nodus.hypergraph.setData({
vertices: [{ id: 'v1' }, { id: 'v2' }, { id: 'v3' }],
hyperedges: [
{ id: 'e1', vertices: ['v1', 'v2'] },
{ id: 'e2', vertices: ['v2', 'v3'] },
],
});

simplify

simplify(opts?) -> { scales, operations }

Reduces the hypergraph through a sequence of operations, producing a multi-scale representation. Options:

OptionType
alpha?number
beta?number
gamma?number
adjacencyT?number
maxScales?number
stopWhen?'linear' | 'noForbidden' | 'targetVertices' | 'targetHyperedges' | 'manual'
targetVertexCount?number
targetHyperedgeCount?number
forbiddenSubgraphs?(the forbidden-subgraph set)
betweennessRefreshEvery?number

Returns { scales, operations }.

const { scales, operations } = nodus.hypergraph.simplify({
stopWhen: 'targetVertices',
targetVertexCount: 50,
});

layout

layout(opts?) -> Promise<{ totalEnergy, overlapCount }>

Computes positions by minimizing an energy. Options:

OptionType
weights?{ regularity, area, separation, intersection, coordination }
separationIters?number
regularityIters?number
solver?'lbfgs' | 'adam'
optimizeDual?boolean
targetAreaPerCardinality?(k)function of cardinality k
bufferDistance?number
useWorker?boolean (default true)
progressEvery?number
onProgress?(info)progress callback

Resolves to { totalEnergy, overlapCount }.

const result = await nodus.hypergraph.layout({
solver: 'lbfgs',
weights: { regularity: 1, area: 1, separation: 1, intersection: 1, coordination: 1 },
onProgress: (info) => console.log(info),
});
console.log(result.totalEnergy, result.overlapCount);

render

render(opts?) -> { primal?, dual? }

Draws the hypergraph. Options:

OptionType
fillOpacity?number
strokeOpacity?number
palette?(the color palette)
showVertices?boolean
showIncidenceEdges?boolean
position?'below' | 'above'
layer?'canvas' | 'svg'
view?'primal' | 'dual' | 'both' | '2.5d'
vertexRadius?number
interactive?boolean
hoverFillOpacityScale?number
hoverStrokeWidth?number
selectionStrokeColor?color
selectionStrokeWidth?number
selectionDash?(dash spec)
levelOf?function
levelSpacing?number
obliqueTilt?number
vertexTag?(vertex tag spec)

Returns { primal?, dual? }.

nodus.hypergraph.render({
view: 'both',
fillOpacity: 0.25,
interactive: true,
});

Queries

  • getData() — the current HypergraphData.
  • getDual() — the dual hypergraph.
  • getScales() — the simplification scales.
  • getOperationStack() — the operation stack from simplify.
  • getVertexPositions() -> Map<id, {x, y}> — primal vertex positions.
  • getDualPositions() — dual positions.
  • getHyperedgePolygon(id) -> {x, y}[] — a hyperedge’s polygon outline.
  • getQualityMetrics() -> { overlapCount, overlapArea, avgRegularity, forbiddenSubgraphCount }.
const positions = nodus.hypergraph.getVertexPositions();
const polygon = nodus.hypergraph.getHyperedgePolygon('e1');
const metrics = nodus.hypergraph.getQualityMetrics();

Selection and hover

  • getSelectedHyperedges(), getSelectedVertices()
  • setSelected({ hyperedges?, vertices?, replace? })
  • toggleSelected({ hyperedge?, vertex? })
  • clearSelection()
  • getHoveredHyperedge(), getHoveredVertex(), getHoveredDual()
  • getHyperedgeAtPoint(world) — hit-test a world-space point.
nodus.hypergraph.setSelected({ hyperedges: ['e1'], replace: true });
nodus.hypergraph.toggleSelected({ vertex: 'v2' });

View and lifecycle

  • setActiveView('primal' | 'dual' | 'both')
  • refresh() — redraw.
  • hide(), show() — toggle visibility.
  • destroy() — tear down the hypergraph view.
nodus.hypergraph.setActiveView('dual');
nodus.hypergraph.refresh();

Events

Subscribe with on(event, cb) and unsubscribe with off(cb). Available events:

simplifyStart, simplifyEnd, layoutStart, layoutPhase, layoutIter, layoutEnd, render, viewChanged, hyperedgeHover, hyperedgeOut, hyperedgeClick, vertexHover, vertexOut, vertexClick, selectionChanged.

const onClick = (e) => console.log('hyperedge clicked', e);
nodus.hypergraph.on('hyperedgeClick', onClick);
nodus.hypergraph.on('layoutEnd', () => console.log('layout done'));
// later:
nodus.hypergraph.off(onClick);