import { ForceDirectedGraph } from "semiotic"
ForceDirectedGraph visualizes network relationships by simulating physical forces that push and pull connected nodes into a stable layout. Pass your nodes and edges, optionally map color and size to data fields, and the component supplies the interactive layout, hover annotations, and legends.
Quick Start
The simplest force-directed graph requires just nodes andedges.
import { ForceDirectedGraph } from "semiotic" const frameProps = { /* --- Data --- */ nodes: [ { id: "Alice", group: "Engineering" }, { id: "Bob", group: "Engineering" }, { id: "Carol", group: "Design" }, // ...more nodes ], edges: [ { source: "Alice", target: "Bob" }, { source: "Alice", target: "Carol" }, // ...more edges ] } export default () => { return <ForceDirectedGraph {...frameProps} /> }
Examples
Colored by Group
Use colorBy to color nodes by a categorical field. A legend is displayed automatically when colorBy is set.
import { ForceDirectedGraph } from "semiotic" const frameProps = { /* --- Data --- */ nodes: nodeData, edges: edgeData, /* --- Customize --- */ colorBy: "group" } export default () => { return <ForceDirectedGraph {...frameProps} /> }
Dynamic Node Sizing
Map nodeSize to a data field and control the range withnodeSizeRange. Combined with colorBy andshowLabels, this creates a rich overview of your network.
import { ForceDirectedGraph } from "semiotic" const frameProps = { /* --- Data --- */ nodes: [ { id: "Alice", group: "Engineering", connections: 3 }, { id: "Bob", group: "Engineering", connections: 3 }, // ...nodes with connections field ], edges: edgeData, /* --- Customize --- */ colorBy: "group", nodeSize: "connections", nodeSizeRange: [5, 20], showLabels: true, nodeLabel: "id" } export default () => { return <ForceDirectedGraph {...frameProps} /> }
Custom Force Strength
Adjust iterations and forceStrength to control the layout density. Lower force strength produces a looser, more spread-out graph.
The layout uses degree-aware repulsion, degree-normalized link attraction, and radius-aware collision. It produces a stable static result without requiring a continuously running interactive simulation.
import { ForceDirectedGraph } from "semiotic" const frameProps = { /* --- Data --- */ nodes: nodeData, edges: edgeData, /* --- Layout --- */ iterations: 500, forceStrength: 0.02, /* --- Customize --- */ colorBy: "group", edgeColor: "#ccc", edgeOpacity: 0.4 } export default () => { return <ForceDirectedGraph {...frameProps} /> }
Layout Execution and Loading
layoutExecution="auto" keeps small graphs and server rendering synchronous, but moves more expensive layouts to a Web Worker. While a cold worker layout is pending, the chart reserves its dimensions and displays a loading placeholder. SetlayoutExecution="sync" for environments that cannot serve the packaged worker asset, or "worker" to force the asynchronous path.
JSX
<ForceDirectedGraph nodes={networkNodes} edges={networkEdges} layoutExecution="auto" layoutLoadingContent={<NetworkSkeleton />} onLayoutStateChange={(state) => setLayoutState(state)} />
Worker execution requires the package'sforceLayoutWorker.js asset to be emitted by your bundler. Content Security Policy deployments should allow same-origin workers with worker-src 'self'. If worker construction or execution fails, Semiotic still renders using the synchronous layout path.
Props
| Prop | Type | Required | Default | Description |
|---|
nodes | array | Yes | — | Array of node objects. Each node should have an id property. |
edges | array | Yes | — | Array of edge objects connecting nodes via source and target properties. |
nodeIDAccessor | string | function | — | "id" | Field name or function to access node IDs. |
sourceAccessor | string | function | — | "source" | Field name or function to access edge source IDs. |
targetAccessor | string | function | — | "target" | Field name or function to access edge target IDs. |
nodeLabel | string | function | — | — | Field name or function to determine node labels. |
colorBy | string | function | — | — | Field name or function to determine node color. |
colorScheme | string | array | — | "category10" | Color scheme name or custom colors array. |
nodeSize | number | string | function | — | 8 | Fixed size, field name, or function to determine node radius. |
nodeSizeRange | [number, number] | — | [5, 20] | Min and max radius for nodes when using dynamic sizing. |
edgeWidth | number | string | function | — | 1 | Fixed width, field name, or function to determine edge stroke width. |
edgeColor | string | — | "#999" | Stroke color applied to edges. |
edgeOpacity | number | — | 0.6 | Opacity applied to edges. |
iterations | number | — | 300 | Number of force simulation iterations. Higher values produce more stable layouts. |
forceStrength | number | — | 0.1 | Strength of the force simulation. Lower values create looser layouts. |
layoutExecution | "auto" | "worker" | "sync" | — | "auto" | Run layout synchronously, in a Web Worker, or automatically choose based on estimated graph cost. |
layoutLoadingContent | ReactNode | false | — | — | Custom content shown in the chart slot while worker layout is pending. |
onLayoutStateChange | function | — | — | Called with pending, ready, or error when internally managed layout changes state. |
showLabels | boolean | — | false | Show text labels on each node. |
enableHover | boolean | — | true | Enable hover annotations on nodes. |
showLegend | boolean | — | true (when colorBy set) | Show a legend. Defaults to true when colorBy is specified. |
tooltip | object | function | — | — | Tooltip configuration or render function. |
width | number | — | 600 | Chart width in pixels. |
height | number | — | 600 | Chart height in pixels. |
margin | object | — | { top: 20, bottom: 20, left: 20, right: 20 } | Margin around the chart area. |
title | string | — | — | Chart title displayed at the top. |
frameProps | object | — | — | Additional StreamNetworkFrame props for advanced customization. Escape hatch to the full Frame API. |
When to Use the Frame
Use StreamNetworkFrame directly for custom node icons, complex interactions, or advanced force parameters. ForceDirectedGraphdelegates to a configured StreamNetworkFrame.
Chart (simple)
JSX
import { ForceDirectedGraph } from "semiotic" <ForceDirectedGraph nodes={networkNodes} edges={networkEdges} colorBy="group" nodeSize="connections" showLabels={true} nodeLabel="id" />
Frame (full control)
JSX
import { StreamNetworkFrame } from "semiotic" <StreamNetworkFrame nodes={networkNodes} edges={networkEdges} nodeIDAccessor="id" sourceAccessor="source" targetAccessor="target" networkType={{ type: "force", iterations: 300, edgeStrength: 0.1 }} nodeStyle={d => ({ fill: colorScale(d.group), r: sizeScale(d.connections) })} edgeStyle={{ stroke: "#999", opacity: 0.6 }} nodeLabels={d => d.id} hoverAnnotation={true} size={[600, 600]} />
The frameProps prop on ForceDirectedGraph lets you pass any StreamNetworkFrame prop without fully graduating:
JSX
// Use frameProps as an escape hatch <ForceDirectedGraph nodes={nodes} edges={edges} colorBy="group" frameProps={{ customNodeIcon: ({ d }) => ( <circle r={10} fill="gold" stroke="black" /> ), annotations: [ { type: "node", id: "Alice", label: "Key Person" } ] }} />
- ChordDiagram — circular layout for showing flow between entities
- SankeyDiagram — flow diagram showing magnitude of movement between nodes
- TreeDiagram — hierarchical layouts for tree-structured data
- StreamNetworkFrame — the underlying Frame with full control over every rendering detail
- Tooltips — custom tooltip content and positioning