Skip to content

A Co-author Hypergraph

A hypergraph lets a single relationship connect any number of entities at once. Co-authorship is a natural fit: each author is a vertex, and each paper is a hyperedge incident to all of its authors. A paper with four co-authors is one hyperedge over four vertices — something a plain edge can’t express.

This tutorial runs the full hypergraph pipeline on a small co-authorship dataset: build the data, simplify it, lay it out, render it, inspect quality metrics, and react to clicks. The full combined script is at the end.

Nodus’s hypergraph subsystem implements the method of Oliver et al., “Scalable Hypergraph Visualization” (TVCG 2023).

1. Container

A standard container with a size; you only need this HTML and CSS once.

<div id="graph"></div>
<style>
html, body { margin: 0; height: 100%; }
#graph { width: 100vw; height: 100vh; }
</style>

2. Create the instance and build the data

Construct Nodus as usual, then assemble a HypergraphData object. It has two arrays: vertices (the authors) and hyperedges (the papers). Each hyperedge lists the ids of the vertices it’s incident to.

import { Nodus } from '@kortexya/nodus';
const nodus = new Nodus({ container: document.getElementById('graph') });
const data = {
vertices: [
{ id: 'alice', data: { name: 'Alice' } },
{ id: 'bob', data: { name: 'Bob' } },
{ id: 'carol', data: { name: 'Carol' } },
{ id: 'dave', data: { name: 'Dave' } },
{ id: 'erin', data: { name: 'Erin' } },
{ id: 'frank', data: { name: 'Frank' } },
],
hyperedges: [
{ id: 'p1', vertices: ['alice', 'bob', 'carol'], data: { title: 'On Hypergraphs' } },
{ id: 'p2', vertices: ['bob', 'dave'], data: { title: 'Layout Methods' } },
{ id: 'p3', vertices: ['carol', 'dave', 'erin'], data: { title: 'Simplification' } },
{ id: 'p4', vertices: ['erin', 'frank'], data: { title: 'Rendering Sets' } },
{ id: 'p5', vertices: ['alice', 'dave', 'frank'],data: { title: 'Quality Metrics' } },
],
};

Each vertex may optionally carry a nodeId to bind it to an existing graph node; here we keep the hypergraph standalone.

3. Set the data

Hand the data to the hypergraph subsystem. setData is chainable — it returns the hypergraph object — but we’ll keep the steps separate for clarity.

nodus.hypergraph.setData(data);

4. Simplify

Real co-authorship hypergraphs are dense and overlapping. simplify reduces them while preserving structure, controlled by the alpha, beta and gamma weights and a stopping condition. stopWhen decides when to halt — here we stop once no forbidden subgraphs remain.

nodus.hypergraph.simplify({
alpha: 1.0,
beta: 1.0,
gamma: 1.0,
stopWhen: 'noForbidden',
});

simplify returns a { scales, operations } summary describing the multi-scale reduction it performed.

5. Lay out

layout positions the vertices and shapes the hyperedge regions. It’s async and resolves to { totalEnergy, overlapCount } — a quick read on how well the layout converged. Tune the per-phase iteration counts and pick a solver ('lbfgs' or 'adam'); the onProgress callback reports intermediate state.

const result = await nodus.hypergraph.layout({
separationIters: 200,
regularityIters: 200,
solver: 'lbfgs',
onProgress: (info) => console.log('layout progress', info),
});
console.log('total energy:', result.totalEnergy);
console.log('overlaps:', result.overlapCount);

6. Render

render draws the result. view: 'primal' shows the author vertices wrapped by their paper regions. palette colors the hyperedge regions, and showVertices toggles the vertex markers.

nodus.hypergraph.render({
view: 'primal',
palette: ['#0ea5e9', '#f59e0b', '#10b981', '#a855f7', '#ef4444'],
showVertices: true,
});

render returns a { primal?, dual? } object with handles to the produced views. Switch views later with nodus.hypergraph.setActiveView('dual'), or render view: 'both' to see the primal and dual side by side.

7. Read quality metrics and handle clicks

getQualityMetrics reports how clean the layout is — overlap count and area, average regularity, and how many forbidden subgraphs remain. Use it to decide whether to re-run simplify or layout with different weights.

Wire interaction with nodus.hypergraph.on(event, cb). The hyperedgeClick event fires with the clicked hyperedge, so you can surface the paper’s title.

const metrics = nodus.hypergraph.getQualityMetrics();
console.log('overlap count:', metrics.overlapCount);
console.log('avg regularity:', metrics.avgRegularity);
console.log('forbidden subgraphs:', metrics.forbiddenSubgraphCount);
nodus.hypergraph.on('hyperedgeClick', (hyperedge) => {
console.log('clicked paper', hyperedge);
});

8. The full script

Steps 2–7 combined. Pair it with the container HTML and CSS from step 1.

import { Nodus } from '@kortexya/nodus';
// 2. Create and build data.
const nodus = new Nodus({ container: document.getElementById('graph') });
const data = {
vertices: [
{ id: 'alice', data: { name: 'Alice' } },
{ id: 'bob', data: { name: 'Bob' } },
{ id: 'carol', data: { name: 'Carol' } },
{ id: 'dave', data: { name: 'Dave' } },
{ id: 'erin', data: { name: 'Erin' } },
{ id: 'frank', data: { name: 'Frank' } },
],
hyperedges: [
{ id: 'p1', vertices: ['alice', 'bob', 'carol'], data: { title: 'On Hypergraphs' } },
{ id: 'p2', vertices: ['bob', 'dave'], data: { title: 'Layout Methods' } },
{ id: 'p3', vertices: ['carol', 'dave', 'erin'], data: { title: 'Simplification' } },
{ id: 'p4', vertices: ['erin', 'frank'], data: { title: 'Rendering Sets' } },
{ id: 'p5', vertices: ['alice', 'dave', 'frank'], data: { title: 'Quality Metrics' } },
],
};
// 3–4. Set data and simplify.
nodus.hypergraph.setData(data);
nodus.hypergraph.simplify({ alpha: 1.0, beta: 1.0, gamma: 1.0, stopWhen: 'noForbidden' });
// 5. Lay out.
const result = await nodus.hypergraph.layout({
separationIters: 200,
regularityIters: 200,
solver: 'lbfgs',
onProgress: (info) => console.log('layout progress', info),
});
console.log('energy', result.totalEnergy, 'overlaps', result.overlapCount);
// 6. Render.
nodus.hypergraph.render({
view: 'primal',
palette: ['#0ea5e9', '#f59e0b', '#10b981', '#a855f7', '#ef4444'],
showVertices: true,
});
// 7. Metrics and clicks.
const metrics = nodus.hypergraph.getQualityMetrics();
console.log('overlaps', metrics.overlapCount, 'regularity', metrics.avgRegularity);
nodus.hypergraph.on('hyperedgeClick', (hyperedge) => {
console.log('clicked paper', hyperedge);
});

The pipeline is setData → simplify → layout → render. Re-run simplify or layout with different weights and iteration counts, then call render again to compare.

Next steps