Mermaid tidy-tree 双向树布局引擎:@mermaid-js/layout-tidy-tree 原理与实践
2026/9/7 6:28:05 网站建设 项目流程

Mermaid tidy-tree 双向树布局引擎:@mermaid-js/layout-tidy-tree 原理与实践

【免费下载链接】mermaidGeneration of diagrams like flowcharts or sequence diagrams from text in a similar manner as markdown项目地址: https://gitcode.com/GitHub_Trending/me/mermaid

本文基于仓库中的 tidy-tree 布局包说明 及其配套源码,完整讲解 Mermaid 的tidy-tree(双向整洁树)布局引擎:它如何通过 frontmatter 配置一键启用,如何在 Bundler 与 CDN 两种环境下注册,以及其底层“左右双树 + 坐标转置”的算法实现与边路由细节。读完后,你将掌握该引擎的接入方式、关键参数含义与源码级工作原理。

什么是 tidy-tree 布局引擎

@mermaid-js/layout-tidy-tree是 Mermaid 官方 monorepo 中的一个独立布局引擎包,基于non-layered-tidy-tree-layout算法实现,为图表提供双向(bidirectional)的整洁树布局。与 dagre 等分层布局不同,它把整棵树拆成左右两棵子树,分别从中心根节点向水平左、右两个方向生长,形成对称、均衡的布局,非常适合思维导图、组织架构图等树状图表。

需要注意其分发的特殊性:正如 README 中明确提示的——该布局引擎不会默认包含在所有支持 mermaid 的网站/提供商中,使用方必须自行安装@mermaid-js/layout-tidy-tree包才能启用 Tidy Tree 布局。

从 package.json 可以确认几个关键事实:

  • 包名为@mermaid-js/layout-tidy-tree,当前版本0.2.2
  • peerDependencies要求mermaid: ^11.0.2,即需要 Mermaid 11 及以上版本的主包;
  • 运行时依赖d3,布局核心算法来自 devDependencies 中的non-layered-tidy-tree-layout^2.0.2);
  • 入口模块为dist/mermaid-layout-tidy-tree.core.mjs,类型声明为dist/layouts.d.ts

包的公开 API 由 src/index.ts 统一导出:默认导出布局加载器定义(./layouts.js)、类型(./types.js)、布局算法函数(./layout.js)以及渲染函数render./render.js)。

快速上手

通过 frontmatter 配置启用

在图表源码前通过 YAML frontmatter 指定layout: tidy-tree,即可让 mermaid 改用该引擎布局。最典型的适用场景是 mindmap(思维导图),README 给出的最小示例如下:

--- config: layout: tidy-tree --- mindmap root((mindmap)) A B

仓库中另一份面向用户的文档 tidy-tree 布局说明 补充了同样的用法示例,并给出了一个带图标(::icon(fa fa-book))与多级缩进的完整 mindmap 例子,同时注明:目前 tidy-tree 主要针对 mindmap 图型提供支持

在 Bundler 环境中接入

npm install @mermaid-js/layout-tidy-tree
import mermaid from 'mermaid'; import tidyTreeLayouts from '@mermaid-js/layout-tidy-tree'; mermaid.registerLayoutLoaders(tidyTreeLayouts);

registerLayoutLoaders是 Mermaid 主包暴露的布局扩展点,定义于 rendering-util/render.ts,并在主入口 mermaid.ts 中导出。调用它之后,布局引擎按需懒加载——这一点可以从本包的加载器定义 layouts.ts 中看出:

const loader = async () => await import(`./render.js`); const tidyTreeLayout: LayoutLoaderDefinition[] = [ { name: 'tidy-tree', loader, algorithm: 'tidy-tree', }, ];

也就是说,注册时并不会立即加载渲染代码,只有当某个图表通过layout: tidy-tree实际请求该算法时,才会动态importrender.ts,这对按需加载与包体积友好。

在 CDN 环境中接入

在纯页面脚本场景下,通过<script type="module">分别以 ESM 方式导入 mermaid 主包与@mermaid-js/layout-tidy-tree的构建产物(mermaid.esm.min.mjsmermaid-layout-tidy-tree.esm.min.mjs,可从常用 ESM CDN 按包名mermaid@mermaid-js/layout-tidy-tree获取),然后执行同一句mermaid.registerLayoutLoaders(tidyTreeLayouts)即可完成注册。两种接入方式的注册 API 完全一致。

双向布局算法解析

布局核心实现在 layout.ts 中,主入口是executeTidyTreeLayout(data: LayoutData)(L23),它遵循 Mermaid 统一的渲染模式:接收LayoutData(节点、边、配置),产出带坐标的LayoutResult。整个流程可以拆解为四步。

1. 数据校验与根节点确定

函数首先校验data.nodes非空,否则抛出No nodes found in layout dataedges缺省会被补为空数组。随后convertToDualTreeFormat(L80-L138)遍历所有边,构建children(父 → 子列表)与parents两个映射,并以“没有任何入边的节点”作为根;若找不到,则回退取nodes[0],尺寸缺省取 100x50。

2. 左右子树的交替拆分

这是“双向”布局的关键:根节点的孩子按索引奇偶交替分配到左右两棵子树——

  • 左树:第 1、3、5… 个孩子(index % 2 === 0);
  • 右树:第 2、4、6… 个孩子。

对应代码见 L124-L130。两棵子树各自挂在一个 1x1 的“虚拟根”(virtual-root-*)下,再交给non-layered-tidy-tree-layoutLayout/BoundingBox完成各自子树的间距与坐标计算。布局间距参数在源码中硬编码:gap = 20(节点间水平间距)、bottomPadding = 40(L38-L43)。

3. 坐标转置与旋转放置

