JavaScript is required

视图控制与效果

目的与范围

本文档介绍 relation-graph 中的视图控制与视觉效果系统,它提供用于操控画布视口并创建平滑视觉过渡的 API。该系统实现于 RelationGraphWith5ZoomRelationGraphWith6Effect 类中,它们构成核心类层级的一部分 参见 3.1

本文档范围:

  • 画布变换操作(缩放、平移)
  • 聚焦操作(节点居中、缩放以适配)
  • 节点与画布的动画控制
  • 与缩放级别联动的性能模式集成

相关主题:


画布坐标系统

relation-graph 的画布使用基于变换的坐标系统,包含两个主要组件:

属性 类型 描述
canvasOffset {x: number, y: number} 以像素为单位的平移偏移量,表示整个图的平移
canvasZoom number 以百分比表示的缩放级别(100 = 100%,50 = 50%,等等)

这些属性存储在 RGOptions 中,并通过数据提供器访问。所有视图控制操作最终都会修改这些值来变换画布。

坐标变换:

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

缩放系统架构

缩放系统为缩放操作提供两个主要方法:

graph TB
    subgraph "公共 API"
        zoom["zoom(buff, userZoomCenter?, e?)"]
        setZoom["setZoom(finalZoom, userZoomCenter?)"]
    end
    
    subgraph "缩放计算"
        bounds["检查最小/最大边界
(minCanvasZoom, maxCanvasZoom)"] event["触发 beforeZoomStart 事件"] scale["计算新比例
oldScale + (buff/100)"] offset["调整 canvasOffset
以保持缩放中心"] end subgraph "副作用" perfHook["_performanceModeLogicHook"] editView["_updateEditingControllerView"] dataUpdate["_dataUpdated"] zoomEvent["触发 onZoomEnd 事件"] end subgraph "性能模式逻辑" checkThreshold{"newZoom <= 40?"} showEasy["设置 showEasyView = true"] hideEasy["设置 showEasyView = false"] updateLines["updateElementLines"] end zoom --> event setZoom --> zoom event --> bounds bounds --> scale scale --> offset offset --> perfHook perfHook --> checkThreshold checkThreshold -->|是| showEasy checkThreshold -->|否| hideEasy hideEasy --> updateLines perfHook --> editView editView --> dataUpdate dataUpdate --> zoomEvent

图示:缩放操作流程

zoom() 方法

zoom() 方法应用相对缩放调整:

  • 参数:

    • buff:以百分点表示的缩放增量(例如,10 表示 +10%,-10 表示 -10%)
    • userZoomCenter:可选的缩放中心点(客户端坐标)
    • e:可选的滚轮事件对象
  • 行为:

    1. 触发 beforeZoomStart 事件(如果返回 true 可中止)
    2. 将缩放限制在 minCanvasZoommaxCanvasZoom 边界内
    3. 计算新比例:newScale = (canvasZoom + buff) / 100
    4. 调整画布偏移以保持缩放中心位置
    5. 在选项中更新 canvasOffsetcanvasZoom
    6. 调用性能模式逻辑钩子
    7. 触发 onZoomEnd 事件并携带新旧缩放值

缩放中心计算:

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

该公式确保鼠标指针下(或指定中心点下)的点在缩放过程中保持静止。

setZoom() 方法

setZoom() 方法设置绝对缩放级别:

  • 参数:

    • finalZoom:目标缩放级别(百分比,例如 100 表示 100%)
    • userZoomCenter:可选的缩放中心点
  • 实现: 计算与当前缩放的差值并调用 zoom()

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

性能模式集成

缩放系统通过 _performanceModeLogicHook() 方法与性能模式集成,该方法会根据缩放级别自动切换渲染模式:

缩放级别 渲染模式 行为
> 40% 标准 SVG 完整的 SVG 渲染,包含所有细节
≤ 40% 简易视图(Canvas) 为性能而简化的基于画布的渲染

逻辑流程:

graph LR
    zoomChange["缩放变化"]
    oldCheck{"oldZoom <= 40?"}
    newCheck1{"newZoom > 40?"}
    newCheck2{"newZoom <= 40?"}
    
    hideEasy["showEasyView = false
updateElementLines()"] showEasy["showEasyView = true"] noChange["无变化"] zoomChange --> oldCheck oldCheck -->|是| newCheck1 oldCheck -->|否| newCheck2 newCheck1 -->|是| hideEasy newCheck1 -->|否| noChange newCheck2 -->|是| showEasy newCheck2 -->|否| noChange

图示:性能模式切换逻辑

40% 阈值在视觉质量与渲染性能之间提供了平衡。当缩小以查看大型图时,系统会自动切换到基于画布的渲染 参见 6.3


平移操作

平移操作通过修改 canvasOffset 属性来移动图的可视区域。主要方法是 setCanvasCenter()

setCanvasCenter()

将特定画布坐标移动到视口中心:

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

数据提供器会计算将给定坐标居中所需的偏移量:

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

聚焦操作

RelationGraphWith6Effect 类提供了若干用于聚焦到特定节点或内容区域的高层操作。

聚焦操作方法

graph TB
    subgraph "公共 API"
        focusById["focusNodeById(nodeId)"]
        zoomToFit["zoomToFit(nodes?)"]
        moveCenter["_moveToCenter(nodes?)"]
        fitHeight["fitContentHeight(padding)"]
    end
    
    subgraph "计算"
        getNode["getNodeById()
获取节点对象"] getRect["getNodesRectBox()
计算包围盒"] getCenter["getNodesCenter()
计算中心点"] end subgraph "变换操作" setZoomOp["setZoom()"] setCenterOp["setCanvasCenter()"] setOffsetOp["setCanvasOffset()"] end focusById --> getNode getNode --> focusNode["focusNode(thisNode)"] focusNode --> setZoomOp focusNode --> setOffsetOp zoomToFit --> getRect zoomToFit --> moveCenter getRect --> calcZoom["计算适配的缩放值"] calcZoom --> setZoomOp moveCenter --> getCenter getCenter --> setCenterOp fitHeight --> getRect fitHeight --> setCenterOp fitHeight --> setZoomOp

图示:聚焦操作方法及其依赖关系

zoomToFit()

自动计算用于显示指定节点的最佳缩放级别与位置:

算法:

  1. 使用 getNodesRectBox() 计算目标节点的包围盒
  2. 在加入内边距后,分别计算两个维度的缩放比例:
    zoomPercentX = viewWidth / (boxWidth + padding * 2)
    zoomPercentY = viewHeight / (boxHeight + padding * 2)
    
  3. 取较小的比例(并限制最大为 100%):
    zoomPercent = Math.min(zoomPercentX, zoomPercentY, 1)
    
  4. 使用 _moveToCenter() 将节点居中
  5. 使用 setZoom() 应用计算出的缩放值

默认行为: 如果未指定节点,则对图中的所有节点生效。

focusNodeById()

根据节点 ID 聚焦到特定节点:

  1. 使用 getNodeById(nodeId) 获取节点
  2. 若找到,则调用私有 focusNode() 方法
  3. 将缩放设置为 100%
  4. 计算偏移量以将节点置于视口中心
  5. 更新 checkedNodeId 选项以标记被聚焦的节点

居中计算:

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

fitContentHeight()

在保持宽度的同时,将画布高度调整为恰好适配内容:

过程:

  1. 临时将画布不透明度设置为 0.01(几乎不可见)
  2. 计算节点包围盒
  3. 确定缩放因子:scale = min(1, viewWidth / nodesWidth)
  4. 计算新高度:newHeight = nodesHeight * scale + padding * 2
  5. 更新视图尺寸选项
  6. 在 200ms 延迟后:
    • 应用计算出的缩放
    • 使用 _moveToCenter() 将内容居中
    • 将画布不透明度恢复为 1

该方法适用于希望图在无需滚动的情况下完全可见的场景,例如嵌入式小组件或报告生成。


动画系统

relation-graph 动画系统提供两套相互独立的动画控制:

节点位置动画

用于在节点位置变化时(例如布局过程中)控制平滑过渡:

方法 效果
enableNodeXYAnimation() 启用节点的平滑位置过渡
disableNodeXYAnimation() 节点直接跳到新位置

RGOptions 中的 enableNodeXYAnimation 选项控制。启用后,CSS 过渡或框架特定的动画机制会处理平滑移动。

画布变换动画

用于控制画布缩放与平移操作的平滑过渡:

方法 效果
enableCanvasAnimation() 启用平滑缩放/平移过渡
disableCanvasAnimation() 画布变换立即生效

enableCanvasTransformAnimation 选项控制。启用后可在导航时提供更平滑的用户体验,但在大型图上可能影响性能。


效果操作集成

下图展示了视图控制操作如何与更广泛的系统集成:

graph TB
    subgraph "用户/API 触发"
        userZoom["鼠标滚轮
双指捏合手势"] apiCall["API 调用
setZoom, zoomToFit,
focusNodeById"] layoutDone["布局完成"] end subgraph "RelationGraphWith5Zoom" zoomMethod["zoom()"] setZoomMethod["setZoom()"] end subgraph "RelationGraphWith6Effect" zoomToFitMethod["zoomToFit()"] focusMethod["focusNodeById()"] fitHeightMethod["fitContentHeight()"] animationToggles["启用/禁用
NodeXYAnimation
CanvasAnimation"] end subgraph "数据提供器" updateOpts["updateOptions()
canvasZoom
canvasOffset
enableNodeXYAnimation
enableCanvasTransformAnimation"] setCenterMethod["setCanvasCenter()"] setOffsetMethod["setCanvasOffset()"] end subgraph "响应式更新" dataUpdated["_dataUpdated()"] updateHook["updateViewHook()"] frameworkReact["框架响应式机制
Vue refs, React state"] end subgraph "视图渲染" componentRerender["组件重新渲染"] canvasRedraw["Canvas/EasyView 更新"] 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

图示:视图控制集成流程

所有视图控制操作遵循一致的模式:

  1. 调用 API 方法(用户交互或程序调用)
  2. 执行变换计算
  3. 通过数据提供器更新选项
  4. _dataUpdated() 触发响应式更新
  5. 框架特定的渲染更新视图

关键实现细节

缩放边界约束

缩放系统强制执行可配置的边界,以防止出现极端缩放级别:

  • minCanvasZoom:允许的最小缩放(默认值因框架而异)
  • maxCanvasZoom:允许的最大缩放(默认值因框架而异)

当调用 zoom() 时,该方法会自动对最终缩放值进行夹取:

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

如果夹取后的 buff 变为 0,则操作被中止。

事件集成

视图控制操作会在关键节点触发事件:

事件 触发方 参数 可取消
beforeZoomStart zoom() (currentZoom, buff, wheelEvent) 是(返回 true
onZoomEnd zoom() (newZoom, oldZoom)

beforeZoomStart 事件可通过返回 true 来阻止缩放操作,从而允许父组件实现自定义的缩放限制。

视图矩形查询

多个操作依赖 getViewBoundingClientRect(),它以客户端坐标获取当前视口尺寸。这对于以下场景至关重要:

  • 计算缩放中心点
  • 确定画布到客户端坐标的变换
  • 基于视口的可见性检查

该方法查询实际 DOM 元素的 bounding client rect,确保即使图嵌入在复杂布局中也能获得准确定位。


总结

视图控制与效果系统提供:

  1. 通过 zoom()setZoom() 实现 缩放操作,并自动保持中心点不变
  2. 通过 setCanvasCenter() 与直接操控偏移实现 平移操作
  3. 包括 zoomToFit()focusNodeById()fitContentHeight()聚焦操作
  4. 同时支持节点位置与画布变换的 动画控制
  5. 在缩放阈值处自动切换渲染模式的 性能模式集成
  6. 支持自定义行为与取消的 事件驱动架构

所有操作都通过数据提供器协同,并触发响应式更新,确保在 Vue 2/3、React 与 Svelte 实现中行为一致 参见 10