JavaScript is required

User Interaction & Events

Purpose and Scope

This page introduces the event system architecture in relation-graph, explaining how user interactions are captured, processed, and propagated to application code. The system provides a unified event model that works consistently across all supported frameworks (Vue, React, Svelte).

For detailed information about specific event types and the internal event dispatcher architecture, see Event System Architecture. For concrete examples of handling user actions like clicks, drags, and gestures, see User Actions & Interactions.


Event System Overview

The relation-graph event system follows a layered event flow pattern where user interactions are captured at the DOM level, processed through the core graph logic, and finally emitted to application-level event handlers. This architecture separates concerns between:

  1. User Action Capture - DOM event listeners attached to graph elements (nodes, lines, canvas)
  2. Core Processing - Business logic in RelationGraphWith7Event and related classes
  3. Event Emission - Standardized event broadcasting through emitEvent()
  4. Application Handlers - User-defined callbacks in the RGListeners interface

All events are type-safe, with TypeScript definitions providing autocomplete and validation for event names, parameters, and return values.

Sources: packages/relation-graph-models/models/RelationGraphWith7Event.ts:1-30, packages/relation-graph-models/models/RelationGraphBase.ts:106-167


Event Flow Architecture

Event Propagation from DOM to Application Handlers

graph TB
    subgraph DOM["DOM Events"]
        MouseEvent["MouseEvent/TouchEvent"]
    end
    
    subgraph Components["Component Layer"]
        RGNodePeel["@mousedown
RGNodePeel"] RGLinePath["@click
RGLinePath"] RGCanvas["@mousedown
RGCanvas"] end subgraph Core["RelationGraphWith7Event"] onNodeDragStart["onNodeDragStart(node, e)"] onNodeClick["onNodeClick(node, e)"] onLineClick["onLineClick(line, e)"] onCanvasClick["onCanvasClick(e)"] expandOrCollapseNode["expandOrCollapseNode(node, e)"] startCreatingNodePlot["startCreatingNodePlot(e, setting)"] startCreatingLinePlot["startCreatingLinePlot(e, setting)"] end subgraph Base["RelationGraphBase"] emitEvent["emitEvent(eventName, ...args)"] defaultEventHandler["defaultEventHandler()"] eventHandlers["eventHandlers: RGEventHandler[]"] _emitHook["_emitHook: RGEventEmitHook"] end subgraph App["Application Layer"] listeners["listeners: RGListeners"] onNodeClickCB["listeners.onNodeClick?"] onNodeDragStartCB["listeners.onNodeDragStart?"] customHandler["Custom RGEventHandler"] end MouseEvent --> RGNodePeel MouseEvent --> RGLinePath MouseEvent --> RGCanvas RGNodePeel -->|"@mousedown"| onNodeDragStart RGNodePeel -->|"click detected"| onNodeClick RGLinePath --> onLineClick RGCanvas --> onCanvasClick onNodeDragStart --> emitEvent onNodeClick --> emitEvent onLineClick --> emitEvent onCanvasClick --> emitEvent expandOrCollapseNode --> emitEvent startCreatingNodePlot --> emitEvent startCreatingLinePlot --> emitEvent emitEvent --> defaultEventHandler emitEvent --> eventHandlers emitEvent --> _emitHook defaultEventHandler --> listeners listeners --> onNodeClickCB listeners --> onNodeDragStartCB eventHandlers --> customHandler _emitHook --> App

Event Processing Chain:

  1. DOM Event Capture - Framework components attach native event listeners (@mousedown, @click, @wheel)
  2. Handler Method Invocation - Component calls corresponding method on RelationGraphWith7Event (e.g., onNodeDragStart())
  3. Business Logic Execution - Handler method processes the interaction, updates dataProvider, checks options
  4. Event Emission - Handler calls this.emitEvent(RGEventNames.onNodeDragStart, node, e)
  5. Multi-tier Dispatch - emitEvent() invokes handlers in order:
    • defaultEventHandler() → checks this.listeners
    • Custom eventHandlers[] → user-registered middleware
    • _emitHook() → framework-specific emit (Vue $emit)
  6. Application Callback - User-defined functions in RGListeners execute with event data

Sources: packages/relation-graph-models/models/RelationGraphWith7Event.ts:37-80, packages/relation-graph-models/models/RelationGraphBase.ts:141-171


Core Event Components

RGEventNames Enumeration

All events in the system are identified by constants defined in the RGEventNames enum. This ensures type safety and prevents typos in event names:

enum RGEventNames {
    onReady = 'onReady',
    onNodeClick = 'onNodeClick',
    onNodeDragStart = 'onNodeDragStart',
    onNodeDragging = 'onNodeDragging',
    onNodeDragEnd = 'onNodeDragEnd',
    onLineClick = 'onLineClick',
    onCanvasClick = 'onCanvasClick',
    onCanvasDragging = 'onCanvasDragging',
    onCanvasDragEnd = 'onCanvasDragEnd',
    // ... 20+ event types total
}

Complete Event List:

Category Events
Node Events onNodeClick, onNodeExpand, onNodeCollapse, onNodeDragStart, onNodeDragging, onNodeDragEnd
Line Events onLineClick, onLineBeCreated, onLineVertexDropped, beforeCreateLine
Canvas Events onCanvasClick, onCanvasDragStart, onCanvasDragging, onCanvasDragEnd, onCanvasSelectionEnd
View Events beforeZoomStart, onZoomEnd, onViewResize, onFullscreen
Editing Events onResizeStart, beforeNodeResize, onResizeEnd
Keyboard Events onKeyboardDown, onKeyboardUp
Lifecycle Events onReady, onForceLayoutFinish, beforeScrollStart
Context Menu onContextmenu
Data Events beforeAddNodes, beforeAddLines

Sources: packages/relation-graph-models/types.ts:649-680

RGListeners Interface

The RGListeners interface defines the type signatures for all event handler callbacks. Applications implement this interface to handle events:

interface RGListeners {
    onReady?: (graphInstance: RelationGraphInstance) => void;
    onNodeClick?: (node: RGNode, e: RGUserEvent) => boolean | void | Promise<boolean | void>;
    onNodeDragging?: (node: RGNode, newX: number, newY: number, buffX: number, buffY: number, e: RGUserEvent) => void | RGPosition | undefined;
    onCanvasDragging?: (newX: number, newY: number, buffX: number, buffY: number) => void | RGPosition | undefined;
    // ... all other event handlers
}

Return Value Patterns:

  • boolean | void - Returning false cancels default behavior (e.g., beforeNodeResize, beforeCreateLine)
  • RGPosition | undefined - Returning coordinates overrides computed position (e.g., onNodeDragging, onCanvasDragging)
  • void - No return value, just notification (most events)

Sources: packages/relation-graph-models/types.ts:417-593

RGUserEvent Type

The system normalizes browser events into a unified RGUserEvent type that supports both mouse and touch interactions:

type RGUserEvent = MouseEvent | TouchEvent;

Utility functions like getClientCoordinate() and isSupportTouch() abstract away the differences between mouse and touch events, allowing the core logic to handle both interaction types uniformly.

Sources: packages/relation-graph-models/types.ts:87


Event Registration and Handling

Method 1: Declarative Listeners (Recommended)

When creating a RelationGraph component, pass event handlers via the component props:

<RelationGraph
  options={graphOptions}
  onNodeClick={(node, e) => {
    console.log('Node clicked:', node.text);
  }}
  onNodeDragEnd={(node, e, xBuff, yBuff) => {
    console.log(`Node ${node.id} moved by (${xBuff}, ${yBuff})`);
  }}
  onCanvasClick={(e) => {
    console.log('Canvas clicked at', e.clientX, e.clientY);
  }}
/>

This approach works identically across Vue, React, and Svelte implementations.

Sources: packages/relation-graph-models/types.ts:868-871

Method 2: Programmatic Registration

For dynamic event handling, use addEventHandler() to register custom handlers that receive all events:

const graphInstance = await getGraphInstance();

graphInstance.addEventHandler((eventName, ...args) => {
  if (eventName === RGEventNames.onNodeClick) {
    const [node, event] = args;
    console.log('Intercepted node click:', node.id);
    return false; // Prevent default behavior
  }
});

Multiple handlers can be registered, and they execute in registration order. Return undefined to continue to the next handler, or return a value to short-circuit the chain.

Sources: packages/relation-graph-models/models/RelationGraphBase.ts:106-123

Method 3: Event Emit Hook (Framework Integration)

Platform-specific implementations can register an emit hook to bridge to framework-native event systems (e.g., Vue’s $emit):

graphInstance.setEventEmitHook((eventName, ...args, callback) => {
  // Forward to framework's event system
  this.$emit(eventName, ...args);
  
  // Call the callback to get return value from parent
  callback(parentReturnValue);
});

This is used internally by Vue2/Vue3 adapters to integrate with component emit() functionality.

Sources: packages/relation-graph-models/models/RelationGraphBase.ts:125-134


User Interaction to Handler Method Mapping

Mapping of DOM Events to Core Handler Methods

User Interaction DOM Event Component Handler Method Event Emitted
Click node @click / @mousedown RGNodePeel onNodeClick(node, e) RGEventNames.onNodeClick
Drag node @mousedown + mousemove RGNodePeel onNodeDragStart(node, e) RGEventNames.onNodeDragStart
RGEventNames.onNodeDragging
RGEventNames.onNodeDragEnd
Click line @click RGLinePath onLineClick(line, e) RGEventNames.onLineClick
Click canvas @mousedown RGCanvas onCanvasClick(e) RGEventNames.onCanvasClick
Drag canvas @mousedown + mousemove RGCanvas onCanvasDragStart(e) RGEventNames.onCanvasDragStart
RGEventNames.onCanvasDragging
RGEventNames.onCanvasDragEnd
Mouse wheel @wheel RGCanvas onMouseWheel(e) RGEventNames.beforeZoomStart
RGEventNames.onZoomEnd
Expand/collapse @click Expand button expandOrCollapseNode(node, e) RGEventNames.onNodeExpand
RGEventNames.onNodeCollapse
Resize node @mousedown RGEditingNodeController onResizeStart(type, e) RGEventNames.onResizeStart
RGEventNames.beforeNodeResize
RGEventNames.onResizeEnd
Context menu @contextmenu Multiple onContextMenu(e, type, obj) RGEventNames.onContextmenu
Keyboard @keydown / @keyup Document onKeyDown(e) / onKeyUp(e) RGEventNames.onKeyboardDown
RGEventNames.onKeyboardUp

Sources: packages/relation-graph-models/models/RelationGraphWith7Event.ts:49-478, packages/relation-graph-models/models/RelationGraphWith91Editing.ts:150-341


Event Processing Pipeline

Internal Structure of emitEvent() Method

graph TB
    Start["emitEvent(eventName: RGEventNames, ...args)"]
    
    subgraph Phase1["Phase 1: defaultEventHandler()"]
        CheckEvent["Switch on eventName"]
        FindListener["Find this.listeners[eventName]"]
        ExecDefault["Execute listener(...args)"]
        CaptureResult1["Capture result, handled = true"]
    end
    
    subgraph Phase2["Phase 2: Custom eventHandlers[]"]
        LoopHandlers["for (handler of this.eventHandlers)"]
        CallHandler["customResult = handler(eventName, ...args)"]
        CheckUndefined["customResult !== undefined?"]
        Override["result = customResult
handled = true"] end subgraph Phase3["Phase 3: _emitHook (Vue emit)"] CheckHook["this._emitHook exists?"] CallHook["this._emitHook(eventName, ...args, callback)"] HookCallback["callback(customReturnValue)"] OverrideHook["if customReturnValue !== undefined
result = customReturnValue"] end Return["return result"] Start --> Phase1 Phase1 --> CheckEvent CheckEvent --> FindListener FindListener -->|"exists"| ExecDefault ExecDefault --> CaptureResult1 CaptureResult1 --> Phase2 Phase2 --> LoopHandlers LoopHandlers --> CallHandler CallHandler --> CheckUndefined CheckUndefined -->|"yes"| Override Override --> LoopHandlers CheckUndefined -->|"no"| LoopHandlers LoopHandlers -->|"done"| Phase3 Phase3 --> CheckHook CheckHook -->|"yes"| CallHook CallHook --> HookCallback HookCallback --> OverrideHook CheckHook -->|"no"| Return OverrideHook --> Return

Handler Execution Priority:

  1. defaultEventHandler() - Executes first, checks this.listeners object for matching event name
  2. Custom eventHandlers[] - Loops through array of RGEventHandler functions registered via addEventHandler()
  3. _emitHook() - Framework integration point, typically used by Vue2/Vue3 for $emit()

Return Value Logic:

  • First non-undefined return value becomes the final result
  • handled flag tracks whether any handler executed
  • Final result propagates back to calling code
  • Events like beforeZoomStart use return value to cancel operations (return false)
  • Events like onNodeDragging use return value to override coordinates (return {x, y})

Sources: packages/relation-graph-models/models/RelationGraphBase.ts:141-171, packages/relation-graph-models/models/RelationGraphBase.ts:179-367


User Interaction Categories

The relation-graph system supports a comprehensive range of user interactions, organized into the following categories:

Node Interactions

  • Click - Select/deselect nodes, trigger custom actions
  • Drag - Move nodes, with auto-canvas-scrolling at viewport edges
  • Resize - Adjust node dimensions via editing controller handles
  • Expand/Collapse - Toggle visibility of child nodes in hierarchical layouts
  • Context Menu - Right-click for custom menus

See User Actions & Interactions for implementation details.

Canvas Interactions

  • Drag - Pan the entire graph view
  • Selection - Draw selection rectangle to select multiple nodes
  • Zoom - Mouse wheel or pinch-to-zoom
  • Click - Deselect all elements

Line Interactions

  • Click - Select lines for editing
  • Drag Control Points - Adjust orthogonal line paths (shapes 44/49)
  • Drag Text - Reposition line labels
  • Create - Multi-step wizard for drawing new connections

Keyboard Interactions

  • Key Down/Up - Generic keyboard event handlers for custom shortcuts
  • Focus-aware input detection prevents interference with text editing

Touch Support

All mouse-based interactions have touch equivalents:

  • Single tap → click
  • Drag → pan/move
  • Pinch → zoom
  • Long press → context menu

The system automatically detects touch capability using isSupportTouch() and adapts UI accordingly (e.g., hiding hover-based template guides).

Sources: packages/relation-graph-models/models/RelationGraphWith7Event.ts:37-478


Event Flow Example: Node Drag Operation

Complete Event Flow for Node Drag with RGDragUtils Integration

sequenceDiagram
    participant User
    participant RGNodePeel
    participant With7Event as "RelationGraphWith7Event"
    participant DragUtils as "RGDragUtils"
    participant DataProvider as "dataProvider"
    participant Base as "RelationGraphBase"
    participant Listeners as "listeners: RGListeners"
    
    User->>RGNodePeel: "@mousedown on node"
    RGNodePeel->>With7Event: onNodeDragStart(node, e)
    
    Note over With7Event: Check node.disableDrag
Check options.disableDragNode
Initialize _nodeXYMappingBeforeDrag With7Event->>DragUtils: startDrag(e, nodeStartXY, dragEnd, draggingTick) Note over DragUtils: document.addEventListener('mousemove')
document.addEventListener('mouseup') loop requestAnimationFrame loop User->>DragUtils: mousemove DragUtils->>With7Event: draggingTick(offsetX, offsetY, ...) alt First move > 4px With7Event->>Base: emitEvent(RGEventNames.onNodeDragStart, node, e) Base->>Listeners: listeners.onNodeDragStart?(node, e) With7Event->>With7Event: this._canvasMovingTimer = requestAnimationFrame(movingLoop) end With7Event->>With7Event: draggingEvent = $draggingEvent Note over With7Event: movingLoop() in requestAnimationFrame With7Event->>With7Event: getCanvasXyByViewXy(draggingEventXy) With7Event->>With7Event: Calculate newX, newY, buff_x, buff_y With7Event->>Base: emitEvent(RGEventNames.onNodeDragging, node, newX, newY, buff_x, buff_y, e) Base->>Listeners: listeners.onNodeDragging?(...) Listeners-->>Base: return {x?, y?} or undefined Base-->>With7Event: customPosition alt customPosition returned With7Event->>With7Event: Override newX/newY with customPosition end With7Event->>With7Event: canvasAutoMoving(draggingEventXy) With7Event->>With7Event: draggingSelectedNodes(node, newX, newY, buff_x, buff_y) With7Event->>DataProvider: updateNode(node.id, {x: newX, y: newY}) With7Event->>With7Event: _dataUpdated() end User->>DragUtils: mouseup DragUtils->>With7Event: dragEnd(x_buff, y_buff, $dragEndEvent) With7Event->>With7Event: cancelAnimationFrame(this._canvasMovingTimer) With7Event->>DataProvider: updateOptions({draggingNodeId: ''}) alt dragStarted With7Event->>Base: emitEvent(RGEventNames.onNodeDragEnd, node, e, x_buff, y_buff) Base->>Listeners: listeners.onNodeDragEnd?(node, e, x_buff, y_buff) else no drag (click) With7Event->>With7Event: onNodeClick(node, e) end With7Event->>With7Event: _dataUpdated()

Implementation Details:

Aspect Implementation
Drag Detection Movement must exceed Math.abs(offsetX) + Math.abs(offsetY) > 4 to distinguish from click
Throttling requestAnimationFrame() loop processes only latest draggingEvent per frame (~60fps)
Position Calculation getCanvasXyByViewXy() converts view coordinates to canvas coordinates, accounting for zoom/offset
Auto-scroll canvasAutoMoving() checks if cursor is within 40px of viewport edge, pans canvas accordingly
Multi-node Drag draggingSelectedNodes() moves all nodes in editingController.nodes by same buff_x, buff_y
Reference Lines updateReferenceLineView() calculates alignment with nearby nodes, returns snap coordinates
Drag Cleanup Clears _nodeXYMappingBeforeDrag, resets draggingNodeId, calls _dataUpdated()

State Tracking:

  • _nodeXYMappingBeforeDrag: {[nodeId:string]: {x, y}} - Stores original positions of dragged nodes
  • _canvasMovingTimer: number - requestAnimationFrame() timer ID for drag loop
  • draggingEvent: RGUserEvent - Latest mouse/touch event, processed once per frame
  • dragStarted: boolean - Tracks whether movement exceeded threshold (4px)

Sources: packages/relation-graph-models/models/RelationGraphWith7Event.ts:80-190, packages/relation-graph-models/models/RelationGraphWith91Editing.ts:348-379, packages/relation-graph-models/utils/RGDragUtils.ts


Event Cancellation and Return Values

Certain events support cancellation or position override through their return values:

Boolean Return - Cancel Default Behavior

Events that return boolean | void allow cancellation by returning false:

Event Default Behavior Cancelled
beforeZoomStart Prevents zoom operation
beforeNodeResize Prevents node resize
beforeCreateLine Prevents line creation
beforeScrollStart Prevents canvas scroll

Example:

onBeforeCreateLine: (lineInfo) => {
  if (lineInfo.fromNode.id === lineInfo.toNode.id) {
    console.log('Self-loops not allowed');
    return false; // Cancel line creation
  }
}

Sources: packages/relation-graph-models/types.ts:489-593

Position Return - Override Coordinates

Drag events that return RGPosition | undefined allow custom positioning logic:

Event Position Override Effect
onNodeDragging Override computed node position during drag
onCanvasDragging Override computed canvas offset during pan

Example - Snap to Grid:

onNodeDragging: (node, newX, newY, buffX, buffY, e) => {
  const gridSize = 20;
  return {
    x: Math.round(newX / gridSize) * gridSize,
    y: Math.round(newY / gridSize) * gridSize
  };
}

The system respects the returned coordinates and uses them instead of the calculated values.

Sources: packages/relation-graph-models/models/RelationGraphWith7Event.ts:129-139, packages/relation-graph-models/models/RelationGraphBase.ts:188-209


Drag Utilities: RGDragUtils

The RGDragUtils module provides a unified drag handling abstraction that works with both mouse and touch events.

Core Method Signature:

RGDragUtils.startDrag(
  e: RGUserEvent,
  basePosition: RGPosition,
  dragEnd: (x_buff, y_buff, endEvent) => void,
  dragging: (offsetX, offsetY, basePosition, startEventInfo, currentEvent) => void
)

Workflow:

  1. Event Normalization - Detects mouse vs. touch via isSupportTouch(e), extracts coordinates using getClientCoordinate(e)
  2. Listener Registration - Attaches mousemove/touchmove and mouseup/touchend to document.body
  3. Tick Callback - Invokes dragging() callback on every move event with offset calculations
  4. Cleanup - Invokes dragEnd() callback, removes event listeners on mouse/touch up

Used By:

  • onNodeDragStart() - Node dragging
  • onCanvasDragStart() - Canvas panning
  • onResizeStart() - Node resizing via editing controller
  • onVisibleViewHandleDragStart() - Mini-view viewport dragging

Sources: packages/relation-graph-models/utils/RGDragUtils.ts


Performance Considerations

Event Throttling with requestAnimationFrame

High-frequency events use requestAnimationFrame() throttling to limit updates to display refresh rate:

let draggingEvent: RGUserEvent;
const draggingCallback = () => {
  if (!draggingEvent || dragStoped) return;
  // Process draggingEvent...
};
const movingLoop = () => {
  draggingCallback();
  this._canvasMovingTimer = requestAnimationFrame(movingLoop);
};
this._canvasMovingTimer = requestAnimationFrame(movingLoop);

Benefits:

  • Limits onNodeDragging and onCanvasDragging emission to ~60fps
  • Prevents excessive React/Vue re-renders
  • Only latest event per frame is processed

Used In:

Touch vs Mouse Detection

The system optimizes UI for touch devices by detecting interaction type:

const isTouchEvent = isSupportTouch(e);
if (!isTouchEvent) {
  this.dataProvider.updateOptions({
    showTemplateNode: true  // Show hover guide for mouse only
  });
}

Utility Functions:

  • isSupportTouch(e: RGUserEvent): boolean - Checks if event is TouchEvent
  • getClientCoordinate(e: RGUserEvent): {clientX, clientY} - Extracts coordinates from mouse or touch event

Sources: packages/relation-graph-models/models/RelationGraphWith7Event.ts:392-397, packages/relation-graph-models/utils/RGCommon.ts

Debounced Layout Recalculation

Batch node expand/collapse operations use debouncing to avoid redundant layouts:

private _relayoutTaskTimer;
private _effectWhenExpandedOrCollapsed(node: RGNode) {
  if (this._relayoutTaskTimer) {
    clearTimeout(this._relayoutTaskTimer);
  }
  this._relayoutTaskTimer = setTimeout(() => {
    this.updateNodesVisibleProperty([node].concat(descendantNodes));
    if (options.reLayoutWhenExpandedOrCollapsed) {
      this.doLayout();
    }
  }, 100);
}

Purpose: When expandNode() or collapseNode() called multiple times in quick succession, only one layout executes after 100ms delay.

Sources: packages/relation-graph-models/models/RelationGraphWith7Event.ts:308-327


Summary

The relation-graph event system provides:

  • Unified event model across all frameworks (Vue, React, Svelte)
  • Type-safe event definitions via RGEventNames enum and RGListeners interface
  • Flexible registration through declarative props or programmatic handlers
  • Event cancellation and override mechanisms for custom behavior
  • Multi-layer handler execution supporting middleware patterns
  • Touch and mouse support with automatic detection and adaptation
  • Performance optimizations including throttling and debouncing

For architectural details about the event dispatcher and handler chain, continue to Event System Architecture. For concrete examples of implementing user interactions, see User Actions & Interactions.

Sources: packages/relation-graph-models/models/RelationGraphWith7Event.ts, packages/relation-graph-models/models/RelationGraphBase.ts, packages/relation-graph-models/types.ts:417-680