运行时图谱控制
这个示例展示一个由悬浮工具窗控制的力导人物关系图。它演示外部 UI 如何调用 relation-graph 实例 API:聚焦节点、动画移动、修改节点状态、运行时新增关注者、切换画布行为并导出图谱图片。
力导向人物关系图上的运行时图谱控制
这个示例构建了什么
这个示例构建了一个全屏人物关系图,并在其上方叠加了一个可拖拽的工具窗口。图谱使用头像作为节点图像、使用带标签的直线表示关系,并采用力导向布局,使整个网络保持动态且自然的排布,而不是固定树形或网格结构。
面向用户的体验核心是运行时控制。这个悬浮窗口可以将视口跳转到特定人物、执行带动画的定位操作、降低某个节点的不透明度、把一个节点移动到另一个节点附近、在图谱已经加载后再添加 follower 节点、切换画布拖拽与滚轮行为,以及将图谱导出为图片。
最重要的一点是,这些行为并不是通过单独的 demo 或画布内编辑器覆盖层实现的。一个外部控制面板会针对同一个实时图谱,驱动多个 graphInstance API。
数据是如何组织的
这个示例从 mock-data-api.ts 加载本地 RGJsonData 对象。数据包含一个 rootId、一个 nodes 数组和一个 lines 数组。每个节点都带有图谱核心字段,例如 id、text、color 和 borderColor,同时还包含一个 data 负载,其中有 icon、sexType 和 isGoodMan。每条连线则带有 from、to、text、颜色字段,以及一个用于关系标签的类型化 data 对象。
在渲染之前几乎没有预处理。fetchJsonData() 通过一个短暂超时来模拟异步请求,然后直接返回本地 JSON 对象。initializeGraph() 会将这份数据直接传入 setJsonData(...),因此这个示例关注的是加载后的运行时控制,而不是加载前的数据归一化。
这份数据集也专门用于展示 relation-graph 的呈现细节。示例中包含像 N13 -> N8 这样的重复连线,因此 multiLineDistance: 20 的效果会明显可见;同时每个节点都存有一个头像 URL,节点插槽可以将它渲染为人物头像。
在运行时,新添加的 follower 节点采用更小的结构:自动生成的 id、默认文本、位于父节点附近的初始 x 和 y 坐标,以及一条回连父节点的简单连线。在生产图谱中,同样的模式可以表示新增员工、推荐联系人、下游服务、调查线索或新发现的实体。
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,并在1与0.3之间切换它的不透明度,这展示了无需重建数据集即可直接修改节点。 - 两个移动操作会先通过调用
zoomToFitWithAnimation(...)让N8与目标节点一同进入合适视野,然后再使用自定义插值循环把N8移动到目标节点附近。 - 一个 follower 操作始终向
N1添加两个子节点;另一个在图谱尚未选中节点时保持禁用,一旦存在选中节点,就会向该节点添加随机数量的子节点。 - 点击画布会清除当前选中的图谱项,并重置本地的隐藏菜单和信息卡片标志。
- 设置面板可以在运行时切换
wheelEventAction和dragEventAction,并能将当前图谱导出为图片。
关键代码片段
这段配置建立了力导向布局、圆形头像节点、带标签的直线连线以及多重连线间距,这些共同构成了示例的基础行为。
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-focus 和 find-min-path 这类示例相比,这个示例的重点并不是查询或检索流程。它的主要价值在于,一个悬浮工作台驱动了多个图谱实例命令,并主动改变实时图谱。
与 scene-relationship-op 相比,它的控制模型不同。后者强调以节点为中心的覆盖层以及画布内部的上下文式编辑,而这个示例则将几乎所有可见控制都放在外部工具窗口中,把图谱视为一个运行时操作界面。
与 line-shape-and-label 或 adv-effect2 相比,它关注的不是全图范围的展示变化或可逆高亮。这个示例更进一步,强调了定向节点变更、增量式图谱增长、动画聚焦以及手动位置更新。
正是这种少见的组合让它成为一个有价值的参考:基于头像的人物数据、悬浮控制窗口、带动画的视口聚焦、直接的节点状态变更、带重布局的运行时节点和连线插入,以及同屏共享的画布设置与图片导出。moveN8To(...) 这段流程尤其有辨识度,因为它把 zoomToFitWithAnimation(...) 与一个会反复调用 updateNode(...) 的自定义 animateMove(...) 循环组合在了一起。
这种模式还能应用到哪里
这种模式非常适合调查工作台场景,分析人员需要在实体之间快速跳转、揭示新发现的邻居节点,并在不离开图谱的情况下导出当前状态。
它也适用于运维拓扑工具,支持人员需要通过外部控件进行镜头移动、重点强调和增量式图谱扩展,同时保持画布本身的视觉简洁。
另一个扩展方向是引导式产品演示或培训工具。一个悬浮面板可以逐个暴露选定的运行时操作,让 relation-graph 实例 API 比完全开放式编辑器更容易讲解。
同样的结构也可以支撑管理控制台,把实时画布设置、快照导出和围绕预加载网络的定向图谱变更组合起来。在这些场景中,这里的人物数据集可以替换为服务、设备、组织、案件,或任何适合受控运行时操作的实体图谱。