JavaScript is required

交互式连线路径编辑

这个示例展示如何点击已有连线,并通过 relation-graph 内置连线编辑覆盖层重塑路径。它还在紧凑编辑式工作区中协调了节点选中、画布清空、运行时画布设置和 toast 反馈。

使用内置覆盖层控制柄编辑现有连线路径

这个示例构建了什么

这个示例构建了一个紧凑的图编辑工作区,用于修改已有连接的路径。界面包含一个全高图画布、一个浮动说明窗口、默认显示为绿色的弧形连线,以及一个点状背景,让画布更像可编辑的工作面,而不是只读查看器。

用户可以点击连线来激活内置的路径编辑控制柄,在画布上拖拽进行框选,并通过点击空白区域清除当前编辑状态。这个示例最值得关注的地方在于,它把连线路径编辑单独抽离出来,而没有扩展成一个完整的图编辑器,因此选择状态与连线编辑之间的关系更容易观察和理解。

数据是如何组织的

图数据以内联方式声明为一个 RGJsonData 对象。它使用 rootId、一个仅包含 idtext 字段的扁平 nodes 数组,以及一个包含 fromtotext 和部分连线级覆盖项(如 lineShape 和 junction points)的 lines 数组。

在调用 setJsonData() 之前没有任何预处理流程。组件会在 initializeGraph() 中创建这份数据集,在挂载时加载它,然后调用 zoomToFit(),使默认图布局能够立即完整显示出来。

在生产系统中,同样的数据结构可以表示工作流步骤、依赖边、审批路径、血缘连接,或其他任何在图渲染完成后仍可能需要手动调整连接路径的记录集合。

relation-graph 是如何使用的

这个示例没有配置自定义布局算法。相反,它将一个小型的基于根节点的数据集加载到 RelationGraph 中,让 relation-graph 按照库的默认行为进行渲染,然后再通过 zoomToFit() 调整视口。

图选项被刻意保持得很少:defaultLineShape 设置为 RGLineShape.StandardCurvedefaultLineColor 设置为 #00a63e。这个示例的大部分重点都放在运行时编辑 API 上,而不是初始布局配置。

RGProvider 包裹了整个示例,因此 RGHooks.useGraphInstance() 可以获取当前激活的图实例。该实例负责驱动 setJsonDatazoomToFitsetEditingNodessetEditingLinetoggleEditingNodegetNodesInSelectionViewclearCheckedaddLines。浮动设置面板还会使用 RGHooks.useGraphStore() 读取当前选项,并通过 setOptions() 在运行时切换滚轮和拖拽行为。

编辑 UI 挂载在 RGSlotOnView 中,而不是通过替换节点或连线渲染器来实现。RGEditingConnectControllerRGEditingLineController 作为视图层覆盖组件被挂载,其中 textEditable={false}pathEditable={true}。这一组合让示例专注于路径几何形状,而不是标签编辑。

样式表定制的是视觉外壳,而不是图模型。它利用 relation-graph 的变换变量添加了一个会随缩放变化的点状画布背景,保持连线标签沿用各自连线当前的颜色,并对已选中连线的标签进行反色处理,从而让当前活动连接在编辑过程中更突出。

关键交互

  • 点击一条连线会将该连线设为当前编辑目标,并显示内置的路径编辑覆盖层。
  • 拖动连线路径控制柄时,会在路径变化以及顶点移动生命周期事件期间触发 Toast 反馈。
  • 点击节点且不带修饰键时,会用该节点替换当前的编辑节点集合;按住 ShiftCtrlMeta 点击时,则会在当前编辑集合中切换该节点。
  • 框选会收集选择框中的所有节点,并用这些结果替换当前的编辑节点集合。
  • 点击画布空白区域会清除编辑节点、清除当前编辑连线,并移除 checked 状态高亮。
  • 打开浮动设置面板后,用户可以切换滚轮行为、切换画布拖拽行为,并将画布导出为图片。

关键代码片段

这段代码展示了该示例使用的是内联图数据集,并在挂载时直接加载它。

const myJsonData: RGJsonData = {
    rootId: 'a',
    nodes: [
        { id: 'a', text: 'Border color' },
        { id: 'a1', text: 'No border' },
        { id: 'a2', text: 'Plain' },
        // ... more nodes and lines ...
    ]
};

