Freely draw lines on the canvas
Freehand Drawing on Graph Canvas with SVG Path Nodes
Functional Overview
This example demonstrates a powerful creative feature: the ability to draw freehand sketches directly on the graph canvas that are automatically converted into graph nodes. Users can toggle “Freely Draw” mode, draw arbitrary shapes with the mouse, and the system converts these drawings into SVG path nodes that become part of the graph. This bridges the gap between manual annotation and structured graph data, enabling use cases like diagramming, whiteboarding, and collaborative sketching.
Implementation of Key Features
Custom Canvas Overlay for Freehand Drawing
The SmoothCanvas component provides an overlay for capturing mouse movements:
<SmoothCanvas
lineColor='red'
lineWidth={3}
onUserPathCreate={onUserPathCreate}
onClose={() => { setFreelyDrawMode(false) }}
/>
This component tracks mouse/touch movements and generates SVG path data representing the user’s drawing.
Path-to-Node Conversion Algorithm
The core innovation is converting raw drawing coordinates into graph nodes:
const onUserPathCreate = (path: string, color: string, width: number) => {
// Parse all points in the path to calculate Bounding Box
const coords = path.split(/[ML]/).filter(s => s.trim() !== "").map(pair => {
const [x, y] = pair.trim().split(/\s+/).map(Number);
return { x, y };
});
// Calculate boundaries in viewport coordinate system
let minX = coords[0].x, maxX = coords[0].x;
let minY = coords[0].y, maxY = coords[0].y;
coords.forEach(p => {
if (p.x < minX) minX = p.x;
if (p.x > maxX) maxX = p.x;
if (p.y < minY) minY = p.y;
if (p.y > maxY) maxY = p.y;
});
// Coordinate Transformation: viewport to canvas coordinates
const canvasPos = graphInstance.getCanvasXyByViewXy({ x: minX, y: minY });
const zoom = graphInstance.getOptions().canvasZoom / 100;
const nodeW = Math.max(viewWidth / zoom, 20);
const nodeH = Math.max(viewHeight / zoom, 20);
// Normalize path data to be relative to node's top-left corner
const relativePath = coords.map((p, i) => {
const relX = (p.x - minX) / zoom;
const relY = (p.y - minY) / zoom;
return `${i === 0 ? 'M' : 'L'}${relX.toFixed(1)} ${relY.toFixed(1)}`;
}).join(' ');
};
This algorithm:
- Parses the SVG path string to extract coordinate points
- Calculates the bounding box of the drawing
- Converts viewport coordinates to canvas coordinates using the current zoom level
- Normalizes path coordinates to be relative to the node’s position
Creating SVG Path Nodes
The converted drawing becomes a node with custom SVG rendering:
const newNode = {
id: newNodeId,
type: 'svg-path',
x: canvasPos.x,
y: canvasPos.y,
width: nodeW,
height: nodeH,
text: '',
color: 'transparent',
borderColor: 'transparent',
data: {
path: relativePath,
originalViewBox: `0 0 ${nodeW} ${nodeH}`,
strokeColor: color,
strokeWidth: width
}
};
graphInstance.addNodes([newNode]);
The node stores the normalized SVG path in its data, allowing it to be rendered later.
Custom Node Slot Rendering for SVG Paths
The RGSlotOnNode component conditionally renders SVG paths differently:
<RGSlotOnNode>
{({ node }: RGNodeSlotProps) => {
if (node.type === 'svg-path') {
return (
<div style={{ width: '100%', height: '100%', overflow: 'visible', pointerEvents: 'none' }}>
<svg width="100%" height="100%" style={{ display: 'block' }}>
<path
d={node.data?.path}
fill="none"
stroke={node.data?.strokeColor || '#FF0000'}
strokeWidth={node.data?.strokeWidth || 4}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
);
}
return <div className="rg-node-text"><span>{node.text}</span></div>;
}}
</RGSlotOnNode>
Nodes with type: 'svg-path' render as inline SVG graphics, while regular nodes render text.
Integration with Editing Tools
The drawing feature integrates seamlessly with graph editing capabilities:
<RGSlotOnView>
<RGEditingNodeController>
<RGEditingResize />
<MyNodeToolbar onRemoveNode={onRemoveNode} />
</RGEditingNodeController>
<RGEditingConnectController />
</RGSlotOnView>
Users can select, resize, move, and delete drawn nodes just like regular nodes.
Creative Use Cases
Collaborative Whiteboarding and Diagramming
Create a hybrid whiteboarding tool where users can sketch freehand annotations alongside structured diagram elements. For example, in architecture or engineering, users can draw rough sketches that are automatically captured and integrated with the formal diagram elements.
Educational Note-Taking
Build educational tools where students can highlight or annotate concept maps with freehand drawings. A biology student studying a food web could circle groups of organisms or draw arrows indicating additional relationships beyond the formal links.
Storyboarding and Visual Planning
Use for storyboarding applications where users can quickly sketch scene ideas or character positions. The freehand drawings become first-class elements that can be rearranged, connected, and annotated like other storyboard components.
UX/UI Design Workflows
Implement design tools where UX designers can sketch user flow annotations or usability concerns directly on interaction diagrams. Freehand notes can point to specific UI elements and be treated as part of the design documentation.
Data Annotation and Labeling
Apply to data analysis workflows where analysts need to mark clusters, outliers, or regions of interest. Freehand circles or highlights can group related nodes for presentation or further analysis.
Mind Mapping with Sketches
Create mind mapping tools that allow freehand sketches alongside text nodes. Users can draw icons, symbols, or quick illustrations that represent concepts, making the mind map more expressive and memorable.
Artistic and Creative Visualization
Use for creating artistic visualizations where freehand drawings add organic, human elements to structured data. This could be used in generative art or for creating more engaging data presentations.