JavaScript is required

Dagre 布局接入与连线标签定位

这是一个 relation-graph 示例:先渲染自定义节点卡片并测量实时尺寸,再在外部运行 Dagre,将计算坐标回写到固定布局场景。它还保留了正交带标签连接线、小地图覆盖层,以及导出和运行时交互设置等共享画布工具。

将 Dagre 集成到固定布局的 relation-graph 查看器中

这个示例构建了什么

这个示例构建了一个全高的 relation-graph 查看器,用于加载静态有向图,让图先渲染一次,以便为每个节点卡片测量尺寸,然后运行 Dagre,并把计算出的坐标写回到当前场景中。最终得到的是一个包含更大根节点卡片、更小矩形子节点卡片、正交带标签连线、浮动说明与设置窗口以及缩略图覆盖层的图。

用户不会交互式调节布局。图会在挂载时自行完成布局,之后用户可以平移、缩放、通过缩略图查看整体结构、打开画布设置、拖动或最小化辅助窗口,以及导出图片。这个示例的主要教学价值在于“外部布局结果回写”的集成模式,而不是内置布局预设。

数据是如何组织的

图数据在 initializeGraph() 内以内联方式声明为一个 RGJsonData 对象。它使用 rootId、扁平的 nodes 数组和扁平的 lines 数组。源数据中的节点只包含 id 和 label。位置被有意省略,因为这些位置会在后续由 Dagre 计算。

在最终布局出现之前,还有一些关键的预处理。代码首先会为尚未拥有 id 的连线分配合成 id。然后调用 setJsonData(),让 relation-graph 渲染自定义节点插槽,并填充每个节点测量得到的 el_Wel_H。完成这次初始渲染后,它会读取相对于根节点的连线,并根据位置将每条线的标签调整为 startend,同时设置不同的 Y 偏移,然后再运行 Dagre。在真实应用中,这种结构同样可以表示依赖图、审批流、服务拓扑、组织结构,或任何其他节点内容需要自定义渲染、且最终坐标应由外部引擎计算的有向网络。

relation-graph 是如何使用的

RGProvider 包裹整个示例,以便 hooks 能够解析到当前激活的图实例。图选项将 relation-graph 保持在 layoutName: 'fixed' 模式下,当最终坐标由外部算法负责时,这正是合适的基础模式。相同的选项还定义了矩形节点、正交连线、上下连接点,以及中性的默认颜色,从而保证 Dagre 的输出具有良好的可读性。

RGHooks.useGraphInstance() 是核心集成点。示例使用它来加载 JSON 数据、检查已渲染的根节点、遍历连线、更新线标签、读取测量得到的节点尺寸、向 Dagre 添加边、通过 updateNodePosition() 回写坐标,并通过 moveToCenter()zoomToFit() 重置视口。节点渲染本身基于插槽:RGSlotOnNode 让根节点显示为更大的灰色卡片,而其他节点使用更小的带边框矩形。RGSlotOnView 挂载了 RGMiniView,因此概览导航能力在外部布局回写之后仍然可用。

浮动工具窗口来自共享辅助代码,而不是示例专属逻辑,但它仍然是最终体验的一部分。在该辅助代码内部,useGraphStore() 会反映当前的拖拽模式和滚轮模式,而 useGraphInstance() 会驱动运行时选项更新,以及图片导出所需的准备与恢复生命周期。本地 SCSS 并不会重定义整套图主题。它主要把线标签样式定义为带灰色边框的紧凑白色小标签,从而让正交连线在重新布局之后依旧清晰可读。

关键交互

第一个重要交互并不是由用户触发的,而是自动发生的:组件挂载时,图会先渲染一次,执行标签位置调整,然后运行 Dagre,最后重新居中。这个启动流程就是主要特性,因为它演示了如何把 relation-graph 的渲染流程与第三方布局引擎结合起来。

布局完成后,用户可以使用常规的平移和缩放行为进行导航,并借助始终可见的缩略图保持方向感。浮动辅助窗口可以被拖动、最小化,并切换到画布设置面板。该面板可以修改滚轮行为、修改画布拖拽行为,并通过共享导出辅助工具下载图片。节点和连线点击处理器仅用于控制台检查,因此它们并不是功能性用户体验的重要部分。

关键代码片段

下面这个选项块证明图保持在固定布局模式,同时仍然使用 relation-graph 的默认样式能力来渲染正交连线和矩形节点。

