JavaScript is required

View Control & Effects

Purpose and Scope

This document covers the view control and visual effects system in relation-graph, which provides APIs for manipulating the canvas viewport and creating smooth visual transitions. The system is implemented in the RelationGraphWith5Zoom and RelationGraphWith6Effect classes, which form part of the core class hierarchy see 3.1.

Scope of this document:

  • Canvas transformation operations (zoom, pan)
  • Focus operations (center nodes, zoom-to-fit)
  • Animation controls for nodes and canvas
  • Performance mode integration with zoom levels

Related topics:


Canvas Coordinate System

The relation-graph canvas uses a transformation-based coordinate system consisting of two primary components:

Property Type Description
canvasOffset {x: number, y: number} Pan offset in pixels, represents the translation of the entire graph
canvasZoom number Zoom level as a percentage (100 = 100%, 50 = 50%, etc.)

These properties are stored in RGOptions and accessed through the data provider. All view control operations ultimately modify these values to transform the canvas.

Coordinate transformation:

displayX = (nodeX * zoomScale) + canvasOffset.x
displayY = (nodeY * zoomScale) + canvasOffset.y
where zoomScale = canvasZoom / 100


Zoom System Architecture

The zoom system provides two primary methods for zoom operations:

graph TB
    subgraph "Public API"
        zoom["zoom(buff, userZoomCenter?, e?)"]
        setZoom["setZoom(finalZoom, userZoomCenter?)"]
    end
    
    subgraph "Zoom Calculation"
        bounds["Check min/max bounds
(minCanvasZoom, maxCanvasZoom)"] event["Emit beforeZoomStart event"] scale["Calculate new scale
oldScale + (buff/100)"] offset["Adjust canvasOffset
to maintain zoom center"] end subgraph "Side Effects" perfHook["_performanceModeLogicHook"] editView["_updateEditingControllerView"] dataUpdate["_dataUpdated"] zoomEvent["Emit onZoomEnd event"] end subgraph "Performance Mode Logic" checkThreshold{"newZoom <= 40?"} showEasy["Set showEasyView = true"] hideEasy["Set showEasyView = false"] updateLines["updateElementLines"] end zoom --> event setZoom --> zoom event --> bounds bounds --> scale scale --> offset offset --> perfHook perfHook --> checkThreshold checkThreshold -->|Yes| showEasy checkThreshold -->|No| hideEasy hideEasy --> updateLines perfHook --> editView editView --> dataUpdate dataUpdate --> zoomEvent

Diagram: Zoom Operation Flow

zoom() Method

The zoom() method applies a relative zoom adjustment:

  • Parameters:

    • buff: Zoom delta in percentage points (e.g., 10 for +10%, -10 for -10%)
    • userZoomCenter: Optional center point for zoom in client coordinates
    • e: Optional wheel event object
  • Behavior:

    1. Emits beforeZoomStart event (can abort if returns true)
    2. Clamps zoom to minCanvasZoom and maxCanvasZoom bounds
    3. Calculates new scale: newScale = (canvasZoom + buff) / 100
    4. Adjusts canvas offset to maintain zoom center position
    5. Updates canvasOffset and canvasZoom in options
    6. Calls performance mode logic hook
    7. Emits onZoomEnd event with new and old zoom values

Zoom center calculation:

mouseX = userZoomCenter.x - viewRectBox.left
mouseY = userZoomCenter.y - viewRectBox.top
newX = mouseX - (mouseX - canvasOffset.x) * (newScale / oldScale)
newY = mouseY - (mouseY - canvasOffset.y) * (newScale / oldScale)

This formula ensures that the point under the mouse cursor (or specified center) remains stationary during zoom.

setZoom() Method

The setZoom() method sets an absolute zoom level:

  • Parameters:

    • finalZoom: Target zoom level as percentage (e.g., 100 for 100%)
    • userZoomCenter: Optional center point for zoom
  • Implementation: Calculates the delta from current zoom and calls zoom():

    const buff = Math.round(finalZoom - options.canvasZoom);
    this.zoom(buff, userZoomCenter);
    


Performance Mode Integration

The zoom system integrates with performance mode through the _performanceModeLogicHook() method, which automatically switches rendering modes based on zoom level:

Zoom Level Rendering Mode Behavior
> 40% Standard SVG Full SVG rendering with all details
≤ 40% Easy View (Canvas) Simplified canvas-based rendering for performance

Logic flow:

graph LR
    zoomChange["Zoom Change"]
    oldCheck{"oldZoom <= 40?"}
    newCheck1{"newZoom > 40?"}
    newCheck2{"newZoom <= 40?"}
    
    hideEasy["showEasyView = false
updateElementLines()"] showEasy["showEasyView = true"] noChange["No change"] zoomChange --> oldCheck oldCheck -->|Yes| newCheck1 oldCheck -->|No| newCheck2 newCheck1 -->|Yes| hideEasy newCheck1 -->|No| noChange newCheck2 -->|Yes| showEasy newCheck2 -->|No| noChange

Diagram: Performance Mode Switching Logic

The 40% threshold provides a balance between visual quality and rendering performance. When zooming out to view large graphs, the system automatically switches to canvas-based rendering see 6.3.


Pan Operations

Pan operations modify the canvasOffset property to move the visible area of the graph. The primary method is setCanvasCenter():

setCanvasCenter()

Moves a specific canvas coordinate to the center of the viewport:

setCanvasCenter(x: number, y: number) {
    this.dataProvider.setCanvasCenter(x, y);
    this._dataUpdated();
}

The data provider calculates the required offset to center the given coordinates:

canvasOffset.x = (viewSize.width / 2) - (x * zoomScale)
canvasOffset.y = (viewSize.height / 2) - (y * zoomScale)


Focus Operations

The RelationGraphWith6Effect class provides several high-level operations for focusing on specific nodes or content areas.

Focus Operation Methods

graph TB
    subgraph "Public API"
        focusById["focusNodeById(nodeId)"]
        zoomToFit["zoomToFit(nodes?)"]
        moveCenter["_moveToCenter(nodes?)"]
        fitHeight["fitContentHeight(padding)"]
    end
    
    subgraph "Calculations"
        getNode["getNodeById()
Retrieve node object"] getRect["getNodesRectBox()
Calculate bounding box"] getCenter["getNodesCenter()
Calculate center point"] end subgraph "Transform Operations" setZoomOp["setZoom()"] setCenterOp["setCanvasCenter()"] setOffsetOp["setCanvasOffset()"] end focusById --> getNode getNode --> focusNode["focusNode(thisNode)"] focusNode --> setZoomOp focusNode --> setOffsetOp zoomToFit --> getRect zoomToFit --> moveCenter getRect --> calcZoom["Calculate zoom to fit"] calcZoom --> setZoomOp moveCenter --> getCenter getCenter --> setCenterOp fitHeight --> getRect fitHeight --> setCenterOp fitHeight --> setZoomOp

Diagram: Focus Operation Methods and Dependencies

zoomToFit()

Automatically calculates the optimal zoom level and position to display specified nodes:

Algorithm:

  1. Calculate bounding box of target nodes using getNodesRectBox()
  2. Calculate zoom ratios for both dimensions with padding:
    zoomPercentX = viewWidth / (boxWidth + padding * 2)
    zoomPercentY = viewHeight / (boxHeight + padding * 2)
    
  3. Use the smaller ratio (clamped to max 100%):
    zoomPercent = Math.min(zoomPercentX, zoomPercentY, 1)
    
  4. Center the nodes using _moveToCenter()
  5. Apply the calculated zoom using setZoom()

Default behavior: If no nodes are specified, operates on all nodes in the graph.

focusNodeById()

Focuses on a specific node by its ID:

  1. Retrieves node using getNodeById(nodeId)
  2. If found, calls private focusNode() method
  3. Sets zoom to 100%
  4. Calculates offset to center the node in the viewport
  5. Updates checkedNodeId option to mark the focused node

Calculation for centering:

finalX = -nodeX + (viewWidth / 2) - (nodeWidth / 2)
finalY = -nodeY + (viewHeight / 2) - (nodeHeight / 2)

fitContentHeight()

Adjusts the canvas height to exactly fit the content while maintaining width:

Process:

  1. Temporarily sets canvas opacity to 0.01 (nearly invisible)
  2. Calculates nodes bounding box
  3. Determines scale factor: scale = min(1, viewWidth / nodesWidth)
  4. Calculates new height: newHeight = nodesHeight * scale + padding * 2
  5. Updates view size options
  6. After 200ms delay:
    • Applies calculated zoom
    • Centers content using _moveToCenter()
    • Restores canvas opacity to 1

This method is useful for scenarios where you want the graph to be fully visible without scrolling, such as in embedded widgets or report generation.


Animation System

The relation-graph animation system provides two independent animation controls:

Node Position Animation

Controls smooth transitions when node positions change (e.g., during layout):

Method Effect
enableNodeXYAnimation() Enables smooth position transitions for nodes
disableNodeXYAnimation() Nodes jump directly to new positions

Controlled by the enableNodeXYAnimation option in RGOptions. When enabled, CSS transitions or framework-specific animation mechanisms handle the smooth movement.

Canvas Transform Animation

Controls smooth transitions for canvas zoom and pan operations:

Method Effect
enableCanvasAnimation() Enables smooth zoom/pan transitions
disableCanvasAnimation() Canvas transforms apply instantly

Controlled by the enableCanvasTransformAnimation option. When enabled, provides a smoother user experience during navigation but may impact performance on large graphs.


Effect Operation Integration

The following diagram shows how view control operations integrate with the broader system:

graph TB
    subgraph "User/API Triggers"
        userZoom["Mouse Wheel
Pinch Gesture"] apiCall["API Calls
setZoom, zoomToFit,
focusNodeById"] layoutDone["Layout Completion"] end subgraph "RelationGraphWith5Zoom" zoomMethod["zoom()"] setZoomMethod["setZoom()"] end subgraph "RelationGraphWith6Effect" zoomToFitMethod["zoomToFit()"] focusMethod["focusNodeById()"] fitHeightMethod["fitContentHeight()"] animationToggles["enable/disable
NodeXYAnimation
CanvasAnimation"] end subgraph "Data Provider" updateOpts["updateOptions()
canvasZoom
canvasOffset
enableNodeXYAnimation
enableCanvasTransformAnimation"] setCenterMethod["setCanvasCenter()"] setOffsetMethod["setCanvasOffset()"] end subgraph "Reactive Update" dataUpdated["_dataUpdated()"] updateHook["updateViewHook()"] frameworkReact["Framework Reactivity
Vue refs, React state"] end subgraph "View Rendering" componentRerender["Component Re-render"] canvasRedraw["Canvas/EasyView Update"] end userZoom --> zoomMethod apiCall --> setZoomMethod apiCall --> zoomToFitMethod apiCall --> focusMethod apiCall --> fitHeightMethod apiCall --> animationToggles layoutDone --> zoomToFitMethod setZoomMethod --> zoomMethod zoomToFitMethod --> setZoomMethod focusMethod --> setZoomMethod fitHeightMethod --> setZoomMethod zoomMethod --> updateOpts zoomToFitMethod --> setCenterMethod focusMethod --> setOffsetMethod fitHeightMethod --> setCenterMethod animationToggles --> updateOpts setCenterMethod --> updateOpts setOffsetMethod --> updateOpts updateOpts --> dataUpdated dataUpdated --> updateHook updateHook --> frameworkReact frameworkReact --> componentRerender componentRerender --> canvasRedraw

Diagram: View Control Integration Flow

All view control operations follow a consistent pattern:

  1. API method called (user interaction or programmatic)
  2. Transform calculations performed
  3. Options updated via data provider
  4. _dataUpdated() triggers reactive update
  5. Framework-specific rendering updates view


Key Implementation Details

Zoom Bounds Enforcement

The zoom system enforces configurable bounds to prevent extreme zoom levels:

  • minCanvasZoom: Minimum allowed zoom (default varies by framework)
  • maxCanvasZoom: Maximum allowed zoom (default varies by framework)

When zoom() is called, the method automatically clamps the resulting zoom level:

if ((options.canvasZoom + buff) < options.minCanvasZoom) {
    buff = options.minCanvasZoom - options.canvasZoom;
} else if ((options.canvasZoom + buff) > options.maxCanvasZoom) {
    buff = options.maxCanvasZoom - options.canvasZoom;
}

If the clamped buff becomes 0, the operation is aborted.

Event Integration

View control operations emit events at key points:

Event Triggered By Parameters Cancellable
beforeZoomStart zoom() (currentZoom, buff, wheelEvent) Yes (return true)
onZoomEnd zoom() (newZoom, oldZoom) No

The beforeZoomStart event can prevent zoom operations by returning true, allowing parent components to implement custom zoom restrictions.

View Rectangle Query

Multiple operations depend on getViewBoundingClientRect(), which retrieves the current viewport dimensions in client coordinates. This is essential for:

  • Calculating zoom center points
  • Determining canvas-to-client coordinate transformations
  • Viewport-based visibility checks

The method queries the actual DOM element’s bounding client rect, ensuring accurate positioning even when the graph is embedded in complex layouts.


Summary

The view control and effects system provides:

  1. Zoom operations via zoom() and setZoom() with automatic center-point preservation
  2. Pan operations through setCanvasCenter() and direct offset manipulation
  3. Focus operations including zoomToFit(), focusNodeById(), and fitContentHeight()
  4. Animation controls for both node positions and canvas transforms
  5. Performance mode integration that automatically switches rendering modes at zoom thresholds
  6. Event-driven architecture allowing custom behavior and cancellation

All operations are coordinated through the data provider and trigger reactive updates, ensuring consistent behavior across Vue 2/3, React, and Svelte implementations see 10.