Data
Diagram
A node/edge graph renderer for flowcharts, org charts, and dependency graphs — a static server-safe SVG for previews, or a pan/zoom/editable InteractiveDiagram for exploring and building the graph interactively.
DiagramInteractiveDiagramPreview
Usage
tsx
import { useState } from "react";
import { InteractiveDiagram, type DiagramNode, type DiagramEdge } from "@vesture/react";
function Example() {
const [nodes, setNodes] = useState<DiagramNode[]>([
{ id: "start", x: 60, y: 100, label: "Start" },
{ id: "validate", x: 260, y: 40, label: "Validate input" },
{ id: "process", x: 260, y: 160, label: "Process" },
{ id: "end", x: 460, y: 100, label: "End", shape: "circle" },
]);
const [edges, setEdges] = useState<DiagramEdge[]>([
{ id: "e1", source: "start", target: "validate" },
{ id: "e2", source: "start", target: "process" },
{ id: "e3", source: "process", target: "end", label: "done" },
]);
return (
<InteractiveDiagram
nodes={nodes}
edges={edges}
editable
snapToGrid={20}
onNodesChange={setNodes}
onEdgesChange={setEdges}
onNodeDelete={(nodeId, orphanedEdgeIds) => {
setNodes((prev) => prev.filter((n) => n.id !== nodeId));
setEdges((prev) => prev.filter((e) => !orphanedEdgeIds.includes(e.id)));
}}
onEdgeDelete={(edgeId) =>
setEdges((prev) => prev.filter((e) => e.id !== edgeId))
}
/>
);
}Behavior
- Direct coordinate positioning, not the data-domain scale mapping charts use — a node's x/y is its center in a shared coordinate space, and an edge connects to whichever side of a node's shape (rectangle/circle/diamond) faces the other node, computed automatically from their relative positions rather than always the center or a fixed point.
- Curved edges (style: "curved") fan out symmetrically when multiple edges share the same pair of nodes, so parallel request/response-style connections stay visually distinguishable instead of overlapping.
- Node and edge labels render via foreignObject, so they can hold arbitrary React content, not just plain text — worth knowing if you ever rasterize the diagram to canvas, since foreignObject has some cross-browser serialization quirks in that specific scenario.
- Diagram (static) has no client JS and no pan/zoom — use it for a server-rendered preview or documentation. InteractiveDiagram adds click-drag pan on empty canvas (not on a node), mouse-wheel/pinch zoom, and click-to-select with a focus-ring highlight.
- editable (default false) unlocks node dragging (optionally snapped to snapToGrid during the drag itself), connection drawing (hover or select a node to reveal four handles, drag one to another node to connect), and Delete/Backspace on a focused node or edge. With editable off, InteractiveDiagram is pan/zoom/select-only.
- Deleting a node only reports intent: onNodeDelete fires with (nodeId, orphanedEdgeIds) so you can remove the node and its connected edges together in the same update — Diagram doesn't own combined nodes+edges state, so it never removes edges on your own behalf.
- Fully keyboard-operable: Tab to a node, arrow keys nudge its position (10px, or snapToGrid's value), C enters connect mode (Tab/Shift+Tab cycles the prospective target, Enter confirms, Escape cancels), Delete/Backspace removes the focused node or edge.
Props
DiagramNode
| Prop | Type | Default | Description |
|---|---|---|---|
| id | string | — | Stable identity, used for edge source/target references and React keys. |
| x / y | number | — | Center position in the diagram's shared coordinate space. |
| width / height | number | 120 / 48 | Node bounding box. |
| shape | "rectangle" | "circle" | "diamond" | "rectangle" | Determines both the rendered geometry and how edges anchor to its boundary. |
| label | ReactNode | — | Rendered inside a foreignObject, so arbitrary content is fine, not just text. |
| color | string | — | Overrides the auto-assigned series1-8 token color for this node's border. |
DiagramEdge
| Prop | Type | Default | Description |
|---|---|---|---|
| id / source / target | string | — | Required — source/target are DiagramNode ids. |
| label | ReactNode | — | Optional label rendered at the edge's midpoint. |
| style | "straight" | "curved" | "straight" | Curved edges between the same node pair fan out automatically (see behavior above). |
| color | string | — | Overrides the auto-assigned series1-8 token color for this edge's stroke. |
Diagram / InteractiveDiagram
| Prop | Type | Default | Description |
|---|---|---|---|
| nodes / edges | DiagramNode[] / DiagramEdge[] | — | Required on both components. |
| width / height | number | 600 / 400 | Outer SVG pixel size. |
| onNodeClick | (node: DiagramNode) => void | — | InteractiveDiagram only. Fires on a plain click, or a node drag released at its start position. |
| onCanvasClick | () => void | — | InteractiveDiagram only. Fires on an empty-canvas click that isn't a pan drag — also clears selection. |
| editable | boolean | false | Unlocks node dragging, connection drawing, and delete/nudge keyboard shortcuts (see behavior above). |
| onNodesChange / onEdgesChange | (nodes) => void / (edges) => void | — | Controlled, same philosophy as everywhere else in the library — called after a node drag, a keyboard nudge, or a new connection is drawn. |
| snapToGrid | number | — | Grid size in px — a dragged or keyboard-nudged node's position snaps to the nearest multiple during the drag itself, not just on drop. |
| onNodeDelete | (nodeId: string, orphanedEdgeIds: string[]) => void | — | Fires on Delete/Backspace for a focused node. See the orphaned-edges note above — you're responsible for removing both the node and these edges. |
| onEdgeDelete | (edgeId: string) => void | — | Fires on Delete/Backspace for a focused edge. |