Skip to main content

Layout & Positioning

Three approaches to positioning nodes: manual coordinates, a grid system, or automatic layout algorithms.

Manual positioning

Use .at(x, y) to place nodes at exact coordinates within the viewBox:

import { viz } from 'vizcraft';

const builder = viz().view(500, 300);
builder.node('a').at(100, 150).circle(24);
builder.node('b').at(400, 150).rect(80, 40);
builder.edge('a', 'b').arrow();
builder.mount(document.getElementById('container')!);

Grid system

Define a grid with .grid(cols, rows, opts?) and position nodes with .cell(col, row):

Grid options:

OptionTypeDescription
xnumberLeft padding
ynumberTop padding

Auto-layout algorithms

Instead of positioning nodes manually, let an algorithm compute coordinates for you.

Circular layout

Arrange nodes in a circle:

Grid layout

Arrange nodes in rows and columns:

Custom algorithms

A layout algorithm is a function conforming to the LayoutAlgorithm<Options> signature — it receives a LayoutGraph (nodes + edges) and returns a LayoutResult mapping node IDs to { x, y }:

import type { LayoutAlgorithm } from 'vizcraft';

const myLayout: LayoutAlgorithm<{ spacing: number }> = (graph, opts) => {
const result: Record<string, { x: number; y: number }> = {};
graph.nodes.forEach((node, i) => {
result[node.id] = { x: i * (opts?.spacing ?? 100), y: 100 };
});
return result;
};

builder.layout(myLayout, { spacing: 120 });

You can also integrate third-party layout engines (like Dagre, ELK, or D3-force) by wrapping them in a LayoutAlgorithm function.

Async layout algorithms

Some layout engines (e.g. ELK via web workers) are asynchronous. VizCraft's LayoutAlgorithm type accepts both sync and Promise-returning functions. Use .layoutAsync() to apply an async algorithm:

import type { LayoutAlgorithm } from 'vizcraft';
import ELK from 'elkjs/lib/elk.bundled';

const elk = new ELK();

const elkLayout: LayoutAlgorithm = async (graph) => {
// Convert VizCraft graph → ELK graph, run layout, convert back
const elkGraph = toElkGraph(graph);
const result = await elk.layout(elkGraph);
return fromElkResult(result);
};

// Use .layoutAsync() — returns a Promise<VizBuilder>
await builder.layoutAsync(elkLayout);
builder.mount(container);

Note: The synchronous .layout() method throws a helpful error if it receives a Promise. Always use .layoutAsync() for async engines.

getNodeBoundingBox utility

When writing custom layout algorithms you often need each node's dimensions. Use the canonical getNodeBoundingBox(shape) utility instead of duplicating shape-specific size logic:

import { getNodeBoundingBox } from 'vizcraft';
import type { LayoutAlgorithm } from 'vizcraft';

const spacedLayout: LayoutAlgorithm = (graph) => {
let x = 0;
const nodes: Record<string, { x: number; y: number }> = {};
for (const node of graph.nodes) {
const { width } = getNodeBoundingBox(node.shape);
nodes[node.id] = { x: x + width / 2, y: 100 };
x += width + 20; // 20px gap
}
return { nodes };
};

getNodeBoundingBox supports all current NodeShape variants — circles, rects, diamonds, stars, hexagons, callouts, block arrows, and others — and centralizes the bounding-box logic so your layout code doesn't need to special-case shapes. It accounts for orientation, direction, pointer height, and other shape-specific parameters.


Found a problem? Open an issue on GitHub.