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" });Classes
Section titled “Classes”| Class | Description |
|---|---|
| ForceLayout | - |
| Network | The 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. |
Interfaces
Section titled “Interfaces”| Interface | Description |
|---|---|
| BuildGraphInput | - |
| BuildStateGraphInput | - |
| CoarseLevel | One coarsening level as a weighted, undirected edge list (parallel edges already collapsed). |
| CoarsenOptions | - |
| CSR | Compressed-sparse-row adjacency. |
| CutOptions | - |
| CutScratch | Reusable 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. |
| DeclutterFrontierScratch | Reusable 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 | - |
| FlowBorderSpec | Flow-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). |
| FlowGraph | Minimal directed-graph shape (a NetworkGraph satisfies it; raw arrays work too). |
| FlowOptions | - |
| FlowResult | - |
| ForceParams | Force-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. |
| HalfLinkGeometry | The resolved anchor + control points of a half-arrow link (the vertices its outline visits). |
| HalfLinkParams | Inputs for one directed half-arrow link (world coordinates/units). |
| Hierarchy | A coarsening hierarchy: progressively smaller graphs plus the maps that connect them. |
| LayoutGraph | Minimal 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. |
| LODTransform | Screen-space transform: screen = world * k + (x, y) (matches BaseEngine ViewTransform). |
| LODTree | A retained coarsening tree, flattened to SoA typed arrays for cache-friendly traversal at scale. |
| ModuleColorOptions | - |
| ModuleNode | A 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. |
| ModulePathNode | A node’s placement in the module tree — path is the Infomap 1-based chain (last entry is the rank). |
| MultilevelLayoutOptions | - |
| NetworkGraph | Network graph: directed-edge SoA for rendering + CSR for traversal. |
| NetworkHit | What a network Network.pick resolved — carried as the datum of the HoverHit passed to `on(“hover" |
| NetworkLayoutOptions | How 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. |
| NetworkLinkHit | What GPU-readback link picking (#141) resolved — carried as the datum of the HoverHit from `on(“hover" |
| NetworkLODOptions | Level-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" }). |
| NetworkOptions | Options for the network engine. Inherits sizing, backend, and tooltipClass. |
| NetworkStyle | Visual style. Link appearance accessors arrive with the link pass (#100 N2.2). |
| ParsedEdges | Edge-list ingestion (sub-issue #99 / epic #98). |
| ParsedPajek | Edge-list ingestion (sub-issue #99 / epic #98). |
| PathSink | A minimal 2-D path sink (matches the subset of CanvasRenderingContext2D / d3gl’s PathContext we use). |
| PhysicalPieWedges | Packed 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. |
| PhysicalToState | Physical 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 | - |
| StateNetworkGraph | A state network plus its engine-derived physical network and the maps between the two levels. |
| StateNetworkOptions | Options 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 Aliases
Section titled “Type Aliases”| Type Alias | Description |
|---|---|
| ImportanceSpec | How 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. |
| LinkColorSpec | Link 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). |
| LinkStyle | How directed links are drawn: plain "line" + a separate arrowhead, or a fused "half-arrow" (the map glyph). |
| LinkWidthSpec | Link 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. |
| NetworkFormat | Network text formats parseNetwork can read. |
| NodeMetric | A 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). |
| NodeRadiusSpec | How 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. |
Variables
Section titled “Variables”| Variable | Description |
|---|---|
| DEFAULT_FORCE | - |
Functions
Section titled “Functions”| Function | Description |
|---|---|
| buildCSR | Build 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. |
| buildGraph | Assemble a NetworkGraph from a directed edge list. |
| buildHierarchy | Build the full coarsening hierarchy, stopping at minNodes or when a pass stops reducing. |
| buildLODTree | Build 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. |
| buildModuleLODTree | Build 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. |
| buildStateGraph | Assemble 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. |
| coarsenLevel | One 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). |
| computeLODGeometry | Fill 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 | - |
| declutterFrontier | Thin 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). |
| detectFormat | Pick 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. |
| halfLinkGeometry | Resolve 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). |
| halfLinkPathString | The 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. |
| makeCutScratch | Fresh 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). |
| makeDeclutterFrontierScratch | Fresh 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). |
| moduleColors | Per-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. |
| multilevelLayout | Multilevel 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. |
| network | Create a Network engine on host. |
| parseEdgeList | Parse a whitespace-separated edge list: source target [weight] per line. Blank lines and # comment lines are ignored. |
| parseNetwork | Parse 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. |
| parsePajek | Parse a Pajek .net document into a directed-or-undirected edge list (+ optional positions). |
| physicalPieWedges | Derive 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. |
| randomWalkFlow | Compute 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. |
| rosettePositions | Derive 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 }). |
| scaleHalfLink | Uniformly 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. |
| seedPositions | Seed 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). |
| sharedMemoryAvailable | Whether 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). |
| traceHalfLink | Trace 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. |