批量编辑选中节点
这个示例在一个居中的 relation graph 之上构建了一个轻量级的节点维护工作区。用户会看到一个位于点状全高画布上的小型图谱、一个悬浮辅助窗口、两个在加载时预选中的节点、当前选区上的尺寸调整手柄,以及一个停靠在所选节点上方的紧凑工具栏。
使用上下文工具栏批量编辑已选节点
这个示例构建了什么
这个示例在一个居中的 relation graph 之上构建了一个轻量级的节点维护工作区。用户会看到一个位于点状全高画布上的小型图谱、一个悬浮辅助窗口、两个在加载时预选中的节点、当前选区上的尺寸调整手柄,以及一个停靠在所选节点上方的紧凑工具栏。
主要交互模型是由选择驱动的编辑。用户可以单击某个节点将其设为当前目标,使用 Shift、Ctrl 或 Meta 点击来构建多选,在拖拽模式设为 selection 时使用画布选择框,一次性为所有已选节点改色,直接在画布上调整它们的大小,在一次操作中移除它们,并通过单击画布空白区域来清除选择。关键点在于,一个 editing-node 状态驱动了所有可见的编辑交互能力。
数据是如何组织的
图谱数据在 initializeGraph() 中以内联方式声明为一个 RGJsonData 对象,其中 rootId: 'a',包含 11 个节点和 11 条连线。每条连线都有自己显式的 id,并且这份数据是通过 setJsonData(...) 一次性加载的,而不是逐步组装。
在 relation-graph 消费这份数据之前,几乎没有任何预处理。在调用 setJsonData(...) 之后,代码会执行 zoomToFit(),按 id 查找节点 d 和 a,并将它们设为初始 editing-node 集合。在真实应用中,这种结构同样可以表示资产、任务、人员、目录条目或工作流实体,在这些场景里,操作人员需要选中多条记录并对它们应用同一种维护操作。
relation-graph 是如何使用的
RGProvider 包裹了这个示例,使图组件和共享叠加层工具都可以使用 relation-graph hooks。在 MyGraph 内部,RGHooks.useGraphInstance() 驱动了完整工作流:它通过 setJsonData(...) 加载内联数据集,使用 zoomToFit() 适配初始视图,通过 getNodeById(...) 解析启动节点,借助 setEditingNodes(...) 和 toggleEditingNode(...) 管理选择,使用 getNodesInSelectionView(...) 将选择框转换为节点对象,通过 setEditingLine(null) 清除连线编辑状态,使用 updateNode(...) 批量更新节点颜色,并借助 removeNodes(...) 删除当前选择。
图配置刻意保持得很小。layout.layoutName 被设为 center,因此这个 demo 的重点放在编辑行为上,而不是布局配置。随后,RelationGraph 组件连接了 onNodeClick、onCanvasSelectionEnd 和 onCanvasClick,从而使 editing-node 状态成为点击选择、选择框交接和空白画布清空这几种交互的共同目标。
可见的编辑 UI 是通过 relation-graph 子组件来组合的,而不是依赖原始 DOM 坐标进行自定义绝对定位。RGSlotOnView 承载了两层相对于视口保持稳定、而不会随着图画布缩放的叠加层。其一是共享的 DraggableWindow,它提供使用说明,以及用于配置 wheelEventAction、dragEventAction 和图片导出的设置面板。其二是 RGEditingNodeController,它将节点相关控件锚定到当前 editing-node 集合上;在它内部,RGEditingResize 提供尺寸调整手柄,同时一个自定义工具栏暴露了三个批量改色色块和一个批量删除操作。
+样式处理极简但有明确目的。局部 SCSS 通过 --rg-canvas-scale 和画布偏移变量,使用与缩放感知一致的点状图案覆盖 .relation-graph 背景,让画布读起来更像一个编辑表面,而不是一个普通查看器。
关键交互
单击节点会更新编辑选择。普通单击会用该节点替换当前选择,而在未按 Alt 的情况下按住 Shift、Ctrl 或 Meta 单击,则会将被点击节点切入或切出当前 editing-node 集合。在这两种情况下,代码还会清除任何激活中的连线编辑状态,从而让这个示例严格聚焦于节点操作。
当画布处于 selection 模式时,onCanvasSelectionEnd 会通过 getNodesInSelectionView(...) 将完成的选择框转换为节点列表,并用这些被框选的节点替换 editing-node 集合。单击画布空白区域则会同时清除节点选择和任何当前激活的编辑连线。
一旦节点进入 editing 集合,节点上方工具栏就成为操作入口。颜色色块会对所有已选节点应用相同的 updateNode(..., { color }) 补丁,Remove Nodes 按钮会删除整个当前选择,而 RGEditingResize 会在同一组选中节点上暴露尺寸调整手柄。悬浮辅助窗口本身也可交互:它可以被拖动、最小化、切换为设置面板,并用于将当前图谱导出为图片。
关键代码片段
这段代码表明,初始化过程会加载一份内联数据集,将其适配到当前视图中,并立即把两个节点设为首个 editing 选择。
await graphInstance.setJsonData(myJsonData);
graphInstance.zoomToFit();
// Set default editing nodes after initialization
const nodeD = graphInstance.getNodeById('d');
const nodeA = graphInstance.getNodeById('a');
if (nodeD && nodeA) {
graphInstance.setEditingNodes([nodeD, nodeA]);
}
这段代码展示了,普通单击与带修饰键的单击如何映射到同一个 editing-node 状态上的两种不同选择行为。
const onNodeClick = (nodeObject: RGNode, $event: RGUserEvent) => {
// Check modifier keys
if ($event.shiftKey || $event.ctrlKey || ($event.metaKey && !$event.altKey)) {
graphInstance.toggleEditingNode(nodeObject);
} else {
graphInstance.setEditingNodes([nodeObject]);
}
// Clear line editing status
graphInstance.setEditingLine(null);
};
这段代码证明,框选交互是通过 relation-graph 的选择 API 接管的,而不是通过自定义命中测试来实现。
const onCanvasSelectionEnd = (selectionView: RGSelectionView, $event: RGUserEvent) => {
// 3.x API directly gets nodes within selection range
const willSelectedNodes = graphInstance.getNodesInSelectionView(selectionView) || [];
// Batch set editing nodes, no need to manually iterate and modify selected property
graphInstance.setEditingNodes(willSelectedNodes);
};
这段代码展示了批量改色操作是命令式的,并且作用于整个当前 editing-node 集合。
const changeNodesColor = (newColor: string) => {
// Get currently editing nodes from instance
const editingNodes = graphInstance.getEditingNodes();
for (const node of editingNodes) {
// 3.x strictly forbids direct node.color = xxx, must use updateNode API
graphInstance.updateNode(node, { color: newColor });
}
};
这段代码展示了如何通过 relation-graph 的编辑组件,将上下文工具栏和尺寸调整手柄附着到已选节点上。
<RGEditingNodeController>
{/* Node size adjustment handle */}
<RGEditingResize />
{/* Custom widget docked above nodes */}
<div className="pointer-events-auto absolute left-0 top-0 transform translate-y-[-40px]">
<div className="w-fit flex gap-1 flex-nowrap whitespace-nowrap bg-white border border-gray-300 p-1 rounded">
<div
className="rg-gee-icon"
style={{ backgroundColor: 'rgba(205,92,92,0.77)' }}
onClick={() => changeNodesColor('rgba(205,92,92,0.77)')}
/>
</div>
</div>
</RGEditingNodeController>
这个示例的独特之处
对比数据表明,这个示例之所以有辨识度,并不只是因为它使用了 RGEditingNodeController、RGEditingResize、基于修饰键的选择或悬浮辅助面板。邻近示例如 custom-node-quick-actions、gee-node-resize、line-vertex-on-node 和 change-line-path 也复用了这套编辑脚手架的一部分。这里真正突出的,是其特定工作流:relation-graph 的 editing-node 状态被转化成一个具体的批量节点维护工具,具备多选、选择框交接、批量改色、批量删除和直接尺寸调整手柄,并且这些能力都指向同一组选中的节点。
与 custom-node-quick-actions 相比,这个示例用真实的变更命令替代了更复杂的叠加层定位。与 gee-node-resize 相比,它把 resize 从一种以单节点为主的控制模式,扩展成更广泛的多节点工作流,在其中 resize 只是多个动作之一。与 line-vertex-on-node 和 change-line-path 相比,它可复用的经验在于节点维护,而不是连接创作或连线路径编辑。当需求是构建一个由选择驱动的节点操作界面,而不是完整的图谱创作工具时,它因此成为更强的起点。
这种模式还适用于哪里
这种模式非常适合内部维护工具,在这类工具中,操作人员需要选择多个图项,并对它们统一应用相同的变更,例如资产清单、组织结构、拓扑清理工具、工作流目录或知识图谱审核界面。相同的 editing-node 工作流也可以驱动批量打标、状态着色、调整大小、锁定、归档或删除。
对于那些希望具备节点级维护能力、但不需要完整边编辑功能的轻量编辑器来说,它也很适用。在这些场景中,RGEditingNodeController 可以让操作界面保持上下文相关,而选择 API 则使批量操作始终绑定到用户当前在画布上主动选中的内容。