Graph layout
ProLays out a directed graph in layers, from the edges the data already has: each node names the nodes it comes after, and the layout works out where everything goes. It is the layered method in a small form: a loop is broken by leaving one of its edges out of the layering (it is still returned, marked as a loop), each node goes one layer past the last node it follows, an edge that skips layers bends through each one it crosses, nodes in a layer are ordered to cross as few edges as a few sweeps find, and each node sits as near the nodes it follows as its neighbours allow. A node can be pinned to a layer, a slot, or both; the rest are laid out around it.
Pure and synchronous, with no DOM, so it runs on the server and in tests. Fit for the size of graph a person reads (tens of nodes, not thousands).
Version 5.2.0· other versions
This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).
Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1, 2.6.2, 2.7.0 and 2.8.0: the page came later, or had another name then.
Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.
Preview
It's types and helpers, listed below.
Install
First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/graph-layout
Brings in: tokens
Functions
layoutGraph(nodes: GraphLayoutNode[])Types
- GraphLayoutNode
- A node to lay out: its id, the ids it comes after, and optionally a fixed layer or slot.
{ id: string; /** The nodes this one comes after: an edge from each of them to this one. An id that isn't in the graph is ignored. */ after?: string[] | undefined; /** A fixed layer (0 is the first). Left out, it is one past the last node it comes after. */ layer?: number | undefined; /** A fixed slot across its layer (0 is the first). Left out, it is placed to cross as few edges as it can. */ slot?: number | undefined; }- GraphLayoutPoint
- A place in the layout: the layer, and the position across it (may be fractional, so a node can sit between two).
{ layer: number; slot: number }- GraphLayoutEdge
- An edge as laid out: from one node to another, through a point in each layer it crosses.
{ from: string; to: string; /** From the source's place to the target's, with a bend in each layer between. */ points: GraphLayoutPoint[]; /** Set when the edge closes a loop, or runs back to the same or an earlier layer: it was left out of the layering. */ loop?: boolean | undefined; }- GraphLayout
{ /** Each node's place, by id. */ places: Map<string, GraphLayoutPoint>; /** How many layers there are. */ layers: number; /** How wide the widest layer is, in slots (the last position plus one). */ slots: number; edges: GraphLayoutEdge[]; /** Edges naming a node that isn't in the graph, left out. */ missing: { from: string; to: string; }[]; /** True when some layer holds more than one node and there is at least one edge: the work branches. */ branches: boolean; }