JavaScript is required

连线端点编辑

这是一个紧凑 relation-graph 编辑器,用户可点击已有连线并拖动起点或终点手柄到其他节点。示例禁用了线文字和路径编辑,在顶点移动生命周期中提供 toast 反馈,并通过悬浮辅助窗提供画布设置与图片导出。

重新指定现有连线端点

这个示例构建了什么

这个示例构建了一个小型的编辑器风格关系图,用户可以选中一条已有连线,并将其可见的起点或终点控制手柄拖动到另一个节点上。图谱会铺满整个视口,使用带点状纹理的工作区背景,并在画布上方保留一个悬浮的辅助窗口。它的关注点被刻意收窄:仅演示端点重连,不暴露连线文本编辑或路径编辑。

数据是如何组织的

图数据在 MyGraph.tsx 中以内联方式声明为一个 RGJsonData 对象。它包含一个 rootId、一个带有 idtext 的节点数组,以及一个带标签的连线数组,连线项包含 idtextfromto

在调用 setJsonData(...) 之前没有本地预处理步骤。组件会直接创建最终的 JSON 字面量,将其直接加载到图实例中,然后调用 zoomToFit()。在业务图中,相同的数据结构可以表示依赖关系、审批链、拓扑连接,或任何后续可能需要重新挂接边端点的记录。

relation-graph 是如何使用的

这个示例包裹在 RGProvider 中,然后使用 RGHooks.useGraphInstance() 作为初始化、编辑状态更新和辅助窗口操作的核心控制面。RelationGraph 接收一个紧凑的 options 对象,将 defaultLineShape 设置为 RGLineShape.StandardCurve,并将 defaultLineColor 设置为 #00a63e

编辑 UI 通过 RGSlotOnView 注入,示例在这里同时挂载了 RGEditingConnectControllerRGEditingLineController。其中 line controller 配置了 textEditable={false}pathEditable={false},使交互只聚焦在起点和终点的重新连接上。

图实例 API 驱动了其余工作流:

  • setJsonData()zoomToFit() 在挂载时负责加载图数据并让图自适应显示。
  • toggleEditingNode()setEditingNodes()setEditingLine() 负责管理哪些节点或哪条连线当前处于编辑模式。
  • getNodesInSelectionView() 会把拖框选择转换为当前的编辑节点集合。
  • clearChecked() 在用户点击空白画布时重置选中状态。
  • addLines() 负责在端点移动成功后处理 controller 回调结果。

悬浮辅助窗口为 relation-graph 集成增加了第二层能力。它的设置面板通过 RGHooks.useGraphStore() 读取实时 store,通过 setOptions() 更新 wheelEventActiondragEventAction,并使用 prepareForImageGeneration()getOptions()restoreAfterImageGeneration() 实现图片导出。

样式设计刻意保持轻量但足够实用。样式表构建了带点的画布背景,并让被选中的连线标签继承当前连线颜色;选中后再反转为白色文字和带填充色的标签。

关键交互

  • 点击一条连线会将这条连线记录为当前编辑中的连线,这也是启用内置端点控制手柄的前提。
  • 拖动已选中连线的端点时,会触发 onMoveLineVertexStart 显示警告提示,并触发 onMoveLineVertexEnd 显示成功或删除反馈。
  • 点击节点且不带修饰键时,会用该节点替换当前编辑节点集合;按住 ShiftCtrl 或受支持的 Meta 行为点击时,则会在当前编辑集合中切换该节点的加入或移除状态。
  • 在画布上进行拖框选择时,会解析选区内的节点,并将它们提升为当前编辑节点集合。
  • 点击空白画布会清空编辑节点、清空当前编辑连线,并重置选中状态,使用户能够快速退出当前编辑上下文。
  • 悬浮窗口可以展开为设置面板,用户可在其中切换滚轮行为、切换画布拖拽行为,并下载当前图谱的图片。

关键代码片段

这个片段展示了示例如何从一个静态内联图数据开始,并在不做本地转换的情况下直接将其加载到 relation-graph 中。

const initializeGraph = async () => {
    const myJsonData: RGJsonData = {
        rootId: 'a',
        nodes: [
            { id: 'a', text: 'Border color' },
            { id: 'a1', text: 'No border' },
            // nodes and lines omitted
        ]
    };
    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 onLineClick = (lineObject: RGLine, linkObject: RGLink) => {
    graphInstance.setEditingLine(lineObject);
};

这个片段展示了端点编辑生命周期回调如何为内置连线编辑器附加自定义反馈。

const onMoveLineVertexStart = (type: RGLineEditPoint, line: RGLine) => {
    SimpleGlobalMessage.warning('MoveLineVertexStart:' + type+ ':' + line.text);
};
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!');
    }
};

这个片段展示了可见的连线编辑覆盖层被限制为只处理端点操作。

<RGSlotOnView>
    {/* Node connection point selector component */}
    <RGEditingConnectController />
    <RGEditingLineController
        textEditable={false}
        pathEditable={false}
        onMoveLineVertexStart={onMoveLineVertexStart}
        onMoveLineVertexEnd={onMoveLineVertexEnd}
    />
</RGSlotOnView>

这个片段展示了一个小型样式覆盖,用于让被选中的连线标签看起来像当前编辑目标的一部分。

.rg-line-peel {
    .rg-line-label {
        color: var(--rg-line-color);
    }
}
.rg-line-peel.rg-line-checked {
    .rg-line-label {
        background-color: var(--rg-line-color);
        color: #fff;
    }
}

这个示例的独特之处

与附近的其他编辑类示例相比,这个示例在范围控制上异常严格。对比数据表明,它最主要的差异点是“仅端点”工作流:它使用内置的已选中连线编辑器,但明确关闭了连线文本编辑和连线路径编辑。

与许多类似的紧凑示例相比,它也更强调编辑生命周期。这里的 onMoveLineVertexStartonMoveLineVertexEnd 并不只是被动钩子;它们被用来展示警告、成功和失败消息,并在编辑最终得到有效端点时追加回调提供的 newLineJson

对比文件还将这个示例与 change-line-textchange-line-pathcustomize-line-toolbarline-vertex-on-node 放得很近,但原因各不相同:

  • 相比 change-line-text,当用户需要重新连接一条边,但又不希望同时编辑其标签时,这个示例是更聚焦的参考。
  • 相比 change-line-path,它聚焦的是端点重定向,而不是连线路由形状调整。
  • 相比 customize-line-toolbar,它依赖内置端点控制手柄,而不是自定义连线操作栏。
  • 相比 line-vertex-on-node,它编辑的是一条现有边,而不是从一个已选中节点创建一条新连接。

因此,对于那些既需要重新连接现有关系、又希望编辑界面比完整图谱创作工具更精简的团队来说,它是一个很好的起点。

这种模式还能应用到哪里

这种模式非常适合那些用户需要修复或重定向现有关系、但不希望打开更完整边编辑控制的产品。

  • 在依赖图中,某个服务、包或流水线步骤需要重新连接到新的上游或下游节点。
  • 在工作流维护工具中,某条审核或审批边需要被重新指派,但不暴露自定义路径编辑能力。
  • 在网络或基础设施图中,某条连接需要从一个设备端点移动到另一个端点。
  • 在知识图谱整理工具中,应允许整理人员重新指定现有关系的目标,但不允许他们改写这条边的所有可视化细节。