Skip to content

Applying Layouts

A layout computes positions for your nodes. Nodus ships a family of layouts on nodus.layouts.*, each an async method that returns Promise<void>. This guide shows how to call them, animate or jump to the result, stop a running layout, and re-run after the graph changes.

For the conceptual differences between the algorithms, see Layouts. For the full signatures, see API: Layouts.

The available layouts

All of these are methods on nodus.layouts:

MethodGood for
force(opts?)General-purpose, organic spread of any graph
forceLink(opts?)Force layout that also respects link/edge structure
hierarchical(opts?)Directed/DAG-like data with clear levels
radial(opts?)Trees radiating from a root
concentric(opts?)Rings of nodes around a centre
grid(opts?)A regular grid arrangement
sequential(opts?)An ordered, sequence-style arrangement

Each returns a promise that resolves once positions are settled.

Running a layout

Call a layout method and await it. A typical first render runs a layout, then fits the result to the viewport.

import { Nodus } from '@kortexya/nodus';
const nodus = new Nodus({ container: document.getElementById('graph') });
await nodus.setGraph({
nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }, { id: 'd' }],
edges: [
{ source: 'a', target: 'b' },
{ source: 'a', target: 'c' },
{ source: 'c', target: 'd' },
],
});
await nodus.layouts.force();
await nodus.view.locateGraph();

view.locateGraph() pans and zooms so the whole graph is visible. Running it after the layout means it frames the final positions. See Controlling the Camera for more.

Try it live — switch between all the layouts on the same graph and re-run them:

Switch between layouts Open in new tab ↗

Animate or jump: the duration option

The one option proven across all layouts is duration (milliseconds):

  • duration: 0 — jump straight to the final positions (static, no animation).
  • a positive value — animate the transition over that many milliseconds.
// Snap into place instantly — good for large graphs or headless prep.
await nodus.layouts.force({ duration: 0 });
// Or animate the nodes gliding to their new positions over 800ms.
await nodus.layouts.force({ duration: 800 });

Other tuning

Beyond duration, each layout accepts options that tune its behaviour — the strength of repulsion and attraction for force layouts (gravity, edgeStrength, theta), the spacing and direction of a hierarchical layout (direction, nodesSeparation, layerSeparation), the grid shape (rows, cols, sortBy), and more. They all have defaults — pass only what you need:

await nodus.layouts.hierarchical({
duration: 600,
direction: 'LR', // 'TB' (default) | 'BT' | 'LR' | 'RL'
layerSeparation: 80,
});

See API: Layouts for the per-layout option tables.

Try it live — change the direction and spacing of a hierarchical layout:

Hierarchical layout Open in new tab ↗

Try it live — a radial layout radiating from its centralNode:

Radial layout (needs centralNode) Open in new tab ↗

Stopping a running layout

Force-directed layouts iterate over time. Call nodus.layouts.stop() to halt the current layout immediately, leaving nodes wherever they are.

// Kick off a long animated layout.
nodus.layouts.force({ duration: 5000 });
// Stop it early — e.g. the user dragged a node or clicked away.
stopButton.addEventListener('click', () => {
nodus.layouts.stop();
});

Re-running a layout after changes

When you add or remove nodes, the new elements need positions too. Add the data, then run the layout again. Because adds and removes are async, await them first so the layout sees the final graph.

// Grow the graph...
nodus.addNodes([{ id: 'e' }, { id: 'f' }]);
await nodus.addEdges([
{ source: 'd', target: 'e' },
{ source: 'e', target: 'f' },
]);
// ...then re-layout and re-frame.
await nodus.layouts.force({ duration: 600 });
await nodus.view.locateGraph({ duration: 600 });

This is the same pattern whether you’re expanding a neighbourhood on demand, loading paged results, or letting users build a graph interactively.

Switching layouts

You can run a different layout at any time to re-arrange the same graph. Each call recomputes positions from the current state.

async function showAs(kind, opts = {}) {
await nodus.layouts[kind]({ duration: 700, ...opts });
await nodus.view.locateGraph({ duration: 700 });
}
await showAs('hierarchical');
// later... (radial and concentric need a central node)
await showAs('radial', { centralNode: 'root' });

The standard pattern

Whatever you’re doing, the reliable sequence is:

// 1. Make sure the graph reflects what you want to lay out.
await nodus.setGraph(myData);
// 2. Run a layout (duration: 0 to snap, >0 to animate).
await nodus.layouts.force({ duration: 0 });
// 3. Fit the result to the viewport.
await nodus.view.locateGraph();

Next steps