Vesture

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.

DiagramInteractiveDiagram

Preview

done
Start
Validate input
Process
End

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

PropTypeDefaultDescription
idstringStable identity, used for edge source/target references and React keys.
x / ynumberCenter position in the diagram's shared coordinate space.
width / heightnumber120 / 48Node bounding box.
shape"rectangle" | "circle" | "diamond""rectangle"Determines both the rendered geometry and how edges anchor to its boundary.
labelReactNodeRendered inside a foreignObject, so arbitrary content is fine, not just text.
colorstringOverrides the auto-assigned series1-8 token color for this node's border.

DiagramEdge

PropTypeDefaultDescription
id / source / targetstringRequired — source/target are DiagramNode ids.
labelReactNodeOptional label rendered at the edge's midpoint.
style"straight" | "curved""straight"Curved edges between the same node pair fan out automatically (see behavior above).
colorstringOverrides the auto-assigned series1-8 token color for this edge's stroke.

Diagram / InteractiveDiagram

PropTypeDefaultDescription
nodes / edgesDiagramNode[] / DiagramEdge[]Required on both components.
width / heightnumber600 / 400Outer SVG pixel size.
onNodeClick(node: DiagramNode) => voidInteractiveDiagram only. Fires on a plain click, or a node drag released at its start position.
onCanvasClick() => voidInteractiveDiagram only. Fires on an empty-canvas click that isn't a pan drag — also clears selection.
editablebooleanfalseUnlocks node dragging, connection drawing, and delete/nudge keyboard shortcuts (see behavior above).
onNodesChange / onEdgesChange(nodes) => void / (edges) => voidControlled, same philosophy as everywhere else in the library — called after a node drag, a keyboard nudge, or a new connection is drawn.
snapToGridnumberGrid 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[]) => voidFires 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) => voidFires on Delete/Backspace for a focused edge.