Layouts & options
Core contracts
Section titled “Core contracts”type LayoutNode = { id: string; width: number; height: number };
type LayoutEdge = { source: string; target: string };
type EgoGraph<N, E> = { nodes: N[]; edges: E[]; root: string };
type Positions = Map<string, { x: number; y: number }>;Your own fields travel through the generic types, so classification callbacks can inspect them.
Functions
Section titled “Functions”customLayout(graph, options?) walks the spanning tree and places each parent’s direct children according to a layer spec. Import it from ego-graph for radial, bubble, and cloud. Import from ego-graph/dagre when a spec uses dagre, dagreSubtree, or sectorThreshold.
buildSpanningTree(graph, options?) and reachableNodeIds(nodeIds, edges, root) expose the underlying tree utilities.
Layer specs
Section titled “Layer specs”type FirstLayerSpec = { mode: 'radial' | 'dagre' | 'bubble' | 'dagreSubtree'; sectorThreshold?: number };type CloudLayerSpec = { mode: 'cloud'; threshold?: 3 | 5 | 8 | 13 | 21 };type FirstLayerBand = FirstLayerSpec & { upTo?: number };type FirstLayersSpec = FirstLayerSpec | readonly FirstLayerBand[];
customLayout(graph, { firstLayer: { mode: 'radial' }, nextLayers: { mode: 'cloud', threshold: 8 },});Both firstLayer and nextLayers may be one spec or an array of bands. Omit firstLayer and it defaults to { mode: 'radial' }. cloud is next-layers only: the root has no outbound ray. Bubble ring capacity is innerRingCapacity on spacing. Cloud’s cap is threshold on the spec (3, 5, 8, 13, or 21; default 8). dagre may set sectorThreshold to split a bushy parent into several shallow pockets.
| Mode | Placement |
|---|---|
radial |
Full circle around the root; a hemisphere centred on the outbound ray for deeper parents |
dagre |
One shallow Dagre pocket along the outbound ray (straight down at the root) |
bubble |
Direct children packed into concentric rings so the group stays round |
dagreSubtree |
Remaining descendants as one Dagre tree; the walk does not recurse |
cloud |
Fibonacci-sized arcs along the outbound ray (deeper parents only) |
A mode picker should use FIRST_LAYER_NAMES for first-layer bands and DISTRIBUTION_NAMES for next layers.
import { DISTRIBUTION_NAMES, FIRST_LAYER_NAMES } from 'ego-graph';The React Flow complete example does this for both layers as band editors.
Count bands
Section titled “Count bands”Both firstLayer and nextLayers can be one spec, or an array of bands. Each band uses its spec while a parent has at most upTo children. Omit upTo on the last band to catch everything remaining.
import { customLayout } from 'ego-graph/dagre';
customLayout(graph, { firstLayer: [ { upTo: 5, mode: 'dagre' }, { upTo: 20, mode: 'radial' }, { mode: 'bubble' }, ], nextLayers: [ { upTo: 5, mode: 'dagre' }, { upTo: 20, mode: 'radial' }, { mode: 'cloud', threshold: 8 }, ],});Layer bands is the executable sample.
Former named functions as specs:
customLayout(graph, { firstLayer: { mode: 'radial' }, nextLayers: { mode: 'radial' } });customLayout(graph, { firstLayer: { mode: 'dagre', sectorThreshold: 5 }, nextLayers: { mode: 'dagre', sectorThreshold: 5 },});customLayout(graph, { firstLayer: { mode: 'radial' }, nextLayers: { mode: 'dagreSubtree' } });The last two must be imported from ego-graph/dagre.
Bubble rings
Section titled “Bubble rings”innerRingCapacity (T, default 6) is the capacity of the innermost ring. It is not a maximum child count. Extra children spill into outer rings.
Number rings from the inside, starting at 0:
| Ring | Capacity |
|---|---|
| 0 (innermost) | T |
| 1 | 2T |
k |
(k + 1) × T |
Children are ordered with heavier subtrees first, then poured into ring 0 until it is full, then ring 1, and so on. A leftover partial ring is allowed. Fourteen children with T = 6 occupy 6 + 8, not a third ring.
Each ring is a full circle of equally spaced slots. Its radius is large enough that those siblings do not overlap. Each outer ring sits outside the previous one plus rankSep.
At the root, every ring is centred on the ego. When the parent is not the root, the whole disk is translated along the parent’s outbound ray so it sits in front of that parent: the parent’s radial half-extent, plus ringPadding, plus the bubble’s own radius.
Do not confuse a bubble ring with the first ring. The first ring is the circle of the root’s children under radial. A bubble ring is one concentric circle inside a bubble packing of a single parent’s children. A band is a child-count range, not a circle.
Cloud layers
Section titled “Cloud layers”cloud packs a deeper parent’s children onto tight arcs centred on the outbound ray (grandparent → parent). It is not valid as firstLayer.
threshold (T, default 8) is one of 3, 5, 8, 13, 21. Layer sizes climb the Fibonacci sequence from 1 up to T, hold at T for leftover nodes, then taper. One non-Fibonacci remainder layer, if needed, sits in the descending run. Heavier children sit inward and on the ray. Adjacent layers are separated by rankSep; parent clearance and arc packing use ringPadding.
Spacing
Section titled “Spacing”Every layout accepts these optional dials. DEFAULT_SPACING exports the length defaults. Bubble ring capacity is innerRingCapacity, a spacing field. Cloud’s cap is threshold on the layer spec.
| Option | Default | Layouts | Meaning |
|---|---|---|---|
nodeSep |
80 | dagre, dagreSubtree |
Gap between siblings inside a Dagre pocket or subtree. |
rankSep |
100 | dagre, dagreSubtree, bubble, cloud |
Gap between Dagre ranks, bubble rings, and cloud layers. |
minRingRadius |
220 | radial, bubble |
Floor on ring radius so children clear the parent. |
ringPadding |
60 | radial, bubble, cloud |
Extra space between adjacent branches, and parent clearance for a cloud. |
innerRingCapacity |
6 | bubble |
Nodes on the innermost bubble ring. Further rings hold 2×, 3×, … that many. |
satelliteGap |
π / 3 | satellites | Angular proximity that triggers satellite fanning. |
minSatelliteGap |
π / 12 | satellites | Minimum separation for fanned satellites. |