Skip to content

FalkorDB/falkordb-canvas

Repository files navigation

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, flow, radial-tree, concentric, components, and arc graph 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/canvas

Quick 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',
      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({
      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.
setIsLoading(isLoading) Show/hide loading skeleton.
setCooldownTicks(ticks) Set simulation ticks before stopping (undefined = infinite).
setDebug(enabled) false Enable or disable debug logging to console. All log messages are prefixed with [FalkorDBCanvas].
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.

Configuration Options

Option Default Description
width <window width> Canvas width in pixels
height <window height> Canvas height in pixels
backgroundColor Background color (hex or CSS color)
foregroundColor Foreground color for borders and text
layoutMode force Layout algorithm to use: force | tree | flow | radial-tree | concentric | components | arc
layoutOptions {} Per-layout options for each mode: tree, flow, radialTree, concentric, components, and arc
cooldownTicks undefined Number of simulation ticks before stopping (undefined = infinite)
cooldownTime 1000 Time in ms for each simulation tick
autoStopOnSettle true Automatically stop simulation when settled
isLoading false Show/hide loading skeleton
onNodeClick Callback when a node is clicked. Signature: (node: GraphNode, event: MouseEvent) => void
onNodeRightClick Callback when a node is right-clicked. Signature: (node: GraphNode, event: MouseEvent) => void
onLinkClick Callback when a link is clicked. Signature: (link: GraphLink, event: MouseEvent) => void
onLinkRightClick Callback when a link is right-clicked. Signature: (link: GraphLink, event: MouseEvent) => void
onNodeHover Callback when hovering over a node. Signature: (node: GraphNode | null) => void
onLinkHover Callback when hovering over a link. Signature: (link: GraphLink | null) => void
onBackgroundClick Callback when clicking the background. Signature: (event: MouseEvent) => void
onBackgroundRightClick Callback when right-clicking the background. Signature: (event: MouseEvent) => void
onZoom Callback when zoom/pan changes. Signature: (transform: Transform) => void
onEngineStop Callback when the force simulation stops. Signature: () => void
onLoadingChange Callback when loading state changes. Signature: (loading: boolean) => void
isNodeSelected Function to determine if a node is selected. Signature: (node: GraphNode) => boolean
isLinkSelected Function to determine if a link is selected. Signature: (link: GraphLink) => boolean
node Custom node rendering functions (see Custom Rendering)
link Custom link rendering functions (see Custom Rendering)
largeGraph Large-graph rendering optimizations (see Large-Graph Optimizations)

Layout Modes

Use layoutMode in setConfig to choose the graph view style:

canvas.setConfig({
  layoutMode: 'flow',
  layoutOptions: {
    flow: {
      direction: 'LR',      // 'LR' | 'RL' | 'TB' | 'BT'
      layerSpacing: 180,
      nodeSpacing: 110
    }
  }
});

Tree / radial tree example:

canvas.setConfig({
  layoutMode: 'radial-tree',
  layoutOptions: {
    radialTree: {
      rootNodeId: 1,
      direction: 'TB',
      radiusStep: 130
    }
  }
});

Concentric / components / arc examples:

canvas.setConfig({
  layoutMode: 'concentric',
  layoutOptions: {
    concentric: {
      metric: 'degree', // 'degree' | 'inDegree' | 'outDegree' | 'bfsDepth'
      ringSpacing: 130
    },
    components: {
      innerLayout: 'concentric', // 'concentric' | 'tree' | 'flow' | 'radial-tree'
      maxColumns: 3
    },
    arc: {
      direction: 'LR', // 'LR' | 'RL'
      orderBy: 'degree', // 'id' | 'label' | 'degree'
      curveScale: 0.22
    }
  }
});

Notes:

  • force keeps simulation enabled.
  • All non-force layouts compute deterministic target positions, animate layout transitions, and support drag interactions while preserving layout structure.
  • components uses its own innerLayout strategy for each disconnected subgraph and arranges components in a tiled view.
  • tree: rootNodeId, direction, levelSpacing, nodeSpacing, componentSpacing.
  • flow: direction, layerSpacing, nodeSpacing, componentSpacing.
  • radialTree: rootNodeId, direction, startAngle, endAngle, radiusStep, componentSpacing.
  • concentric: metric, rootNodeId, ringSpacing, minRingNodeSpacing, sortWithinRing.
  • components: innerLayout, componentGap, maxColumns, sortComponentsBy.
  • arc: orderBy, direction, nodeSpacing, curveScale.

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 6 Node radius
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 6)
  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;
} | undefined

Transform

{
  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 clean

Web 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 exclusively
  • before: Renders custom content before default rendering
  • after (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

  1. Large graphs: Use cooldownTicks to limit simulation iterations
  2. Static graphs: Set cooldownTicks: 0 after initial layout
  3. Custom rendering: Optimize your custom nodeCanvasObject and linkCanvasObject functions
  4. Viewport: Use getViewport() and setViewport() to preserve user's view when updating data
  5. Very large graphs: Enable viewport culling via the largeGraph option (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 / linkCanvasObject callbacks, 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 (false by default – no behavioural change unless true)
    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({
  cooldownTicks: 300,   // limit physics simulation ticks
  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 disabled by default (enabled: false / property absent). Existing code that does not set largeGraph is completely unaffected.

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 more

Tips 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.

  1. Fork the repository
  2. Create your 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)
  5. Open a Pull Request

About

A standalone web component for visualizing FalkorDB graphs using force-directed layouts.

httpsd://www.falkordb.com

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors