dataflow-diagram
v0.1.0
Published
React-based DFD / flowchart diagram editor with SVG canvas, draggable shapes, port routing, and an adjustable orthogonal connector with ghost-handle segment editing.
Maintainers
Readme
dataflow-diagram
A React-based DFD / flowchart diagram editor for the browser. Built on a custom SVG renderer with draggable shapes, port-aware connectors, and an adjustable orthogonal connector featuring ghost-handle segment editing (like Syncfusion's orthogonal connector).
- 25+ built-in flowchart shapes (rectangle, diamond, document, database, predefined process, manual input, off-page connectors, summing junction, etc.)
- Multiple connector types: straight, orthogonal (sharp + rounded), wave, polyangled, and a fully adjustable orthogonal connector with mid-segment drag handles
- Auto-routing path that re-stitches when endpoints move; trims itself when the head/tail is dropped back onto the line
- Zoom-aware canvas that grows automatically when shapes are dragged past the visible edge
- Undo/redo, selection, context menu, copy/paste primitives in the engine
- TypeScript types shipped
Installation
npm install dataflow-diagram
# or
pnpm add dataflow-diagram
# or
yarn add dataflow-diagramPeer dependencies (React 18 or 19):
npm install react react-domQuick start
import { useEffect, useMemo, useState } from 'react';
import { DataflowEngine, DataflowCanvas } from 'dataflow-diagram';
import 'dataflow-diagram/styles.css'; // shape/arrow icons + canvas styles
export default function App() {
const [version, setVersion] = useState(0);
const engine = useMemo(
() =>
new DataflowEngine({
onDiagramChanged: () => setVersion(v => v + 1),
}),
[]
);
// Seed a couple of nodes
useEffect(() => {
engine.createNode('rectangle', {
id: 'a', label: 'Start',
x: 80, y: 80, width: 120, height: 60,
ports: ['a-top', 'a-right', 'a-bottom', 'a-left'],
});
engine.createNode('diamond', {
id: 'b', label: 'Decision',
x: 320, y: 80, width: 120, height: 80,
ports: ['b-top', 'b-right', 'b-bottom', 'b-left'],
});
setVersion(v => v + 1);
}, [engine]);
return (
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<DataflowCanvas
engine={engine}
width={800}
height={600}
version={version}
editMode
zoom={1}
/>
</div>
);
}User manual
The canvas
<DataflowCanvas> renders an SVG inside a scrollable host. The host:
- Scrolls with browser-native scrollbars when content overflows.
- Grows beyond
width/heightprops automatically when a node or edge is positioned past the visible edge (with 80 px padding). - Scales content via CSS
transform: scale(zoom); the host dimensions are pre-multiplied so scrollbars appear in lockstep.
Place the canvas inside a parent with explicit dimensions (e.g. height: 600px). The component fills 100% of that parent.
Selecting and editing
- Click a shape or line → selects it. Resize handles appear for nodes.
- Right-click a shape or line → opens a context menu (Delete / Duplicate).
- Click empty canvas → clears selection.
- Delete / Backspace → removes the selected node or edge.
- Set
editMode={false}to render in read-only mode (no drag handles or context menus).
Adding shapes
Either drive it imperatively from your own toolbar, or use the bundled <Toolbox>:
import { Toolbox } from 'dataflow-diagram';
<Toolbox
onAddShape={(type) => engine.createNode(type, { x: 100, y: 100, width: 120, height: 60, ports: [] })}
onAddEdge={(type) => engine.createEdge?.(type, /* ... */)}
/>Built-in shape types: rectangle, circle, diamond, oval, document, inputOutput, predefinedProcess, internalStorage, loopLimit, manualInput, multipleDocuments, storedData, manualLoop, directData, display, database, delay, offPageConnector, preparation, offPageConnectorRight, collate, merge, or, summingJunction, sort.
Connector types
| edge.type | Description |
| --- | --- |
| straight | Plain straight line with arrowhead. |
| OrthogonalRight | Right-then-down L. |
| OrthogonalDown | Down-then-right L. |
| OrthogonalRightRound / OrthogonalDownRound | Same L's with rounded corner. |
| RightwardsWave | Sine wave going right. |
| polyangled | Single V bend. |
| PloigonalArrow | Multi-segment polygonal arrow. |
| adjustable-connector | Auto-routed orthogonal connector with draggable ghost handles at every segment midpoint. Drag a horizontal segment vertically (or a vertical segment horizontally) to bend the path. When you drop the source/target endpoint back onto the connector's own line, the connector auto-trims the redundant waypoints. |
Editing an adjustable connector
- Click the connector to select it. Small blue squares appear at the midpoint of every segment.
- Drag a midpoint:
- On a horizontal segment → drag up/down to move the segment vertically.
- On a vertical segment → drag left/right to move it horizontally.
- To retract the line, grab the source or target endpoint dot and drop it on a segment of the same connector — the bypassed waypoints are dropped.
Zoom
<DataflowCanvas> accepts a zoom prop (default 2). Wire it up to your own zoom UI:
const [zoom, setZoom] = useState(1);
<DataflowCanvas engine={engine} zoom={zoom} ... />
<button onClick={() => setZoom(z => z + 0.1)}>+</button>The scroller and SVG hit-area scale together — drag works correctly at any zoom.
API reference
DataflowEngine
Headless state container. All graph mutations go through it.
const engine = new DataflowEngine({
snapToGrid?: boolean,
gridSize?: number,
onNodeSelected?: (id: string) => void,
onEdgeSelected?: (id: string) => void,
onConnectionCreated?: (edge: Edge) => void,
onDiagramChanged?: (graph: DiagramGraph) => void,
onClickedOutside?: (e: Event) => void,
});| Method | Purpose |
| --- | --- |
| setEditMode(mode) | Toggle read-only mode. |
| getGraph() | Returns the current DiagramGraph. |
| createNode(type, config) | Add a node. |
| updateNode(id, updates) | Mutate a node (e.g. on drag). |
| selectNode(id) / selectEdge(id) | Programmatic selection. |
| deleteNode(id) / deleteEdge(id) | Remove. |
| copyNode(id) / copyEdge(id) | Duplicate with an offset. |
| updateEdge(id, updates) | Mutate an edge (used by drag handlers). |
| clearSelection(e) | Deselect all. |
| undo() / redo() | Time-travel through the debounced history. |
<DataflowCanvas>
| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| engine | DataflowEngine | — | Required. |
| width / height | number | — | Minimum logical canvas dimensions (in SVG units). |
| version | number | — | Bump after engine mutations to force re-render. Wire up onDiagramChanged to setVersion(v => v + 1). |
| editMode | boolean | false | Toggle drag handles + context menus. |
| zoom | number | 2 | CSS scale of the SVG; host grows in lockstep. |
| onNodeSelected | (id: string) => void | — | Called when a node is clicked. |
| onEdgeSelected | (id: string) => void | — | Called when an edge is clicked. |
Type exports
import type {
DiagramGraph,
NodeConfig,
EdgeConfig,
Edge,
AdjustableEdge,
Point,
PortConfig,
LabelProps,
ArrowHeadType,
} from 'dataflow-diagram';Adjustable connector helpers
For programmatic path manipulation outside the canvas:
import {
createAdjustableConnector,
autoRouteOrthogonal,
buildOrthogonalPoints,
getAdjustableConnectorPath,
trimOnSelfHit,
} from 'dataflow-diagram';buildOrthogonalPoints(source, target, userPoints[])— returns the full point list including source/target with auto-inserted bridge corners.autoRouteOrthogonal(s, t)— Z-shape between two points, picks the bend axis based on which delta dominates.getAdjustableConnectorPath(edge)— returns an SVGM…L…path string.trimOnSelfHit(source, target, userPoints, which)— if the moved endpoint lands on the connector's own line, returns the trimmed waypoints (null otherwise).
Custom shapes and edges
Register your own renderer:
import { registerShape, registerEdge } from 'dataflow-diagram';
registerShape('myShape', (node, svgNS) => {
const el = document.createElementNS(svgNS, 'rect');
el.setAttribute('x', String(node.x));
el.setAttribute('y', String(node.y));
el.setAttribute('width', String(node.width));
el.setAttribute('height', String(node.height));
el.setAttribute('fill', node.color || '#ccc');
el.setAttribute('data-id', node.id);
return el;
});
registerEdge('myEdge', (edge, svgNS /*, obstacles */) => {
const line = document.createElementNS(svgNS, 'line');
// ...build your edge here
return line;
});Renderers receive the namespace string ("http://www.w3.org/2000/svg") and must return an SVGElement carrying data-id={node.id} (or the edge id) so drag handlers can hook into it.
Building from source
npm install
npm run build:lib # produces dist/index.js, dist/index.cjs, dist/index.d.ts, dist/styles.css
npm run dev # runs the in-repo demo app on http://localhost:5173The demo lives in src/App.tsx; the publishable library is everything under src/package/.
License
MIT
