JavaScript is required

运行时图谱控制

这个示例展示一个由悬浮工具窗控制的力导人物关系图。它演示外部 UI 如何调用 relation-graph 实例 API:聚焦节点、动画移动、修改节点状态、运行时新增关注者、切换画布行为并导出图谱图片。

力导向人物关系图上的运行时图谱控制

这个示例构建了什么

这个示例构建了一个全屏人物关系图,并在其上方叠加了一个可拖拽的工具窗口。图谱使用头像作为节点图像、使用带标签的直线表示关系,并采用力导向布局,使整个网络保持动态且自然的排布,而不是固定树形或网格结构。

面向用户的体验核心是运行时控制。这个悬浮窗口可以将视口跳转到特定人物、执行带动画的定位操作、降低某个节点的不透明度、把一个节点移动到另一个节点附近、在图谱已经加载后再添加 follower 节点、切换画布拖拽与滚轮行为,以及将图谱导出为图片。

最重要的一点是,这些行为并不是通过单独的 demo 或画布内编辑器覆盖层实现的。一个外部控制面板会针对同一个实时图谱,驱动多个 graphInstance API。

数据是如何组织的

这个示例从 mock-data-api.ts 加载本地 RGJsonData 对象。数据包含一个 rootId、一个 nodes 数组和一个 lines 数组。每个节点都带有图谱核心字段,例如 idtextcolorborderColor,同时还包含一个 data 负载,其中有 iconsexTypeisGoodMan。每条连线则带有 fromtotext、颜色字段,以及一个用于关系标签的类型化 data 对象。

在渲染之前几乎没有预处理。fetchJsonData() 通过一个短暂超时来模拟异步请求,然后直接返回本地 JSON 对象。initializeGraph() 会将这份数据直接传入 setJsonData(...),因此这个示例关注的是加载后的运行时控制,而不是加载前的数据归一化。

这份数据集也专门用于展示 relation-graph 的呈现细节。示例中包含像 N13 -> N8 这样的重复连线,因此 multiLineDistance: 20 的效果会明显可见;同时每个节点都存有一个头像 URL,节点插槽可以将它渲染为人物头像。

在运行时,新添加的 follower 节点采用更小的结构:自动生成的 id、默认文本、位于父节点附近的初始 xy 坐标,以及一条回连父节点的简单连线。在生产图谱中,同样的模式可以表示新增员工、推荐联系人、下游服务、调查线索或新发现的实体。

relation-graph 是如何使用的

图谱挂载在 RGProvider 之下,因此 relation-graph 的 hooks 在 MyGraph 和共享的 CanvasSettingsPanel 中都可以使用。

RelationGraph 实例被配置为力导向布局,带有 maxLayoutTimes: 100、圆形节点、直线连线、沿连线路径渲染的标签、multiLineDistance: 20,以及默认的节点边框和填充样式。这个组合很重要,因为示例想展示的是一个已经完成样式化的人物图谱之上,节点移动、重复边以及实时图谱增长的效果。

RGHooks.useGraphInstance() 是核心集成点。它被用于初始加载、视口居中与自适应、节点查找、动画聚焦、运行时变更、重新布局、选项切换以及图片导出准备。RGHooks.useCheckedItem() 提供当前被选中的节点 id,因此“随机 follower”按钮只有在图谱存在有效目标时才会启用。在共享窗口组件中,RGHooks.useGraphStore() 暴露当前的拖拽与滚轮设置,使设置面板能够反映实时画布状态。

这个示例有选择地使用了插槽。RGSlotOnNode 用头像图片和下方文字标签替换了默认节点主体。RGSlotOnView 虽然已挂载,但在当前版本中故意保持为空,这意味着该 demo 将 UI 保持在画布之外,而不是在视图内部跟踪覆盖层。

样式被拆分为图谱选项和 SCSS 覆盖。SCSS 会修改节点标签颜色、重着色展开按钮、为选中节点添加明显的光晕和实底标签芯片,并将选中连线的标签切换为实底状态。结合基于插槽的头像渲染,这些覆盖让图谱具有更贴近业务域的外观,同时不需要替换底层图谱引擎。

关键交互

  • 这个悬浮窗口可以被拖拽、最小化,并且可以从其头部切换到设置模式。
  • 一个定位按钮会立刻聚焦到节点 N5,另一个则会临时启用画布动画、聚焦 N3、等待 300 ms,然后再关闭动画。
  • 一个专门的操作会聚焦 N6,并在 10.3 之间切换它的不透明度,这展示了无需重建数据集即可直接修改节点。
  • 两个移动操作会先通过调用 zoomToFitWithAnimation(...)N8 与目标节点一同进入合适视野,然后再使用自定义插值循环把 N8 移动到目标节点附近。
  • 一个 follower 操作始终向 N1 添加两个子节点;另一个在图谱尚未选中节点时保持禁用,一旦存在选中节点,就会向该节点添加随机数量的子节点。
  • 点击画布会清除当前选中的图谱项,并重置本地的隐藏菜单和信息卡片标志。
  • 设置面板可以在运行时切换 wheelEventActiondragEventAction,并能将当前图谱导出为图片。

关键代码片段

这段配置建立了力导向布局、圆形头像节点、带标签的直线连线以及多重连线间距,这些共同构成了示例的基础行为。

