Layout
graphraum renders positioned nodes. Where the positions come from is up to you: a server, a worker, a saved file, or the force helpers in the package. Layout never blocks the first render.
Stream positions
Section titled “Stream positions”GraphraumLayoutPositions keeps a layout worker separate from the renderer. positions contains an XYZ triplet for each ID in nodeIds; its buffer can be transferred from a worker.
const layout: GraphraumLayoutPositions = { nodeIds: ["person:ada", "place:london"], positions: new Float32Array([20, 12, 0, -20, 8, 0]),};
graph.applyLayout(layout);Force layout
Section titled “Force layout”The package exports computeForcePositions() for a static layout, computeClusteredForcePositions() for data-defined coarse communities, and createForceSimulation() for incremental or worker-driven layouts. All accept ForceSettings; omitted settings use DEFAULT_FORCE_SETTINGS. Pass iterations to override the size-based default used by computeForcePositions().
import { createForceSimulation, DEFAULT_FORCE_SETTINGS } from "@yggy-tech/graphraum";
const simulation = createForceSimulation({ dimensions: 3, edges: new Uint32Array([0, 1, 1, 2]), nodeCount: 3, settings: { ...DEFAULT_FORCE_SETTINGS, damping: 0.75, repulsion: 800 },});
simulation.step(0.35);The simulation recalculates and recenters its centroid after every step, so a live renderer can keep orbit controls focused without refitting the camera. In 2D every Z coordinate remains zero.
Clusters in a live simulation
Section titled “Clusters in a live simulation”Pass clusters (one Uint32Array entry per node) to createForceSimulation() to keep data-defined communities together while the layout runs. The simulation seeds nodes around cluster centres exactly like computeClusteredForcePositions(), then adds a weak pull of every node towards its cluster centroid on each step. settings.clusterStrength scales the pull (default 0.02, range 0 to 0.1); 0 disables it, and it is ignored without clusters. The array length must equal nodeCount, and the simulation keeps its own copy. Cluster ids must be dense: every id has to be smaller than the node count (addNodes() checks against the new total), otherwise the call throws before changing anything. createForceSimulation() honours clusters, and so does computeForcePositions(), which runs a simulation; computeClusteredForcePositions() keeps its one-shot seeding and accepts sparse ids.
const simulation = createForceSimulation({ clusters: new Uint32Array([0, 0, 0, 1, 1, 1]), dimensions: 2, edges: new Uint32Array([0, 1, 1, 2, 3, 4, 4, 5, 2, 3]), nodeCount: 6, settings: { ...DEFAULT_FORCE_SETTINGS, clusterStrength: 0.04 },});
// A clustered simulation requires one cluster index per added node.// A new index starts a new cluster.simulation.addNodes(2, undefined, new Uint32Array([1, 2]));addNodes(count, initial?, clusters?) requires clusters (length count) when the simulation was created with clusters, and throws when it was created without them. Nodes added without initial positions start at the default seed and are drawn into their cluster by the pull.