节点环绕快捷操作栏
这个示例构建了一个轻量级图编辑工作区,在当前处于 relation-graph 编辑状态的节点周围挂载了自定义快捷操作托盘。页面展示了一个全高的点状画布、一个浮动辅助窗口,以及位于编辑节点上方、下方、左侧、右侧和四个角落的白色操作组。
编辑节点周围的八方向快捷操作
这个示例构建了什么
这个示例构建了一个轻量级图编辑工作区,在当前处于 relation-graph 编辑状态的节点周围挂载了自定义快捷操作托盘。页面展示了一个全高的点状画布、一个浮动辅助窗口,以及位于编辑节点上方、下方、左侧、右侧和四个角落的白色操作组。
用户可以点击节点,通过组合键把更多节点切换进编辑集合,完成框选后重新定位覆盖层,并点击空白画布区域清除当前编辑状态。这个示例的重点不是按钮背后的业务逻辑,而是精确演示如何在一个或多个节点周围放置上下文控制项。
数据是如何组织的
图数据以内联方式声明为一个 RGJsonData 对象,包含一个 rootId、一个扁平的 nodes 数组和一个扁平的 lines 数组。在这个示例中,数据集很小:11 个节点和 11 条连线,字段仅包含简单的 id、text、from 和 to。
在调用 setJsonData(...) 之前,没有任何外部获取或转换流程。唯一的数据准备步骤发生在 initializeGraph() 内部:示例构建 JSON 对象,将其加载到图实例中,使视口自适应,然后解析节点 d 和 a,从而让编辑覆盖层在首次渲染时就立即可见。
在真实产品中,同样的结构可以表示人员与汇报关系、工作流步骤与状态迁移、服务与依赖关系,或任何需要在选中节点周围提供上下文操作的实体图。
relation-graph 是如何使用的
入口组件用 RGProvider 包裹页面,MyGraph 通过 RGHooks.useGraphInstance() 读取 provider 作用域内的实例。该实例负责初始数据加载、视口自适应、编辑节点更新、编辑连线重置、选择框结果解析以及 checked 状态清除。
这个示例没有定义自定义布局对象。它依赖 relation-graph 在 setJsonData(...) 期间的常规布局处理,然后调用 zoomToFit(),让初始图形填满视口。
RelationGraph 被配置为 showToolBar: false,这会移除内置工具栏,并把页面的交互外壳交给自定义 UI。组件注册了 onNodeClick、onCanvasSelectionEnd 和 onCanvasClick,这三个回调都会通过实例 API 更新同一套编辑状态模型,例如 toggleEditingNode(...)、setEditingNodes(...)、getNodesInSelectionView(...) 和 setEditingLine(null)。
覆盖层本身是这个示例里最关键的 relation-graph 技巧。RGSlotOnView 挂载 RGEditingNodeController,而在这个控制器内部,示例使用绝对定位 class 渲染自定义 HTML 托盘。这些托盘不是 relation-graph 的内置预设;它们只是普通的 <div> 块,通过继承控制器提供的位置参考,再利用 transform 放置在编辑节点的不同侧边。
共享的 DraggableWindow 提供了第二层集成。它的设置面板通过 RGHooks.useGraphStore() 反映当前的滚轮模式和拖拽模式,通过 setOptions(...) 更新这些模式,并通过 prepareForImageGeneration() 和 restoreAfterImageGeneration() 导出画布。这个辅助窗口很实用,但相较于节点周围覆盖层这一模式,它属于次要部分。
关键交互
- 初始化后会将两个节点预先放入编辑状态,因此快捷操作托盘会立即可见。
- 不带组合键点击节点时,会用该节点替换当前编辑节点集合,并清除任何处于激活状态的编辑连线。
- 按住
Shift、Ctrl或Meta点击节点时,会在编辑选择中切换该节点,而不是替换整个集合。 - 当选择框操作完成时,示例通过
getNodesInSelectionView(...)解析被框住的节点,并将该列表作为新的编辑节点集合。 - 点击空白画布区域时,会清除编辑节点、清除当前激活的编辑连线,并移除 checked 状态。
- 浮动辅助窗口可以被拖动,可以打开设置面板,可以在运行时切换滚轮和拖拽行为,还可以把图导出为图片。
- 快捷操作托盘只是占位示例。它们展示的是放置方式和目标绑定,而不是真正的变更命令。
关键代码片段
下面这个片段展示了示例以内联方式构建图数据、使视口自适应,并预置两个编辑节点,从而让覆盖层在加载时出现。
const initializeGraph = async () => {
const myJsonData: RGJsonData = {
rootId: 'a',
nodes: [
{ id: 'a', text: 'Border color' },
// ...
],
lines: [
{ id: 'l1', from: 'a', to: 'b' },
// ...
]
};
await graphInstance.setJsonData(myJsonData);
graphInstance.zoomToFit();
const nodeD = graphInstance.getNodeById('d');
const nodeA = graphInstance.getNodeById('a');
if (nodeD && nodeA) {
graphInstance.setEditingNodes([nodeD, nodeA]);
}
};
下面这个片段展示了普通点击、组合键点击、选择框完成以及画布重置,都会更新同一套编辑节点状态。
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: RGNode[] = graphInstance.getNodesInSelectionView(selectionView) || [];
graphInstance.setEditingNodes(willSelectedNodes);
};
下面这个片段展示了图配置如何关闭内置工具栏,并在视图插槽中挂载自定义覆盖层。
const graphOptions: RGOptions = {
showToolBar: false
};
<RelationGraph
options={graphOptions}
onCanvasSelectionEnd={onCanvasSelectionEnd}
onCanvasClick={onCanvasClick}
onNodeClick={onNodeClick}
>
<RGSlotOnView>
<RGEditingNodeController>
下面这个片段展示了其中一个自定义托盘。相同的模式也被重复用于其他侧边和角落。
{/* top */}
<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 shadow">
<div className="bg-blue-500 text-white text-xs hover:bg-blue-700 px-1 py-0.5 rounded">top1</div>
<div className="bg-blue-500 text-white text-xs hover:bg-blue-700 px-1 py-0.5 rounded">top2</div>
</div>
</div>
{/* ... similar blocks follow for bottom, left, right, and all four corners ... */}
下面这个片段展示了共享辅助窗口如何通过 relation-graph API 导出图像,并改变运行时画布行为。
const downloadImage = async () => {
const canvasDom = await graphInstance.prepareForImageGeneration();
let graphBackgroundColor = graphInstance.getOptions().backgroundColor;
if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {
graphBackgroundColor = '#ffffff';
}
const imageBlob = await domToImageByModernScreenshot(canvasDom, {
backgroundColor: graphBackgroundColor
});
await graphInstance.restoreAfterImageGeneration();
};
下面这个片段展示了样式表如何把画布变成类似编辑器的点状表面,并随着图视口一起缩放。
.relation-graph {
--rg-canvas-scale: 1;
--rg-canvas-offset-x: 0px;
--rg-canvas-offset-y: 0px;
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);
}
这个示例的独特之处
与附近的其他编辑示例相比,这个示例的突出点在于它主要把 RGEditingNodeController 当作位置参考,而不是变更工具。相较于 create-line-from-node 和 line-vertex-on-node,这里的覆盖层并不是连线编排工作流,而是一种视图层模式,用于把上下文控制项停靠在当前编辑节点或多个节点周围。
相比 batch-operations-on-nodes,这里的重点也不是连接诸如重新着色、调整尺寸或删除之类的命令。这个示例真正不同的地方在于空间覆盖:它展示了顶部、底部、左侧、右侧和四个角落的托盘布局,并让这种布局模式兼容预置选择、基于组合键的多选以及框选后的重新定位。
相比 gee-node-resize 和 gee-node-alignment-guides,这里可复用的价值也不是内置缩放手柄或拖拽辅助,而是点状编辑画布、浮动工具窗口、showToolBar: false 以及跟随当前编辑节点集合的自定义 HTML 覆盖层这一组合。
这一模式还适用于哪些场景
这一模式非常适合工作流和流程编辑器,在这些场景中,被选中的步骤需要就近显示诸如分支、批准、拒绝、分配或查看之类的操作。
它也适用于服务拓扑工具、依赖关系图以及知识图谱整理界面。在这些场景里,用户通常已经知道自己要执行什么命令,但需要这些命令出现在当前目标节点附近,而不是远处的全局工具栏中。
在内部管理工具中,同样的方法还可以用于组织架构图、审核队列或资产关系视图,让同一套选择模型同时驱动单节点和多节点的上下文控制项。