由于 tidy-tree 算法本身生成的是竖直向下生长的树,而本引擎要求水平生长,源码对宽高了做了一次“转置”:convertNodeToTidyTreeTransposed(L166-L184)把节点送入算法前交换 width/height(width: node.height, height: node.width),算法返回后再按旋转 90° 的语义还原坐标——

  • positionLeftTreeBidirectional(L298-L326):左树逆时针旋转 90°,最终x = offsetX - distanceFromRoot,即向生长;
  • positionRightTreeBidirectional(L332-L360):右树顺时针旋转 90°,x = offsetX + distanceFromRoot,即向生长。

两棵树与根之间的间距由treeSpacing = rootNode.width / 2 + 30决定(L199),根节点最终落在(0, 20)(L258-L266)。combineAndPositionTrees(L189 起)还会分别计算左右两侧第一层节点的垂直中心,并整体平移,使左右两树的“第一层”在根节点处对齐,形成 README 中描述的对称结构:

[Child 3] ← [Child 1] ← [Root] → [Child 2] → [Child 4]

每个节点会被打上section: 'root' | 'left' | 'right'标签,用于后续边路由判断。

4. 边路由:从形状边缘出发,按分区折线

calculateEdgePositions(L455-L628)根据定位后的节点计算每条边的折线点:

  • 起点/终点不落在节点中心,而是与形状边界求交:矩形用intersection()按中心连线与矩形四边的交点计算(L389 起);circlecloudbang这类圆形形状则用computeCircleEdgeIntersection()求直线与圆的交点(L369-L387);
  • 折线中间点依据源/目标的section生成:从根出发的边会先水平走向目标所在的一侧(偏移量intersectionShift = 30,L40),使边“从根面向目标的那一侧”离开;进入 left/right 分区节点的边同样先贴水平方向再折向目标。这一行为有专门测试覆盖——layout.test.ts 中标注了它对应上游 issue #7572(“route root-sourced edges out the side of the root facing the target”)。

渲染管线:DOM 实测尺寸 → 布局 → 定位

render.ts 中的render函数(L27 起)接收 Mermaid 统一渲染接口(data4Layout: LayoutDatasvgInternalHelpersRenderOptions),与 ELK/dagre 渲染器遵循同样的三步模式:

  1. 插入节点测量尺寸:先调用 helpers 的insertNode/insertCluster把节点真实渲染进隐藏 DOM,再通过getBBox()取回每个节点的真实宽高(L51-L89),并以实测值回填LayoutData(L93-L103)。真实尺寸正是转置计算能避免“高节点压住相邻节点”的前提;
  2. 执行布局await executeTidyTreeLayout(updatedLayoutData)得到带坐标的节点与边(L105);
  3. 定位与连线:按结果对每个节点 DOM 施加transform: translate(x, y)(L109-L124),随后insertEdge绘制边、positionEdgeLabel定位边标签(L128-L177)。

布局结果的数据结构

types.ts 定义了引擎对外暴露的核心类型:

类型含义关键字段
PositionedNode(L9-L18)布局完成后的节点idxysection: 'root'\|'left'\|'right'widthheightoriginalNode
PositionedEdge(L23-L41)布局完成后的边startX/YendX/YmidX/Ypoints(折线点列)、两端节点的section与尺寸
LayoutResult(L46-L49)executeTidyTreeLayout的返回值nodesedges
TidyTreeNode(L54-L62)送入算法的树节点(与non-layered-tidy-tree-layout兼容)idwidthheightchildren_originalNode
TidyTreeLayoutConfig(L67-L70)布局间距配置gapbottomPadding

从源码结构看,gap/bottomPadding实际以20/40的形式直接传入BoundingBox构造函数,TidyTreeLayoutConfig更偏向对这一配置形态的类型化描述。

测试如何验证“双向交替”行为

单测 layout.test.ts 对算法行为做了精确断言,可直接作为理解算法的“参照系”(测试中 mock 掉了non-layered-tidy-tree-layout依赖):

  • 用 root + 4 个孩子的数据,断言根节点位于(0, 20),且child1child3的 x 坐标小于 0(在根的左侧),child2child4的 x 坐标大于 0(在根的右侧),完整验证了“奇数孩子在左、偶数孩子在右”的交替拆分(L279-L310);
  • 用高 120 与高 30 的两个孩子验证坐标转置不会导致节点重叠、尺寸回填正确(L312-L407);
  • validateLayoutData的校验分支(缺 data/config/nodes/edges 各自抛错)与空节点数据的错误处理也有专门用例(L184-L207、L246-L264)。

适用场景与限制

  • 适用前提:Mermaid 11.0.2+,且必须显式安装并registerLayoutLoaders注册本包;图表源码需以layout: tidy-treefrontmatter 声明启用;
  • 图型支持:从官方文档 tidy-tree.md 的说明看,当前 tidy-tree 主要针对mindmap图型;算法本身遵循 Mermaid 统一渲染模式,从源码结构看,任何能提供兼容LayoutData的图型理论上都可复用,但实际支持范围以文档标注为准;
  • 布局假设:算法把“无入边的节点”识别为根(找不到时回退到第一个节点),因此它面向的是单一根、树状的拓扑。对于存在多父节点或环的数据,其拆分与定位行为在源码中没有专门处理分支,使用时应确保输入确实是树结构。

如果你想进一步阅读,建议沿以下路径:入口 index.ts → 加载器 layouts.ts → 算法 layout.ts → 渲染 render.ts → 测试 layout.test.ts,并对照 Mermaid 主包的布局扩展点 rendering-util/render.ts 理解registerLayoutLoaders的工作机制。

【免费下载链接】mermaidGeneration of diagrams like flowcharts or sequence diagrams from text in a similar manner as markdown项目地址: https://gitcode.com/GitHub_Trending/me/mermaid

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询