Skip to main content

React Flowchart — Build Interactive Flowcharts and Diagrams on Canvas with JavaScript

Build interactive flowcharts and connected diagrams directly on an HTML5 Canvas. Drag nodes around and watch the connecting arrows follow in real time — a common pattern for workflow builders, mind maps, org charts, and visual programming tools. Works with vanilla JavaScript, React (via react-konva), and Vue.

Instructions: Drag any node to reposition it. Connections update automatically. Click "Add Node" to add more.

import Konva from 'konva';

var width = window.innerWidth;
var height = window.innerHeight;

var stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});

var layer = new Konva.Layer();
stage.add(layer);

var container = document.getElementById('container');

var controls = document.createElement('div');
controls.style.cssText = 'margin-bottom: 8px; display: flex; gap: 8px; align-items: center; font: 13px Arial, sans-serif;';

var addBtn = document.createElement('button');
addBtn.textContent = '+ Add Node';
addBtn.style.cssText = 'padding: 6px 14px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; font-weight: 600;';
addBtn.onclick = function() {
var x = 100 + Math.random() * (width - 200);
var y = 80 + Math.random() * (height - 160);
var node = makeNode('New Node', '#2196F3', x, y);
node.moveToTop();
};
controls.appendChild(addBtn);

var hint = document.createElement('span');
hint.textContent = 'Drag nodes to reposition. Arrows follow automatically.';
hint.style.cssText = 'color: #888; font-size: 12px;';
controls.appendChild(hint);

container.parentNode.insertBefore(controls, container);

var nodeList = [];
var arrowList = [];

function makeNode(label, color, x, y) {
var group = new Konva.Group({ x: x, y: y, draggable: true });

var rect = new Konva.Rect({
width: 120, height: 50,
fill: color, cornerRadius: 8,
shadowColor: 'rgba(0,0,0,0.15)', shadowBlur: 6, shadowOffsetY: 2,
offsetX: 60, offsetY: 25,
});

var text = new Konva.Text({
text: label, fontSize: 14, fontFamily: 'Arial',
fill: '#fff', width: 120, height: 50,
align: 'center', verticalAlign: 'middle',
offsetX: 60, offsetY: 25,
});

group.add(rect);
group.add(text);
layer.add(group);

var entry = { group: group };
nodeList.push(entry);

group.on('dragmove', updateArrows);

return group;
}

function connect(from, to) {
var arrow = new Konva.Arrow({
points: [from.x(), from.y(), to.x(), to.y()],
pointerLength: 10, pointerWidth: 8,
fill: '#555', stroke: '#555', strokeWidth: 2,
listening: false,
});
layer.add(arrow);
arrow.moveToBottom();
arrowList.push({ shape: arrow, from: from, to: to });
}

function updateArrows() {
for (var i = 0; i < arrowList.length; i++) {
var a = arrowList[i];
a.shape.points([a.from.x(), a.from.y(), a.to.x(), a.to.y()]);
}
layer.batchDraw();
}

var start = makeNode('Start', '#4CAF50', 100, 120);
var procA = makeNode('Process A', '#2196F3', 300, 80);
var decide = makeNode('Decision', '#FF9800', 500, 120);
var procB = makeNode('Process B', '#2196F3', 300, 260);
var end = makeNode('End', '#f44336', 500, 260);

connect(start, procA);
connect(procA, decide);
connect(decide, procB);
connect(decide, end);
connect(procB, end);

Designing the graph data

The demo above keeps every node in one place and lets each connection store only two node IDs. That normalization matters more than it looks: never copy a node's position into its edges, or every move leaves stale duplicates behind. Read the current node positions when you draw instead.

For a small graph, saving a position on every drag-move event and recalculating the arrows is clear and fast enough. For a large one, move the dragged node directly during the gesture and commit a single document change when the drag ends — one history entry per action, not one per frame.

This example joins the horizontal sides of two rectangles, which reads well for a left-to-right flow. Real editors need explicit ports: store the port ID on the edge and derive its absolute position from the node that owns it. Route orthogonal connectors once positions settle — recalculating a route on every frame is expensive.

If you add pan and zoom, convert pointer positions through the inverse stage transform before any hit or drop test, and keep graph coordinates independent of the camera. See zoom relative to pointer.

For large graphs, draw only visible nodes, use a spatial index for viewport and selection queries, keep connectors below the node shapes, and set listening: false on connectors that never receive pointer input. See performance tips.

Ports, edge selection, cycle rules, automatic layout, history, and persistence are all graph rules. Define each one in the data model before you build its interface.