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
package/test/cases.mjs
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
// p10 合成測試案例 — 每案隔離「一個 p10 機制」, 失敗時可快速定位是哪個機制被改壞。
|
|
2
|
+
// data 為標準正規化數據 {dir,nodes,edges}(與 9 張報告圖同格式; cls 見 common/palette.mjs)。
|
|
3
|
+
// expect: 選用之比例界線(A4 折排案)。 check(spec,geom,h): 選用之案例專屬斷言(回 [{name,pass,detail}])。
|
|
4
|
+
// 通用不變量(存在性/包含/標題不溢出/不重疊/items框/邊)由 lib.runInvariants 對「所有」案例(含 9 真圖)套用。
|
|
5
|
+
|
|
6
|
+
export const CASES = [
|
|
7
|
+
|
|
8
|
+
// 1) 最基本: 純 label 矩形節點 + 實線箭頭, 直式
|
|
9
|
+
{
|
|
10
|
+
name: 'flat-tb',
|
|
11
|
+
desc: '基本 — 純 label 矩形 + 實線箭頭 (TB)',
|
|
12
|
+
data: {
|
|
13
|
+
dir: 'TB',
|
|
14
|
+
nodes: [
|
|
15
|
+
{ id: 'A', label: '節點 A', cls: 'blue' },
|
|
16
|
+
{ id: 'B', label: '節點 B', cls: 'green' },
|
|
17
|
+
{ id: 'C', label: '節點 C', cls: 'orange' },
|
|
18
|
+
],
|
|
19
|
+
edges: [{ from: 'A', to: 'B' }, { from: 'B', to: 'C' }],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
// 2) 標題+items 富節點(6 層框: 框背景/標題背景/框線/分隔線/標題/items); 不同 item 數驗框高算法
|
|
24
|
+
{
|
|
25
|
+
name: 'items-node',
|
|
26
|
+
desc: '富節點 — 標題+items 分層框 (含框高算法)',
|
|
27
|
+
data: {
|
|
28
|
+
dir: 'TB',
|
|
29
|
+
nodes: [
|
|
30
|
+
{ id: 'X', title: '服務 X', items: ['項目一', '項目二', '項目三', '項目四', '項目五'], cls: 'orange' },
|
|
31
|
+
{ id: 'Y', title: '服務 Y', items: ['只有一項'], cls: 'orange' },
|
|
32
|
+
],
|
|
33
|
+
edges: [{ from: 'X', to: 'Y', label: '呼叫' }],
|
|
34
|
+
},
|
|
35
|
+
check(spec, geom, h) {
|
|
36
|
+
const X = h.node('X'); const Y = h.node('Y')
|
|
37
|
+
return [
|
|
38
|
+
{ name: 'X 為 items 框', pass: !!X && X.kind === 'items' && X.headH > 0, detail: X && `kind=${X.kind} headH=${X.headH}` },
|
|
39
|
+
{ name: 'Y 為 items 框', pass: !!Y && Y.kind === 'items' && Y.headH > 0, detail: Y && `kind=${Y.kind} headH=${Y.headH}` },
|
|
40
|
+
// 多 item 框應高於少 item 框(框高隨內容, 非固定)
|
|
41
|
+
{ name: '框高隨 item 數', pass: !!X && !!Y && X.h > Y.h, detail: X && Y && `X.h=${X.h} Y.h=${Y.h}` },
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// 3) 二層巢狀容器 + 跨容器邊(LCA 邊路由: a→c 應掛在最近共同祖先 OUT)
|
|
47
|
+
{
|
|
48
|
+
name: 'nested-group',
|
|
49
|
+
desc: '巢狀 — 二層容器 + 跨內層邊 (LCA 路由)',
|
|
50
|
+
data: {
|
|
51
|
+
dir: 'TB',
|
|
52
|
+
nodes: [
|
|
53
|
+
{ id: 'OUT', label: '外層容器', cls: 'blueG' },
|
|
54
|
+
{ id: 'IN1', label: '內層 A', cls: 'greenG', group: 'OUT' },
|
|
55
|
+
{ id: 'IN2', label: '內層 B', cls: 'greenG', group: 'OUT' },
|
|
56
|
+
{ id: 'a', label: 'a', cls: 'green', group: 'IN1' },
|
|
57
|
+
{ id: 'b', label: 'b', cls: 'green', group: 'IN1' },
|
|
58
|
+
{ id: 'c', label: 'c', cls: 'green', group: 'IN2' },
|
|
59
|
+
],
|
|
60
|
+
edges: [{ from: 'a', to: 'b' }, { from: 'a', to: 'c', label: '跨層' }],
|
|
61
|
+
},
|
|
62
|
+
check(spec, geom, h) {
|
|
63
|
+
const OUT = h.group('OUT'); const IN1 = h.group('IN1'); const IN2 = h.group('IN2')
|
|
64
|
+
return [
|
|
65
|
+
{ name: '三層深度齊備', pass: !!OUT && !!IN1 && !!IN2 && OUT.depth === 0 && IN1.depth === 1 && IN2.depth === 1, detail: `OUT=${OUT && OUT.depth} IN1=${IN1 && IN1.depth} IN2=${IN2 && IN2.depth}` },
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// 4) 容器標題比成員寬 → 2-pass padding 撐寬, 標題不溢出(此前修過的 bug)
|
|
71
|
+
{
|
|
72
|
+
name: 'title-overflow',
|
|
73
|
+
desc: '標題撐寬 — 容器標題比成員寬, 2-pass 不溢出',
|
|
74
|
+
data: {
|
|
75
|
+
dir: 'TB',
|
|
76
|
+
nodes: [
|
|
77
|
+
{ id: 'WIDE', label: '這是一個相當長的容器標題用於測試自動撐寬', cls: 'purpleG' },
|
|
78
|
+
{ id: 's1', label: '短', cls: 'purple', group: 'WIDE' },
|
|
79
|
+
{ id: 's2', label: 'A', cls: 'purple', group: 'WIDE' },
|
|
80
|
+
],
|
|
81
|
+
edges: [{ from: 's1', to: 's2' }],
|
|
82
|
+
},
|
|
83
|
+
check(spec, geom, h) {
|
|
84
|
+
const W = h.group('WIDE')
|
|
85
|
+
return [
|
|
86
|
+
{ name: '標題寬 ≤ 容器寬', pass: !!W && W.labelW <= W.w + 2, detail: W && `labelW=${W.labelW} w=${W.w}` },
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
// 5) 決策菱形 + 通過/退回分支
|
|
92
|
+
{
|
|
93
|
+
name: 'diamond',
|
|
94
|
+
desc: '菱形 — 決策節點 + 是/否分支',
|
|
95
|
+
data: {
|
|
96
|
+
dir: 'TB',
|
|
97
|
+
nodes: [
|
|
98
|
+
{ id: 'P', label: '輸入', cls: 'blue' },
|
|
99
|
+
{ id: 'D', label: '判斷?', cls: 'diamond' },
|
|
100
|
+
{ id: 'Y', label: '通過', cls: 'green' },
|
|
101
|
+
{ id: 'N', label: '退回', cls: 'red' },
|
|
102
|
+
],
|
|
103
|
+
edges: [{ from: 'P', to: 'D' }, { from: 'D', to: 'Y', label: '是' }, { from: 'D', to: 'N', label: '否' }],
|
|
104
|
+
},
|
|
105
|
+
check(spec, geom, h) {
|
|
106
|
+
const D = h.node('D')
|
|
107
|
+
return [{ name: 'D 為菱形', pass: !!D && D.kind === 'diamond', detail: D && `kind=${D.kind}` }]
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
// 6) 邊樣式: 實線 / 虛線(dashed) / 帶標籤(白光暈) / 短邊(ensureStub)
|
|
112
|
+
{
|
|
113
|
+
name: 'edges-mix',
|
|
114
|
+
desc: '邊樣式 — 實線/虛線/帶標籤/短邊 stub',
|
|
115
|
+
data: {
|
|
116
|
+
dir: 'TB',
|
|
117
|
+
nodes: [
|
|
118
|
+
{ id: 'A', label: '來源', cls: 'blue' },
|
|
119
|
+
{ id: 'B', label: '目標一', cls: 'green' },
|
|
120
|
+
{ id: 'C', label: '目標二', cls: 'orange' },
|
|
121
|
+
],
|
|
122
|
+
edges: [
|
|
123
|
+
{ from: 'A', to: 'B', label: '實線標籤' },
|
|
124
|
+
{ from: 'A', to: 'C', kind: 'dashed', label: '虛線標籤' },
|
|
125
|
+
{ from: 'B', to: 'C' },
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
// 7) A4 過高 → 折排: 最高之多子容器(垂直鏈)折成多欄縮高
|
|
131
|
+
{
|
|
132
|
+
name: 'fold-tall',
|
|
133
|
+
desc: 'A4 折排 — 過高, 多子容器折成多欄',
|
|
134
|
+
data: {
|
|
135
|
+
dir: 'TB',
|
|
136
|
+
nodes: [
|
|
137
|
+
{ id: 'G', label: '垂直長鏈容器', cls: 'blueG' },
|
|
138
|
+
...Array.from({ length: 7 }, (_, i) => ({ id: 'n' + i, label: '步驟 ' + (i + 1), cls: 'blue', group: 'G' })),
|
|
139
|
+
],
|
|
140
|
+
edges: Array.from({ length: 6 }, (_, i) => ({ from: 'n' + i, to: 'n' + (i + 1) })),
|
|
141
|
+
},
|
|
142
|
+
expect: { ratioMax: 1.45 },
|
|
143
|
+
check(spec, geom, h) {
|
|
144
|
+
const xs = new Set(geom.nodes.filter(n => n.parent === 'G').map(n => Math.round(n.x / 20)))
|
|
145
|
+
return [{ name: '已折成 ≥2 欄', pass: xs.size >= 2, detail: '欄數(x 帶)=' + xs.size }]
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
// 8) A4 過寬 → 折排: 最寬之多子容器(水平鏈)折成多列增高
|
|
150
|
+
{
|
|
151
|
+
name: 'fold-wide',
|
|
152
|
+
desc: 'A4 折排 — 過寬, 多子容器折成多列',
|
|
153
|
+
data: {
|
|
154
|
+
dir: 'LR',
|
|
155
|
+
nodes: [
|
|
156
|
+
{ id: 'G', label: '水平長鏈容器', cls: 'orangeG' },
|
|
157
|
+
...Array.from({ length: 7 }, (_, i) => ({ id: 'n' + i, label: '階段 ' + (i + 1), cls: 'orange', group: 'G' })),
|
|
158
|
+
],
|
|
159
|
+
edges: Array.from({ length: 6 }, (_, i) => ({ from: 'n' + i, to: 'n' + (i + 1) })),
|
|
160
|
+
},
|
|
161
|
+
expect: { ratioMin: 0.70 },
|
|
162
|
+
check(spec, geom, h) {
|
|
163
|
+
const ys = new Set(geom.nodes.filter(n => n.parent === 'G').map(n => Math.round(n.y / 20)))
|
|
164
|
+
return [{ name: '已折成 ≥2 列', pass: ys.size >= 2, detail: '列數(y 帶)=' + ys.size }]
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
// 9) 容器內矩形框垂直對齊: top / center / bottom(各容器一高框一矮框, 驗矮框位置)
|
|
169
|
+
{
|
|
170
|
+
name: 'valign',
|
|
171
|
+
desc: '垂直對齊 — 容器內框 top/center/bottom',
|
|
172
|
+
data: {
|
|
173
|
+
dir: 'TB',
|
|
174
|
+
nodes: [
|
|
175
|
+
{ id: 'gT', label: 'top 對齊', cls: 'blueG', align: 'top' },
|
|
176
|
+
{ id: 'tT', title: '高框', items: ['一', '二', '三', '四', '五'], cls: 'blue', group: 'gT' },
|
|
177
|
+
{ id: 'sT', label: '矮', cls: 'green', group: 'gT' },
|
|
178
|
+
{ id: 'gC', label: 'center 對齊', cls: 'blueG', align: 'center' },
|
|
179
|
+
{ id: 'tC', title: '高框', items: ['一', '二', '三', '四', '五'], cls: 'blue', group: 'gC' },
|
|
180
|
+
{ id: 'sC', label: '矮', cls: 'green', group: 'gC' },
|
|
181
|
+
{ id: 'gB', label: 'bottom 對齊', cls: 'blueG', align: 'bottom' },
|
|
182
|
+
{ id: 'tB', title: '高框', items: ['一', '二', '三', '四', '五'], cls: 'blue', group: 'gB' },
|
|
183
|
+
{ id: 'sB', label: '矮', cls: 'green', group: 'gB' },
|
|
184
|
+
],
|
|
185
|
+
edges: [],
|
|
186
|
+
},
|
|
187
|
+
check(spec, geom, h) {
|
|
188
|
+
const pair = (t, s) => {
|
|
189
|
+
const T = h.node(t); const S = h.node(s); return { T, S }
|
|
190
|
+
}
|
|
191
|
+
const top = pair('tT', 'sT'); const cen = pair('tC', 'sC'); const bot = pair('tB', 'sB')
|
|
192
|
+
const okTop = top.T && top.S && Math.abs(top.S.y - top.T.y) <= 3
|
|
193
|
+
const okCen = cen.T && cen.S && Math.abs((cen.S.y + cen.S.h / 2) - (cen.T.y + cen.T.h / 2)) <= 4
|
|
194
|
+
const okBot = bot.T && bot.S && Math.abs((bot.S.y + bot.S.h) - (bot.T.y + bot.T.h)) <= 3
|
|
195
|
+
return [
|
|
196
|
+
{ name: 'top: 矮框頂端對齊', pass: okTop, detail: top.T && top.S && `Δtop=${(top.S.y - top.T.y).toFixed(1)}` },
|
|
197
|
+
{ name: 'center: 矮框置中對齊', pass: okCen, detail: cen.T && cen.S && `Δmid=${((cen.S.y + cen.S.h / 2) - (cen.T.y + cen.T.h / 2)).toFixed(1)}` },
|
|
198
|
+
{ name: 'bottom: 矮框底端對齊', pass: okBot, detail: bot.T && bot.S && `Δbot=${((bot.S.y + bot.S.h) - (bot.T.y + bot.T.h)).toFixed(1)}` },
|
|
199
|
+
]
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
// 10) 水平佈局 dir:LR(含一個包覆容器)
|
|
204
|
+
{
|
|
205
|
+
name: 'lr-flat',
|
|
206
|
+
desc: '水平佈局 — dir:LR + 包覆容器',
|
|
207
|
+
data: {
|
|
208
|
+
dir: 'LR',
|
|
209
|
+
nodes: [
|
|
210
|
+
{ id: 'A', label: '入口', cls: 'blue' },
|
|
211
|
+
{ id: 'GP', label: '處理群組', cls: 'greenG' },
|
|
212
|
+
{ id: 'B', label: '處理一', cls: 'green', group: 'GP' },
|
|
213
|
+
{ id: 'C', label: '處理二', cls: 'green', group: 'GP' },
|
|
214
|
+
{ id: 'D', label: '出口', cls: 'orange' },
|
|
215
|
+
],
|
|
216
|
+
edges: [{ from: 'A', to: 'B' }, { from: 'B', to: 'C' }, { from: 'C', to: 'D' }],
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
// 11) 循環退回邊(hasCycle 路徑: cycleBreaking MODEL_ORDER, 根層不加位置提示; 入口節點應仍在頂端)
|
|
221
|
+
{
|
|
222
|
+
name: 'cycle',
|
|
223
|
+
desc: '循環 — 決策退回邊 (cycleBreaking)',
|
|
224
|
+
data: {
|
|
225
|
+
dir: 'TB',
|
|
226
|
+
nodes: [
|
|
227
|
+
{ id: 'S', label: '開始', cls: 'blue' },
|
|
228
|
+
{ id: 'W', label: '處理', cls: 'green' },
|
|
229
|
+
{ id: 'D', label: '審核?', cls: 'diamond' },
|
|
230
|
+
{ id: 'E', label: '完成', cls: 'done' },
|
|
231
|
+
],
|
|
232
|
+
edges: [
|
|
233
|
+
{ from: 'S', to: 'W' },
|
|
234
|
+
{ from: 'W', to: 'D' },
|
|
235
|
+
{ from: 'D', to: 'E', label: '通過' },
|
|
236
|
+
{ from: 'D', to: 'W', kind: 'dashed', label: '退回' },
|
|
237
|
+
],
|
|
238
|
+
},
|
|
239
|
+
check(spec, geom, h) {
|
|
240
|
+
const S = h.node('S')
|
|
241
|
+
const minY = Math.min(...geom.nodes.map(n => n.y))
|
|
242
|
+
return [
|
|
243
|
+
{ name: '入口節點居頂(不因循環偏移)', pass: !!S && S.y <= minY + 2, detail: S && `S.y=${S.y} minY=${minY}` },
|
|
244
|
+
]
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
// 12) 三層巢狀容器 + 由最深葉節點連出至頂層節點(LCA=root 之跨三層邊)
|
|
249
|
+
{
|
|
250
|
+
name: 'deep-nest',
|
|
251
|
+
desc: '深巢狀 — 三層容器 + 跨三層邊',
|
|
252
|
+
data: {
|
|
253
|
+
dir: 'TB',
|
|
254
|
+
nodes: [
|
|
255
|
+
{ id: 'L1', label: '第一層', cls: 'blueG' },
|
|
256
|
+
{ id: 'L2', label: '第二層', cls: 'greenG', group: 'L1' },
|
|
257
|
+
{ id: 'L3', label: '第三層', cls: 'orangeG', group: 'L2' },
|
|
258
|
+
{ id: 'deep', label: '最深節點', cls: 'orange', group: 'L3' },
|
|
259
|
+
{ id: 'top', label: '頂層節點', cls: 'blue' },
|
|
260
|
+
],
|
|
261
|
+
edges: [{ from: 'deep', to: 'top', label: '跨三層' }],
|
|
262
|
+
},
|
|
263
|
+
check(spec, geom, h) {
|
|
264
|
+
const L1 = h.group('L1'); const L2 = h.group('L2'); const L3 = h.group('L3')
|
|
265
|
+
return [
|
|
266
|
+
{ name: '容器深度 0/1/2', pass: !!L1 && !!L2 && !!L3 && L1.depth === 0 && L2.depth === 1 && L3.depth === 2, detail: `L1=${L1 && L1.depth} L2=${L2 && L2.depth} L3=${L3 && L3.depth}` },
|
|
267
|
+
]
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
// 13) 英文數據(半形括號/斜線之衍生 label 情境 + 長英文識別字折行不攔腰斷)
|
|
272
|
+
{
|
|
273
|
+
name: 'english',
|
|
274
|
+
desc: '英文 — 半形標籤 + items 富節點 + 長識別字',
|
|
275
|
+
data: {
|
|
276
|
+
dir: 'TB',
|
|
277
|
+
nodes: [
|
|
278
|
+
{ id: 'api', label: 'API Gateway', cls: 'blue' },
|
|
279
|
+
{ id: 'svc', title: 'Auth Service', items: ['login/logout', 'token refresh (JWT)', 'session mgmt'], cls: 'green' },
|
|
280
|
+
{ id: 'cfg', label: 'InternationalizationConfigurationManager', cls: 'orange' },
|
|
281
|
+
],
|
|
282
|
+
edges: [{ from: 'api', to: 'svc', label: 'HTTPS' }, { from: 'svc', to: 'cfg', kind: 'dashed' }],
|
|
283
|
+
},
|
|
284
|
+
check(spec, geom, h) {
|
|
285
|
+
const svc = h.node('svc')
|
|
286
|
+
return [
|
|
287
|
+
{ name: 'svc 為 items 框', pass: !!svc && svc.kind === 'items' && svc.headH > 0, detail: svc && `kind=${svc.kind} headH=${svc.headH}` },
|
|
288
|
+
]
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
// 14) 長中文標籤折行(measure maxW=200 → 框寬受限, 不無限撐寬)
|
|
293
|
+
{
|
|
294
|
+
name: 'long-label',
|
|
295
|
+
desc: '長標籤 — 超長中文自動折行, 框寬受 MAXW 限制',
|
|
296
|
+
data: {
|
|
297
|
+
dir: 'TB',
|
|
298
|
+
nodes: [
|
|
299
|
+
{ id: 'L', label: '這是一段非常非常長的節點標籤文字用於驗證自動折行機制與框寬上限行為', cls: 'purple' },
|
|
300
|
+
{ id: 'B', label: '下游', cls: 'blue' },
|
|
301
|
+
],
|
|
302
|
+
edges: [{ from: 'L', to: 'B' }],
|
|
303
|
+
},
|
|
304
|
+
check(spec, geom, h) {
|
|
305
|
+
const L = h.node('L')
|
|
306
|
+
return [
|
|
307
|
+
{ name: '框寬受 MAXW 限制(≤230)', pass: !!L && L.w <= 230, detail: L && `w=${L.w}` },
|
|
308
|
+
{ name: '折行後高於單行(≥2行)', pass: !!L && L.h >= 50, detail: L && `h=${L.h}` },
|
|
309
|
+
]
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
// 15) 無邊圖(純節點+容器; 邊相關機制全跳過仍須正確佈局)
|
|
314
|
+
{
|
|
315
|
+
name: 'no-edges',
|
|
316
|
+
desc: '無邊 — 純節點與容器, 無任何連線',
|
|
317
|
+
data: {
|
|
318
|
+
dir: 'TB',
|
|
319
|
+
nodes: [
|
|
320
|
+
{ id: 'G', label: '群組', cls: 'greenG' },
|
|
321
|
+
{ id: 'a', label: '甲', cls: 'green', group: 'G' },
|
|
322
|
+
{ id: 'b', label: '乙', cls: 'green', group: 'G' },
|
|
323
|
+
{ id: 'solo', label: '獨立節點', cls: 'blue' },
|
|
324
|
+
],
|
|
325
|
+
edges: [],
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
// 16) 群組對群組邊 + 葉節點對群組邊(端點為複合容器之階層邊)
|
|
330
|
+
{
|
|
331
|
+
name: 'group-edge',
|
|
332
|
+
desc: '群組邊 — 群組↔群組 / 葉節點↔群組',
|
|
333
|
+
data: {
|
|
334
|
+
dir: 'TB',
|
|
335
|
+
nodes: [
|
|
336
|
+
{ id: 'G1', label: '來源群組', cls: 'blueG' },
|
|
337
|
+
{ id: 'x', label: 'x', cls: 'blue', group: 'G1' },
|
|
338
|
+
{ id: 'G2', label: '目標群組', cls: 'orangeG' },
|
|
339
|
+
{ id: 'y', label: 'y', cls: 'orange', group: 'G2' },
|
|
340
|
+
{ id: 'ext', label: '外部節點', cls: 'green' },
|
|
341
|
+
],
|
|
342
|
+
edges: [
|
|
343
|
+
{ from: 'G1', to: 'G2', label: '群組相依' },
|
|
344
|
+
{ from: 'ext', to: 'G2', kind: 'dashed' },
|
|
345
|
+
],
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dir": "TB",
|
|
3
|
+
"nodes": [
|
|
4
|
+
{
|
|
5
|
+
"id": "s1",
|
|
6
|
+
"title": "執行單位產製詮釋資料",
|
|
7
|
+
"items": [
|
|
8
|
+
"Excel 模板+附件"
|
|
9
|
+
],
|
|
10
|
+
"cls": "blue"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "s2",
|
|
14
|
+
"label": "上傳(分片)",
|
|
15
|
+
"cls": "blue"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "s3",
|
|
19
|
+
"label": "移至批次區",
|
|
20
|
+
"cls": "blue"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "s4",
|
|
24
|
+
"title": "格式檢測",
|
|
25
|
+
"items": [
|
|
26
|
+
"欄位",
|
|
27
|
+
"必填",
|
|
28
|
+
"重複編號",
|
|
29
|
+
"重複檔案 SHA",
|
|
30
|
+
"引用關聯"
|
|
31
|
+
],
|
|
32
|
+
"cls": "blue"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "s5",
|
|
36
|
+
"label": "檢測摘要報告",
|
|
37
|
+
"cls": "blue"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "s6",
|
|
41
|
+
"label": "管理員確認",
|
|
42
|
+
"cls": "diamond"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "s7",
|
|
46
|
+
"label": "寫入資料庫",
|
|
47
|
+
"cls": "done"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "s8",
|
|
51
|
+
"label": "查詢/展示/報表輸出",
|
|
52
|
+
"cls": "done"
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"edges": [
|
|
56
|
+
{
|
|
57
|
+
"from": "s1",
|
|
58
|
+
"to": "s2"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"from": "s2",
|
|
62
|
+
"to": "s3"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"from": "s3",
|
|
66
|
+
"to": "s4"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"from": "s4",
|
|
70
|
+
"to": "s5"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"from": "s5",
|
|
74
|
+
"to": "s6"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"from": "s6",
|
|
78
|
+
"to": "s7",
|
|
79
|
+
"label": "通過"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"from": "s7",
|
|
83
|
+
"to": "s8"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"from": "s6",
|
|
87
|
+
"to": "s1",
|
|
88
|
+
"label": "退回",
|
|
89
|
+
"kind": "dashed"
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dir": "TB",
|
|
3
|
+
"nodes": [
|
|
4
|
+
{
|
|
5
|
+
"id": "APP",
|
|
6
|
+
"title": "App/AppMod",
|
|
7
|
+
"items": [
|
|
8
|
+
"前端入口"
|
|
9
|
+
],
|
|
10
|
+
"cls": "blue"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "LOGIN",
|
|
14
|
+
"title": "登入",
|
|
15
|
+
"items": [
|
|
16
|
+
"SSO 驗證+取權限"
|
|
17
|
+
],
|
|
18
|
+
"cls": "red"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "LAYOUT",
|
|
22
|
+
"title": "Layout",
|
|
23
|
+
"items": [
|
|
24
|
+
"主畫面選單+各頁面元件"
|
|
25
|
+
],
|
|
26
|
+
"cls": "blue"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "CH",
|
|
30
|
+
"label": "$fapi 兩通道",
|
|
31
|
+
"cls": "greenG"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "WS",
|
|
35
|
+
"title": "即時資料介接",
|
|
36
|
+
"items": [
|
|
37
|
+
"全表推播 → Vuex Store"
|
|
38
|
+
],
|
|
39
|
+
"cls": "green",
|
|
40
|
+
"group": "CH"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "HTTP",
|
|
44
|
+
"title": "HTTP 檔案/批次",
|
|
45
|
+
"items": [
|
|
46
|
+
"upload",
|
|
47
|
+
"download",
|
|
48
|
+
"batchUploadCntr"
|
|
49
|
+
],
|
|
50
|
+
"cls": "green",
|
|
51
|
+
"group": "CH"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "STORE",
|
|
55
|
+
"title": "Vuex Store",
|
|
56
|
+
"items": [
|
|
57
|
+
"keyTable",
|
|
58
|
+
"syncState",
|
|
59
|
+
"loading"
|
|
60
|
+
],
|
|
61
|
+
"cls": "purple"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "SRV",
|
|
65
|
+
"label": "srv 主服務",
|
|
66
|
+
"cls": "orange"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": "BATSRV",
|
|
70
|
+
"label": "srvBatch 批次服務",
|
|
71
|
+
"cls": "orange"
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
"edges": [
|
|
75
|
+
{
|
|
76
|
+
"from": "APP",
|
|
77
|
+
"to": "LOGIN"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"from": "LOGIN",
|
|
81
|
+
"to": "LAYOUT"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"from": "LAYOUT",
|
|
85
|
+
"to": "WS",
|
|
86
|
+
"label": "通道一"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"from": "LAYOUT",
|
|
90
|
+
"to": "HTTP",
|
|
91
|
+
"label": "通道二"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"from": "WS",
|
|
95
|
+
"to": "STORE",
|
|
96
|
+
"label": "推播"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"from": "STORE",
|
|
100
|
+
"to": "LAYOUT",
|
|
101
|
+
"label": "讀取"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"from": "WS",
|
|
105
|
+
"to": "SRV"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"from": "HTTP",
|
|
109
|
+
"to": "BATSRV"
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dir": "LR",
|
|
3
|
+
"nodes": [
|
|
4
|
+
{
|
|
5
|
+
"id": "SRVS",
|
|
6
|
+
"label": "後端服務程式",
|
|
7
|
+
"cls": "orangeG"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "srv",
|
|
11
|
+
"title": "srv 主服務",
|
|
12
|
+
"items": [
|
|
13
|
+
"ORM CRUD",
|
|
14
|
+
"全表推播+擴充函數"
|
|
15
|
+
],
|
|
16
|
+
"cls": "orange",
|
|
17
|
+
"group": "SRVS"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "api",
|
|
21
|
+
"title": "srvApi 對外服務",
|
|
22
|
+
"items": [
|
|
23
|
+
"REST 每表 GET /api/表名"
|
|
24
|
+
],
|
|
25
|
+
"cls": "orange",
|
|
26
|
+
"group": "SRVS"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "bat",
|
|
30
|
+
"title": "srvBatch 批次服務",
|
|
31
|
+
"items": [
|
|
32
|
+
"批次任務",
|
|
33
|
+
"大檔分片"
|
|
34
|
+
],
|
|
35
|
+
"cls": "orange",
|
|
36
|
+
"group": "SRVS"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "prv",
|
|
40
|
+
"title": "srvProvide 推送服務",
|
|
41
|
+
"items": [
|
|
42
|
+
"排程每 30 秒推送"
|
|
43
|
+
],
|
|
44
|
+
"cls": "orange",
|
|
45
|
+
"group": "SRVS"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "FE",
|
|
49
|
+
"label": "前端應用",
|
|
50
|
+
"cls": "blue"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "EXT1",
|
|
54
|
+
"label": "外部應用系統",
|
|
55
|
+
"cls": "purple"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "DB",
|
|
59
|
+
"label": "資料庫(LMDB)",
|
|
60
|
+
"cls": "green"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "EXT2",
|
|
64
|
+
"label": "權限管理系統",
|
|
65
|
+
"cls": "purple"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "EXT3",
|
|
69
|
+
"label": "API 管理系統",
|
|
70
|
+
"cls": "purple"
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"edges": [
|
|
74
|
+
{
|
|
75
|
+
"from": "FE",
|
|
76
|
+
"to": "srv",
|
|
77
|
+
"label": "即時資料介接"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"from": "FE",
|
|
81
|
+
"to": "bat",
|
|
82
|
+
"label": "HTTP 分片"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"from": "EXT1",
|
|
86
|
+
"to": "api",
|
|
87
|
+
"label": "REST 取用詮釋資料"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"from": "srv",
|
|
91
|
+
"to": "DB"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"from": "api",
|
|
95
|
+
"to": "DB"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"from": "bat",
|
|
99
|
+
"to": "DB"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"from": "prv",
|
|
103
|
+
"to": "DB"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"from": "prv",
|
|
107
|
+
"to": "EXT2",
|
|
108
|
+
"label": "providePerm 推權限目標"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"from": "prv",
|
|
112
|
+
"to": "EXT3",
|
|
113
|
+
"label": "provideApi 推 API 說明"
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|