Skip to content

network

Large-scale network and map-of-networks rendering. The network engine draws node–link graphs as GPU-instanced glyphs — toward millions of nodes and links — with an in-library force layout (ForceLayout; main-thread, worker, or GPU backend), an adaptive level-of-detail cut over a coarsening or provided module hierarchy (Network.lod), and state (memory) networks (buildStateGraph). buildGraph assembles the graph from an edge list.

import { network, buildGraph } from "@mapequation/d3gl/network";
const graph = buildGraph({ nodeCount, source, target });
network(el, { width, height }).data(graph).layout({ backend: "worker" });
ClassDescription
ForceLayout-
NetworkThe network rendering engine (epic #98). A dedicated engine — nodes, links, layout, and LOD are one coupled system — built on the shared BaseEngine host/transform/zoom/interaction shell, rendering through the instanced lane (#100) rather than the retained Scene path.
InterfaceDescription
BuildGraphInput-
BuildStateGraphInput-
CoarseLevelOne coarsening level as a weighted, undirected edge list (parallel edges already collapsed).
CoarsenOptions-
CSRCompressed-sparse-row adjacency.
CutOptions-
CutScratchReusable scratch for cut (#213) — engine-owned so a zoom/pan frame allocates nothing steady-state: the frontier output, the DFS stack, and its parallel cross-fade alpha stack are grow-on-demand typed arrays reused across frames. The returned frontier is a subarray view of frontier, valid until the next cut with the same scratch — every per-frame consumer re-selects before reading, so one scratch per engine serves all paths.
DeclutterFrontierScratchReusable scratch for declutterFrontier (#213) — engine-owned so the per-frame thinning allocates nothing steady-state: the projected centres/radii, the typed sort keys + visit order, the kept flags, the shared declutter grid, and the output buffer are all grown on demand (to the largest frontier seen) and reused. The returned kept set is a subarray view of out, valid until the next call with the same scratch.
DeclutterOptions-
FlowBorderSpecFlow-border style (#104 N6): a ring around each node/module whose width encodes a per-node flow (e.g. Infomap enter/exit flow). The app supplies the flow; d3gl renders the ring. For module aggregates the metric is summed over members (see computeLODStyle).
FlowGraphMinimal directed-graph shape (a NetworkGraph satisfies it; raw arrays work too).
FlowOptions-
FlowResult-
ForceParamsForce-directed layout core (sub-issue #102, epic #98). Operates directly on a LayoutGraph’s positions buffer + edge list — pure typed-array math, no DOM, so it runs unchanged on the main thread or inside a Web Worker (the worker + SharedArrayBuffer transport land in a later slice). Repulsion is Barnes-Hut O(n log n) via BarnesHutTree.
HalfLinkGeometryThe resolved anchor + control points of a half-arrow link (the vertices its outline visits).
HalfLinkParamsInputs for one directed half-arrow link (world coordinates/units).
HierarchyA coarsening hierarchy: progressively smaller graphs plus the maps that connect them.
LayoutGraphMinimal graph view the force core needs: node count, a directed edge list (used as undirected springs), and the positions buffer it mutates. NetworkGraph satisfies this structurally, and so does a synthetic coarse level (see ./coarsen.js) — so one force core lays out the full graph and every coarsening level without casts.
LODTransformScreen-space transform: screen = world * k + (x, y) (matches BaseEngine ViewTransform).
LODTreeA retained coarsening tree, flattened to SoA typed arrays for cache-friendly traversal at scale.
ModuleColorOptions-
ModuleNodeA graph node’s placement in the provided module tree — the Infomap JSON node shape. Extra fields (flow, name, modules, …) are accepted and ignored here; flow feeds flow-border rendering in N6b.
ModulePathNodeA node’s placement in the module tree — path is the Infomap 1-based chain (last entry is the rank).
MultilevelLayoutOptions-
NetworkGraphNetwork graph: directed-edge SoA for rendering + CSR for traversal.
NetworkHitWhat a network Network.pick resolved — carried as the datum of the HoverHit passed to `on(“hover"
NetworkLayoutOptionsHow node positions are produced. Applies to plain graphs (Network.layout) and, since #182, state networks (Network.stateNetwork) — there every backend lays out the physical graph and the state/both views’ rosette is derived from it.
NetworkLinkHitWhat GPU-readback link picking (#141) resolved — carried as the datum of the HoverHit from `on(“hover"
NetworkLODOptionsLevel-of-detail (#103): an adaptive hierarchy cut so a large network draws only what’s visible. Each pan/zoom re-cuts a retained coarsening tree — dense regions collapse to aggregate glyphs and expand into their members as you zoom in — bounding per-frame work to the visible frontier. Opt-in via Network.lod; off by default (every node/link drawn). The tree’s geometry updates as the layout converges (so LOD helps during the solve, not only after), and the zoom-time path re-cuts only the visible frontier. Best paired with style({ sizeMode: "screen" }).
NetworkOptionsOptions for the network engine. Inherits sizing, backend, and tooltipClass.
NetworkStyleVisual style. Link appearance accessors arrive with the link pass (#100 N2.2).
ParsedEdgesEdge-list ingestion (sub-issue #99 / epic #98).
ParsedPajekEdge-list ingestion (sub-issue #99 / epic #98).
PathSinkA minimal 2-D path sink (matches the subset of CanvasRenderingContext2D / d3gl’s PathContext we use).
PhysicalPieWedgesPacked per-physical-node pie wedges. Physical p’s wedges are the slice [offset[p], offset[p + 1]) of the parallel end / color / moduleKey arrays, ordered by module (consistent across physical nodes). end is the cumulative fraction in [0, 1] (a wedge spans [end[i-1] .. end[i]], the first from 0; the last is exactly 1). wedgeCount[p] ≤ 1 ⇒ a single module ⇒ draw a solid disc, not a pie.
PhysicalToStatePhysical node → its state nodes, as CSR. Physical p’s state nodes are states[offsets[p] .. offsets[p + 1]]. The inverse of StateNetworkGraph.stateToPhysical.
PieWedgeOptions-
RosetteOptions-
StateNetworkGraphA state network plus its engine-derived physical network and the maps between the two levels.
StateNetworkOptionsOptions for Network.stateNetwork (#171). The engine ingests a state network + a per-state-node module assignment, derives the physical network’s overlapping-module pie wedges + module colours, and lets you toggle the state ↔ physical view with Network.view.
Type AliasDescription
ImportanceSpecHow a node’s declutter importance is determined: a NodeMetric ("degree"/"strength"/ "flow"/accessor), a per-node Float32Array, or "order" (a flat priority — so the survivor of a cluster falls back to input order, and an aggregate ranks by its subtree size). Resolved per-leaf and summed up the LOD tree (so a module’s importance is its members’ total — e.g. total flow), then used to break overlaps in the declutter: the highest-importance glyph in a cluster is kept.
LinkColorSpecLink colour. A single CSS colour; a (weight) => cssColour scale (a bare d3 sequential/linear colour scale fits — scaleSqrt().range([light, dark]) interpolates RGBA, alpha included); or { by, scale } for parity with LinkWidthSpec. Whichever form, it resolves to a function of a weight value, so a super-edge colours by its accumulated subsumed weight (darker/heavier reads as more important).
LinkStyleHow directed links are drawn: plain "line" + a separate arrowhead, or a fused "half-arrow" (the map glyph).
LinkWidthSpecLink width. A constant, a (weight) => width scale of the edge weight (a bare d3 scale fits — its input is the edge’s weight, which is the per-edge flow), or { by, scale } for parity with NodeRadiusSpec (by is "weight"/"flow" — the same per-edge quantity — so the scale maps the weight). Whichever form, it resolves to a function of a weight value, so a super-edge applies the same scale to the accumulated weight of the edges it subsumes.
NetworkFormatNetwork text formats parseNetwork can read.
NodeMetricA built-in per-node metric d3gl can read directly, or a custom (index, graph) => value accessor. "degree" is the neighbour count (CSR.degree); "strength" the weighted degree (NetworkGraph.strength); "flow" the app-provided NetworkGraph.flow (errors if absent).
NodeRadiusSpecHow node radius is determined. Resolves once (per style() call) to a per-node Float32Array, which is already a per-instance GPU attribute — so any of these forms is free at draw time.
VariableDescription
DEFAULT_FORCE-
FunctionDescription
buildCSRBuild undirected (symmetric) CSR adjacency from a directed edge list. Each edge contributes to both endpoints, so layout/traversal see the graph as undirected while the directed edges remain available for arrow rendering.
buildGraphAssemble a NetworkGraph from a directed edge list.
buildHierarchyBuild the full coarsening hierarchy, stopping at minNodes or when a pass stops reducing.
buildLODTreeBuild the retained LOD tree topology from a graph’s coarsening hierarchy. Geometry is left zeroed; call computeLODGeometry once positions have settled. This is the main-thread path (the force/positions backends and LOD enabled after a worker has finished); the worker backend streams an already-built LODTopology instead (#103), assembled via lodTreeFromTopology.
buildModuleLODTreeBuild a LODTree from a provided module hierarchy (the priority-chain entry that precedes structural coarsening). Geometry is left zeroed — fill it with computeLODGeometry once positions exist.
buildStateGraphAssemble a StateNetworkGraph from a state-level edge list + a per-state-node physical id. The physical network is derived once (its links are the directed, flow-summed aggregation of state edges that cross a physical boundary). Positions in both graphs are left zeroed for layout to fill.
coarsenLevelOne coarsening step. Visit nodes in index order and pair each unmatched node with its heaviest unmatched neighbour (lowest index breaks ties → deterministic). A node with no unmatched neighbour is adopted into its heaviest already-matched neighbour’s group rather than left as a singleton: without this, hub/star structures (pervasive in power-law graphs) barely shrink — each pass strips only a couple of nodes, the hierarchy hits its level cap, and the multilevel seed runs dozens of force solves on near-full graphs (#117). Adoption keeps the per-level reduction roughly geometric. Then aggregate the surviving inter-group edges (parallel edges summed, internal edges dropped).
computeLODGeometryFill the tree’s full geometry from the settled layout, the per-leaf visual radii, and a per-leaf importance weight — computeLODPositions then computeLODStyle. The main-thread path (synchronous solve / supplied positions); the worker backend splits these passes across threads.
cut-
declutterFrontierThin an LOD frontier in screen space: keep higher-importance glyphs (by tree LODTree.weight = strength) and drop lower-importance ones that would overlap a kept glyph (centre distance < sum of the two radii). Greedy in descending importance over a uniform screen grid, so a dense cluster keeps its most important members and the kept set is overlap-free (no overdraw). Runs per cut, so it’s zoom-dependent — more glyphs resolve as you zoom in. Returns the kept frontier ids (original order).
detectFormatPick a parser: Pajek for a .net filename or text with a *Vertices/*Arcs/*Edges section header; otherwise the plain edge list. A filename extension wins over content sniffing.
halfLinkGeometryResolve the half-arrow link’s outline vertices, or null when the link should be skipped (the nodes overlap and the bend is too small to route around them — matches the reference’s guard).
halfLinkPathStringThe reference SVG path string for a half-arrow link (or "" when skipped) — same command sequence and number formatting as mapequation’s network-rendering, so it is golden-tested against its example.svg.
makeCutScratchFresh CutScratch. The network engine keeps ONE per instance; cut falls back to a throwaway one when none is passed (backward-compatible, but then every call allocates).
makeDeclutterFrontierScratchFresh DeclutterFrontierScratch. The network engine keeps ONE per instance; declutterFrontier falls back to a throwaway one when none is passed (backward-compatible, but then every call allocates).
moduleColorsPer-node CSS colours (indexed by node id) for a module hierarchy. A node takes the hue of its enclosing module (path minus the final rank), so all nodes in a module share a colour and sibling modules get neighbouring hues within their parent’s arc.
multilevelLayoutMultilevel force layout: multilevelSeed then refine the finest level in place. Writes graph.positions. This is the synchronous main-thread path; the worker reuses multilevelSeed and streams the finest-level refinement instead.
networkCreate a Network engine on host.
parseEdgeListParse a whitespace-separated edge list: source target [weight] per line. Blank lines and # comment lines are ignored.
parseNetworkParse a network from text, choosing the format via detectFormat: Pajek .net or a plain edge list (source target [weight], # comments). Edge lists are reported as undirected.
parsePajekParse a Pajek .net document into a directed-or-undirected edge list (+ optional positions).
physicalPieWedgesDerive per-physical-node pie wedges from a state-node module assignment. modules is Infomap’s nodes array (each record’s id is a state-node index, path its module chain), as consumed by moduleColors / buildModuleLODTree. Records must cover every state node.
randomWalkFlowCompute the random-walk FlowResult for a directed graph. O(iterations · edges); a handful of milliseconds for the thousands-of-nodes scale this is meant for. Run it offline to bake a fixture.
rosettePositionsDerive state-node positions by rosette placement around the (already laid-out) physical nodes. Reads graph.physical.positions (fill it first via a layout) and returns a fresh interleaved [x, y, …] array of length 2 * graph.state.nodeCount, ready for net.data(graph.state).layout({ backend: "positions", positions }).
scaleHalfLinkUniformly scale a resolved half-arrow geometry’s coordinates by s (about the origin). Used to “bake” a screen-space shape for the retained SVG/Canvas path: the shape is solved in pixel space (node centres × k, sizes in px) — because the tip length (width^⅓), tip width (width^½) and outer bend (^0.4) are not linear in size, so they can’t be reproduced by pre-dividing the sizes — and the result is scaled by 1/k, so the Scene’s ×k view transform reproduces the exact pixel shape.
seedPositionsSeed node positions deterministically as a phyllotaxis (“sunflower”) disc centred on the viewport — a good, reproducible starting distribution for ForceLayout when a graph arrives without coordinates (no two nodes coincident, no RNG).
sharedMemoryAvailableWhether this environment can use the SharedArrayBuffer zero-copy position transport: SharedArrayBuffer exists and the page is cross-origin isolated (served with Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corp). When false, the worker posts per-frame position snapshots instead. This reports the environment’s capability; whether a given run actually used it is WorkerLayoutHandle.shared (they differ when the worker is unavailable and the layout falls back to a synchronous main-thread solve).
traceHalfLinkTrace a resolved half-arrow link onto a PathSink (Canvas/SVG export), as the reference does: inner-start → source centre → foot → outer edge (quadratic via cp2) → barb → tip → inner base → inner edge (quadratic via cp1) → close.