布局预设自动切换
这个示例构建了一个全高的 relation-graph 画布,把一份固定的分支型数据集变成一个布局对比界面。同一组节点和连线会分别以 `center`、从左到右的 `tree`、`circle` 和 `force` 布局显示,同时一个小型浮动窗口始终悬浮在画布上方。
让同一张图在四种内置布局之间自动轮换
此示例构建了什么
这个示例构建了一个全高的 relation-graph 画布,把一份固定的分支型数据集变成一个布局对比界面。同一组节点和连线会分别以 center、从左到右的 tree、circle 和 force 布局显示,同时一个小型浮动窗口始终悬浮在画布上方。
用户可以暂停或恢复自动轮换,拖动或最小化辅助窗口,打开共享的画布设置浮层,修改滚轮和拖拽行为,并将当前图导出为图片。这个示例的重点不是自定义渲染,也不是数据编辑,而是展示一种紧凑模式:同一张已挂载的图在运行时更新布局选项后,会如何改变形态。
数据如何组织
数据位于 initializeGraph() 内部,以一个内联的 RGJsonData 对象存在,包含 rootId: 'a'、一个扁平的 nodes 数组,以及一个带有显式连线 id 的扁平 lines 数组。整张图由一个根节点、四个一级分支,以及每个分支下的若干叶子节点组成。
在调用 setJsonData() 之前没有预处理步骤。这个示例是在原地构造完整载荷,一次性加载,然后持续复用同一个内存中的图,只改变布局选项。
在真实项目中,这种结构可以用来表示组织架构图、服务依赖树、产品分类体系,或任何需要在不同布局家族下查看同一批实体的有根图。
relation-graph 的使用方式
index.tsx 使用 RGProvider 包裹页面,MyGraph.tsx 则通过 RGHooks.useGraphInstance() 获取已挂载的图实例。图选项刻意保持精简:关闭调试模式,默认使用矩形节点,初始布局取自 exampleLayouts 数组中的第一个预设。
初始化采用命令式方式。setJsonData() 加载图数据后,示例会调用 moveToCenter() 和 zoomToFit(),确保初始视图可读,然后将 React 状态切换为 playing,从而开始自动播放。运行时切换循环会通过 updateOptions({ layout }) 在活动实例上更新 layout,等待 doLayout() 完成,然后在每次切换预设后再次居中并适配缩放。
这个示例没有定义图事件处理器、插槽或编辑 API。本地 SCSS 文件也只有空的选择器占位,因此最终可见结果主要来自 relation-graph 的内置渲染,以及共享的浮动辅助组件。
这个浮动窗口不是图插槽,而是一个单独的覆盖层组件,通过 RGProvider 共享图上下文。它的设置面板使用 RGHooks.useGraphStore() 读取当前的滚轮和拖拽模式,调用 setOptions() 修改这些画布行为,并通过 prepareForImageGeneration() 与 restoreAfterImageGeneration() 包装截图导出流程。
关键交互
- 页面在首次加载数据后会立即开始自动播放。
- 播放/停止按钮会切换
playing状态;启用时,一个 2 秒的定时器会通过预设数组不断对同一张图重新布局。 - 每次定时切换都会更新布局,等待
doLayout()完成,然后重新居中并适配缩放,以保证每种构图都保持可读。 - 浮动辅助窗口可以被拖动、最小化,也可以切换到共享设置浮层。
- 设置浮层可以修改滚轮与拖拽行为,并提供图片导出能力。
- 这个示例中没有为单个布局预设提供直接的手动选择器。
关键代码片段
下面这段代码表明,该示例定义了一个预设数组,而不是在运行时动态构建布局。
const exampleLayouts: { label: string, layoutOptions: RGLayoutOptions }[] = [
{
label: 'Center',
layoutOptions: {
layoutName: 'center'
}
},
// ...
{
label: 'Force',
layoutOptions: {
layoutName: 'force'
}
}
];
下面这段代码表明,图数据以内联方式组装为一个 RGJsonData 载荷,并只加载一次。
const myJsonData: RGJsonData = {
rootId: 'a',
nodes: [
{ id: 'a', text: 'a' },
{ id: 'b', text: 'b' },
{ id: 'b1', text: 'b1' },
// ...
],
lines: [
{ id: 'l1', from: 'a', to: 'b' },
// ...
]
};
下面这段代码是挂载阶段的引导流程:加载图数据、重置视口,并启动自动播放。
await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
setPlaying(true);
下面这段代码是重新布局循环的核心:重写当前布局、再次执行布局,然后调度下一个预设。
if (currentLayoutIndexRef.current > exampleLayouts.length - 1) currentLayoutIndexRef.current = 0;
graphInstance.updateOptions({
layout: exampleLayouts[currentLayoutIndexRef.current].layoutOptions
});
await graphInstance.doLayout();
graphInstance.moveToCenter();
graphInstance.zoomToFit();
playNextLayoutTimer.current = setTimeout(() => {
currentLayoutIndexRef.current++;
switchToNextLayout();
}, 2000);
下面这段代码展示了,共享辅助窗口也通过已挂载的图实例提供画布导出能力。
const downloadImage = async () => {
const canvasDom = await graphInstance.prepareForImageGeneration();
let graphBackgroundColor = graphInstance.getOptions().backgroundColor;
if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {
graphBackgroundColor = '#ffffff';
}
const imageBlob = await domToImageByModernScreenshot(canvasDom, {
backgroundColor: graphBackgroundColor
});
await graphInstance.restoreAfterImageGeneration();
};
这个示例的独特之处
这份对比数据将该示例定位为一个布局对比演示场,而不是手动调参面板。它较少见的组合在于:一份内联的分支型数据集、一个 exampleLayouts 预设数组、首次加载后立即启动的自动播放,以及在每次切换后通过 updateOptions({ layout }) 加上 doLayout() 循环并重新居中图形。
与 graph-angle-offset 和 center-layout-options 相比,这个示例更强调在多个布局家族之间进行无人值守的预设轮换,而不是围绕某一个布局家族去调节滑块、角度或间距。与 ever-changing-tree 相比,它去掉了方向、间距和连线几何调节,让重点保持在几个内置布局之间的整体轮廓变化上。
对比记录还表明,共享的浮动工作区在这里是次要角色。不同于 drag-and-wheel-event,这里的主要主题不是画布输入行为;不同于 node-style2,这里的主要主题也不是 CSS 样式。这个示例更聚焦,也更适合用于“只想展示同一张图如何在多个内置布局之间重排且无需重新加载数据”的场景。
这一模式还适用于哪里
这种模式很适合用于演示页面、文档页面和内部评审工具,在这些场景中,团队需要基于同一张图比较多种布局家族,再决定默认展示方式。
它同样适用于组织结构图、依赖图、分类树和知识结构等场景:实体保持不变,但需要比较不同的整体轮廓。可复用的技巧是对已挂载实例的控制流程:数据只加载一次,更新布局选项,重新执行布局,并在不重建整张图的前提下重置视口。