使用 Graphology ForceAtlas2 布局
这是一个 relation-graph 示例:先渲染样例数据并读取节点实测尺寸,再执行 Graphology 圆形初始化 + ForceAtlas2 计算,并将缩放后的坐标写回固定布局场景。它还提供数据集切换、布局缩放调节,以及共享悬浮面板中的画布设置与图片导出。
在固定布局的 relation-graph Viewer 中使用 Graphology ForceAtlas2
这个示例构建了什么
这个示例构建了一个全高度的 relation-graph 查看器。它会加载两组示例网络中的一组,先让 relation-graph 渲染一次节点,以便获得节点的真实尺寸,然后再运行一次外部的 Graphology 布局计算,并将返回的坐标写回当前图中。最终的视觉结果是一个轻量级网络图:包含白色圆形节点、灰色直线连接、沿路径绘制的连线标签,以及悬浮在画布上方的辅助窗口。
用户可以切换数据集,通过缩放滑块拉伸或压缩返回的布局,拖动或最小化辅助窗口,打开画布设置,以及导出图片。这个示例的核心实现点是“渲染-测量-重新布局”这一流程:relation-graph 负责渲染和交互,而 Graphology 加上 ForceAtlas2 负责计算最终坐标。
数据是如何组织的
图数据直接在 initializeGraph() 中以内联方式声明为一个 RGJsonData 对象,其中包含 rootId、一个扁平的 nodes 数组,以及一个扁平的 lines 数组。该示例在内存中维护了两份数据集,并通过本地 React state 在它们之间切换,而不是从远程数据源加载。
在最终布局出现之前,还有一个重要的预处理过程。代码会先在缺少 id 的情况下为连线补充合成的 line id,然后调用 setJsonData() 让 relation-graph 渲染图,再读取每个已渲染节点的 el_W 和 el_H,之后才构建一个临时的 Graphology 图。外部布局完成后,示例会先将计算得到的 x 和 y 与 layoutScale 相乘,再把它们写回固定布局场景中。在真实应用里,同样的数据结构也可以用于承载服务依赖、实体关系、工作流连接、协作网络,或任何需要从外部求解器获取坐标的节点连线型数据集。
relation-graph 是如何使用的
入口文件中使用 RGProvider 包裹整个页面,这样 RGHooks.useGraphInstance() 才能解析当前激活的图实例。主图配置将 relation-graph 保持在 layoutName: 'fixed',当坐标来自库外部时,这正是正确的模式。这组配置还同时启用了沿路径显示的连线标签、圆形节点、直线连接、边界连接点、60 x 60 的默认节点尺寸,以及极简的白灰配色。
RGHooks.useGraphInstance() 是这里最核心的集成入口。示例通过它使用 setJsonData() 加载 JSON 数据,使用 getNodes() 和 getLines() 读取已渲染的节点和连线,通过 updateNodePosition() 写回外部坐标,并结合 moveToCenter() 与 zoomToFit() 将结果重新居中。悬浮设置面板也使用了同一个 hook,并配合 RGHooks.useGraphStore() 在运行时切换滚轮和拖拽行为,以及通过 prepareForImageGeneration() 和 restoreAfterImageGeneration() 完成图片导出的完整流程。
图本身在这个演示中没有使用节点、连线或视图插槽。它依赖 relation-graph 的默认渲染,而把外部布局流程作为主要的定制点。本地 SCSS 也只做了很少的覆盖:基本只是保留了扩展钩子,并为 .my-layout-top 定义了一个单独的竖排文本变体,但当前组件并没有启用它。
关键交互
最重要的交互是由状态驱动的重新布局流程。切换示例选择器会重新加载另一组内联数据集,重新渲染图,并再次执行外部布局。调整 layout-scale 滑块不会修改 ForceAtlas2 的参数,而是重新执行坐标写回流程,让同一套布局结果在 relation-graph 画布上变得更疏或更密。
辅助窗口提供了次级交互。用户可以在画布上拖动它、将其最小化、打开设置面板、把滚轮行为切换为滚动、缩放或禁用,把拖拽行为切换为框选、移动或禁用,以及下载图片。节点点击和连线点击只用于控制台检查,因此不会改变图,也不会触发其他流程。
关键代码片段
这段配置代码表明,图被有意保持在固定布局模式下,同时 relation-graph 仍然负责提供节点和连线的默认渲染。
const graphOptions: RGOptions = {
debug: false,
defaultLineTextOnPath: true,
layout: {
layoutName: 'fixed' // 使用自定义布局时,建议设为 fixed
},
defaultNodeShape: RGNodeShape.circle,
defaultLineShape: RGLineShape.StandardStraight,
defaultJunctionPoint: RGJunctionPoint.border
};
这段初始化片段展示了预处理步骤:在 relation-graph 执行首次渲染之前,先补充合成的 line id。
myJsonData.lines.forEach((line, index) => {
if (!line.id) line.id = `L${index}`;
});
await graphInstance.setJsonData(myJsonData);
doMyLayout();
这段代码证明,外部布局图是基于 relation-graph 已渲染的节点和当前连线列表构建的,而不是基于硬编码的几何信息。
graphInstance.getNodes().forEach(node => {
graph.addNode(node.id, {
text: node.text,
width: node.el_W,
height: node.el_H
});
});
graphInstance.getLines().forEach(line => {
graph.addEdge(line.from, line.to, {
id: line.id,
weight: 1
});
});
这段代码展示了外部 Graphology 计算过程,以及如何把经过缩放的坐标写回 relation-graph。
circular.assign(graph);
forceAtlas2.assign(graph, 50);
graph.nodes().forEach(nodeId => {
const node = graph.getNodeAttributes(nodeId);
const rgNode = graphInstance.getNodeById(nodeId);
graphInstance.updateNodePosition(nodeId, node.x * layoutScale, node.y * layoutScale);
});
graphInstance.moveToCenter();
graphInstance.zoomToFit();
这一对 effect 说明,数据集变化会触发完整重载,而 layout-scale 变化会重新执行外部坐标写回流程。
useEffect(() => {
initializeGraph();
}, [dataId]);
useEffect(() => {
doMyLayout();
}, [layoutScale]);
这段辅助代码展示了共享悬浮面板如何为导出做准备、截取画布,并在之后恢复图状态。
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();
这个示例的独特之处
对比数据表明,这个示例并不是唯一的外部布局演示,但它相较于邻近的 Dagre 和内置 force 示例,组合方式更少见。它最鲜明的模式,是一个两阶段的“渲染-测量-重新布局”流程:先将 relation-graph 的节点和连线镜像到 Graphology 中,用 circular.assign(...) 为图提供初始状态,再通过 forceAtlas2.assign(...) 做迭代松弛,最后把经过缩放的坐标写回一个 fixed 的 relation-graph 场景。
与 use-dagre-layout 相比,这个示例更强调 force 风格的实验、数据集切换,以及坐标缩放,而不是正交结构、小地图导航或连线标签摆放启发式。与 use-dagre-layout-2 相比,它的重点不是调节单一算法的间距参数,而是展示一个可对不同示例重复执行的外部布局交接方案。与 layout-force 相比,关键差别在于布局智能所在的位置:后者完全使用 relation-graph 的内置 force 求解器,而这个示例把布局工作交给 Graphology,只把 relation-graph 用于渲染、交互和视口管理。
这种模式还适用于哪里
这种模式非常适合已经依赖 Graphology、ForceAtlas2 或其他外部布局引擎,但仍希望使用 relation-graph 来完成渲染、视口工具和辅助浮层的应用场景。典型迁移目标包括知识图谱查看器、依赖关系浏览器、社交网络分析工具、资产关系图,以及那些节点尺寸取决于实际渲染内容的架构图。
当同一底层图需要多种展示密度时,它也很有用。产品可以保留同一份求解结果,然后通过缩放返回坐标来支持概览模式、紧凑模式、展厅大屏显示或截图准备,而不必改变原始关系,也不需要脱离 relation-graph 的固定布局渲染模型。