节点与连线提示框及右键菜单
这个示例展示如何在 relation-graph 查看器中为节点和连线同时挂载自定义悬停提示与右键菜单。它通过图命中检测、相对视图的覆盖层定位和插槽渲染,让这些面板始终对齐在图表面内;共享辅助窗还提供画布设置和图片导出。
节点与连线的自定义提示框和右键菜单
这个示例构建了什么
这个示例构建了一个查看器风格的 relation graph,并提供两类上下文浮层:悬停提示框和右键菜单。用户会看到一个全屏图谱,其中包含圆形的图标节点、带标签的曲线连线、点状画布背景,以及一个悬浮的辅助窗口。悬停在节点上时,会显示其标签和图标键;悬停在连线上时,会显示其标签以及解析后的源节点和目标节点;右键点击任一目标时,会在指针附近打开对应的专用菜单。
最重要的点不在于示例数据集本身。这个示例是一个紧凑的参考,用来说明如何同时为节点和连线挂载自定义检查 UI,并且让这些面板保留在图谱视图层中,而不是把它们硬编码进页面布局。
数据是如何组织的
图数据直接在 initializeGraph() 内声明为一个 RGJsonData 对象。它使用一个 rootId、一个扁平的 nodes 数组,以及一个 lines 数组。每个节点都带有 data.myicon 字段,而每条连线都具有显式的 id 和 text 标签。
在调用 setJsonData() 之前,没有单独的数据预处理流程,示例也没有调用 doLayout()。唯一的准备工作是结构层面的:节点被标注了图标元数据,连线则被赋予稳定的 id 和标签,以便浮层在命中测试后展示有意义的细节。
在业务数据集中,同样的数据形态可以表示服务依赖、设备连接、审批流、团队关系,或产品到功能的映射。节点的 data 对象可以承载图标键、状态、风险等级或分类标签,而连线元数据则可以承载关系类型、方向或 SLA 文本。
relation-graph 是如何使用的
页面包裹在 RGProvider 中,主组件通过 RGHooks.useGraphInstance() 获取绑定到 provider 作用域的实例。组件挂载后,它通过调用 setJsonData() 加载内联图数据,然后立即调用 moveToCenter() 和 zoomToFit(),这样整个示例图谱无需额外用户操作即可完整可见。
图谱选项主要聚焦于展示层默认值,而不是编辑能力。defaultNodeShape 被设置为 RGNodeShape.circle,defaultLineShape 是 RGLineShape.StandardCurve,defaultJunctionPoint 是 RGJunctionPoint.border。这个示例没有定义自定义布局算法,也没有实现变更工作流。
两个插槽层承担了大部分工作。RGSlotOnNode 用基于 node.data.myicon 派生的图标居中内容替换了内置节点主体,RGSlotOnView 则渲染所有悬浮浮层:节点提示框、连线提示框、节点菜单和连线菜单。这个分离方式很关键,因为这些上下文面板是按照视图坐标定位的,而不是嵌入在节点标记内部。
交互逻辑依赖多个图实例 API。isNode() 和 isLine() 在悬停期间把当前 DOM 目标转换为图对象。getLinkByLine() 会把已渲染的连线解析回 link,从而让连线提示框可以显示端点标签。getViewBoundingClientRect() 会把浏览器的客户端坐标转换为相对于图谱视图的位置,以便放置浮层。setCheckedNode() 会高亮被右键点击的节点,而 clearChecked() 会在用户点击空白画布时移除该状态。
该示例还复用了一个共享的 DraggableWindow 辅助组件。这个辅助组件不是演示的主要主题,但它暴露了 RGHooks.useGraphStore() 和 setOptions(),让用户可以切换滚轮与拖拽行为,并且它还使用 prepareForImageGeneration() 和 restoreAfterImageGeneration() 来导出图谱图片。
关键交互
- 悬停节点时,会打开一个深色提示框,显示节点文本和图标键。
- 悬停连线时,会打开一个深色提示框,显示连线文本以及解析后的
from和to节点标签。 - 右键点击节点时,会打开节点专用菜单,并在视觉上将该节点设为 checked。
- 右键点击连线时,代码会先把该连线解析回其 link 对象,然后打开独立的连线菜单。
- 点击节点或连线时,会关闭所有已打开的浮层。
- 点击空白画布时,会关闭所有浮层并清除 checked 状态。
- 打开共享设置面板后,用户可以切换滚轮模式、切换拖拽模式并导出图谱图片,但这些控制来自共享查看器脚手架,而不是示例专属的菜单动作。
关键代码片段
这段代码展示了内联数据集的结构,其中包括节点图标元数据,以及后续会被浮层复用的带标签连线记录。
const myJsonData: RGJsonData = {
rootId: '2',
nodes: [
{ id: '1', text: 'Node-1', data: { myicon: 'star' } },
{ id: '2', text: 'Node-2', data: { myicon: 'settings' } },
{ id: '3', text: 'Node-3', data: { myicon: 'settings' } },
// ...
],
lines: [
{ id: 'l1', from: '7', to: '71', text: 'Line text1' },
{ id: 'l2', from: '7', to: '72', text: 'Line text2' }
]
};
这段代码展示了初始化路径:加载准备好的 JSON,然后将图谱居中并缩放到适配视图。
useEffect(() => {
initializeGraph();
}, []);
const initializeGraph = async () => {
// ...
await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
};
这段代码展示了右键菜单如何锚定在图谱视图内部而不是页面上,以及处理逻辑如何根据图对象类型分支。
const viewRect = graphInstance.getViewBoundingClientRect();
const position = {
x: event.clientX - viewRect.left + 10,
y: event.clientY - viewRect.top + 10
};
if (objectType === 'node' && object) {
const node = object as RGNode;
graphInstance.setCheckedNode(node.id);
setCurrentNode(node);
setNodeMenuPanelPosition(position);
}
这段代码展示了悬停命中测试流程,它会在节点提示框状态和连线提示框状态之间切换。
const el = $event.target as HTMLElement;
const node = graphInstance.isNode(el);
const viewRect = graphInstance.getViewBoundingClientRect();
const position = {
x: $event.clientX - viewRect.left + 10,
y: $event.clientY - viewRect.top + 10
};
if (node) {
setCurrentNode(node);
setNodeTipsPosition(position);
setIsShowNodeTips(true);
}
这段代码展示了浮层位于 RGSlotOnView 中,而节点渲染则通过 RGSlotOnNode 单独进行自定义。
<RGSlotOnNode>
{({ node }: RGNodeSlotProps) => (
<div className="w-12 h-12 flex place-items-center justify-center">
{renderIcon(node.data?.myicon)}
<div className="absolute top-[100%] px-2 py-0.5 text-xs rouned bg-gray-300">
{node.text}
</div>
</div>
)}
</RGSlotOnNode>
<RGSlotOnView>
{isShowNodeTips && (
<div className="c-tips" style={{ left: nodeTipsPosition.x, top: nodeTipsPosition.y }}>
<div>NodeId: {currentNode?.text}</div>
<div>Icon: {currentNode?.data?.myicon}</div>
</div>
)}
</RGSlotOnView>
这段代码展示了深色提示框、白色上下文菜单以及点状画布表面之间的样式划分。
.my-context-menu {
background-color: #ffffff;
border: #cccccc solid 1px;
box-shadow: 0px 0px 8px #cccccc;
}
.c-tips {
background-color: #333333;
color: #ffffff;
}
这个示例的独特之处
与相近示例相比,当需求是在同一个查看器中同时为节点和连线提供上下文检查 UI 时,这个示例最有参考价值。对比记录显示,node-menu 是最接近的菜单类邻近示例,但那个示例聚焦于右键菜单,而这个示例额外加入了针对两类目标的悬停提示框,并且分别维护节点菜单状态和连线菜单状态。
它也不同于 canvas-event 和 drag-and-wheel-event 这类偏工作区交互的演示。这些示例强调的是画布拖拽或滚轮行为,而这个示例强调的是对象级命中测试、浮层锚定,以及针对四种浮层状态的共享关闭路径:节点提示框、连线提示框、节点菜单和连线菜单。
与更广义的 node 样式示例相比,这里的自定义节点插槽只是次要部分。真正有辨识度的组合是:静态的图标型图谱、圆形节点、带标签的连线、点状工具型画布,以及由 isNode()、isLine()、getLinkByLine() 和 getViewBoundingClientRect() 驱动的视图层浮层。这使它成为一个面向只读上下文 UI 的聚焦起点,而不是用于图谱编辑或画布命令系统。
这种模式还适用于哪里
这种模式也很适合监控面板:当用户悬停连接关系时需要查看健康状态细节,右键点击时需要打开快速诊断菜单。它同样适用于资产地图、依赖图和网络拓扑查看器,在这些场景中,用户需要轻量级检查面板,而不需要进入编辑模式。
同样的方法还可以扩展到安全关系视图、工作流可观测性工具或服务地图。在这些场景下,节点提示框可以显示实时状态或归属信息,连线提示框可以显示吞吐量或策略数据,而右键菜单则可以打开领域动作,例如 drill-down、trace lookup 或异常审查,同时仍然沿用同样的视图层定位策略。