这段代码展示了挂载时的加载步骤:将数据集传入 relation-graph,并让视口自动适配。

// setJsonData is async
await graphInstance.setJsonData(myJsonData);
graphInstance.zoomToFit();

这段代码展示了节点选择与连线选择如何保持同步,而不是被当作两个彼此无关的模式。

const onNodeClick = (nodeObject: RGNode, $event: RGUserEvent) => {
    if ($event.shiftKey || $event.ctrlKey || ($event.metaKey && !$event.altKey)) {
        graphInstance.toggleEditingNode(nodeObject);
    } else {
        graphInstance.setEditingNodes([nodeObject]);
    }
    graphInstance.setEditingLine(null);
};

这段代码展示了画布层面的状态交接:在一次框选结束后,用新的结果替换编辑节点集合。

const onCanvasSelectionEnd = (selectionView: RGSelectionView) => {
    const willSelectedNodes = graphInstance.getNodesInSelectionView(selectionView) || [];
    graphInstance.setEditingNodes(willSelectedNodes);
};

这段代码展示了精确的覆盖层配置:已有连线可以编辑路径,但内联文本编辑保持关闭。

<RGSlotOnView>
    <RGEditingConnectController />
    <RGEditingLineController
        textEditable={false}
        pathEditable={true}
        onLinePathChanged={onLinePathChanged}
        onMoveLineVertexStart={onMoveLineVertexStart}
        onMoveLineVertexEnd={onMoveLineVertexEnd}
    />
</RGSlotOnView>

这段代码展示了该示例并不只是可视化地暴露控制柄:它还会响应编辑生命周期回调,并向用户反馈结果。

const onMoveLineVertexEnd = (
    fromNode: RGNode | RGLineTarget | RGPosition,
    toNode: RGNode | RGLineTarget | RGPosition,
    newLineJson?: JsonLine
) => {
    if (newLineJson && newLineJson.from && newLineJson.to) {
        graphInstance.addLines([newLineJson]);
        SimpleGlobalMessage.success('LineVertexChanged:' + newLineJson.text);
    } else {
        SimpleGlobalMessage.error('Line Removed!');
    }
};

这段代码展示了样式表如何把画布变成一个点状编辑工作面,并让已选中连线的标签更醒目。

background-position: var(--rg-canvas-offset-x) var(--rg-canvas-offset-y);
background-size: calc(var(--rg-canvas-scale) * 15px) calc(var(--rg-canvas-scale) * 15px);
background-image: radial-gradient(
    circle,
    rgb(197, 197, 197) calc(var(--rg-canvas-scale) * 1px),
    transparent 0
);

这个示例的独特之处

与附近的 change-line-verticeschange-line-text 等示例相比,这个示例是最清晰的路径几何参考。它启用了 pathEditable={true},同时保持 textEditable={false},因此重点在于重塑一条已存在连线的路径,而不是重新连接端点或维护标签。

它在偏编辑器风格的 VIP 示例中也很突出,因为它在一个很小的文件集合中组合了几种少见能力:已选中连线的覆盖层控制柄、节点/连线/画布点击之间的选择状态交接、路径编辑时的 Toast 反馈,以及带有已选中连线强调效果的点状画布。这种组合使它成为一个很实用的起点,适合那些希望获得可编辑边路径、但又不想引入更大图编辑界面的团队。

对比数据还清楚地划定了一个边界。它不是自定义工具栏示例,不是节点对齐示例,也不是从节点发起创建连线的示例。它最有辨识度的价值,在于对一条已经存在的连接路径进行内置编辑。

这种模式还适用于哪里

这种模式可以复用到工作流设计器中,在自动布局之后让用户继续整理连接线路径。它同样适用于依赖关系图、数据血缘工具和架构地图,因为这些场景中的图结构通常是预先已知的,但部分边仍需要通过手动修正路径来提升可读性。

第二个扩展方向是审查或维护类工具。团队可以沿用同样的选择状态交接与回调模式,来持久化编辑后的路径、记录几何变更的审计事件,或者在接受更新后的连线路径之前附加验证规则。