@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,271 +0,0 @@
1
- import type { DslActor, DslDocument } from "../types";
2
- import { 箱の題 } from "./node-title";
3
- import { slugify } from "./slug";
4
-
5
- /**
6
- * 路線図の形 (`shape: metro`, #2799) の配置規則。
7
- *
8
- * 担当 (`lane`) ごとに横の帯を 1 本作り、箱を駅として書いた順に左から並べる。
9
- * 担当を書かない箱は箱の名前を線路にする。省略したことは `compile.ts` の
10
- * `reportTrackFromName` が知らせる。
11
- */
12
-
13
- /**
14
- * 帯 1 本の高さ。見本の担当の線路の中心 (240 / 460 / 680) の差 220 に合わせる。
15
- */
16
- const TRACK_HEIGHT = 220;
17
-
18
- /**
19
- * 最初の駅の中心。
20
- * 見本の名札の左端 46 を描く側 0.125.0 の inset 24 に合わせると、lane の左端は 22 になる。
21
- * 見本の始まりの中心 290 から lane の左端 22 を引いた 268 に合わせる。
22
- * 名札 (24..224) と案内線の始まり (248) より右へ置く。
23
- */
24
- const FIRST_STATION_X = 268;
25
-
26
- /** 同じ線路の図形どうしに空ける 40。見本の駅と線を詰めすぎない値。 */
27
- const NODE_SHAPE_GAP = 40;
28
-
29
- /** 見本で始まりの中心 290 から最初の駅 380 まで離した 90。 */
30
- const START_MARK_CENTER_GAP = 90;
31
-
32
- /** 見本で「届けに行く」の中心 1410 から分かれ道「在宅?」の中心 1520 まで離した 110。 */
33
- const STATION_DECISION_CENTER_GAP = 110;
34
-
35
- /** 見本で最後の駅の中心 1690 から終わり 1752 まで離した 62。 */
36
- const END_MARK_CENTER_GAP = 62;
37
-
38
- /**
39
- * 担当が替わる 2 駅の間に足す 140。見本の中心間 360 から線路間 220 を引いた値。
40
- * 傾き ±1 の斜線の前後に横線と円弧を置く余裕になる。
41
- */
42
- const TRANSFER_MARGIN = 140;
43
-
44
- /**
45
- * 同じ担当で隣り合う駅名どうしの最小の隙間と、名前の幅の見積りに使う字の大きさ。
46
- * 25 は描く側 0.125.0 が駅名に使う字の大きさ。
47
- * 描く側が字の大きさを変えたら、ここも合わせる。
48
- */
49
- const TITLE_GAP = 27;
50
- /** 駅名を下へ回した組だけは、見本の中心間 130 まで詰められる。 */
51
- const BOTTOM_TITLE_GAP = 14.5;
52
- const STATION_TITLE_FONT_SIZE = 25;
53
- /** 太字の駅名が字面の外へ張り出す分を 3 と見積もる。 */
54
- const STATION_TITLE_BOLD_OVERHANG = 3;
55
-
56
- /** 見本の分かれ道の枝を、半幅 58 + 直線 30 + 半径 22 の曲がり 2 つ + 余白 22 で作る。 */
57
- const DECISION_HALF_WIDTH = 58;
58
- const BRANCH_STUB = 30;
59
- const BRANCH_CORNER_RADIUS = 22;
60
- const BRANCH_END_MARGIN = 22;
61
-
62
- /** 見本で分かれ道の中心 y=680 から真下の駅 y=910 まで離した 230。 */
63
- const DECISION_DOWN_GAP = 230;
64
-
65
- /**
66
- * 線路の右側に残す 26。
67
- * 見本の案内線は、終わりの中心 1752 より 2 先の x2=1754 で終わる。
68
- * 描く側 0.125.0 は `METRO_LANE_BADGE_INSET` の 24 を使い、案内線を lane の右端から 24 手前で止める。
69
- * 2 と 24 の和を使う。
70
- */
71
- const TRACK_END_PADDING = 26;
72
-
73
- type MetroNodeKind = "station" | "decision" | "mark-start" | "mark-end";
74
-
75
- /** 描く側 0.125.0 の路線図の図形の半幅。 */
76
- function nodeHalfWidth(kind: MetroNodeKind): number {
77
- if (kind === "decision") return DECISION_HALF_WIDTH;
78
- if (kind === "mark-start") return 17;
79
- if (kind === "mark-end") return 19;
80
- return 16;
81
- }
82
-
83
- /** 始まりと終わりの枠を、描く側が見せる丸の直径に合わせる。 */
84
- export function metroMarkFrameSize(kind: DslActor["kind"]): number | undefined {
85
- if (kind !== "mark-start" && kind !== "mark-end") return undefined;
86
- return nodeHalfWidth(kind) * 2;
87
- }
88
-
89
- /** 路線図では3種の印だけをその形で描き、それ以外の箱は駅として描く。 */
90
- function metroNodeKind(actor: DslActor): MetroNodeKind {
91
- if (actor.kind === "decision" || actor.kind === "mark-start" || actor.kind === "mark-end") {
92
- return actor.kind;
93
- }
94
- return "station";
95
- }
96
-
97
- export type MetroTrack = {
98
- id: string;
99
- label: string;
100
- subtitle?: string;
101
- posX: number;
102
- posY: number;
103
- posW: number;
104
- posH: number;
105
- };
106
-
107
- export type MetroStation = {
108
- actorName: string;
109
- trackId: string;
110
- posX: number;
111
- posY: number;
112
- };
113
-
114
- export type MetroPlacement = {
115
- width: number;
116
- tracks: MetroTrack[];
117
- stations: MetroStation[];
118
- };
119
-
120
- /** 担当を書かない箱を独立した線路にするため、その箱の名前を線路の鍵として返す。 */
121
- function 線路の鍵(actor: DslActor): string {
122
- return actor.lane ?? actor.name;
123
- }
124
-
125
- /** 駅名が重ならない間隔を取るため、描く側と同じ字の大きさで名前の幅を概算する。 */
126
- function estimateStationTitleWidth(title: string): number {
127
- if (title === "") return 0;
128
- return (
129
- [...title].reduce(
130
- (width, char) =>
131
- width +
132
- (/^[\x20-\x7e]$/u.test(char) ? STATION_TITLE_FONT_SIZE * 0.6 : STATION_TITLE_FONT_SIZE),
133
- 0,
134
- ) + STATION_TITLE_BOLD_OVERHANG
135
- );
136
- }
137
-
138
- /**
139
- * 路線図の幅と、線路・駅の絶対座標を返す。
140
- *
141
- * 静止図と動く図はどちらも `compileGenericWithAnimate` からここを呼び、同じ配置を使う。
142
- */
143
- export function placeMetro(doc: Pick<DslDocument, "actors" | "flow" | "lanes">): MetroPlacement {
144
- const usedTracks: string[] = [];
145
- for (const actor of doc.actors) {
146
- const track = 線路の鍵(actor);
147
- if (!usedTracks.includes(track)) usedTracks.push(track);
148
- }
149
-
150
- const declared = doc.lanes ? Object.keys(doc.lanes) : [];
151
- const trackOrder = [
152
- ...declared.filter((track) => usedTracks.includes(track)),
153
- ...usedTracks.filter((track) => !declared.includes(track)),
154
- ];
155
-
156
- const usedIds = new Set<string>();
157
- const trackIdByKey = new Map<string, string>();
158
- for (const [index, track] of trackOrder.entries()) {
159
- const base = `track-${slugify(track) || index}`;
160
- let id = base;
161
- let suffix = 2;
162
- while (usedIds.has(id)) id = `${base}-${suffix++}`;
163
- usedIds.add(id);
164
- trackIdByKey.set(track, id);
165
- }
166
-
167
- const actorByName = new Map(doc.actors.map((actor) => [actor.name, actor]));
168
- const 下へ置く = new Map<string, { decision: string; order: number }>();
169
- for (const decision of doc.actors.filter((actor) => metroNodeKind(actor) === "decision")) {
170
- let order = 0;
171
- const branches = doc.flow.filter((step) => step.from === decision.name);
172
- for (const branch of branches.slice(1)) {
173
- const target = actorByName.get(branch.to);
174
- if (
175
- target === undefined ||
176
- 線路の鍵(target) !== 線路の鍵(decision) ||
177
- metroNodeKind(target) !== "station" ||
178
- 下へ置く.has(target.name)
179
- ) {
180
- continue;
181
- }
182
- order += 1;
183
- 下へ置く.set(target.name, { decision: decision.name, order });
184
- }
185
- }
186
-
187
- const stations: MetroStation[] = [];
188
- const stationByActor = new Map<string, MetroStation>();
189
- let previousOnTrack: { actor: DslActor; station: MetroStation } | undefined;
190
- for (const [index, actor] of doc.actors.entries()) {
191
- const track = 線路の鍵(actor);
192
- const trackIndex = trackOrder.indexOf(track);
193
- const posY = trackIndex * TRACK_HEIGHT + TRACK_HEIGHT / 2;
194
- const below = 下へ置く.get(actor.name);
195
- const decisionStation = below === undefined ? undefined : stationByActor.get(below.decision);
196
- if (below !== undefined && decisionStation !== undefined) {
197
- const station = {
198
- actorName: actor.name,
199
- trackId: trackIdByKey.get(track) ?? `track-${index}`,
200
- posX: decisionStation.posX,
201
- posY: decisionStation.posY + DECISION_DOWN_GAP * below.order,
202
- };
203
- stations.push(station);
204
- stationByActor.set(actor.name, station);
205
- continue;
206
- }
207
-
208
- let posX = FIRST_STATION_X;
209
- if (previousOnTrack !== undefined) {
210
- const previousActor = previousOnTrack.actor;
211
- const previous = previousOnTrack.station;
212
- const previousKind = metroNodeKind(previousActor);
213
- const currentKind = metroNodeKind(actor);
214
- if (線路の鍵(previousActor) !== track) {
215
- const gap =
216
- previousKind === "decision"
217
- ? DECISION_HALF_WIDTH +
218
- BRANCH_STUB +
219
- BRANCH_CORNER_RADIUS * 2 +
220
- BRANCH_END_MARGIN +
221
- nodeHalfWidth(currentKind)
222
- : Math.abs(posY - previous.posY) + TRANSFER_MARGIN;
223
- posX = previous.posX + gap;
224
- } else {
225
- const shapeGap = previousKind === "mark-start" && currentKind === "station"
226
- ? START_MARK_CENTER_GAP
227
- : previousKind === "station" && currentKind === "decision"
228
- ? STATION_DECISION_CENTER_GAP
229
- : previousKind === "station" && currentKind === "mark-end"
230
- ? END_MARK_CENTER_GAP
231
- : nodeHalfWidth(previousKind) + nodeHalfWidth(currentKind) + NODE_SHAPE_GAP;
232
- const titleGap =
233
- previousKind === "station" && currentKind === "station"
234
- ? estimateStationTitleWidth(箱の題(previousActor)) / 2 +
235
- estimateStationTitleWidth(箱の題(actor)) / 2 +
236
- (actor.stationNamePosition === "bottom" ? BOTTOM_TITLE_GAP : TITLE_GAP)
237
- : 0;
238
- posX = previous.posX + Math.max(shapeGap, titleGap);
239
- }
240
- }
241
- const station = {
242
- actorName: actor.name,
243
- trackId: trackIdByKey.get(track) ?? `track-${index}`,
244
- posX,
245
- posY,
246
- };
247
- stations.push(station);
248
- stationByActor.set(actor.name, station);
249
- previousOnTrack = { actor, station };
250
- }
251
-
252
- const last = previousOnTrack;
253
- const titleEndPadding =
254
- last !== undefined && metroNodeKind(last.actor) === "station"
255
- ? estimateStationTitleWidth(箱の題(last.actor)) / 2 + 8
256
- : 0;
257
- const width = (last?.station.posX ?? 0) + Math.max(TRACK_END_PADDING, titleEndPadding);
258
- const tracks = trackOrder.map((track, index): MetroTrack => ({
259
- id: trackIdByKey.get(track) ?? `track-${index}`,
260
- label: doc.lanes?.[track]?.label ?? track,
261
- ...(doc.lanes?.[track]?.subtitle !== undefined
262
- ? { subtitle: doc.lanes[track].subtitle }
263
- : {}),
264
- posX: 0,
265
- posY: index * TRACK_HEIGHT,
266
- posW: width,
267
- posH: TRACK_HEIGHT,
268
- }));
269
-
270
- return { width, tracks, stations };
271
- }
@@ -1,393 +0,0 @@
1
- import { diagram } from "@cardenelabs/cdl";
2
- import type { CdlDiagram } from "@cardenelabs/cdl";
3
- import type { DslActor, DslDocument } from "../types";
4
- import { 図表の大きさ, 図表の大きさを読む } from "./chart-fields";
5
- import { 矢印から親を決める, 放射に出す文字 } from "./hierarchy";
6
- import type { CompileNotice } from "./notice";
7
- import { slugify } from "./slug";
8
- import { truncateForMessage, 図の小見出し } from "./subtitle";
9
- /**
10
- * 記法の `type: mind` を放射の図に直す (#2031 で `compile.ts` から移した)。
11
- *
12
- * 中心 1 つと枝の並びを 1 つの箱 (`mind-map`) に載せる。 枝の親は矢印で決まり、
13
- * 規則は `type: tree` と共有する (`矢印から親を決める`)。
14
- *
15
- * 放射だけが使う小道具もここへ一緒に移した。 `compile.ts` に残していた間、
16
- * 「放射で描けるか描けないか」 の割り当てが `compileMind` から 250 行離れており、
17
- * 欄を足す人が両方を見つけにくかった。
18
- */
19
-
20
- /**
21
- * 1 箱で描く放射が **描ける欄**。 これ以外は書いても出ない (#1177 Round 3)。
22
- *
23
- * 数え上げは 3 度直した。 副題 / 値 / 行 → 位置 / 大きさ → 種類 / 枠 / 積む順 …と、 見落とした
24
- * 欄が review のたびに出た。 数え漏らしても検査は通ってしまう = 「書いたのに出ない」 が黙って
25
- * 残る形が繰り返し発生した。
26
- *
27
- * そこで **`DslActor` の全ての欄を、 描ける側か描けない側のどちらかに必ず割り当てる**。
28
- * 欄が増えた時に両方へ入れ忘れると型検査が落ちるので、 「描けるのか描けないのか」 を必ず
29
- * 判断することになる (`rules/quality.md § 多層 SSOT 経路の全 registration 保証` と同じ形)。
30
- */
31
- type 放射で描ける欄 =
32
- /** 中心の名前 / 枝の名前になる */
33
- | "name"
34
- /** 名前と分けて中心 / 枝に出す題。 書かなければ名前を出す */
35
- | "title"
36
- /**
37
- * 名前とは分けて補足に出す 2 欄。
38
- *
39
- * `- 描く量を減らす: "{draw} ms"` の形は副題として解析されるため、 値の欄だけを見ると
40
- * 記法で最も普通な書き方が届かない (#1230 で実測)。 明示的に書いた値の欄も同じ場所へ出す。
41
- *
42
- * 描画側は枝の `subtitle` と中心の `rootSubtitle` を名前とは別の行に描く。 2 欄を
43
- * `放射に出す文字` で 1 つの補足にまとめ、 名前とは分けて渡す。
44
- */
45
- | "subtitle"
46
- | "value"
47
- /** 枝の色 (`MindBranchNode.tone`)。 中心は持てないので `描けない欄` が別に見る */
48
- | "tone"
49
- /** 見本は放射に載せず、 見本の中身だけを描く (別経路で伝える) */
50
- | "partId"
51
- /** 種類を書いたかどうかの印。 `kind` と対で見るので単独では扱わない */
52
- | "kindWritten"
53
- /** 本文の行番号。 知らせに載せるために使う */
54
- | "pos";
55
-
56
- /** 放射では描けない欄。 書かれていたら伝える */
57
- type 放射で描けない欄 =
58
- | "kind"
59
- | "kindForm"
60
- | "icon"
61
- | "emptyRowsNote"
62
- | "titleAlign"
63
- | "rowRules"
64
- | "rowDividerForm"
65
- | "rowWell"
66
- | "rowMarkForm"
67
- | "nestIn"
68
- | "figureCard"
69
- | "eyebrow"
70
- | "touchpoint"
71
- | "opportunity"
72
- | "opportunityPosition"
73
- | "owner"
74
- | "end"
75
- | "at"
76
- | "atPos"
77
- | "rows"
78
- | "marks"
79
- | "stage"
80
- | "lane"
81
- | "stack"
82
- | "initial"
83
- | "final"
84
- // 前の時点の値 (#1450)。 放射の枝は 1 時点しか描かない = 2 本目の帯に当たるものが無い
85
- | "previous"
86
- | "colorHex"
87
- // 部品に書いた色の名前 (#1973)。 色番号と同じく部品にしか残らない
88
- | "partColorName"
89
- | "stateOverride"
90
- | "posX"
91
- | "posY"
92
- | "posW"
93
- | "posH"
94
- | "scale"
95
- | "scaleKeys"
96
- | "posRel"
97
- | "layoutPos"
98
- // 箱の中に描く図形 (#1374)。 放射の枝は箱の中に図形を持たない
99
- | "shape"
100
- // 出す条件 (#1381)。 放射の枝は個別に出し分けられない
101
- | "visibleIf"
102
- // 値に追随する 5 欄 (#1392)。 放射の枝は大きさも位置も中心からの配置で決まる
103
- | "wBind"
104
- | "hBind"
105
- | "opacity"
106
- | "renderOffsetX"
107
- | "renderOffsetY"
108
- | "stationNamePosition"
109
- | "subtitlePlacement"
110
- | "titleFontSize"
111
- | "startLabel"
112
- | "milestone"
113
- | "emphasis"
114
- | "markGap"
115
- | "rowStripe"
116
- | "rowLayout";
117
-
118
- /** 引数が `never` でなければ型検査が落ちる */
119
- type 空であること<T extends never> = T;
120
-
121
- /** `DslActor` に割り当て漏れの欄があると落ちる */
122
- export type _放射の欄を覆えている = 空であること<
123
- Exclude<keyof DslActor, 放射で描ける欄 | 放射で描けない欄>
124
- >;
125
-
126
- /** `DslActor` に無い欄を割り当てていると落ちる */
127
- export type _放射の欄に余りがない = 空であること<
128
- Exclude<放射で描ける欄 | 放射で描けない欄, keyof DslActor>
129
- >;
130
-
131
- /** 描ける側と描けない側が重なっていると落ちる */
132
- export type _放射の欄が重なっていない = 空であること<Extract<放射で描ける欄, 放射で描けない欄>>;
133
-
134
- /**
135
- * 描けない欄と、 知らせに出す名前。
136
- *
137
- * **欄ごとに式を持たせない** (Round 5 の指摘)。 `{ 説明, 書いたか }` の形にすると、 項目名と式が
138
- * 型で結ばれず `scale: { 書いたか: () => false }` のように **判定を骨抜きにしても型検査が通る**。
139
- * 名前だけを持ち、 書かれていたかは下の 1 つの式で見る。
140
- *
141
- * `Record<放射で描けない欄, string>` なので、 欄を足すと項目も要る。 判定の側は欄ごとに書く所が
142
- * 無いため、 書き忘れも骨抜きも起きない。
143
- *
144
- * 表示の名前は複数の欄で同じでよい (`posX` と `posY` はどちらも「位置 (座標)」)。 出す時に
145
- * 重複を除く。
146
- */
147
- const 放射で描けない欄の名前: Record<放射で描けない欄, string> = {
148
- kind: "種類",
149
- kindForm: "部品箱の形",
150
- icon: "部品箱の線画",
151
- emptyRowsNote: "行のない表の注記",
152
- titleAlign: "表の題の位置",
153
- rowRules: "表の行の罫線",
154
- rowStripe: "表の行の縞",
155
- rowLayout: "表の題と行の位置",
156
- rowDividerForm: "表の見出し下の罫線",
157
- rowWell: "表の行の受け皿",
158
- rowMarkForm: "表の印の形",
159
- nestIn: "表の入れ子",
160
- figureCard: "表・部品箱の足",
161
- eyebrow: "上の小見出し",
162
- touchpoint: "場所 (ユーザージャーニーの欄)",
163
- opportunity: "改善の余地 (ユーザージャーニーの欄)",
164
- opportunityPosition: "改善の余地の位置 (ユーザージャーニーの欄)",
165
- owner: "担当 (工程の並びの欄)",
166
- end: "終わる時期 (工程の並びの欄)",
167
- startLabel: "始まりの札 (工程の並びの欄)",
168
- milestone: "節目 (工程の並びの欄)",
169
- emphasis: "主役 (工程の並びの欄)",
170
- at: "点の位置 (四象限の欄)",
171
- atPos: "点の位置 (四象限の欄)",
172
- rows: "行",
173
- marks: "印",
174
- stage: "段階",
175
- lane: "枠の指定",
176
- stack: "積む順",
177
- initial: "始まり / 終わり の印",
178
- final: "始まり / 終わり の印",
179
- previous: "前の時点の値",
180
- colorHex: "色番号",
181
- partColorName: "色の名前",
182
- stateOverride: "状態の上書き",
183
- // 位置は「登場人物ごとの箱をどこに置くか」 の指定で、 箱が 1 つの図では置く先が無い
184
- posX: "位置 (座標)",
185
- posY: "位置 (座標)",
186
- posW: "大きさ",
187
- posH: "大きさ",
188
- scale: "倍率",
189
- scaleKeys: "倍率",
190
- posRel: "位置 (相対)",
191
- layoutPos: "配置のずらし",
192
- shape: "箱の中の図形",
193
- visibleIf: "出す条件",
194
- wBind: "値に追随する大きさ",
195
- hBind: "値に追随する大きさ",
196
- opacity: "濃さ",
197
- renderOffsetX: "描く時のずらし",
198
- renderOffsetY: "描く時のずらし",
199
- stationNamePosition: "駅名の位置",
200
- subtitlePlacement: "副題の位置",
201
- titleFontSize: "題の字の大きさ",
202
- markGap: "印までの間隔",
203
- };
204
-
205
- /**
206
- * その欄が書かれていたか。 **全ての欄をこの 1 つの式で見る**。
207
- *
208
- * 欄ごとに式を持たせると、 1 つだけ骨抜きにしても型検査が通る (Round 5 の指摘)。 1 つにすれば
209
- * 骨抜きにした時点で全ての欄の検査が落ちる。
210
- *
211
- * 例外は種類だけ。 既定値 (`actor`) が必ず入るので、 書いたかどうかの印 (`kindWritten`) で見る。
212
- * 真偽を持つ欄 (`initial` / `final`) は `false` を「書いていない」 として扱う = 既定と同じ意味で、
213
- * 伝えると書いていない人にも出る。
214
- */
215
- function 放射で描けない欄を書いたか(a: DslActor, 欄: 放射で描けない欄): boolean {
216
- if (欄 === "kind") return a.kindWritten === true;
217
- const v = a[欄];
218
- if (typeof v === "boolean") return v;
219
- return v !== undefined;
220
- }
221
-
222
- /**
223
- * 記法の `type: mind` を engine の `mind-map` 種別の 1 箱に組む (#1177)。
224
- *
225
- * 以前は `card` を 3 列 (`mind-left` / `mind-center` / `mind-right`) に並べる別実装で、
226
- * engine の `mind-map` を使っていなかった。 絵は 3 列の箱から中心の放射に変わり、 破壊的変更
227
- * として `CHANGELOG` に記録している。
228
- *
229
- * 記法の `actors` は「1 つ目が中心、 残りが枝」 の並び。 枝の親は矢印で決まり (#1251)、
230
- * 書かなければ全ての枝を中心の直下に置く。 描けない欄と、 親にできなかった矢印は黙って
231
- * 捨てず、 書いた人に伝える。
232
- */
233
- export function compileMind(
234
- doc: DslDocument,
235
- onNotice?: (n: CompileNotice) => void,
236
- ): CdlDiagram {
237
- // 図の型は描画側の組み立て関数 `mindMap()` と同じ綴り (cdl 0.63.0 で `mindmap` から `mind` に揃った)
238
- const b = diagram(slugify(doc.title), { topic: doc.title, type: "mind" });
239
- const { w: W, h: H } = 図表の大きさを読む(doc, 図表の大きさ.mind);
240
-
241
- const 伝える = (kind: CompileNotice["kind"], 名: string, message: string, line = 0): void => {
242
- onNotice?.({ kind, actor: 名, line, message });
243
- if (typeof console !== "undefined" && console.warn) console.warn(`[dragon] ${message}`);
244
- };
245
-
246
- // **見本 (`parts`) を重ねた登場人物は中心にも枝にもしない** (review 指摘)。 後段の
247
- // `mergePartsFromActors` が見本の中身を別の箱として足すため、 こちらにも載せると同じ
248
- // 登場人物が 2 箇所に描かれる。 中心だけ外し忘れると、 中心が見本の記法で二重になる
249
- const 見本でない: DslActor[] = [];
250
- for (const a of doc.actors) {
251
- if (a.partId !== undefined) {
252
- 伝える(
253
- "part-not-drawn",
254
- a.name,
255
- `type: mind では見本 (${a.partId}) を中心にも枝にもできません。 見本はそのまま描き、 放射には載せません`,
256
- a.pos?.line ?? 0,
257
- );
258
- continue;
259
- }
260
- 見本でない.push(a);
261
- }
262
- // 枝の親は矢印で決まる (#1251)。 書かなければ全て中心の直下 = 従来と同じ図になる。
263
- // 規則は `type: tree` と共有する = 同じ本文で図種だけ変えた時に親子の解釈が割れない。
264
- //
265
- // **早期 return より前に置く** = 見本しか居ない記法で矢印を書いた時、 枝が 1 本も無いため
266
- // どの矢印も親にできない。 後ろに置くと、その形で矢印が黙って消える (Round 3 の指摘と同じ理由)
267
- const 枝の名前 = new Set(見本でない.map((a) => slugify(a.name)));
268
- const 親 = 矢印から親を決める(doc, "mind", 枝の名前, (名, message, line) =>
269
- // 親にできなかった矢印は描かれない。 知らせの種別も「矢印を落とした」 にする
270
- 伝える("chart-edge-dropped", 名, message, line ?? 0),
271
- );
272
-
273
- // 放射に載る登場人物が 0 人なら枠も作らない。 中身の無い枠が 1 つ残るのを避ける (#1096)。
274
- // **枠を作る前に見る** = 見本しか居ない記法で作ると、 見本だけが描かれた図に空の枠が残る
275
- if (見本でない.length === 0) return b.build();
276
-
277
- b.lane("chart", { width: W + 64 });
278
-
279
- // **同じ slug になる名前を先に見る** (`type: tree` と同じ理由)。 違う名前が同じ id に潰れると、
280
- // 枝が 1 本消えたり、 枝の親を見る規則が別の枝を指したりする
281
- const slug別 = new Map<string, string[]>();
282
- for (const a of 見本でない) {
283
- const k = slugify(a.name);
284
- slug別.set(k, [...(slug別.get(k) ?? []), a.name]);
285
- }
286
- for (const [k, 群] of slug別) {
287
- if (群.length > 1) {
288
- 伝える(
289
- "chart-value-unreadable",
290
- 群[0]!,
291
- `type: mind で ${群.join(" / ")} が同じ id (${k}) になります。 名前を変えてください`,
292
- );
293
- }
294
- }
295
-
296
- const root = 見本でない[0]!;
297
- const rootId = slugify(root.name) || "root";
298
-
299
- // **中心を子にする矢印は表せない** (#1251 Round 1 の指摘)。 中心は枝の並びに居ないため
300
- // 親を持てず、 解決はできても誰にも読まれずに消える。 黙って捨てると「書いたのに
301
- // 図が変わらない」 が手掛かりなしで起きるので、 行番号付きで伝える
302
- for (const f of doc.flow) {
303
- if (slugify(f.to) !== rootId) continue;
304
- 伝える(
305
- "chart-edge-dropped",
306
- f.to,
307
- `type: mind で中心 (${truncateForMessage(f.to)}) を子にはできません (${truncateForMessage(f.from)} -> ${truncateForMessage(f.to)} を使いません)。 中心は放射の真ん中に置く 1 つだけです`,
308
- f.pos?.line ?? 0,
309
- );
310
- }
311
-
312
- // **1 箱で描く種別が持てる欄は限られる**。 枝は名前と色、 中心は名前だけ。 書いても描けない
313
- // 欄は伝える = 箱ごとに描いていた頃は載っていた欄で、 黙って消すと「書いたのに出ない」 が残る
314
- const 描けない欄 = (a: DslActor): string[] => {
315
- const out: string[] = [];
316
- for (const 欄 of Object.keys(放射で描けない欄の名前) as 放射で描けない欄[]) {
317
- if (!放射で描けない欄を書いたか(a, 欄)) continue;
318
- const 名前 = 放射で描けない欄の名前[欄];
319
- // 同じ名前を持つ欄 (`posX` と `posY`) は 1 度だけ出す
320
- if (!out.includes(名前)) out.push(名前);
321
- }
322
- return out;
323
- };
324
- const 消えた欄 = new Map<string, string[]>();
325
- const 記録する = (a: DslActor): void => {
326
- const 欄 = 描けない欄(a);
327
- if (欄.length > 0) 消えた欄.set(a.name, 欄);
328
- };
329
-
330
- const branches: NonNullable<CdlDiagram["nodes"][number]["mindData"]>["branches"] = [];
331
- const 使った = new Set<string>([rootId]);
332
- 記録する(root);
333
- // 中心は色の欄を持たない (`MindBranchPayload` に `tone` が無い)
334
- if (root.tone)
335
- 消えた欄.set(root.name, [...(消えた欄.get(root.name) ?? []), "色 (中心は持てない)"]);
336
-
337
- 見本でない.slice(1).forEach((a, i) => {
338
- const id = slugify(a.name) || `leaf-${i}`;
339
- if (使った.has(id)) {
340
- 伝える(
341
- "chart-value-unreadable",
342
- a.name,
343
- `type: mind で ${a.name} が既にある id (${id}) と重なります (枝に載せません)`,
344
- a.pos?.line ?? 0,
345
- );
346
- return;
347
- }
348
- 使った.add(id);
349
- 記録する(a);
350
- // 矢印を書かなかった枝は中心の直下。 中心を親に指した矢印も同じ値になる
351
- // (中心は `見本でない` の先頭なので、 その名前の slug が `rootId` そのもの)
352
- // 枝は色を持てる (`MindBranchNode.tone`)
353
- branches.push({
354
- id,
355
- ...放射に出す文字(a),
356
- parent: 親.get(id) ?? rootId,
357
- ...(a.tone ? { tone: a.tone } : {}),
358
- });
359
- });
360
-
361
- if (消えた欄.size > 0) {
362
- const 一覧 = [...消えた欄].map(([名, 欄]) => `${名} の${欄.join(" / ")}`).join("、 ");
363
- 伝える(
364
- "chart-value-unreadable",
365
- [...消えた欄.keys()][0]!,
366
- `type: mind は名前と副題 / 値、 枝の色しか描けません (描かない欄: ${一覧})。 これらを描くなら type: tree か type: flow を使ってください`,
367
- );
368
- }
369
-
370
- b.node(`${slugify(doc.title) || "mind"}-chart`, {
371
- lane: "chart",
372
- stack: 0,
373
- kind: "mind-map",
374
- title: doc.title,
375
- ...図の小見出し(doc),
376
- w: W,
377
- h: H,
378
- mindData: (() => {
379
- const 中心 = 放射に出す文字(root);
380
- return {
381
- rootId,
382
- rootTitle: 中心.title,
383
- ...(中心.subtitle === undefined ? {} : { rootSubtitle: 中心.subtitle }),
384
- branches,
385
- };
386
- })(),
387
- ...(doc.mindForm !== undefined ? { mindForm: doc.mindForm } : {}),
388
- ...(doc.mindSize !== undefined ? { mindSize: doc.mindSize } : {}),
389
- ...(doc.figureKindLabel !== undefined ? { figureKindLabel: doc.figureKindLabel } : {}),
390
- ...(doc.terminalDecoration !== undefined ? { terminalDecoration: doc.terminalDecoration } : {}),
391
- });
392
- return b.build();
393
- }
@@ -1,24 +0,0 @@
1
- import type { DslActor } from "../types";
2
- /**
3
- * 箱に出す題を決める (#2030 で `compile.ts` から移した)。
4
- *
5
- * 15 図種の組み立て器が使う。 `compileC4` だけは自前で題を組む。
6
- */
7
-
8
- /** 題を出さない種類。 始まりと終わりの印は丸だけを描き、中に字を入れない */
9
- const 題を持たない種類: ReadonlySet<string> = new Set(["mark-start", "mark-end"]);
10
-
11
- /**
12
- * 箱に出す題 (#1381)。 書いていなければ名前をそのまま使う。
13
- *
14
- * 名前は図の中で 1 つに決まる必要がある (`focus:` と `flow:` が名前で指す) 一方、題は
15
- * 重なってよい。 同じ題の箱を並べる図と、題を持たない箱は、名前と切り離さないと書けない。
16
- *
17
- * **始まりと終わりの印は題を持たない** (#1466)。 塗った丸と輪で描くもので、名前を出す場所が
18
- * 無い。 名前は矢印の端として指すために要るので、名前をそのまま題にすると `begin` の字が
19
- * 丸の上に乗る (組み立て API 側の `.mark()` は題を空で作る)。 書いた題があればそれを使う。
20
- */
21
- export function 箱の題(a: DslActor): string {
22
- if (a.title === undefined && a.kind !== undefined && 題を持たない種類.has(a.kind)) return "";
23
- return a.title ?? a.name;
24
- }