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 theHypergraphDatadirectly.fromNodesAndEdges({ groupBy: (node) => id[] }) -> this— build a hypergraph from the current graph, wheregroupByreturns 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:
| Option | Type |
|---|---|
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:
| Option | Type |
|---|---|
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:
| Option | Type |
|---|---|
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 currentHypergraphData.getDual()— the dual hypergraph.getScales()— the simplification scales.getOperationStack()— the operation stack fromsimplify.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);