@cardenelabs/dragon 0.34.0 → 0.35.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.
Files changed (127) hide show
  1. package/README.md +39 -720
  2. package/dist/cli.cjs +4529 -0
  3. package/dist/cli.cjs.map +1 -0
  4. package/dist/cli.js +4523 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/export.cjs +3912 -0
  7. package/dist/export.cjs.map +1 -0
  8. package/dist/export.d.cts +36 -0
  9. package/dist/export.d.ts +36 -0
  10. package/dist/export.js +3907 -0
  11. package/dist/export.js.map +1 -0
  12. package/dist/fonts.cjs +7 -0
  13. package/dist/fonts.cjs.map +1 -0
  14. package/dist/fonts.js +4 -0
  15. package/dist/fonts.js.map +1 -0
  16. package/dist/index.cjs +4105 -20356
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +227 -5522
  19. package/dist/index.d.ts +227 -5522
  20. package/dist/index.js +4091 -20283
  21. package/dist/index.js.map +1 -1
  22. package/dist/player.global.js +2 -0
  23. package/dist/schema-BBWfniwH.d.cts +112 -0
  24. package/dist/schema-BBWfniwH.d.ts +112 -0
  25. package/package.json +39 -24
  26. package/src/cli.ts +237 -0
  27. package/src/export/capture.ts +277 -0
  28. package/src/export/fonts.ts +68 -0
  29. package/src/export/html.ts +32 -0
  30. package/src/export/index.ts +146 -0
  31. package/src/field-messages.ts +104 -0
  32. package/src/figure-text.ts +42 -0
  33. package/src/index.ts +30 -283
  34. package/src/layout/architecture.ts +381 -0
  35. package/src/layout/measure.ts +21 -0
  36. package/src/layout/scene.ts +353 -0
  37. package/src/layout/sequence.ts +189 -0
  38. package/src/load-fonts.ts +8 -0
  39. package/src/look/fonts.ts +33 -0
  40. package/src/look/palettes.json +209 -0
  41. package/src/look/values.ts +155 -0
  42. package/src/markdown.ts +35 -0
  43. package/src/messages.ts +122 -0
  44. package/src/parser.ts +342 -310
  45. package/src/player-global.ts +4 -0
  46. package/src/player.ts +271 -0
  47. package/src/printer.ts +76 -0
  48. package/src/render/icons.ts +41 -0
  49. package/src/render/motion.ts +106 -0
  50. package/src/render/parts.ts +279 -0
  51. package/src/render/svg.ts +341 -0
  52. package/src/schema.ts +161 -16
  53. package/src/schemas/diagram.schema.json +150 -0
  54. package/src/schemas/validate-diagram.d.ts +12 -0
  55. package/src/schemas/validate-diagram.js +8 -0
  56. package/src/timeline.ts +166 -0
  57. package/src/tokenize.ts +20 -281
  58. package/src/version.ts +55 -0
  59. package/src/view.ts +16 -0
  60. package/src/words.ts +79 -0
  61. package/examples/quick-start.md +0 -142
  62. package/src/canvas-bounds.ts +0 -52
  63. package/src/color.ts +0 -197
  64. package/src/compile/actor-ids.ts +0 -297
  65. package/src/compile/actor-option-notice.ts +0 -379
  66. package/src/compile/actors.ts +0 -103
  67. package/src/compile/c4.ts +0 -74
  68. package/src/compile/chain.ts +0 -93
  69. package/src/compile/chart-fields.ts +0 -193
  70. package/src/compile/deep-rewrite.ts +0 -30
  71. package/src/compile/derived-source.ts +0 -18
  72. package/src/compile/direction.ts +0 -102
  73. package/src/compile/edge-option-notice.ts +0 -112
  74. package/src/compile/er-relation.ts +0 -200
  75. package/src/compile/flow.ts +0 -54
  76. package/src/compile/flowchart.ts +0 -344
  77. package/src/compile/funnel.ts +0 -87
  78. package/src/compile/gantt.ts +0 -337
  79. package/src/compile/generic.ts +0 -696
  80. package/src/compile/hierarchy.ts +0 -92
  81. package/src/compile/journey.ts +0 -93
  82. package/src/compile/kinds.ts +0 -139
  83. package/src/compile/lanes.ts +0 -138
  84. package/src/compile/metro.ts +0 -271
  85. package/src/compile/mind.ts +0 -393
  86. package/src/compile/node-title.ts +0 -24
  87. package/src/compile/notice.ts +0 -141
  88. package/src/compile/order.ts +0 -57
  89. package/src/compile/parts-lookup.ts +0 -17
  90. package/src/compile/parts.ts +0 -2709
  91. package/src/compile/placement-apply.ts +0 -689
  92. package/src/compile/placement.ts +0 -15
  93. package/src/compile/quadrant.ts +0 -265
  94. package/src/compile/record.ts +0 -75
  95. package/src/compile/row-marks.ts +0 -128
  96. package/src/compile/rows.ts +0 -40
  97. package/src/compile/sequence.ts +0 -305
  98. package/src/compile/slug.ts +0 -37
  99. package/src/compile/stages.ts +0 -247
  100. package/src/compile/subtitle.ts +0 -35
  101. package/src/compile/swimlane-marks.ts +0 -87
  102. package/src/compile/swimlane.ts +0 -82
  103. package/src/compile/timeline.ts +0 -213
  104. package/src/compile/topology.ts +0 -44
  105. package/src/compile/tree.ts +0 -69
  106. package/src/compile/value-chart.ts +0 -337
  107. package/src/compile/values.ts +0 -432
  108. package/src/compile/word-state.ts +0 -82
  109. package/src/compile.ts +0 -3168
  110. package/src/focus.ts +0 -46
  111. package/src/input-size.ts +0 -181
  112. package/src/json-line-map.ts +0 -30
  113. package/src/json-parser.ts +0 -4306
  114. package/src/keywords.ts +0 -417
  115. package/src/near-name.ts +0 -42
  116. package/src/notation-lint.ts +0 -342
  117. package/src/relative-pos.ts +0 -251
  118. package/src/schemas/diagram.json +0 -3088
  119. package/src/types.ts +0 -1009
  120. package/src/v05/index.ts +0 -9
  121. package/src/v05/input-table.generated.ts +0 -138
  122. package/src/v05/parser-types.ts +0 -32
  123. package/src/v05/parser.ts +0 -6668
  124. package/src/v05/readout-table.generated.ts +0 -1088
  125. package/src/v05/removed-syntax.ts +0 -115
  126. package/src/value-syntax.ts +0 -313
  127. package/src/write-position.ts +0 -270
