@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
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
Property | Type | Description | Default |
---|---|---|---|
data | GraphData | The graph data to render | - |
options | GraphOptions | Additional options for the graph rendering | - |
width | number | The width of the graph container | 600 |
height | number | The height of the graph container | 800 |
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
Parameter | Description | Default |
---|---|---|
maxIteration | Maximum number of iterations | 100 |
gravity | Gravity | 10 |
speed | Speed | 0.1 |
textureWidth | Node texture width | 16 |
edgeTextureWidth | Edge texture width | 20 |
force-gpu
Parameter | Description | Default |
---|---|---|
maxIteration | Maximum number of iterations | 100 |
damping | Damping coefficient | 0.9 |
maxSpeed | Maximum speed | 1000 |
minMovement | Stop iterating when average movement per iteration is below this value | 0.5 |
factor | Repulsion coefficient | 1 |
coulombDisScale | Coulomb coefficient | 0.005 |
edgeDistance | Ideal edge length | 10 |
gravity | Central gravity | 10 |
edgeStrength | Spring attraction coefficient | 200 |
nodeStrength | Node force | 100 |
textureWidth | Node texture width | 16 |
edgeTextureWidth | Edge texture width | 20 |
rotate
Parameter | Description | Default |
---|---|---|
rad | Rotation radians | 0 |
Use AntV Layout
- Source: https://github.com/antvis/layout/blob/v5/packages/layout/README.md
- Demo https://observablehq.com/d/2db6b0cc5e97d8d6
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.
Category | Event Name | Description |
---|---|---|
Node Events | node:click | Fired when a node is clicked |
node:contextmenu | Fired when node context menu is triggered | |
node:drag | Fired continuously while dragging a node | |
node:dragstart | Fired when node dragging begins | |
node:dragend | Fired when node dragging ends | |
node:mouseenter | Fired when mouse enters node area | |
node:mousemove | Fired when mouse moves over node | |
node:mouseout | Fired when mouse leaves node area | |
nodeselectchange | Fired when node selection state changes | |
Edge Events | edge:click | Fired when an edge is clicked |
edge:contextmenu | Fired when edge context menu is triggered | |
edge:drag | Fired continuously while dragging an edge | |
edge:dragstart | Fired when edge dragging begins | |
edge:dragend | Fired when edge dragging ends | |
edge:mouseenter | Fired when mouse enters edge area | |
edge:mousemove | Fired when mouse moves over edge | |
edge:mouseout | Fired when mouse leaves edge area | |
Canvas Events | canvas:click | Fired when canvas background is clicked |
canvas:contextmenu | Fired when canvas context menu is triggered | |
canvas:drag | Fired continuously while dragging canvas | |
canvas:dragstart | Fired when canvas dragging begins | |
canvas:dragend | Fired when canvas dragging ends | |
Layout Events | beforelayout | Fired before layout calculation begins |
afterlayout | Fired after layout calculation completes | |
ticklayout | Fired 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.