Skip to Content
@kairos-ai/graphgl

@kairos-ai/graphgl

@kairos-ai/graphgl is a high-performance graph visualization library built on top of WebGL. It provides a flexible and efficient way to render large graphs with various layout algorithms and styles.

Online Demo: https://preview.kairos-graph.com

Online Edit

Installation

add the following to your .npmrc file:

@kairos-ai:registry=https://registry.npmjs.org/ //registry.npmjs.org/:_authToken=AUTH_TOKEN

then install the package using npm or yarn:

npm install @kairos-ai/graphgl

or

yarn add @kairos-ai/graphgl

Features

  • High Performance: Utilizes WebGL for rendering, ensuring smooth performance even with large graphs.
  • Customizable: Supports custom node and edge styles, allowing for a wide range of visualizations.
  • Layout Algorithms: Includes built-in layout algorithms like Fruchterman and Force-directed layouts, with the ability to integrate AntV layouts.
  • React Integration: Provides a React component for easy integration into React applications.
  • Custom Layouts: Allows for custom layout implementations using Graphology or other libraries.
  • TypeScript Support: Fully typed with TypeScript, ensuring type safety and better developer experience.
  • Extensible: Easily extendable with custom layouts and styles.

GraphGL

GraphGL is a React component that renders a graph using WebGL.

Props

PropertyTypeDescriptionDefault
dataGraphDataThe graph data to render-
optionsGraphOptionsAdditional options for the graph rendering-
widthnumberThe width of the graph container600
heightnumberThe height of the graph container800

Usage Example

import React from 'react'; import { createRoot } from 'react-dom/client'; import GraphGL from '@kairos-ai/graphgl'; const data = { nodes: [ { id: '1', name: 'Node 1' }, { id: '2', name: 'Node 2' }, { id: '3', name: 'Node 3' }, ], edges: [ { source: '1', target: '2' }, { source: '2', target: '3' }, { source: '3', target: '1' }, ], }; const App = () => { return <GraphGL data={data} width={800} height={600} />; }; createRoot(document.getElementById('app')).render(<App />);

Data

Definition

interface Node { id: string; x?: number; y?: number; [key: string]: any; } interface Edge { id?: string; source: string; target: string; [key: string]: any; } interface GraphData { nodes: Node[]; edges: Edge[]; }

Demo Data

const data: GraphData = { nodes: [ { id: 'n1', x: 0, y: 0 }, { id: 'n2', x: 100, y: 100 }, ], edges: [{ source: 'n1', target: 'n2', weight: 1 }], };

Options

options are used to configure the graph rendering, including styles, layout, and other features.

/** * Core graph configuration options */ export interface IGraphOptions<L = LayoutOptionsBase> { /* ============= Basic Config ============= */ /** * Skip layout and render directly * @default false */ fastRender?: boolean; /* ============= Component Config ============= */ /** * Minimap configuration */ minimap?: MinimapOptions; /** * Tooltip configuration */ tooltip?: TooltipOptions; /* ============= Style Config ============= */ /** * Node styling configuration */ nodeStyle?: NodeStyleOptions; /** * Edge styling configuration */ edgeStyle?: EdgeStyleOptions; /* ============= Layout Config ============= */ /** * Layout configuration (supports built-in or custom layouts) */ layout?: BuiltInLayoutOptions | L; } /* ============= Base Types ============= */ /** * Color type (supports multiple formats: string, number or RGBAColor) * RGBAColor is an array containing r, g, b, a components, each ranging from 0 to 255. */ type ColorType = string | number | RGBAColor;

Minimap

interface MinimapOptions { /** * Whether to enable minimap * @default true */ enabled?: boolean; /** * Minimap width in pixels * @default 120 */ width?: number; /** * Minimap height in pixels * @default 120 */ height?: number; /** * Color theme */ theme?: 'light' | 'dark'; /** * Custom styles for minimap */ style?: React.CSSProperties; /** * Custom styles for minimap overlay */ overlayerStyle?: React.CSSProperties; }

Tooltip