@@ -1,297 +0,0 @@
1
- import type { CdlDiagram } from "@cardenelabs/cdl";
2
- import type { DslActor, DslDocument, PresetType } from "../types";
3
- import { 図種の作り } from "./kinds";
4
- import type { CompileNotice } from "./notice";
5
- import { ID_MAX, slugify } from "./slug";
6
- import { truncateForMessage } from "./subtitle";
7
- /**
8
- * 名前の重なりを解く (#2036 で `compile.ts` から移した)。
9
- *
10
- * 違う名前が同じ id に潰れると、箱が 1 つ消えたり、矢印が別の箱を指したりする。
11
- * 潰れる前に名前を作り替え、図を組み立てた後で元へ戻す。
12
- *
13
- * 外へ出す口は 3 つ。 作り替える `disambiguateActorIds`、作り替えた相手を控える
14
- * `collectRenamedTargets`、元へ戻す `restoreActorNames`。 いずれも入口が呼ぶ。
15
- */
16
-
17
- /**
18
- * 名前から作る id の重なりを解く (#1220)。
19
- *
20
- * 箱と枠の id は登場人物の名前から作る (`slugify`)。 **違う名前が同じ id に潰れる** と、
21
- * どちらも正しく書いているのに図が組み立たない (実測 = `foo-bar` と `Foo Bar` を書くと
22
- * 9 図種が `duplicate-id` で落ちる)。 知らせも出ない = どちらの名前も `actors` に在るため。
23
- *
24
- * ## なぜ名前を作り替えるのか
25
- *
26
- * id を作る所は 90 箇所を超え、 さらに **cdl 側の組み立てが名前から id を作る経路** がある
27
- * (`swimlane()` / `er()` は渡した名札から lane id を作る)。 dragon 側だけを直しても届かない。
28
- *
29
- * そこで **渡す名前を変え、 最後に表示だけ戻す**。 作り替えた名前は組み立ての間だけ使い、
30
- * 出口で `nodes[].title` と `lanes[].label` を元に戻す (`restoreActorNames`)。
31
- *
32
- * ## 尾は名前から作る
33
- *
34
- * 書き順で決めると、 登場人物を並べ替えただけで id が入れ替わる。 元の名前だけから決まる
35
- * 短い値を尾に付ければ、 並べ替えても同じ id になる。
36
- *
37
- * ## まったく同じ名前は畳む
38
- *
39
- * 名前が 1 文字も違わない登場人物は区別できない。 2 つの箱に同じ題が付くだけなので、
40
- * 先に書いた方を残して知らせる。
41
- */
42
- function 名前の尾(name: string): string {
43
- // FNV-1a。 短くて名前だけから決まればよく、 衝突しても下の検査が拾う
44
- let h = 0x811c9dc5;
45
- for (const c of name) {
46
- h ^= c.codePointAt(0) ?? 0;
47
- h = Math.imul(h, 0x01000193) >>> 0;
48
- }
49
- return h.toString(16).padStart(8, "0").slice(0, 6);
50
- }
51
-
52
- /**
53
- * cdl 側が名札から id を作る時の規則 (`presets.ts` の `slugify`)。
54
- *
55
- * **dragon の規則と違う**。 dragon は `-` と `_` を残し `NFKC` で揃え 64 字で切るが、 cdl は
56
- * どちらも `-` に潰し、 長さも切らない。 このため `a_b` と `a-b` は **dragon では別 id、 cdl では
57
- * 同じ id** になる (実測 = 動きを書かない `sequence` / `solidity` が `duplicate-id` で落ちる)。
58
- *
59
- * ここに写している = cdl は `slugify` を公開していない。 **ずれると衝突を見落とす** ので、
60
- * 下の検査が既知の組で対応を固定する。
61
- */
62
- function cdl側のslug(s: string): string {
63
- return s
64
- .toLowerCase()
65
- .replace(/[^a-z0-9぀-ゟ゠-ヿ一-龯]+/g, "-")
66
- .replace(/^-|-$/g, "");
67
- }
68
-
69
- /**
70
- * cdl 側が「形が空になった名前」 に付ける id の頭 (#1220 Round 2 / 3)。
71
- *
72
- * cdl は形が空になった時に並びの位置へ逃げる。 逃げ先を鍵に入れないと、 逃げ先と同じ名前の
73
- * 登場人物が居る図で重なる (実測 = `sequence` の `😀` と `actor-0`)。
74
- *
75
- * **図種ごとに違う** (Round 3 の指摘)。 両方を入れると、 その図種では使われない逃げ先まで
76
- * 衝突とみなして **重なっていない図の id を変える**。 実測した対応は次のとおり。
77
- *
78
- * | 図種 | `😀` だけを書いた時に付く id |
79
- * |---|---|
80
- * | `sequence` | 枠 `actor-0` (cdl の `sequence()`) |
81
- * | `swimlane` | 枠 `lane-0` (cdl の `swimlane()`) |
82
- * | 残る 5 図種 | 箱 `n` (dragon 側の逃げ先。 cdl の逃げ道を通らない) |
83
- *
84
- * 空配列は「cdl の逃げ道を通らない」 を表す。 1 箱で描く図種はここに来ない (作り替え自体を
85
- * しない) が、 図種を足した時に決め忘れないよう全種を並べる。
86
- *
87
- * **この表が効くのは動きを書いていない図だけ** (Round 4 の指摘)。 動きを書くと組み立てが別経路に
88
- * 切り替わり、 id は dragon 側の規則で作られる = cdl の逃げ道を通らない。 使う側で条件を見る。
89
- */
90
- const 空の形の逃げ先: Record<PresetType, string[]> = {
91
- sequence: ["actor-"],
92
- swimlane: ["lane-"],
93
- // 分かれ道の図も縦列を持ち、 描画側は縦列の名前から箱を作る (`swimlane` と同じ逃げ先)
94
- flowchart: ["lane-"],
95
- flow: [],
96
- record: [],
97
- topology: [],
98
- c4: [],
99
- // 図全体を 1 箱で描く群 (作り替えないのでここは使わない)
100
- gantt: [],
101
- chart: [],
102
- funnel: [],
103
- tree: [],
104
- journey: [],
105
- quadrant: [],
106
- mind: [],
107
- };
108
-
109
- /**
110
- * その名前が下流で id になりうる形。 どれか 1 つでも重なれば衝突する。
111
- *
112
- * 位置と逃げ先が分からない時 (作り替えた候補を確かめる時) は逃げ先を数えない。 作り替えた
113
- * 名前は尾が付いて形が空にならないので、 そもそも逃げ道を通らない。
114
- */
115
- function idになる形(name: string, 位置?: number, 逃げ先の頭: string[] = []): string[] {
116
- const cdl = cdl側のslug(name);
117
- const out = [slugify(name), cdl];
118
- if (cdl === "" && 位置 !== undefined) {
119
- for (const 頭 of 逃げ先の頭) out.push(`${頭}${位置}`);
120
- }
121
- return out;
122
- }
123
-
124
- export function disambiguateActorIds(
125
- doc: DslDocument,
126
- onNotice?: (notice: CompileNotice) => void,
127
- ): { doc: DslDocument; 元の名前: Map<string, string> } {
128
- const 元の名前 = new Map<string, string>();
129
- if (図種の作り[doc.type] !== "登場人物ごとに箱") return { doc, 元の名前 };
130
-
131
- // 1. まったく同じ名前を畳む
132
- const 見た = new Set<string>();
133
- const 残す: DslActor[] = [];
134
- for (const a of doc.actors) {
135
- if (見た.has(a.name)) {
136
- onNotice?.({
137
- kind: "chart-value-unreadable",
138
- actor: a.name,
139
- line: a.pos?.line ?? 0,
140
- message: `"${truncateForMessage(a.name)}" を 2 度書いています (先に書いた方だけ描きます)`,
141
- hint: "違う名前にするか、 1 つにまとめる",
142
- });
143
- continue;
144
- }
145
- 見た.add(a.name);
146
- 残す.push(a);
147
- }
148
-
149
- // 2. どの名前が重なるかを見る。 **両方の規則で見る** = 片方だけだと cdl 側の経路で落ちる
150
- const 形ごとの名前 = new Map<string, Set<string>>();
151
- const 位置 = new Map<string, number>();
152
- 残す.forEach((a, i) => 位置.set(a.name, i));
153
- // **動きを書いた図では cdl の逃げ道を通らない** (Round 4 の指摘)。 動きがあると組み立てが
154
- // 別経路 (`compileGenericWithAnimate` / `compileSequenceWithAnimate`) に切り替わり、 id は
155
- // dragon 側の規則で作られる (実測 = `😀` は 枠 `n` / `lane-n` になり、 位置を使わない)。
156
- //
157
- // 逃げ先を鍵に入れたままにすると、 その経路で **重なっていない図の id を変える**。
158
- // dragon 側の規則で作る分は、 1 つ目の鍵 (`slugify`) が既に見ている。
159
- const 動きを書いた = (doc.animate?.phases.length ?? 0) > 0;
160
- const 逃げ先の頭 = 動きを書いた ? [] : 空の形の逃げ先[doc.type];
161
- for (const a of 残す) {
162
- for (const 形 of idになる形(a.name, 位置.get(a.name), 逃げ先の頭)) {
163
- const 群 = 形ごとの名前.get(形) ?? new Set<string>();
164
- 群.add(a.name);
165
- 形ごとの名前.set(形, 群);
166
- }
167
- }
168
- const 重なる = (name: string): boolean =>
169
- idになる形(name, 位置.get(name), 逃げ先の頭).some(
170
- (形) => (形ごとの名前.get(形)?.size ?? 0) > 1,
171
- );
172
-
173
- // **見本 (`parts`) を重ねた登場人物は作り替えない**。 見本の中身は `別名__元の id` の形で
174
- // 名前空間を持ち、 別名は名前から作るため、 作り替えると見本の id が総入れ替えになる。
175
- //
176
- // ただし **重なりの判定には数える** (Round 1 の指摘)。 数えないと、 素の登場人物と見本が
177
- // 同じ id の仮置きを共有し、 見本を片付ける時に素の登場人物の箱まで消える。
178
- const 作り替える = 残す.filter((a) => a.partId === undefined && 重なる(a.name));
179
-
180
- // 3. 名前から決まる尾を付ける。 **できあがる id が一意になるまで見る**
181
- //
182
- // 尾は元の名前だけから決まる = 並べ替えても同じ id になる。 それでも重なる時 (尾そのものが
183
- // 重なる形) は、 名前を並べ替えた順で番号を足す = ここも書き順に依らない
184
- const 使う形 = new Set<string>();
185
- for (const a of 残す) {
186
- if (作り替える.some((b) => b.name === a.name)) continue;
187
- for (const 形 of idになる形(a.name)) 使う形.add(形);
188
- }
189
- const 新しい名前 = new Map<string, string>();
190
- // 名前で並べてから配る = 書いた順に依らない
191
- for (const a of [...作り替える].sort((x, y) =>
192
- x.name < y.name ? -1 : x.name > y.name ? 1 : 0,
193
- )) {
194
- const 尾 = 名前の尾(a.name);
195
- // **id の長さの上限のぶん、 元の名前を先に切る** (Round 1 の指摘)。 切らないと尾が
196
- // 64 字で落ちて、 同じ頭を持つ長い名前どうしが元のまま重なる
197
- const 余地 = ID_MAX - (尾.length + 1);
198
- const 基底 = a.name.slice(0, 余地);
199
- let 候補 = `${基底} ${尾}`;
200
- let n = 0;
201
- while (idになる形(候補).some((形) => 使う形.has(形))) {
202
- n += 1;
203
- 候補 = `${基底.slice(0, 余地 - String(n).length)} ${尾}${n}`;
204
- }
205
- for (const 形 of idになる形(候補)) 使う形.add(形);
206
- 新しい名前.set(a.name, 候補);
207
- 元の名前.set(候補, a.name);
208
- }
209
-
210
- if (新しい名前.size === 0 && 残す.length === doc.actors.length) return { doc, 元の名前 };
211
-
212
- const 直す = (名: string): string => 新しい名前.get(名) ?? 名;
213
- const 次: DslDocument = {
214
- ...doc,
215
- actors: 残す.map((a) => (新しい名前.has(a.name) ? { ...a, name: 直す(a.name) } : a)),
216
- flow: doc.flow.map((s) => {
217
- const from = 直す(s.from);
218
- const to = 直す(s.to);
219
- return from === s.from && to === s.to ? s : { ...s, from, to };
220
- }),
221
- events: doc.events?.map((event) => {
222
- const target = event.target;
223
- if (target.kind === "node") {
224
- const name = 直す(target.name);
225
- return name === target.name ? event : { ...event, target: { ...target, name } };
226
- }
227
- if (target.kind === "edge") {
228
- const from = 直す(target.from);
229
- const to = 直す(target.to);
230
- return from === target.from && to === target.to
231
- ? event
232
- : { ...event, target: { ...target, from, to } };
233
- }
234
- // sequence 系の縦列は登場人物から作るため、同じ名前の読み替えが必要。
235
- if (target.kind === "lane" && doc.type === "sequence") {
236
- const name = 直す(target.name);
237
- return name === target.name ? event : { ...event, target: { ...target, name } };
238
- }
239
- return event;
240
- }),
241
- };
242
- // 光らせる指定と位置の基準も名前で書くので、 同じ表で直す
243
- if (次.animate) {
244
- 次.animate = {
245
- ...次.animate,
246
- phases: 次.animate.phases.map((ph) =>
247
- ph.highlight ? { ...ph, highlight: ph.highlight.map((h) => 直す(h)) } : ph,
248
- ),
249
- };
250
- }
251
- 次.actors = 次.actors.map((a) =>
252
- a.posRel ? { ...a, posRel: { ...a.posRel, anchor: 直す(a.posRel.anchor) } } : a,
253
- );
254
- return { doc: 次, 元の名前 };
255
- }
256
-
257
- /**
258
- * 作り替えた名前を持つ箱と枠を控える (#1220)。
259
- *
260
- * **組み立て直後に控える** (Round 1 の指摘)。 出口で題の文字だけを見て戻すと、 後から足された
261
- * 見本の中の箱がたまたま同じ題を持っていた時に、 その表示まで書き換えてしまう。
262
- */
263
- export function collectRenamedTargets(
264
- diagram: CdlDiagram,
265
- 元の名前: Map<string, string>,
266
- ): { 箱: Set<string>; 枠: Set<string> } {
267
- const 箱 = new Set<string>();
268
- const 枠 = new Set<string>();
269
- if (元の名前.size === 0) return { 箱, 枠 };
270
- for (const n of diagram.nodes) if (元の名前.has(n.title)) 箱.add(n.id);
271
- for (const l of diagram.lanes) if (l.label !== undefined && 元の名前.has(l.label)) 枠.add(l.id);
272
- return { 箱, 枠 };
273
- }
274
-
275
- /**
276
- * 作り替えた名前を、 図の表示だけ元に戻す (#1220)。
277
- *
278
- * 戻すのは題と名札だけ。 id は作り替えたまま = 分けるために作り替えたので、 戻すと元の
279
- * 重なりに帰る。
280
- */
281
- export function restoreActorNames(
282
- diagram: CdlDiagram,
283
- 元の名前: Map<string, string>,
284
- 対象: { 箱: Set<string>; 枠: Set<string> },
285
- ): void {
286
- if (元の名前.size === 0) return;
287
- for (const n of diagram.nodes) {
288
- if (!対象.箱.has(n.id)) continue;
289
- const 元 = 元の名前.get(n.title);
290
- if (元 !== undefined) n.title = 元;
291
- }
292
- for (const l of diagram.lanes) {
293
- if (!対象.枠.has(l.id) || l.label === undefined) continue;
294
- const 元 = 元の名前.get(l.label);
295
- if (元 !== undefined) l.label = 元;
296
- }
297
- }
@@ -1,379 +0,0 @@
1
- /**
2
- * 箱に書いた指定が効かない時の、伝える欄の割り出しと呼び名 (#2368 で 1 箇所にまとめた)。
3
- *
4
- * 同じ判定を図種の族ごとの経路が使う (経路の数は下の表ではなく実装が SSOT)。
5
- *
6
- * | 経路 | 図種 | 起点 |
7
- * |---|---|---|
8
- * | 板の面 | `sequence` | #2358 |
9
- * | 木の図 | `tree` | #2360 |
10
- * | 値の図とじょうご | `pie` ほか 9 図種 + `funnel` | #2368 |
11
- * | ガントチャートと体験の地図と四象限 | `gantt` / `journey` / `quadrant` | #2370 |
12
- * | 骨組みの図 | `flow` / `flowchart` / `swimlane` / `topology` / `c4` / `record` | #2376 / #2382 / #2782 |
13
- *
14
- * 矢印の側は `edge-option-notice.ts` が同じ形を持つ (#2366)。
15
- */
16
- import type { DslActor } from "../types";
17
- import type { DslDiagramShape } from "../keywords";
18
-
19
- /**
20
- * 効かない箱の欄の呼び名 (#2358)。 **並べた順に知らせへ出す**。
21
- *
22
- * 呼び名を持たない欄は名前をそのまま出すので、ここに足し忘れても知らせは消えない。
23
- * 複数の欄が 1 つの呼び名を持つ形 (位置が 4 欄) は、1 度だけ出す。
24
- *
25
- * **図種をまたいで 1 つの表にする** (#2360)。 同じ欄を図種ごとに別の文言で呼ぶと、
26
- * 図種を変えた読み手が同じ指定だと気付けない。 どの欄を伝えるかは図種ごとの除外が決める。
27
- *
28
- * **箱に無い欄名を書けないようにする** (#2384)。 表と除外は箱の欄と文字で突き合わせるので、
29
- * 綴りが箱と違う行は静かに何とも一致しない。 実測 = 記法の `色:` に引きずられて書いた
30
- * `color` が、箱に生まれない欄名のまま残っていた (箱に入るのは `colorHex` か `tone`)。
31
- */
32
- export const 効かない箱の欄の呼び名 = [
33
- ["kind", "種類"],
34
- // 呼び名 (#2390)。 板 / 木 / 骨組みの図は箱に描くので、伝えるのは値として読む図だけ
35
- ["subtitle", "呼び名"],
36
- ["posW", "大きさ"],
37
- ["posH", "大きさ"],
38
- ["posX", "位置"],
39
- ["posY", "位置"],
40
- ["posRel", "位置"],
41
- // 位置のずらし (#1971)。 板は面ごとの箱を持たないので動かす相手が無い
42
- ["layoutPos", "位置"],
43
- ["rows", "行"],
44
- ["tone", "色"],
45
- ["colorHex", "色"],
46
- ["eyebrow", "小見出し"],
47
- ["value", "値"],
48
- ["shape", "図形"],
49
- ["previous", "前の値"],
50
- ["marks", "印"],
51
- ["stack", "段"],
52
- ["initial", "始まりの印"],
53
- ["final", "終わりの印"],
54
- ["visibleIf", "出す条件"],
55
- ["opacity", "透け具合"],
56
- ["wBind", "値への追随"],
57
- ["hBind", "値への追随"],
58
- ["renderOffsetX", "値への追随"],
59
- ["renderOffsetY", "値への追随"],
60
- ["owner", "担当"],
61
- ["stage", "段階"],
62
- ["end", "終わる時期"],
63
- ["startLabel", "始まりの札"],
64
- ["milestone", "節目"],
65
- ["emphasis", "主役"],
66
- ["at", "点の位置"],
67
- ["atPos", "点の位置"],
68
- ["touchpoint", "接点"],
69
- ["opportunity", "伸びしろ"],
70
- ["opportunityPosition", "伸びしろの位置"],
71
- ] as const satisfies readonly (readonly [keyof DslActor, string])[];
72
-
73
- /**
74
- * 体験と工程の欄 (#2380)。 **どの族の除外にも入る**。
75
- *
76
- * 担当 (`owner`) と終わる時期 (`end`) はガントチャートが、接点 (`touchpoint`) と伸びしろ
77
- * (`opportunity`) と伸びしろの位置 (`opportunityPosition`) は体験の地図が描く。 描けない図種で書いた時は
78
- * `reportChartFieldsNotHonored` が図種ごとの行き先を添えて伝える。
79
- *
80
- * 箱の知らせが同じ欄を重ねて伝えると、1 つの欄が 2 つの名前で呼ばれる (「担当」 と `owner`)
81
- * うえ、案内が食い違う = 箱の知らせは「箱を持つ図種へ」 と言うが、担当は箱を持つ図種でも
82
- * 効かない。
83
- *
84
- * **1 か所に置いて各族が読む**。 欄を族ごとに書き並べると、新しい欄を足した日に
85
- * 直し忘れた族だけが 2 件並べるようになる (実測 = 板の族だけが 4 欄を持たず、
86
- * `sequence` / `solidity` の 8 通りで知らせが 2 件並んでいた)。
87
- */
88
- export const 体験と工程の欄: ReadonlySet<string> = new Set([
89
- "owner",
90
- "end",
91
- "startLabel",
92
- "milestone",
93
- "emphasis",
94
- "touchpoint",
95
- "opportunity",
96
- "opportunityPosition",
97
- ]);
98
-
99
- /**
100
- * 板の面で、効かないと伝えない箱の欄 (#2358)。
101
- *
102
- * **除外側を書く**。 効かない欄を並べる形にすると、箱に欄を足した日にその欄だけが
103
- * 一覧から漏れ、書いた人には「書いたのに何も起きない」 としか見えない。
104
- *
105
- * | 区分 | 欄 |
106
- * |---|---|
107
- * | 板が描く | 名前 / 題 / 呼び名 と、書いた場所と種類を書いたかの印 |
108
- * | 別の知らせが受け持つ | 縦列 (`reportLaneNotHonored`) / 体験と工程の 4 欄 (#2380) |
109
- *
110
- * 種類 (`kind`) は図種で分かれるので、判定の側で足す。
111
- */
112
- export const 板が伝えない箱の欄: ReadonlySet<string> = new Set([
113
- "name",
114
- "title",
115
- "subtitle",
116
- "pos",
117
- "kindWritten",
118
- "lane",
119
- ...体験と工程の欄,
120
- ]);
121
-
122
- /**
123
- * 木の図で、効かないと伝えない箱の欄 (#2360)。
124
- *
125
- * | 区分 | 欄 |
126
- * |---|---|
127
- * | 木の図が描く | 名前 / 題 / 呼び名 / 値 と、書いた場所と種類を書いたかの印 |
128
- * | 別の知らせが受け持つ | 縦列 / 位置のずらし / 体験と工程の 4 欄 |
129
- *
130
- * 別の知らせは順に `lane-not-honored` / `position-offset-ignored` / `chart-value-unreadable`。
131
- * 除かないと同じ箱に 2 件並ぶ。 体験と工程の 4 欄は `体験と工程の欄` から読む (#2380)。
132
- */
133
- export const 木の図が伝えない箱の欄: ReadonlySet<string> = new Set([
134
- "name",
135
- "title",
136
- "subtitle",
137
- "value",
138
- "pos",
139
- "kindWritten",
140
- "lane",
141
- "layoutPos",
142
- ...体験と工程の欄,
143
- ]);
144
-
145
- /**
146
- * 箱を名前と値の組として読む図で、どの図種でも効かないと伝えない箱の欄 (#2368 / #2370)。
147
- *
148
- * | 区分 | 欄 |
149
- * |---|---|
150
- * | どの図種も読む | 名前 / 題 / 値 と、書いた場所と種類を書いたかの印 |
151
- * | 別の知らせが受け持つ | 縦列 / 位置のずらし / 体験と工程の 4 欄 |
152
- *
153
- * 呼び名 (`subtitle`) は除かない (#2390)。 値を書いていない箱で **値として読まれる**
154
- * (`a.value ?? a.subtitle`) ので除いていたが、**値を書いた箱では読む先が無い**。
155
- * 除いた結果、値と呼び名を両方書いた箱では誰も伝えず、13 図種で黙って消えていた。
156
- * 値を書いていない箱で鳴らさない判定は呼び元が持つ = 箱 1 つごとに決まる条件なので、
157
- * 図種ごとの除外では表せない (#2384 の色番号と同じ形)。
158
- *
159
- * 位置 (`posX` / `posY`) は除かない = この図種では両方書いても効かないため、
160
- * 片方だけの知らせ (#2362) ではなくこちらが伝える。
161
- *
162
- * 記法の `offsetX` / `offsetY` は `layoutPos` に入る (`position-offset-ignored` が受け持つ)。
163
- * 欄の名前で判定するので、記法の項目名ではなく入った先の欄を書く。
164
- *
165
- * 体験と工程の欄 (`体験と工程の欄`) は、読む図種と別の知らせが受け持つ図種に分かれる。
166
- * どちらも伝えないので 1 つにまとめて除く。
167
- */
168
- const 値として読む図に共通の除外: ReadonlySet<string> = new Set([
169
- "name",
170
- "title",
171
- "value",
172
- "pos",
173
- "kindWritten",
174
- "lane",
175
- "layoutPos",
176
- ...体験と工程の欄,
177
- ]);
178
-
179
- /**
180
- * 箱を名前と値の組として読む図種と、共通に加えて読む欄 (#2368 / #2370)。
181
- *
182
- * **表で持つ**。 図種ごとに除外の集合を丸ごと書くと、共通部分が図種の数だけ写され、
183
- * 1 つ直した日に他が古いまま残る (実測 = 値の図とじょうごで 2 つに分かれていた)。
184
- *
185
- * | 図種 | 共通に加えて読む欄 |
186
- * |---|---|
187
- * | 数を描く図 (`chart`) | 色味 (`tone`) / 前の値 (`previous`) |
188
- * | じょうご | 色味 (`tone`) |
189
- * | 体験の地図 / 四象限 | 無し (名前と値だけを読む) |
190
- * | ガントチャート | 色味 (`tone`)。 担当と終わる時期は共通の除外に入っている |
191
- */
192
- export const 値として読む図種: ReadonlyMap<string, readonly string[]> = new Map([
193
- ["chart", ["tone", "previous"]],
194
- ["funnel", ["tone"]],
195
- ["gantt", ["tone"]],
196
- ["journey", []],
197
- ["quadrant", ["at", "atPos"]],
198
- ]);
199
-
200
- /**
201
- * 件ごとの主役 (`emphasis`) を実際に描く値の図。
202
- *
203
- * `chart` という node の種類だけでは円・折れ線なども混ざるため、`chart/stacked` まで
204
- * 形を含めて区別する。じょうごは形を持たず、段の `emphasis` を読む。
205
- */
206
- const 主役を読む値の図: ReadonlySet<string> = new Set(["chart/stacked", "funnel"]);
207
-
208
- /** 件ごとの `emphasis` がその図で描かれるか */
209
- export function 値として読む図が主役を読む(図種: string, 形?: DslDiagramShape): boolean {
210
- return 主役を読む値の図.has(形 === undefined ? 図種 : `${図種}/${形}`);
211
- }
212
-
213
- /**
214
- * その図種で、効かないと伝えない箱の欄を返す (#2370)。
215
- *
216
- * @param 図種 `値として読む図種` に載っている図種
217
- * @returns 共通の除外に、その図種が読む欄を足した集合
218
- */
219
- export function 値として読む図が伝えない箱の欄(図種: string): ReadonlySet<string> {
220
- const 読む欄 = 値として読む図種.get(図種) ?? [];
221
- if (読む欄.length === 0) return 値として読む図に共通の除外;
222
- return new Set([...値として読む図に共通の除外, ...読む欄]);
223
- }
224
-
225
- /**
226
- * 箱を並べて線で繋ぐ図 (骨組みの図) で、どの図種でも効かないと伝えない箱の欄 (#2376)。
227
- *
228
- * | 区分 | 欄 |
229
- * |---|---|
230
- * | 骨組みの図が描く | 名前 / 題 / 呼び名 / 小見出し / 値 / 行 / 色味 / 図形 / 種類 / 段 / 透け具合 / 出す条件 |
231
- * | 位置と大きさ | `posX` / `posY` / `posW` / `posH` / `posRel` / `layoutPos` |
232
- * | 値への追随 | `wBind` / `hBind` / `renderOffsetX` / `renderOffsetY` |
233
- * | 別の知らせが受け持つ | 縦列 / 体験と工程の 4 欄 |
234
- *
235
- * 色番号 (`colorHex`) は除かない (#2384)。 部品を置いた箱でだけ効く欄なので
236
- * 「部品の側が伝えるから重ねない」 として除いていたが、**呼び元が部品を置いた箱を
237
- * 判定の入口で飛ばす** (`a.partId !== undefined`) ため重なりようがなかった。
238
- * 除いた結果、部品でない箱では誰も伝えず、7 図種で黙って消えていた。
239
- *
240
- * 記法の `色:` は 16 進数なら `colorHex` に、色の名前なら `tone` に入る。
241
- * 名前で書いた形は箱の色として効くので、伝えるのは 16 進数で書いた形だけになる。
242
- *
243
- * 印 (`marks`) は除かない (#2377)。 行頭の記号に読み替えるのは ER 図と状態遷移図だけ
244
- * (`行頭の印にする`) で、骨組みの残りの図種では行と組にしても効かない。
245
- * 効く図種で鳴らさない判定は呼び元が持つ = 図種の一覧をここに写すと、印を読む図種を
246
- * 足した日に古くなる。
247
- *
248
- * 残るのは始まりの印 / 終わりの印 / 印 / 前の値の 4 つ。
249
- * 実測 = フロー 447 枚 ・ 泳路の図 35 枚 ・ 配置の図 8 枚の見本すべてで、この 4 件が
250
- * 図も変えず知らせも出さなかった。
251
- *
252
- * **図種ごとの違いは `骨組みの図種` が持つ** (#2382)。 状態遷移図は始まりの印と終わりの印を
253
- * 読むので、その 2 欄だけ除外に足す。
254
- */
255
- const 骨組みの図に共通の除外: ReadonlySet<string> = new Set([
256
- "name",
257
- "title",
258
- "subtitle",
259
- "pos",
260
- "kindWritten",
261
- "value",
262
- "eyebrow",
263
- "tone",
264
- "rows",
265
- // cdl 0.129.0-0.136.0 の表箱・部品箱の欄。 applyV05Extensions が node へ渡し、
266
- // 描き手が箱の種類に応じて題・罫線・印・入れ子・線画・足として読む (#2854)。
267
- "kindForm",
268
- "icon",
269
- "emptyRowsNote",
270
- "titleAlign",
271
- "rowRules",
272
- "rowStripe",
273
- "rowLayout",
274
- "rowDividerForm",
275
- "rowWell",
276
- "rowMarkForm",
277
- "nestIn",
278
- "figureCard",
279
- "shape",
280
- "kind",
281
- "stack",
282
- "opacity",
283
- "visibleIf",
284
- "posX",
285
- "posY",
286
- "posW",
287
- "posH",
288
- "posRel",
289
- "layoutPos",
290
- "wBind",
291
- "hBind",
292
- "renderOffsetX",
293
- "renderOffsetY",
294
- "lane",
295
- ...体験と工程の欄,
296
- ]);
297
-
298
- /**
299
- * 図種ごとの、共通の除外との違い (#2382)。
300
- *
301
- * **両方向に持つ**。 共通の除外は「どの骨組みの図も読む欄」 を集めたものなので、
302
- * 図種ごとの違いは 2 通り出る。 外す側だけだと、1 つの図種だけが読む欄を持つ図種を
303
- * 族に入れられない (実測 = 状態遷移図が始まりの印を読むため、族の外に置かれていた)。
304
- */
305
- export type 骨組みの図の違い = {
306
- /** その図種だけが読まない欄。 共通の除外から外して、伝える側へ回す */
307
- readonly 読まない?: readonly string[];
308
- /** その図種だけが読む欄。 共通の除外に足して、伝えない側へ回す */
309
- readonly 読む?: readonly string[];
310
- };
311
-
312
- /**
313
- * 箱を並べて線で繋ぐ図種と、共通の除外との違い (#2376 / #2382)。
314
- *
315
- * | 図種 | 違い | 理由 |
316
- * |---|---|---|
317
- * | フロー / 泳路の図 / 配置の図 | 無し | `compile/generic.ts` が共通の経路で箱を作る |
318
- * | 分かれ道の図 (`flowchart`) | 無し | 種類は専用の形または箱の見た目として読む |
319
- * | 構成の図 (`c4`) | 段 (`stack`) を読まない | `compile/c4.ts` が `a.stack` を読まない |
320
- * | 行を持つ図 (`record`) | 始まりの印と終わりの印を読む | `compile/generic.ts` の札がこの図種でだけ出る |
321
- */
322
- export const 骨組みの図種: ReadonlyMap<string, 骨組みの図の違い> = new Map([
323
- ["flow", {}],
324
- ["flowchart", {}],
325
- ["swimlane", {}],
326
- ["topology", {}],
327
- ["c4", { 読まない: ["stack"] }],
328
- ["record", { 読む: ["initial", "final"] }],
329
- ]);
330
-
331
- /**
332
- * その図種で、効かないと伝えない箱の欄を返す (#2376 / #2382)。
333
- *
334
- * @param 図種 `骨組みの図種` に載っている図種
335
- * @returns 共通の除外から読まない欄を抜き、読む欄を足した集合
336
- */
337
- export function 骨組みの図が伝えない箱の欄(図種: string): ReadonlySet<string> {
338
- const 違い = 骨組みの図種.get(図種) ?? {};
339
- const 読まない欄 = 違い.読まない ?? [];
340
- const 読む欄 = 違い.読む ?? [];
341
- if (読まない欄.length === 0 && 読む欄.length === 0) return 骨組みの図に共通の除外;
342
- const 残り = new Set(骨組みの図に共通の除外);
343
- for (const 欄 of 読まない欄) 残り.delete(欄);
344
- for (const 欄 of 読む欄) 残り.add(欄);
345
- return 残り;
346
- }
347
-
348
- /**
349
- * 箱に書いた欄のうち、効かないものの呼び名を並べる (#2368)。
350
- *
351
- * **伝える欄を並べず、読む欄を除いた残り全部を返す**。 効かない欄を並べる形にすると、
352
- * 箱に欄を足した日にその欄だけが一覧から漏れ、書いた人には「書いたのに何も起きない」
353
- * としか見えない。 除外側を書けば、足し忘れは「余計に知らせる」 側へ倒れる。
354
- *
355
- * @param 箱 記法から読んだ箱 1 つ
356
- * @param 伝えない欄 その図種が読む欄と、別の知らせが受け持つ欄
357
- * @param 判定で外す欄 図種の中でさらに外す欄 (種類が図種で分かれる形、#2358)
358
- * @returns 知らせに出す呼び名。 空なら伝えるものが無い
359
- */
360
- export function 効かない箱の欄を並べる(
361
- 箱: object,
362
- 伝えない欄: ReadonlySet<string>,
363
- 判定で外す欄: readonly string[] = [],
364
- ): string[] {
365
- const 残り = new Set(
366
- Object.entries(箱 as Record<string, unknown>)
367
- .filter(([欄, 値]) => 値 !== undefined && !伝えない欄.has(欄))
368
- .map(([欄]) => 欄),
369
- );
370
- for (const 欄 of 判定で外す欄) 残り.delete(欄);
371
- const 効かない: string[] = [];
372
- for (const [欄, 呼び名] of 効かない箱の欄の呼び名) {
373
- if (!残り.delete(欄)) continue;
374
- if (!効かない.includes(呼び名)) 効かない.push(呼び名);
375
- }
376
- // 呼び名を持たない欄は名前をそのまま出す = 読みにくい名前でも、黙って落とすよりは伝わる
377
- 効かない.push(...[...残り].sort());
378
- return 効かない;
379
- }