const graphOptions: RGOptions = {
    debug: false,
    defaultLineShape: RGLineShape.StandardStraight,
    defaultNodeShape: RGNodeShape.circle,
    defaultLineTextOnPath: true,
    multiLineDistance: 20,
    layout: {
        layoutName: 'force',
        maxLayoutTimes: 100
    },
    defaultNodeBorderWidth: 2,
    defaultNodeColor: '#e85f84'
};

这段加载流程表明,这个 demo 在渲染前并不会重新映射数据;它会加载 JSON、将其绑定到实例、自适应视口,然后通过程序方式激活一个节点。

const initializeGraph = async () => {
    const myJsonData: RGJsonData = await fetchJsonData();

    graphInstance.loading();
    await graphInstance.setJsonData(myJsonData);
    graphInstance.clearLoading();
    graphInstance.moveToCenter();
    graphInstance.zoomToFit();
    setTimeout(() => {
        onNodeClick(graphInstance.getNodeById('N3'));
    }, 1000);
};

这个辅助函数展示了运行时如何以增量方式扩展图谱:创建 id、在父节点附近设置初始坐标、添加节点和连线,然后重新启动自动布局。

const addChildNodeTo = (parentNodeId: string, newChildrenCount: number) => {
    const newNodes: JsonNode[] = [];
    const newLines: JsonLine[] = [];
    const parentNode = graphInstance.getNodeById(parentNodeId);
    for (let i = 0; i < newChildrenCount; i++) {
        const newNodeId = graphInstance.generateNewNodeId();
        newNodes.push({
            id: newNodeId,
            text: 'New Node',
            x: parentNode.x + 200,
            y: parentNode.y + (Math.random() * 200 - 100)
        });
        newLines.push({ id: 'line-to-' + newNodeId, from: parentNodeId, to: newNodeId });
    }

这段移动逻辑把内置视口动画与自定义逐帧节点更新结合在一起,是这个示例中最清晰的运行时控制模式之一。

await graphInstance.zoomToFitWithAnimation([node8, targetNode]);
const finalXy = {
    x: targetNode.x - 50,
    y: targetNode.y
}
await animateMove(node8, finalXy, (x, y) => {
    graphInstance.updateNode(node8, {
        x, y
    });
}, 800);

节点插槽基于数据集里的 data.icon 字段,用头像渲染替换了默认节点内容。

<RGSlotOnNode>
    {({ node }: RGNodeSlotProps) => (
        <div className="w-12 h-12 flex place-items-center justify-center">
            <div className="my-node-avatar" style={{ backgroundImage: `url(${node.data?.icon})` }} />
            <div className="my-node-name absolute transform translate-y-[35px]">{node.text}</div>
        </div>
    )}
</RGSlotOnNode>

共享设置面板表明,画布行为和导出也都是运行时实例操作,而不是编译时选项。

const canvasDom = await graphInstance.prepareForImageGeneration();
let graphBackgroundColor = graphInstance.getOptions().backgroundColor;
if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {
    graphBackgroundColor = '#ffffff';
}
const imageBlob = await domToImageByModernScreenshot(canvasDom, {
    backgroundColor: graphBackgroundColor
});
if (imageBlob) {
    downloadBlob(imageBlob, 'my-image-name');
}
await graphInstance.restoreAfterImageGeneration();

这个示例的独特之处

与附近的 search-and-focusfind-min-path 这类示例相比,这个示例的重点并不是查询或检索流程。它的主要价值在于,一个悬浮工作台驱动了多个图谱实例命令,并主动改变实时图谱。

scene-relationship-op 相比,它的控制模型不同。后者强调以节点为中心的覆盖层以及画布内部的上下文式编辑,而这个示例则将几乎所有可见控制都放在外部工具窗口中,把图谱视为一个运行时操作界面。

line-shape-and-labeladv-effect2 相比,它关注的不是全图范围的展示变化或可逆高亮。这个示例更进一步,强调了定向节点变更、增量式图谱增长、动画聚焦以及手动位置更新。

正是这种少见的组合让它成为一个有价值的参考:基于头像的人物数据、悬浮控制窗口、带动画的视口聚焦、直接的节点状态变更、带重布局的运行时节点和连线插入,以及同屏共享的画布设置与图片导出。moveN8To(...) 这段流程尤其有辨识度,因为它把 zoomToFitWithAnimation(...) 与一个会反复调用 updateNode(...) 的自定义 animateMove(...) 循环组合在了一起。

这种模式还能应用到哪里

这种模式非常适合调查工作台场景,分析人员需要在实体之间快速跳转、揭示新发现的邻居节点,并在不离开图谱的情况下导出当前状态。

它也适用于运维拓扑工具,支持人员需要通过外部控件进行镜头移动、重点强调和增量式图谱扩展,同时保持画布本身的视觉简洁。

另一个扩展方向是引导式产品演示或培训工具。一个悬浮面板可以逐个暴露选定的运行时操作,让 relation-graph 实例 API 比完全开放式编辑器更容易讲解。

同样的结构也可以支撑管理控制台,把实时画布设置、快照导出和围绕预加载网络的定向图谱变更组合起来。在这些场景中,这里的人物数据集可以替换为服务、设备、组织、案件,或任何适合受控运行时操作的实体图谱。