Mermaid ELK 布局引擎插件完全指南:@mermaid-js/layout-elk 的安装、配置与源码解析
【免费下载链接】mermaidGeneration of diagrams like flowcharts or sequence diagrams from text in a similar manner as markdown项目地址: https://gitcode.com/GitHub_Trending/me/mermaid
本文基于 Mermaid 仓库中的packages/mermaid-layout-elk包文档,讲解如何将 ELK 布局引擎作为插件接入 Mermaid:包括三种启用 ELK 布局的方式(独立指令、frontmatter 配置、代码注册)、各布局算法的选型,以及elk配置组下每个参数的源码级含义。读完本文,你可以在自己的项目中让流程图获得正交路由、自动分层、可预测的布局结果,并理解 Mermaid 通用布局渲染框架如何调用 elkjs 完成排版。
1. 什么是 ELK 布局插件
Mermaid 的默认布局基于 dagre 系分层算法。@mermaid-js/layout-elk包则提供了基于 Eclipse Layout Kernel(ELK)的替代布局引擎,由独立的 README 说明:
This package provides a layout engine for Mermaid based on the ELK layout engine.
该包当前的实现事实(来自 package.json):
- 包名
@mermaid-js/layout-elk,当前版本 0.2.3,MIT 协议; - 核心依赖为
elkjs(^0.9.3,ELK 的 JavaScript 移植)与d3(^7.9.0,曲线工具); - peer 依赖
mermaid: ^11.0.2,即需要 Mermaid 11.x 环境; - 发布产物为
dist/mermaid-layout-elk.core.mjs(ESM),类型声明在dist/layouts.d.ts。
官方文档特别强调了一条部署前提:ELK 布局引擎不会随所有支持 Mermaid 的第三方站点默认提供,网站方必须自行安装该包才能使用。这正是它被设计成独立 npm 插件包的原因——ELK 体积较大,插件化可以让不需要它的用户零成本使用 Mermaid 核心。
2. 三种启用 ELK 布局的方式
README 给出了三类用法,覆盖指令级、文档级和代码级三个粒度。
2.1 方式一:独立指令flowchart-elk
最简单的启用方式是在图中直接声明flowchart-elk,Mermaid 检测到该指令后会加载 ELK 布局器:
flowchart-elk TD A --> B A --> C2.2 方式二:frontmatter 配置layout
在 YAML frontmatter 中设置layout: elk,对标准flowchart指令生效:
--- config: layout: elk --- flowchart TD A --> B A --> C也可以指定具体算法,例如应力布局(stress layout):
--- config: layout: elk.stress --- flowchart TD A --> B A --> C2.3 方式三:代码注册布局加载器
无论哪种方式,都需要在宿主应用中把 ELK 加载器注册进 Mermaid。使用打包器时:
npm install @mermaid-js/layout-elkimport mermaid from 'mermaid'; import elkLayouts from '@mermaid-js/layout-elk'; mermaid.registerLayoutLoaders(elkLayouts);无打包器、走 CDN 的场景(ESM 动态导入):
<script type="module"> import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'; import elkLayouts from 'https://cdn.jsdelivr.net/npm/@mermaid-js/layout-elk@0/dist/mermaid-layout-elk.esm.min.mjs'; mermaid.registerLayoutLoaders(elkLayouts); </script>从源码看,registerLayoutLoaders是 Mermaid 主包公开 API 的一部分,定义在 mermaid.ts,实际逻辑位于 rendering-util/render.ts。它接收LayoutLoaderDefinition[],每个条目包含name(布局名)、loader(惰性加载函数的 Promise)和可选的algorithm(底层 ELK 算法标识)。
3. 支持的布局算法
README 的 "Supported layouts" 一节列出了五个布局名。对照 layouts.ts 可以确认其注册结构:
| 布局名 | 底层算法 | 说明 |
|---|---|---|
elk | elk.layered | 默认布局,等价于elk.layered,分层布局 |
elk.layered | elk.layered | 分层布局(Sugiyama 风格,正交边路由) |
elk.stress | elk.stress | 应力布局(基于节点间"弹簧"平衡) |
elk.force | elk.force | 力导向布局 |
elk.mrtree | elk.mrtree | 多根树布局(Multi-Root Tree) |
elk.sporeOverlap | elk.sporeOverlap | Spore 重叠布局 |
layouts.ts的关键结构:
const loader = async () => await import(`./render.js`); const algos = ['elk.stress', 'elk.force', 'elk.mrtree', 'elk.sporeOverlap']; const layouts: LayoutLoaderDefinition[] = [ { name: 'elk', loader, algorithm: 'elk.layered', }, ...algos.map((algo) => ({ name: algo, loader, algorithm: algo, })), ];两个值得注意的设计点:
elk是elk.layered的别名。无论用户写layout: elk还是elk.layered,底层都会把elk.layered作为elk.algorithm传给 elkjs。- 所有布局共用同一个渲染模块(
./render.js),差异只在algorithm字段。这意味着 ELK 的图构建、几何修正、绘制逻辑对五种算法完全复用。
4. ELK 布局的内部工作流程
插件的入口逻辑在 render.ts。它通过 Mermaid 导出的createCommonLayoutRenderer工厂挂入通用布局渲染框架,暴露两个核心回调:
export const render = createCommonLayoutRenderer<ElkLayoutResult, ElkPreparedLayout>({ prepareLayout: prepareLayoutForElk, runLayoutCore: runElkLayoutCore, paintOptions: { skipIntersect: true, }, });从源码结构看,一次完整的 ELK 布局按如下阶段执行(对应runElkLayoutCore→buildElkGraphFromLayoutData调用链):
- 同步配置:
syncElkPackageConfig通过mermaid.mermaidAPI.setConfig把当前解析后的配置回写给内部 API,保证后续测量与主题计算使用同一份配置。 - 构建 ELK 图:
buildElkGraphFromLayoutData依次执行——createRootElkGraph:创建根 ELK 图节点,写入全部默认布局选项(elk.hierarchyHandling: INCLUDE_CHILDREN、spacing.baseValue: 40、elk.layered.mergeHierarchyEdges: true等),并透传用户在config.elk下设置的nodePlacementStrategy、nodePlacementAlignment、mergeEdges、forceNodeModelOrder、considerModelOrder、cycleBreakingStrategy;addSubGraphs:按parentId建立子图(subgraph)父子关系树;addVertices/createElkNode:把 Mermaid 的LayoutData.nodes转为 ELK 节点,普通节点带上预测量的width/height,组节点保留children递归结构;addEdgesToElkGraph:把边转为 ELK 边,标签设为edgeLabels.inline: true、placement: CENTER;configureSubgraphNodes:为每个子图写入布局选项(spacing.baseValue: 30、nodeLabels.placement: '[H_CENTER V_TOP, INSIDE]'),子图方向通过dir2ElkDirection映射(LR→RIGHT、RL→LEFT、TB/TD→DOWN、BT→UP);configureCrossHierarchyEdges:对跨子图边查找公共祖先(findCommonAncestor),沿路径设置elk.hierarchyHandling: INCLUDE_CHILDREN,让 ELK 允许边"穿过"中间容器;applyCyclicEntryConstraint:若开启elk.keepEntryNodeOnTop,把含环子图的入口节点钉在首层(详见第 5 节)。
- 执行布局:
runElkLayout调用elk.layout(elkGraph),拿到带坐标的 ELK 结果。 - 回写坐标:
applyElkLayoutResult→applyElkNodePositions递归地把 ELK 返回的节点坐标(含嵌套子图的相对偏移)换算回 MermaidLayoutData中的中心点坐标;applyElkEdgeLayout把 ELK 边的sections(起点、折点、终点)换算成 Mermaid 边点序列,并把端点吸附到节点中心、统一设置layoutEdge.curve = 'rounded'。 - 排序绘制:
orderNodesForElkPaint把组节点(按嵌套深度升序)排在普通节点之前,保证容器先于内容绘制;随后由通用渲染器的 paint 阶段完成 SVG 绘制。
其中paintOptions.skipIntersect: true表明 ELK 路径跳过了通用渲染器的节点相交检测——因为 ELK 的几何修正由本插件自己完成,geometry.ts 提供了computeNodeIntersection、replaceEndpoint、onBorder等工具函数处理边与节点边界(含菱形等形状)的精确交点。
仓库还包含针对这些函数的单测,位于 packages/mermaid-layout-elk/src/tests/,覆盖几何计算(geometry.spec.ts)、完整渲染(render.spec.ts)与公共渲染器导入(common-renderer-import.spec.ts)。
5.elk配置项详解
Mermaid 全局配置中的elk对象(schema 定义在 config.schema.yaml)提供以下可选项。它们在createRootElkGraph中被逐一映射为 ELK 的layoutOptions:
config: layout: elk elk: mergeEdges: true nodePlacementStrategy: BRANDES_KOEPF nodePlacementAlignment: NONE cycleBreakingStrategy: GREEDY_MODEL_ORDER forceNodeModelOrder: false considerModelOrder: NODES_AND_EDGES keepEntryNodeOnTop: false5.1mergeEdges(布尔,默认false)
Elk specific option that allows edges to share path where it convenient. It can make for pretty diagrams but can also make it harder to read the diagram.
允许多条边共享同一路径(合并平行边)。映射到 ELK 的elk.layered.mergeEdges。源码中它同时被透传到子图层:buildSubgraphLayoutOptions对每个子图写入'elk.layered.mergeEdges': elkConfig?.mergeEdges。这一点在 0.2.2 版本曾是一个 bug 修复点——此前子图内部的边不会应用该配置(见 CHANGELOG),修复后顶层与子图行为一致。
5.2nodePlacementStrategy(枚举,默认BRANDES_KOEPF)
控制分层布局中每层内节点的横向放置算法,取值:
SIMPLE:简单按序放置;NETWORK_SIMPLEX:网络单纯形法优化交叉;LINEAR_SEGMENTS:线性段法;BRANDES_KOEPF:Brandes-Koepf 法(默认,兼顾对齐与交叉数)。
映射到nodePlacement.strategy,并同样透传给每个子图。
5.3nodePlacementAlignment(枚举,默认NONE)
Elk specific option affecting Brandes-Koepf node placement alignment. NONE picks the alignment with the smallest height.
在BRANDES_KOEPF策略产生多个合法对齐(alignment)时决定选哪一个,取值NONE/LEFTUP/LEFTDOWN/RIGHTUP/RIGHTDOWN/BALANCED,其中NONE表示选高度最小的对齐(整体图更紧凑)。对应 ELK 选项elk.layered.nodePlacement.bk.fixedAlignment,未设置时以常量DEFAULT_NODE_PLACEMENT_ALIGNMENT = 'NONE'兜底(见 render.ts)。
5.4cycleBreakingStrategy(枚举,默认GREEDY_MODEL_ORDER)
This strategy decides how to find cycles in the graph and deciding which edges need adjustment to break loops.
决定如何检测环路并选择"回边"反转,取值GREEDY/DEPTH_FIRST/INTERACTIVE/MODEL_ORDER/GREEDY_MODEL_ORDER,映射到elk.layered.cycleBreaking.strategy。默认值带MODEL_ORDER,即尽量尊重源码中声明的节点顺序来断环。
5.5forceNodeModelOrder(布尔,默认false)
The node order given by the model does not change to produce a better layout.
设为true时,交叉最小化阶段不再重排节点——模型里 A 在 B 之前,布局里就保持 A 在 B 之前。官方提示这需配合considerModelOrder.strategy: NODES_AND_EDGES才能达到预期。映射到elk.layered.crossingMinimization.forceNodeModelOrder。
5.6considerModelOrder(枚举,默认NODES_AND_EDGES)
Preserves the order of nodes and edges in the model file if this does not lead to additional edge crossings.
在不增加交叉的前提下保留源码中节点/边的声明顺序,取值NONE/NODES_AND_EDGES/PREFER_EDGES/PREFER_NODES。这是 0.1.8~0.1.9 版本引入的系列特性("Make elk respect the order of nodes based from the code"),让 ELK 布局结果与代码书写顺序更可预测。
5.7keepEntryNodeOnTop(布尔,默认false)
Elk specific option that keeps the entry node of a recursive flow at the top of the layout.
这是 0.2.3 版本新增的选项,解决的问题是:elk.layered必须先断环才能分层,而其默认断环启发式纯按度数计算,没有"入口点"概念。一旦流程图存在回边(递归/循环),第一个声明的节点可能被排到布局中部,阅读顺序被打乱。
从源码看其实现(findCyclicEntryNodes+applyCyclicEntryConstraint):
- 按容器(
parentId)分组,只在容器内部寻找弱连通分量; - 若某分量不存在入度为 0 的节点(忽略自环),说明该分量必含环,则提名声明顺序中第一个节点作为入口;
- 无环分量存在天然源头,不做任何提名,布局不受影响;
- 被提名的节点通过
elk.layered.layering.layerConstraint: 'FIRST'钉在首层。
两个明确的适用边界(与 schema 描述一致):如果循环流程外部有节点指入(如 start 节点指向环),该分量已有天然源头,不触发钉选;检测按容器作用域进行,跨越子图边界的环不会被识别。该选项是纯增量行为——关闭时对既有 ELK 图无任何影响。
6. 其他内置行为(无需配置)
createRootElkGraph中还固化了一批不可通过配置覆盖的选项,了解它们有助于理解 ELK 布局的默认观感:
spacing.baseValue: 40(根图)/30(子图):基础间距;elk.layered.unnecessaryBendpoints: true与elk.layered.mergeHierarchyEdges: true:减少多余拐点、合并层级边;elk.direction默认DOWN,随后按图的direction指令(LR/RL/TB/BT)改写;- 边绘制统一
curve = 'rounded'(圆角直角边)。这正是 0.2.1 版本的关键修复:此前 ELK 边会继承全局basis曲线默认值导致本应直角走线的边出现弯曲,现在 ELK 布局默认使用圆角直角边,而非 ELK 布局保持原有basis默认值。
7. 验证与示例资源
- E2E 快照用例:e2e/diagrams/flowchart/elk/ 与 e2e/diagrams/class-diagram/elk/ 目录收录了大量使用 ELK 布局渲染的流程图与类图基准图,可用作布局行为的回归参照;
- 交互式演示页:demos/flowchart-elk.html 可直接在浏览器中体验
flowchart-elk指令; - 布局机制总览文档:docs/config/layouts.md 介绍 Mermaid 布局系统(dagre 与 ELK)的整体关系;
- 单元测试:packages/mermaid-layout-elk/src/tests/。
8. 小结:适用前提与限制
综合 README 与源码,使用 ELK 布局的前提与边界可以归纳为:
- 必须是 Mermaid 11.x(peer 依赖
mermaid: ^11.0.2),且宿主需自行安装/引入@mermaid-js/layout-elk并调用mermaid.registerLayoutLoaders; - 五个布局名中
elk即elk.layered,分层算法适合流程图/类图这类有向分层结构;stress、force适合无固定阅读方向的网络状图; elk配置组提供 7 个可调项(mergeEdges、nodePlacementStrategy、nodePlacementAlignment、cycleBreakingStrategy、forceNodeModelOrder、considerModelOrder、keepEntryNodeOnTop),默认值已针对"贴近源码声明顺序、紧凑分层"调优;- 递归流程建议开启
keepEntryNodeOnTop保证入口节点置顶;但注意它对"有外部入边的环"和"跨子图的环"不生效; - 边默认圆角直角路由、组节点先绘制的顺序、子图标签内嵌顶部居中等行为由插件内置,属于该包的既定观感,不是全局配置项。
【免费下载链接】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),仅供参考