interface TooltipOptions { /** * Whether to enable tooltip * @default true */ enabled?: boolean; /** * X offset in pixels * @default 6 */ offsetX?: number; /** * Y offset in pixels * @default 6 */ offsetY?: number; /** * Custom content generator, supports DOM elements or strings * @default data => data.id */ getContent?: (data: Item) => string; /** * Custom styles for tooltip */ style?: React.CSSProperties; }

NodeStyle

interface NodeStyleOptions { /** * Node size * @default 12 */ size?: number | ((node: INode) => number); /** * Node fill color * @default [255, 235, 59, 255] */ fill?: ColorType | ((node: INode) => ColorType); /** * Node stroke color * @default [0, 0, 0, 255] */ stroke?: ColorType | ((node: INode) => ColorType); /** * Node stroke width * @default 2 */ lineWidth?: number | ((node: INode) => number); /** * Node highlight color * @default [72, 255, 0, 255] */ highlightColor?: ColorType | ((node: INode) => ColorType); /** * Label configuration */ label?: { /** * Whether to show label * @default false */ visible?: boolean; /** * Label position in pixels, relative to node center * @default [0, -12] */ position?: [number, number]; /** * Label text * @default data => data.id */ value?: string | ((node: INode) => string); /** * Label fill color (supports dynamic function) * @default [0, 0, 0, 255] */ fill?: ColorType | ((node: INode) => ColorType); /** * Font size * @default 12 */ fontSize?: number; /** * Font family * @default 'PingFang SC' */ fontFamily?: string; /** * Font style * @default 'top' */ align?: 'top' | 'center' | 'bottom'; /** * Text anchor position * @default 'middle' */ anchor?: 'start' | 'middle' | 'end'; }; /** * Icon configuration */ icon?: { /** * Whether to show icon */ visible?: boolean; /** * Icon identifier */ value?: string | ((node: INode) => string); /** * Icon fill color (supports dynamic function) * @default [255, 255, 255, 255], */ fill?: ColorType | ((node: INode) => ColorType); /** * Icon font family */ fontFamily?: string; getIcon?: IconLayerProps<INode>['getIcon']; }; }

EdgeStyle

interface EdgeStyleOptions { /** * Edge color * @default [100, 100, 100, 255] */ fill?: ColorType | ((edge: IEdge) => ColorType); /** * Edge width * @default 1 */ lineWidth?: number | ((edge: IEdge) => number); /** * Edge color * @default [72, 255, 0, 255] */ highlightColor?: ColorType | ((edge: IEdge) => ColorType); /** * Whether to show curve edges * @default true */ curveVisible?: boolean; /** * Start arrow configuration * @default 1 */ endArrow?: number | ((edge: IEdge) => number); /** * Edge type (0: curve, 1: straight, 2: loop) * @default 1 */ type?: number | ((edge: IEdge) => number); /** * Edge curve offset * @default 0 */ curveOffset?: number | ((edge: IEdge) => number); /** * Edge curve radius * @default 0 */ loopRad?: number | ((edge: IEdge) => number); /** * Edge loop scale factor * @default 10 */ loopScale?: number | ((edge: IEdge) => number); /** * Edge label configuration */ label?: { /** * Whether to show label * @default false */ visible?: boolean; /** * Label position * @default 'center' */ position?: 'center' | 'bottom'; /** * Label text * @default data => data.id */ value?: string | ((node: INode) => string); /** * Label fill color (supports dynamic function) * @default [0, 0, 0, 255] */ fill?: ColorType | ((node: INode) => ColorType); /** * Font size * @default 12 */ fontSize?: number; /** * Font family * @default 'PingFang SC' */ fontFamily?: string; /** * Text alignment * @default 'bottom' */ align?: 'top' | 'center' | 'bottom'; }; }

Layout

Configure the layout using options.layout

Built-In Layout

fruchterman-gpu

ParameterDescriptionDefault
maxIterationMaximum number of iterations100
gravityGravity10
speedSpeed0.1
textureWidthNode texture width16
edgeTextureWidthEdge texture width20

force-gpu

