Layouts ​
Guide to available layout algorithms and configuration.
Overview ​
Layout algorithms determine how nodes are positioned in the visualization. Choose the right layout based on your graph's structure and what you want to communicate.
Available Layouts ​
setLayout takes either an engine name from the table below or a layout id from catalog.layouts(). An id runs that layout's default engine, so setLayout("force") runs ngraph, "force-2d" runs arf, "hierarchical" runs bfs and "layers" runs multipartite.
| Layout | Type | Best For | Dimensions |
|---|---|---|---|
ngraph | Force-directed | General graphs | 2D/3D |
d3-force | Force-directed | Web-standard | 2D |
circular | Geometric | Cycles, small graphs | 2D/3D |
grid | Geometric | Regular structures | 2D |
radial | Geometric | Distance from one node | 2D |
hierarchical | Layered | Trees, DAGs | 2D/3D |
random | Random | Testing, initial state | 2D/3D |
fixed | Manual | Pre-computed positions | 2D/3D |
forceatlas2 | Force-directed | Clusters and communities | 2D/3D |
spring | Force-directed | General graphs | 2D/3D |
spring-electrical | Force-directed | Large graphs, with a hardware accelerator | 2D/3D |
The last three are live simulations: they keep stepping until the arrangement comes to rest rather than computing one arrangement and stopping, so element.setRunning(false) pauses one and element.setRunning(true) sets it going again. The pause holds until you resume it: loading more nodes, setting another layout, an accelerator attaching or dragging a node places and moves nodes but never restarts the simulation. getLayoutManager().isPaused is true for a layout stopped before it came to rest, and isSettled is true only once it has. They are also the three that run on a hardware accelerator when there is one -- and spring-electrical only runs on one. It has no CPU implementation at all, so setLayout("spring-electrical") without an accelerator that implements it throws E_NO_ACCELERATOR rather than quietly arranging the graph some other way. See the acceleration guide.
The default layout runs on an accelerator too, without being asked to and without being named differently. ngraph and spring-electrical are one force model with two implementations, so on a graph of two thousand nodes or more, with an accelerator attached that computes it, the element draws ngraph's arrangement on the accelerator; below that size, and on any machine with no accelerator, ngraph itself draws it. getLayoutManager().layoutType says ngraph either way -- it is the same arrangement -- and nothing you write chooses between them. Two thousand is where ngraph's own step stops fitting inside a frame, measured: 2.6 ms at a thousand nodes, 12 ms at two thousand and 1.8 seconds at a hundred thousand. acceleration="required" uses the accelerator at any size, and acceleration="off" never does.
Setting a Layout ​
Via HTML Attribute ​
<graphty-element layout="ngraph"></graphty-element> <graphty-element layout="circular"></graphty-element>Via JavaScript ​
// Simple layout change
graph.setLayout("circular");
// With configuration options
graph.setLayout("ngraph", {
springLength: 100,
springCoefficient: 0.0008,
gravity: -1.2,
dimensions: 3,
});Layout Descriptions ​
ngraph (Force-Directed) ​
The default layout. Uses physics simulation where:
- Edges act like springs pulling connected nodes together
- Nodes repel each other to prevent overlap
- Works well for most general graphs
- Runs on a hardware accelerator, as
spring-electrical, from two thousand nodes upwards when one is attached; see the note at the top of this page
graph.setLayout("ngraph", {
springLength: 100, // Ideal edge length
springCoefficient: 0.0008, // Spring stiffness
gravity: -1.2, // Global attraction/repulsion
dimensions: 3, // 2 or 3
dragCoefficient: 0.02, // Damping
theta: 0.8, // Barnes-Hut approximation
seed: 7, // Starting positions; unset, they differ on every load
});The same drawing every time ​
The default layout is unseeded, so the same data can settle in a different place each time it is loaded. To have one file draw the same way on every load, pass a seed. A seeded layout settles in the same place however its nodes and edges arrive -- in one write, or split across several writes with frames between them:
const element = document.querySelector("graphty-element");
// Seed the default layout before the data arrives.
element.layoutConfig = { seed: 7 };
element.nodeData = nodes;
element.edgeData = edges;
// Or seed it by name. A different seed gives a different, equally repeatable drawing.
await element.setLayout("force", { seed: 7 });d3-force (Force-Directed) ​
D3's force simulation. Industry-standard for web visualizations:
graph.setLayout("d3-force", {
strength: -30, // Node repulsion
distance: 50, // Link distance
iterations: 300, // Simulation steps
});circular ​
Arranges nodes in a circle. Good for:
- Small graphs
- Cycle detection
- Ring topologies
graph.setLayout("circular", {
radius: 100, // Circle radius
startAngle: 0, // Starting angle (radians)
endAngle: Math.PI * 2, // Ending angle
});grid ​
Arranges nodes in evenly spaced rows and columns, in the order they were loaded:
graph.setLayout("grid", {
columns: 5, // Number of columns (default: as close to square as possible)
scale: 1, // Half the length of the grid's longer side
});radial ​
Puts one node at the centre and every other node on a ring by its hop distance from it. Nodes the root cannot reach share one extra outer ring:
graph.setLayout("radial", {
root: "node-1", // The node at the centre (default: the node with the most edges)
scale: 1, // Radius of the outermost ring
});hierarchical ​
Tree-like layout for directed graphs:
graph.setLayout("hierarchical", {
direction: "TB", // TB, BT, LR, RL
levelSeparation: 100, // Vertical spacing
nodeSeparation: 50, // Horizontal spacing
});random ​
Random positions. Useful for:
- Testing
- Initial state before force layout
- Deliberate chaos visualization
graph.setLayout("random", {
seed: 42, // For reproducible layouts
dimensions: 3,
});fixed ​
Use pre-computed positions from node data:
// Node data includes positions
const nodes = [
{ id: "a", x: 0, y: 0, z: 0 },
{ id: "b", x: 100, y: 0, z: 0 },
{ id: "c", x: 50, y: 100, z: 0 },
];
await graph.addNodes(nodes);
graph.setLayout("fixed");forceatlas2 (Force-Directed, live) ​
Gephi's ForceAtlas2, kept running rather than solved once. Good for pulling communities apart:
graph.setLayout("forceatlas2", {
seed: 42, // Same seed, same settled shape
scalingRatio: 2.0, // Node repulsion
gravity: 1.0, // Pull towards the centre
linlog: false, // Log attraction: tighter clusters
dissuadeHubs: false, // Push high-degree nodes outwards
});Runs on a hardware accelerator when one is attached. See the acceleration guide.
spring (Force-Directed, live) ​
Fruchterman-Reingold, also a live simulation. Pick it when the arrangement has to be reproducible from a seed:
graph.setLayout("spring", {
seed: 42, // Same seed, same settled shape
k: null, // Ideal node distance; null auto-calculates it
iterations: 50, // Simulation steps per settle
scale: 1, // Multiplies the radius the arrangement is drawn at
});Runs on a hardware accelerator when one is attached.
spring-electrical (Force-Directed, live, accelerator only) ​
ngraph's spring-electrical model at a size ngraph itself cannot reach, and what the default layout is drawn by on a big enough graph when an accelerator is attached. Asked for by name it has no CPU implementation: without an accelerator that implements it, setLayout("spring-electrical") throws E_NO_ACCELERATOR rather than quietly arranging the graph some other way.
graph.setLayout("spring-electrical", {
seed: 42,
springLength: 10, // The distance an edge pulls its nodes towards
springCoefficient: 0.8, // How hard an edge pulls
gravity: -12, // Node repulsion; negative repels
dragCoefficient: 0.9, // How quickly motion bleeds away
});See the acceleration guide for how to attach one.
Layout Transitions ​
Animate between layouts for smooth visual transitions:
// Current layout
graph.setLayout("random");
await graph.waitForSettled();
// Transition to new layout
graph.setLayout(
"circular",
{},
{
animate: true,
duration: 1000,
},
);Waiting for Settled ​
Force-directed layouts converge over time. Wait for stabilization:
graph.setLayout("ngraph");
// Wait for physics to settle
await graph.waitForSettled();
// Now safe to zoom to fit
graph.zoomToFit();You can also listen to the event:
graph.on("graph-settled", () => {
console.log("Layout complete");
graph.zoomToFit();
});2D vs 3D ​
Most layouts support both dimensions:
// 3D layout (default)
graph.setLayout("ngraph", { dimensions: 3 });
// 2D layout
graph.setLayout("ngraph", { dimensions: 2 });For 2D layouts, also set the view mode:
<graphty-element layout="d3-force" view-mode="2d"></graphty-element>Edge weights ​
Two layouts read edge weights: kamada-kawai and forceatlas2. Both are on by default on a graph whose edges carry weights, and both read the number the same way: a larger weight means a stronger connection, drawn shorter.
// Weights are read by default. This says so explicitly.
graph.setLayout("forceatlas2", { weighted: true });
// Arrange this graph as though its weights were not there.
graph.setLayout("forceatlas2", { weighted: false });A graph whose weights are all 1 -- which is every unweighted graph -- is arranged exactly as it was before weights existed, by construction: with no information in the weight column the element hands the layout no weight callback at all.
Where a weight comes from is data.knownFields.edgeWeightPath, which defaults to weight. Parallel edges are summed into one weight per ordered pair, because the layout functions read a weight by endpoint pair and have nowhere to put a second one.
Ask the catalogue rather than hard-coding the list of two:
for (const layout of element.session.catalog.layouts()) {
layout.honoursWeights; // whether a "use edge weights" control belongs in your UI
}weighted replaces 1.x's weightProperty and weightPath. Both are gone, and a stored layout configuration carrying either is refused at parse rather than ignored.
Pinned nodes ​
A node the reader drags is pinned where they dropped it, and it stays there through a layout change, a 2D/3D switch and a template apply. That is true under every layout, including the fourteen with no physics of their own and including a layout you wrote yourself.
element.pin("alice");
element.unpin("alice");
element.pinnedNodes; // a Set of the pinned node idsTurn the drag behaviour off with pinOnDrag: false in the graph's behaviour configuration; the verbs above still work.
Laying out part of the graph ​
setLayout takes a scope as its third argument. The layout moves only the scope's nodes and holds every other node exactly where it is -- useful for tidying one community or a kept set without disturbing the rest:
const cluster = element.session.sets.create({ kind: "fixed", nodes: ["a", "b", "c", "d"], reading: "induced" });
await element.setLayout("ngraph", { seed: 7 }, { scope: { set: cluster } });
// The scope is kept: changing an option lays out the same nodes again
element.layoutConfig = { seed: 8 };
// Back to the whole graph
await element.setLayout("ngraph", {}, { scope: "graph" });The same scope is the layoutScope property and the layout-scope attribute (JSON), which read undefined for the whole graph.
- The members are captured when the layout starts. A click, a filter change or an attribute edit does not move the hold, and a node added later is held too.
- The physics layouts accept a scope:
ngraph,d3,forceatlas2,springandspring-electrical. Any other refuses one withE_UNSUPPORTED;session.catalog.layouts()says which asscoped. - A hold is not a pin. It is never saved as a pin, and unpinning a held node does not release it.
- Removing the set releases the hold: the layout runs over the whole graph, and nothing throws.
- Held nodes still push and pull on the members, so a scoped layout costs the same per step as a whole-graph one, and a small scope is spread over the whole layout's extent.
Performance Tips ​
- Large graphs: Use Barnes-Hut approximation (ngraph with default theta)
- Initial state: Start with random layout, then switch to force-directed
- Fixed data: Pre-compute positions and use
fixedlayout - Incremental updates: Add nodes in batches, not one at a time
Custom Layouts ​
Create your own layout algorithms. See Custom Layouts for details.