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.2peerDependencies要求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-treeimport 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 typemodule分别以 ESM 方式导入 mermaid 主包与mermaid-js/layout-tidy-tree的构建产物mermaid.esm.min.mjs与mermaid-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缺省会被补为空数组。随后convertToDualTreeFormatL80-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-layout的Layout/BoundingBox完成各自子树的间距与坐标计算。布局间距参数在源码中硬编码gap 20节点间水平间距、bottomPadding 40L38-L43。3. 坐标转置与旋转放置由于 tidy-tree 算法本身生成的是竖直向下生长的树而本引擎要求水平生长源码对宽高了做了一次“转置”convertNodeToTidyTreeTransposedL166-L184把节点送入算法前交换 width/heightwidth: node.height, height: node.width算法返回后再按旋转 90° 的语义还原坐标——positionLeftTreeBidirectionalL298-L326左树逆时针旋转 90°最终x offsetX - distanceFromRoot即向左生长positionRightTreeBidirectionalL332-L360右树顺时针旋转 90°x offsetX distanceFromRoot即向右生长。两棵树与根之间的间距由treeSpacing rootNode.width / 2 30决定L199根节点最终落在(0, 20)L258-L266。combineAndPositionTreesL189 起还会分别计算左右两侧第一层节点的垂直中心并整体平移使左右两树的“第一层”在根节点处对齐形成 README 中描述的对称结构[Child 3] ← [Child 1] ← [Root] → [Child 2] → [Child 4]每个节点会被打上section: root | left | right标签用于后续边路由判断。4. 边路由从形状边缘出发按分区折线calculateEdgePositionsL455-L628根据定位后的节点计算每条边的折线点起点/终点不落在节点中心而是与形状边界求交矩形用intersection()按中心连线与矩形四边的交点计算L389 起circle、cloud、bang这类圆形形状则用computeCircleEdgeIntersection()求直线与圆的交点L369-L387折线中间点依据源/目标的section生成从根出发的边会先水平走向目标所在的一侧偏移量intersectionShift 30L40使边“从根面向目标的那一侧”离开进入 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: LayoutData、svg、InternalHelpers、RenderOptions与 ELK/dagre 渲染器遵循同样的三步模式插入节点测量尺寸先调用 helpers 的insertNode/insertCluster把节点真实渲染进隐藏 DOM再通过getBBox()取回每个节点的真实宽高L51-L89并以实测值回填LayoutDataL93-L103。真实尺寸正是转置计算能避免“高节点压住相邻节点”的前提执行布局await executeTidyTreeLayout(updatedLayoutData)得到带坐标的节点与边L105;定位与连线按结果对每个节点 DOM 施加transform: translate(x, y)L109-L124随后insertEdge绘制边、positionEdgeLabel定位边标签L128-L177。布局结果的数据结构types.ts 定义了引擎对外暴露的核心类型类型含义关键字段PositionedNodeL9-L18布局完成后的节点id、x、y、section: root\|left\|right、width、height、originalNodePositionedEdgeL23-L41布局完成后的边startX/Y、endX/Y、midX/Y、points折线点列、两端节点的section与尺寸LayoutResultL46-L49executeTidyTreeLayout的返回值nodes、edgesTidyTreeNodeL54-L62送入算法的树节点与non-layered-tidy-tree-layout兼容id、width、height、children、_originalNodeTidyTreeLayoutConfigL67-L70布局间距配置gap、bottomPadding从源码结构看gap/bottomPadding实际以20/40的形式直接传入BoundingBox构造函数TidyTreeLayoutConfig更偏向对这一配置形态的类型化描述。测试如何验证“双向交替”行为单测 layout.test.ts 对算法行为做了精确断言可直接作为理解算法的“参照系”测试中 mock 掉了non-layered-tidy-tree-layout依赖用 root 4 个孩子的数据断言根节点位于(0, 20)且child1、child3的 x 坐标小于 0在根的左侧child2、child4的 x 坐标大于 0在根的右侧完整验证了“奇数孩子在左、偶数孩子在右”的交替拆分L279-L310;用高 120 与高 30 的两个孩子验证坐标转置不会导致节点重叠、尺寸回填正确L312-L407validateLayoutData的校验分支缺 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),仅供参考