@falkordb/canvas
v0.2.5
Published
A standalone web component for visualizing FalkorDB graphs using force-directed layouts
Downloads
2,275
Maintainers
Readme
FalkorDB Canvas
A standalone web component for visualizing FalkorDB graphs using force-directed layouts.
Features
- 🎨 Force-directed graph layout - Automatic positioning using D3 force simulation with smart collision detection
- 🧭 Multiple layout modes - Switch between
force,tree, andradialgraph views - 🎯 Interactive - Click, hover, right-click interactions on nodes, links, and background
- 🌓 Theme support - Light and dark mode compatible with customizable colors
- ⚡ Performance - Optimized rendering with HTML5 canvas, including viewport culling and low-zoom draw skipping for large graphs
- 💫 Loading states - Built-in skeleton loading with pulse animation
- 🎨 Customizable - Colors, sizes, behaviors, and custom rendering functions
- 📦 TypeScript support - Full type definitions included
- 🔧 Web Component - Works with any framework or vanilla JavaScript
- 🎮 Viewport control - Zoom, pan, and auto-fit functionality
- 🔄 Smart layout - Adaptive force algorithm based on node connectivity
Installation
npm install @falkordb/canvasQuick Start
Vanilla JavaScript
<!DOCTYPE html>
<html>
<head>
<title>FalkorDB Canvas Example</title>
</head>
<body>
<falkordb-canvas id="graph" style="width: 100%; height: 600px;"></falkordb-canvas>
<script type="module">
import '@falkordb/canvas';
const canvas = document.getElementById('graph');
// Set data
canvas.setData({
nodes: [
{ id: 1, labels: ['Person'], color: '#FF6B6B', visible: true, data: { name: 'Alice' } },
{ id: 2, labels: ['Person'], color: '#4ECDC4', visible: true, data: { name: 'Bob' } }
],
links: [
{ id: 1, relationship: 'KNOWS', color: '#999', source: 1, target: 2, visible: true, data: {} }
]
});
// Configure
canvas.setConfig({
width: 800,
height: 600,
backgroundColor: '#FFFFFF',
foregroundColor: '#1A1A1A',
eventHandlers: {
onNodeClick: (node) => console.log('Clicked:', node),
},
});
</script>
</body>
</html>React / TypeScript
import { useEffect, useRef } from 'react';
import '@falkordb/canvas';
import type { FalkorDBCanvas, Data, GraphNode } from '@falkordb/canvas';
function GraphVisualization() {
const canvasRef = useRef<FalkorDBCanvas>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const data: Data = {
nodes: [
{ id: 1, labels: ['Person'], color: '#FF6B6B', visible: true, data: { name: 'Alice' } },
{ id: 2, labels: ['Person'], color: '#4ECDC4', visible: true, data: { name: 'Bob' } }
],
links: [
{ id: 1, relationship: 'KNOWS', color: '#999', source: 1, target: 2, visible: true, data: {} }
]
};
canvas.setData(data);
canvas.setConfig({
eventHandlers: {
onNodeClick: (node: GraphNode) => {
console.log('Clicked node:', node);
},
},
});
}, []);
return (
<falkordb-canvas
ref={canvasRef}
style={{ width: '100%', height: '600px' }}
/>
);
}API
Methods
| Method | Default | Description |
|--------|---------|-------------|
| setData(data) | | Set the graph data (nodes and links). Automatically triggers layout simulation and loading states. |
| getData() | | Get the current graph data in the simplified format. |
| setGraphData(data) | | Set graph data in the internal format (with computed properties). Use this for better performance when you already have GraphData format. |
| getGraphData() | | Get the current graph data in the internal format with all computed properties (x, y, vx, vy, etc.). |
| setConfig(config) | | Configure the graph visualization and behavior. Accepts a ForceGraphConfig object with styling, callbacks, and rendering options. |
| setWidth(width) | | Set canvas width in pixels. |
| setHeight(height) | | Set canvas height in pixels. |
| setBackgroundColor(color) | | Set background color (hex or CSS color). |
| setForegroundColor(color) | | Set foreground color for text and borders. |
| setAnimation(enabled) | | Enable or disable force simulation animation. When disabled, pins all nodes in place. |
| setPinOnDragEnd(pin) | | Enable or disable pinning nodes after dragging. |
| setLayout(layoutMode) | 'force' | Switch layout mode: 'force' | 'tree' | 'radial'. |
| setLayoutOptions(options) | | Update per-layout options (tree, radial, force). Triggers re-layout. |
| setDebug(enabled) | false | Enable or disable debug logging to console. All log messages are prefixed with [FalkorDBCanvas]. |
| refresh() | | Trigger a repaint after in-place property mutations (visibility, color, size). Recomputes positions for deterministic layouts. |
| getViewport() | | Get current zoom and center position as ViewportState. |
| setViewport(viewport) | | Restore a previously saved viewport state. |
| getZoom() | | Get current zoom level. |
| zoom(zoomLevel) | | Set zoom level. |
| zoomToFit(paddingMultiplier, filter) | 1.0, undefined | Auto-fit all visible nodes in view. Optional padding multiplier and node filter function. |
| getGraph() | | Get the underlying force-graph instance for advanced control. |
| getCullingStats() | | Get viewport culling statistics (bounds, visible vs total node/link counts). |
Configuration Options
Configuration is passed to setConfig() as a ForceGraphConfig object. It is organized into sub-configs:
Top-Level Options
| Option | Default | Description |
|--------|---------|-------------|
| width | <window width> | Canvas width in pixels |
| height | <window height> | Canvas height in pixels |
| backgroundColor | '#FFFFFF' | Background color (hex or CSS color) |
| foregroundColor | '#1A1A1A' | Foreground color for borders and text |
| layoutMode | 'force' | Layout algorithm: 'force' | 'tree' | 'radial' |
| layoutOptions | {} | Per-layout options (see Layout Modes) |
| animation | | Enable/disable layout animation |
| captionsKeys | [] | Node property keys to display as labels |
| showPropertyKeyPrefix | false | Show property key prefix in node labels |
| pinOnDragEnd | false | Pin nodes after dragging |
| isNodeSelected | | Function: (node: GraphNode) => boolean |
| isLinkSelected | | Function: (link: GraphLink) => boolean |
| linkLineDash | | Function: (link: GraphLink) => number[] |
| node | | Custom node rendering (see Custom Rendering) |
| link | | Custom link rendering (see Custom Rendering) |
| largeGraph | | Large-graph optimizations (see Large-Graph Optimizations) |
nodeStyle — Node Visual Style
| Option | Default | Description |
|--------|---------|-------------|
| fontFamily | 'SofiaSans' | Font family for node labels |
| fontWeightUnselected | 400 | Font weight when not selected |
| fontWeightSelected | 700 | Font weight when selected |
| fontSize | 2 | Fixed font size (world units). Used when textFillRatio is 0 or unset |
| textFillRatio | 0.85 | Auto-size: fraction of node radius that text fills (0–1). Takes precedence over fontSize |
| strokeWidthSelected | 1 | Border width when selected |
| strokeWidthUnselected | 0.5 | Border width when not selected |
| glowDuration | 10000 | Glow effect duration (ms) after expand/collapse |
| glowSpread | 12 | Glow spread radius (px) |
| glowSteps | 16 | Number of gradient rings in glow |
| glowColor | [59, 130, 246] | Glow color as [r, g, b] |
| glowMaxOpacity | 0.6 | Maximum glow opacity (0–1) |
linkStyle — Link/Edge Visual Style
| Option | Default | Description |
|--------|---------|-------------|
| fontFamily | 'SofiaSans' | Font family for link labels |
| fontSize | 2 | Font size for link labels (world units) |
| fontWeightUnselected | 400 | Font weight when not selected |
| fontWeightSelected | 700 | Font weight when selected |
| lineWidthSelected | 2 | Line width when selected |
| lineWidthUnselected | 1 | Line width when not selected |
| arrowLengthSelected | 16 | Arrow length when selected |
| arrowLengthUnselected | 8 | Arrow length when not selected |
| arrowWidthRatio | 1.6 | Arrow width-to-height ratio |
| arrowNotchRatio | 0.2 | Arrow notch depth ratio |
| selfLoopCurveFactor | 11.67 | Self-loop curve factor |
| parallelEdgeCurveMultiplier | 0.4 | Parallel edge curve multiplier |
| labelBackgroundPadding | 0.3 | Label background padding (world units) |
| edgeGap | 2 | Gap between edge tip and visible node border (px) |
simulation — Force Simulation Tuning
| Option | Default | Description |
|--------|---------|-------------|
| centerStrength | 0.03 | Center force strength (X and Y) |
| chargeStrength | -400 | Charge repulsion strength |
| velocityDecay | 0.4 | Velocity decay (damping) |
| alphaMin | 0.05 | Alpha min (convergence threshold) |
| warmupTicks | 300 | Warmup ticks for force simulation |
interaction — Interaction / UX Parameters
| Option | Default | Description |
|--------|---------|-------------|
| tooltipFontSize | 12 | Tooltip font size (px) |
| tooltipPadding | '4px 8px' | Tooltip padding CSS value |
| tooltipBorderRadius | '4px' | Tooltip border radius CSS value |
| tooltipZIndex | 1000 | Tooltip z-index |
| zoomToFitPadding | 0.1 | Zoom-to-fit padding as fraction of smallest dimension |
| zoomToFitDelay | 50 | Delay (ms) before zoom-to-fit after layout change |
| linkHitWidth | 10 | Link pointer hit-test width (screen px) |
| contrastThreshold | 0.5 | Luminance threshold for text color contrast |
eventHandlers — Event Callbacks
| Option | Description |
|--------|-------------|
| onNodeClick | (node: GraphNode, event: MouseEvent) => void |
| onNodeRightClick | (node: GraphNode, event: MouseEvent) => void |
| onLinkClick | (link: GraphLink, event: MouseEvent) => void |
| onLinkRightClick | (link: GraphLink, event: MouseEvent) => void |
| onNodeHover | (node: GraphNode \| null) => void |
| onNodeDragEnd | (node: GraphNode) => void |
| onPinChange | (pinned: boolean) => void |
| onLinkHover | (link: GraphLink \| null) => void |
| onBackgroundClick | (event: MouseEvent) => void |
| onBackgroundRightClick | (event: MouseEvent) => void |
| onZoom | (transform: Transform) => void |
| onEngineStop | () => void |
| onLayoutChange | (layout: LayoutMode) => void |
Layout Modes
Use layoutMode in setConfig to choose the graph view style:
canvas.setConfig({
layoutMode: 'tree',
layoutOptions: {
tree: {
direction: 'lr', // 'lr' | 'rl' | 'td' | 'bu'
levelDistance: 180,
nodeSpacing: 110
}
}
});Radial example:
canvas.setConfig({
layoutMode: 'radial',
layoutOptions: {
radial: {
direction: 'out', // 'out' | 'in'
levelDistance: 130
}
}
});Notes:
forcekeeps simulation enabled with configurable physics (seesimulationconfig).tree:direction('td'|'bu'|'lr'|'rl'),levelDistance,nodeSpacing.radial:direction('out'|'in'),levelDistance,nodeSpacing.
Data Types
Node
| Property | Default | Description |
|----------|---------|-------------|
| id | required | Unique identifier for the node |
| labels | required | Array of label names for the node |
| color | required | Node color (hex or CSS color) |
| visible | required | Whether the node is visible |
| size | 9 | Node radius (world units) |
| caption | 'id' | Property key to use from the data for display text |
| data | required | Node properties as key-value pairs |
Link
| Property | Default | Description |
|----------|---------|-------------|
| id | required | Unique identifier for the link |
| relationship | required | Label displayed on the link |
| color | required | Link color (hex or CSS color) |
| source | required | Source node ID |
| target | required | Target node ID |
| visible | required | Whether the link is visible |
| data | required | Link properties as key-value pairs |
GraphNode
Internal format with computed properties:
{
...Node;
size: number; // Always present (defaults to 9)
displayName: [string, string]; // Computed text lines
x?: number; // Position from simulation
y?: number;
vx?: number; // Velocity
vy?: number;
fx?: number; // Fixed position
fy?: number;
}GraphLink
Internal format with resolved node references:
{
...Link;
source: GraphNode; // Resolved node object
target: GraphNode; // Resolved node object
curve: number; // Computed curvature for rendering
}ViewportState
{
zoom: number;
centerX: number;
centerY: number;
} | undefinedTransform
{
k: number; // zoom scale
x: number; // pan x
y: number; // pan y
}Custom Rendering
You can provide custom rendering functions for nodes and links:
canvas.setConfig({
node: {
nodeCanvasObject: (node: GraphNode, ctx: CanvasRenderingContext2D) => {
// Custom node drawing logic
ctx.fillStyle = node.color;
ctx.fillRect(node.x! - 5, node.y! - 5, 10, 10);
},
nodePointerAreaPaint: (node: GraphNode, color: string, ctx: CanvasRenderingContext2D) => {
// Define clickable area
ctx.fillStyle = color;
ctx.fillRect(node.x! - 5, node.y! - 5, 10, 10);
}
},
link: {
linkCanvasObject: (link: GraphLink, ctx: CanvasRenderingContext2D) => {
// Custom link drawing logic
},
linkPointerAreaPaint: (link: GraphLink, color: string, ctx: CanvasRenderingContext2D) => {
// Define clickable area for link
}
}
});Utility Functions
The package exports utility functions for data manipulation:
import {
dataToGraphData,
graphDataToData,
getNodeDisplayText,
getNodeDisplayKey,
wrapTextForCircularNode
} from '@falkordb/canvas';
// Convert between formats
const graphData = dataToGraphData(data);
const data = graphDataToData(graphData);
// Get display text for a node
const text = getNodeDisplayText(node); // Returns node.data[caption] or defaults to id
// Wrap text for circular nodes
const [line1, line2] = wrapTextForCircularNode(ctx, text, radius);Development
# Install dependencies
npm install
# Build (TypeScript compilation)
npm run build
# Watch mode (auto-rebuild on changes)
npm run dev
# Run example server
npm run example
# Then open http://localhost:8080/examples/falkordb-canvas.example.html
# Lint code
npm run lint
# Clean build artifacts
npm run cleanWeb Component Attributes
The component supports HTML attributes for render modes:
<falkordb-canvas
node-mode="replace" <!-- 'before' | 'after' | 'replace' -->
link-mode="after"> <!-- 'before' | 'after' | 'replace' -->
</falkordb-canvas>replace(default for nodes): Uses custom rendering exclusivelybefore: Renders custom content before default renderingafter(default for links): Renders custom content after default rendering
Browser Support
- Chrome/Edge (latest)
- Firefox (latest)
- Safari (latest)
Requires support for:
- Web Components (Custom Elements)
- ES Modules
- Shadow DOM
- HTML5 Canvas
Testing & Automation
Engine Status Indicator
The canvas element inside the web component's shadow DOM exposes a data-engine-status attribute that indicates whether the force simulation is currently running or stopped. This is useful for automated testing to wait for the canvas to finish animating.
Values:
"running"- Force simulation is actively running"stopped"- Force simulation has stopped (animation complete)
Example usage with Playwright:
// Wait for canvas to be ready
const canvasElement = page.locator("falkordb-canvas").locator("canvas").first();
await canvasElement.waitFor({ state: "attached" });
// Poll until animation completes
while (true) {
const status = await canvasElement.getAttribute("data-engine-status");
if (status === "stopped") break;
await page.waitForTimeout(500);
}Note: The attribute is set on the <canvas> element within the shadow DOM, not on the <falkordb-canvas> web component itself.
Performance Tips
- Large graphs: Disable animation (
setAnimation(false)) once the layout stabilizes - Static graphs: Use a deterministic layout (
setLayout('tree')) to avoid simulation overhead - Custom rendering: Optimize your custom
nodeCanvasObjectandlinkCanvasObjectfunctions - Viewport: Use
getViewport()andsetViewport()to preserve user's view when updating data - Very large graphs: Enable viewport culling via the
largeGraphoption (see below)
Large-Graph Optimizations
For graphs with thousands of nodes and links, enable the built-in viewport culling and low-zoom draw-skipping optimizations via the largeGraph configuration option.
Note: These optimizations are applied by the default renderer. If you provide custom
nodeCanvasObject/linkCanvasObjectcallbacks, the library will not cull or skip drawing for those elements automatically. Implement equivalent viewport culling and low-zoom checks in your custom renderer if needed.
canvas.setConfig({
largeGraph: {
enabled: true, // master switch (true by default)
viewportPadding: 50, // world-unit padding around the visible viewport (default: 0)
lowZoomThreshold: 0.5, // zoom level below which expensive details are skipped
skipLabelsAtLowZoom: true, // skip node labels at low zoom (default: true)
skipArrowsAtLowZoom: true, // skip link arrowheads at low zoom (default: true)
skipLinkLabelsAtLowZoom: true, // skip link relationship labels at low zoom (default: true)
}
});How it works
Viewport culling – before each node or link is drawn, the renderer checks whether its world-space bounding box overlaps the currently visible area (with optional extra viewportPadding). Elements that are entirely offscreen are skipped without any canvas work.
- For nodes: the bounding box is a circle of radius
node.size + 2. - For links: the bounding box is the convex-hull axis-aligned rectangle of the Bezier curve's control points (source, control point, target). This is a conservative bound – it never produces false negatives.
- For self-loops: the bounding box is a square centered on the node with a side length derived from the loop curvature.
Low-zoom draw skipping – when the current zoom level drops below lowZoomThreshold, expensive per-element details that would be too small to read are skipped:
- Node labels (text inside nodes) – controlled by
skipLabelsAtLowZoom - Link arrowheads – controlled by
skipArrowsAtLowZoom - Link relationship labels – controlled by
skipLinkLabelsAtLowZoom
Node and link shapes are always drawn so the overall graph structure remains visible.
Recommended settings for large graphs
// Good starting point for graphs with 1,000 – 10,000+ elements
canvas.setConfig({
largeGraph: {
enabled: true,
viewportPadding: 100, // pre-render elements slightly off-screen to avoid pop-in
lowZoomThreshold: 0.4, // tune to match your typical minimum zoom
}
});Backward compatibility
The feature is enabled by default (enabled: true). The lowZoomThreshold defaults to 1 (always active) and viewport culling uses zero padding.
Debugging
The canvas component includes a debug logging system that can be enabled to help troubleshoot issues or understand the internal behavior of the graph visualization.
Enable Debug Mode
const canvas = document.getElementById('graph');
// Enable debug logging
canvas.setDebug(true);
// Disable debug logging
canvas.setDebug(false);Debug Output
When debug mode is enabled, the canvas will log detailed information to the console including:
- Component lifecycle events (connected, disconnected)
- Data updates (setData, setGraphData)
- Configuration changes (colors, dimensions, render modes)
- Force simulation state (initialization, setup, engine stop)
- Viewport operations (zoom, pan, fit)
- Loading state transitions
- Node degree calculations
- Canvas resizing
All debug messages are prefixed with [FalkorDBCanvas] for easy filtering.
Example Debug Session
const canvas = document.getElementById('graph');
canvas.setDebug(true);
canvas.setData({
nodes: [{ id: 1, labels: ['Person'], color: '#FF6B6B', visible: true, data: { name: 'Alice' } }],
links: []
});
// Console output:
// [FalkorDBCanvas] Debug mode enabled
// [FalkorDBCanvas] setData called with 1 nodes and 0 links
// [FalkorDBCanvas] Loading state: true
// [FalkorDBCanvas] Initializing graph
// [FalkorDBCanvas] Calculating node degrees for 1 nodes
// [FalkorDBCanvas] Setting up force simulation
// ... and moreTips for Debugging
- Enable debug mode early in development to understand component behavior
- Use browser console filtering (
[FalkorDBCanvas]) to focus on canvas logs - Debug mode has minimal performance impact but should be disabled in production
- Combine with browser DevTools to inspect graph state and performance
Examples
See the examples directory for complete working examples including:
- Basic usage
- Custom node/link rendering
- Event handling
- Dynamic data updates
- Theme switching
Links
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
