w-flowchart 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +9 -0
- package/.eslintignore +3 -0
- package/.eslintrc.js +55 -0
- package/.jsdoc +25 -0
- package/LICENSE +21 -0
- package/README.md +49 -0
- package/SECURITY.md +5 -0
- package/babel.config.js +16 -0
- package/dist/w-flowchart.umd.js +7 -0
- package/dist/w-flowchart.umd.js.map +1 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
- package/docs/index.html +84 -0
- package/docs/scripts/collapse.js +39 -0
- package/docs/scripts/commonNav.js +28 -0
- package/docs/scripts/linenumber.js +25 -0
- package/docs/scripts/nav.js +12 -0
- package/docs/scripts/polyfill.js +4 -0
- package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/docs/scripts/prettify/lang-css.js +2 -0
- package/docs/scripts/prettify/prettify.js +28 -0
- package/docs/scripts/search.js +99 -0
- package/docs/styles/jsdoc.css +776 -0
- package/docs/styles/prettify.css +80 -0
- package/package.json +49 -0
- package/script.txt +17 -0
- package/src/WFlowchart.mjs +0 -0
- package/src/common/derive.mjs +7 -0
- package/src/common/palette.mjs +34 -0
- package/src/common/pkg.mjs +31 -0
- package/src/p1/README.md +65 -0
- package/src/p1/gen.mjs +203 -0
- package/src/p10/README.md +31 -0
- package/src/p10/gen.mjs +228 -0
- package/src/p2/README.md +87 -0
- package/src/p2/gen.mjs +210 -0
- package/src/p3/README.md +99 -0
- package/src/p3/gen.mjs +188 -0
- package/src/p4/README.md +84 -0
- package/src/p4/gen.mjs +82 -0
- package/src/p5/README.md +90 -0
- package/src/p5/gen.mjs +182 -0
- package/src/p6/README.md +87 -0
- package/src/p6/gen.mjs +186 -0
- package/src/p7/README.md +84 -0
- package/src/p7/gen.mjs +386 -0
- package/src/p8/README.md +92 -0
- package/src/p8/gen.mjs +416 -0
- package/src/p9/README.md +90 -0
- package/src/p9/gen.mjs +63 -0
- package/src/p9/page9.html +420 -0
- package/test/cases.mjs +349 -0
- package/test/data//344/275/234/346/245/255/346/265/201/347/250/213/345/234/226.json +92 -0
- package/test/data//345/211/215/347/253/257/346/236/266/346/247/213/350/210/207/350/263/207/346/226/231/346/265/201/345/234/226.json +112 -0
- package/test/data//345/276/214/347/253/257/346/234/215/345/213/231/347/265/204/346/210/220/345/234/226.json +116 -0
- package/test/data//346/211/271/346/254/241/344/270/212/345/202/263/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.json +251 -0
- package/test/data//347/250/213/345/274/217/345/205/203/344/273/266/347/265/204/346/210/220/345/234/226.json +126 -0
- package/test/data//347/250/213/345/274/217/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.json +133 -0
- package/test/data//347/263/273/347/265/261/346/236/266/346/247/213/345/234/226.json +102 -0
- package/test/data//347/263/273/347/265/261/351/227/234/350/201/257/345/234/226.json +117 -0
- package/test/data//347/267/232/344/270/212/347/225/253/351/235/242/351/227/234/350/201/257/345/234/226.json +162 -0
- package/test/figures.mjs +10 -0
- package/test/lib.mjs +116 -0
- package/test/p10.test.mjs +89 -0
- package/test/pics/cycle.png +0 -0
- package/test/pics/cycle.svg +1 -0
- package/test/pics/deep-nest.png +0 -0
- package/test/pics/deep-nest.svg +1 -0
- package/test/pics/diamond.png +0 -0
- package/test/pics/diamond.svg +1 -0
- package/test/pics/edges-mix.png +0 -0
- package/test/pics/edges-mix.svg +1 -0
- package/test/pics/english.png +0 -0
- package/test/pics/english.svg +1 -0
- package/test/pics/flat-tb.png +0 -0
- package/test/pics/flat-tb.svg +1 -0
- package/test/pics/fold-tall.png +0 -0
- package/test/pics/fold-tall.svg +1 -0
- package/test/pics/fold-wide.png +0 -0
- package/test/pics/fold-wide.svg +1 -0
- package/test/pics/group-edge.png +0 -0
- package/test/pics/group-edge.svg +1 -0
- package/test/pics/items-node.png +0 -0
- package/test/pics/items-node.svg +1 -0
- package/test/pics/long-label.png +0 -0
- package/test/pics/long-label.svg +1 -0
- package/test/pics/lr-flat.png +0 -0
- package/test/pics/lr-flat.svg +1 -0
- package/test/pics/nested-group.png +0 -0
- package/test/pics/nested-group.svg +1 -0
- package/test/pics/no-edges.png +0 -0
- package/test/pics/no-edges.svg +1 -0
- package/test/pics/title-overflow.png +0 -0
- package/test/pics/title-overflow.svg +1 -0
- package/test/pics/valign.png +0 -0
- package/test/pics/valign.svg +1 -0
- package/test/pics//344/275/234/346/245/255/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics//344/275/234/346/245/255/346/265/201/347/250/213/345/234/226.svg +1 -0
- package/test/pics//345/211/215/347/253/257/346/236/266/346/247/213/350/210/207/350/263/207/346/226/231/346/265/201/345/234/226.png +0 -0
- package/test/pics//345/211/215/347/253/257/346/236/266/346/247/213/350/210/207/350/263/207/346/226/231/346/265/201/345/234/226.svg +1 -0
- package/test/pics//345/276/214/347/253/257/346/234/215/345/213/231/347/265/204/346/210/220/345/234/226.png +0 -0
- package/test/pics//345/276/214/347/253/257/346/234/215/345/213/231/347/265/204/346/210/220/345/234/226.svg +1 -0
- package/test/pics//346/211/271/346/254/241/344/270/212/345/202/263/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
- package/test/pics//346/211/271/346/254/241/344/270/212/345/202/263/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.svg +1 -0
- package/test/pics//347/250/213/345/274/217/345/205/203/344/273/266/347/265/204/346/210/220/345/234/226.png +0 -0
- package/test/pics//347/250/213/345/274/217/345/205/203/344/273/266/347/265/204/346/210/220/345/234/226.svg +1 -0
- package/test/pics//347/250/213/345/274/217/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics//347/250/213/345/274/217/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.svg +1 -0
- package/test/pics//347/263/273/347/265/261/346/236/266/346/247/213/345/234/226.png +0 -0
- package/test/pics//347/263/273/347/265/261/346/236/266/346/247/213/345/234/226.svg +1 -0
- package/test/pics//347/263/273/347/265/261/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics//347/263/273/347/265/261/351/227/234/350/201/257/345/234/226.svg +1 -0
- package/test/pics//347/267/232/344/270/212/347/225/253/351/235/242/351/227/234/350/201/257/345/234/226.png +0 -0
- package/test/pics//347/267/232/344/270/212/347/225/253/351/235/242/351/227/234/350/201/257/345/234/226.svg +1 -0
- package/test/wflowchart.test.mjs +41 -0
- package/toolg/addVersion.mjs +4 -0
- package/toolg/cleanFolder.mjs +4 -0
- package/toolg/gDistRollup.mjs +51 -0
- package/toolg/modifyReadme.mjs +4 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
.pln {
|
|
2
|
+
color: #ddd;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/* string content */
|
|
6
|
+
.str {
|
|
7
|
+
color: #61ce3c;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* a keyword */
|
|
11
|
+
.kwd {
|
|
12
|
+
color: #fbde2d;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* a comment */
|
|
16
|
+
.com {
|
|
17
|
+
color: #aeaeae;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* a type name */
|
|
21
|
+
.typ {
|
|
22
|
+
color: #8da6ce;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* a literal value */
|
|
26
|
+
.lit {
|
|
27
|
+
color: #fbde2d;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* punctuation */
|
|
31
|
+
.pun {
|
|
32
|
+
color: #ddd;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* lisp open bracket */
|
|
36
|
+
.opn {
|
|
37
|
+
color: #000000;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* lisp close bracket */
|
|
41
|
+
.clo {
|
|
42
|
+
color: #000000;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* a markup tag name */
|
|
46
|
+
.tag {
|
|
47
|
+
color: #8da6ce;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* a markup attribute name */
|
|
51
|
+
.atn {
|
|
52
|
+
color: #fbde2d;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* a markup attribute value */
|
|
56
|
+
.atv {
|
|
57
|
+
color: #ddd;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* a declaration */
|
|
61
|
+
.dec {
|
|
62
|
+
color: #EF5050;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* a variable name */
|
|
66
|
+
.var {
|
|
67
|
+
color: #c82829;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* a function name */
|
|
71
|
+
.fun {
|
|
72
|
+
color: #4271ae;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Specify class=linenums on a pre to get line numbering */
|
|
76
|
+
ol.linenums {
|
|
77
|
+
margin-top: 0;
|
|
78
|
+
margin-bottom: 0;
|
|
79
|
+
padding-bottom: 2px;
|
|
80
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "w-flowchart",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "dist/w-flowchart.umd.js",
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"@antv/g6": "^5.1.1",
|
|
7
|
+
"@dagrejs/dagre": "^1.1.8",
|
|
8
|
+
"@joint/core": "^4.3.0",
|
|
9
|
+
"@logicflow/core": "^2.2.4",
|
|
10
|
+
"@mermaid-js/layout-elk": "^0.2.2",
|
|
11
|
+
"@terrastruct/d2": "0.1.33",
|
|
12
|
+
"@xyflow/react": "^12.11.2",
|
|
13
|
+
"cytoscape": "^3.34.0",
|
|
14
|
+
"cytoscape-dagre": "^4.0.0",
|
|
15
|
+
"dagre": "^0.8.5",
|
|
16
|
+
"elkjs": "^0.11.1",
|
|
17
|
+
"graphre": "^0.1.3",
|
|
18
|
+
"lodash-es": "^4.18.1",
|
|
19
|
+
"mermaid10": "npm:mermaid@^10.9.6",
|
|
20
|
+
"mermaid11": "npm:mermaid@^11.16.0",
|
|
21
|
+
"nomnoml": "^1.7.0",
|
|
22
|
+
"playwright": "^1.61.1",
|
|
23
|
+
"react": "^18.3.1",
|
|
24
|
+
"react-dom": "^18.3.1",
|
|
25
|
+
"sharp": "0.35.3",
|
|
26
|
+
"wsemi": "^1.8.64"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"pixelmatch": "^7.2.0",
|
|
30
|
+
"pngjs": "^7.0.0",
|
|
31
|
+
"w-package-tools": "^1.1.10"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"test": "mocha --parallel --timeout 60000",
|
|
35
|
+
"deploy": "gh-pages -d docs"
|
|
36
|
+
},
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/yuda-lyu/w-flowchart.git"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"package",
|
|
43
|
+
"tool",
|
|
44
|
+
"flowchart",
|
|
45
|
+
"nodejs"
|
|
46
|
+
],
|
|
47
|
+
"author": "yuda-lyu(semisphere)",
|
|
48
|
+
"license": "MIT"
|
|
49
|
+
}
|
package/script.txt
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#node toolg/addVersion.mjs
|
|
2
|
+
node toolg/modifyReadme.mjs
|
|
3
|
+
|
|
4
|
+
node toolg/cleanFolder.mjs
|
|
5
|
+
./node_modules/.bin/jsdoc -c .jsdoc
|
|
6
|
+
|
|
7
|
+
node toolg/gDistRollup.mjs
|
|
8
|
+
|
|
9
|
+
git add . -A
|
|
10
|
+
git commit -m 'modify: '
|
|
11
|
+
git push origin master:master
|
|
12
|
+
|
|
13
|
+
npm run deploy
|
|
14
|
+
|
|
15
|
+
#npm test
|
|
16
|
+
|
|
17
|
+
#npm publish
|
|
File without changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// rich 節點(title+items)→ 自動衍生 label「標題(item1/item2)」給 p1~p9 當回退(p10 用 title+items 分層渲染)。純 label 節點不受影響。
|
|
2
|
+
// 括號/分隔符用半形, 英文數據亦通用。
|
|
3
|
+
export function deriveLabels(d) {
|
|
4
|
+
d.nodes.forEach(n => {
|
|
5
|
+
if (n.title && n.label == null) n.label = n.title + (n.items && n.items.length ? ' (' + n.items.join('/') + ')' : '')
|
|
6
|
+
}); return d
|
|
7
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// 標準語意色票:類別(cls)→ 顏色/形狀。所有產線(pN)共用同一套色, 確保 10 套出圖視覺一致。
|
|
2
|
+
// 一般節點為 box; diamond 為決策菱形; cls 結尾為 G / G2 者為「群組容器」(較淺填色, 標題置頂)。
|
|
3
|
+
export const PALETTE = {
|
|
4
|
+
// 一般節點
|
|
5
|
+
blue: { fill: '#eef4fb', stroke: '#3f6fb0', text: '#1c2b36', shape: 'box' },
|
|
6
|
+
green: { fill: '#eaf4ef', stroke: '#348a5c', text: '#1c2b36', shape: 'box' },
|
|
7
|
+
orange: { fill: '#fcf0e2', stroke: '#c46e1a', text: '#1c2b36', shape: 'box' },
|
|
8
|
+
purple: { fill: '#f4f0fa', stroke: '#8163ad', text: '#23303a', shape: 'box' },
|
|
9
|
+
red: { fill: '#fbecea', stroke: '#c0392b', text: '#23303a', shape: 'box' },
|
|
10
|
+
done: { fill: '#eaf4ef', stroke: '#256046', text: '#256046', shape: 'box' },
|
|
11
|
+
// 決策(菱形)
|
|
12
|
+
diamond: { fill: '#fcf0e2', stroke: '#c46e1a', text: '#23303a', shape: 'diamond' },
|
|
13
|
+
// 群組容器(較淺填色, group:true)
|
|
14
|
+
blueG: { fill: '#f5f9fe', stroke: '#3f6fb0', text: '#3f6fb0', group: true },
|
|
15
|
+
blueG2: { fill: '#eef4fb', stroke: '#3f6fb0', text: '#3f6fb0', group: true },
|
|
16
|
+
greenG: { fill: '#f0f7f3', stroke: '#348a5c', text: '#348a5c', group: true },
|
|
17
|
+
greenG2: { fill: '#eaf4ef', stroke: '#348a5c', text: '#348a5c', group: true },
|
|
18
|
+
orangeG: { fill: '#fdf6ec', stroke: '#c46e1a', text: '#c46e1a', group: true },
|
|
19
|
+
purpleG: { fill: '#faf7fd', stroke: '#8163ad', text: '#8163ad', group: true },
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 邊樣式
|
|
23
|
+
// textBg 一律「透明」(不可用不透明白底遮線, 否則轉折線會看似中斷);
|
|
24
|
+
// 文字清晰度改以「白色光暈/描邊」達成(halo): 各產線以該庫機制實作
|
|
25
|
+
// (SVG paint-order:stroke + stroke:white / canvas strokeText / CSS text-shadow), 全域一致。
|
|
26
|
+
export const EDGE = { line: '#44505a', text: '#4a5560', textBg: 'transparent', haloColor: '#ffffff', haloWidth: 3.5 }
|
|
27
|
+
|
|
28
|
+
// 字型(中文)
|
|
29
|
+
export const FONT = '\'Microsoft JhengHei\', sans-serif'
|
|
30
|
+
|
|
31
|
+
// 工具:判斷 cls 是否為群組容器
|
|
32
|
+
export const isGroupCls = cls => /G2?$/.test(cls || '')
|
|
33
|
+
// 工具:取某 cls 之色票(查無則回 blue)
|
|
34
|
+
export const colorOf = cls => PALETTE[cls] || PALETTE.blue
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// 本機套件檔案定位與讀取: 供各產線把繪圖庫由 CDN 改為 node_modules 內聯注入(斷網環境可用)。
|
|
2
|
+
// 以 fs 實體路徑向上逐層找 node_modules/<rel>, 不走 require.resolve(不受套件 exports 白名單限制),
|
|
3
|
+
// 亦相容本套件被安裝後 node_modules 被 hoist 至上層的情況。
|
|
4
|
+
import fs from 'fs'
|
|
5
|
+
import { fileURLToPath } from 'url'
|
|
6
|
+
import { dirname, join } from 'path'
|
|
7
|
+
|
|
8
|
+
const __dir = dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
|
|
10
|
+
// rel 例: 'mermaid10/dist/mermaid.min.js' → 回傳實體絕對路徑
|
|
11
|
+
export function pkgFile(rel) {
|
|
12
|
+
let dir = __dir
|
|
13
|
+
for (let i = 0; i < 10; i++) {
|
|
14
|
+
const fp = join(dir, 'node_modules', rel)
|
|
15
|
+
if (fs.existsSync(fp)) return fp
|
|
16
|
+
const up = dirname(dir)
|
|
17
|
+
if (up === dir) break
|
|
18
|
+
dir = up
|
|
19
|
+
}
|
|
20
|
+
throw new Error('找不到本機套件檔案 node_modules/' + rel)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 讀取套件 JS 內容供 <script> 內聯(轉義 </script> 避免提早關閉標籤)
|
|
24
|
+
export function pkgScript(rel) {
|
|
25
|
+
return fs.readFileSync(pkgFile(rel), 'utf8').replace(/<\/script/gi, '<\\/script')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 讀取套件文字內容(CSS 等)
|
|
29
|
+
export function pkgText(rel) {
|
|
30
|
+
return fs.readFileSync(pkgFile(rel), 'utf8')
|
|
31
|
+
}
|
package/src/p1/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# p1 — mermaid@10 dagre 產線
|
|
2
|
+
|
|
3
|
+
## 技術核心
|
|
4
|
+
|
|
5
|
+
- **繪圖庫**: mermaid 第 10 版系列(package.json 以 npm alias `mermaid10` 安裝,與 p2 之第 11 版系列並存),透過 `common/pkg.mjs` 的 `pkgScript('mermaid10/dist/mermaid.min.js')` 讀取本機 `node_modules` 內容並內聯注入 `<script>` 標籤,於 Playwright 開啟的 Chromium 頁面內執行;不經 CDN,斷網環境亦可運作。
|
|
6
|
+
- **渲染方式**: `genFig()` 為每張圖建立獨立的 Playwright `page`(`browser.newPage()`),避免 mermaid 內部 render id 衝突。將 `mermaidHtml()` 產生的 HTML 字串以 `page.setContent()` 注入,等待 `document.title === 'DONE'` 確認渲染完成(或 `FAIL` 時拋出錯誤),再以 `page.waitForFunction` 超時 30 秒防呆。
|
|
7
|
+
- **截圖目標**: `page.locator('#box svg').screenshot()`,只截 SVG 元素本身,不含外層 `div#box` 的 padding 背景。
|
|
8
|
+
- **解析度**: `deviceScaleFactor: 4`,Chromium 以 4 倍畫素密度渲染 SVG 向量圖,輸出 PNG 清晰度極高,適合印刷品質報告。
|
|
9
|
+
- **字型**: HTML `<body>` 及 mermaid `themeVariables.fontFamily` 均指定 `'微軟正黑體','Microsoft JhengHei',sans-serif`;截圖前以 `page.evaluate(() => document.fonts.ready)` 確保字型載入完成。
|
|
10
|
+
|
|
11
|
+
## 產製原理(資料驅動)
|
|
12
|
+
|
|
13
|
+
`translate(data)` 將標準數據的 `{ dir, nodes, edges }` 轉為 mermaid flowchart DSL 字串,流程如下:
|
|
14
|
+
|
|
15
|
+
### 節點形狀與色票
|
|
16
|
+
|
|
17
|
+
- `cls === 'diamond'` → `id{"label"}`(菱形決策節點)
|
|
18
|
+
- 其他所有 cls → `id["label"]`(矩形方框)
|
|
19
|
+
- 色票由 `PALETTE[cls] || PALETTE.blue` 取得,每個出現過的 cls 各產生一行 `classDef`:
|
|
20
|
+
- 群組容器(`isGroupCls` 判斷,即 cls 結尾為 `G` 或 `G2`):`stroke-width:1.8px`
|
|
21
|
+
- diamond:`stroke-width:1.6px`
|
|
22
|
+
- 其他一般節點:`stroke-width:1.5px`
|
|
23
|
+
- 最終以 `class id1,id2,... cls` 批次指派,每個 cls 一行。
|
|
24
|
+
|
|
25
|
+
### 群組容器(subgraph)與巢狀
|
|
26
|
+
|
|
27
|
+
- `isGroupCls(nd.cls)` 為 `true` 的節點視為群組容器,輸出為 `subgraph id["label"]`。
|
|
28
|
+
- 拓樸分析:
|
|
29
|
+
- **頂層容器**:自身 `group` 欄位為空(無父容器)的容器。
|
|
30
|
+
- **巢狀容器**:自身有 `group` 欄位(屬於某父容器)的容器,遞迴嵌入父容器 subgraph 內。
|
|
31
|
+
- **獨立節點**:`!isGroupCls` 且無 `group` 欄位,直接輸出在 flowchart 頂層。
|
|
32
|
+
- `renderSubgraph(gid, indent)` 遞迴處理,支援二層巢狀(例如 DET 群組在 CORE 群組內)。
|
|
33
|
+
|
|
34
|
+
### 邊與虛線
|
|
35
|
+
|
|
36
|
+
- `ed.kind === 'dashed'` → `-..->`(虛線箭頭)
|
|
37
|
+
- 其他 → `-->`(實線箭頭)
|
|
38
|
+
- 有 `ed.label` 時插入 `|"label"|` 語法。
|
|
39
|
+
- 跨容器邊(from/to 分屬不同 subgraph)直接以一般 edge 語法處理,mermaid dagre 自行解決跨容器連線路由。
|
|
40
|
+
|
|
41
|
+
### 流向(dir)
|
|
42
|
+
|
|
43
|
+
- `data.dir` 直接對應 mermaid `flowchart <dir>`,例如 `TB`(上到下)或 `LR`(左到右),無任何轉換。
|
|
44
|
+
|
|
45
|
+
### 對外介面
|
|
46
|
+
|
|
47
|
+
- 產線唯一輸出入口為 `genPng(data, opt)`,`data` 為正規化繪圖數據 `{ dir, nodes, edges }`,回傳 `Promise<Buffer>`(PNG);`genPng` 自帶瀏覽器生命週期(`chromium.launch()` → 渲染 → `browser.close()`),呼叫端不需自行管理 Playwright。
|
|
48
|
+
- 統一由 `src/WFlowchart.mjs` 依 `mode === 'p1'` 呼叫,不提供批次流程或落地寫檔行為,是否寫檔、寫至何處由呼叫端決定。
|
|
49
|
+
|
|
50
|
+
## 自動化機制
|
|
51
|
+
|
|
52
|
+
p1 的版面自動化完全委由 **mermaid dagre** 引擎處理,產線本身不計算任何節點座標、間距或畫布尺寸:
|
|
53
|
+
|
|
54
|
+
- **零逐圖魔術數字**:`translate()` 不依圖的節點數或標籤長度調整任何數值;所有佈局由 dagre 演算法依拓樸自動推算。
|
|
55
|
+
- **邊線樣式強制覆蓋**:mermaid `themeCSS` 設定 `.flowchart-link`、`.edgePaths .path` 的 stroke 為 `#44505a`、stroke-width `2.5px`、箭頭 fill/stroke `#44505a`,確保 theme:base 預設邊線色不干擾統一視覺。渲染完成後再以 `page.evaluate()` 對 DOM 補強一次 `!important` inline style(雙層覆蓋,防 mermaid 版本差異導致 themeCSS 失效)。
|
|
56
|
+
- **cluster(subgraph)框線**:DOM 後處理統一設 `.cluster rect` 等元素 stroke `#777777`、stroke-width `1.5px`。
|
|
57
|
+
- **獨立 page 防衝突**:每張圖開新 `page`,`mermaid.render('g', ...)` 的固定 id `g` 不會因先前渲染殘留而出錯。
|
|
58
|
+
|
|
59
|
+
## 已知限制 / 回退
|
|
60
|
+
|
|
61
|
+
- **線性鏈被拉長**:dagre 對長鏈節點(如單一主幹多節點)會垂直拉伸畫布,輸出圖比例偏高。
|
|
62
|
+
- **跨容器邊路由不可控**:mermaid 對跨 subgraph 的邊路由由 dagre 自動決定,邊可能繞行或重疊,無法手動指定路徑。
|
|
63
|
+
- **同標籤平行邊重疊**:若兩條邊 from/to 相同但 label 不同,mermaid 可能渲染為重疊線段,視覺難以區分。
|
|
64
|
+
- **subgraph 標題色不受 classDef 控制**:subgraph 標題文字顏色由 mermaid 主題決定,`classDef` 的 `color` 屬性僅影響節點內文字,容器標題需透過 `themeVariables` 或 CSS 覆蓋,現行產線未特別處理。
|
|
65
|
+
- **無自動裁切/縮放**:截圖對象為 `#box svg`,SVG 本身大小由 dagre 決定,若圖形極大則 PNG 尺寸亦大,無自動縮放至固定畫布。
|
package/src/p1/gen.mjs
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// p1 — mermaid@10 dagre 產線 adapter(資料驅動)
|
|
2
|
+
// genPng(data): 正規化繪圖數據 → translate → mermaid flowchart DSL → Playwright 渲染 → 回傳 PNG Buffer
|
|
3
|
+
// 自動排版: mermaid dagre 自行排版, 無任何逐圖魔術數字。
|
|
4
|
+
import { chromium } from 'playwright'
|
|
5
|
+
import { PALETTE, isGroupCls } from '../common/palette.mjs'
|
|
6
|
+
import { pkgScript } from '../common/pkg.mjs'
|
|
7
|
+
|
|
8
|
+
// mermaid@10 由本機 node_modules 內聯注入(取代 CDN, 斷網環境可用)
|
|
9
|
+
const MERMAID_JS = pkgScript('mermaid10/dist/mermaid.min.js')
|
|
10
|
+
|
|
11
|
+
// 把標準節點 label 轉成 mermaid 節點形狀語法
|
|
12
|
+
// diamond → {label} / 其餘 → ["label"]
|
|
13
|
+
function nodeShape(nd) {
|
|
14
|
+
if (nd.cls === 'diamond') return `${nd.id}{"${nd.label}"}`
|
|
15
|
+
return `${nd.id}["${nd.label}"]`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 正規化數據 → mermaid flowchart DSL 字串
|
|
19
|
+
// 流程:
|
|
20
|
+
// 1. 收集所有出現的 cls → 產生 classDef 區塊(依 palette 取色)
|
|
21
|
+
// 2. 分析群組包含關係(支援二層巢狀, 如 DET 在 CORE 內)
|
|
22
|
+
// 3. 輸出 flowchart <dir>
|
|
23
|
+
// a. 先宣告無 group 的非容器節點
|
|
24
|
+
// b. 頂層 subgraph → 直接子節點 → 巢狀 subgraph → 巢狀子節點 → end → end
|
|
25
|
+
// c. edges(dashed 用 -.->, 有 label 用 |".."|)
|
|
26
|
+
// d. classDef + class 指派
|
|
27
|
+
function translate(data) {
|
|
28
|
+
const { dir, nodes, edges } = data
|
|
29
|
+
|
|
30
|
+
// 索引: id → node
|
|
31
|
+
const byId = {}
|
|
32
|
+
for (const nd of nodes) byId[nd.id] = nd
|
|
33
|
+
|
|
34
|
+
// 群組容器 id set
|
|
35
|
+
const groupIds = new Set(nodes.filter(nd => isGroupCls(nd.cls)).map(nd => nd.id))
|
|
36
|
+
|
|
37
|
+
// 收集所有出現的 cls
|
|
38
|
+
const clsSet = new Set(nodes.map(nd => nd.cls))
|
|
39
|
+
|
|
40
|
+
// classDef 行: 每個 cls 對應 palette 色票
|
|
41
|
+
const classDefLines = []
|
|
42
|
+
for (const cls of clsSet) {
|
|
43
|
+
const p = PALETTE[cls] || PALETTE.blue
|
|
44
|
+
if (isGroupCls(cls)) {
|
|
45
|
+
// 容器: text-valign top, bold 不在 classDef 裡控制; 只設填色/框色/字色
|
|
46
|
+
classDefLines.push(` classDef ${cls} fill:${p.fill},stroke:${p.stroke},stroke-width:1.8px,color:${p.text}`)
|
|
47
|
+
} else if (cls === 'diamond') {
|
|
48
|
+
classDefLines.push(` classDef ${cls} fill:${p.fill},stroke:${p.stroke},stroke-width:1.6px,color:${p.text}`)
|
|
49
|
+
} else {
|
|
50
|
+
classDefLines.push(` classDef ${cls} fill:${p.fill},stroke:${p.stroke},stroke-width:1.5px,color:${p.text}`)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// class 指派行: 每個 cls 一行, 收集該 cls 的所有節點 id
|
|
55
|
+
const clsToIds = {}
|
|
56
|
+
for (const nd of nodes) {
|
|
57
|
+
if (!clsToIds[nd.cls]) clsToIds[nd.cls] = []
|
|
58
|
+
clsToIds[nd.cls].push(nd.id)
|
|
59
|
+
}
|
|
60
|
+
const classLines = []
|
|
61
|
+
for (const [cls, ids] of Object.entries(clsToIds)) {
|
|
62
|
+
classLines.push(` class ${ids.join(',')} ${cls}`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 拓樸: 哪些節點直接屬於哪個容器(含巢狀)
|
|
66
|
+
// topLevel 容器: 自身無 group 的容器
|
|
67
|
+
// nested 容器: 自身有 group 的容器(巢狀放在父容器 subgraph 內)
|
|
68
|
+
const topGroupIds = nodes.filter(nd => isGroupCls(nd.cls) && !nd.group).map(nd => nd.id)
|
|
69
|
+
// 獨立節點(非容器、無 group): 直接輸出在 flowchart 頂層
|
|
70
|
+
const standaloneNodes = nodes.filter(nd => !isGroupCls(nd.cls) && !nd.group)
|
|
71
|
+
|
|
72
|
+
// 建立 subgraph 內容產生器(支援巢狀)
|
|
73
|
+
function renderSubgraph(gid, indent) {
|
|
74
|
+
const g = byId[gid]
|
|
75
|
+
const lines = []
|
|
76
|
+
lines.push(`${indent}subgraph ${gid}["${g.label}"]`)
|
|
77
|
+
// 直接子節點(非容器)
|
|
78
|
+
const directChildren = nodes.filter(nd => nd.group === gid && !isGroupCls(nd.cls))
|
|
79
|
+
for (const nd of directChildren) {
|
|
80
|
+
lines.push(`${indent} ${nodeShape(nd)}`)
|
|
81
|
+
}
|
|
82
|
+
// 巢狀容器(容器的 group === gid)
|
|
83
|
+
const nestedGroups = nodes.filter(nd => isGroupCls(nd.cls) && nd.group === gid)
|
|
84
|
+
for (const ng of nestedGroups) {
|
|
85
|
+
lines.push(...renderSubgraph(ng.id, indent + ' '))
|
|
86
|
+
}
|
|
87
|
+
lines.push(`${indent}end`)
|
|
88
|
+
return lines
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// edges DSL
|
|
92
|
+
const edgeLines = []
|
|
93
|
+
for (const ed of edges) {
|
|
94
|
+
const arrow = ed.kind === 'dashed' ? '-..->' : '-->'
|
|
95
|
+
const lbl = ed.label ? `|"${ed.label}"| ` : ''
|
|
96
|
+
edgeLines.push(` ${ed.from} ${arrow} ${lbl}${ed.to}`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 組裝 DSL
|
|
100
|
+
const lines = [`flowchart ${dir}`]
|
|
101
|
+
// 1. 獨立節點
|
|
102
|
+
for (const nd of standaloneNodes) {
|
|
103
|
+
lines.push(` ${nodeShape(nd)}`)
|
|
104
|
+
}
|
|
105
|
+
// 2. 頂層 subgraph
|
|
106
|
+
for (const gid of topGroupIds) {
|
|
107
|
+
lines.push(...renderSubgraph(gid, ' '))
|
|
108
|
+
}
|
|
109
|
+
// 3. edges
|
|
110
|
+
lines.push(...edgeLines)
|
|
111
|
+
// 4. classDef + class
|
|
112
|
+
lines.push(...classDefLines)
|
|
113
|
+
lines.push(...classLines)
|
|
114
|
+
|
|
115
|
+
return lines.join('\n')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// mermaid@10 CDN HTML 外殼: 渲染 DSL → SVG 注入 #box, 截 #box svg
|
|
119
|
+
// 沿用舊版完整邏輯(themeCSS 覆蓋邊線色/寬/箭頭, 截圖前確保字型載入)
|
|
120
|
+
// 修正 A: 邊標籤白底→透明; 文字加白色光暈(paint-order:stroke + stroke:#fff/stroke-width:3.5px)
|
|
121
|
+
// 修正 B: 英數識別字不攔腰斷(word-break:normal; overflow-wrap:break-word)
|
|
122
|
+
function mermaidHtml(code) {
|
|
123
|
+
return `<!doctype html><html lang="zh-Hant"><head><meta charset="utf-8">
|
|
124
|
+
<script>${MERMAID_JS}</script>
|
|
125
|
+
</head><body style="margin:0;background:#fff;font-family:'微軟正黑體','Microsoft JhengHei',sans-serif">
|
|
126
|
+
<div id="box" style="display:inline-block;padding:24px"></div>
|
|
127
|
+
<script>
|
|
128
|
+
mermaid.initialize({ startOnLoad:false, theme:'base', themeVariables:{ fontFamily:"'Microsoft JhengHei','微軟正黑體',sans-serif", fontSize:'17px', primaryColor:'#eef4fb', primaryBorderColor:'#3f6fb0', lineColor:'#44505a', primaryTextColor:'#1c2b36' }, themeCSS: '.flowchart-link{stroke:#44505a !important;stroke-width:2.5px !important;} .edgePaths .path{stroke:#44505a !important;stroke-width:2.5px !important;} .marker,.marker path{fill:#44505a !important;stroke:#44505a !important;} .edgeLabel .label{background:transparent !important;} .edgeLabel{background:transparent !important;} .edgeLabel rect{fill:transparent !important;opacity:0 !important;} .label foreignObject{overflow:visible;} .cluster-label text,.cluster-label span,.cluster-label div,.edgeLabel span{font-family:"Microsoft JhengHei","微軟正黑體",sans-serif !important;}' });
|
|
129
|
+
(async () => {
|
|
130
|
+
try {
|
|
131
|
+
const { svg } = await mermaid.render('g', ${JSON.stringify(code)})
|
|
132
|
+
document.getElementById('box').innerHTML = svg
|
|
133
|
+
const DK = '#333333', W = '3px'
|
|
134
|
+
document.querySelectorAll('#box .edgePaths path, #box .edgePath path, #box path.flowchart-link, #box g.edgePaths path').forEach((p) => { p.style.setProperty('stroke', DK, 'important'); p.style.setProperty('stroke-width', W, 'important') })
|
|
135
|
+
document.querySelectorAll('#box marker path, #box .marker, #box .marker path').forEach((m) => { m.style.setProperty('fill', DK, 'important'); m.style.setProperty('stroke', DK, 'important') })
|
|
136
|
+
document.querySelectorAll('#box .node rect, #box .node polygon, #box .node circle, #box .node path').forEach((n) => { n.style.setProperty('stroke-width', '2px', 'important') })
|
|
137
|
+
document.querySelectorAll('#box .cluster rect, #box .cluster polygon, #box .cluster path').forEach((c) => { c.style.setProperty('stroke', '#777777', 'important'); c.style.setProperty('stroke-width', '1.5px', 'important') })
|
|
138
|
+
// 修正 A: 邊標籤背景透明 + 白色光暈(透過 foreignObject 內 span 的 CSS)
|
|
139
|
+
document.querySelectorAll('#box .edgeLabel rect, #box .edgeLabel .label').forEach((el) => {
|
|
140
|
+
el.style.setProperty('fill', 'transparent', 'important')
|
|
141
|
+
el.style.setProperty('background', 'transparent', 'important')
|
|
142
|
+
el.style.setProperty('opacity', '1', 'important')
|
|
143
|
+
})
|
|
144
|
+
document.querySelectorAll('#box .edgeLabel foreignObject').forEach((fo) => {
|
|
145
|
+
fo.style.setProperty('overflow', 'visible', 'important')
|
|
146
|
+
})
|
|
147
|
+
document.querySelectorAll('#box .edgeLabel span, #box .edgeLabel .edgeLabel').forEach((sp) => {
|
|
148
|
+
sp.style.setProperty('background', 'transparent', 'important')
|
|
149
|
+
sp.style.setProperty('color', '#4a5560', 'important')
|
|
150
|
+
// 字型覆蓋: 邊標籤 span 可能繼承系統字型, 強制指定
|
|
151
|
+
sp.style.setProperty('font-family', "'Microsoft JhengHei', '微軟正黑體', sans-serif", 'important')
|
|
152
|
+
// 白色光暈: CSS text-shadow 多向
|
|
153
|
+
sp.style.setProperty('text-shadow', '-1.5px -1.5px 0 #fff, 1.5px -1.5px 0 #fff, -1.5px 1.5px 0 #fff, 1.5px 1.5px 0 #fff, 0 -2px 0 #fff, 0 2px 0 #fff, -2px 0 0 #fff, 2px 0 0 #fff', 'important')
|
|
154
|
+
// 修正 B: 英數識別字不攔腰斷
|
|
155
|
+
sp.style.setProperty('word-break', 'normal', 'important')
|
|
156
|
+
sp.style.setProperty('overflow-wrap', 'break-word', 'important')
|
|
157
|
+
})
|
|
158
|
+
// 字型覆蓋: cluster(subgraph)標題文字 — mermaid@10 的 .cluster-label text/span 不繼承 fontFamily themeVariable
|
|
159
|
+
document.querySelectorAll('#box .cluster-label text, #box .cluster-label span, #box .cluster-label div').forEach((el) => {
|
|
160
|
+
el.style.setProperty('font-family', "'Microsoft JhengHei', '微軟正黑體', sans-serif", 'important')
|
|
161
|
+
})
|
|
162
|
+
document.title = 'DONE'
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
document.title = 'FAIL'
|
|
166
|
+
console.log('mmErr', e.message)
|
|
167
|
+
}
|
|
168
|
+
})()
|
|
169
|
+
</script></body></html>`
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 單張渲染: 每張用新 page(避免 mermaid render id 衝突), deviceScaleFactor:4(向量圖放大提高清晰度)
|
|
173
|
+
// fpOut 省略時不寫檔; 皆回傳 PNG Buffer
|
|
174
|
+
async function genFig(browser, code, fpOut) {
|
|
175
|
+
const page = await browser.newPage({ deviceScaleFactor: 4 })
|
|
176
|
+
try {
|
|
177
|
+
await page.setContent(mermaidHtml(code), { waitUntil: 'load' })
|
|
178
|
+
await page.waitForFunction(() => document.title === 'DONE' || document.title === 'FAIL', { timeout: 30000 })
|
|
179
|
+
const title = await page.title()
|
|
180
|
+
if (title !== 'DONE') {
|
|
181
|
+
const msg = await page.evaluate(() => { const el = document.getElementById('box'); return el ? el.innerText : '' })
|
|
182
|
+
throw new Error(`mermaid render failed for ${fpOut || '(buffer)'}: ${msg}`)
|
|
183
|
+
}
|
|
184
|
+
await page.evaluate(() => document.fonts.ready)
|
|
185
|
+
return await page.locator('#box svg').screenshot(fpOut ? { path: fpOut } : {})
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
await page.close()
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 單張渲染 → PNG Buffer(供 WFlowchart 統一入口呼叫; 自帶瀏覽器生命週期)
|
|
193
|
+
async function genPng(data, opt = {}) {
|
|
194
|
+
const browser = await chromium.launch()
|
|
195
|
+
try {
|
|
196
|
+
return await genFig(browser, translate(data), null)
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
await browser.close()
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export { genPng }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# p10 — ELK 直用 + 自寫 SVG 渲染器 產線
|
|
2
|
+
|
|
3
|
+
p1~p9 為現成繪圖套件;**p10 是唯一「自寫 SVG 渲染器」產線**:用 elkjs 只負責「算佈局」,座標拿到後由本產線**自行繪製 SVG**,版面與樣式完全可控。
|
|
4
|
+
|
|
5
|
+
## 技術核心
|
|
6
|
+
|
|
7
|
+
- **佈局引擎**:`elkjs`(Eclipse Layout Kernel 的 JS 版,`elk.bundled.js` UMD 主執行緒、免 worker),由本機 `node_modules` 讀出後於 `gen.mjs` 模組載入時以 `common/pkg.mjs` 的 `pkgScript` 內聯注入 `PAGE_HTML`(取代 CDN,斷網環境可用),於 Playwright headless 頁內執行。`new ELK().layout(graph)` 回傳各節點/容器之座標尺寸與各邊之 sections(轉折點)。
|
|
8
|
+
- **渲染**:本產線**自行把 ELK 結果組成 SVG 字串**(rect / polygon 菱形 / path 折線 + 箭頭 / foreignObject 文字),插入頁面後對 `#stage svg` 截圖。`deviceScaleFactor:2`。中文以 `foreignObject` 內 div 渲染(瀏覽器原生量字、不爆框)。
|
|
9
|
+
- **呼叫入口**:`export async function genPng(data, opt = {})` 為唯一對外介面,回傳 `Promise<Buffer>`(PNG);由 `src/WFlowchart.mjs` 之 `GENS.p10` 統一調用(`mode: 'p10'`)。`data` 為正規化繪圖數據 `{ dir, nodes, edges }`;`opt` 保留擴充,目前原樣接收但未使用。`gen.mjs` 另外 `export function translate` 與 `export const PAGE_HTML`,供 `test/`(19 案結構不變量 + 快照回歸測試)重用真實管線(不複製渲染器)。
|
|
10
|
+
|
|
11
|
+
## 產製原理(資料驅動)
|
|
12
|
+
|
|
13
|
+
`translate(data)` 把標準數據轉成 ELK 圖 + 渲染:
|
|
14
|
+
- **節點**:量測標籤(`#meas` 隱形 div)得寬高 → ELK node width/height;`diamond` 類別畫 polygon 菱形(尺寸放大以內含標籤),其餘畫圓角 rect;色彩依 `colorOf(cls)`。
|
|
15
|
+
- **群組/巢狀容器**:群組為 ELK 的 child node、其成員為該 node 的 `children`(支援多層巢狀);`elk.padding` 上方留標題空間;`hierarchyHandling:INCLUDE_CHILDREN` 確保**跨容器邊不會把成員拉出容器**。渲染時容器 rect 墊底(深度小先畫)+ 標題置頂粗體。
|
|
16
|
+
- **邊**:依 source/target 之最近共同祖先容器(LCA)宣告(無共同容器則掛於 root)——ELK 對「宣告於某容器的邊」回傳之 section 座標相對該容器,故渲染時需對位;ELK 以 `edgeRouting:ORTHOGONAL` 算**正交直角路由**,輸出 sections 轉折點 → 本產線繪 polyline + 自繪三角箭頭。座標依邊所在容器之絕對位移還原。
|
|
17
|
+
- **流向**:`dir` → ELK `elk.direction`(TB→DOWN、LR→RIGHT)。
|
|
18
|
+
|
|
19
|
+
## 自動化機制
|
|
20
|
+
|
|
21
|
+
- **巢狀容器零逃逸**:ELK `INCLUDE_CHILDREN` 是本產線相對 maxGraph/vis 的關鍵優勢——群組成員一律待在容器內,容器自動依成員撐大(含多層巢狀)。
|
|
22
|
+
- **正交直角邊路由**:現成 10 套皆無;由 ELK 演算、本產線自繪。
|
|
23
|
+
- **字型全覆蓋**:所有 SVG 文字(節點/容器標題/邊標籤)走 `foreignObject` + `font-family:Microsoft JhengHei`,無遺漏、不爆框。
|
|
24
|
+
- **邊標籤白光暈**:邊標籤 div 以多向 `text-shadow` 白光暈(`EDGE.haloColor/haloWidth`),透明底、不遮線。
|
|
25
|
+
- **菱形標籤置中**:菱形以 polygon 畫、標籤 foreignObject 置中於外接框,天然置中(優於 vis 需 vadjust 硬調)。
|
|
26
|
+
- 版面間距為通用常數(`elk.spacing.*`),尺寸由內容自然決定,無逐圖魔術數字。
|
|
27
|
+
|
|
28
|
+
## 已知限制
|
|
29
|
+
|
|
30
|
+
- **循環(回邊)起點**:ELK 的斷環點不一定讓「流程起點」落在最上(如作業流程的「退回」迴圈),屬版面取捨、非錯誤(可調 ELK cycle-breaking 選項)。
|
|
31
|
+
- **線性長鏈**:純線性流程(如批次)會被拉得較高(與 dagre 系一致)。
|