A lightweight flowchart library for creating interactive flowcharts
npm install @thihaeung/flowchart-libA lightweight, zero-dependency flowchart library for creating interactive flowcharts with Canvas. Perfect for Electron, Vue, React, and vanilla JavaScript applications.
- đ¨ Simple and Intuitive API - Easy to learn and use
- đ Zero Dependencies - No external libraries required (jsPDF optional for PDF export)
- đĻ Multiple Build Formats - UMD, CommonJS, and ES Module
- đģ Framework Agnostic - Works with Electron, Vue, React, Angular, and vanilla JS
- đ¯ TypeScript Support - Full type definitions included
- đąī¸ Interactive - Drag, connect, and edit nodes with mouse and keyboard
- đž Import/Export - JSON, PNG, and PDF export capabilities
- âŠī¸ Undo/Redo - Full history management
- đą Responsive - Auto-sizing canvas support
- đ¨ High-DPI Ready - Crisp rendering on Retina and 4K displays
---
- Installation
- Quick Start
- API Reference
- Framework Integration
- Electron
- Vue.js
- React
- Configuration Options
- Methods
- Events
- Advanced Usage
- TypeScript
- Publishing Updates
- License
---
``bash`
npm install @thihaeung/flowchart-lib
`html
`
1. Download the latest release from GitHub Releases
2. Include the appropriate build file:
`html
`
Required:
- Modern browser with Canvas API support
- ES6+ JavaScript environment
Optional:
- jsPDF library (for PDF export functionality)
`html`
---
`html`
My Flowchart
`javascript
import { Canvas, Node, Connection } from '@thihaeung/flowchart-lib';
const flowchart = new Canvas('canvas-container', {
mode: 'edit',
pixelRatio: window.devicePixelRatio
});
// Add your nodes and connections
const start = flowchart.addNode('start', 'Begin', 100, 100);
const process = flowchart.addNode('process', 'Process', 300, 100);
flowchart.addConnection(start, 'right', process, 'left');
`
`javascript
const { Canvas, Node, Connection } = require('@thihaeung/flowchart-lib');
const flowchart = new Canvas('canvas-container');
flowchart.addNode('start', 'Start', 100, 100);
`
---
`javascript`
new Canvas(containerId, options)
Parameters:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| containerId | string | Yes | ID of the HTML element to contain the canvas |options
| | object | No | Configuration options |
Options Object:
`javascript`
{
mode: 'edit', // 'edit' or 'view'
width: null, // Canvas width in pixels (null = auto)
height: null, // Canvas height in pixels (null = auto)
readonly: false, // Disable all interactions
pixelRatio: 2 // Device pixel ratio for high-DPI displays
}
---
#### Edit Mode (default)
`javascript`
const flowchart = new Canvas('container', {
mode: 'edit'
});
- Full editing capabilities
- Users can add, move, delete, and connect nodes
- All keyboard shortcuts enabled
#### View Mode
`javascript`
const flowchart = new Canvas('container', {
mode: 'view'
});
- Display only, no editing
- Good for presentations or read-only displays
#### Readonly Mode
`javascript`
const flowchart = new Canvas('container', {
readonly: true
});
- No mouse or keyboard interactions
- Canvas is completely static
For fullscreen or responsive layouts:
`javascript`
const flowchart = new Canvas('container', {
width: null, // Auto-size to container width
height: null // Auto-size to container height
});
`css`
#container {
width: 100%;
height: 100vh; / or any size /
}
For crisp rendering on Retina and 4K displays:
`javascript`
const flowchart = new Canvas('container', {
pixelRatio: window.devicePixelRatio || 2
});
---
#### addNode(type, text, x, y)
Add a new node to the canvas.
`javascript`
const node = flowchart.addNode('process', 'My Process', 200, 150);
Parameters:
- type (string): Node type - 'start', 'process', 'decision', or 'end'text
- (string): Text label for the nodex
- (number): X coordinatey
- (number): Y coordinate
Returns: Node object
Node Types:
- start - Green oval (entry point)process
- - Blue rectangle (action/task)decision
- - Yellow diamond (conditional)end
- - Red oval (termination)
#### deleteNode(node)
Remove a node and all its connections.
`javascript`
flowchart.deleteNode(node);
Parameters:
- node (Node): Node object to delete
#### addConnection(fromNode, fromPort, toNode, toPort)
Create a connection between two nodes.
`javascript`
flowchart.addConnection(node1, 'right', node2, 'left');
Parameters:
- fromNode (Node): Source nodefromPort
- (string): Port position - 'top', 'right', 'bottom', 'left'toNode
- (Node): Target nodetoPort
- (string): Port position - 'top', 'right', 'bottom', 'left'
Returns: Connection object or null if connection already exists
#### deleteConnection(connection)
Remove a connection.
`javascript`
flowchart.deleteConnection(connection);
#### clear()
Clear all nodes and connections.
`javascript`
flowchart.clear();
#### render()
Manually trigger a canvas re-render.
`javascript`
flowchart.render();
#### undo()
Undo the last action.
`javascript`
flowchart.undo();
Keyboard shortcut: Ctrl+Z or Cmd+Z
#### redo()
Redo an undone action.
`javascript`
flowchart.redo();
Keyboard shortcut: Ctrl+Y or Cmd+Shift+Z
#### exportToJSON()
Export flowchart data as JSON string.
`javascript
const jsonData = flowchart.exportToJSON();
console.log(jsonData);
// Save to file
const blob = new Blob([jsonData], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'flowchart.json';
a.click();
`
Returns: JSON string
#### importFromJSON(jsonString)
Import flowchart from JSON string.
`javascript`
const jsonData = '{"nodes":[...],"connections":[...]}';
flowchart.importFromJSON(jsonData);
Parameters:
- jsonString (string): JSON string in the correct format
#### exportToPNG()
Export as PNG image data URL.
`javascript
const pngUrl = flowchart.exportToPNG();
// Download PNG
const a = document.createElement('a');
a.href = pngUrl;
a.download = 'flowchart.png';
a.click();
`
Returns: Data URL string
#### exportToPDF()
Export as PDF document (requires jsPDF).
`javascript`
// Make sure jsPDF is included first
const pdf = flowchart.exportToPDF();
pdf.save('flowchart.pdf');
Returns: jsPDF object
#### destroy()
Clean up event listeners and remove canvas.
`javascript`
// Always call destroy when removing the flowchart
flowchart.destroy();
---
Installation:
`bash`
npm install @thihaeung/flowchart-lib
Main Process (main.js):
`javascript
const { app, BrowserWindow } = require('electron');
const path = require('path');
function createWindow() {
const win = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
});
win.loadFile('index.html');
}
app.whenReady().then(createWindow);
`
Renderer Process (index.html):
`html
`
package.json for Electron:
`json`
{
"name": "flowchart-electron-app",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"dependencies": {
"@thihaeung/flowchart-lib": "^1.0.0"
},
"devDependencies": {
"electron": "^25.0.0"
}
}
---
Installation:
`bash`
npm install @thihaeung/flowchart-lib
Single File Component:
`vue
`
Vue 3 Composition API:
`vue
`
---
Installation:
`bash`
npm install @thihaeung/flowchart-lib
Function Component with Hooks:
`jsx
import React, { useEffect, useRef, useState } from 'react';
import { Canvas } from '@thihaeung/flowchart-lib';
import './FlowchartEditor.css';
function FlowchartEditor() {
const canvasRef = useRef(null);
const flowchartRef = useRef(null);
const [nodeCounter, setNodeCounter] = useState(0);
useEffect(() => {
// Initialize flowchart
flowchartRef.current = new Canvas('flowchart-canvas', {
mode: 'edit',
pixelRatio: window.devicePixelRatio || 2
});
// Cleanup on unmount
return () => {
if (flowchartRef.current) {
flowchartRef.current.destroy();
}
};
}, []);
const addNode = (type) => {
if (flowchartRef.current) {
const x = 100 + Math.random() * 300;
const y = 100 + Math.random() * 200;
const newCounter = nodeCounter + 1;
flowchartRef.current.addNode(type, ${type} ${newCounter}, x, y);
setNodeCounter(newCounter);
}
};
const undo = () => {
flowchartRef.current?.undo();
};
const redo = () => {
flowchartRef.current?.redo();
};
const clear = () => {
if (window.confirm('Clear all nodes and connections?')) {
flowchartRef.current?.clear();
}
};
const exportJSON = () => {
if (flowchartRef.current) {
const data = flowchartRef.current.exportToJSON();
const blob = new Blob([data], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'flowchart.json';
a.click();
URL.revokeObjectURL(url);
}
};
const exportPNG = () => {
if (flowchartRef.current) {
const dataUrl = flowchartRef.current.exportToPNG();
const a = document.createElement('a');
a.href = dataUrl;
a.download = 'flowchart.png';
a.click();
}
};
return (
export default FlowchartEditor;
`
CSS (FlowchartEditor.css):
`css
.flowchart-editor {
display: flex;
flex-direction: column;
height: 100vh;
}
.toolbar {
background: #f5f5f5;
padding: 10px;
border-bottom: 1px solid #ddd;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.toolbar button {
padding: 8px 16px;
background: #2196F3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background 0.2s;
}
.toolbar button:hover {
background: #1976D2;
}
#flowchart-canvas {
flex: 1;
position: relative;
}
`
---
The library doesn't currently expose custom events, but you can monitor state changes through methods:
`javascript
class FlowchartWithEvents extends Canvas {
constructor(containerId, options) {
super(containerId, options);
this.listeners = {};
}
on(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
}
emit(event, data) {
if (this.listeners[event]) {
this.listeners[event].forEach(callback => callback(data));
}
}
addNode(...args) {
const node = super.addNode(...args);
this.emit('nodeAdded', node);
return node;
}
deleteNode(node) {
super.deleteNode(node);
this.emit('nodeDeleted', node);
}
addConnection(...args) {
const conn = super.addConnection(...args);
if (conn) this.emit('connectionAdded', conn);
return conn;
}
deleteConnection(conn) {
super.deleteConnection(conn);
this.emit('connectionDeleted', conn);
}
}
// Usage
const flowchart = new FlowchartWithEvents('container');
flowchart.on('nodeAdded', (node) => {
console.log('Node added:', node);
});
flowchart.on('nodeDeleted', (node) => {
console.log('Node deleted:', node);
});
flowchart.on('connectionAdded', (conn) => {
console.log('Connection added:', conn);
});
`
---
`javascript
function createWorkflowFlowchart(canvas) {
// Create nodes
const nodes = {
start: canvas.addNode('start', 'Start Process', 100, 100),
validate: canvas.addNode('decision', 'Valid Input?', 300, 100),
process: canvas.addNode('process', 'Process Data', 500, 100),
error: canvas.addNode('process', 'Show Error', 300, 250),
save: canvas.addNode('process', 'Save Result', 700, 100),
end: canvas.addNode('end', 'End', 700, 250)
};
// Create connections
canvas.addConnection(nodes.start, 'right', nodes.validate, 'left');
canvas.addConnection(nodes.validate, 'right', nodes.process, 'left');
canvas.addConnection(nodes.validate, 'bottom', nodes.error, 'top');
canvas.addConnection(nodes.process, 'right', nodes.save, 'left');
canvas.addConnection(nodes.save, 'bottom', nodes.end, 'top');
canvas.addConnection(nodes.error, 'right', nodes.end, 'left');
return nodes;
}
// Usage
const flowchart = new Canvas('container');
const nodes = createWorkflowFlowchart(flowchart);
`
`javascript
class AutoSaveFlowchart {
constructor(containerId, storageKey = 'flowchart-data') {
this.canvas = new Canvas(containerId);
this.storageKey = storageKey;
this.saveTimeout = null;
// Load existing data
this.load();
// Intercept state-changing methods
this.interceptMethods();
}
interceptMethods() {
const methods = ['addNode', 'deleteNode', 'addConnection', 'deleteConnection'];
methods.forEach(method => {
const original = this.canvas[method].bind(this.canvas);
this.canvas[method] = (...args) => {
const result = original(...args);
this.scheduleSave();
return result;
};
});
}
scheduleSave() {
clearTimeout(this.saveTimeout);
this.saveTimeout = setTimeout(() => {
this.save();
}, 1000); // Save after 1 second of inactivity
}
save() {
const data = this.canvas.exportToJSON();
localStorage.setItem(this.storageKey, data);
console.log('Flowchart auto-saved');
}
load() {
const data = localStorage.getItem(this.storageKey);
if (data) {
try {
this.canvas.importFromJSON(data);
console.log('Flowchart loaded');
} catch (e) {
console.error('Failed to load flowchart:', e);
}
}
}
clear() {
localStorage.removeItem(this.storageKey);
this.canvas.clear();
}
}
// Usage
const flowchart = new AutoSaveFlowchart('container', 'my-flowchart');
`
`javascript`
// Modify this in your Node.js source file
getColors() {
const colors = {
'start': { fill: '#4CAF50', border: '#388E3C' },
'process': { fill: '#2196F3', border: '#1976D2' },
'decision': { fill: '#FFC107', border: '#FFA000' },
'end': { fill: '#F44336', border: '#D32F2F' },
// Add custom node types
'database': { fill: '#9C27B0', border: '#7B1FA2' },
'api': { fill: '#00BCD4', border: '#0097A7' },
'user': { fill: '#FF9800', border: '#F57C00' }
};
return colors[this.type] || colors['process'];
}
The library includes built-in keyboard shortcuts:
- Delete - Delete selected node
- Ctrl/Cmd + Z - Undo
- Ctrl/Cmd + Y or Ctrl/Cmd + Shift + Z - Redo
- Escape - Deselect node
`javascriptNode "${node.text}" is not connected
function validateFlowchart(canvas) {
const errors = [];
// Check if there's a start node
const hasStart = canvas.nodes.some(node => node.type === 'start');
if (!hasStart) {
errors.push('Flowchart must have a start node');
}
// Check if there's an end node
const hasEnd = canvas.nodes.some(node => node.type === 'end');
if (!hasEnd) {
errors.push('Flowchart must have an end node');
}
// Check for disconnected nodes
canvas.nodes.forEach(node => {
const hasConnections = canvas.connections.some(
conn => conn.fromNode === node || conn.toNode === node
);
if (!hasConnections && canvas.nodes.length > 1) {
errors.push();
}
});
return {
valid: errors.length === 0,
errors
};
}
// Usage
const validation = validateFlowchart(flowchart);
if (!validation.valid) {
console.error('Validation errors:', validation.errors);
alert('Flowchart has errors:\n' + validation.errors.join('\n'));
}
`
---
The library includes TypeScript definitions. Create or use the included flowchart-lib.d.ts:
`typescript
declare module '@thihaeung/flowchart-lib' {
export interface CanvasOptions {
mode?: 'edit' | 'view';
width?: number | null;
height?: number | null;
readonly?: boolean;
pixelRatio?: number;
}
export interface Node {
id: string;
type: 'start' | 'process' | 'decision' | 'end';
x: number;
y: number;
text: string;
width: number;
height: number;
}
export interface Connection {
id: string;
fromNode: Node;
fromPort: 'top' | 'right' | 'bottom' | 'left';
toNode: Node;
toPort: 'top' | 'right' | 'bottom' | 'left';
}
export class Canvas {
constructor(containerId: string, options?: CanvasOptions);
nodes: Node[];
connections: Connection[];
addNode(type: string, text: string, x: number, y: number): Node;
deleteNode(node: Node): void;
addConnection(
fromNode: Node,
fromPort: string,
toNode: Node,
toPort: string
): Connection | null;
deleteConnection(connection: Connection): void;
clear(): void;
render(): void;
undo(): void;
redo(): void;
exportToJSON(): string;
importFromJSON(jsonString: string): void;
exportToPNG(): string;
exportToPDF(): any;
destroy(): void;
}
}
`
`typescript
import { Canvas, Node, Connection, CanvasOptions } from '@thihaeung/flowchart-lib';
const options: CanvasOptions = {
mode: 'edit',
pixelRatio: window.devicePixelRatio || 2
};
const flowchart = new Canvas('canvas-container', options);
const startNode: Node = flowchart.addNode('start', 'Begin', 100, 100);
const processNode: Node = flowchart.addNode('process', 'Process', 300, 100);
const connection: Connection | null = flowchart.addConnection(
startNode,
'right',
processNode,
'left'
);
`
---
When you make changes to the library and want to publish an update:
Edit package.json and bump the version following Semantic Versioning:
`json`
{
"version": "1.0.1" // Patch: bug fixes
"version": "1.1.0" // Minor: new features (backward compatible)
"version": "2.0.0" // Major: breaking changes
}
`bash`
npm run build
`bash`
npm packThis creates a .tgz file you can test in another project
`bashMake sure you're logged in
npm login
$3
`bash
git tag v1.0.1
git push origin v1.0.1
`---
đ Performance Tips
1. Use pixelRatio wisely - Higher values = better quality but slower rendering
`javascript
// For retina displays
pixelRatio: window.devicePixelRatio || 2
// For better performance on lower-end devices
pixelRatio: 1
`2. Limit history size - Modify history length if memory is a concern (default is 50)
3. Batch operations - Add multiple nodes before connecting them
`javascript
// Good
const node1 = flowchart.addNode('start', 'Start', 100, 100);
const node2 = flowchart.addNode('process', 'Process', 300, 100);
flowchart.addConnection(node1, 'right', node2, 'left');
// Avoid calling render() manually unless necessary
`4. Avoid frequent re-renders - The library auto-renders, don't call
render() unnecessarily---
đ Security Considerations
- The library does not use
eval() or execute user-provided code
- Sanitize any JSON imports from untrusted sources
- Canvas exports are safe for download
- No external API calls are made
- All rendering happens client-side---
đ Troubleshooting
$3
- Ensure the container element exists before initializing
- Check that the container has width and height
- Verify the library is properly imported$3
- Check if mode is set to 'edit'
- Verify readonly is not set to true$3
- For PDF export, ensure jsPDF is loaded
- Check browser console for errors$3
- Lower the pixelRatio value
- Reduce the number of nodes on canvas
- Consider using view mode for read-only displays---
đ License
MIT License - See LICENSE file for details
Copyright (c) 2025 Your Name
---
đ¤ Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch (
git checkout -b feature/amazing-feature)
3. Commit your changes (git commit -m 'Add amazing feature')
4. Push to the branch (git push origin feature/amazing-feature`)---
For issues, questions, or feature requests:
- GitHub Issues: https://github.com/Morrowga/flowchart-lib/issues
- Email: your.email@example.com
- Documentation: https://github.com/Morrowga/flowchart-lib#readme
---
Built with â¤ī¸ for developers who need simple, powerful flowchart capabilities.
---
---
Happy Flowcharting! đ