ParameterDescriptionDefault
maxIterationMaximum number of iterations100
dampingDamping coefficient0.9
maxSpeedMaximum speed1000
minMovementStop iterating when average movement per iteration is below this value0.5
factorRepulsion coefficient1
coulombDisScaleCoulomb coefficient0.005
edgeDistanceIdeal edge length10
gravityCentral gravity10
edgeStrengthSpring attraction coefficient200
nodeStrengthNode force100
textureWidthNode texture width16
edgeTextureWidthEdge texture width20

rotate

ParameterDescriptionDefault
radRotation radians0

Use AntV Layout

import GraphGL, { register } from '@graphgl/react'; import AntVLayout, { AntVLayoutOptions } from '@graphgl/react/layouts/adapter/antv'; AntVLayout.layouts.forEach((type) => { AntVLayout.workerScirptURL = [ 'https://unpkg.com/@antv/layout@1.2.13/dist/index.min.js', 'https://unpkg.com/@antv/graphlib@2.0.4/dist/index.umd.min.js', ]; register('layout', type, AntVLayout); }); ... <GraphGL<AntVLayoutOptions> options={{ layout: { type: 'd3force' } }} />
  • Available AntV Layout

    • force
    • forceAtlas2
    • fruchterman
    • d3force
    • circular
    • grid
    • random
    • mds
    • concentric
    • dagre
    • radial

Integrate Other Layouts

import { Layouts, LayoutBaseProps, LayoutOptionsBase } from '@graphgl/react'; import forceAtlas2, { ForceAtlas2Settings, } from 'graphology-layout-forceatlas2'; import FA2Layout from 'graphology-layout-forceatlas2/worker'; import Graphology from 'graphology'; const getNodesBuffer = (nodes: any) => { const length = nodes.length; const nodeBuffer = new Float32Array(length * 2); for (let index = 0; index < length; index++) { nodeBuffer[index * 2] = nodes[index].attributes?.x as number; nodeBuffer[index * 2 + 1] = nodes[index].attributes?.y as number; } return nodeBuffer; }; export type GraphologyLayoutOptions = LayoutOptionsBase< { type: 'graphology-forceatlas2'; isTick?: boolean; } & ForceAtlas2Settings >; export type GraphologyLayoutProps = LayoutBaseProps<GraphologyLayoutOptions>; export default class GraphologyLayout extends Layouts.BaseLayout { public options: GraphologyLayoutProps['options'] | null = null; public nodes: GraphologyLayoutProps['nodes'] = []; public edges: GraphologyLayoutProps['edges'] = []; public layout: FA2Layout | null = null; static layouts = ['graphology-forceatlas2']; static workerScirptURL: string | string[] = []; constructor(opts: GraphologyLayoutProps) { super(); this.options = opts.options; this.nodes = opts.nodes; this.edges = opts.edges; } public destroy() { this.layout?.stop(); this.layout?.kill(); } public execute() { const { nodes, edges, options } = this; const { onTick, onLayoutEnd, isTick, type, ...restOptions } = options as GraphologyLayoutProps['options']; const graph = new Graphology(); graph.import({ nodes: nodes.map((item) => ({ key: item.id, attributes: { x: item.x === undefined ? Math.random() * 2000 - 1000 : item.x, y: item.y === undefined ? Math.random() * 2000 - 1000 : item.y, }, })), edges: edges, }); const sensibleSettings = forceAtlas2.inferSettings(graph); const layout = new FA2Layout(graph, { settings: { ...sensibleSettings, ...restOptions, }, }); this.layout = layout; this.layout.start(); const animate = () => { requestAnimationFrame(() => { const results = graph.export().nodes; const nodesBuffer = getNodesBuffer(results); if (this.layout?.isRunning()) { onTick?.(nodesBuffer); animate(); } else { onLayoutEnd?.(nodesBuffer); } }); }; animate(); } }

Instance API

Definition

