or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

behaviors.mddata-management.mdelements.mdevents.mdgraph-runtime.mdindex.mdlayouts.mdplugins.mdtypes.md
tile.json

events.mddocs/

Event System

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.

Event Handling Basics

Event Listener Registration

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);
});

Event Listener Removal

// 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);
  }
};

Element Events

Node Events

// 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 Events

// 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 Events

// 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 and Viewport Events

Canvas Events

// 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 Transform Events

// 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

Keyboard Event Handling

// 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

Lifecycle Event Handling

// 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);
});

Event Constants

Built-in Event Types

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'
};

Custom Events

Creating and Emitting Custom Events

// 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' 
});

Event Data Transformation

// 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));

Event Performance Optimization

Event Throttling and Debouncing

// 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 Handling

// 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);
  }
};

Type Definitions

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;
}