Skip to content

Getting Started ​

@graphty/layout positions the nodes of a graph. It is a TypeScript port of the NetworkX layouts, with 15 layouts in 2D and 3D and two force simulations you can step frame by frame.

Installation ​

bash
npm install @graphty/layout @graphty/graph-format
bash
pnpm add @graphty/layout @graphty/graph-format
bash
yarn add @graphty/layout @graphty/graph-format

@graphty/graph-format builds the graph every layout reads. See Installation for a CDN import and the supported runtimes.

Your First Layout ​

Build a graph, run a layout, and read each node's position by its id:

typescript
import { GraphBuilder } from "@graphty/graph-format";
import { circular, toPositionMap } from "@graphty/layout";

const builder = new GraphBuilder({ directed: false });
builder.addEdge("a", "b");
builder.addEdge("b", "c");
builder.addEdge("c", "a");
builder.addEdge("c", "d");
const graph = builder.freeze();

const result = circular(graph, { scale: 100 });
const byId = toPositionMap(result, graph.ids);
console.log(byId.a); // [100, 0]

builder.freeze() returns a read-only graph snapshot. circular places the four nodes on a circle of radius 100, and toPositionMap turns the result into an object keyed by node id, each value an [x, y] array.

The Result ​

Every layout returns the same shape, a LayoutResult:

  • positions: a Float32Array holding dim numbers per node, node after node
  • dim: 2 or 3, the numbers per node
  • n: the number of nodes

Nodes are numbered in the order their ids first appeared in the builder, so "a" is node 0, "b" node 1 and so on. Node i's coordinates start at positions[i * dim]. graph.ids converts between ids and those numbers:

typescript
import { GraphBuilder } from "@graphty/graph-format";
import { circular } from "@graphty/layout";

const builder = new GraphBuilder({ directed: false });
builder.addEdge("a", "b");
builder.addEdge("b", "c");
builder.addEdge("c", "a");
builder.addEdge("c", "d");
const graph = builder.freeze();

const result = circular(graph, { scale: 100 });
console.log(result.dim, result.n, result.positions.length); // 2 4 8

const i = graph.ids.requireIndex("b");
const x = result.positions[i * result.dim];
const y = result.positions[i * result.dim + 1];
console.log(Math.round(x), Math.round(y)); // 0 100
console.log(graph.ids.idOf(3)); // d

Reading the array directly avoids building an object per node, which matters for large graphs; toPositionMap is the convenient form for small ones.

Choosing a Layout ​

You wantUseGuide
a quick, predictable placement that ignores the edgescircular, grid, spiral, randomGeometric
nodes grouped in rings you chooseshellGeometric
layers, levels or two sidesbfs, radial, bipartite, multipartiteStructural
a drawing with no crossing edgesplanarStructural
connected nodes close together, clusters visibleforceAtlas2, fruchtermanReingold, kamadaKawai, arfForce-directed
a quick overview of a graph's overall structurespectralForce-directed
an animated layout the reader can dragcreateSimulationSimulations

The Storybook draws every layout on sample graphs, with its options as controls.

Graphs You Already Have ​

A graph held as arrays of source and target node numbers becomes a snapshot in one call with fromEdgeArrays:

typescript
import { fromEdgeArrays } from "@graphty/graph-format";
import { grid } from "@graphty/layout";

const graph = fromEdgeArrays({
    directed: false,
    ids: ["x", "y", "z"],
    src: Uint32Array.of(0, 1),
    dst: Uint32Array.of(1, 2),
});
console.log(grid(graph).n); // 3

To read a file (GraphML, GEXF, GML, CSV and more), use @graphty/graph-io, which returns the same snapshot. Code written against layout 1.x, which took an object with nodes() and edges() methods, converts it with toLayoutSnapshot; see Results and Options.

Next Steps ​