/** * Core graph manipulation interface providing methods for layout control, * viewport manipulation, and graph interaction. */ interface GraphInterface { /** * Refresh node positions with optional transition animation * @param transition - Whether to animate the position changes (default: false) */ refreshPositions: (transition?: boolean) => void; /** * Refresh the graph display based on specified changes. If no parameters are provided, all content will be refreshed. * @param type - Type of refresh operation or trigger type(s) * @param ranges - Optional range parameters for partial refresh */ refresh: ( type?: RefreshType | TriggerType | TriggerType[], ranges?: IIRanges ) => void; /** * Execute the current layout algorithm */ executeLayout: () => void; /** * Destroy the current layout instance and clean up resources */ destroyLayout: () => void; /** * Download current graph view as an image * @param type - Image format (default: 'image/png') * @param name - Optional filename (without extension) */ downloadImage: (type?: DataUrlType, name?: string) => void; /** * Get the WebGL rendering context * @returns Current WebGL context */ getGLContext: () => WebGLRenderingContext; /** * Get current zoom level * @returns Current zoom ratio (1.0 = 100%) */ getZoom: () => number; /** * Zoom to specific level with optional animation * @param ratio - Target zoom ratio * @param center - Optional focal point coordinates * @param transitionDuration - Animation duration in ms (0 for immediate) */ zoomTo: (ratio: number, center?: Point, transitionDuration?: number) => void; /** * Apply relative zoom change * @param ratio - Zoom delta (e.g., 1.1 = 10% zoom in) * @param center - Optional focal point coordinates * @param transitionDuration - Animation duration in ms */ zoom: (ratio: number, center?: Point, transitionDuration?: number) => void; /** * Move viewport by relative offset * @param dx - Horizontal movement in pixels * @param dy - Vertical movement in pixels * @param transitionDuration - Animation duration in ms */ translate: (dx: number, dy: number, transitionDuration?: number) => void; /** * Move viewport to absolute position * @param x - Target horizontal position * @param y - Target vertical position * @param transitionDuration - Animation duration in ms */ moveTo: (x: number, y: number, transitionDuration?: number) => void; /** * Center the graph in viewport (no zoom adjustment) */ fitCenter: () => void; /** * Auto-fit graph to viewport * @param noZoomOut - Prevent zooming out beyond current level * @param transitionDuration - Animation duration in ms * @param padding - Additional padding around graph * @returns Final zoom ratio applied */ fitView: ( noZoomOut?: boolean, transitionDuration?: number, padding?: Padding ) => number; /** * Find graph objects in specified rectangular region * @param options - Selection area parameters * @returns Array of items within selection bounds */ pickObjects: (options: { x: number; y: number; width: number; height: number; }) => any; /** * Convert screen coordinates to canvas coordinates * @param x - Screen X position * @param y - Screen Y position * @returns Corresponding canvas coordinates */ getCanvasByPoint: (x: number, y: number) => Point; /** * Adjust view to fit the minimap display */ fitMiniMap: () => void; }

Usage Example

import React from 'react'; import { createRoot } from 'react-dom/client'; import GraphGL from '@kairos-ai/graphgl'; const data = { nodes: [ { id: '1', name: 'Node 1' }, { id: '2', name: 'Node 2' }, { id: '3', name: 'Node 3' }, ], edges: [ { source: '1', target: '2' }, { source: '2', target: '3' }, { source: '3', target: '1' }, ], }; const App = () => { const graphRef = React.useRef<GraphGL>(null); React.useEffect(() => { const graph = graphRef.current; if (graph) { graph.fitView(); } }, []); return <GraphGL ref={graphRef} data={data} width={800} height={600} />; }; createRoot(document.getElementById('app')).render(<App />);

Event

Definition

export interface IGraphInterface extends GraphInterface { /** * Registers an event handler function for the specified event type * * @param evt - The event name to listen for * @param callback - The function to be called when the event occurs * @param once - Optional flag indicating if the handler should only be called once * @returns The graph instance for method chaining */ on: (evt: string, callback: Function, once?: boolean) => this; /** * Removes one or more event handlers * * @param evt - Optional event name. If omitted, removes all handlers. * @param callback - Optional specific handler function. If omitted, removes all handlers for the event. * @returns The graph instance for method chaining */ off: (evt?: string, callback?: Function) => this; }

Available Events

@kairos-ai/graphgl provides a comprehensive set of events to handle user interactions and graph updates. These events can be used to respond to actions like clicking nodes or edges, dragging items, and layout changes.

