力导向布局性能模式压测实验
这个示例展示全屏 relation-graph 力导布局压测场,可从 100 到 10000 节点/边生成合成数据,保持 `performanceMode` 开启,并提供 6 个实时力系数滑块及连续/固定布局切换。它同时启用自定义头像节点插槽、可拖拽工具窗、小地图和共享画布设置,用于评估更丰富渲染在高负载力导场景下的表现。
带实时运行时控制的力导布局性能压力实验室
这个示例构建了什么
这个示例围绕 relation-graph 内置的 force 布局,构建了一个全屏的力导布局压力测试游乐场。画布会渲染一个由圆形头像节点和带颜色的标题胶囊组成的密集网络,同时提供一个可拖拽的悬浮控制窗口,以及右下角用于总览导航的缩略图。用户可以在六组图规模预设之间切换,在图持续运行时调节六个力导系数,并选择求解器是持续运行,还是在固定迭代次数后停止。
这个示例的核心目的是观察 relation-graph 在更重的合成力导布局负载下的表现,同时保持更丰富的基于 slot 的节点渲染器处于启用状态。它是一个用于规模与调参的实验室,而不是面向特定领域的查看器,也不是编辑示例。
数据是如何组织的
这个示例从一个本地 RGJsonData 种子开始,定义在 example-data.ts 中。这个种子定义了 rootId: 'a'、一组分叉的基础节点以及对应的基础连线。在图加载之前,generateTestJsonData(testDataSize) 会深拷贝这个种子,扩展每个 id 中包含 - 的节点,并根据所选的规模因子追加额外的子节点和额外连线。
扩展规则会在更大规模时发生变化。当取值不超过 30 时,生成器会为每个符合条件的节点增加一轮额外的扇出扩展。超过 30 后,它会切换为基于 Math.sqrt(testDataSize) 的两级扩展,这样图就可以在不手工编写数据的前提下增长到更大的规模。扩展完成后,每个节点都会收到一个随机图片 URL 和一个随机颜色,每条连线也都会收到一个生成的 id 和一个随机颜色。
在生产系统中,这种结构可以对应渐进展开的关系邻域、依赖半径、客户账户分层,或者基础设施爆炸半径视图。这里的重要模式是,页面会按需重新生成一次性测试数据,而不是去编辑一张持久存在的业务图。
relation-graph 是如何使用的
index.tsx 使用 RGProvider 包装整个示例,而 MyGraph.tsx 则通过 RGHooks.useGraphInstance() 获取实时图实例。图配置启用了 performanceMode,将内置布局设置为 force,选择锚定在边框上的直线连线,并使用无默认边框的 80x80 圆形节点。初始的力导布局运行配置为 maxLayoutTimes: Number.MAX_SAFE_INTEGER,因此页面会以持续布局模式启动。
这个示例以三种不同方式命令式地使用 relation-graph。数据集变化会触发一条硬重载路径:stopAutoLayout()、clearGraph()、sleep(500)、setJsonData(...)、moveToCenter() 和 setZoom(30)。力导系数变化会走一条更轻量的路径:代码读取 graphInstance.layoutor,将其转换为 RGLayouts.ForceLayout,然后调用 updateOptions(myForceLayoutOptions),从而就地修改正在运行的布局器。迭代模式变化则使用第三条路径:updateOptions({ layout }),然后 doLayout(),最后重新居中并重置缩放。
Slots 是视觉结果中的重要组成部分。RGSlotOnView 在右下角挂载了 RGMiniView,使页面在不替换主画布的情况下拥有总览小地图。RGSlotOnNode 渲染了一个自定义节点模板,它使用 node.data.pic 作为圆形背景图片,使用 node.color 作为标题胶囊的背景色。my-relation-graph.scss 中的样式将这个 slot 塑造成完整的圆形照片节点,并重写了选中状态样式,使被选中的节点和连线获得洋红色高亮。
悬浮工具窗口来自共享组件 DraggableWindow.tsx,分段选择器来自 SimpleUISelect.tsx。共享窗口中的设置覆盖层会使用图实例 API 来切换滚轮行为、切换画布拖拽行为,并通过 domToImageByModernScreenshot.ts 导出当前图像。这些工具属于继承而来的共享实用组件,而不是这个示例特有的力导布局代码。
关键交互
- 切换数据集预设会以新的规模重新生成图数据,将数据重新加载到 relation-graph 中,使视口重新居中,并将缩放重置为
30。 - 移动六个范围输入中的任意一个,都会在运行时更新当前
ForceLayout实例,因此用户可以在不重新挂载组件的情况下,观察同一张图在不同全局求解系数下的表现。 - 在
Layout Forever与Layout Fixed Times(...)之间切换,会改变力导求解器的持续时间策略。固定模式启用时,会额外出现一个范围滑块,用于调整迭代次数上限。 - 悬浮控制窗口可以被拖拽、最小化、恢复,并切换到共享的画布设置面板。
- 共享设置面板可以切换滚轮行为、切换画布拖拽行为,并将当前图导出为图片。
关键代码片段
下面这段代码证明,这个页面被配置为一个启用 performanceMode 的内置力导布局示例,并使用圆形节点几何和锚定边框的直线连线。
const graphOptions: RGOptions = {
debug: true,
defaultNodeBorderWidth: 0,
defaultNodeShape: RGNodeShape.circle,
defaultNodeWidth: 80,
defaultNodeHeight: 80,
defaultLineColor: 'rgb(0,139,189)',
defaultNodeColor: 'rgb(0,139,189)',
defaultLineShape: RGLineShape.StandardStraight,
toolBarPositionH: 'center',
layout: {
layoutName: 'force',
maxLayoutTimes: Number.MAX_SAFE_INTEGER
},
defaultJunctionPoint: RGJunctionPoint.border,
performanceMode: true
};
下面这段代码展示了数据集规模变化时使用的重载路径。
const initializeGraph = async () => {
const myJsonData = generateTestJsonData(testDataSize);
graphInstance.stopAutoLayout();
graphInstance.clearGraph();
await graphInstance.sleep(500);
await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.setZoom(30);
};
下面这段代码展示了该示例如何更新正在运行的 ForceLayout 实例,而不是在每次滑块变化时都重建整张图。
const updateMyOptions = async () => {
const forceLayout = graphInstance.layoutor as InstanceType<typeof RGLayouts.ForceLayout>;
if (forceLayout) {
forceLayout.updateOptions(myForceLayoutOptions);
}
};
下面这段代码证明,这个页面将持续布局与固定次数布局的持续时间策略,与实时系数调节路径分离开来。
const restartForceLayout = async () => {
graphInstance.updateOptions({
layout: {
layoutName: 'force',
maxLayoutTimes: layoutForever ? Number.MAX_SAFE_INTEGER : maxLayoutTimes
}
});
await graphInstance.doLayout();
graphInstance.moveToCenter();
graphInstance.setZoom(30);
};
下面这段代码展示了数据集规模是如何通过扩展一个带分叉的种子图,再对节点和连线的表现数据进行随机化来合成的。
for (const node of data.nodes) {
if (node.id.includes('-')) {
if (testDataSize > 30) {
const level1 = Math.sqrt(testDataSize);
for (let a = 1; a <= level1; a++) {
const aNodeId = node.id + '-' + a;
newNodes.push({ id: aNodeId, text: aNodeId });
newLines.push({ from: node.id, to: aNodeId });
}
} else {
for (let i = 1; i <= testDataSize; i++) {
newNodes.push({ id: node.id + '-' + i, text: node.text + '-' + i });
newLines.push({ from: node.id, to: node.id + '-' + i });
}
}
}
}
下面这段代码证明,最终图在压力测试期间同时保留了缩略图和自定义节点 slot。
<RelationGraph options={graphOptions} onNodeClick={onNodeClick} onLineClick={onLineClick}>
<RGSlotOnView>
<RGMiniView width="300px" height="150px" position="br" />
</RGSlotOnView>
<RGSlotOnNode>
{(nodeSlotProps: RGNodeSlotProps) => {
return <MyNodeSlot node={nodeSlotProps.node} />;
}}
</RGSlotOnNode>
</RelationGraph>
这个示例的独特之处
根据准备好的对比数据,这个示例的独特之处在于,它把通常分散在其他 demo 中的多种模式组合到了一起:relation-graph 内置的 force 布局、performanceMode、从 100 到 10000 个节点和边的六组数据规模预设、六个实时力导系数滑块、一个可拖拽的工具覆盖层、一个缩略图,以及一个在图规模增大时仍保持启用的头像风格自定义节点 slot。这样的组合使它比常规的力导布局展示更适合作为力导布局压力测试的参考。
与 performance-test-tree-layout 相比,这个示例把 UI 配额放在了力导求解器系数和布局持续时间策略上,而不是树方向、节点间距或连线路由控制上。与 layout-force-options 相比,它保留了同类运行时力导调参思路,但将其推进到了显式的 performanceMode 压力测试场景中,使用更大的重新生成数据集、缩略图以及更丰富的节点渲染器。与 layout-force 相比,它不是最小化的内置力导基线示例,而是在负载条件下进一步强调了规模预设和基于 slot 的渲染。
对比数据也限定了不应声称的内容。它不是唯一一个内置 force 示例,也不是唯一一个会更新运行中 ForceLayout 的 demo,更不能作为正式基准测试套件的证据,因为代码并不会收集 FPS、耗时或内存指标。
这种模式还适用于哪里
这种模式适用于内部实验环境,在团队决定是否投入自定义布局引擎之前,用来判断内置图引擎能够被推进到什么程度。合适的场景包括知识图谱探索工具、依赖网络查看器、服务拓扑工作台,以及图半径会随用例显著变化的关系分析控制台。
它也很适合迁移到预生产诊断场景中。团队可以把合成生成器替换为采样后的生产邻域,保留同样的运行时力导控制、同样的缩略图和截图工具,并用这个页面来比较不同图密度下的布局稳定性与渲染成本。这里最关键、最可复用的思路,是把数据重生成、运行中布局器变更,以及重新运行策略分离开来,让每个控制项都具有可预测的效果。