G6 provides a comprehensive event system based on EventEmitter that handles user interactions, graph lifecycle events, and custom events. The system supports typed event handlers with detailed event information and flexible event management.
import { Graph } from '@antv/g6';
import type {
IElementEvent,
IPointerEvent,
IKeyboardEvent,
IDragEvent,
IViewportEvent
} from '@antv/g6';
const graph = new Graph({
// ... configuration
});
// Basic event registration
graph.on('node:click', (event: IElementEvent) => {
console.log('Node clicked:', event.target.id);
});
// One-time event listener
graph.once('render', () => {
console.log('Graph rendered for the first time');
});
// Event listener with context binding
graph.on('edge:hover', function(this: Graph, event: IElementEvent) {
this.setElementState(event.target.id, 'hover');
});
// Multiple event registration
const eventHandlers = {
'node:click': handleNodeClick,
'edge:click': handleEdgeClick,
'canvas:click': handleCanvasClick
};
Object.entries(eventHandlers).forEach(([event, handler]) => {
graph.on(event, handler);
});// Remove all listeners
graph.off();
// Remove listeners for specific event
graph.off('node:click');
// Remove specific listener function
const handleClick = (event: IElementEvent) => console.log('clicked');
graph.on('node:click', handleClick);
graph.off('node:click', handleClick);
// Store references for later removal
const listeners = new Map();
const addListener = (eventName: string, handler: Function) => {
graph.on(eventName, handler);
listeners.set(eventName, handler);
};
const removeListener = (eventName: string) => {
const handler = listeners.get(eventName);
if (handler) {
graph.off(eventName, handler);
listeners.delete(eventName);
}
};// Node interaction events
graph.on('node:click', (event: IElementEvent) => {
const { target, targetType, client, canvas, viewport } = event;
console.log(`Node ${target.id} clicked at canvas position:`, canvas);
});
graph.on('node:dblclick', (event: IElementEvent) => {
// Handle double-click - often used for node expansion
graph.focusElement(event.target.id, { duration: 500 });
});
graph.on('node:mouseenter', (event: IElementEvent) => {
// Show hover effects
graph.setElementState(event.target.id, 'hover');
});
graph.on('node:mouseleave', (event: IElementEvent) => {
// Remove hover effects
graph.setElementState(event.target.id, [], false);
});
graph.on('node:contextmenu', (event: IElementEvent) => {
// Show context menu
event.originalEvent.preventDefault();
showContextMenu(event.client, event.target);
});
// Node drag events
graph.on('node:dragstart', (event: IDragEvent) => {
console.log('Drag started on node:', event.target.id);
graph.setElementState(event.target.id, 'dragging');
});
graph.on('node:drag', (event: IDragEvent) => {
const { movement, cumulative } = event;
console.log('Drag movement:', movement, 'Total:', cumulative);
});
graph.on('node:dragend', (event: IDragEvent) => {
console.log('Drag ended on node:', event.target.id);
graph.setElementState(event.target.id, [], false);
});// Edge interaction events
graph.on('edge:click', (event: IElementEvent) => {
const edge = event.target;
console.log(`Edge clicked: ${edge.source} -> ${edge.target}`);
});
graph.on('edge:mouseenter', (event: IElementEvent) => {
// Highlight edge path
graph.setElementState(event.target.id, 'highlight');
// Also highlight connected nodes
const edge = event.target;
graph.setElementState([edge.source, edge.target], 'connected');
});
graph.on('edge:mouseleave', (event: IElementEvent) => {
// Remove highlights
const edge = event.target;
graph.setElementState(event.target.id, [], false);
graph.setElementState([edge.source, edge.target], [], false);
});
graph.on('edge:dblclick', (event: IElementEvent) => {
// Edit edge properties
openEdgeEditor(event.target);
});// Combo interaction events
graph.on('combo:click', (event: IElementEvent) => {
console.log('Combo clicked:', event.target.id);
});
graph.on('combo:dblclick', (event: IElementEvent) => {
// Toggle combo collapse/expand
const combo = event.target;
if (combo.style?.collapsed) {
graph.expandElement(combo.id);
} else {
graph.collapseElement(combo.id);
}
});
graph.on('combo:dragstart', (event: IDragEvent) => {
// Handle combo dragging
console.log('Combo drag started:', event.target.id);
});// Canvas interaction events
graph.on('canvas:click', (event: IPointerEvent) => {
// Clear selections when clicking on empty space
const selectedElements = graph.getElementDataByState('node', 'selected');
if (selectedElements.length > 0) {
selectedElements.forEach(element => {
graph.setElementState(element.id, [], false);
});
}
console.log('Canvas clicked at:', event.canvas);
});
graph.on('canvas:dblclick', (event: IPointerEvent) => {
// Create new node at click position
const newNodeId = `node-${Date.now()}`;
graph.addNodeData([{
id: newNodeId,
style: {
x: event.canvas[0],
y: event.canvas[1]
}
}]);
});
graph.on('canvas:contextmenu', (event: IPointerEvent) => {
// Show canvas context menu
event.originalEvent.preventDefault();
showCanvasContextMenu(event.client);
});
// Canvas drag events (pan operations)
graph.on('canvas:dragstart', (event: IDragEvent) => {
console.log('Canvas pan started');
});
graph.on('canvas:drag', (event: IDragEvent) => {
console.log('Canvas panning:', event.movement);
});
graph.on('canvas:dragend', (event: IDragEvent) => {
console.log('Canvas pan ended');
});// Viewport transformation events
graph.on('viewport:transform', (event: IViewportEvent) => {
const { transform } = event;
console.log('Viewport transformed:', {
zoom: transform.k,
x: transform.x,
y: transform.y
});
// Update UI controls based on zoom level
updateZoomControls(transform.k);
});
// Wheel/scroll events
graph.on('canvas:wheel', (event: IWheelEvent) => {
console.log('Wheel event:', {
deltaX: event.deltaX,
deltaY: event.deltaY,
deltaMode: event.deltaMode
});
});// Keyboard events
graph.on('key:down', (event: IKeyboardEvent) => {
const { key, code, ctrlKey, shiftKey, altKey, metaKey } = event;
// Handle keyboard shortcuts
if (ctrlKey && key === 'a') {
event.originalEvent.preventDefault();
selectAllNodes();
}
if (key === 'Delete') {
deleteSelectedElements();
}
if (key === 'Escape') {
clearSelection();
}
// Arrow key navigation
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(key)) {
handleArrowKeyNavigation(key, shiftKey);
}
});
graph.on('key:up', (event: IKeyboardEvent) => {
console.log('Key released:', event.key);
});
// Helper functions for keyboard actions
const selectAllNodes = () => {
const allNodes = graph.getNodeData();
allNodes.forEach(node => {
graph.setElementState(node.id, 'selected');
});
};
const deleteSelectedElements = () => {
const selectedNodes = graph.getElementDataByState('node', 'selected');
const selectedEdges = graph.getElementDataByState('edge', 'selected');
const nodeIds = selectedNodes.map(node => node.id);
const edgeIds = selectedEdges.map(edge => edge.id);
graph.removeNodeData(nodeIds);
graph.removeEdgeData(edgeIds);
};// Graph lifecycle events
graph.on('render', () => {
console.log('Graph rendered');
hideLoadingIndicator();
});
graph.on('destroy', () => {
console.log('Graph destroyed');
cleanupResources();
});
graph.on('resize', (event: { size: [number, number] }) => {
console.log('Graph resized to:', event.size);
updateLayoutConstraints();
});
// Data change events
graph.on('data:change', (event: { type: string, data: any }) => {
console.log('Data changed:', event.type, event.data);
updateDataStatistics();
});
// Animation events
graph.on('animate:start', (event: any) => {
console.log('Animation started:', event.animation);
});
graph.on('animate:end', (event: any) => {
console.log('Animation completed:', event.animation);
});import {
NodeEvent,
EdgeEvent,
ComboEvent,
CanvasEvent,
GraphEvent,
CommonEvent
} from '@antv/g6';
// Node event constants
const NODE_EVENTS = {
CLICK: NodeEvent.CLICK, // 'node:click'
DBLCLICK: NodeEvent.DBLCLICK, // 'node:dblclick'
MOUSEENTER: NodeEvent.MOUSEENTER, // 'node:mouseenter'
MOUSELEAVE: NodeEvent.MOUSELEAVE, // 'node:mouseleave'
MOUSEMOVE: NodeEvent.MOUSEMOVE, // 'node:mousemove'
MOUSEDOWN: NodeEvent.MOUSEDOWN, // 'node:mousedown'
MOUSEUP: NodeEvent.MOUSEUP, // 'node:mouseup'
CONTEXTMENU: NodeEvent.CONTEXTMENU, // 'node:contextmenu'
DRAGSTART: NodeEvent.DRAGSTART, // 'node:dragstart'
DRAG: NodeEvent.DRAG, // 'node:drag'
DRAGEND: NodeEvent.DRAGEND, // 'node:dragend'
DRAGOVER: NodeEvent.DRAGOVER, // 'node:dragover'
DRAGENTER: NodeEvent.DRAGENTER, // 'node:dragenter'
DRAGLEAVE: NodeEvent.DRAGLEAVE, // 'node:dragleave'
DROP: NodeEvent.DROP // 'node:drop'
};
// Canvas event constants
const CANVAS_EVENTS = {
CLICK: CanvasEvent.CLICK, // 'canvas:click'
DBLCLICK: CanvasEvent.DBLCLICK, // 'canvas:dblclick'
MOUSEDOWN: CanvasEvent.MOUSEDOWN, // 'canvas:mousedown'
MOUSEMOVE: CanvasEvent.MOUSEMOVE, // 'canvas:mousemove'
MOUSEUP: CanvasEvent.MOUSEUP, // 'canvas:mouseup'
CONTEXTMENU: CanvasEvent.CONTEXTMENU, // 'canvas:contextmenu'
WHEEL: CanvasEvent.WHEEL, // 'canvas:wheel'
DRAGSTART: CanvasEvent.DRAGSTART, // 'canvas:dragstart'
DRAG: CanvasEvent.DRAG, // 'canvas:drag'
DRAGEND: CanvasEvent.DRAGEND // 'canvas:dragend'
};// Emit custom events
graph.emit('custom:event', {
data: 'custom data',
timestamp: Date.now()
});
// Listen to custom events
graph.on('custom:event', (data) => {
console.log('Custom event received:', data);
});
// Plugin communication via custom events
graph.emit('plugin:action', {
plugin: 'minimap',
action: 'refresh',
parameters: { force: true }
});
// Custom business logic events
const emitBusinessEvent = (eventType: string, payload: any) => {
graph.emit(`business:${eventType}`, {
...payload,
timestamp: Date.now(),
user: getCurrentUser()
});
};
// Usage
emitBusinessEvent('nodeCreated', { nodeId: 'new-node', nodeType: 'person' });
emitBusinessEvent('relationshipEstablished', {
source: 'node1',
target: 'node2',
type: 'friendship'
});// Transform event data before handling
const createEventHandler = (transform: (event: any) => any) => {
return (event: any) => {
const transformedEvent = transform(event);
handleTransformedEvent(transformedEvent);
};
};
// Add metadata to events
const addMetadata = (event: any) => ({
...event,
metadata: {
timestamp: Date.now(),
sessionId: getSessionId(),
userAgent: navigator.userAgent
}
});
graph.on('node:click', createEventHandler(addMetadata));// Throttle high-frequency events
const throttle = (fn: Function, delay: number) => {
let lastCall = 0;
return (...args: any[]) => {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
fn(...args);
}
};
};
// Debounce events
const debounce = (fn: Function, delay: number) => {
let timeoutId: number;
return (...args: any[]) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
};
};
// Apply to high-frequency events
graph.on('canvas:mousemove', throttle((event: IPointerEvent) => {
updateCursorPosition(event.canvas);
}, 16)); // ~60fps
graph.on('viewport:transform', debounce((event: IViewportEvent) => {
updateMinimap(event.transform);
}, 100));// Conditional event handlers
const conditionalHandler = (condition: () => boolean, handler: Function) => {
return (event: any) => {
if (condition()) {
handler(event);
}
};
};
// Only handle events when graph is interactive
graph.on('node:click', conditionalHandler(
() => !graph.isLayoutRunning?.(),
handleNodeClick
));
// Only handle events for specific element types
const handleElementInteraction = (event: IElementEvent) => {
if (event.target.elementType === 'node') {
handleNodeInteraction(event);
} else if (event.target.elementType === 'edge') {
handleEdgeInteraction(event);
}
};interface IEvent {
type: string;
target?: any;
originalEvent?: Event;
preventDefault?: () => void;
stopPropagation?: () => void;
}
interface IElementEvent extends IEvent {
target: Element; // Target element (Node | Edge | Combo)
targetType: ElementType; // 'node' | 'edge' | 'combo'
client: Point; // Client coordinates [x, y]
canvas: Point; // Canvas coordinates [x, y]
viewport: Point; // Viewport coordinates [x, y]
originalEvent: MouseEvent;
}
interface IPointerEvent extends IEvent {
client: Point; // Client coordinates
canvas: Point; // Canvas coordinates
viewport: Point; // Viewport coordinates
originalEvent: MouseEvent;
}
interface IDragEvent extends IElementEvent {
movement: Point; // Movement delta [dx, dy]
cumulative: Point; // Cumulative movement [totalDx, totalDy]
}
interface IKeyboardEvent extends IEvent {
key: string; // Key pressed
code: string; // Key code
ctrlKey: boolean; // Ctrl key state
shiftKey: boolean; // Shift key state
altKey: boolean; // Alt key state
metaKey: boolean; // Meta key state (Cmd on Mac)
originalEvent: KeyboardEvent;
}
interface IWheelEvent extends IPointerEvent {
deltaX: number; // Horizontal scroll delta
deltaY: number; // Vertical scroll delta
deltaZ: number; // Z-axis scroll delta
deltaMode: number; // Delta mode (0=pixel, 1=line, 2=page)
originalEvent: WheelEvent;
}
interface IViewportEvent extends IEvent {
transform: {
x: number; // Translation X
y: number; // Translation Y
k: number; // Zoom scale
r?: number; // Rotation (if applicable)
};
}
// Event handler type definitions
type ElementEventHandler = (event: IElementEvent) => void;
type PointerEventHandler = (event: IPointerEvent) => void;
type KeyboardEventHandler = (event: IKeyboardEvent) => void;
type DragEventHandler = (event: IDragEvent) => void;
type ViewportEventHandler = (event: IViewportEvent) => void;
// Graph event emitter interface
interface GraphEventEmitter {
on<T extends IEvent>(eventName: string, callback: (event: T) => void, once?: boolean): this;
once<T extends IEvent>(eventName: string, callback: (event: T) => void): this;
off(): this;
off(eventName: string): this;
off(eventName: string, callback: Function): this;
emit(eventName: string, ...args: any[]): boolean;
}