CategoryEvent NameDescription
Node Eventsnode:clickFired when a node is clicked
node:contextmenuFired when node context menu is triggered
node:dragFired continuously while dragging a node
node:dragstartFired when node dragging begins
node:dragendFired when node dragging ends
node:mouseenterFired when mouse enters node area
node:mousemoveFired when mouse moves over node
node:mouseoutFired when mouse leaves node area
nodeselectchangeFired when node selection state changes
Edge Eventsedge:clickFired when an edge is clicked
edge:contextmenuFired when edge context menu is triggered
edge:dragFired continuously while dragging an edge
edge:dragstartFired when edge dragging begins
edge:dragendFired when edge dragging ends
edge:mouseenterFired when mouse enters edge area
edge:mousemoveFired when mouse moves over edge
edge:mouseoutFired when mouse leaves edge area
Canvas Eventscanvas:clickFired when canvas background is clicked
canvas:contextmenuFired when canvas context menu is triggered
canvas:dragFired continuously while dragging canvas
canvas:dragstartFired when canvas dragging begins
canvas:dragendFired when canvas dragging ends
Layout EventsbeforelayoutFired before layout calculation begins
afterlayoutFired after layout calculation completes
ticklayoutFired during each layout iteration

Usage Example

You can register event handlers using the on method of the GraphGL instance. Here’s an example of how to handle node clicks:

import React from 'react'; import { createRoot } from 'react-dom/client'; import GraphGL from '@kairos-ai/graphgl'; const data = { nodes: [ { id: '1', name: 'Node 1' }, { id: '2', name: 'Node 2' }, { id: '3', name: 'Node 3' }, ], edges: [ { source: '1', target: '2' }, { source: '2', target: '3' }, { source: '3', target: '1' }, ], }; const App = () => { const graphRef = React.useRef<GraphGL>(null); React.useEffect(() => { const graph = graphRef.current; if (graph) { graph.on('node:click', (event) => { console.log('Node clicked:', event.data); }); } }, []); return <GraphGL ref={graphRef} data={data} width={800} height={600} />; }; createRoot(document.getElementById('app')).render(<App />);

Plugin

Grid

The Grid plugin provides a grid overlay for the graph.

Usage Example

import GraphGL, { Grid } from '@kairos-ai/graphgl'; ... const App = () => { return ( <GraphGL data={data} width={800} height={600}> <Grid /> </GraphGL> ); };

ClickSelect

The ClickSelect plugin allows users to select graph items (nodes or edges) by clicking on them. It supports single and multiple selections, and can be customized to trigger selection events.

  • Usage Example
import GraphGL, { ClickSelect } from '@kairos-ai/graphgl'; ... const App = () => { return ( <GraphGL data={data} width={800} height={600}> <ClickSelect /> </GraphGL> ); };

BrushSelect

The BrushSelect plugin enables users to select multiple graph items by dragging a rectangular selection area over the graph. This is useful for quickly selecting groups of nodes or edges without clicking each one individually.

  • Usage Example
import GraphGL, { BrushSelect } from '@kairos-ai/graphgl'; ... const App = () => { return ( <GraphGL data={data} width={800} height={600}> <BrushSelect /> </GraphGL> ); };

NeighbourSelect

The NeighbourSelect plugin allows users to select a node and automatically selects all its connected neighbours (edges and adjacent nodes). This is useful for quickly exploring relationships in the graph.

  • Usage Example
import GraphGL, { NeighbourSelect } from '@kairos-ai/graphgl'; ... const App = () => { return ( <GraphGL data={data} width={800} height={600}> <NeighbourSelect /> </GraphGL> ); };

LassoSelect

The LassoSelect plugin enables users to select graph items by drawing a freeform lasso shape around them. This provides a flexible way to select complex shapes or irregular groups of nodes and edges.

  • Usage Example
import GraphGL, { LassoSelect } from '@kairos-ai/graphgl'; ... const App = () => { return ( <GraphGL data={data} width={800} height={600}> <LassoSelect /> </GraphGL> ); };

Advanced

TODO: This section is reserved for advanced topics such as performance optimizations, and integration with other libraries. It will be updated in future releases.

Last updated on