JavaScript is required

节点与连线提示框及右键菜单

这个示例展示如何在 relation-graph 查看器中为节点和连线同时挂载自定义悬停提示与右键菜单。它通过图命中检测、相对视图的覆盖层定位和插槽渲染,让这些面板始终对齐在图表面内;共享辅助窗还提供画布设置和图片导出。

节点与连线的自定义提示框和右键菜单

这个示例构建了什么

这个示例构建了一个查看器风格的 relation graph,并提供两类上下文浮层:悬停提示框和右键菜单。用户会看到一个全屏图谱,其中包含圆形的图标节点、带标签的曲线连线、点状画布背景,以及一个悬浮的辅助窗口。悬停在节点上时,会显示其标签和图标键;悬停在连线上时,会显示其标签以及解析后的源节点和目标节点;右键点击任一目标时,会在指针附近打开对应的专用菜单。

最重要的点不在于示例数据集本身。这个示例是一个紧凑的参考,用来说明如何同时为节点和连线挂载自定义检查 UI,并且让这些面板保留在图谱视图层中,而不是把它们硬编码进页面布局。

数据是如何组织的

图数据直接在 initializeGraph() 内声明为一个 RGJsonData 对象。它使用一个 rootId、一个扁平的 nodes 数组,以及一个 lines 数组。每个节点都带有 data.myicon 字段,而每条连线都具有显式的 idtext 标签。

在调用 setJsonData() 之前,没有单独的数据预处理流程,示例也没有调用 doLayout()。唯一的准备工作是结构层面的:节点被标注了图标元数据,连线则被赋予稳定的 id 和标签,以便浮层在命中测试后展示有意义的细节。

在业务数据集中,同样的数据形态可以表示服务依赖、设备连接、审批流、团队关系,或产品到功能的映射。节点的 data 对象可以承载图标键、状态、风险等级或分类标签,而连线元数据则可以承载关系类型、方向或 SLA 文本。

relation-graph 是如何使用的

页面包裹在 RGProvider 中,主组件通过 RGHooks.useGraphInstance() 获取绑定到 provider 作用域的实例。组件挂载后,它通过调用 setJsonData() 加载内联图数据,然后立即调用 moveToCenter()zoomToFit(),这样整个示例图谱无需额外用户操作即可完整可见。

图谱选项主要聚焦于展示层默认值,而不是编辑能力。defaultNodeShape 被设置为 RGNodeShape.circledefaultLineShapeRGLineShape.StandardCurvedefaultJunctionPointRGJunctionPoint.border。这个示例没有定义自定义布局算法,也没有实现变更工作流。

两个插槽层承担了大部分工作。RGSlotOnNode 用基于 node.data.myicon 派生的图标居中内容替换了内置节点主体,RGSlotOnView 则渲染所有悬浮浮层:节点提示框、连线提示框、节点菜单和连线菜单。这个分离方式很关键,因为这些上下文面板是按照视图坐标定位的,而不是嵌入在节点标记内部。

交互逻辑依赖多个图实例 API。isNode()isLine() 在悬停期间把当前 DOM 目标转换为图对象。getLinkByLine() 会把已渲染的连线解析回 link,从而让连线提示框可以显示端点标签。getViewBoundingClientRect() 会把浏览器的客户端坐标转换为相对于图谱视图的位置,以便放置浮层。setCheckedNode() 会高亮被右键点击的节点,而 clearChecked() 会在用户点击空白画布时移除该状态。

该示例还复用了一个共享的 DraggableWindow 辅助组件。这个辅助组件不是演示的主要主题,但它暴露了 RGHooks.useGraphStore()setOptions(),让用户可以切换滚轮与拖拽行为,并且它还使用 prepareForImageGeneration()restoreAfterImageGeneration() 来导出图谱图片。

关键交互

  • 悬停节点时,会打开一个深色提示框,显示节点文本和图标键。
  • 悬停连线时,会打开一个深色提示框,显示连线文本以及解析后的 fromto 节点标签。
  • 右键点击节点时,会打开节点专用菜单,并在视觉上将该节点设为 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-eventdrag-and-wheel-event 这类偏工作区交互的演示。这些示例强调的是画布拖拽或滚轮行为,而这个示例强调的是对象级命中测试、浮层锚定,以及针对四种浮层状态的共享关闭路径:节点提示框、连线提示框、节点菜单和连线菜单。

与更广义的 node 样式示例相比,这里的自定义节点插槽只是次要部分。真正有辨识度的组合是:静态的图标型图谱、圆形节点、带标签的连线、点状工具型画布,以及由 isNode()isLine()getLinkByLine()getViewBoundingClientRect() 驱动的视图层浮层。这使它成为一个面向只读上下文 UI 的聚焦起点,而不是用于图谱编辑或画布命令系统。

这种模式还适用于哪里

这种模式也很适合监控面板:当用户悬停连接关系时需要查看健康状态细节,右键点击时需要打开快速诊断菜单。它同样适用于资产地图、依赖图和网络拓扑查看器,在这些场景中,用户需要轻量级检查面板,而不需要进入编辑模式。

同样的方法还可以扩展到安全关系视图、工作流可观测性工具或服务地图。在这些场景下,节点提示框可以显示实时状态或归属信息,连线提示框可以显示吞吐量或策略数据,而右键菜单则可以打开领域动作,例如 drill-down、trace lookup 或异常审查,同时仍然沿用同样的视图层定位策略。