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
本文基于仓库中 packages/mermaid-layout-elk/CHANGELOG.md 的完整版本记录,系统梳理@mermaid-js/layout-elk包的演进脉络(0.1.1 → 0.2.3),逐一拆解各版本引入的elk.*配置项(mergeEdges、keepEntryNodeOnTop、nodePlacementAlignment等)在源码中的真实接线方式,并深入渲染管线(ELK 图构建、跨子图边处理、边裁剪与圆角折线生成),帮助你在项目或站点中正确接入并使用 Mermaid 的 ELK 布局引擎。
一、包的定位与包元信息
@mermaid-js/layout-elk是为 Mermaid 提供的基于 Eclipse ELK(通过elkjs)的布局引擎插件包。它不是独立的图表库,而是一个布局加载器(Layout Loader):主包mermaid保留原有的 Dagre 布局,用户按需注册 ELK 加载器后即可用layout: elk配置或flowchart-elk语法切换布局。
packages/mermaid-layout-elk/package.json 中的关键元信息:
| 字段 | 值 | 说明 |
|---|---|---|
name | @mermaid-js/layout-elk | npm 包名 |
version | 0.2.3 | 与 CHANGELOG 中最新版本一致 |
module | dist/mermaid-layout-elk.core.mjs | ES Module 入口 |
types | dist/layouts.d.ts | 类型声明入口 |
dependencies | d3 ^7.9.0、elkjs ^0.9.3 | 依赖 d3 曲线工具与 ELK 打包版内核 |
peerDependencies | mermaid ^11.0.2 | 要求主包 11.x(对应 0.1.1 起) |
包 README(packages/mermaid-layout-elk/README.md)特别提示:支持 mermaid 的宿主站点默认不会提供 ELK 布局,需要自行安装该包才能启用layout: elk。
支持的五种布局算法
从 layouts.ts 的源码可直接确认注册表:
const algos = ['elk.stress', 'elk.force', 'elk.mrtree', 'elk.sporeOverlap']; const layouts: LayoutLoaderDefinition[] = [ { name: 'elk', loader, algorithm: 'elk.layered', // `elk` 别名实际跑的是 layered 分层布局 }, ...algos.map((algo) => ({ name: algo, loader, algorithm: algo })), ];即五个可选值:elk(默认,等价elk.layered分层布局)、elk.stress(力导向/应力布局)、elk.force、elk.mrtree(多根树布局)、elk.sporeOverlap(Spore 重叠布局)。注意elk.layered本身也作为算法名传递给 ELK,别名与算法名在 render.ts 中通过preparedLayout.algorithm解析(见下文管线部分)。
二、接入方式与三种启用写法
2.1 启用 ELK 布局的三种写法
按 README 记载,任选其一即可让普通flowchart走 ELK:
写法 1:切换图表类型前缀
flowchart-elk TD A --> B A --> C写法 2:frontmatter 配置layout: elk
--- config: layout: elk --- flowchart TD A --> B A --> C写法 3:frontmatter 指定具体算法(如 stress)
--- config: layout: elk.stress --- flowchart TD A --> B A --> C2.2 与打包器集成
npm install @mermaid-js/layout-elkimport mermaid from 'mermaid'; import elkLayouts from '@mermaid-js/layout-elk'; mermaid.registerLayoutLoaders(elkLayouts);核心 API 是mermaid.registerLayoutLoaders(...):把上面layouts.ts导出的LayoutLoaderDefinition[]注册进主包,之后解析器遇到layout: elk*配置或xxx-elk图表类型时,才会按需(loader函数动态import('./render.js'))加载 ELK 渲染器。
三、版本演进全记录(CHANGELOG 逐版本解读)
CHANGELOG 采用 changesets 生成的 Patch/Minor 分组格式。下面按时间倒序完整继承每一条记录,并结合源码说明其实际落点。
3.1 0.2.3(对应 mermaid 11.17.0)——两项新增配置
elk.keepEntryNodeOnTop(feat):让递归流(自环回流图)的入口节点固定在顶层。elk.nodePlacementAlignment(feat):暴露 BK 分层对齐策略配置。
这两项在 defaultConfig.ts 中都有默认值:
elk: { mergeEdges: false, // 见 3.2 nodePlacementStrategy: 'BRANDES_KOEPF', nodePlacementAlignment: 'NONE', // 0.2.3 新增项,默认 NONE forceNodeModelOrder: false, considerModelOrder: 'NODES_AND_EDGES', keepEntryNodeOnTop: false, // 0.2.3 新增项,默认关闭 },3.2 0.2.2(对应 mermaid 11.16.0)——mergeEdges 子图传播修复
fix(elk): propagate
elk.mergeEdgesconfig to subgraphs in ELK layout — previously edges defined inside a subgraph were not merged even whenelk.mergeEdges: truewas set.
修复前:即使全局设置elk.mergeEdges: true,定义在子图内部的边也不会被合并(多条同起同止的边会各自画线,视觉冗余)。修复后该配置正确下传。源码印证在 render.ts 的buildSubgraphLayoutOptions中——子图的 layoutOptions 显式读取了同一份elkConfig:
const layoutOptions: Record<string, unknown> = { 'spacing.baseValue': 30, 'nodeLabels.placement': '[H_CENTER V_TOP, INSIDE]', 'elk.layered.mergeEdges': elkConfig?.mergeEdges, // ← 传播到子图 'nodePlacement.strategy': elkConfig?.nodePlacementStrategy, 'elk.layered.nodePlacement.bk.fixedAlignment': elkConfig?.nodePlacementAlignment ?? DEFAULT_NODE_PLACEMENT_ALIGNMENT, // ← 0.2.3 新增 };而根图在createRootElkGraph(render.ts)中同样读取:
'elk.layered.mergeEdges': data4Layout.config.elk?.mergeEdges,3.3 0.2.1(对应 mermaid 11.13.0)——ELK 边默认改为圆角折线
fix: use rounded right-angle edges for ELK layout —— ELK 布局的边从继承全局
basis默认曲线,改为默认rounded(直角段 + 圆角转折),修复了 ELK 边"弯曲而非直角路由"的问题(issue #7213)。非 ELK 布局不受影响,仍保留原basis默认。
源码印证:render.ts 在把 ELK 返回的边段写回LayoutData时硬编码:
layoutEdge.curve = 'rounded';3.4 0.2.0(对应 mermaid 11.11.0,Minor)
feat: Update mindmap rendering to support multiple layouts, improved edge intersections, and new shapes
思维导图渲染升级为支持多布局(ELK 是其中之一)、改进边交叉、新增形状。此版本把layout-elk从 patch 序列提升到 minor,因为它开始承载 mindmap 的新布局能力。
3.5 0.1.9(对应 mermaid 11.10.0)——模型顺序控制配置暴露
两条记录共同构成一组能力:
elk.forceNodeModelOrder/elk.considerModelOrder暴露到 mermaid 配置(feat);- 默认行为改变:ELK 不再"强制"按代码书写顺序排节点,而是"强烈参考"该顺序(make elk not force node model order, but strongly consider it instead)。
对应默认值forceNodeModelOrder: false+considerModelOrder: 'NODES_AND_EDGES'(defaultConfig.ts)。接线位置在根图选项(render.ts):
'elk.layered.crossingMinimization.forceNodeModelOrder': data4Layout.config.elk?.forceNodeModelOrder, 'elk.layered.considerModelOrder.strategy': data4Layout.config.elk?.considerModelOrder,3.6 0.1.8(对应 mermaid 11.7.0)
Make elk respect the order of nodes based from the code
开始让 ELK 尊重源码中节点的声明顺序——这是 0.1.9 中"模型顺序"系列配置的前身:先有硬尊重,随后演进为可配置的 force/consider 两级策略。
3.7 0.1.7 / 0.1.6(对应 mermaid 11.4.x)——菱形交点修复
- 0.1.7:更新处理交点时菱形(diamond)形状偏移量计算(fix: Updated offset calculations for diamond shape when handling intersections);
- 0.1.6:修复菱形形状在 ELK 渲染中的交点计算(fix: Elk rendering of Diamond shape intersections)。
这两次修复针对的是"边裁剪到节点边界"阶段中菱形(决策节点)四边非轴平行导致的裁剪点错误。当前代码中该阶段对应 render.ts 的sanitizeElkEdgePoints/cutter2与 geometry.ts 中的形状交点计算函数(computeNodeIntersection、replaceEndpoint等)。
3.8 0.1.5(对应 mermaid 11.3.0)
chore: Update render options
渲染选项更新,为后续把 ELK 配置统一进data4Layout.config.elk命名空间做铺垫。
3.9 0.1.4(无对应 mermaid 依赖变更)
chore: fix render types
修复渲染相关的类型定义。
3.10 0.1.3(对应 mermaid 11.1.0)——默认配置更新 + 破环策略暴露
fix: Updates to the default elk configuration feat: exposing cycleBreakingStrategy to the configuration so that it can be modified using the configuration.
- 更新了 ELK 的默认 layoutOptions(即今天
createRootElkGraph中那组'elk.layered.*'默认项的雏形:unnecessaryBendpoints: true、mergeHierarchyEdges: true、multiEdge.improveCuts等); - 新增
elk.cycleBreakingStrategy配置,允许用户指定 layered 布局的破环策略(如GREEDY_MODEL_ORDER/MODEL_ORDER),接线点在 render.ts:
'elk.layered.cycleBreaking.strategy': data4Layout.config.elk?.cycleBreakingStrategy, // 源码注释中还列出了候选值: // 'elk.layered.cycleBreaking.strategy': 'GREEDY_MODEL_ORDER', // 'elk.layered.cycleBreaking.strategy': 'MODEL_ORDER',3.11 0.1.2 / 0.1.1(对应 mermaid 11.0.2)
- 0.1.2:Fix type file path(修复类型文件路径);
- 0.1.1:fix: Types path(修复
types指向)。
这两次都是打包/类型声明路径修正,标志着该包自 mermaid 11.0 起以独立 workspace 包形式对外发布。
四、elk.* 配置参数全表(结合源码解析)
综合 CHANGELOG 与 defaultConfig.ts、render.ts,elk配置命名空间下可写参数及其实时接线位置如下:
| 配置项 | 默认值 | 引入版本 | ELK 选项接线位置 | 作用 |
|---|---|---|---|---|
mergeEdges | false | 11.0 前已存在,0.2.2 修复子图传播 | 根图elk.layered.mergeEdges+ 子图同选项 | 合并同一起止的多条边为一条,减少视觉冗余 |
nodePlacementStrategy | 'BRANDES_KOEPF' | 0.1.5 前后 | 根图与子图nodePlacement.strategy | 节点坐标放置策略 |
nodePlacementAlignment | 'NONE' | 0.2.3 | 根图与子图elk.layered.nodePlacement.bk.fixedAlignment | BK 策略下的固定对齐(如FIRST/LAST等),控制节点在层内贴边排列 |
forceNodeModelOrder | false | 0.1.9 | elk.layered.crossingMinimization.forceNodeModelOrder | 强制按声明顺序减少交叉(牺牲交叉数最优性) |
considerModelOrder | 'NODES_AND_EDGES' | 0.1.9 | elk.layered.considerModelOrder.strategy | 破交叉时"强烈参考"声明顺序的程度(NODES_AND_EDGES/NODES/EDGES/NONE) |
cycleBreakingStrategy | 由 JSON Schema 提供 | 0.1.3 | elk.layered.cycleBreaking.strategy | 破环(回边)策略,影响递归图的阅读方向 |
keepEntryNodeOnTop | false | 0.2.3 | 命中节点上写elk.layered.layering.layerConstraint: 'FIRST' | 把递归流入口节点钉在首层 |
配置通过 frontmatterconfig: elk: {...}或mermaid.initialize({ elk: {...} })写入,渲染时整体挂在data4Layout.config.elk上被 render.ts 读取;schema 定义见 config.schema.yaml(elk节点自 L118 起),类型见 config.type.ts。
4.1keepEntryNodeOnTop的算法细节
这是 0.2.3 的核心特性,其实现值得展开。elk.layered必须先破环才能给节点分层,而其默认破环启发式是纯度数的,没有"入口"概念——于是递归流程里第一个声明的节点可能被排到布局中部,阅读顺序被打乱。
render.ts 的findCyclicEntryNodes算法:
- 按
parentId把节点分组(即按容器/子图隔离),组内保持声明顺序; - 只用容器内部边、忽略自环,统计每个节点的入度,并用无向邻接表求出弱连通分量(迭代 DFS);
- 若某分量中不存在入度为 0 的节点,则该分量必然含环(无环有向图的每个弱连通分量必有源节点),于是提名该分量中声明顺序最靠前的节点作为入口;
- 有源的分量(即无环部分)不做任何提名,布局保持原样——这保证了无环图零影响。
随后applyCyclicEntryConstraint(render.ts)只在keepEntryNodeOnTop为真时对命中节点写约束:
if (!data4Layout.config.elk?.keepEntryNodeOnTop) { return; // 默认关闭,现有 ELK 图不受影响 } // ... elkNode.layoutOptions = { ...elkNode.layoutOptions, 'elk.layered.layering.layerConstraint': 'FIRST', };五、渲染管线:从 LayoutData 到 SVG 坐标
render.ts 用主包提供的工厂函数装配出完整渲染器:
export const render = createCommonLayoutRenderer<ElkLayoutResult, ElkPreparedLayout>({ prepareLayout: prepareLayoutForElk, runLayoutCore: runElkLayoutCore, paintOptions: { skipIntersect: true }, // 交点已由 ELK + 自研裁剪处理 });完整调用链(buildElkGraphFromLayoutData,render.ts):
createRootElkGraph:构建根 ELK 图,写入elk.algorithm(由加载器注入,如elk.layered/elk.stress)、elk.direction(经dir2ElkDirection把TB/TD→DOWN、LR→RIGHT、RL→LEFT、BT→UP)及第四节所列全部根级 layoutOptions;addSubGraphs:按parentId建立父子查找表parentLookupDb(供跨层级边与坐标偏移使用);addVertices/addVertex:递归把节点加入 ELK 图;子图节点挂labelData(标签实测宽高),普通节点挂width/height;addEdgesToElkGraph:把 mermaid 边转换为 ELK 边,边标签带edgeLabels.placement: CENTER;configureSubgraphNodes:为每个子图写buildSubgraphLayoutOptions(子图可有自己的dir与elk.algorithm,并设elk.hierarchyHandling: 'SEPARATE_CHILDREN'独立布局子图内部),并删除子图自身宽高让其由内容撑开;configureCrossHierarchyEdges:对父子不同的两端,用 find-common-ancestor.ts 找最近公共祖先,并沿祖先链设置elk.hierarchyHandling: 'INCLUDE_CHILDREN',使跨子图边可以正确穿越层级容器;applyCyclicEntryConstraint:按 4.1 所述钉住递归入口;runElkLayout:调用new ELK().layout(graph)(elkjs 打包版),并支持 dev 构建下通过globalThis.__mermaidProfiler对layoutCore单独计时;出错时打印完整 ELK 图便于排查;applyElkLayoutResult:applyElkNodePositions递归回填坐标——子图坐标是相对父容器的,需累加relX/relY偏移得到全局posX/posY,节点中心取x + width/2;applyElkEdgeLayout取 ELK 返回的sections[0](起点/折点/终点),叠加calcOffset(公共祖先偏移)换算到全局坐标,再经sanitizeElkEdgePoints(cutter2裁剪到节点边界、组端点贴边检测、去重、无效点兜底)与ensureEndMarkerSegmentLength(保证箭头头段长度 ≥ 8px)输出最终points,并写curve = 'rounded'(0.2.1 行为);
orderNodesForElkPaint:绘制顺序上,组(子图)先于普通节点,组之间按嵌套深度升序,保证子图背景先画、节点后画。
六、验证方式:e2e 用例目录
仓库为 ELK 布局维护了完整的 e2e 快照用例,可用于对照验证各版本行为:
- 流程图 ELK 用例:e2e/diagrams/flowchart/elk/(56 个
.mmd,覆盖嵌套子图、样式表达式、边裁剪2824-elk-clipping-of-edges.mmd、方向继承2050-elk-handling-of-different-rendering-direction-in-subgraphs.mmd等场景); - 类图 ELK 用例:e2e/diagrams/class-diagram/elk/(60 个
.mmd); - 快照测试入口:e2e/helpers/mmd-snapshots.ts、e2e/helpers/mmd-snapshots.spec.ts;
- 包自身的单元测试:packages/mermaid-layout-elk/src/tests/render.spec.ts(针对布局核心函数)、geometry.spec.ts(边界交点几何)。
七、实践要点小结
- 接入前提:ELK 不随 mermaid 主包默认提供,宿主环境必须
npm install @mermaid-js/layout-elk并registerLayoutLoaders,否则layout: elk不生效;peer 要求mermaid ^11.0.2。 - 默认行为(0.2.3 起):
elk别名 =elk.layered分层布局;边默认rounded圆角折线;节点顺序"强烈参考"声明序而非强制;mergeEdges、keepEntryNodeOnTop默认关闭,属纯 opt-in增强,不改变存量图。 - 调参路径:全局
elk.*配置 → 根图 layoutOptions;子图内部则经buildSubgraphLayoutOptions传播(0.2.2 修复后mergeEdges对子图内边同样生效)。递归流程阅读顺序问题用keepEntryNodeOnTop: true解决;层内排列不齐可用nodePlacementAlignment/nodePlacementStrategy调整;回边方向不佳可用cycleBreakingStrategy换破环策略。 - 可追溯性:每条行为都能对上 CHANGELOG 条目与源码位置(CHANGELOG、render.ts、defaultConfig.ts),配合 e2e
.mmd快照即可复现验证。
【免费下载链接】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),仅供参考