Skip to main content

Props

<ForceGraph<TNode, TEdge>> accepts the following props. TNode must extend BaseNode { id: string } and TEdge must extend BaseEdge { id: string; source: string; target: string }.
TNode[]
required
Array of nodes to render. Each must have a unique id.
TEdge[]
required
Array of edges connecting nodes by their id.
(node: TNode) => string
default:"n.label ?? n.name ?? n.id"
Text rendered below each node.
(node: TNode) => number
default:"n.weight ?? 0"
Drives degree-based collision radius and clustering hub priority.
(edge: TEdge) => string
default:"e.label ?? \"\""
Text rendered on the midpoint of the edge. An empty string hides the label.
(node: TNode) => void
Fires after the camera centres on the node.
(edge: TEdge) => void
Fires after the camera centres on the edge midpoint.
() => void
Fires on canvas background click.
Partial<GraphTheme>
default:"DEFAULT_THEME"
Deep-merged with defaults. See Theme.
boolean
default:"false"
Skip Louvain detection and the cluster-aware forces. The rest of the simulation (charge, collision, link spring) is untouched.
false | "top-right" | "top-left" | "bottom-right" | "bottom-left"
default:"false"
Render a live zoom-percentage indicator at the given corner.
string
default:"\"cg-root\""
Replaces the container class entirely.
string
default:"\"Knowledge graph\""
Container aria-label.
ReactNode
default:"\"No entities to display\""
Rendered when nodes is empty and isLoading is falsy.
ReactNode
Rendered while the canvas lib is dynamic-imported, or when nodes is empty and isLoading is true.
boolean
Sets aria-busy on the container.
Ref<ForceGraphHandle>
Imperative handle — see below.

Imperative handle

Grab a ref to drive the graph from outside React state.
(scale: number, durationMs?: number) => void
Animate to an absolute zoom level.
(durationMs?: number, paddingPx?: number) => void
Fit every node in view.
(x: number, y: number, durationMs?: number) => void
Pan to graph coordinates.
() => void
Stop the simulation.
() => void
Resume the simulation.
() => void
Force a single canvas repaint.

Clustering

Enabled by default. On each nodes or edges change, the renderer runs Louvain community detection and feeds the result into the simulation as:
  • Variable link distance — intra-community links pull to theme.cluster.intraLinkDistance (120 by default); inter-community bridges pull to theme.cluster.interLinkDistance (280).
  • Centroid force — every tick each node is nudged toward its community’s centroid at strength theme.cluster.strength · alpha.
Pass disableClustering to turn the whole thing off. Useful when you already have a layout, but the result is a single gravity-well that often looks crowded for graphs with natural communities.
For 500+ node graphs that still feel cramped at high zoom, the right knobs to reach for are force.chargeStrength (more negative = more repulsion), cluster.strength (lower = looser clusters), and node.radius (bigger collision floor). See Theme for ranges.
Bundle impact: graphology + graphology-communities-louvain add ~60 KB gzipped. Both are bundled into the package — no peer dependency to install.

Data

The package does no fetching. Pull data with whatever you already use (TanStack Query, SWR, RSC, plain fetch) and pass arrays straight in.
TNode must extend BaseNode { id: string }; TEdge must extend BaseEdge { id: string; source: string; target: string }. Carry any domain fields on the node or edge directly.

Sub-path exports

SSR and Next.js

The renderer wraps react-force-graph-2d, which is canvas-based and client-only. <ForceGraph> is marked "use client" and dynamic-imports the lib internally, so it’s safe to mount inside Next.js server components — the container renders its loadingState (or nothing) on the server and hydrates on the client.
Don’t import @crosmos/graph from a server-only module. It needs the browser to actually draw.