const graphOptions: RGOptions = {
    debug: false,
    layout: {
        layoutName: 'fixed'
    },
    defaultNodeShape: RGNodeShape.rect,
    defaultLineShape: RGLineShape.StandardOrthogonal,
    defaultJunctionPoint: RGJunctionPoint.tb,
    defaultNodeBorderWidth: 0,
    defaultLineColor: '#666',
    defaultNodeColor: '#fff'
};

下面这个初始化片段展示了“两阶段流程”:先渲染以完成测量,再调整线标签,然后调用外部布局例程。

await graphInstance.setJsonData(myJsonData);
const rootNode = graphInstance.getNodeById(myJsonData.rootId)!;
graphInstance.getLinks().forEach(link => {
    if (link.fromNode.y < rootNode.y) {
        graphInstance.updateLine(link.line.id, { placeText: 'start', textOffsetY: 20 });
    } else {
        graphInstance.updateLine(link.line.id, { placeText: 'end', textOffsetY: -20 });
    }
});
await doMyLayout();

下面这个 Dagre 配置证明该示例使用的是 relation-graph 渲染后得到的节点尺寸,而不是写死的固定尺寸。

const g = new dagre.graphlib.Graph();
g.setGraph({ nodesep: 20, ranksep: 90, ranker: 'network-simplex' });

graphInstance.getNodes().forEach(node => {
    g.setNode(node.id, { width: node.el_W || 100, height: node.el_H || 40 });
});

graphInstance.getLines().forEach(line => {
    g.setEdge(line.from, line.to, line);
});

下面这个回写片段表明,Dagre 只负责计算坐标,而 relation-graph 仍然负责维护实时场景和视口管理。

dagre.layout(g);

g.nodes().forEach((nodeId: string) => {
    const dagreNode = g.node(nodeId);
    graphInstance.updateNodePosition(nodeId, dagreNode.x, dagreNode.y);
});

graphInstance.moveToCenter();
graphInstance.zoomToFit();

下面这个插槽片段证明,外部布局并不会妨碍在同一张图中继续使用自定义节点卡片和缩略图覆盖层。

<RGSlotOnNode>
    {({ node }) => {
        return node.id === 'root' ? (
            <div className="px-2 min-w-[200px] min-h-[50px] rounded border border-gray-500 bg-gray-100 flex items-center justify-center w-full h-full text-sm text-slate-800 font-bold select-none">
                {node.text}
            </div>
        ) : (
            <div className="px-2 min-w-[100px] rounded border border-gray-500 flex items-center justify-center w-full h-full text-sm text-slate-800 font-medium select-none">
                {node.text}
            </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
});
await graphInstance.restoreAfterImageGeneration();

这个示例的独特之处

对比数据表明,这个示例并不是唯一一个第三方布局集成示例,但它是 Dagre 回写模式最清晰的基础参考之一。相较于 use-dagre-layout-2,它移除了运行时 Dagre 调节控件,并把教学重点收窄为一个更明确的流程:渲染图、测量自定义节点插槽、运行 Dagre、回写位置。这样一来,它更容易被复用为实现范式,而不是布局实验场。

对比也突出了它与邻近示例相比更少见的一组组合特性。与 use-sigma-layout 相比,这个示例更强调有向结构、正交连线,以及布局前的线标签启发式处理,而不是力导布局实验或数据集切换。与 use-d3-layout 相比,它保持节点几何形态稳定,并且只把外部算法用于位置更新,而不是把节点重塑成另一种层级可视化。这里最具辨识度的组合是:外部 Dagre 集成加“渲染-测量-回写”流程、自定义矩形节点卡片、正交标签样式、缩略图导航,以及共享查看器工具全部集中在一个紧凑的查看器型示例中。

这一模式还能应用到哪里

这种模式非常适合那些需要第三方布局引擎、但仍希望保留 relation-graph 插槽、覆盖层和视口工具能力的系统。典型场景包括依赖图、架构图、编排流程、策略树、服务调用图,以及其他节点内容比纯文本标签更丰富的有向结构。

当最终节点尺寸取决于已渲染 HTML,而不是固定模式时,它同样是一个很好的起点。在这种情况下,可以复用“渲染-测量-布局-回写”这一序列,并继续使用 Dagre,或者替换为其他外部引擎,而 relation-graph 仍负责节点插槽、连线样式、缩略图显示、导出准备,以及运行时画布控制。