节点悬停详情提示框
这个示例展示如何在自定义 `RGSlotOnNode` 渲染器中,于节点悬停时打开不可交互的详情提示卡,并在 `RGSlotOnView` 中渲染。它使用相对容器的指针坐标、悬停进入/离开处理,以及内联图标节点数据,在保持图谱只读的同时暴露节点元信息。
构建仅在悬停时显示的节点详情提示卡
这个示例构建了什么
这个示例构建了一个只读的 relation graph,当鼠标悬停在自定义节点上时,会在指针附近打开一个小型详情卡片。可见场景被刻意压缩得较为紧凑:绿色的圆形图标节点、渲染在节点下方的短标签,以及一个始终停留在图谱视图内部的白色浮动 tooltip。
用户可以通过将指针移到节点上来查看节点详情,并在离开该节点时关闭卡片。节点点击和连线点击也都存在,但它们只会把选中的对象输出到控制台,因此这里的核心价值是被动查看,而不是编辑或执行命令。
这个示例最有用的思路,是把职责拆分到 node slot 和 view slot:node slot 负责悬停触发,view slot 负责覆盖层 UI。
数据是如何组织的
图数据是在 initializeGraph() 内部以内联方式创建的,一个 RGJsonData 对象直接定义了这些数据。它包含 rootId: '2'、一个 nodes 数组,以及一个带有明确连线 id 和标签的 lines 数组。每个节点还携带 data.myicon,自定义节点渲染器会用它来选择一个 Lucide 图标。
在调用 setJsonData() 之前没有任何预处理步骤。组件直接在代码中构建最终 payload,并原样加载,然后将视口居中并适配显示。在业务应用中,这种结构同样可以表示产品、服务、设备、部门或依赖项,而 data.myicon 也可以替换为类型、状态或分类元数据,用来选择对应的自定义视觉样式。
relation-graph 是如何使用的
index.tsx 通过 RGProvider 包裹这个示例,而 MyGraph.tsx 使用 RGHooks.useGraphInstance() 获取实时图实例。一个 useEffect() hook 会在实例可用后执行 initializeGraph(),通过 setJsonData() 加载内联数据,然后调用 moveToCenter() 和 zoomToFit()。
图配置非常少,而且主要聚焦视觉层面。defaultNodeColor 提供了绿色填充,自定义 slot 会通过 node.color 读取它;defaultNodeShape 被设置为 RGNodeShape.circle;defaultJunctionPoint 被设置为 RGJunctionPoint.border。从当前审阅的源码来看,并没有显式设置布局选项,因此这个数据集中的节点排列方式依赖 relation-graph 的默认行为。
主要定制发生在两个 slot 上。RGSlotOnNode 用圆形图标渲染器替换了内置节点主体,并直接在节点 DOM 上绑定 onMouseEnter 和 onMouseLeave。RGSlotOnView 则在图场景内部以覆盖层 HTML 的方式渲染提示卡。提示卡的位置不是通过图事件回调计算的,而是根据相对于一个包装元素的指针坐标来计算。
图级别的点击事件被有意弱化。onNodeClick 和 onLineClick 都绑定在 RelationGraph 上,但两个处理器都只是在控制台打印对象。这让示例保持在 viewer 模式下,也使悬停覆盖层成为唯一真正有意义的图交互。
本地样式较轻,且目标明确。SCSS 文件通过 .c-my-rg-node 定义了圆形节点的尺寸和居中方式,而提示卡中的各行则通过 .c-node-menu-item 控制间距和排版。覆盖层本身还设置了 pointerEvents: 'none',这样就不会把卡片变成交互式菜单,而是保留 slotted node 上的悬停流程。
关键交互
- 悬停节点时,会在当前指针位置附近打开一个详情提示卡。
- 将指针移出节点后,提示卡会立即关闭。
- 提示卡保持为非交互状态,因为它的覆盖层容器禁用了 pointer events。
- 点击节点会输出节点对象,但不会改变图状态。
- 点击连线会输出连线对象,但不会打开任何额外 UI。
关键代码片段
这个 options 配置块说明,这个示例主要把 relation-graph 当作 viewer 使用,只定制了基础的节点形状、颜色和连线连接点模式。
const graphOptions: RGOptions = {
defaultNodeColor: 'rgba(66,187,66,1)',
defaultNodeShape: RGNodeShape.circle,
defaultJunctionPoint: RGJunctionPoint.border,
};
这个内联 payload 说明,图标选择由每个节点自身的元数据驱动,而图内容则直接在组件代码中组装完成。
const myJsonData: RGJsonData = {
rootId: '2',
nodes: [
{ id: '1', text: 'Node-1', data: { myicon: 'star' } },
{ id: '2', text: 'Node-2', data: { myicon: 'settings' } },
// ... more icon-tagged nodes ...
{ id: '5', text: 'Node-5', data: { myicon: 'gift' } }
],
lines: [
{ id: 'l1', from: '7', to: '71', text: 'Investment' },
// ... more labeled lines ...
]
};
这段初始化流程证明,图实例是通过标准的基于 hook 的启动流程加载的。
await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
这个悬停处理器是核心实现细节,它通过相对于包装元素的指针坐标来定位覆盖层。
const showNodeTips = (nodeObject: RGNode, $event: React.MouseEvent) => {
setCurrentNode(nodeObject);
if (myPage.current) {
const _base_position = myPage.current.getBoundingClientRect();
setIsShowNodeTipsPanel(true);
setNodeMenuPanelPosition({
x: $event.clientX - _base_position.x + 10,
y: $event.clientY - _base_position.y + 10
});
}
};
这个 node slot 片段表明,悬停触发器是挂在自定义节点 DOM 上的,而不是挂在图范围的鼠标移动监听器上。
<RGSlotOnNode>
{({ node }: RGNodeSlotProps) => (
<div
className="h-full"
onMouseEnter={(e) => showNodeTips(node, e)}
onMouseLeave={() => hideNodeTips()}
>
<div className="c-my-rg-node" style={{ backgroundColor: node.color }}>
<GetLucideIcon name={node.data?.myicon} />
</div>
</div>
)}
</RGSlotOnNode>
这个 view slot 片段证明,提示卡是在图场景内部渲染的,并且有意避免拦截指针输入。
<RGSlotOnView>
{isShowNodeTipsPanel && currentNode && (
<div
className="p-3 bg-white border border-gray-300 shadow-xl absolute rounded-lg"
style={{
left: `${nodeMenuPanelPosition.x}px`,
top: `${nodeMenuPanelPosition.y}px`,
zIndex: 1000,
pointerEvents: 'none'
}}
>
这个示例的独特之处
对比数据表明,这个示例与 node-menu-2、node-menu 和 node-line-tips-contentmenu 比较接近,但它把共享的 slot + overlay 构建方式用于更收敛的目标。与 node-menu-2 和 node-menu 相比,关键区别在于这个示例是被动的、由悬停驱动的:它不会打开可操作菜单,不会在节点、连线和画布目标之间切换,也不需要为交互面板实现点击外部关闭逻辑。
与 node-line-tips-contentmenu 相比,这里是一个更小巧的、只针对节点的方案。它没有加入连线命中检测、右键菜单、辅助窗口或混合覆盖层类型。对比记录还强调了这里一个相对少见的组合:绿色圆形图标节点、节点下方的徽标式标签、相对于包装元素的指针定位计算、悬停进入与离开处理器,以及在一个只读 viewer 中带有 pointerEvents: 'none' 的白色浮动卡片。
因此,当需求是在图场景内部进行轻量级节点查看,同时又不想引入菜单状态、编辑流程或图变更时,这个示例会是一个更合适的起点。它不应该被描述为唯一的覆盖层示例,但它确实是非交互式节点悬停详情模式中最清晰的参考之一。
这种模式还适用于哪里
这种模式很适合迁移到依赖关系图、服务拓扑查看器、产品关系浏览器、设备网络和组织浏览界面中。在这些场景里,用户需要快速查看只读详情,而不必离开图本身。提示卡内容可以替换为健康摘要、归属字段、状态元数据、风险标签或紧凑指标,同时继续沿用相同的悬停触发和 view overlay 结构。
它同样是一个适合继续扩展的检查型 UI 起点。团队可以先从这个被动卡片开始,后续再加入视口边缘限制、悬停延迟、固定详情面板或适用于触摸设备的替代方案,同时仍然保留“slotted node 触发器 + 场景内覆盖层渲染”这个基础思路。