@cardenelabs/dragon 0.26.0 → 0.28.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/README.md +42 -8
- package/dist/index.cjs +1417 -369
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +225 -113
- package/dist/index.d.ts +225 -113
- package/dist/index.js +1416 -370
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/compile.ts +1572 -303
- package/src/index.ts +5 -3
- package/src/json-parser.ts +171 -71
- package/src/notation-lint.ts +112 -74
- package/src/schemas/diagram.json +92 -30
- package/src/types.ts +59 -40
- package/src/v05/input-table.generated.ts +6 -4
- package/src/v05/parser-types.ts +12 -1
- package/src/v05/parser.ts +412 -77
package/src/compile.ts
CHANGED
|
@@ -150,6 +150,18 @@ export type CompileNotice = {
|
|
|
150
150
|
| "direction-not-honored"
|
|
151
151
|
// `倍率:` を書いた見本が、同じ名前の状態も持っていた (#1026)
|
|
152
152
|
| "scale-reserved"
|
|
153
|
+
// 部品に書いた色が効かない (#1973)。 色番号を入れる状態を 1 つも持たない部品に色番号を
|
|
154
|
+
// 書いた時と、色番号でなく色の名前を書いた時
|
|
155
|
+
| "part-color-ignored"
|
|
156
|
+
// 部品に、部品が持たない状態の名前で値を書いた (#1976)。 綴り違いと、外した欄 (`nodes`) を書いた時
|
|
157
|
+
| "part-state-missing"
|
|
158
|
+
// 部品へ引いた矢印を、部品の中のどの要素にも繋げず外した (#1979)。 要素が 2 つ以上あり名指しが無い時、
|
|
159
|
+
// 名指しした要素が部品に無い時、部品を取り込まなかった時
|
|
160
|
+
| "part-edge-dropped"
|
|
161
|
+
// 矢印に部品の要素の名指し (`fromPartNode` / `toPartNode`) を書いたが、その端が部品でない (#1979)
|
|
162
|
+
| "part-node-ignored"
|
|
163
|
+
// 縦列に置くはずの部品が他の箱の位置の基準になっていて、縦列に置けなかった (#1980)
|
|
164
|
+
| "part-lane-ignored"
|
|
153
165
|
// 値で描く図 (`pie` / `bar` / `line`) で値を読めなかった (#1154)
|
|
154
166
|
| "chart-value-unreadable"
|
|
155
167
|
// 同上で矢印を書いた。 これらの図は関係を描けない (#1154)
|
|
@@ -182,10 +194,14 @@ export type CompileNotice = {
|
|
|
182
194
|
| "event-target-missing"
|
|
183
195
|
// 順序図で面に種類を書いたが、板は名前と呼び名しか描かない (#1466)
|
|
184
196
|
| "actor-kind-not-honored"
|
|
185
|
-
// 箱の中の小さな箱に位置を書いたが、その名前の箱が図に無かった (#1466)
|
|
186
|
-
| "sub-node-not-found"
|
|
187
197
|
// 順序図の言づてに、板が描かない飾り (色味 / 添え字 / 寄せ) を書いた (#1466)
|
|
188
|
-
| "message-option-not-honored"
|
|
198
|
+
| "message-option-not-honored"
|
|
199
|
+
// 位置のずらし (`pos` / `offsetX` / `offsetY`) を載せる相手が無いか、書いた量だけ動かせなかった (#1971)
|
|
200
|
+
| "position-offset-ignored"
|
|
201
|
+
// 組 (`groups:`) が束ねる縦列が図に無い (#1972)
|
|
202
|
+
| "group-lane-missing"
|
|
203
|
+
// 組が束ねる縦列の間に、束ねない縦列を挟んでいる (#1972)
|
|
204
|
+
| "group-lanes-apart";
|
|
189
205
|
/** 対象の名前。 光らせる相手なら書かれた指定そのまま */
|
|
190
206
|
actor: string;
|
|
191
207
|
/** 書かれていた行 */
|
|
@@ -226,7 +242,7 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
226
242
|
diagram = compileSequence(doc);
|
|
227
243
|
break;
|
|
228
244
|
case "flow":
|
|
229
|
-
diagram = compileFlow(doc);
|
|
245
|
+
diagram = compileFlow(doc, opts?.partsCatalog);
|
|
230
246
|
break;
|
|
231
247
|
case "swimlane":
|
|
232
248
|
diagram = compileSwimlane(doc);
|
|
@@ -306,8 +322,11 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
306
322
|
// edge と本文の行の対応は表に集めてから 1 edge = 1 回で知らせる (#998)。 経路ごとに
|
|
307
323
|
// その場で呼ぶと、 同じ edge に別の行を 2 度知らせることになる。
|
|
308
324
|
const edgeSourceLines = opts?.onEdgeSource ? new Map<string, number>() : undefined;
|
|
309
|
-
|
|
310
|
-
|
|
325
|
+
// 矢印ごとに、どの行から来たか。 部品へ引いた矢印を部品の要素へ繋ぎ直す時に、その行に書いた
|
|
326
|
+
// 名指し (`toPartNode`) を読む (#1979)。 矢印の id は部品の取り込みで変わらない
|
|
327
|
+
const 矢印の行 = new Map<string, DslStep>();
|
|
328
|
+
applyEdgeInlineOptions(diagram, doc, edgeSourceLines, 矢印の行, opts?.partsCatalog);
|
|
329
|
+
const 作った組の枠 = applyGroupContainers(diagram, doc);
|
|
311
330
|
applyNodeTones(diagram, doc);
|
|
312
331
|
// 光らせる相手が実在するかを確かめる。 id への解決は図種ごとに違うが、 名前が居るか
|
|
313
332
|
// 居ないかは記述だけで決まるので 1 か所で見る
|
|
@@ -317,19 +336,21 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
317
336
|
// 両端が同じ矢印を伝える (#1227)。 落とす前の `flow` を見る
|
|
318
337
|
// 静止した `type: flow` で書いた矢印の端が使われないことを伝える (#1269)。
|
|
319
338
|
// 自分へ戻る形と居ない名前を指す形は既に落ちた後の `doc` を見る = 上の 2 件と重ねない
|
|
320
|
-
reportFlowEndpointNotHonored(doc, 分けた.元の名前, opts?.onNotice);
|
|
339
|
+
reportFlowEndpointNotHonored(doc, 分けた.元の名前, opts?.onNotice, opts?.partsCatalog);
|
|
321
340
|
reportLaneNotHonored(書いたまま, opts?.onNotice);
|
|
322
341
|
reportActorKindNotHonored(書いたまま, opts?.onNotice);
|
|
323
342
|
reportMessageOptionNotHonored(書いたまま, opts?.onNotice);
|
|
343
|
+
reportFlowOffsetNotHonored(書いたまま, opts?.onNotice);
|
|
324
344
|
reportDocEyebrowNotHonored(書いたまま, opts?.onNotice);
|
|
325
345
|
reportDirectionNotHonored(書いたまま, opts?.onNotice);
|
|
326
346
|
reportDrawNotHonored(書いたまま, opts?.onNotice);
|
|
327
347
|
reportChartFieldsNotHonored(書いたまま, opts?.onNotice);
|
|
328
348
|
reportAxesNotHonored(書いたまま, opts?.onNotice);
|
|
349
|
+
reportPartNodeNotHonored(書いたまま, opts?.onNotice);
|
|
329
350
|
// `位置: Web の右` を実際の配置から絶対座標に直す。 以降は座標を直接書いた時と同じ経路
|
|
330
351
|
const placed = resolveRelativeDoc(diagram, doc, opts?.onNotice, opts?.partsCatalog);
|
|
331
352
|
// canvas pivot 新 spec = 全 preset 共通の post-process で actor.posX/Y を CDL lane / node に伝播
|
|
332
|
-
applyCanvasPivotPositions(diagram, placed
|
|
353
|
+
applyCanvasPivotPositions(diagram, placed);
|
|
333
354
|
// CAR-1657 = parts kind actor を merge (opts.partsCatalog 経由)、 applyV05Extensions 後段で実行
|
|
334
355
|
const 追加した縦列: DslLane[] = [];
|
|
335
356
|
const extended = applyV05Extensions(diagram, placed, 追加した縦列);
|
|
@@ -345,6 +366,7 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
345
366
|
opts?.partsCatalog,
|
|
346
367
|
opts?.onNotice,
|
|
347
368
|
inheritedDerivedSourceLines,
|
|
369
|
+
矢印の行,
|
|
348
370
|
);
|
|
349
371
|
// 箱が 1 つも入らなかった縦列を伝える (#1241)。
|
|
350
372
|
//
|
|
@@ -385,6 +407,7 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
385
407
|
* どれかを見落とす (`injectStaticPhase` / `materializeStates` と同じ理由)。
|
|
386
408
|
*/
|
|
387
409
|
if (doc.reveal !== undefined) merged.edgeReveal = doc.reveal;
|
|
410
|
+
if (doc.relations !== undefined) merged.relationFocus = doc.relations;
|
|
388
411
|
// 語の欄が状態を読むとき、その状態には記法の語が入っている。 図の語へ直す (#1201)
|
|
389
412
|
語の状態を図の語へ直す(merged);
|
|
390
413
|
// きっかけ形の値を段の時計を読む式へ畳む (#1161 段 2)。 **値を載せるより先に呼ぶ** =
|
|
@@ -425,6 +448,8 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
425
448
|
const ownFormulas = doc.formulas.map((formula) => ({
|
|
426
449
|
id: formula.id,
|
|
427
450
|
expression: formula.expression,
|
|
451
|
+
// 名札は操作部が式の名前として描く (#1916、cdl#853)。 書かなければ欄ごと付けない
|
|
452
|
+
...(formula.label !== undefined ? { label: formula.label } : {}),
|
|
428
453
|
line: formula.pos?.line ?? 0,
|
|
429
454
|
}));
|
|
430
455
|
/*
|
|
@@ -484,7 +509,11 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
484
509
|
});
|
|
485
510
|
}
|
|
486
511
|
if (解けない) continue;
|
|
487
|
-
載せる式.push({
|
|
512
|
+
載せる式.push({
|
|
513
|
+
id: formula.id,
|
|
514
|
+
expression: formula.expression,
|
|
515
|
+
...(formula.label !== undefined ? { label: formula.label } : {}),
|
|
516
|
+
});
|
|
488
517
|
先に書かれた式.add(formula.id);
|
|
489
518
|
}
|
|
490
519
|
if (載せる式.length > 0) merged.formulas = 載せる式;
|
|
@@ -551,6 +580,12 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
551
580
|
// 20 か所以上あり、そのどれに足しても残りが取り残される
|
|
552
581
|
配色と縞を当てる(merged, doc);
|
|
553
582
|
静止した図の焦点を外す(merged, doc);
|
|
583
|
+
// 位置のずらしは **配置に効く欄を全て載せた後** に当てる (#1971)。 縦列の幅や視点の間隔を
|
|
584
|
+
// 足す前に測ると、後から足された分だけ狙いがずれる
|
|
585
|
+
applyLayoutOffsets(merged, doc, opts?.onNotice);
|
|
586
|
+
// 組の枠は **縦列の位置が全て決まった後** に置く (#1972)。 位置のずらしより前に置くと、
|
|
587
|
+
// ずらした縦列から枠が離れる
|
|
588
|
+
applyGroupFrames(merged, doc, 作った組の枠, opts?.onNotice);
|
|
554
589
|
return merged;
|
|
555
590
|
}
|
|
556
591
|
|
|
@@ -907,7 +942,7 @@ function reportDrawNotHonored(doc: DslDocument, onNotice?: (n: CompileNotice) =>
|
|
|
907
942
|
* 段の `draw:` が効く図種 (#1312 / #1314 / #1318)。
|
|
908
943
|
*
|
|
909
944
|
* **語と図種の対応表から導く** (`DRAW_TARGETS`)。 一覧を写すと、語を足した時に片方だけ
|
|
910
|
-
* 古いまま残る。 描画側 (`cdl` の `
|
|
945
|
+
* 古いまま残る。 描画側 (`cdl` の `DRAW_KINDS`) が対応する種別と一致する。
|
|
911
946
|
*/
|
|
912
947
|
const DRAWABLE_DOC_TYPES: ReadonlySet<PresetType> = new Set<PresetType>(DRAW_TARGETS.values());
|
|
913
948
|
|
|
@@ -946,7 +981,8 @@ function reportLaneMixed(doc: DslDocument, onNotice: (n: CompileNotice) => void)
|
|
|
946
981
|
* ## 鎖にすること自体は変えない
|
|
947
982
|
*
|
|
948
983
|
* 線形の流れを描く図種なので、鎖にするのは仕様。 書いた端どおりに繋ぎたい形は
|
|
949
|
-
* 箱に `lane:`
|
|
984
|
+
* 箱に `lane:` を書くか (#1266)、`direction:` を書けば別の経路へ回る (#1494)。 知らせの hint で
|
|
985
|
+
* それを案内する。 `direction: 縦` を先に挙げるのは、箱ごとに書かずに済み、並びも鎖の時と変わらないため。
|
|
950
986
|
*
|
|
951
987
|
* ## 偶然一致する形では知らせない
|
|
952
988
|
*
|
|
@@ -964,6 +1000,8 @@ function reportFlowEndpointNotHonored(
|
|
|
964
1000
|
doc: DslDocument,
|
|
965
1001
|
元の名前: Map<string, string>,
|
|
966
1002
|
onNotice?: (n: CompileNotice) => void,
|
|
1003
|
+
/** 鎖に並べない部品を決める一覧 (#1987)。 `compileFlow` に渡したものと同じ */
|
|
1004
|
+
partsCatalog?: Record<string, CdlDiagram>,
|
|
967
1005
|
): void {
|
|
968
1006
|
if (!onNotice) return;
|
|
969
1007
|
if (!鎖でつなぐ形か(doc)) return;
|
|
@@ -975,8 +1013,9 @@ function reportFlowEndpointNotHonored(
|
|
|
975
1013
|
// 鎖が作る組を集める。 登場人物が 1 人以下なら矢印が 1 本も出来ないので、
|
|
976
1014
|
// 書いた矢印は全て使われない扱いになる
|
|
977
1015
|
const 鎖の組 = new Set<string>();
|
|
978
|
-
doc
|
|
979
|
-
|
|
1016
|
+
const 並び = 鎖に並べる登場人物(doc, partsCatalog);
|
|
1017
|
+
並び.forEach((a, i) => {
|
|
1018
|
+
const 次 = 並び[i + 1];
|
|
980
1019
|
if (次 === undefined) return;
|
|
981
1020
|
鎖の組.add(`${a.name}\u0000${次.name}`);
|
|
982
1021
|
});
|
|
@@ -987,7 +1026,7 @@ function reportFlowEndpointNotHonored(
|
|
|
987
1026
|
actor: 書いた名前(s.from),
|
|
988
1027
|
line: s.pos.line,
|
|
989
1028
|
message: `"${truncateForMessage(書いた名前(s.from))} -> ${truncateForMessage(書いた名前(s.to))}" の端は使われません (type: flow は登場人物を書いた順に繋ぎます)`,
|
|
990
|
-
hint: "
|
|
1029
|
+
hint: "書いた端どおりに繋ぐには、direction: 縦 を書くか箱に lane: を書いてください (向きか縦列を書いた形は書いた端がそのまま矢印になります)",
|
|
991
1030
|
});
|
|
992
1031
|
}
|
|
993
1032
|
}
|
|
@@ -1039,6 +1078,10 @@ function reportMessageOptionNotHonored(doc: DslDocument, onNotice?: (n: CompileN
|
|
|
1039
1078
|
s.tone !== undefined ? "色味" : "",
|
|
1040
1079
|
s.sub !== undefined ? "添え字" : "",
|
|
1041
1080
|
s.side !== undefined ? "寄せ" : "",
|
|
1081
|
+
// 名前のずらしも板には載せる先が無い (#1971)。 位置のずらし (`pos`) は名前のずらしに足す欄なので同じ扱い
|
|
1082
|
+
s.layoutPos !== undefined || s.labelOffsetX !== undefined || s.labelOffsetY !== undefined
|
|
1083
|
+
? "名前のずらし"
|
|
1084
|
+
: "",
|
|
1042
1085
|
].filter((x) => x !== "");
|
|
1043
1086
|
if (効かない.length === 0) continue;
|
|
1044
1087
|
onNotice({
|
|
@@ -1051,6 +1094,28 @@ function reportMessageOptionNotHonored(doc: DslDocument, onNotice?: (n: CompileN
|
|
|
1051
1094
|
}
|
|
1052
1095
|
}
|
|
1053
1096
|
|
|
1097
|
+
/**
|
|
1098
|
+
* 名前の無い矢印に書いた位置のずらしを知らせる (#1971)。
|
|
1099
|
+
*
|
|
1100
|
+
* 矢印の位置のずらしは名前をずらす欄に足す。 名前が無い矢印では動くものが無く、書いても図は
|
|
1101
|
+
* 変わらない。 順序図の板は `reportMessageOptionNotHonored` が知らせるので、ここでは見ない。
|
|
1102
|
+
*/
|
|
1103
|
+
function reportFlowOffsetNotHonored(doc: DslDocument, onNotice?: (n: CompileNotice) => void): void {
|
|
1104
|
+
if (!onNotice) return;
|
|
1105
|
+
if (doc.type === "sequence" || doc.type === "solidity") return;
|
|
1106
|
+
for (const s of doc.flow) {
|
|
1107
|
+
if (s.layoutPos === undefined) continue;
|
|
1108
|
+
if (s.label !== undefined && s.label.trim() !== "") continue;
|
|
1109
|
+
onNotice({
|
|
1110
|
+
kind: "position-offset-ignored",
|
|
1111
|
+
actor: s.from,
|
|
1112
|
+
line: s.pos?.line ?? 0,
|
|
1113
|
+
message: `${s.from} -> ${s.to} の位置のずらしは、名前の無い矢印では動かすものがありません`,
|
|
1114
|
+
hint: "矢印の位置のずらしは名前をずらします。 名前を書くか、ずらしを外す",
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1054
1119
|
function reportActorKindNotHonored(doc: DslDocument, onNotice?: (n: CompileNotice) => void): void {
|
|
1055
1120
|
if (!onNotice) return;
|
|
1056
1121
|
if (doc.type !== "sequence" && doc.type !== "solidity") return;
|
|
@@ -1068,7 +1133,13 @@ function reportActorKindNotHonored(doc: DslDocument, onNotice?: (n: CompileNotic
|
|
|
1068
1133
|
*/
|
|
1069
1134
|
doc.type === "sequence" && a.kindWritten !== false && a.kind !== undefined ? "種類" : "",
|
|
1070
1135
|
a.posW !== undefined || a.posH !== undefined ? "大きさ" : "",
|
|
1071
|
-
a.posX !== undefined ||
|
|
1136
|
+
a.posX !== undefined ||
|
|
1137
|
+
a.posY !== undefined ||
|
|
1138
|
+
a.posRel !== undefined ||
|
|
1139
|
+
// 位置のずらし (#1971)。 板は面ごとの箱を持たないので動かす相手が無い
|
|
1140
|
+
a.layoutPos !== undefined
|
|
1141
|
+
? "位置"
|
|
1142
|
+
: "",
|
|
1072
1143
|
a.rows !== undefined ? "行" : "",
|
|
1073
1144
|
a.tone !== undefined ? "色" : "",
|
|
1074
1145
|
a.eyebrow !== undefined ? "小見出し" : "",
|
|
@@ -1789,6 +1860,309 @@ function withPositions(
|
|
|
1789
1860
|
return probe;
|
|
1790
1861
|
}
|
|
1791
1862
|
|
|
1863
|
+
/** ずらしを当てる相手 1 つ。 狙いは配置後の位置 (箱は中心、縦列は左上) */
|
|
1864
|
+
type ずらしの相手 = {
|
|
1865
|
+
名前: string;
|
|
1866
|
+
line: number;
|
|
1867
|
+
箱: { id: string; 狙いX: number; 狙いY: number }[];
|
|
1868
|
+
縦列: { id: string; 狙いX: number; 狙いY: number }[];
|
|
1869
|
+
/** 縦列に書いたずらしの相手なら、その縦列の id */
|
|
1870
|
+
ずらした縦列?: string;
|
|
1871
|
+
};
|
|
1872
|
+
|
|
1873
|
+
/**
|
|
1874
|
+
* 書いた位置のずらし (JSON の `pos` / 記法の `offsetX` / `offsetY`) を、配置後の位置に足して置き直す (#1971)。
|
|
1875
|
+
*
|
|
1876
|
+
* **最後の図の上で置き直す**。 配置は縦列の間隔や視点の間隔を足した後でないと決まらず、途中の
|
|
1877
|
+
* 図で測ると、後から足される間隔の分だけ狙いがずれる。
|
|
1878
|
+
*
|
|
1879
|
+
* 手順は 3 つ。
|
|
1880
|
+
*
|
|
1881
|
+
* 1. 1 度配置して、ずらす箱の中心と縦列の左上を測る
|
|
1882
|
+
* 2. 狙い (測った位置 + ずらし) を `posX` / `posY` に書いてもう 1 度配置する
|
|
1883
|
+
* 3. 狙いからずれた分を足して直し (最大 2 回)、それでも 1 を超えてずれる相手は固定を戻して知らせる
|
|
1884
|
+
*
|
|
1885
|
+
* **縦列は 1 本でもずらすなら全ての縦列を固定する**。 1 本だけ固定すると、残りの縦列が左端から
|
|
1886
|
+
* 詰め直されて崩れる (実測 = 3 本のうち最初を動かすと 2 本目が 0 に来た)。 固定する位置と大きさは
|
|
1887
|
+
* 配置後の値なので、ずらさない縦列の配置は変わらない (実測 = 6 図種で一致)。
|
|
1888
|
+
*
|
|
1889
|
+
* **ずらした縦列の箱も同じ量で固定し、他の箱は動いたら元の位置に留める**。 縦列を固定しただけでは、
|
|
1890
|
+
* 中の箱は縦列の並ぶ向きにしか付いて来ず (`topology` / `c4` は縦列自身も縦に 32 ずれた)、最初の
|
|
1891
|
+
* 縦列を縦にずらすと他の縦列の箱まで動く (実測)。 動いた箱を留めるのは手順 3 の直し。
|
|
1892
|
+
*
|
|
1893
|
+
* **近すぎる箱を押し下げるのは止めない**。 ずらした箱に同じ縦列の箱が近づくと、描画側が間隔を保つよう
|
|
1894
|
+
* そちらを動かす (実測 = 静止した flow で下の箱が 100 下がった)。 これは描画側の規則で、ずらした箱
|
|
1895
|
+
* 自身は狙いに置かれる。
|
|
1896
|
+
*
|
|
1897
|
+
* ずらしを持つ相手が 1 つも無い図は何もしない = 配置を 1 度も計算しない。
|
|
1898
|
+
*/
|
|
1899
|
+
function applyLayoutOffsets(
|
|
1900
|
+
diagram: CdlDiagram,
|
|
1901
|
+
doc: DslDocument,
|
|
1902
|
+
onNotice?: (notice: CompileNotice) => void,
|
|
1903
|
+
): void {
|
|
1904
|
+
// `mind` は放射に描けない欄として「配置のずらし」 を既に知らせる (`放射で描けない欄の名前`)。 2 度知らせない
|
|
1905
|
+
const ずらす箱 =
|
|
1906
|
+
doc.type === "mind" ? [] : doc.actors.filter((a) => a.layoutPos !== undefined);
|
|
1907
|
+
const ずらす縦列 = Object.entries(doc.lanes ?? {}).filter(([, l]) => l.layoutPos !== undefined);
|
|
1908
|
+
if (ずらす箱.length === 0 && ずらす縦列.length === 0) return;
|
|
1909
|
+
// 順序図の板は面ごとの箱を持たない。 効かないことは `reportActorKindNotHonored` が伝える
|
|
1910
|
+
if (doc.type === "sequence" || doc.type === "solidity") return;
|
|
1911
|
+
|
|
1912
|
+
const 前 = layout(diagram);
|
|
1913
|
+
const 箱の配置 = new Map(前.nodes.map((n) => [n.id, n] as const));
|
|
1914
|
+
const 縦列の配置 = new Map(前.lanes.map((l) => [l.id, l] as const));
|
|
1915
|
+
const 相手たち: ずらしの相手[] = [];
|
|
1916
|
+
const 縦列のずらし = new Map<string, { x: number; y: number }>();
|
|
1917
|
+
/** 箱のずらしだけの狙い (縦列のずらしを足さない)。 縦列をずらす図で、ずらさない箱の基準を取るのに使う */
|
|
1918
|
+
const 箱だけの狙い = new Map<string, { x: number; y: number }>();
|
|
1919
|
+
|
|
1920
|
+
for (const [id, lane] of ずらす縦列) {
|
|
1921
|
+
const laid = 縦列の配置.get(id);
|
|
1922
|
+
const d = lane.layoutPos!;
|
|
1923
|
+
if (!laid) {
|
|
1924
|
+
onNotice?.({
|
|
1925
|
+
kind: "position-offset-ignored",
|
|
1926
|
+
actor: id,
|
|
1927
|
+
line: lane.pos?.line ?? 0,
|
|
1928
|
+
message: `縦列 "${id}" の位置のずらしを載せる縦列が図にありません (type: ${doc.type})`,
|
|
1929
|
+
hint: "縦列を作る図種で書くか、ずらしを外す",
|
|
1930
|
+
});
|
|
1931
|
+
continue;
|
|
1932
|
+
}
|
|
1933
|
+
縦列のずらし.set(id, d);
|
|
1934
|
+
相手たち.push({
|
|
1935
|
+
名前: id,
|
|
1936
|
+
line: lane.pos?.line ?? 0,
|
|
1937
|
+
// 縦列を固定しただけでは、中の箱は縦列の並ぶ向きにしか付いて来ない (実測 = flow の縦列を
|
|
1938
|
+
// 右へ 120、下へ 40 ずらすと、見出しは両方動き箱は右へ 120 だけ動いた)。 箱も同じ量で固定する
|
|
1939
|
+
箱: diagram.nodes.flatMap((n) => {
|
|
1940
|
+
const 箱laid = n.lane === id ? 箱の配置.get(n.id) : undefined;
|
|
1941
|
+
return 箱laid ? [{ id: n.id, 狙いX: 箱laid.cx + d.x, 狙いY: 箱laid.cy + d.y }] : [];
|
|
1942
|
+
}),
|
|
1943
|
+
縦列: [{ id, 狙いX: (laid.x ?? 0) + d.x, 狙いY: (laid.y ?? 0) + d.y }],
|
|
1944
|
+
ずらした縦列: id,
|
|
1945
|
+
});
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
for (const a of ずらす箱) {
|
|
1949
|
+
const d = a.layoutPos!;
|
|
1950
|
+
// 見本は `{名前}__{元の id}` で重なる。 箱と縦列を見本 1 つ分まとめて動かす
|
|
1951
|
+
const 前置き = `${a.name}__`;
|
|
1952
|
+
const 箱 =
|
|
1953
|
+
a.partId !== undefined
|
|
1954
|
+
? diagram.nodes.filter((n) => n.id.startsWith(前置き))
|
|
1955
|
+
: diagram.nodes.filter((n) => n.id === slugify(a.name));
|
|
1956
|
+
const 縦列 = a.partId !== undefined ? diagram.lanes.filter((l) => l.id.startsWith(前置き)) : [];
|
|
1957
|
+
if (箱.length === 0) {
|
|
1958
|
+
onNotice?.({
|
|
1959
|
+
kind: "position-offset-ignored",
|
|
1960
|
+
actor: a.name,
|
|
1961
|
+
line: a.pos?.line ?? 0,
|
|
1962
|
+
message: `"${truncateForMessage(a.name)}" の位置のずらしを載せる箱が図にありません (type: ${doc.type})`,
|
|
1963
|
+
hint: "箱を描く図種で書くか、ずらしを外す",
|
|
1964
|
+
});
|
|
1965
|
+
continue;
|
|
1966
|
+
}
|
|
1967
|
+
相手たち.push({
|
|
1968
|
+
名前: a.name,
|
|
1969
|
+
line: a.pos?.line ?? 0,
|
|
1970
|
+
箱: 箱.map((n) => {
|
|
1971
|
+
const laid = 箱の配置.get(n.id)!;
|
|
1972
|
+
箱だけの狙い.set(n.id, { x: laid.cx + d.x, y: laid.cy + d.y });
|
|
1973
|
+
// 箱を固定すると縦列を動かしても付いて来ないので、縦列のずらしも狙いに足す
|
|
1974
|
+
const 列 = 縦列のずらし.get(n.lane);
|
|
1975
|
+
return { id: n.id, 狙いX: laid.cx + d.x + (列?.x ?? 0), 狙いY: laid.cy + d.y + (列?.y ?? 0) };
|
|
1976
|
+
}),
|
|
1977
|
+
縦列: 縦列.map((l) => {
|
|
1978
|
+
const laid = 縦列の配置.get(l.id)!;
|
|
1979
|
+
return { id: l.id, 狙いX: (laid.x ?? 0) + d.x, 狙いY: (laid.y ?? 0) + d.y };
|
|
1980
|
+
}),
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
if (相手たち.length === 0) return;
|
|
1984
|
+
|
|
1985
|
+
// 固定を戻せるよう、書き換える前の欄を控える
|
|
1986
|
+
const 元の箱 = new Map(diagram.nodes.map((n) => [n.id, { posX: n.posX, posY: n.posY }] as const));
|
|
1987
|
+
const 元の縦列 = new Map(
|
|
1988
|
+
diagram.lanes.map((l) => [l.id, { posX: l.posX, posY: l.posY, posW: l.posW, posH: l.posH }] as const),
|
|
1989
|
+
);
|
|
1990
|
+
const 箱の狙い = new Map<string, { x: number; y: number }>();
|
|
1991
|
+
const 縦列の狙い = new Map<string, { x: number; y: number }>();
|
|
1992
|
+
for (const t of 相手たち) {
|
|
1993
|
+
for (const b of t.箱) 箱の狙い.set(b.id, { x: b.狙いX, y: b.狙いY });
|
|
1994
|
+
for (const l of t.縦列) 縦列の狙い.set(l.id, { x: l.狙いX, y: l.狙いY });
|
|
1995
|
+
}
|
|
1996
|
+
const 縦列を固定する = 縦列の狙い.size > 0;
|
|
1997
|
+
|
|
1998
|
+
/**
|
|
1999
|
+
* 縦列をずらす図で、ずらさない箱を留める位置。
|
|
2000
|
+
*
|
|
2001
|
+
* 描画側は箱を並べ始める高さを最初の縦列に合わせるので、最初の縦列を縦にずらすと固定しない箱が
|
|
2002
|
+
* 全て一緒に動く (実測 = 3 本のうち最初の縦列を 40 下げると他の縦列の箱も 40 下がり、2 本目と
|
|
2003
|
+
* 3 本目を下げても動かない)。 箱のずらしだけを当てた配置を基準に取り、そこから動いた箱をその位置に
|
|
2004
|
+
* 留める。 基準に箱のずらしを含めるのは、近すぎる箱の押し下げを残すため。
|
|
2005
|
+
*/
|
|
2006
|
+
const 留める位置 = new Map<string, { x: number; y: number }>();
|
|
2007
|
+
if (縦列を固定する) {
|
|
2008
|
+
let 基準 = 前;
|
|
2009
|
+
if (箱だけの狙い.size > 0) {
|
|
2010
|
+
for (const node of diagram.nodes) {
|
|
2011
|
+
const 狙い = 箱だけの狙い.get(node.id);
|
|
2012
|
+
if (!狙い) continue;
|
|
2013
|
+
node.posX = 狙い.x;
|
|
2014
|
+
node.posY = 狙い.y;
|
|
2015
|
+
}
|
|
2016
|
+
基準 = layout(diagram);
|
|
2017
|
+
for (const node of diagram.nodes) {
|
|
2018
|
+
if (!箱だけの狙い.has(node.id)) continue;
|
|
2019
|
+
const 元 = 元の箱.get(node.id)!;
|
|
2020
|
+
node.posX = 元.posX;
|
|
2021
|
+
node.posY = 元.posY;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
const 基準の箱 = new Map(基準.nodes.map((n) => [n.id, n] as const));
|
|
2025
|
+
// 縦列と一緒に動かす箱は、押し下げを含む基準の位置から縦列のずらしだけ動かす
|
|
2026
|
+
for (const t of 相手たち) {
|
|
2027
|
+
const d = t.ずらした縦列 === undefined ? undefined : 縦列のずらし.get(t.ずらした縦列);
|
|
2028
|
+
if (!d) continue;
|
|
2029
|
+
for (const b of t.箱) {
|
|
2030
|
+
const n = 基準の箱.get(b.id);
|
|
2031
|
+
if (!n || 箱だけの狙い.has(b.id)) continue;
|
|
2032
|
+
箱の狙い.set(b.id, { x: n.cx + d.x, y: n.cy + d.y });
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
for (const n of 基準.nodes) {
|
|
2036
|
+
if (!箱の狙い.has(n.id)) 留める位置.set(n.id, { x: n.cx, y: n.cy });
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
/** 基準から動いたので固定した、ずらさない箱 */
|
|
2040
|
+
const 留めた箱 = new Set<string>();
|
|
2041
|
+
|
|
2042
|
+
// 手順 2。 縦列を固定する時は、ずらさない縦列も配置後の位置と大きさで固定する
|
|
2043
|
+
const 書く = (直し: ReadonlyMap<string, { x: number; y: number }>): void => {
|
|
2044
|
+
if (縦列を固定する) {
|
|
2045
|
+
for (const lane of diagram.lanes) {
|
|
2046
|
+
const laid = 縦列の配置.get(lane.id);
|
|
2047
|
+
if (!laid) continue;
|
|
2048
|
+
const 狙い = 縦列の狙い.get(lane.id) ?? { x: laid.x ?? 0, y: laid.y ?? 0 };
|
|
2049
|
+
const 差 = 直し.get(`lane:${lane.id}`) ?? { x: 0, y: 0 };
|
|
2050
|
+
lane.posX = 狙い.x - 差.x;
|
|
2051
|
+
lane.posY = 狙い.y - 差.y;
|
|
2052
|
+
lane.posW = laid.width;
|
|
2053
|
+
lane.posH = laid.height;
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
for (const node of diagram.nodes) {
|
|
2057
|
+
const 狙い = 箱の狙い.get(node.id);
|
|
2058
|
+
const 留める = 留めた箱.has(node.id) ? 留める位置.get(node.id) : undefined;
|
|
2059
|
+
if (!狙い && !留める) continue;
|
|
2060
|
+
const 差 = 直し.get(狙い ? `node:${node.id}` : `keep:${node.id}`) ?? { x: 0, y: 0 };
|
|
2061
|
+
node.posX = (狙い ?? 留める)!.x - 差.x;
|
|
2062
|
+
node.posY = (狙い ?? 留める)!.y - 差.y;
|
|
2063
|
+
}
|
|
2064
|
+
};
|
|
2065
|
+
/** 狙いと実際の差。 縦列を固定する時は、ずらさない縦列と箱も元の位置に居るかを見る */
|
|
2066
|
+
const 測る = (): Map<string, { x: number; y: number }> => {
|
|
2067
|
+
const 後 = layout(diagram);
|
|
2068
|
+
const 差 = new Map<string, { x: number; y: number }>();
|
|
2069
|
+
for (const n of 後.nodes) {
|
|
2070
|
+
const 狙い = 箱の狙い.get(n.id);
|
|
2071
|
+
if (狙い) 差.set(`node:${n.id}`, { x: n.cx - 狙い.x, y: n.cy - 狙い.y });
|
|
2072
|
+
const 留める = 留める位置.get(n.id);
|
|
2073
|
+
if (留める) 差.set(`keep:${n.id}`, { x: n.cx - 留める.x, y: n.cy - 留める.y });
|
|
2074
|
+
}
|
|
2075
|
+
if (縦列を固定する) {
|
|
2076
|
+
for (const l of 後.lanes) {
|
|
2077
|
+
const laid = 縦列の配置.get(l.id);
|
|
2078
|
+
if (!laid) continue;
|
|
2079
|
+
const 狙い = 縦列の狙い.get(l.id) ?? { x: laid.x ?? 0, y: laid.y ?? 0 };
|
|
2080
|
+
差.set(`lane:${l.id}`, { x: (l.x ?? 0) - 狙い.x, y: (l.y ?? 0) - 狙い.y });
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
return 差;
|
|
2084
|
+
};
|
|
2085
|
+
const ずれた = (差: { x: number; y: number } | undefined): boolean =>
|
|
2086
|
+
差 !== undefined && (Math.abs(差.x) > PLACEMENT_TOLERANCE || Math.abs(差.y) > PLACEMENT_TOLERANCE);
|
|
2087
|
+
|
|
2088
|
+
const 直し = new Map<string, { x: number; y: number }>();
|
|
2089
|
+
書く(直し);
|
|
2090
|
+
let 差 = 測る();
|
|
2091
|
+
// 手順 3。 狙いからの差を書いた位置から引いて置き直す。 固定した相手の差は図種ごとに一定なので
|
|
2092
|
+
// 1 度で合う。 基準から動いた箱はその回に初めて固定し、固定で出る差をもう 1 度で直す。
|
|
2093
|
+
//
|
|
2094
|
+
// **ずらす箱と縦列そのものの差 (`node:` / `lane:`) を直す入力は組めていない** (#1971 の実測)。
|
|
2095
|
+
// 縦列だけを固定していた形では `topology` / `c4` の縦列が縦に 32 ずれたが、中の箱も固定する今の形
|
|
2096
|
+
// ではずれない。 試した形 = 7 図種の箱 / 全ての縦列を 1 本ずつ / 箱の無い縦列。 実際に直すのは
|
|
2097
|
+
// 留める箱 (`keep:`) で、最初の縦列を縦にずらした図が通る
|
|
2098
|
+
for (let 回 = 0; 回 < 2 && [...差.values()].some(ずれた); 回++) {
|
|
2099
|
+
for (const [k, v] of 差) {
|
|
2100
|
+
if (!ずれた(v)) continue;
|
|
2101
|
+
const id = k.slice(k.indexOf(":") + 1);
|
|
2102
|
+
if (k.startsWith("keep:") && !留めた箱.has(id)) {
|
|
2103
|
+
留めた箱.add(id);
|
|
2104
|
+
continue;
|
|
2105
|
+
}
|
|
2106
|
+
const 前の直し = 直し.get(k) ?? { x: 0, y: 0 };
|
|
2107
|
+
直し.set(k, { x: 前の直し.x + v.x, y: 前の直し.y + v.y });
|
|
2108
|
+
}
|
|
2109
|
+
書く(直し);
|
|
2110
|
+
差 = 測る();
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
const 戻す箱 = (t: ずらしの相手): void => {
|
|
2114
|
+
for (const b of t.箱) {
|
|
2115
|
+
const node = diagram.nodes.find((n) => n.id === b.id)!;
|
|
2116
|
+
const 元 = 元の箱.get(b.id)!;
|
|
2117
|
+
node.posX = 元.posX;
|
|
2118
|
+
node.posY = 元.posY;
|
|
2119
|
+
}
|
|
2120
|
+
};
|
|
2121
|
+
const 知らせる = (t: ずらしの相手): void =>
|
|
2122
|
+
onNotice?.({
|
|
2123
|
+
kind: "position-offset-ignored",
|
|
2124
|
+
actor: t.名前,
|
|
2125
|
+
line: t.line,
|
|
2126
|
+
message: `"${truncateForMessage(t.名前)}" の位置のずらしは type: ${doc.type} の図では書いた量だけ動かせません`,
|
|
2127
|
+
hint: "ずらしを外すか、座標 (`位置: 300,200`) で置く",
|
|
2128
|
+
});
|
|
2129
|
+
|
|
2130
|
+
// **ここから下の「戻して知らせる」 経路に届く入力は組めていない** (#1971 の実測)。 描画側は
|
|
2131
|
+
// `posX` / `posY` を書いた箱と縦列をそのまま置き、囲いの縦列のずれも 1 度の直しで合う。 試した
|
|
2132
|
+
// 形 = 大きく正と負にずらす / 図の広さを固定してその外へ出す / 7 図種の箱 / 囲いを持つ 2 図種と
|
|
2133
|
+
// クラス図と横長の図の縦列。 描画側が置いた位置を動かす規則を持った時に備えて残す
|
|
2134
|
+
//
|
|
2135
|
+
// 縦列が 1 本でも狙いに置けないか、ずらさない箱を留められなければ、全ての固定を戻して全ての相手を
|
|
2136
|
+
// 知らせる。 縦列を 1 本だけ残すと他が詰め直され、箱の狙いは縦列のずらしを含むので、縦列を戻すと
|
|
2137
|
+
// 箱の狙いも崩れる
|
|
2138
|
+
if ([...差].some(([k, v]) => (k.startsWith("lane:") || k.startsWith("keep:")) && ずれた(v))) {
|
|
2139
|
+
for (const lane of diagram.lanes) {
|
|
2140
|
+
const 元 = 元の縦列.get(lane.id);
|
|
2141
|
+
if (!元) continue;
|
|
2142
|
+
lane.posX = 元.posX;
|
|
2143
|
+
lane.posY = 元.posY;
|
|
2144
|
+
lane.posW = 元.posW;
|
|
2145
|
+
lane.posH = 元.posH;
|
|
2146
|
+
}
|
|
2147
|
+
for (const node of diagram.nodes) {
|
|
2148
|
+
if (!留めた箱.has(node.id)) continue;
|
|
2149
|
+
const 元 = 元の箱.get(node.id)!;
|
|
2150
|
+
node.posX = 元.posX;
|
|
2151
|
+
node.posY = 元.posY;
|
|
2152
|
+
}
|
|
2153
|
+
for (const t of 相手たち) {
|
|
2154
|
+
戻す箱(t);
|
|
2155
|
+
知らせる(t);
|
|
2156
|
+
}
|
|
2157
|
+
return;
|
|
2158
|
+
}
|
|
2159
|
+
for (const t of 相手たち) {
|
|
2160
|
+
if (!t.箱.some((b) => ずれた(差.get(`node:${b.id}`)))) continue;
|
|
2161
|
+
戻す箱(t);
|
|
2162
|
+
知らせる(t);
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
|
|
1792
2166
|
/**
|
|
1793
2167
|
* 全図種共通の後処理で、 登場人物に書かれた色を対応する箱に載せる。
|
|
1794
2168
|
*
|
|
@@ -1824,13 +2198,11 @@ function applyNodeTones(diagram: CdlDiagram, doc: DslDocument): void {
|
|
|
1824
2198
|
* preset builder が生成した diagram に対して、 doc.actors の 4 field を絶対座標として反映する。
|
|
1825
2199
|
* slugify で actor 名 → lane id / node id の逆引き、 posX/Y set 済 actor に対応する lane / node に
|
|
1826
2200
|
* 座標を書込む。 未指定 actor は従来 auto layout 経路そのまま。
|
|
2201
|
+
*
|
|
2202
|
+
* **知らせは出さない**。 かつて `sub-node-not-found` を出していたが、 その知らせが見ていた
|
|
2203
|
+
* `nodes` の欄を #1976 で外した時に出し所が消えた。 知らせる相手が戻った時に引数を足す。
|
|
1827
2204
|
*/
|
|
1828
|
-
function applyCanvasPivotPositions(
|
|
1829
|
-
diagram: CdlDiagram,
|
|
1830
|
-
doc: DslDocument,
|
|
1831
|
-
// 下見 (`probe`) の呼出では渡さない = 同じ知らせが 2 度出る
|
|
1832
|
-
onNotice?: (notice: CompileNotice) => void,
|
|
1833
|
-
): void {
|
|
2205
|
+
function applyCanvasPivotPositions(diagram: CdlDiagram, doc: DslDocument): void {
|
|
1834
2206
|
for (const actor of doc.actors) {
|
|
1835
2207
|
if (actor.partId !== undefined) continue; // parts actor は別経路 (mergePartsFromActors) で処理
|
|
1836
2208
|
const aliasSlug = slugify(actor.name);
|
|
@@ -1853,45 +2225,6 @@ function applyCanvasPivotPositions(
|
|
|
1853
2225
|
}
|
|
1854
2226
|
}
|
|
1855
2227
|
}
|
|
1856
|
-
// canvas pivot UX 修正 (B1) = actor.nodes[subKey] を対応 CDL node に個別反映。
|
|
1857
|
-
// sub-node id pattern を actor scope 限定の 2 経路に絞る (subagent review MAJOR-1 対応、 CAR-canvas-pivot):
|
|
1858
|
-
// 1. `{aliasSlug}-{subKey}` = header / footer / spacer 等 suffix
|
|
1859
|
-
// 2. `{subKey}-{aliasSlug}` = sequence step box `s{N}-{aliasSlug}` 等 prefix
|
|
1860
|
-
// 旧 `node.id === subKey` 完全一致 fallback は actor scope を持たず cross-actor pollution risk
|
|
1861
|
-
// (別 actor が保有する同名 id node に座標が漏れる silent bug) のため削除。 全 sub-node は必ず
|
|
1862
|
-
// aliasSlug を接頭 / 接尾に含む形式で生成されるため、 2 経路で網羅済。
|
|
1863
|
-
// lane 側は触らない = 他 sub-node の auto layout 経路を保持 (B1 独立性の SSOT)。
|
|
1864
|
-
if (actor.nodes) {
|
|
1865
|
-
for (const [subKey, override] of Object.entries(actor.nodes)) {
|
|
1866
|
-
if (override.posX === undefined || override.posY === undefined) continue;
|
|
1867
|
-
let 当たった = 0;
|
|
1868
|
-
for (const node of diagram.nodes) {
|
|
1869
|
-
if (node.id === `${aliasSlug}-${subKey}` || node.id === `${subKey}-${aliasSlug}`) {
|
|
1870
|
-
node.posX = override.posX;
|
|
1871
|
-
node.posY = override.posY;
|
|
1872
|
-
if (override.posW !== undefined) node.posW = override.posW;
|
|
1873
|
-
if (override.posH !== undefined) node.posH = override.posH;
|
|
1874
|
-
当たった += 1;
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
/*
|
|
1878
|
-
* **当たらなかったことを伝える** (#1466)。
|
|
1879
|
-
*
|
|
1880
|
-
* この経路が動くのは、図種が 1 人につき複数の箱を作る時だけ。 順序図が名札 / 余白 /
|
|
1881
|
-
* 足を作っていた頃はそこに当たっていたが、板になって作らなくなった = いまはどの図種も
|
|
1882
|
-
* この形の箱を作らない。 黙って落とすと、書いた側は効いていると思い込む。
|
|
1883
|
-
*/
|
|
1884
|
-
if (当たった === 0) {
|
|
1885
|
-
onNotice?.({
|
|
1886
|
-
kind: "sub-node-not-found",
|
|
1887
|
-
actor: actor.name,
|
|
1888
|
-
line: actor.pos?.line ?? 0,
|
|
1889
|
-
message: `"${truncateForMessage(actor.name)}" の nodes に書いた "${truncateForMessage(subKey)}" に当たる箱が図にありません`,
|
|
1890
|
-
hint: "1 人に複数の箱を作る図種でだけ効きます。 箱そのものの位置は actor 側の 位置: に書いてください",
|
|
1891
|
-
});
|
|
1892
|
-
}
|
|
1893
|
-
}
|
|
1894
|
-
}
|
|
1895
2228
|
}
|
|
1896
2229
|
}
|
|
1897
2230
|
|
|
@@ -1975,10 +2308,13 @@ function minOf(values: readonly number[], fallback: number): number {
|
|
|
1975
2308
|
}
|
|
1976
2309
|
|
|
1977
2310
|
/**
|
|
1978
|
-
*
|
|
2311
|
+
* 段 1 つぶんの送り幅。 2 つの用途で使う。
|
|
2312
|
+
*
|
|
2313
|
+
* - `大きさ:` の縦の基準 (`partScaleBase`)。 縦は段の送り幅の合計に対する倍率で掛かる
|
|
2314
|
+
* - 部品の頁を配置できない部品を、書いた値で置く時の段の間 (`書いた置き方`)
|
|
1979
2315
|
*
|
|
1980
|
-
*
|
|
1981
|
-
* (実測 =
|
|
2316
|
+
* 頁を配置できる部品の要素の縦位置は、頁で配置した位置から取る (#1992)。 この値で置くと、
|
|
2317
|
+
* 頁で段の間が 220 より広い部品 (実測 = `bind-grid-4` は段の中心の間 280) が置いた図で詰まる。
|
|
1982
2318
|
*/
|
|
1983
2319
|
const PART_STACK_PITCH = 220;
|
|
1984
2320
|
|
|
@@ -2058,28 +2394,24 @@ export function partTargetSize(
|
|
|
2058
2394
|
/**
|
|
2059
2395
|
* `大きさ:` と `倍率:` が掛かる時の基準の大きさ (#1026)。
|
|
2060
2396
|
*
|
|
2061
|
-
*
|
|
2062
|
-
*
|
|
2397
|
+
* 横は部品の頁で配置した縦列の外接矩形 (`部品の置き方を読む`)、縦は段の送り幅の合計。
|
|
2398
|
+
* **図枠 (`partRenderSize`) ではない**。 図枠は余白を含むため、これを基準にすると書いた倍率より
|
|
2399
|
+
* 大きく掛かる。
|
|
2400
|
+
*
|
|
2401
|
+
* 横を頁の縦列にするのは、組み込みが要素を頁の縦列に沿って置くため (#1992)。 書いた縦列を
|
|
2402
|
+
* 基準のまま残すと、頁で縦列が広がる部品は `大きさ:` に書いた幅より広く描かれる。
|
|
2063
2403
|
*
|
|
2064
2404
|
* `partTargetScale` と `partTargetSize` が同じ物差しを使うことで、
|
|
2065
2405
|
* `partTargetScale(part, base.w * k, base.h * k)` が丁度 `k` 倍を返す関係が保たれる。
|
|
2406
|
+
* 組み込み (`mergePartIntoDiagram`) も同じ基準で伸縮する。
|
|
2066
2407
|
*/
|
|
2067
2408
|
function partScaleBase(part: CdlDiagram): { w: number; h: number } {
|
|
2068
|
-
const lanes = Array.isArray(part.lanes) ? part.lanes : [];
|
|
2069
2409
|
const nodes = Array.isArray(part.nodes) ? part.nodes : [];
|
|
2070
|
-
const lefts: number[] = [];
|
|
2071
|
-
const rights: number[] = [];
|
|
2072
|
-
for (const l of lanes) {
|
|
2073
|
-
const lx = typeof l.x === "number" && Number.isFinite(l.x) ? l.x : 0;
|
|
2074
|
-
const lw = positiveOr(l.width, 400);
|
|
2075
|
-
lefts.push(lx);
|
|
2076
|
-
rights.push(lx + lw);
|
|
2077
|
-
}
|
|
2078
2410
|
const stacks = nodes.map((n) =>
|
|
2079
2411
|
typeof n.stack === "number" && Number.isFinite(n.stack) ? n.stack : 0,
|
|
2080
2412
|
);
|
|
2081
2413
|
return {
|
|
2082
|
-
w:
|
|
2414
|
+
w: 部品の置き方を読む(part).幅,
|
|
2083
2415
|
h: Math.max(1, (maxOf(stacks, 0) - minOf(stacks, 0) + 1) * PART_STACK_PITCH),
|
|
2084
2416
|
};
|
|
2085
2417
|
}
|
|
@@ -2123,9 +2455,8 @@ export function partTargetScale(
|
|
|
2123
2455
|
* 箱ごとに位置と大きさを見る。 一番高い箱の高さと段の数から概算すると実際の矩形と合わない
|
|
2124
2456
|
* (実測 = 段 5 だけのパーツで 200 空けたいところが 750、 段 0,5 で高さが違うと 275 になった)。
|
|
2125
2457
|
*
|
|
2126
|
-
*
|
|
2127
|
-
*
|
|
2128
|
-
* 「merge がどこに置くか」 なので merge の規則に合わせる。
|
|
2458
|
+
* 箱の位置は merge と同じ置き方 (`部品の置き方を読む`) と同じ式 (`部品の要素のずれ`) から出す。
|
|
2459
|
+
* ここで見たいのは「merge がどこに置くか」 なので、merge と別の規則を持たない (#1992)。
|
|
2129
2460
|
*/
|
|
2130
2461
|
function partExtent(
|
|
2131
2462
|
part: CdlDiagram,
|
|
@@ -2136,37 +2467,17 @@ function partExtent(
|
|
|
2136
2467
|
if (!Array.isArray(part.lanes) || !Array.isArray(part.nodes)) return fallback;
|
|
2137
2468
|
if (part.nodes.length === 0) return fallback;
|
|
2138
2469
|
|
|
2139
|
-
|
|
2140
|
-
const lanes = new Map<string, { x: number; w: number }>();
|
|
2141
|
-
const laneLefts: number[] = [];
|
|
2142
|
-
const laneRights: number[] = [];
|
|
2143
|
-
for (const l of part.lanes) {
|
|
2144
|
-
const x = typeof l.x === "number" && Number.isFinite(l.x) ? l.x : 0;
|
|
2145
|
-
const w = positiveOr(l.width, 400);
|
|
2146
|
-
lanes.set(l.id, { x, w });
|
|
2147
|
-
laneLefts.push(x);
|
|
2148
|
-
laneRights.push(x + w);
|
|
2149
|
-
}
|
|
2150
|
-
const bboxW = positiveOr(maxOf(laneRights, 400) - minOf(laneLefts, 0), 400);
|
|
2151
|
-
const bboxCenterX = minOf(laneLefts, 0) + bboxW / 2;
|
|
2470
|
+
const 置き方 = 部品の置き方を読む(part);
|
|
2152
2471
|
const { x: scaleX, y: scaleY } = partTargetScale(part, targetW, targetH);
|
|
2153
2472
|
|
|
2154
|
-
const stacks = part.nodes.map((n) =>
|
|
2155
|
-
typeof n.stack === "number" && Number.isFinite(n.stack) ? n.stack : 0,
|
|
2156
|
-
);
|
|
2157
|
-
const maxStack = maxOf(stacks, 0);
|
|
2158
|
-
const minStack = minOf(stacks, 0);
|
|
2159
|
-
const centerStack = (minStack + maxStack) / 2;
|
|
2160
|
-
|
|
2161
2473
|
// 箱ごとに、 merge が置く位置 (基準からの相対) と大きさから上下左右の端を出す
|
|
2162
2474
|
const tops: number[] = [];
|
|
2163
2475
|
const bottoms: number[] = [];
|
|
2164
2476
|
const lefts: number[] = [];
|
|
2165
2477
|
const rights: number[] = [];
|
|
2166
2478
|
part.nodes.forEach((n, i) => {
|
|
2167
|
-
const
|
|
2168
|
-
const
|
|
2169
|
-
const cy = ((stacks[i] ?? 0) - centerStack) * PART_STACK_PITCH * scaleY;
|
|
2479
|
+
const 頁 = 置き方.要素[i] ?? { x: 置き方.左端 + 置き方.幅 / 2, y: 0 };
|
|
2480
|
+
const { dx: cx, dy: cy } = 部品の要素のずれ(n, 頁, 置き方, scaleX, scaleY);
|
|
2170
2481
|
const halfW = (positiveOr(n.w, CDL_DEFAULT_NODE_W) * scaleX) / 2;
|
|
2171
2482
|
const halfH = (positiveOr(n.h, CDL_DEFAULT_NODE_H) * scaleY) / 2;
|
|
2172
2483
|
lefts.push(cx - halfW);
|
|
@@ -2279,8 +2590,8 @@ export function partBoxInFrame(part: CdlDiagram): {
|
|
|
2279
2590
|
* 別名の数だけ組み立て直す (相対指定があると 1 個につき 4 回になる)。 覚えるのは大きさだけで、
|
|
2280
2591
|
* 色などの見た目は含まないため、 呼出側が色を差し替えても古い値にはならない。
|
|
2281
2592
|
*
|
|
2282
|
-
*
|
|
2283
|
-
*
|
|
2593
|
+
* 配置は `部品の頁を配置する` から受け取る。 置き方 (`部品の置き方を読む`) も同じ配置を読むので、
|
|
2594
|
+
* 部品を 1 つ置いても配置は 1 度で済む。
|
|
2284
2595
|
*
|
|
2285
2596
|
* 測れない図では既定の大きさと余白 0 に落とす。
|
|
2286
2597
|
*/
|
|
@@ -2300,45 +2611,210 @@ function partFrameGeometry(part: CdlDiagram): PartFrameGeometry {
|
|
|
2300
2611
|
if (cached) return cached;
|
|
2301
2612
|
const fallback = { w: 400, h: 200, left: 0, top: 0, boxW: 400, boxH: 200 };
|
|
2302
2613
|
let out = fallback;
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
y1 = Math.max(y1, n.cy + n.h / 2);
|
|
2322
|
-
}
|
|
2323
|
-
const left = x0 - vb.x;
|
|
2324
|
-
const top = y0 - vb.y;
|
|
2325
|
-
out = {
|
|
2326
|
-
w,
|
|
2327
|
-
h,
|
|
2328
|
-
left: Number.isFinite(left) ? left : 0,
|
|
2329
|
-
top: Number.isFinite(top) ? top : 0,
|
|
2330
|
-
boxW: positiveOr(x1 - x0, w),
|
|
2331
|
-
boxH: positiveOr(y1 - y0, h),
|
|
2332
|
-
};
|
|
2614
|
+
const own = 部品の頁を配置する(part);
|
|
2615
|
+
if (own !== undefined) {
|
|
2616
|
+
const vb = own.viewBox;
|
|
2617
|
+
const w = positiveOr(vb.w, 400);
|
|
2618
|
+
const h = positiveOr(vb.h, 200);
|
|
2619
|
+
if (own.nodes.length === 0) {
|
|
2620
|
+
// 箱を持たない図では図枠をそのまま箱として扱う。 相対指定の間隔は図枠の縁から測る
|
|
2621
|
+
out = { w, h, left: 0, top: 0, boxW: w, boxH: h };
|
|
2622
|
+
} else {
|
|
2623
|
+
let x0 = Infinity;
|
|
2624
|
+
let y0 = Infinity;
|
|
2625
|
+
let x1 = -Infinity;
|
|
2626
|
+
let y1 = -Infinity;
|
|
2627
|
+
for (const n of own.nodes) {
|
|
2628
|
+
x0 = Math.min(x0, n.cx - n.w / 2);
|
|
2629
|
+
x1 = Math.max(x1, n.cx + n.w / 2);
|
|
2630
|
+
y0 = Math.min(y0, n.cy - n.h / 2);
|
|
2631
|
+
y1 = Math.max(y1, n.cy + n.h / 2);
|
|
2333
2632
|
}
|
|
2334
|
-
|
|
2335
|
-
|
|
2633
|
+
const left = x0 - vb.x;
|
|
2634
|
+
const top = y0 - vb.y;
|
|
2635
|
+
out = {
|
|
2636
|
+
w,
|
|
2637
|
+
h,
|
|
2638
|
+
left: Number.isFinite(left) ? left : 0,
|
|
2639
|
+
top: Number.isFinite(top) ? top : 0,
|
|
2640
|
+
boxW: positiveOr(x1 - x0, w),
|
|
2641
|
+
boxH: positiveOr(y1 - y0, h),
|
|
2642
|
+
};
|
|
2336
2643
|
}
|
|
2337
2644
|
}
|
|
2338
2645
|
PART_FRAME_CACHE.set(part, out);
|
|
2339
2646
|
return out;
|
|
2340
2647
|
}
|
|
2341
2648
|
|
|
2649
|
+
/**
|
|
2650
|
+
* 部品の頁 (部品の図だけ) を配置した結果。 配置できない図では `undefined` (#1992)。
|
|
2651
|
+
*
|
|
2652
|
+
* 図枠 (`partFrameGeometry`) と置き方 (`部品の置き方を読む`) が同じ結果を読む。 見本ごとに
|
|
2653
|
+
* 覚えておくのは、catalog の見本が複数の別名から同じものを指し、画面を描くたびにも呼ばれるため。
|
|
2654
|
+
*
|
|
2655
|
+
* 大きすぎる図は配置する前に止める。 上限は組み立て側と同じ物差しを使う (#1005)。
|
|
2656
|
+
* catalog の見本は数十要素なので通常は掛からない。
|
|
2657
|
+
*/
|
|
2658
|
+
const PART_PAGE_CACHE = new WeakMap<CdlDiagram, LaidDiagram | null>();
|
|
2659
|
+
|
|
2660
|
+
function 部品の頁を配置する(part: CdlDiagram): LaidDiagram | undefined {
|
|
2661
|
+
const cached = PART_PAGE_CACHE.get(part);
|
|
2662
|
+
if (cached !== undefined) return cached ?? undefined;
|
|
2663
|
+
let out: LaidDiagram | null = null;
|
|
2664
|
+
if (countDiagramElements(part) <= MAX_INPUT_ELEMENTS) {
|
|
2665
|
+
try {
|
|
2666
|
+
out = layout(part);
|
|
2667
|
+
} catch {
|
|
2668
|
+
out = null;
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
PART_PAGE_CACHE.set(part, out);
|
|
2672
|
+
return out ?? undefined;
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
/**
|
|
2676
|
+
* 部品を図に置く時に、縦列と要素をどこに置くか (#1992)。
|
|
2677
|
+
*
|
|
2678
|
+
* 部品の頁で配置した縦列の位置と幅、要素の中心を使う。 組み込み (`mergePartIntoDiagram`)、
|
|
2679
|
+
* 組み込んだ大きさの見積り (`partExtent`)、`大きさ:` の横の基準 (`partScaleBase`) の 3 か所が
|
|
2680
|
+
* これを読む。
|
|
2681
|
+
*
|
|
2682
|
+
* 部品が書いた縦列の位置と幅は、頁で配置した後の値と違う。 描画側が隣と近づきすぎないよう
|
|
2683
|
+
* 縦列を広げ、段の間を箱の高さに合わせて取るため。 書いた値で置くと頁で空いた分が消え、
|
|
2684
|
+
* 置いた図だけが間隔の検査に掛かる (実測 = 80 種のうち 14 種。 `wifi-signal` は頁で要素の間 80、
|
|
2685
|
+
* 書いた値で置くと 40)。
|
|
2686
|
+
*
|
|
2687
|
+
* 頁を配置できない図 (描画側が止める図と、大きすぎて測らない図) は書いた値に落とす
|
|
2688
|
+
* (`書いた置き方`)。 頁の位置が数にならない図も同じ。 描画側は書いた値が数でない時に止めずに
|
|
2689
|
+
* 数でない座標を返す (実測 = 幅を書かない縦列は幅が `NaN`、段が数でない要素は縦が `-Infinity`)。
|
|
2690
|
+
*
|
|
2691
|
+
* 1 つの部品で 2 つを混ぜない。 数にならない縦列や要素が 1 つでもあれば全体を書いた値にする。
|
|
2692
|
+
* 混ぜると、要素ごとに別の物差しで置くことになる。
|
|
2693
|
+
*/
|
|
2694
|
+
type 部品の置き方 = {
|
|
2695
|
+
/** 縦列ごとの左端と幅 */
|
|
2696
|
+
縦列: ReadonlyMap<string, { x: number; w: number }>;
|
|
2697
|
+
/** 縦列の外接矩形の左端と幅。 横に写す時の中心と、`大きさ:` の横の基準に使う */
|
|
2698
|
+
左端: number;
|
|
2699
|
+
幅: number;
|
|
2700
|
+
/**
|
|
2701
|
+
* 要素ごとの中心 (`part.nodes` と同じ並び)。 横は縦列と同じ座標、縦は要素全体の縦の中心からの差。
|
|
2702
|
+
* どちらも伸縮を掛ける前の値
|
|
2703
|
+
*/
|
|
2704
|
+
要素: readonly { x: number; y: number }[];
|
|
2705
|
+
};
|
|
2706
|
+
|
|
2707
|
+
const 部品の置き方の控え = new WeakMap<CdlDiagram, 部品の置き方>();
|
|
2708
|
+
|
|
2709
|
+
function 部品の置き方を読む(part: CdlDiagram): 部品の置き方 {
|
|
2710
|
+
const 控え = 部品の置き方の控え.get(part);
|
|
2711
|
+
if (控え) return 控え;
|
|
2712
|
+
const 置き方 = 頁の置き方(part) ?? 書いた置き方(part);
|
|
2713
|
+
部品の置き方の控え.set(part, 置き方);
|
|
2714
|
+
return 置き方;
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
/**
|
|
2718
|
+
* 縦列の外接矩形。 幅が正でない時 (縦列が 1 本も無い図) は 400 に落とす。
|
|
2719
|
+
* 1 未満の正の幅はそのまま使う。 1 に切り上げると、その分だけ `大きさ:` の倍率が小さくなる
|
|
2720
|
+
*/
|
|
2721
|
+
function 縦列の外接(縦列: ReadonlyMap<string, { x: number; w: number }>): { 左端: number; 幅: number } {
|
|
2722
|
+
const 値 = [...縦列.values()];
|
|
2723
|
+
const 左端 = minOf(値.map((g) => g.x), 0);
|
|
2724
|
+
const 右端 = maxOf(値.map((g) => g.x + g.w), 400);
|
|
2725
|
+
return { 左端, 幅: positiveOr(右端 - 左端, 400) };
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
/**
|
|
2729
|
+
* 部品の頁で配置した縦列と要素の中心。 頁を配置できない時と、頁の位置が数にならない時は
|
|
2730
|
+
* `undefined` を返し、呼出側が書いた値に落とす。
|
|
2731
|
+
*
|
|
2732
|
+
* 縦の中心は、頁に並んだ要素全体の上下の中心 (一番上と一番下の要素の中心の中間)。 書いた値の
|
|
2733
|
+
* 経路が段の番号の中間を中心にするのと同じ取り方で、部品の中心を置く位置に合わせる。
|
|
2734
|
+
*
|
|
2735
|
+
* 部品の要素が頁に見つからない場合も書いた値に落とす。 描画側は部品の要素を全て配置して返す
|
|
2736
|
+
* ため、この場合に届く入力は作れない (実測 = カタログ 80 種で見つからない要素は 0)。 残すのは、
|
|
2737
|
+
* 見つからない要素だけを別の物差しで置かないため。
|
|
2738
|
+
*/
|
|
2739
|
+
function 頁の置き方(part: CdlDiagram): 部品の置き方 | undefined {
|
|
2740
|
+
if (!Array.isArray(part.lanes) || !Array.isArray(part.nodes)) return undefined;
|
|
2741
|
+
const laid = 部品の頁を配置する(part);
|
|
2742
|
+
if (laid === undefined) return undefined;
|
|
2743
|
+
const 縦列 = new Map<string, { x: number; w: number }>();
|
|
2744
|
+
for (const l of laid.lanes) {
|
|
2745
|
+
if (!Number.isFinite(l.x) || !Number.isFinite(l.width) || !(l.width > 0)) return undefined;
|
|
2746
|
+
縦列.set(l.id, { x: l.x, w: l.width });
|
|
2747
|
+
}
|
|
2748
|
+
const 頁の箱 = new Map(laid.nodes.map((n) => [n.id, n]));
|
|
2749
|
+
const 箱たち: { cx: number; cy: number }[] = [];
|
|
2750
|
+
for (const n of part.nodes) {
|
|
2751
|
+
const 箱 = 頁の箱.get(n.id);
|
|
2752
|
+
if (箱 === undefined || !Number.isFinite(箱.cx) || !Number.isFinite(箱.cy)) return undefined;
|
|
2753
|
+
箱たち.push(箱);
|
|
2754
|
+
}
|
|
2755
|
+
const 縦たち = 箱たち.map((b) => b.cy);
|
|
2756
|
+
const 縦の中心 = (minOf(縦たち, 0) + maxOf(縦たち, 0)) / 2;
|
|
2757
|
+
return {
|
|
2758
|
+
縦列,
|
|
2759
|
+
...縦列の外接(縦列),
|
|
2760
|
+
要素: 箱たち.map((b) => ({ x: b.cx, y: b.cy - 縦の中心 })),
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
/**
|
|
2765
|
+
* 部品が書いた値で置く時の縦列と要素の中心。 頁を配置できない部品だけが使う。
|
|
2766
|
+
*
|
|
2767
|
+
* 横は書いた縦列の中心、縦は段の番号に段の送り幅 (`PART_STACK_PITCH`) を掛けた位置。
|
|
2768
|
+
* 縦列の位置と幅は先に正す。 catalog は呼出側が渡す値で、生値のまま外接矩形を出すと
|
|
2769
|
+
* 拡大の基準が崩れて箱が桁違いに大きくなる (実測 = 指定間隔 200 が -31800 になった)。
|
|
2770
|
+
* 数でない段は 0 として扱う (#1018)。
|
|
2771
|
+
*/
|
|
2772
|
+
function 書いた置き方(part: CdlDiagram): 部品の置き方 {
|
|
2773
|
+
const lanes = Array.isArray(part.lanes) ? part.lanes : [];
|
|
2774
|
+
const nodes = Array.isArray(part.nodes) ? part.nodes : [];
|
|
2775
|
+
const 縦列 = new Map<string, { x: number; w: number }>();
|
|
2776
|
+
for (const l of lanes) {
|
|
2777
|
+
縦列.set(l.id, {
|
|
2778
|
+
x: typeof l.x === "number" && Number.isFinite(l.x) ? l.x : 0,
|
|
2779
|
+
w: positiveOr(l.width, 400),
|
|
2780
|
+
});
|
|
2781
|
+
}
|
|
2782
|
+
const stacks = nodes.map((n) =>
|
|
2783
|
+
typeof n.stack === "number" && Number.isFinite(n.stack) ? n.stack : 0,
|
|
2784
|
+
);
|
|
2785
|
+
const 中央の段 = (minOf(stacks, 0) + maxOf(stacks, 0)) / 2;
|
|
2786
|
+
return {
|
|
2787
|
+
縦列,
|
|
2788
|
+
...縦列の外接(縦列),
|
|
2789
|
+
要素: nodes.map((n, i) => {
|
|
2790
|
+
const l = 縦列.get(n.lane) ?? { x: 0, w: 320 };
|
|
2791
|
+
return { x: l.x + l.w / 2, y: ((stacks[i] ?? 0) - 中央の段) * PART_STACK_PITCH };
|
|
2792
|
+
}),
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
/**
|
|
2797
|
+
* 部品の要素 1 つの中心が、置く位置 (merge に渡す座標) からどれだけずれるか (#1992)。
|
|
2798
|
+
*
|
|
2799
|
+
* 組み込み (`mergePartIntoDiagram`) と見積り (`partExtent`) が同じ式を通る。 別々に書くと、
|
|
2800
|
+
* 格子が確保した場所と実際に置いた場所がずれる。
|
|
2801
|
+
*
|
|
2802
|
+
* 要素が自分で位置を書いていれば、その値を使う。 横は縦列と同じ写し方 (縦列の外接矩形の中心を
|
|
2803
|
+
* 置く位置に合わせて伸縮する) で写し、縦は置く位置に足すだけで伸縮を掛けない。
|
|
2804
|
+
*/
|
|
2805
|
+
function 部品の要素のずれ(
|
|
2806
|
+
node: { posX?: number; posY?: number },
|
|
2807
|
+
頁: { x: number; y: number },
|
|
2808
|
+
置き方: 部品の置き方,
|
|
2809
|
+
scaleX: number,
|
|
2810
|
+
scaleY: number,
|
|
2811
|
+
): { dx: number; dy: number } {
|
|
2812
|
+
return {
|
|
2813
|
+
dx: ((node.posX ?? 頁.x) - (置き方.左端 + 置き方.幅 / 2)) * scaleX,
|
|
2814
|
+
dy: node.posY ?? 頁.y * scaleY,
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2342
2818
|
/**
|
|
2343
2819
|
* パーツ 1 個が図の上で確保する図枠 (merge に渡す座標での表し方)。
|
|
2344
2820
|
*
|
|
@@ -2406,26 +2882,25 @@ export function partVisualSize(
|
|
|
2406
2882
|
/** 格子に並べる時の 1 行あたりの個数と隙間。 */
|
|
2407
2883
|
const PARTS_PER_ROW = 3;
|
|
2408
2884
|
const PARTS_GAP = 120;
|
|
2409
|
-
/**
|
|
2410
|
-
* 既存の図の下に置く時の目安。
|
|
2411
|
-
*
|
|
2412
|
-
* 既存の箱は自動配置なので、 この時点では座標を持たない。 箱の数から概算する。
|
|
2413
|
-
* 1 段あたりの高さは cdl の既定の縦送り幅に合わせる。
|
|
2414
|
-
*/
|
|
2415
|
-
const STACK_PITCH = 280;
|
|
2416
2885
|
|
|
2417
2886
|
/**
|
|
2418
|
-
*
|
|
2887
|
+
* 組み立てた図の下端 (#2002)。 箱が 1 つも無ければ `undefined`。
|
|
2888
|
+
*
|
|
2889
|
+
* **段数で概算しない**。 かつては箱の数から `箱 1 つ = 1 段 (280)` で見積もっていたが、
|
|
2890
|
+
* `flow` の箱は実際には高さ 68 で 168 おきに並ぶため 1 段あたり 112 ずつ余分に見積もる。
|
|
2891
|
+
* 誤差は箱の数に比例して積み上がり、実測で箱 2 つの図に 496、箱 4 つの図に 720 の空きが
|
|
2892
|
+
* 入っていた (部品どうしの間は 120)。
|
|
2419
2893
|
*
|
|
2420
|
-
*
|
|
2421
|
-
*
|
|
2422
|
-
*
|
|
2894
|
+
* **箱の下端ではなく図枠の下端を返す**。 格子は図枠どうしを `PARTS_GAP` 空けて並べるので、
|
|
2895
|
+
* 既存の図との間も図枠で測らないと、部品どうしの間と既存の図との間が揃わない
|
|
2896
|
+
* (箱で測ると 180、図枠で測ると 240 で、後者が部品どうしの段の間と一致する)。
|
|
2423
2897
|
*
|
|
2424
|
-
*
|
|
2425
|
-
*
|
|
2898
|
+
* **箱が無いことを 0 に潰さない**。 0 を下端として使うと、部品だけを並べた図で部品が
|
|
2899
|
+
* 図の上端へ貼り付く。 呼出側が「箱が無い」 と「下端が 0」 を分けられるように `undefined` を返す。
|
|
2426
2900
|
*/
|
|
2427
|
-
export function
|
|
2428
|
-
|
|
2901
|
+
export function partsBaseBottom(laid: LaidDiagram): number | undefined {
|
|
2902
|
+
if (laid.nodes.length === 0) return undefined;
|
|
2903
|
+
return laid.viewBox.y + laid.viewBox.h;
|
|
2429
2904
|
}
|
|
2430
2905
|
|
|
2431
2906
|
/**
|
|
@@ -2438,18 +2913,24 @@ export function partsBaseRows(nodes: ReadonlyArray<{ h?: number }>): number {
|
|
|
2438
2913
|
* 隣と重なる (実測 = 400 の次に 200 を置くと 280 重なった)。 段の高さも段内の最大高で揃える。
|
|
2439
2914
|
* 縦は自分の高さの半分だけ段の上端から下げて、 段内で上端を揃える。
|
|
2440
2915
|
*
|
|
2441
|
-
* @param
|
|
2916
|
+
* @param baseBottom 既存の図の下端 (`partsBaseBottom`)。 その下に間を空けて並べ始める。
|
|
2917
|
+
* 箱が 1 つも無い図では `undefined` を渡す
|
|
2442
2918
|
*/
|
|
2443
2919
|
export function partsGridCenters(
|
|
2444
|
-
|
|
2920
|
+
baseBottom: number | undefined,
|
|
2445
2921
|
items: ReadonlyArray<{ id: string; w: number; h: number }>,
|
|
2446
2922
|
): Map<string, { cx: number; cy: number }> {
|
|
2447
2923
|
const out = new Map<string, { cx: number; cy: number }>();
|
|
2448
2924
|
if (items.length === 0) return out;
|
|
2449
|
-
// 公開している関数なので、 呼出側が渡す値を入口で閉じる。
|
|
2450
|
-
// そのまま計算に入れると、
|
|
2451
|
-
|
|
2452
|
-
|
|
2925
|
+
// 公開している関数なので、 呼出側が渡す値を入口で閉じる。 数でない下端や桁溢れを
|
|
2926
|
+
// そのまま計算に入れると、 描けない座標を返すことになる。
|
|
2927
|
+
//
|
|
2928
|
+
// 負の下端は捨てない = 図は原点より上にも置ける。 0 で下げ止めると、上にある図の下に
|
|
2929
|
+
// 無駄な空きが入る
|
|
2930
|
+
const top =
|
|
2931
|
+
baseBottom === undefined || !Number.isFinite(baseBottom)
|
|
2932
|
+
? PARTS_GAP * 2
|
|
2933
|
+
: baseBottom + PARTS_GAP;
|
|
2453
2934
|
// 同じ名前が 2 度来たら先の方だけを見る。 後の分を残すと、 どちらを指したか決められない
|
|
2454
2935
|
// まま列の送り幅にも影響する
|
|
2455
2936
|
const seen = new Set<string>();
|
|
@@ -2516,6 +2997,44 @@ function partsBudget(
|
|
|
2516
2997
|
return accepted;
|
|
2517
2998
|
}
|
|
2518
2999
|
|
|
3000
|
+
/**
|
|
3001
|
+
* パーツを除いた図を組み立てて、その下端を返す (#2002)。 箱が 1 つも無ければ `undefined`。
|
|
3002
|
+
*
|
|
3003
|
+
* **宣言のままの箱は座標を持たない** (`CdlNode` に `cx` / `cy` が無い)。 下端を知るには
|
|
3004
|
+
* 一度組み立てる必要がある。 パーツを除いた図を組み立てるのは、パーツを含めて測ると
|
|
3005
|
+
* パーツを足すたびに下端が下がり、置き場所が自分自身に追随して逃げるため。
|
|
3006
|
+
*
|
|
3007
|
+
* ## 除く 3 つは、いまの呼び出し順では結果を変えない
|
|
3008
|
+
*
|
|
3009
|
+
* 箱 / 縦列 / 矢印の 3 つを除いているが、**この 3 つを残しても下端は 1 も動かない**
|
|
3010
|
+
* (変異試験で 3 つとも、また 3 つ同時でも落ちる検査は 0 件)。 この関数が走るのは
|
|
3011
|
+
* パーツを取り込む前で、その時点のパーツの仮の箱は書いた位置も縦列の段も反映しておらず、
|
|
3012
|
+
* 必ず本体の箱と同じ段に居るため。 図種 5 種 × 段の深さ 2 通り、位置を書いた深さ 3 通り、
|
|
3013
|
+
* 縦列に置いた段数 2 通りを実測して、1 件も差が出なかった。
|
|
3014
|
+
*
|
|
3015
|
+
* それでも残すのは、この関数が「パーツを除いた図を測る」 と読めることに意味があるため。
|
|
3016
|
+
* 呼び出し順が変わった時に黙って下端が動くより、除く形が書いてある方が直しやすい。
|
|
3017
|
+
*/
|
|
3018
|
+
function baseDiagramBottom(
|
|
3019
|
+
target: CdlDiagram,
|
|
3020
|
+
baseNodes: readonly CdlDiagram["nodes"][number][],
|
|
3021
|
+
partsLaneIds: ReadonlySet<string>,
|
|
3022
|
+
): number | undefined {
|
|
3023
|
+
// 箱が 1 つも無い図では組み立てない。 **返す値を変えるための分岐ではない**
|
|
3024
|
+
// (`partsBaseBottom` も箱 0 個で `undefined` を返す) = 組み立て 1 回ぶんを省くためだけの
|
|
3025
|
+
// 早い戻り。 変異試験でこの行を外しても落ちる検査は 0 件で、それが期待どおり
|
|
3026
|
+
if (baseNodes.length === 0) return undefined;
|
|
3027
|
+
const baseIds = new Set(baseNodes.map((n) => n.id));
|
|
3028
|
+
return partsBaseBottom(
|
|
3029
|
+
layout({
|
|
3030
|
+
...target,
|
|
3031
|
+
lanes: target.lanes.filter((l) => !partsLaneIds.has(l.id)),
|
|
3032
|
+
nodes: [...baseNodes],
|
|
3033
|
+
edges: target.edges.filter((e) => baseIds.has(e.from) && baseIds.has(e.to)),
|
|
3034
|
+
}),
|
|
3035
|
+
);
|
|
3036
|
+
}
|
|
3037
|
+
|
|
2519
3038
|
/**
|
|
2520
3039
|
* 位置を書かなかったパーツの、 merge に渡す座標。
|
|
2521
3040
|
*
|
|
@@ -2536,8 +3055,16 @@ function partGridCenters(
|
|
|
2536
3055
|
//
|
|
2537
3056
|
// merge 側は「縦横どちらも書かなかった時」 に格子へ落とす。 条件が食い違うと、 片方だけ
|
|
2538
3057
|
// 書いたパーツが格子の枠を 1 つ消費して後続がずれる (実測 = 後続の中心が 200 から 720 に動いた)
|
|
3058
|
+
//
|
|
3059
|
+
// 縦列に置く部品も外す (#1980)。 格子の枠を使うと、縦列へ動かした後に後続の部品との間に
|
|
3060
|
+
// 空きが残る。 相対の位置を解く側 (`partBoxes`) も同じ関数を通るので、基準の位置は揃う
|
|
3061
|
+
const 縦列の部品 = 縦列に置く部品(target, doc).縦列;
|
|
2539
3062
|
const autoActors = partsActors.filter(
|
|
2540
|
-
(a) =>
|
|
3063
|
+
(a) =>
|
|
3064
|
+
a.posX === undefined &&
|
|
3065
|
+
a.posY === undefined &&
|
|
3066
|
+
a.posRel === undefined &&
|
|
3067
|
+
!縦列の部品.has(a.name),
|
|
2541
3068
|
);
|
|
2542
3069
|
if (autoActors.length === 0) return new Map();
|
|
2543
3070
|
// パーツ自身の仮の箱は数えない。 この時点では未削除で残っており、 数えるとパーツを足すたびに
|
|
@@ -2596,7 +3123,7 @@ function partGridCenters(
|
|
|
2596
3123
|
extents.set(a.name, partFrameExtent(part, t.w, t.h));
|
|
2597
3124
|
}
|
|
2598
3125
|
const centers = partsGridCenters(
|
|
2599
|
-
|
|
3126
|
+
baseDiagramBottom(target, baseNodes, partsLaneIds),
|
|
2600
3127
|
placedActors.map((a) => ({ id: a.name, ...extents.get(a.name)! })),
|
|
2601
3128
|
);
|
|
2602
3129
|
// merge に渡すのは段の中心。 矩形の中心とのずれを引く。 引かないと、 段ごとに箱の高さが
|
|
@@ -2659,6 +3186,240 @@ function partBoxes(
|
|
|
2659
3186
|
return out;
|
|
2660
3187
|
}
|
|
2661
3188
|
|
|
3189
|
+
/**
|
|
3190
|
+
* 部品へ引いた矢印を、部品の図の要素へ繋ぎ直す手順を作る (#1979)。
|
|
3191
|
+
*
|
|
3192
|
+
* 部品を置くと組み立ては仮の箱を消し、部品の図の要素を `{名前}__{要素の id}` で足す。 仮の箱へ
|
|
3193
|
+
* 引いた矢印は繋ぎ先を失うため、以前は知らせも無く消えていた。
|
|
3194
|
+
*
|
|
3195
|
+
* | 部品 | 繋ぎ先 |
|
|
3196
|
+
* |---|---|
|
|
3197
|
+
* | 要素 1 つ | その要素 |
|
|
3198
|
+
* | 要素 2 つ以上 | 矢印の行に書いた `fromPartNode` / `toPartNode` の要素 |
|
|
3199
|
+
*
|
|
3200
|
+
* **要素が 2 つ以上ある部品で 1 つを自動で選ばない**。 カタログの部品は外枠を持たず同格の要素が並ぶ
|
|
3201
|
+
* (信号の 3 灯、星 5 つ、棒 5 本) ため、最初の要素や一番大きい要素を選ぶと「その 1 つだけ」 を
|
|
3202
|
+
* 指す矢印に見える。 名指しが無い時、名指しした要素が無い時、部品を取り込まなかった時 (`part` が
|
|
3203
|
+
* `undefined`) は矢印を外し、矢印ごとに 1 件知らせる。
|
|
3204
|
+
*
|
|
3205
|
+
* **繋ぐのは書いた矢印だけ** = 矢印を書いた行の端の名前が、この部品の名前と一致する時。 静止した
|
|
3206
|
+
* `type: flow` は行を書かなくても箱を並び順で繋ぐが、その矢印は書き手が部品へ引いたものではないので
|
|
3207
|
+
* 従来どおり知らせずに外す。 繋ぐと、部品を本文から抜いて図の上に重ねる編集画面 (行が部品を指す
|
|
3208
|
+
* 本文だけを抜かずに描く) と絵が食い違う (実測 = 箱と部品を並べただけの本文に、組み立て側だけ矢印が出た)。
|
|
3209
|
+
*/
|
|
3210
|
+
function 部品の要素へ繋ぐ(
|
|
3211
|
+
部品の名前: string,
|
|
3212
|
+
partId: string,
|
|
3213
|
+
part: CdlDiagram | undefined,
|
|
3214
|
+
edgeSteps: Map<string, DslStep> | undefined,
|
|
3215
|
+
onNotice: ((notice: CompileNotice) => void) | undefined,
|
|
3216
|
+
): (edge: CdlEdge, 端: { from: boolean; to: boolean }) => CdlEdge | undefined {
|
|
3217
|
+
const 要素 = (part?.nodes ?? []).map((n) => n.id);
|
|
3218
|
+
const 見せる数 = 8;
|
|
3219
|
+
const 要素の一覧 = `${要素.slice(0, 見せる数).map(truncateForMessage).join(", ")}${要素.length > 見せる数 ? ` ほか ${要素.length - 見せる数} 件` : ""}`;
|
|
3220
|
+
return (edge, 端) => {
|
|
3221
|
+
const s = edgeSteps?.get(edge.id);
|
|
3222
|
+
const 繋いだ: CdlEdge = { ...edge };
|
|
3223
|
+
for (const 側 of ["from", "to"] as const) {
|
|
3224
|
+
if (!端[側]) continue;
|
|
3225
|
+
// 書いていない矢印 (並び順で作られた矢印) は知らせずに外す
|
|
3226
|
+
if (s === undefined || s[側] !== 部品の名前) return undefined;
|
|
3227
|
+
const 欄 = 側 === "from" ? "fromPartNode" : "toPartNode";
|
|
3228
|
+
const 名指し = s[欄];
|
|
3229
|
+
let 理由: { message: string; hint: string } | undefined;
|
|
3230
|
+
if (part === undefined) {
|
|
3231
|
+
理由 = {
|
|
3232
|
+
message: "を図に取り込まなかった",
|
|
3233
|
+
hint: "部品が図に入らないため、矢印の端にできません",
|
|
3234
|
+
};
|
|
3235
|
+
} else if (名指し !== undefined) {
|
|
3236
|
+
if (要素.includes(名指し)) 繋いだ[側] = `${部品の名前}__${名指し}`;
|
|
3237
|
+
else
|
|
3238
|
+
理由 = {
|
|
3239
|
+
message: `の中に ${欄} に書いた "${truncateForMessage(名指し)}" という要素が無い`,
|
|
3240
|
+
hint: `この部品の要素 = ${要素の一覧}`,
|
|
3241
|
+
};
|
|
3242
|
+
} else if (要素.length === 1) {
|
|
3243
|
+
繋いだ[側] = `${部品の名前}__${要素[0]}`;
|
|
3244
|
+
} else {
|
|
3245
|
+
理由 =
|
|
3246
|
+
要素.length === 0
|
|
3247
|
+
? { message: "の中に繋げる要素が無い", hint: "この部品は要素を持たないため、矢印の端にできません" }
|
|
3248
|
+
: {
|
|
3249
|
+
message: "の中のどの要素に繋ぐかが決まらない",
|
|
3250
|
+
hint: `要素が 2 つ以上ある部品は、矢印に ${欄}: <要素の id> を書いて繋ぐ要素を選ぶ (要素 = ${要素の一覧})`,
|
|
3251
|
+
};
|
|
3252
|
+
}
|
|
3253
|
+
if (理由 === undefined) continue;
|
|
3254
|
+
const 矢印 = `"${truncateForMessage(s.from)}" から "${truncateForMessage(s.to)}" への矢印`;
|
|
3255
|
+
onNotice?.({
|
|
3256
|
+
kind: "part-edge-dropped",
|
|
3257
|
+
actor: 部品の名前,
|
|
3258
|
+
line: s.pos.line,
|
|
3259
|
+
message: `${矢印}は、"${truncateForMessage(部品の名前)}" (${truncateForMessage(partId)}) ${理由.message}ため外しました`,
|
|
3260
|
+
hint: 理由.hint,
|
|
3261
|
+
});
|
|
3262
|
+
return undefined;
|
|
3263
|
+
}
|
|
3264
|
+
return 繋いだ;
|
|
3265
|
+
};
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
/**
|
|
3269
|
+
* 矢印に書いた部品の要素の名指し (`fromPartNode` / `toPartNode`) が効かないことを知らせる (#1979)。
|
|
3270
|
+
*
|
|
3271
|
+
* 名指しは部品の端でだけ読む。 部品でない箱の端に書くと値はどこにも届かず、黙って捨てると
|
|
3272
|
+
* 「書いたのに繋ぎ先が変わらない」 が手掛かりなしで起きる。
|
|
3273
|
+
*
|
|
3274
|
+
* 順序図 (`sequence` / `solidity`) は板の言づてが縦の線に届き、部品の要素へは繋がないため、
|
|
3275
|
+
* 部品の端に書いても効かない。 部品の一覧に無い部品は組み立てが部品を引いてから知らせる。
|
|
3276
|
+
*/
|
|
3277
|
+
function reportPartNodeNotHonored(
|
|
3278
|
+
doc: DslDocument,
|
|
3279
|
+
onNotice?: (notice: CompileNotice) => void,
|
|
3280
|
+
): void {
|
|
3281
|
+
if (!onNotice) return;
|
|
3282
|
+
const 順序図 = doc.type === "sequence" || doc.type === "solidity";
|
|
3283
|
+
const 部品 = new Set(doc.actors.filter((a) => a.partId !== undefined).map((a) => a.name));
|
|
3284
|
+
const 居る = new Set(doc.actors.map((a) => a.name));
|
|
3285
|
+
for (const s of doc.flow) {
|
|
3286
|
+
for (const [側, 欄] of [
|
|
3287
|
+
["from", "fromPartNode"],
|
|
3288
|
+
["to", "toPartNode"],
|
|
3289
|
+
] as const) {
|
|
3290
|
+
const 値 = s[欄];
|
|
3291
|
+
if (値 === undefined) continue;
|
|
3292
|
+
const 名 = s[側];
|
|
3293
|
+
// 居ない名前は `flow-actor-missing` が知らせる
|
|
3294
|
+
if (!居る.has(名)) continue;
|
|
3295
|
+
if (!順序図 && 部品.has(名)) continue;
|
|
3296
|
+
onNotice({
|
|
3297
|
+
kind: "part-node-ignored",
|
|
3298
|
+
actor: 名,
|
|
3299
|
+
line: s.pos.line,
|
|
3300
|
+
message: 順序図
|
|
3301
|
+
? `順序図の言づては "${truncateForMessage(名)}" の縦の線に届くため、${欄} に書いた "${truncateForMessage(値)}" は効きません`
|
|
3302
|
+
: `"${truncateForMessage(名)}" は部品ではないため、${欄} に書いた "${truncateForMessage(値)}" は効きません`,
|
|
3303
|
+
hint: 順序図
|
|
3304
|
+
? "部品の中の要素へ矢印を繋ぐのは、矢印を箱の間に引く図種 (flow / swimlane など)"
|
|
3305
|
+
: "部品の中の要素の名指しは、kind に部品の名前を書いた箱の端にだけ効く",
|
|
3306
|
+
});
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
|
|
3311
|
+
/**
|
|
3312
|
+
* 部品のために作られた仮の箱の id かを見分ける。
|
|
3313
|
+
*
|
|
3314
|
+
* 仮の箱の id は部品の名前の slug か、それを頭に持つ形 (`a-header` / `s0-a`) で作られる。
|
|
3315
|
+
* 仮の箱を消す側 (`cleanupPlaceholderActor`) と、部品を置く縦列を探す側 (`縦列に置く部品`) が
|
|
3316
|
+
* 同じ判定を使う = 別々に持つと、消した箱と縦列を探した箱が食い違う。
|
|
3317
|
+
*
|
|
3318
|
+
* 素の登場人物が持つ id は仮の箱に数えない (#1466)。 名前が重なった素の登場人物は
|
|
3319
|
+
* `重なりを解く` 側で `a-c40bf6` のような id に作り替えられるため、同じ頭で始まり
|
|
3320
|
+
* **本体の箱まで巻き込む** (実測で `flow` / `swimlane` / `er` / `state` / `topology` /
|
|
3321
|
+
* `class` / `c4` の 7 図種すべてで素の箱が黙って消えていた)。
|
|
3322
|
+
*
|
|
3323
|
+
* 名前の頭が自分と重なる別の登場人物 (`設備` に対する `設備-予備`) の仮の箱も数えない (#1980)。
|
|
3324
|
+
* 数えると `設備` の仮の箱が 2 本の縦列にまたがって見え、`設備` を消す時に `設備-予備` の
|
|
3325
|
+
* 仮の箱と縦列まで消す。 部品同士でも起き、後から組み込む `設備-予備` が消えた縦列を指して
|
|
3326
|
+
* 配置が止まっていた (`cdl layout: node "設備-予備__ind" の lane "設備-予備" が定義されていない`)。
|
|
3327
|
+
*/
|
|
3328
|
+
function 仮の箱のidか(doc: DslDocument, name: string): (id: string) => boolean {
|
|
3329
|
+
const aliasSlug = slugify(name);
|
|
3330
|
+
const 素の箱のid = new Set(
|
|
3331
|
+
doc.actors.filter((x) => x.partId === undefined).map((x) => slugify(x.name)),
|
|
3332
|
+
);
|
|
3333
|
+
const 頭が重なるid = [
|
|
3334
|
+
...new Set(
|
|
3335
|
+
doc.actors
|
|
3336
|
+
.filter((x) => x.name !== name)
|
|
3337
|
+
.map((x) => slugify(x.name))
|
|
3338
|
+
.filter((s) => s.startsWith(`${aliasSlug}-`) || s.endsWith(`-${aliasSlug}`)),
|
|
3339
|
+
),
|
|
3340
|
+
];
|
|
3341
|
+
// sequence step anchor = `s{N}-{slug}` pattern
|
|
3342
|
+
const 自分の形か = (id: string, slug: string): boolean =>
|
|
3343
|
+
id === slug || id.startsWith(`${slug}-`) || (/^s\d+-/.test(id) && id.endsWith(`-${slug}`));
|
|
3344
|
+
return (id: string): boolean => {
|
|
3345
|
+
if (素の箱のid.has(id)) return false;
|
|
3346
|
+
if (頭が重なるid.some((s) => 自分の形か(id, s))) return false;
|
|
3347
|
+
return 自分の形か(id, aliasSlug);
|
|
3348
|
+
};
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
/**
|
|
3352
|
+
* 縦列の中に置く部品と、その縦列の id (#1980)。
|
|
3353
|
+
*
|
|
3354
|
+
* 位置を書かない部品は格子に並ぶが、格子は縦列の位置を読まない。 部品が縦列に属する図では、
|
|
3355
|
+
* 部品の要素だけが縦列に属し、描く位置は別の縦列の上になっていた (実測 = 縦列 b 480〜890 に
|
|
3356
|
+
* 属する部品が 60〜420 に描かれた)。
|
|
3357
|
+
*
|
|
3358
|
+
* 縦列に置くのは、位置 (`posX` / `posY` / `位置:`) を書かない部品のうち次のどちらかに当たるもの。
|
|
3359
|
+
*
|
|
3360
|
+
* | 形 | 縦列 |
|
|
3361
|
+
* |---|---|
|
|
3362
|
+
* | `lane:` に図にある縦列を書いた | 書いた縦列 |
|
|
3363
|
+
* | 仮の箱が自分だけの縦列を持つ (`swimlane` / `state` / `er` / `class` の作り方) | その縦列 |
|
|
3364
|
+
*
|
|
3365
|
+
* **自分だけの縦列は図の形で見分ける**。 図種の名前で分けると、縦列の作り方を変えた図種で
|
|
3366
|
+
* 黙って外れる。 仮の箱が入った縦列に他の登場人物の箱が無く、図に縦列が 2 本以上ある時に
|
|
3367
|
+
* 限る = 全員が 1 本の縦列を共有する図種 (`flow` / `topology` / `c4`) は格子のまま残る。
|
|
3368
|
+
* 仮の箱が作られない形 (流れに現れない `swimlane` の登場人物) は、名札が部品の名前の空の縦列で引く。
|
|
3369
|
+
*
|
|
3370
|
+
* **順序図 (`sequence` / `solidity`) は対象にしない**。 1 枚の板で描き、縦列を持たない (#1466)。
|
|
3371
|
+
*
|
|
3372
|
+
* **他の箱の位置の基準になっている部品は縦列に置かない**。 相対の位置 (`位置: 印 の右 200`) は
|
|
3373
|
+
* 組み立ての前に格子の位置を基準に解くため、基準の部品だけを縦列へ動かすと書いた位置関係が
|
|
3374
|
+
* 崩れる。 外した部品は `基準のため外した` に入れ、呼出側が知らせる。
|
|
3375
|
+
*/
|
|
3376
|
+
function 縦列に置く部品(
|
|
3377
|
+
target: CdlDiagram,
|
|
3378
|
+
doc: DslDocument,
|
|
3379
|
+
): { 縦列: Map<string, string>; 基準のため外した: Map<string, string> } {
|
|
3380
|
+
const 縦列 = new Map<string, string>();
|
|
3381
|
+
const 基準のため外した = new Map<string, string>();
|
|
3382
|
+
if (doc.type === "sequence" || doc.type === "solidity") return { 縦列, 基準のため外した };
|
|
3383
|
+
const 基準の名前 = new Set(
|
|
3384
|
+
doc.actors.map((a) => a.posRel?.anchor).filter((n): n is string => n !== undefined),
|
|
3385
|
+
);
|
|
3386
|
+
const 縦列のid = new Set(target.lanes.map((l) => l.id));
|
|
3387
|
+
for (const a of doc.actors) {
|
|
3388
|
+
if (a.partId === undefined) continue;
|
|
3389
|
+
// 同じ名前を 2 度書いた時は先の 1 件を使う (組み立て側の他の判定と同じ)
|
|
3390
|
+
if (縦列.has(a.name) || 基準のため外した.has(a.name)) continue;
|
|
3391
|
+
if (a.posX !== undefined || a.posY !== undefined || a.posRel !== undefined) continue;
|
|
3392
|
+
const 置く縦列 = 部品の縦列(target, doc, a, 縦列のid);
|
|
3393
|
+
if (置く縦列 === undefined) continue;
|
|
3394
|
+
if (基準の名前.has(a.name)) 基準のため外した.set(a.name, 置く縦列);
|
|
3395
|
+
else 縦列.set(a.name, 置く縦列);
|
|
3396
|
+
}
|
|
3397
|
+
return { 縦列, 基準のため外した };
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
/** 部品を置く縦列。 書いた縦列か、仮の箱が自分だけで使う縦列。 どちらも無ければ `undefined` */
|
|
3401
|
+
function 部品の縦列(
|
|
3402
|
+
target: CdlDiagram,
|
|
3403
|
+
doc: DslDocument,
|
|
3404
|
+
a: DslActor,
|
|
3405
|
+
縦列のid: ReadonlySet<string>,
|
|
3406
|
+
): string | undefined {
|
|
3407
|
+
if (a.lane !== undefined) return 縦列のid.has(a.lane) ? a.lane : undefined;
|
|
3408
|
+
if (target.lanes.length < 2) return undefined;
|
|
3409
|
+
const 仮の箱か = 仮の箱のidか(doc, a.name);
|
|
3410
|
+
const 仮の箱の縦列 = new Set(target.nodes.filter((n) => 仮の箱か(n.id)).map((n) => n.lane));
|
|
3411
|
+
// 2 本にまたがる入力は見つかっていない (実測 = 縦列に置く 7 図種とも仮の箱は登場人物ごとに 1 つ。
|
|
3412
|
+
// 名前の頭が重なる登場人物の箱は `仮の箱のidか` が外す)。 またがった時にどちらかの縦列を選ぶと
|
|
3413
|
+
// 他の縦列を部品が占めるため、格子に残す
|
|
3414
|
+
if (仮の箱の縦列.size > 1) return undefined;
|
|
3415
|
+
const [入っていた] = [...仮の箱の縦列];
|
|
3416
|
+
const 候補 =
|
|
3417
|
+
入っていた ?? target.lanes.find((l) => l.label === a.name && !target.nodes.some((n) => n.lane === l.id))?.id;
|
|
3418
|
+
if (候補 === undefined) return undefined;
|
|
3419
|
+
// 他の登場人物の箱が同じ縦列に居れば、自分だけの縦列ではない
|
|
3420
|
+
return target.nodes.some((n) => n.lane === 候補 && !仮の箱か(n.id)) ? undefined : 候補;
|
|
3421
|
+
}
|
|
3422
|
+
|
|
2662
3423
|
/**
|
|
2663
3424
|
* パーツ用に作られた仮の箱 / 線 / 列を掃除する (#1015 で helper 化)。
|
|
2664
3425
|
*
|
|
@@ -2669,6 +3430,11 @@ function cleanupPlaceholderActor(
|
|
|
2669
3430
|
target: CdlDiagram,
|
|
2670
3431
|
doc: DslDocument,
|
|
2671
3432
|
a: { name: string; lane?: string },
|
|
3433
|
+
/**
|
|
3434
|
+
* 仮の箱に繋がっていた矢印を、部品の図の要素へ繋ぎ直す (#1979)。 仮の箱の側の端を受け取り、
|
|
3435
|
+
* 繋ぎ直した矢印を返す。 `undefined` を返した矢印は外す。 渡さなければ全て外す
|
|
3436
|
+
*/
|
|
3437
|
+
繋ぎ直す?: (edge: CdlEdge, 端: { from: boolean; to: boolean }) => CdlEdge | undefined,
|
|
2672
3438
|
): void {
|
|
2673
3439
|
const aliasSlug = slugify(a.name);
|
|
2674
3440
|
const ownedLaneIds = new Set<string>();
|
|
@@ -2683,41 +3449,30 @@ function cleanupPlaceholderActor(
|
|
|
2683
3449
|
for (const n of target.nodes) {
|
|
2684
3450
|
if (ownedLaneIds.has(n.lane)) ownedNodeIds.add(n.id);
|
|
2685
3451
|
}
|
|
2686
|
-
/*
|
|
2687
|
-
* 素の登場人物が持つ id は消さない (#1466)。
|
|
2688
|
-
*
|
|
2689
|
-
* 下の頭一致は、見本のために作られた仮の箱 (`a-header` / `s0-a`) を拾うためのもの。
|
|
2690
|
-
* ところが名前が重なった素の登場人物は `重なりを解く` 側で `a-c40bf6` のような id に
|
|
2691
|
-
* 作り替えられるため、同じ頭で始まり **本体の箱まで巻き込む**。
|
|
2692
|
-
*
|
|
2693
|
-
* 見本と重なる名前を書いた図で、素の箱が黙って消えていた (実測で `flow` / `swimlane` /
|
|
2694
|
-
* `er` / `state` / `topology` / `class` / `c4` の 7 図種すべて)。 順序図だけは面ごとの
|
|
2695
|
-
* 縦列で引けたため免れており、#1466 で板になって縦列が無くなると同じ穴に落ちる。
|
|
2696
|
-
*/
|
|
2697
|
-
const 素の箱のid = new Set(
|
|
2698
|
-
doc.actors.filter((x) => x.partId === undefined).map((x) => slugify(x.name)),
|
|
2699
|
-
);
|
|
2700
3452
|
// actor 専用 lane を引き当てられない経路 (flow / topology 等の共有 lane preset) は従来どおり dragon
|
|
2701
3453
|
// slug の prefix match に fallback する。 これらは 1 actor = 1 node (id = slug) の生成規則。
|
|
2702
|
-
const matchesAliasSlug = (
|
|
2703
|
-
if (素の箱のid.has(id)) return false;
|
|
2704
|
-
if (id === aliasSlug) return true;
|
|
2705
|
-
if (id.startsWith(`${aliasSlug}-`)) return true;
|
|
2706
|
-
// sequence step anchor = `s{N}-{aliasSlug}` pattern
|
|
2707
|
-
if (/^s\d+-/.test(id) && id.endsWith(`-${aliasSlug}`)) return true;
|
|
2708
|
-
return false;
|
|
2709
|
-
};
|
|
3454
|
+
const matchesAliasSlug = 仮の箱のidか(doc, a.name);
|
|
2710
3455
|
const relatedToActor = (id: string): boolean =>
|
|
2711
3456
|
ownedLaneIds.size > 0 ? ownedNodeIds.has(id) : matchesAliasSlug(id);
|
|
3457
|
+
// 仮の箱が入っていた縦列。 仮の箱を消して空になった縦列は下で消す (#1973)
|
|
3458
|
+
const 仮の箱の縦列 = new Set(target.nodes.filter((n) => relatedToActor(n.id)).map((n) => n.lane));
|
|
2712
3459
|
target.nodes = target.nodes.filter((n) => !relatedToActor(n.id));
|
|
2713
|
-
//
|
|
2714
|
-
//
|
|
3460
|
+
// 仮の箱に繋がっていた矢印は、部品の要素へ繋ぎ直せたものだけ残す (#1979)。 以前は全て消しており、
|
|
3461
|
+
// 部品へ引いた矢印が知らせも無く図から消えていた。 繋ぎ直した矢印は id を変えないので、段の
|
|
3462
|
+
// 点灯 (`activate`) からも外さない。 外した矢印の id は `activate` に残ると存在しない参照になるため回収する
|
|
2715
3463
|
const removedEdgeIds = new Set<string>();
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
3464
|
+
const 残す矢印: CdlEdge[] = [];
|
|
3465
|
+
for (const e of target.edges) {
|
|
3466
|
+
const 端 = { from: relatedToActor(e.from), to: relatedToActor(e.to) };
|
|
3467
|
+
if (!端.from && !端.to) {
|
|
3468
|
+
残す矢印.push(e);
|
|
3469
|
+
continue;
|
|
3470
|
+
}
|
|
3471
|
+
const 繋いだ = 繋ぎ直す?.(e, 端);
|
|
3472
|
+
if (繋いだ) 残す矢印.push(繋いだ);
|
|
3473
|
+
else removedEdgeIds.add(e.id);
|
|
3474
|
+
}
|
|
3475
|
+
target.edges = 残す矢印;
|
|
2721
3476
|
// lane も削除 = sequence preset は parts actor 用に lane (id = aliasSlug、 label = actor 名) を
|
|
2722
3477
|
// 生成する。 node/edge だけ消して lane を残すと、 merge 後の part 側 lane (label = alias) と 2 本が
|
|
2723
3478
|
// 同じ label を lane-label として描画し二重表示になる (actor ラベル二重表示 bug の root cause)。
|
|
@@ -2749,6 +3504,10 @@ function cleanupPlaceholderActor(
|
|
|
2749
3504
|
if (残る箱を持つ.has(l.id)) return true;
|
|
2750
3505
|
if (l.label === a.name) return false;
|
|
2751
3506
|
if (l.id === aliasSlug) return false;
|
|
3507
|
+
// 図種が自動で作った縦列 (`flow` 等) に部品の箱しか無かった図では、仮の箱を消すと縦列が
|
|
3508
|
+
// 空のまま残る (実測 = 部品 1 つだけの図に中身の無い `flow` の縦列)。 書き手が `lanes:` に
|
|
3509
|
+
// 書いた縦列は、空でも書いたとおりに残す (#1973)
|
|
3510
|
+
if (仮の箱の縦列.has(l.id) && !Object.hasOwn(doc.lanes ?? {}, l.id)) return false;
|
|
2752
3511
|
return true;
|
|
2753
3512
|
});
|
|
2754
3513
|
// 削除された node / edge を activate 参照している既存 phase の cleanup (node 削除と同じ判定経路
|
|
@@ -2769,6 +3528,8 @@ function mergePartsFromActors(
|
|
|
2769
3528
|
partsCatalog?: Record<string, CdlDiagram>,
|
|
2770
3529
|
onNotice?: (notice: CompileNotice) => void,
|
|
2771
3530
|
derivedSourceLines?: Map<string, number[]>,
|
|
3531
|
+
/** 矢印の id から、その矢印を書いた行。 行に書いた要素の名指しを読む (#1979) */
|
|
3532
|
+
edgeSteps?: Map<string, DslStep>,
|
|
2772
3533
|
): CdlDiagram {
|
|
2773
3534
|
const partsActors = doc.actors.filter((a) => a.partId !== undefined);
|
|
2774
3535
|
if (partsActors.length === 0) return target;
|
|
@@ -2793,6 +3554,10 @@ function mergePartsFromActors(
|
|
|
2793
3554
|
// 格子より先に決める。 後にすると、落とす見本が格子の枠を消費して後続がずれる
|
|
2794
3555
|
const budget = partsBudget(target, partsActors, partsCatalog);
|
|
2795
3556
|
const gridCenters = partGridCenters(target, doc, partsCatalog, budget, partsActors);
|
|
3557
|
+
// 縦列の中に置く部品 (#1980)。 仮の箱を消す前に探す = 消した後は自分だけの縦列を見分けられない
|
|
3558
|
+
const { 縦列: 縦列の部品, 基準のため外した } = 縦列に置く部品(target, doc);
|
|
3559
|
+
const 縦列に置いた: 縦列に置いた部品[] = [];
|
|
3560
|
+
const 自分の縦列に置いた: 自分の縦列に置いた部品[] = [];
|
|
2796
3561
|
|
|
2797
3562
|
for (const [actorIndex, actor] of partsActors.entries()) {
|
|
2798
3563
|
const partId = actor.partId;
|
|
@@ -2815,8 +3580,14 @@ function mergePartsFromActors(
|
|
|
2815
3580
|
? `要素数が上限 (${MAX_INPUT_ELEMENTS}) を超えています`
|
|
2816
3581
|
: `図全体の要素数が上限 (${MAX_INPUT_ELEMENTS}) を超えます`,
|
|
2817
3582
|
});
|
|
2818
|
-
// 落とす時も仮の箱を掃除する。
|
|
2819
|
-
|
|
3583
|
+
// 落とす時も仮の箱を掃除する。 残すと格子から外した後続の見本と重なる。
|
|
3584
|
+
// 部品が図に入らないので、部品へ引いた矢印も繋ぎ先が無い = 矢印ごとに知らせて外す
|
|
3585
|
+
cleanupPlaceholderActor(
|
|
3586
|
+
target,
|
|
3587
|
+
doc,
|
|
3588
|
+
actor,
|
|
3589
|
+
部品の要素へ繋ぐ(actor.name, partId, undefined, edgeSteps, onNotice),
|
|
3590
|
+
);
|
|
2820
3591
|
continue;
|
|
2821
3592
|
}
|
|
2822
3593
|
const part = found;
|
|
@@ -2826,6 +3597,25 @@ function mergePartsFromActors(
|
|
|
2826
3597
|
`[dragon] parts kind "${partId}" not found in partsCatalog (actor: ${actor.name})`,
|
|
2827
3598
|
);
|
|
2828
3599
|
}
|
|
3600
|
+
// 一覧に無い部品は仮の箱のまま描かれ、矢印も仮の箱に繋がる。 要素の名指しは効かない
|
|
3601
|
+
for (const s of doc.flow) {
|
|
3602
|
+
for (const [側, 欄] of [
|
|
3603
|
+
["from", "fromPartNode"],
|
|
3604
|
+
["to", "toPartNode"],
|
|
3605
|
+
] as const) {
|
|
3606
|
+
const 値 = s[欄];
|
|
3607
|
+
if (値 === undefined || s[側] !== actor.name) continue;
|
|
3608
|
+
// 順序図は部品の有無に依らず名指しが効かない。 そちらの知らせと 2 度出さない
|
|
3609
|
+
if (doc.type === "sequence" || doc.type === "solidity") continue;
|
|
3610
|
+
onNotice?.({
|
|
3611
|
+
kind: "part-node-ignored",
|
|
3612
|
+
actor: actor.name,
|
|
3613
|
+
line: s.pos.line,
|
|
3614
|
+
message: `"${truncateForMessage(actor.name)}" (${truncateForMessage(partId)}) は部品の一覧に無いため、${欄} に書いた "${truncateForMessage(値)}" は効きません`,
|
|
3615
|
+
hint: "kind に部品の名前を書く (綴りを確かめる)",
|
|
3616
|
+
});
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
2829
3619
|
continue;
|
|
2830
3620
|
}
|
|
2831
3621
|
// codex-review MAJOR fix (§ sequence header/footer/spacer 削除) = preset (sequence 等) が生成した
|
|
@@ -2843,14 +3633,80 @@ function mergePartsFromActors(
|
|
|
2843
3633
|
// 厳密収集する。 slug の prefix 推測を挟まないため、 slug 実装差の取りこぼしと、 別 actor を巻き込む
|
|
2844
3634
|
// 誤削除 (parts actor `a_b` の lane id `a-b` が actor `a-b-c` の `a-b-c-header` に prefix match する)
|
|
2845
3635
|
// の両方を同時に排除する。
|
|
2846
|
-
|
|
3636
|
+
//
|
|
3637
|
+
// 仮の箱に繋がっていた矢印は、部品の図の要素へ繋ぎ直す (#1979)。
|
|
3638
|
+
// 縦列に置く部品は、仮の箱の縦列を消さずに残す (#1980)。 部品用の縦列 (`名前__l`) を足すと、
|
|
3639
|
+
// 縦列が書いた順ではなく仮に置いた横位置の順に並ぶ (実測 = 3 人目に書いた部品が真ん中になった)
|
|
3640
|
+
const 置く縦列 = 縦列の部品.get(actor.name);
|
|
3641
|
+
cleanupPlaceholderActor(
|
|
3642
|
+
target,
|
|
3643
|
+
doc,
|
|
3644
|
+
置く縦列 !== undefined ? { name: actor.name, lane: 置く縦列 } : actor,
|
|
3645
|
+
部品の要素へ繋ぐ(actor.name, partId, part, edgeSteps, onNotice),
|
|
3646
|
+
);
|
|
3647
|
+
const 外した縦列 = 基準のため外した.get(actor.name);
|
|
3648
|
+
if (外した縦列 !== undefined) {
|
|
3649
|
+
onNotice?.({
|
|
3650
|
+
kind: "part-lane-ignored",
|
|
3651
|
+
actor: actor.name,
|
|
3652
|
+
line: actor.pos?.line ?? 0,
|
|
3653
|
+
message: `"${actor.name}" は他の箱の位置の基準になっているため、縦列 "${target.lanes.find((l) => l.id === 外した縦列)?.label ?? 外した縦列}" には置かず図の下に並べました`,
|
|
3654
|
+
hint: `縦列に置きたい時は、"${actor.name}" を基準にした位置 (位置: ${actor.name} の右 200 など) を座標で書く`,
|
|
3655
|
+
});
|
|
3656
|
+
}
|
|
2847
3657
|
const merged = applyColorHex(part, actor.colorHex, actor.stateOverride ?? {});
|
|
3658
|
+
// 色番号は部品の色の状態へ入れる。 塗りを図形に直接書いた部品 (`arc-gauge` 等) は入れる先が
|
|
3659
|
+
// 無く、書いても絵が変わらない。 黙って既定の色で描くと手掛かりが残らないので知らせる (#1973)
|
|
3660
|
+
if (actor.colorHex && !(part.states ?? []).some((st) => isColorValue(st.initial))) {
|
|
3661
|
+
onNotice?.({
|
|
3662
|
+
kind: "part-color-ignored",
|
|
3663
|
+
actor: actor.name,
|
|
3664
|
+
line: actor.pos?.line ?? 0,
|
|
3665
|
+
message: `"${actor.name}" (${partId}) は色を変えられる状態を持たないため、色番号 ${actor.colorHex} は効きません`,
|
|
3666
|
+
hint: "色を変えられる部品は、初期値が色番号の状態を持つもの (例 = state-indicator)",
|
|
3667
|
+
});
|
|
3668
|
+
}
|
|
3669
|
+
// 色の名前は箱の色にしか効かず、部品の色の状態は色番号しか受けない。 名前の色は描く側の
|
|
3670
|
+
// 配色の変数 (`--cdl-tone-*`) で決まり固定の色番号を持たないため、置き換えて入れることもしない
|
|
3671
|
+
if (actor.partColorName) {
|
|
3672
|
+
onNotice?.({
|
|
3673
|
+
kind: "part-color-ignored",
|
|
3674
|
+
actor: actor.name,
|
|
3675
|
+
line: actor.pos?.line ?? 0,
|
|
3676
|
+
// 残っている名前は読み替えた後の正規の名前 (`成功` なら `success`) なので文には出さない
|
|
3677
|
+
message: `"${actor.name}" (${partId}) の色は色の名前では変わりません`,
|
|
3678
|
+
hint: '部品の色は色番号で書く (例 = color: "#d9534f")',
|
|
3679
|
+
});
|
|
3680
|
+
}
|
|
3681
|
+
// 上書きが読むのは部品の状態の名前と `phase` (段を外す) だけで、他の名前は何も変えない。
|
|
3682
|
+
// 綴り違いや、外した欄 (`nodes`、#1976) を書いた時に、効いていない値を黙って持たない
|
|
3683
|
+
const 部品の状態 = new Set((part.states ?? []).map((st) => st.id));
|
|
3684
|
+
const 無い状態 = Object.keys(actor.stateOverride ?? {}).filter(
|
|
3685
|
+
(k) => k !== "phase" && !部品の状態.has(k),
|
|
3686
|
+
);
|
|
3687
|
+
if (無い状態.length > 0) {
|
|
3688
|
+
onNotice?.({
|
|
3689
|
+
kind: "part-state-missing",
|
|
3690
|
+
actor: actor.name,
|
|
3691
|
+
line: actor.pos?.line ?? 0,
|
|
3692
|
+
message: `"${actor.name}" (${partId}) は ${無い状態.map((k) => `"${truncateForMessage(k)}"`).join(" / ")} という状態を持たないため、書いた値は効きません`,
|
|
3693
|
+
hint:
|
|
3694
|
+
部品の状態.size > 0
|
|
3695
|
+
? `この部品の状態 = ${[...部品の状態].join(", ")}`
|
|
3696
|
+
: "この部品は状態を持たないため、値を書いても変わりません",
|
|
3697
|
+
});
|
|
3698
|
+
}
|
|
2848
3699
|
// 位置を書いていないパーツは格子に並べる。 書いてあればその位置を使う
|
|
2849
3700
|
let placeX = actor.posX;
|
|
2850
3701
|
let placeY = actor.posY;
|
|
2851
3702
|
// 格子に落とすのは縦横どちらも書かなかった時だけ。 片方だけ書いた時に残りを格子で
|
|
2852
3703
|
// 埋めると、 書いた値と格子が混ざった位置になる (従来の条件をそのまま保つ)
|
|
2853
|
-
if (
|
|
3704
|
+
if (置く縦列 !== undefined) {
|
|
3705
|
+
// 縦列の位置は図を配置するまで決まらない。 ここでは座標で置く形にだけして、
|
|
3706
|
+
// 全部の部品を組み込んだ後に縦列へ寄せる (`縦列に置いた部品を揃える`)
|
|
3707
|
+
placeX = 0;
|
|
3708
|
+
placeY = 0;
|
|
3709
|
+
} else if (placeX === undefined && placeY === undefined) {
|
|
2854
3710
|
const center = gridCenters.get(actor.name);
|
|
2855
3711
|
placeX = center?.cx;
|
|
2856
3712
|
placeY = center?.cy;
|
|
@@ -2874,12 +3730,13 @@ function mergePartsFromActors(
|
|
|
2874
3730
|
}
|
|
2875
3731
|
}
|
|
2876
3732
|
const t = partTargetSize(part, actor.posW, actor.posH, actor.scale);
|
|
2877
|
-
|
|
3733
|
+
const 組み込む前の箱の数 = target.nodes.length;
|
|
3734
|
+
const 作った縦列 = mergePartIntoDiagram(
|
|
2878
3735
|
target,
|
|
2879
3736
|
part,
|
|
2880
3737
|
actor.name,
|
|
2881
3738
|
merged,
|
|
2882
|
-
actor.lane,
|
|
3739
|
+
置く縦列 ?? actor.lane,
|
|
2883
3740
|
placeX,
|
|
2884
3741
|
placeY,
|
|
2885
3742
|
t.w,
|
|
@@ -2889,10 +3746,219 @@ function mergePartsFromActors(
|
|
|
2889
3746
|
derivedSourceLines,
|
|
2890
3747
|
値の前置き.get(actor.name),
|
|
2891
3748
|
);
|
|
3749
|
+
const 要素 = new Set(target.nodes.slice(組み込む前の箱の数).map((n) => n.id));
|
|
3750
|
+
if (置く縦列 !== undefined) {
|
|
3751
|
+
縦列に置いた.push({ 縦列: 置く縦列, 要素 });
|
|
3752
|
+
} else if (作った縦列.length > 0) {
|
|
3753
|
+
自分の縦列に置いた.push({ 名前: actor.name, 縦列: 作った縦列, 要素 });
|
|
3754
|
+
}
|
|
2892
3755
|
}
|
|
3756
|
+
// 部品用の縦列を先に固定する。 縦列に置く部品は配置した縦列の位置に合わせるので、固定する前に
|
|
3757
|
+
// 揃えると、揃えた時の縦列 (部品用の縦列も詰める送りに加わる) と描いた図の縦列が食い違う
|
|
3758
|
+
部品の縦列を部品に固定する(target, 自分の縦列に置いた);
|
|
3759
|
+
縦列に置いた部品を揃える(target, 縦列に置いた);
|
|
2893
3760
|
return target;
|
|
2894
3761
|
}
|
|
2895
3762
|
|
|
3763
|
+
type 自分の縦列に置いた部品 = {
|
|
3764
|
+
/** 登場人物の名前。 名札に使う */
|
|
3765
|
+
名前: string;
|
|
3766
|
+
/** 部品用に作った縦列。 部品の縦列の順 */
|
|
3767
|
+
縦列: readonly 部品用の縦列[];
|
|
3768
|
+
要素: ReadonlySet<string>;
|
|
3769
|
+
};
|
|
3770
|
+
|
|
3771
|
+
type 部品用の縦列 = {
|
|
3772
|
+
id: string;
|
|
3773
|
+
/** 部品自身が縦列に書いた名札 */
|
|
3774
|
+
部品の名札: string | undefined;
|
|
3775
|
+
};
|
|
3776
|
+
|
|
3777
|
+
/** 部品用の縦列の、部品の要素の上端から縦列の上端までの高さ。 名札 (縦列の上端から 26 下) を収める */
|
|
3778
|
+
const 部品の縦列の名札の高さ = 60;
|
|
3779
|
+
|
|
3780
|
+
/**
|
|
3781
|
+
* 部品用の縦列を部品の位置に固定し、登場人物の名前の名札を部品のすぐ上に 1 つだけ出す (#1990)。
|
|
3782
|
+
*
|
|
3783
|
+
* 部品の要素は座標で置くが、縦列は描画側が左から詰めて並べ直す。 格子の 2 段目の部品の縦列は
|
|
3784
|
+
* 1 段目の縦列と重なるため右へ送られ、名札が別の部品の上に出て、送った分だけ図が横に伸びた
|
|
3785
|
+
* (実測 = 部品 4 つの流れ図で `稼働2` の名札が `稼働3` の上、図の右に 875 の空き)。 部品が 1 つでも
|
|
3786
|
+
* 名札は図の上端に出て、流れ図の途中に置くと先頭の箱の見出しに見えた。
|
|
3787
|
+
*
|
|
3788
|
+
* 描画側は `posX` と `posY` を両方持つ縦列を置いた位置に置き、詰める送りも幅の自動拡張もしない。
|
|
3789
|
+
* 幅は `posW` が無ければ縦列の `width` を使うので、横は位置だけを書く。
|
|
3790
|
+
*
|
|
3791
|
+
* | 向き | 固定する範囲 |
|
|
3792
|
+
* |---|---|
|
|
3793
|
+
* | 横 | 組み込みで決めた位置と幅のまま (部品が書いた縦列を、置いた位置へ写したもの。 #879 / #880) |
|
|
3794
|
+
* | 縦 | 部品全体の上端より名札の分 (60) 上から、部品全体の下端まで。 部品の縦列は全て同じ |
|
|
3795
|
+
*
|
|
3796
|
+
* | 縦列 | 名札 |
|
|
3797
|
+
* |---|---|
|
|
3798
|
+
* | 左端が最も左の縦列 (同じなら部品の縦列の順で先) | 部品が書いた名札、無ければ登場人物の名前 |
|
|
3799
|
+
* | 他の縦列 | 部品が書いた名札だけ |
|
|
3800
|
+
*
|
|
3801
|
+
* 縦を部品全体に揃えるのは、部品の頁の絵と同じく縦列の名札を 1 列に並べるため (部品の頁では
|
|
3802
|
+
* 縦列が全て同じ上端から始まる)。 縦列ごとの要素に合わせると、左の縦列の要素が他より低い部品で
|
|
3803
|
+
* 名前の名札が下がり、右の縦列の要素と重なる。
|
|
3804
|
+
*
|
|
3805
|
+
* **下に余白を足さない**。 縦列の枠は塗りも線も持たず絵に出ないが、図の大きさには数えられる。
|
|
3806
|
+
* 足すと、部品が一番下にある図だけ図の下の空きが広がる (実測 = 箱だけの図と直す前の図は 60、
|
|
3807
|
+
* 下に 25 足すと 85)。 高さを書かないと縦列は名札の分 (40) に縮み、部品の要素が縦列の外に出る。
|
|
3808
|
+
*
|
|
3809
|
+
* **要素の高さは配置しないと分からない** (種類ごとの既定値で描画側が決める)。 全ての部品を
|
|
3810
|
+
* 組み込んだ後に 1 度だけ配置して測る。 座標で置いた要素は配置で動かないので、測った範囲に
|
|
3811
|
+
* 固定しても要素は動かない。
|
|
3812
|
+
*
|
|
3813
|
+
* 座標で置かない要素を持つ部品は固定しない。 そういう要素は縦列の中心に描かれるため、縦列の
|
|
3814
|
+
* 詰める送りを止めると要素の位置も変わる。 **この分岐はテストで覆えていない** = 本文から入力を
|
|
3815
|
+
* 作れない。 格子の部品は必ず格子の中心を持ち、位置を書いた部品は縦横の両方を持つ (片方だけの
|
|
3816
|
+
* 位置と基準が見つからない位置は読む段階で止まる)。 どちらも座標で置かれる (`shouldForcePos`)。
|
|
3817
|
+
*
|
|
3818
|
+
* 配置できない図 (描画側が止める図) では固定しない。 描画でも同じ所で止まる。
|
|
3819
|
+
*/
|
|
3820
|
+
function 部品の縦列を部品に固定する(
|
|
3821
|
+
target: CdlDiagram,
|
|
3822
|
+
部品たち: readonly 自分の縦列に置いた部品[],
|
|
3823
|
+
): void {
|
|
3824
|
+
const 座標で置いた = (id: string): boolean => {
|
|
3825
|
+
const n = target.nodes.find((x) => x.id === id);
|
|
3826
|
+
return n !== undefined && n.posX !== undefined && n.posY !== undefined;
|
|
3827
|
+
};
|
|
3828
|
+
const 対象 = 部品たち.filter((p) => [...p.要素].every(座標で置いた));
|
|
3829
|
+
if (対象.length === 0) return;
|
|
3830
|
+
let laid: LaidDiagram;
|
|
3831
|
+
try {
|
|
3832
|
+
laid = layout(target);
|
|
3833
|
+
} catch {
|
|
3834
|
+
return;
|
|
3835
|
+
}
|
|
3836
|
+
for (const p of 対象) {
|
|
3837
|
+
const 箱 = laid.nodes.filter((n) => p.要素.has(n.id));
|
|
3838
|
+
if (箱.length === 0) continue;
|
|
3839
|
+
const 上端 = Math.min(...箱.map((n) => n.cy - n.h / 2));
|
|
3840
|
+
const 下端 = Math.max(...箱.map((n) => n.cy + n.h / 2));
|
|
3841
|
+
const 縦列たち = p.縦列.flatMap((l) => {
|
|
3842
|
+
const 縦列 = target.lanes.find((x) => x.id === l.id);
|
|
3843
|
+
return 縦列 ? [{ ...l, 縦列 }] : [];
|
|
3844
|
+
});
|
|
3845
|
+
let 名札の縦列: (typeof 縦列たち)[number] | undefined;
|
|
3846
|
+
for (const l of 縦列たち) {
|
|
3847
|
+
if (名札の縦列 === undefined || (l.縦列.x ?? 0) < (名札の縦列.縦列.x ?? 0)) 名札の縦列 = l;
|
|
3848
|
+
}
|
|
3849
|
+
for (const l of 縦列たち) {
|
|
3850
|
+
l.縦列.posX = l.縦列.x ?? 0;
|
|
3851
|
+
l.縦列.posY = 上端 - 部品の縦列の名札の高さ;
|
|
3852
|
+
l.縦列.posH = 下端 - 上端 + 部品の縦列の名札の高さ;
|
|
3853
|
+
const 名札 = l === 名札の縦列 ? (l.部品の名札 ?? p.名前) : l.部品の名札;
|
|
3854
|
+
if (名札 === undefined) delete l.縦列.label;
|
|
3855
|
+
else l.縦列.label = 名札;
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
|
|
3860
|
+
type 縦列に置いた部品 = { 縦列: string; 要素: ReadonlySet<string> };
|
|
3861
|
+
|
|
3862
|
+
/** 部品が縦列より広い時に、縦列の左右に残す余白。 描画側が縦列の中の箱に取る最小の余白と同じ */
|
|
3863
|
+
const 部品の縦列の余白 = 25;
|
|
3864
|
+
|
|
3865
|
+
/**
|
|
3866
|
+
* 縦列に置いた部品を、縦列の中心と図の下へ動かす (#1980)。
|
|
3867
|
+
*
|
|
3868
|
+
* 部品は座標で置く (部品の中の要素の並びを保つため)。 座標で置いた箱は描画側の配置が動かさないので、
|
|
3869
|
+
* 縦列の位置を知るには図を 1 度配置する。
|
|
3870
|
+
*
|
|
3871
|
+
* **配置は 1 度で足りる**。 縦列の横位置は縦列の幅・中の箱の幅・矢印の札で決まり、座標で置いた箱の
|
|
3872
|
+
* 座標には依らない。 部品を動かしても縦列は動かない。
|
|
3873
|
+
*
|
|
3874
|
+
* | 向き | 置き方 |
|
|
3875
|
+
* |---|---|
|
|
3876
|
+
* | 横 | 部品の中心を縦列の中心に合わせる。 部品が縦列より広ければ縦列を広げ、配置し直す |
|
|
3877
|
+
* | 縦 | 部品以外の箱の一番下から `PARTS_GAP` 空ける。 同じ縦列の部品は書いた順に下へ積む |
|
|
3878
|
+
*
|
|
3879
|
+
* **縦列の段には入れない**。 段の高さは縦列をまたいで共有され、背の高い部品を段に入れると隣の
|
|
3880
|
+
* 縦列の箱まで伸びる (実測 = `受付` の高さが 68 から 380 になった)。
|
|
3881
|
+
*
|
|
3882
|
+
* 配置できない図 (描画側が止める図) では動かさない。 描画でも同じ所で止まるので、ここで
|
|
3883
|
+
* 別の知らせは足さない。
|
|
3884
|
+
*/
|
|
3885
|
+
function 縦列に置いた部品を揃える(target: CdlDiagram, 置いた: readonly 縦列に置いた部品[]): void {
|
|
3886
|
+
if (置いた.length === 0) return;
|
|
3887
|
+
const 配置する = (): LaidDiagram | undefined => {
|
|
3888
|
+
try {
|
|
3889
|
+
return layout(target);
|
|
3890
|
+
} catch {
|
|
3891
|
+
return undefined;
|
|
3892
|
+
}
|
|
3893
|
+
};
|
|
3894
|
+
let laid = 配置する();
|
|
3895
|
+
if (!laid) return;
|
|
3896
|
+
const 範囲 = (d: LaidDiagram, 要素: ReadonlySet<string>) => {
|
|
3897
|
+
const 箱 = d.nodes.filter((n) => 要素.has(n.id));
|
|
3898
|
+
if (箱.length === 0) return undefined;
|
|
3899
|
+
return {
|
|
3900
|
+
x0: Math.min(...箱.map((n) => n.cx - n.w / 2)),
|
|
3901
|
+
x1: Math.max(...箱.map((n) => n.cx + n.w / 2)),
|
|
3902
|
+
y0: Math.min(...箱.map((n) => n.cy - n.h / 2)),
|
|
3903
|
+
y1: Math.max(...箱.map((n) => n.cy + n.h / 2)),
|
|
3904
|
+
};
|
|
3905
|
+
};
|
|
3906
|
+
// 部品より狭い縦列を広げる。 描画側は縦列を中の箱 1 つの幅までしか広げず、要素を横に並べた
|
|
3907
|
+
// 部品は隣の縦列へはみ出す
|
|
3908
|
+
let 広げた = false;
|
|
3909
|
+
for (const p of 置いた) {
|
|
3910
|
+
const r = 範囲(laid, p.要素);
|
|
3911
|
+
const 縦列 = target.lanes.find((l) => l.id === p.縦列);
|
|
3912
|
+
const 描いた縦列 = laid.lanes.find((l) => l.id === p.縦列);
|
|
3913
|
+
if (!r || !縦列 || !描いた縦列) continue;
|
|
3914
|
+
const 要る幅 = r.x1 - r.x0 + 部品の縦列の余白 * 2;
|
|
3915
|
+
if (要る幅 > 描いた縦列.width && 要る幅 > 縦列.width) {
|
|
3916
|
+
縦列.width = 要る幅;
|
|
3917
|
+
広げた = true;
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
if (広げた) {
|
|
3921
|
+
laid = 配置する();
|
|
3922
|
+
if (!laid) return;
|
|
3923
|
+
}
|
|
3924
|
+
// 動かした後に配置し直して確かめ、狙いと合うまで繰り返す。 座標で置いた箱が他の箱と重なると
|
|
3925
|
+
// 描画側は他の箱を下げるため、仮の位置 (図の上端) のまま測った下端は実際より下にずれる
|
|
3926
|
+
// (実測 = 狙った上端 316 が 400 になった)。 部品を図の下へ動かせば重ならなくなり、2 回目で合う
|
|
3927
|
+
const 部品の要素 = new Set(置いた.flatMap((p) => [...p.要素]));
|
|
3928
|
+
for (let 回 = 0; 回 < 部品を揃え直す上限; 回 += 1) {
|
|
3929
|
+
// 下端は部品以外の箱 (格子に並べた部品を含む) と縦列の名札で測る。 箱を囲む縦列 (`contain`) は
|
|
3930
|
+
// 仮に置いた部品まで囲んでいるため数えない = 中の箱は箱として数えている
|
|
3931
|
+
const 他の箱の下端 = Math.max(
|
|
3932
|
+
...laid.nodes.filter((n) => !部品の要素.has(n.id)).map((n) => n.cy + n.h / 2),
|
|
3933
|
+
...laid.lanes.filter((l) => !l.contain).map((l) => l.y + l.height),
|
|
3934
|
+
);
|
|
3935
|
+
const 次の上端 = new Map<string, number>();
|
|
3936
|
+
let 動かした = false;
|
|
3937
|
+
for (const p of 置いた) {
|
|
3938
|
+
const r = 範囲(laid, p.要素);
|
|
3939
|
+
const 縦列 = laid.lanes.find((l) => l.id === p.縦列);
|
|
3940
|
+
if (!r || !縦列) continue;
|
|
3941
|
+
const 上端 = 次の上端.get(p.縦列) ?? 他の箱の下端 + PARTS_GAP;
|
|
3942
|
+
const dx = 縦列.x + 縦列.width / 2 - (r.x0 + r.x1) / 2;
|
|
3943
|
+
const dy = 上端 - r.y0;
|
|
3944
|
+
次の上端.set(p.縦列, 上端 + (r.y1 - r.y0) + PARTS_GAP);
|
|
3945
|
+
if (Math.abs(dx) <= PLACEMENT_TOLERANCE && Math.abs(dy) <= PLACEMENT_TOLERANCE) continue;
|
|
3946
|
+
for (const n of target.nodes) {
|
|
3947
|
+
if (!p.要素.has(n.id) || n.posX === undefined || n.posY === undefined) continue;
|
|
3948
|
+
n.posX += dx;
|
|
3949
|
+
n.posY += dy;
|
|
3950
|
+
}
|
|
3951
|
+
動かした = true;
|
|
3952
|
+
}
|
|
3953
|
+
if (!動かした) return;
|
|
3954
|
+
laid = 配置する();
|
|
3955
|
+
if (!laid) return;
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
|
|
3959
|
+
/** 縦列に置いた部品を配置し直して確かめる回数の上限。 実測では 2 回目で動かなくなる */
|
|
3960
|
+
const 部品を揃え直す上限 = 3;
|
|
3961
|
+
|
|
2896
3962
|
/**
|
|
2897
3963
|
* 状態の初期値に上書きを当てた結果と、 色として読めないため捨てたかどうか。
|
|
2898
3964
|
*
|
|
@@ -2942,6 +4008,38 @@ function applyColorHex(
|
|
|
2942
4008
|
return out;
|
|
2943
4009
|
}
|
|
2944
4010
|
|
|
4011
|
+
/**
|
|
4012
|
+
* 本文に書いた状態の上書きと色番号を、部品の図そのものに当てて返す (#1973)。
|
|
4013
|
+
*
|
|
4014
|
+
* 編集画面の本文欄は、部品を図から抜いて別に重ねて描く。 重ねる側が部品の図をそのまま
|
|
4015
|
+
* 描くと、`state: { lvl: 0.4 }` や `color: "#d9534f"` がカタログの絵にだけ効き、
|
|
4016
|
+
* 編集画面では既定の値で描かれる。 値の決め方は組み立て側と同じ 2 つの関数を通す。
|
|
4017
|
+
*
|
|
4018
|
+
* `phase: false` は組み立て側と同じく部品の段を外す。 段の数は残し、中身 (点灯と値の変化)
|
|
4019
|
+
* だけを空にする = 段を消すと、重ねた部品だけ段の進みが止まらない形になる。
|
|
4020
|
+
*
|
|
4021
|
+
* 色として読めない上書きは組み立て側と同じく捨てる。 知らせは組み立て側が出すため、
|
|
4022
|
+
* ここでは出さない (同じ本文で 2 度出さない)。
|
|
4023
|
+
*/
|
|
4024
|
+
export function 部品に上書きを当てる(
|
|
4025
|
+
part: CdlDiagram,
|
|
4026
|
+
actor: Pick<DslActor, "stateOverride" | "colorHex">,
|
|
4027
|
+
): CdlDiagram {
|
|
4028
|
+
const 上書き = applyColorHex(part, actor.colorHex, actor.stateOverride ?? {});
|
|
4029
|
+
const 段を外す = 上書き["phase"] === false;
|
|
4030
|
+
if (Object.keys(上書き).length === 0) return part;
|
|
4031
|
+
return {
|
|
4032
|
+
...part,
|
|
4033
|
+
states: part.states.map((st) => ({
|
|
4034
|
+
...st,
|
|
4035
|
+
initial: resolveStateOverride(st.initial, 上書き[st.id]).initial,
|
|
4036
|
+
})),
|
|
4037
|
+
phases: 段を外す
|
|
4038
|
+
? part.phases.map((ph) => ({ ...ph, activate: [], tweens: [], sets: [] }))
|
|
4039
|
+
: part.phases,
|
|
4040
|
+
};
|
|
4041
|
+
}
|
|
4042
|
+
|
|
2945
4043
|
/**
|
|
2946
4044
|
* CAR-1657 = parts CdlDiagram (単一 part 内容) を target CdlDiagram に prefix 付きで merge する。
|
|
2947
4045
|
* alias = user が書く actor 名 ('arc1')、 全 id を '{alias}__{origId}' で prefix、 lane 参照 rename、
|
|
@@ -3030,7 +4128,7 @@ function mergePartIntoDiagram(
|
|
|
3030
4128
|
* 登場人物の名前をそのまま使えない。 渡されない経路では従来どおり名前をそのまま使う
|
|
3031
4129
|
*/
|
|
3032
4130
|
valueAlias?: string,
|
|
3033
|
-
):
|
|
4131
|
+
): 部品用の縦列[] {
|
|
3034
4132
|
const prefix = (id: string): string => `${alias}__${id}`;
|
|
3035
4133
|
// 値と状態だけ別の前置きを使う (#1189)。 箱 / 縦列 / 矢印の id は `prefix` のまま
|
|
3036
4134
|
const 値前置き = valueAlias ?? alias;
|
|
@@ -3069,11 +4167,13 @@ function mergePartIntoDiagram(
|
|
|
3069
4167
|
// 決定的 lane 参照 = user が書いた lane 指定を優先、 なければ parts 内部 lane を prefix 付きで作る
|
|
3070
4168
|
const targetLaneId = laneMapping;
|
|
3071
4169
|
const laneIdMap = new Map<string, string>();
|
|
4170
|
+
// 部品用に作った縦列。 組み込んだ後に部品の位置へ固定する (#1990)
|
|
4171
|
+
const 作った縦列: 部品用の縦列[] = [];
|
|
3072
4172
|
// parts lane の横位置。
|
|
3073
4173
|
// offset (drop / click 座標) 指定時 = part 中心を offsetX に合わせる = user が置いた位置に
|
|
3074
4174
|
// parts の中心が来る。 node は lane 中心 (lane.x + laneW/2) に描画されるため、 lane 左端を
|
|
3075
4175
|
// offsetX - laneW/2 に置くと node 中心 = offsetX となり cursor / viewport 中央に一致する
|
|
3076
|
-
// (縦方向 offsetY と対称、 offsetY
|
|
4176
|
+
// (縦方向 offsetY と対称、 offsetY 側は要素全体の縦の中心で合わせる = `部品の置き方を読む`)。
|
|
3077
4177
|
// 従来の auto-adjust (max(offsetX, existingMax + gap) で既存 lane 右端へ強制右寄せ) は user
|
|
3078
4178
|
// directive で廃止 (2026-07-21)。 重なりは user の意図位置を優先し、 手動移動で回避する経路。
|
|
3079
4179
|
// 未指定 (座標なし fallback) 時のみ existingMax + gap で右外配置 (通常経路は drop/click で座標を渡す)。
|
|
@@ -3084,37 +4184,26 @@ function mergePartIntoDiagram(
|
|
|
3084
4184
|
// scale 基準は part 全体の bbox 幅 (全 lane の最左端〜最右端) にする。 lane[0] 幅だけを基準にすると
|
|
3085
4185
|
// multi-lane part (複数 lane を横に並べた part) で全体幅を過小評価し、 非先頭 lane の node が自 lane
|
|
3086
4186
|
// 中心からずれる (#880)。
|
|
3087
|
-
//
|
|
3088
|
-
//
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
w: positiveOr(l.width, 400),
|
|
3094
|
-
});
|
|
3095
|
-
}
|
|
3096
|
-
const laneXs = [...partLaneGeom.values()].map((g) => g.x);
|
|
3097
|
-
const laneRights = [...partLaneGeom.values()].map((g) => g.x + g.w);
|
|
3098
|
-
const partMinLaneX = minOf(laneXs, 0);
|
|
3099
|
-
const partMaxLaneRight = maxOf(laneRights, 400);
|
|
3100
|
-
// 幅は max >= min で常に非負。 正の幅 (極小 sub-pixel 含む) はそのまま scale 基準に使い、
|
|
3101
|
-
// 0 (全 lane が同一 x + 幅 0 の退化ケース) の時だけ除算保護で 1 に fallback する。
|
|
3102
|
-
// Math.max(1, w) だと 0 < w < 1 の正当な幅まで 1 に floor して over-scale するため使わない。
|
|
3103
|
-
const rawBboxW = partMaxLaneRight - partMinLaneX;
|
|
3104
|
-
const partsBboxW = rawBboxW > 0 ? rawBboxW : 1;
|
|
4187
|
+
//
|
|
4188
|
+
// 縦列の位置と幅、要素の中心は部品の頁で配置した値を使う (`部品の置き方を読む`、#1992)。
|
|
4189
|
+
// 書いた値で置くと、頁で描画側が広げた縦列の間と段の間が消え、要素が頁より詰まる。
|
|
4190
|
+
// 伸縮の基準は画面側 (`partScaleFactor`) と同じ関数から取る
|
|
4191
|
+
const 置き方 = 部品の置き方を読む(part);
|
|
4192
|
+
const 伸縮の基準 = partScaleBase(part);
|
|
3105
4193
|
// 非有限は 1 に倒す。 桁が溢れた `大きさ:` (`Number()` が Infinity を返す長さ) を
|
|
3106
4194
|
// そのまま掛けると描けない座標になり、 大きさを見積る側 (`partTargetScale`) だけが
|
|
3107
4195
|
// 1 に倒していたため経路で食い違っていた (#1018)
|
|
3108
|
-
const rawLaneScaleX = targetW !== undefined && targetW > 0 ? targetW /
|
|
4196
|
+
const rawLaneScaleX = targetW !== undefined && targetW > 0 ? targetW / 伸縮の基準.w : 1;
|
|
3109
4197
|
const laneScaleX = Number.isFinite(rawLaneScaleX) && rawLaneScaleX > 0 ? rawLaneScaleX : 1;
|
|
3110
4198
|
// part 全体を「元 bbox 中心 → drop 座標」 の scale 変換で写す単一式 mapLaneX。 lane も node も同じ式で
|
|
3111
4199
|
// 変換し、 lane.x = mapLaneX(元 lane 左端) にすることで全 lane / 全 node が一貫して drop 座標を中心に
|
|
3112
4200
|
// scale 配置される (cc-codex #879 の mapPartX と同じ発想を lane push まで前倒し、 #880 root fix)。
|
|
3113
|
-
|
|
4201
|
+
// node 側は `部品の要素のずれ` が同じ中心と倍率で写す
|
|
4202
|
+
const partOrigBboxCenterX = 置き方.左端 + 置き方.幅 / 2;
|
|
3114
4203
|
const dropCenterX =
|
|
3115
4204
|
offsetX !== undefined
|
|
3116
4205
|
? offsetX
|
|
3117
|
-
: existingLaneMaxX + PARTS_LANE_GAP + (
|
|
4206
|
+
: existingLaneMaxX + PARTS_LANE_GAP + (置き方.幅 * laneScaleX) / 2;
|
|
3118
4207
|
const mapLaneX = (x: number): number => (x - partOrigBboxCenterX) * laneScaleX + dropCenterX;
|
|
3119
4208
|
|
|
3120
4209
|
for (const laneOrig of part.lanes) {
|
|
@@ -3126,7 +4215,7 @@ function mergePartIntoDiagram(
|
|
|
3126
4215
|
// lane の左端を mapLaneX で変換 = 元 lane 左端 (x) を scale 変換後の位置に置く。 lane 幅も
|
|
3127
4216
|
// scale して lane 中心が mapLaneX(元 lane 中心) に一致する。 これで multi-lane でも各 lane が
|
|
3128
4217
|
// part 全体の scale 変換に沿って配置される。
|
|
3129
|
-
const geom =
|
|
4218
|
+
const geom = 置き方.縦列.get(laneOrig.id) ?? { x: 0, w: 400 };
|
|
3130
4219
|
target.lanes.push({
|
|
3131
4220
|
...laneOrig,
|
|
3132
4221
|
id: newLaneId,
|
|
@@ -3134,6 +4223,7 @@ function mergePartIntoDiagram(
|
|
|
3134
4223
|
x: mapLaneX(geom.x),
|
|
3135
4224
|
width: geom.w * laneScaleX,
|
|
3136
4225
|
});
|
|
4226
|
+
作った縦列.push({ id: newLaneId, 部品の名札: laneOrig.label });
|
|
3137
4227
|
}
|
|
3138
4228
|
}
|
|
3139
4229
|
|
|
@@ -3144,8 +4234,8 @@ function mergePartIntoDiagram(
|
|
|
3144
4234
|
// (1) 全 parts node に posX/posY 明示 set (CDL layout の絶対配置経路 = stack 計算 skip)
|
|
3145
4235
|
// (2) parts の stack 番号を target 側 max stack + STACK_ISOLATION_OFFSET (1000) に shift
|
|
3146
4236
|
// = 万一 layout が rowH で参照しても sequence stack と重ならず影響 0 化
|
|
3147
|
-
// D1 = drop
|
|
3148
|
-
//
|
|
4237
|
+
// D1 = drop 座標尊重 = 部品の頁で配置した要素の中心を、部品の中心が drop 座標に来るよう
|
|
4238
|
+
// 写して描画する (#1992、`部品の要素のずれ`)。
|
|
3149
4239
|
//
|
|
3150
4240
|
const STACK_ISOLATION_OFFSET = 1000;
|
|
3151
4241
|
const shouldForcePos = offsetX !== undefined || offsetY !== undefined;
|
|
@@ -3160,34 +4250,23 @@ function mergePartIntoDiagram(
|
|
|
3160
4250
|
// に対する比率 = scale 係数、 全 sub-node の w / h + cx / cy 相対位置に scale 反映。
|
|
3161
4251
|
// scaleX は lane push と同じ part bbox 幅基準 (laneScaleX) を使う = multi-lane で lane と node の
|
|
3162
4252
|
// scale 係数が一致する (#880、 lane[0] 幅基準だと非先頭 lane の node がずれる)。
|
|
3163
|
-
//
|
|
3164
|
-
// 標準 node h (~140-200) の合計相当。 parts の cy を厳密に再現しないが、 渡した座標付近に
|
|
3165
|
-
// parts が中心配置される見た目に十分な近似。
|
|
3166
|
-
//
|
|
3167
|
-
// 実配置に置き換える案を試したが、 拡大の基準 (縦列基準 → 箱基準) まで変わって既存の
|
|
3168
|
-
// 期待 14 件が崩れた。 段を持つパーツ (実 catalog で 80 件中 7 件) の内部比率が実配置と
|
|
3169
|
-
// 3% ずれるが、 見た目の大きさは呼出側が揃えるため観測される差は無い
|
|
3170
|
-
const STACK_PITCH_APPROX = 220;
|
|
3171
|
-
// 数でない段は 0 として扱う。 大きさを見積る側 (`partTargetScale`) が同じ判定をしており、
|
|
3172
|
-
// ここだけ NaN を通すと段の数が NaN になって倍率が経路で食い違う (#1018)
|
|
3173
|
-
const partStacks = part.nodes.map((n) =>
|
|
3174
|
-
typeof n.stack === "number" && Number.isFinite(n.stack) ? n.stack : 0,
|
|
3175
|
-
);
|
|
3176
|
-
const minStack = partStacks.length > 0 ? Math.min(...partStacks) : 0;
|
|
3177
|
-
const maxStack = partStacks.length > 0 ? Math.max(...partStacks) : 0;
|
|
3178
|
-
const partCenterStack = (minStack + maxStack) / 2;
|
|
3179
|
-
const partOrigH = Math.max(1, (maxStack - minStack + 1) * STACK_PITCH_APPROX);
|
|
4253
|
+
// 縦の基準は段の送り幅の合計 (`partScaleBase`)。 `大きさ:` の縦は、この合計に対する倍率で掛かる
|
|
3180
4254
|
const scaleX = laneScaleX;
|
|
3181
|
-
const rawScaleY = targetH !== undefined && targetH > 0 ? targetH /
|
|
4255
|
+
const rawScaleY = targetH !== undefined && targetH > 0 ? targetH / 伸縮の基準.h : 1;
|
|
3182
4256
|
const scaleY = Number.isFinite(rawScaleY) && rawScaleY > 0 ? rawScaleY : 1;
|
|
3183
4257
|
|
|
4258
|
+
// 縦列を 2 本以上持つ部品を 1 本の縦列へまとめる時は、要素ごとに別の段番号を振る (#1980)。
|
|
4259
|
+
// 元の段番号のままだと横に並んでいた要素が同じ段で重なり、配置が止まる (実測 =
|
|
4260
|
+
// `lane "b" の stack=1000 に node が重複`)。 座標で置く要素の位置は段番号で決まらないので、
|
|
4261
|
+
// 振り直しても絵は変わらない
|
|
4262
|
+
const 段を振り直す = targetLaneId !== undefined && shouldForcePos && part.lanes.length > 1;
|
|
3184
4263
|
// node merge = id prefix + lane 参照 rewrite + shape / subtitle / value 内 template rewrite
|
|
3185
|
-
for (const nodeOrig of part.nodes) {
|
|
4264
|
+
for (const [nodeIndex, nodeOrig] of part.nodes.entries()) {
|
|
3186
4265
|
const mappedLane = laneIdMap.get(nodeOrig.lane) ?? nodeOrig.lane;
|
|
3187
4266
|
// codex-review MAJOR fix (§ nested shape template) = recursive walk で shape 内 nested object /
|
|
3188
4267
|
// array の string leaf 全対象、 前実装は 1 depth のみで `fill: { gradient: "{v}" }` 等 miss。
|
|
3189
4268
|
let newShape = nodeOrig.shape
|
|
3190
|
-
? deepRewriteStrings(nodeOrig.shape
|
|
4269
|
+
? deepRewriteStrings(nodeOrig.shape, rewriteTemplate)
|
|
3191
4270
|
: undefined;
|
|
3192
4271
|
// parts 全体 resize (I2 forensic): shape 内 radius / outerRadius / innerRadius / thickness に
|
|
3193
4272
|
// scale 反映 = user が SE handle drag で拡大すると shape の見た目も比例拡大される。 scaleX を採用
|
|
@@ -3211,31 +4290,24 @@ function mergePartIntoDiagram(
|
|
|
3211
4290
|
}
|
|
3212
4291
|
return out;
|
|
3213
4292
|
};
|
|
3214
|
-
newShape = scaleGeom(newShape)
|
|
4293
|
+
newShape = scaleGeom(newShape);
|
|
3215
4294
|
}
|
|
3216
4295
|
// parts drop 位置 offset 反映:
|
|
3217
|
-
// - node.posX set 済 (parts
|
|
3218
|
-
// -
|
|
3219
|
-
// - offset なし (従来経路) は auto layout 継続 (posX undefined)
|
|
4296
|
+
// - node.posX / posY set 済 (parts が座標を持つ) = 横は part 中心基準で scale 変換、縦は offsetY を足す
|
|
4297
|
+
// - offset 指定時 (drop 経路) で未設定 = 部品の頁で配置した node の中心を同じ式で変換
|
|
4298
|
+
// - offset なし (従来経路) は auto layout 継続 (posX / posY undefined)
|
|
3220
4299
|
//
|
|
3221
|
-
// 明示 posX
|
|
3222
|
-
//
|
|
3223
|
-
// lane
|
|
3224
|
-
//
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
nodePosX = mapLaneX(geom.x + geom.w / 2);
|
|
3233
|
-
}
|
|
3234
|
-
if (shouldForcePos && nodePosY === undefined) {
|
|
3235
|
-
// parts の元 stack から近似 pitch で cy を組み立て、 全 parts の中心が offsetY に来るよう調整
|
|
3236
|
-
const stack = nodeOrig.stack ?? 0;
|
|
3237
|
-
nodePosY = (stack - partCenterStack) * STACK_PITCH_APPROX * scaleY + (offsetY ?? 0);
|
|
3238
|
-
}
|
|
4300
|
+
// 明示 posX と頁の中心の両経路を、 lane push と同じ中心と倍率で写す (cc-codex #879 Round 2/3
|
|
4301
|
+
// MAJOR + #880)。 写す式は見積り (`partExtent`) と共有する `部品の要素のずれ` が持つため、
|
|
4302
|
+
// lane.x != 0 でも multi-lane でも node 中心と自 lane 中心が一致し、格子が確保した場所とも一致する。
|
|
4303
|
+
//
|
|
4304
|
+
// offset なしの経路 (格子が場所を返さない部品だけが通る) では、node が書いた縦位置は
|
|
4305
|
+
// `...nodeOrig` がそのまま残す。 足す offsetY が無いので写す必要が無い
|
|
4306
|
+
const 頁 = 置き方.要素[nodeIndex] ?? { x: partOrigBboxCenterX, y: 0 };
|
|
4307
|
+
const ずれ = 部品の要素のずれ(nodeOrig, 頁, 置き方, scaleX, scaleY);
|
|
4308
|
+
const nodePosX =
|
|
4309
|
+
shouldForcePos || nodeOrig.posX !== undefined ? dropCenterX + ずれ.dx : undefined;
|
|
4310
|
+
const nodePosY = shouldForcePos ? (offsetY ?? 0) + ずれ.dy : undefined;
|
|
3239
4311
|
// parts sub-node の w / h に scale 適用 (I2 forensic 対応、 targetW/H 指定時のみ)
|
|
3240
4312
|
// catalog の値は呼出側が渡すので、 拡大しない時も数として通るか確かめる。 通さないと
|
|
3241
4313
|
// 座標が非有限になって図が描けない (実測 = 箱の中心が NaN になった)
|
|
@@ -3253,7 +4325,7 @@ function mergePartIntoDiagram(
|
|
|
3253
4325
|
subtitle: rewriteTemplate(nodeOrig.subtitle),
|
|
3254
4326
|
value: rewriteTemplate(nodeOrig.value),
|
|
3255
4327
|
// parts stack を target 側と分離 (D2 fix、 posX/posY 明示との 2 段防御)
|
|
3256
|
-
stack: (nodeOrig.stack ?? 0) + stackShiftBase,
|
|
4328
|
+
stack: (段を振り直す ? nodeIndex : (nodeOrig.stack ?? 0)) + stackShiftBase,
|
|
3257
4329
|
...(newShape ? { shape: newShape as CdlDiagram["nodes"][number]["shape"] } : {}),
|
|
3258
4330
|
...(nodePosX !== undefined ? { posX: nodePosX } : {}),
|
|
3259
4331
|
...(nodePosY !== undefined ? { posY: nodePosY } : {}),
|
|
@@ -3312,7 +4384,7 @@ function mergePartIntoDiagram(
|
|
|
3312
4384
|
if (!target.readouts) target.readouts = [];
|
|
3313
4385
|
for (const readoutOrig of part.readouts) {
|
|
3314
4386
|
const rewritten = deepRewriteStrings(
|
|
3315
|
-
readoutOrig
|
|
4387
|
+
readoutOrig,
|
|
3316
4388
|
rewriteTemplate,
|
|
3317
4389
|
) as CdlDiagram["readouts"] extends readonly (infer R)[] ? R : never;
|
|
3318
4390
|
// id は shape 全 walk で rewrite されないので個別に prefix
|
|
@@ -3328,7 +4400,7 @@ function mergePartIntoDiagram(
|
|
|
3328
4400
|
// は union。 stateOverride.phase === false 時は parts phase 破棄 (opt-out)。
|
|
3329
4401
|
const phaseOptOut = stateOverride["phase"] === false;
|
|
3330
4402
|
if (phaseOptOut) {
|
|
3331
|
-
return
|
|
4403
|
+
return 作った縦列; // parts phase を破棄、 activate / tweens / sets の rewrite 不要
|
|
3332
4404
|
}
|
|
3333
4405
|
if (target.phases.length === 0) {
|
|
3334
4406
|
// target に phase なし = parts phase をそのまま追加 (prefix 付き)
|
|
@@ -3373,6 +4445,7 @@ function mergePartIntoDiagram(
|
|
|
3373
4445
|
});
|
|
3374
4446
|
}
|
|
3375
4447
|
}
|
|
4448
|
+
return 作った縦列;
|
|
3376
4449
|
}
|
|
3377
4450
|
|
|
3378
4451
|
/**
|
|
@@ -3387,8 +4460,8 @@ function deepRewriteStrings(
|
|
|
3387
4460
|
): unknown {
|
|
3388
4461
|
if (typeof value === "string") return rewrite(value) ?? value;
|
|
3389
4462
|
if (value === null || typeof value !== "object") return value;
|
|
3390
|
-
if (seen.has(value
|
|
3391
|
-
seen.add(value
|
|
4463
|
+
if (seen.has(value)) return value;
|
|
4464
|
+
seen.add(value);
|
|
3392
4465
|
if (Array.isArray(value)) {
|
|
3393
4466
|
return value.map((v) => deepRewriteStrings(v, rewrite, seen));
|
|
3394
4467
|
}
|
|
@@ -3416,13 +4489,19 @@ function applyEdgeInlineOptions(
|
|
|
3416
4489
|
doc: DslDocument,
|
|
3417
4490
|
/** 対応が取れた edge を記録する表。 callback は呼ばない (1 edge = 1 回にするため)。 */
|
|
3418
4491
|
sourceLines?: Map<string, number>,
|
|
4492
|
+
/** 対応が取れた edge の行そのもの。 部品の取り込みが、行に書いた要素の名指しを読む (#1979) */
|
|
4493
|
+
edgeSteps?: Map<string, DslStep>,
|
|
4494
|
+
/** 鎖に並べない部品を決める一覧 (#1987)。 `compileFlow` に渡したものと同じ */
|
|
4495
|
+
partsCatalog?: Record<string, CdlDiagram>,
|
|
3419
4496
|
): void {
|
|
3420
4497
|
// **静止した `type: flow` は書いた端で対応が取れない** (#1267)。 鎖の規則で先に埋める
|
|
3421
4498
|
if (鎖でつなぐ形か(doc)) {
|
|
4499
|
+
const 並び = 鎖に並べる登場人物(doc, partsCatalog);
|
|
3422
4500
|
diagram.edges.forEach((e, idx) => {
|
|
3423
|
-
const s = 鎖のどの行から来たか(doc, idx);
|
|
4501
|
+
const s = 鎖のどの行から来たか(doc, 並び, idx);
|
|
3424
4502
|
if (s === undefined) return;
|
|
3425
4503
|
sourceLines?.set(e.id, s.pos.line);
|
|
4504
|
+
edgeSteps?.set(e.id, s);
|
|
3426
4505
|
矢印へ書き写す(e, s, doc);
|
|
3427
4506
|
});
|
|
3428
4507
|
return;
|
|
@@ -3441,6 +4520,7 @@ function applyEdgeInlineOptions(
|
|
|
3441
4520
|
if (!target) return;
|
|
3442
4521
|
used.add(target.id);
|
|
3443
4522
|
sourceLines?.set(target.id, s.pos.line);
|
|
4523
|
+
edgeSteps?.set(target.id, s);
|
|
3444
4524
|
矢印へ書き写す(target, s, doc);
|
|
3445
4525
|
});
|
|
3446
4526
|
}
|
|
@@ -3493,7 +4573,11 @@ function 矢印へ書き写す(target: CdlEdge, s: DslStep, doc: DslDocument): v
|
|
|
3493
4573
|
// **書いた補足が勝つ** (#1275)。 ここで写さないと 2 つ落ちる。 静止した `type: flow` は
|
|
3494
4574
|
// 鎖を作る時に説明文しか渡さないため補足が消え、`er` は見本が多重度から作った補足が
|
|
3495
4575
|
// 残って書いた値が無視される (どちらも実測)
|
|
3496
|
-
|
|
4576
|
+
//
|
|
4577
|
+
// **クラス図の `sub` は多重度なので写さない** (#1769)。 クラス図の組み立てが既に `cardinality` として
|
|
4578
|
+
// 渡し、engine が行き先の端に添える (cdl#821)。 ここで札の下の行にも写すと、`1..*` が札と端の
|
|
4579
|
+
// 2 か所に出て、組み立て器で書いた同じ図と食い違う (実測 = 記法の見本が組み立て器の見本と一致しなくなった)
|
|
4580
|
+
if (s.sub !== undefined && doc.type !== "class") target.sub = s.sub;
|
|
3497
4581
|
if (s.guard !== undefined) {
|
|
3498
4582
|
target.guard = s.guard;
|
|
3499
4583
|
// FSM preset では sub が guard 同期、 author 明示 guard を sub に反映 (sub 既存なら上書きしない)
|
|
@@ -3523,6 +4607,12 @@ function 矢印へ書き写す(target: CdlEdge, s: DslStep, doc: DslDocument): v
|
|
|
3523
4607
|
if (s.labelPlate !== undefined) target.labelPlate = s.labelPlate;
|
|
3524
4608
|
if (s.labelOffsetX !== undefined) target.labelOffsetX = s.labelOffsetX;
|
|
3525
4609
|
if (s.labelOffsetY !== undefined) target.labelOffsetY = s.labelOffsetY;
|
|
4610
|
+
// 矢印の位置のずらし (#1971)。 矢印は自分の位置を持たず、通り道は両端の箱で決まるので、
|
|
4611
|
+
// 動かせるのは名前だけ。 名前のずらしに足す = 両方書いた時は合わせた量だけ動く
|
|
4612
|
+
if (s.layoutPos !== undefined) {
|
|
4613
|
+
target.labelOffsetX = (s.labelOffsetX ?? 0) + s.layoutPos.x;
|
|
4614
|
+
target.labelOffsetY = (s.labelOffsetY ?? 0) + s.layoutPos.y;
|
|
4615
|
+
}
|
|
3526
4616
|
if (s.overlay !== undefined) target.overlay = s.overlay;
|
|
3527
4617
|
// 値に追随する 3 欄 (#1396)。 太さ / 色 / 破線の位置が値に合わせて動く。
|
|
3528
4618
|
// 通り道そのものは動かないので、配置計算と重なり解消には影響しない
|
|
@@ -3534,15 +4624,48 @@ function 矢印へ書き写す(target: CdlEdge, s: DslStep, doc: DslDocument): v
|
|
|
3534
4624
|
/**
|
|
3535
4625
|
* 静止した `type: flow` かどうか。 この形だけ矢印を鎖状に作る (`compileFlow`)。
|
|
3536
4626
|
*
|
|
3537
|
-
*
|
|
3538
|
-
*
|
|
4627
|
+
* 段を持つ形・縦列を書いた形・向きを書いた形は generic 経路へ回るため鎖にならない。
|
|
4628
|
+
*
|
|
4629
|
+
* **`compileFlow` の分岐もこの判定を使う**。 判定を 2 か所に書くと、組み立てだけが別経路へ回り、
|
|
4630
|
+
* 行の対応と端の知らせが鎖のまま残る。 向きを書いた形 (#1494) は組み立てにだけ足され、`A -> C` に
|
|
4631
|
+
* 書いた `head` が `C -> B` に載り、書いた端のとおりの矢印に「端は使われません」 が出ていた (#1986)。
|
|
3539
4632
|
*/
|
|
3540
4633
|
function 鎖でつなぐ形か(doc: DslDocument): boolean {
|
|
3541
4634
|
if (doc.type !== "flow") return false;
|
|
3542
4635
|
if (doc.animate && doc.animate.phases.length > 0) return false;
|
|
4636
|
+
if (doc.direction !== undefined) return false;
|
|
3543
4637
|
return !書いた縦列に置く("flow", doc);
|
|
3544
4638
|
}
|
|
3545
4639
|
|
|
4640
|
+
/**
|
|
4641
|
+
* 静止した流れ図の鎖に並べる登場人物 (#1987)。
|
|
4642
|
+
*
|
|
4643
|
+
* **どの行にも書かれていない部品は鎖に並べない**。 並べると、部品の前後の並び順の矢印は書き手が
|
|
4644
|
+
* 部品へ引いたものではないため部品へ繋がずに外され (#1979)、部品を途中に置いた図から矢印が全て
|
|
4645
|
+
* 消えていた (実測 = `受付 / 印 / 出荷` で矢印 0 本、知らせ 0 件)。 編集画面は部品を本文から抜いてから
|
|
4646
|
+
* 鎖を作るので `受付 -> 出荷` を描き、カタログと絵が食い違っていた。 部品は図の下の格子に置くため、
|
|
4647
|
+
* 鎖から外しても流れの列に空きは生まれない。
|
|
4648
|
+
*
|
|
4649
|
+
* - 部品の一覧に無い部品は仮の箱のまま描かれるので残す (一覧を渡さない組み立ても同じ)
|
|
4650
|
+
* - 大きすぎて取り込まない部品は一覧にあるので外す (図に入らない場所へ鎖を通さない)
|
|
4651
|
+
* - 行の端に書かれた部品は残し、#1979 の繋ぎ方に従う
|
|
4652
|
+
*
|
|
4653
|
+
* 鎖を作る `compileFlow`、行との対応 (`鎖のどの行から来たか`)、端の知らせ
|
|
4654
|
+
* (`reportFlowEndpointNotHonored`) の 3 つがこの並びを使う。 1 つでも `doc.actors` を見ると、
|
|
4655
|
+
* N 本目の矢印と行の対応がずれる。
|
|
4656
|
+
*/
|
|
4657
|
+
function 鎖に並べる登場人物(
|
|
4658
|
+
doc: DslDocument,
|
|
4659
|
+
partsCatalog: Record<string, CdlDiagram> | undefined,
|
|
4660
|
+
): DslActor[] {
|
|
4661
|
+
if (!partsCatalog) return doc.actors;
|
|
4662
|
+
const 行に書かれた = new Set(doc.flow.flatMap((s) => [s.from, s.to]));
|
|
4663
|
+
// 部品でない箱は `partId` を持たず一覧を引けないので、一覧に無い部品と同じく残る
|
|
4664
|
+
return doc.actors.filter(
|
|
4665
|
+
(a) => 行に書かれた.has(a.name) || lookupPartRaw(partsCatalog, a.partId) === undefined,
|
|
4666
|
+
);
|
|
4667
|
+
}
|
|
4668
|
+
|
|
3546
4669
|
/**
|
|
3547
4670
|
* 鎖の N 本目の矢印が、本文のどの行から来たかを返す (#1267)。
|
|
3548
4671
|
*
|
|
@@ -3552,8 +4675,12 @@ function 鎖でつなぐ形か(doc: DslDocument): boolean {
|
|
|
3552
4675
|
*
|
|
3553
4676
|
* 説明文を決めた規則と同じ規則で指定も決める = 説明文と指定が必ず同じ行から来る。
|
|
3554
4677
|
*/
|
|
3555
|
-
function 鎖のどの行から来たか(
|
|
3556
|
-
|
|
4678
|
+
function 鎖のどの行から来たか(
|
|
4679
|
+
doc: DslDocument,
|
|
4680
|
+
並び: readonly DslActor[],
|
|
4681
|
+
edgeIndex: number,
|
|
4682
|
+
): DslStep | undefined {
|
|
4683
|
+
const to = 並び[edgeIndex + 1];
|
|
3557
4684
|
if (to === undefined) return undefined;
|
|
3558
4685
|
return doc.flow.find((s) => s.to === to.name);
|
|
3559
4686
|
}
|
|
@@ -4077,6 +5204,16 @@ const VALUE_NOTICE_HINT: Readonly<Record<string, string>> = {
|
|
|
4077
5204
|
* `mind-map` は engine 側でその性質を持ち続けており、 記法が到達しないのは当時の
|
|
4078
5205
|
* `compileMind` の実装によるものだった。 `#1177` で `compileMind` を `mind-map` に寄せたため、
|
|
4079
5206
|
* **今は記法からも到達する** (一覧へ戻す作業が要らなかったのはこのため)。
|
|
5207
|
+
*
|
|
5208
|
+
* ## 図表の種別は 1 つ残らず載せる (#1668)
|
|
5209
|
+
*
|
|
5210
|
+
* 弧と帯で量を表す 3 種 (`chart-gauge` / `chart-radial` / `chart-stacked-bar`) と、
|
|
5211
|
+
* 値 1 つを大きく示す 2 種 (`chart-stat` / `chart-waffle`) が抜けていた。 5 種とも中身を
|
|
5212
|
+
* payload で受け取って 1 箱で描く = この一覧が言う性質をそのまま持つ。
|
|
5213
|
+
*
|
|
5214
|
+
* 抜けている間、段の `draw:` を書いても指す先が引けず **書けるのに動かない** 状態になる
|
|
5215
|
+
* (`draw` の解決は「1 箱で描く箱がちょうど 1 つ」 を条件にしている)。 `focus:` で登場人物の
|
|
5216
|
+
* 名前を書いた時に何も光らないのも同じ穴で、載せると図の箱が光るようになる。
|
|
4080
5217
|
*/
|
|
4081
5218
|
const SINGLE_BOX_KINDS: ReadonlySet<string> = new Set([
|
|
4082
5219
|
"chart-pie",
|
|
@@ -4090,6 +5227,14 @@ const SINGLE_BOX_KINDS: ReadonlySet<string> = new Set([
|
|
|
4090
5227
|
"journey-map",
|
|
4091
5228
|
// 2 時点を直線でつなぐ図も 1 箱で全体を描く (#1647)
|
|
4092
5229
|
"chart-slope",
|
|
5230
|
+
// 弧と帯で量を表す 3 種も 1 箱で全体を描く (#1668)
|
|
5231
|
+
"chart-gauge",
|
|
5232
|
+
"chart-radial",
|
|
5233
|
+
"chart-stacked-bar",
|
|
5234
|
+
// 値 1 つを大きく示す図と、1 個 = 1% の印を埋める図も同じ (#1668)。
|
|
5235
|
+
// 段の `draw:` は受けない (描画側が起点から描く動きを持たない) が、性質は同じなので載せる
|
|
5236
|
+
"chart-stat",
|
|
5237
|
+
"chart-waffle",
|
|
4093
5238
|
]);
|
|
4094
5239
|
|
|
4095
5240
|
|
|
@@ -4114,18 +5259,19 @@ const SINGLE_BOX_KINDS: ReadonlySet<string> = new Set([
|
|
|
4114
5259
|
* するため、 #998)。
|
|
4115
5260
|
*/
|
|
4116
5261
|
/**
|
|
4117
|
-
* v0.5+ groups section
|
|
4118
|
-
* group.lanes に含まれる lane id 集合に対し、 wrap する `group-{id}` lane を contain: true で生成。
|
|
5262
|
+
* v0.5+ groups section を、 束ねる縦列に重ねる枠の縦列 (`group-{id}`、 contain: true) として図に足す。
|
|
4119
5263
|
*
|
|
4120
|
-
*
|
|
4121
|
-
*
|
|
4122
|
-
* 本実装は CdlDiagram 上に「contain: true な group container lane」 を追加する最小骨格に留める。
|
|
5264
|
+
* ここでは枠を作るだけで、 位置と大きさは縦列の位置が全て決まった後に `applyGroupFrames` が決める。
|
|
5265
|
+
* 作った枠の id を返す = 同じ id の縦列を書き手が `lanes:` に書いていた時は作らず、 後で置き直さない。
|
|
4123
5266
|
*/
|
|
4124
|
-
function applyGroupContainers(diagram: CdlDiagram, doc: DslDocument):
|
|
4125
|
-
|
|
5267
|
+
function applyGroupContainers(diagram: CdlDiagram, doc: DslDocument): Set<string> {
|
|
5268
|
+
const 作った = new Set<string>();
|
|
5269
|
+
if (!doc.groups || Object.keys(doc.groups).length === 0) return 作った;
|
|
4126
5270
|
for (const [id, g] of Object.entries(doc.groups)) {
|
|
4127
5271
|
const containerId = `group-${id}`;
|
|
4128
|
-
|
|
5272
|
+
// 書き手が `lanes:` に同じ id を書いた縦列は、ここより後で図に入る。 枠として作ると書いた縦列と
|
|
5273
|
+
// 重なって 1 本になり、 置き直しが書き手の位置と幅を上書きする
|
|
5274
|
+
if (diagram.lanes.some((l) => l.id === containerId) || doc.lanes?.[containerId]) continue;
|
|
4129
5275
|
diagram.lanes.push({
|
|
4130
5276
|
id: containerId,
|
|
4131
5277
|
width: 800,
|
|
@@ -4134,7 +5280,119 @@ function applyGroupContainers(diagram: CdlDiagram, doc: DslDocument): void {
|
|
|
4134
5280
|
// 束ねる lane 群に重ねて描く枠。 横に並べる lane ではないので、 engine の間隔調整
|
|
4135
5281
|
// (lane を詰めた分を幅で埋め合わせる処理) の対象から外す。
|
|
4136
5282
|
role: "overlay",
|
|
5283
|
+
// 置き直すまでは座標を固定しておく。 固定しない枠は縦列の並びに加わり、 他の縦列の間隔を
|
|
5284
|
+
// 広げる (実測 = 3 本の縦列の 2 本目が 666 から 710 へ動いた)。 並びから外した配置で測らないと、
|
|
5285
|
+
// 枠を置いた後に縦列が詰め直されて枠から外れる
|
|
5286
|
+
posX: 0,
|
|
5287
|
+
posY: 0,
|
|
5288
|
+
posW: 1,
|
|
5289
|
+
posH: 1,
|
|
4137
5290
|
});
|
|
5291
|
+
作った.add(containerId);
|
|
5292
|
+
}
|
|
5293
|
+
return 作った;
|
|
5294
|
+
}
|
|
5295
|
+
|
|
5296
|
+
/**
|
|
5297
|
+
* 組の枠と束ねた縦列の間の余白。
|
|
5298
|
+
*
|
|
5299
|
+
* | 向き | 値 | 理由 (実測) |
|
|
5300
|
+
* |---|---|---|
|
|
5301
|
+
* | 上 | 40 | 枠の名札を縦列の名札に重ねない高さ。 0 で 74 重なり、 40 で重ならない |
|
|
5302
|
+
* | 下 | 20 | 箱の下端から枠を離す |
|
|
5303
|
+
* | 横 (囲いを持つ縦列) | 20 | 枠を縦列の囲いの線に重ねない。 囲いを持つ縦列の間は矢印の名前が入る広さがある |
|
|
5304
|
+
* | 横 (囲いの無い縦列) | 0 | 縦列の端は箱から既に離れている。 20 取ると、 縦列の間に置いた矢印の名前を枠の線が貫く (箱の種別 `person` で 8 食い込んだ) |
|
|
5305
|
+
*/
|
|
5306
|
+
const 組の枠の余白 = { 上: 40, 下: 20, 囲いの横: 20, 囲いの無い横: 0 } as const;
|
|
5307
|
+
|
|
5308
|
+
/**
|
|
5309
|
+
* 組 (`groups:`) の枠を、 束ねた縦列とその中の箱を全て囲む位置と大きさに置く (#1972)。
|
|
5310
|
+
*
|
|
5311
|
+
* **縦列の位置が全て決まった後に 1 度だけ配置して測る**。 枠は作った時から座標を固定してあるので、
|
|
5312
|
+
* 枠を置いても他の縦列は動かない (固定した縦列は並びに加わらない)。
|
|
5313
|
+
*
|
|
5314
|
+
* 束ねた縦列の扱い。
|
|
5315
|
+
*
|
|
5316
|
+
* | 状態 | 扱い |
|
|
5317
|
+
* |---|---|
|
|
5318
|
+
* | 図に無い縦列を書いた | その縦列を除いて囲み、 無いことを知らせる |
|
|
5319
|
+
* | 図に在る縦列が 1 本も無い | 枠を作らず、 知らせる (図の端に何も囲まない枠を描かない) |
|
|
5320
|
+
* | 間に束ねない縦列を挟む | 間の縦列ごと囲み、 挟んだ縦列を知らせる (縦列の並びは書いた順を変えない) |
|
|
5321
|
+
*/
|
|
5322
|
+
function applyGroupFrames(
|
|
5323
|
+
diagram: CdlDiagram,
|
|
5324
|
+
doc: DslDocument,
|
|
5325
|
+
作った枠: ReadonlySet<string>,
|
|
5326
|
+
onNotice?: (notice: CompileNotice) => void,
|
|
5327
|
+
): void {
|
|
5328
|
+
if (作った枠.size === 0) return;
|
|
5329
|
+
const 置く: { 枠: CdlDiagram["lanes"][number]; 名前: string; line: number; 縦列: string[] }[] = [];
|
|
5330
|
+
for (const [id, g] of Object.entries(doc.groups ?? {})) {
|
|
5331
|
+
const 枠 = diagram.lanes.find((l) => l.id === `group-${id}`);
|
|
5332
|
+
if (!枠 || !作った枠.has(枠.id)) continue;
|
|
5333
|
+
const 書いた = Array.isArray(g.lanes) ? g.lanes : [];
|
|
5334
|
+
const 在る = 書いた.filter((x) => diagram.lanes.some((l) => l.id === x && !作った枠.has(l.id)));
|
|
5335
|
+
const 無い = 書いた.filter((x) => !在る.includes(x));
|
|
5336
|
+
if (在る.length === 0) {
|
|
5337
|
+
diagram.lanes = diagram.lanes.filter((l) => l !== 枠);
|
|
5338
|
+
onNotice?.({
|
|
5339
|
+
kind: "group-lane-missing",
|
|
5340
|
+
actor: id,
|
|
5341
|
+
line: g.pos?.line ?? 0,
|
|
5342
|
+
message:
|
|
5343
|
+
書いた.length === 0
|
|
5344
|
+
? `組 "${truncateForMessage(id)}" は束ねる縦列を書いていないので、枠を描きません`
|
|
5345
|
+
: `組 "${truncateForMessage(id)}" が束ねる縦列 ${無い.map((x) => `"${truncateForMessage(x)}"`).join(", ")} が図に無いので、枠を描きません`,
|
|
5346
|
+
hint: "`lanes: [縦列の名前, ...]` に `lanes:` で書いた縦列の名前を並べる",
|
|
5347
|
+
});
|
|
5348
|
+
continue;
|
|
5349
|
+
}
|
|
5350
|
+
if (無い.length > 0) {
|
|
5351
|
+
onNotice?.({
|
|
5352
|
+
kind: "group-lane-missing",
|
|
5353
|
+
actor: id,
|
|
5354
|
+
line: g.pos?.line ?? 0,
|
|
5355
|
+
message: `組 "${truncateForMessage(id)}" が束ねる縦列 ${無い.map((x) => `"${truncateForMessage(x)}"`).join(", ")} が図に無いので、残りの縦列だけを囲みます`,
|
|
5356
|
+
hint: "`lanes:` で書いた縦列の名前か確かめる",
|
|
5357
|
+
});
|
|
5358
|
+
}
|
|
5359
|
+
置く.push({ 枠, 名前: id, line: g.pos?.line ?? 0, 縦列: 在る });
|
|
5360
|
+
}
|
|
5361
|
+
if (置く.length === 0) return;
|
|
5362
|
+
|
|
5363
|
+
const 配置 = layout(diagram);
|
|
5364
|
+
const 並びの縦列 = 配置.lanes.filter((l) => !作った枠.has(l.id));
|
|
5365
|
+
for (const { 枠, 名前, line, 縦列 } of 置く) {
|
|
5366
|
+
const 束 = 並びの縦列.filter((l) => 縦列.includes(l.id));
|
|
5367
|
+
const 左 = Math.min(...束.map((l) => l.x ?? 0));
|
|
5368
|
+
const 右 = Math.max(...束.map((l) => (l.x ?? 0) + l.width));
|
|
5369
|
+
// 束ねた縦列の左端と右端の間に入る縦列は、 束ねなくても枠に入る
|
|
5370
|
+
const 挟んだ = 並びの縦列.filter(
|
|
5371
|
+
(l) => !縦列.includes(l.id) && (l.x ?? 0) < 右 && (l.x ?? 0) + l.width > 左,
|
|
5372
|
+
);
|
|
5373
|
+
if (挟んだ.length > 0) {
|
|
5374
|
+
onNotice?.({
|
|
5375
|
+
kind: "group-lanes-apart",
|
|
5376
|
+
actor: 名前,
|
|
5377
|
+
line,
|
|
5378
|
+
message: `組 "${truncateForMessage(名前)}" が束ねる縦列の間に ${挟んだ.map((l) => `"${truncateForMessage(l.id)}"`).join(", ")} があるので、その縦列も枠に入ります`,
|
|
5379
|
+
hint: "束ねる縦列を `lanes:` で隣り合う順に並べる",
|
|
5380
|
+
});
|
|
5381
|
+
}
|
|
5382
|
+
const 囲む縦列 = new Set([...束, ...挟んだ].map((l) => l.id));
|
|
5383
|
+
const 囲む = [...束, ...挟んだ];
|
|
5384
|
+
const 箱 = 配置.nodes.filter((n) => 囲む縦列.has(n.lane));
|
|
5385
|
+
const 上 = Math.min(...囲む.map((l) => l.y ?? 0));
|
|
5386
|
+
const 下 = Math.max(
|
|
5387
|
+
...囲む.map((l) => (l.y ?? 0) + (l.height ?? 0)),
|
|
5388
|
+
...箱.map((n) => n.cy + n.h / 2),
|
|
5389
|
+
);
|
|
5390
|
+
const 囲いを持つ = diagram.lanes.some((l) => 囲む縦列.has(l.id) && l.contain === true);
|
|
5391
|
+
const 横 = 囲いを持つ ? 組の枠の余白.囲いの横 : 組の枠の余白.囲いの無い横;
|
|
5392
|
+
枠.posX = 左 - 横;
|
|
5393
|
+
枠.posY = 上 - 組の枠の余白.上;
|
|
5394
|
+
枠.posW = 右 - 左 + 横 * 2;
|
|
5395
|
+
枠.posH = 下 - 上 + 組の枠の余白.上 + 組の枠の余白.下;
|
|
4138
5396
|
}
|
|
4139
5397
|
}
|
|
4140
5398
|
|
|
@@ -4459,6 +5717,8 @@ function compileClass(doc: DslDocument): CdlDiagram {
|
|
|
4459
5717
|
type: s2.relation ?? "uses",
|
|
4460
5718
|
...(s2.label ? { label: s2.label } : {}),
|
|
4461
5719
|
...(s2.sub ? { cardinality: s2.sub } : {}),
|
|
5720
|
+
// 出どころ側の多重度 (#1771)。 engine が出どころの端に添える (cdl#825)
|
|
5721
|
+
...(s2.tailSub ? { tailCardinality: s2.tailSub } : {}),
|
|
4462
5722
|
...(s2.tone ? { tone: s2.tone } : {}),
|
|
4463
5723
|
...(s2.style ? { style: s2.style } : {}),
|
|
4464
5724
|
...(s2.head ? { head: s2.head } : {}),
|
|
@@ -5170,7 +6430,7 @@ function compileJourney(doc: DslDocument, onNotice?: (n: CompileNotice) => void)
|
|
|
5170
6430
|
読めない.push(a.name);
|
|
5171
6431
|
continue;
|
|
5172
6432
|
}
|
|
5173
|
-
data.push({ id: slugify(a.name), title: 箱の題(a), emotion:
|
|
6433
|
+
data.push({ id: slugify(a.name), title: 箱の題(a), emotion: 語, ...道筋の欄(a) });
|
|
5174
6434
|
continue;
|
|
5175
6435
|
}
|
|
5176
6436
|
const e = 気持ち.get(語);
|
|
@@ -5283,7 +6543,7 @@ function compileQuadrant(doc: DslDocument, onNotice?: (n: CompileNotice) => void
|
|
|
5283
6543
|
読めない.push(a.name);
|
|
5284
6544
|
continue;
|
|
5285
6545
|
}
|
|
5286
|
-
items.push({ id: slugify(a.name), title: 箱の題(a), quadrant: 語
|
|
6546
|
+
items.push({ id: slugify(a.name), title: 箱の題(a), quadrant: 語 });
|
|
5287
6547
|
continue;
|
|
5288
6548
|
}
|
|
5289
6549
|
const q = 区画.get(語);
|
|
@@ -5326,7 +6586,7 @@ function compileQuadrant(doc: DslDocument, onNotice?: (n: CompileNotice) => void
|
|
|
5326
6586
|
* 段の目印を読み取る。 目印と、 それを落とした残りの説明を返す (#1098)。
|
|
5327
6587
|
*
|
|
5328
6588
|
* 目印 (`L1` / `L2` / `L3`) は「どの段に置くか」 を組み立てに伝えるためのもので、 読む人には
|
|
5329
|
-
* 意味を持たない。 段の名前は枠のラベル (
|
|
6589
|
+
* 意味を持たない。 段の名前は枠のラベル (`全体の見取り図` 等) が出すので二重でもある。
|
|
5330
6590
|
* 読み取ったら説明から落とす = 書いた人が説明として書いた部分だけが箱に出る。
|
|
5331
6591
|
*
|
|
5332
6592
|
* | 書いた文字 | 段 | 残る説明 |
|
|
@@ -5356,9 +6616,9 @@ function 段を読み取る(subtitle: string | undefined): { 段: number; 説明
|
|
|
5356
6616
|
* C4 preset (C4 model 階層 system context 専用 layout)
|
|
5357
6617
|
*
|
|
5358
6618
|
* 設計 ... actor.subtitle の先頭に "L1" / "L2" / "L3" を置き、 階層 lane を生成。
|
|
5359
|
-
* - L1 = System Context
|
|
5360
|
-
* - L2 = Container
|
|
5361
|
-
* - L3 = Component
|
|
6619
|
+
* - L1 = 全体の見取り図 (C4 model の System Context)
|
|
6620
|
+
* - L2 = 動かす単位 (同 Container = 単体で動かす application や保管庫)
|
|
6621
|
+
* - L3 = 部品 (同 Component)
|
|
5362
6622
|
*
|
|
5363
6623
|
* 実装 ... **中身のある段だけ** lane を作り、 使う段を左から順に詰めて横並び (contain: true で
|
|
5364
6624
|
* 囲む) 配置する。 3 lane を常に作ると中身のない枠が画面に残り、 描かれ損ねたように見える (#1078)。
|
|
@@ -5371,7 +6631,9 @@ function compileC4(doc: DslDocument): CdlDiagram {
|
|
|
5371
6631
|
const b = diagram(slugify(doc.title), { topic: doc.title, type: "infrastructure" });
|
|
5372
6632
|
const LANE_W = 400;
|
|
5373
6633
|
const LANE_GAP = 80;
|
|
5374
|
-
|
|
6634
|
+
// 段の名前は **枠に描く字** で、記法に打ち込む識別子ではない (#1886)。 打ち込む字は目印
|
|
6635
|
+
// (`L1` / `L2` / `L3`) の側で、そちらは綴りを変えない
|
|
6636
|
+
const 段の名前: Record<number, string> = { 1: "全体の見取り図", 2: "動かす単位", 3: "部品" };
|
|
5375
6637
|
|
|
5376
6638
|
// 段は **先頭一致** で読み、 読んだ目印は説明から落とす (`段を読み取る` の説明を参照)
|
|
5377
6639
|
const 割当 = doc.actors.map((a, idx) => {
|
|
@@ -5612,6 +6874,8 @@ type 放射で描けない欄 =
|
|
|
5612
6874
|
// 前の時点の値 (#1450)。 放射の枝は 1 時点しか描かない = 2 本目の帯に当たるものが無い
|
|
5613
6875
|
| "previous"
|
|
5614
6876
|
| "colorHex"
|
|
6877
|
+
// 部品に書いた色の名前 (#1973)。 色番号と同じく部品にしか残らない
|
|
6878
|
+
| "partColorName"
|
|
5615
6879
|
| "stateOverride"
|
|
5616
6880
|
| "posX"
|
|
5617
6881
|
| "posY"
|
|
@@ -5620,7 +6884,6 @@ type 放射で描けない欄 =
|
|
|
5620
6884
|
| "scale"
|
|
5621
6885
|
| "scaleKeys"
|
|
5622
6886
|
| "posRel"
|
|
5623
|
-
| "nodes"
|
|
5624
6887
|
| "layoutPos"
|
|
5625
6888
|
// 箱の中に描く図形 (#1374)。 放射の枝は箱の中に図形を持たない
|
|
5626
6889
|
| "shape"
|
|
@@ -5677,6 +6940,7 @@ const 放射で描けない欄の名前: Record<放射で描けない欄, string
|
|
|
5677
6940
|
final: "始まり / 終わり の印",
|
|
5678
6941
|
previous: "前の時点の値",
|
|
5679
6942
|
colorHex: "色番号",
|
|
6943
|
+
partColorName: "色の名前",
|
|
5680
6944
|
stateOverride: "状態の上書き",
|
|
5681
6945
|
// 位置は「登場人物ごとの箱をどこに置くか」 の指定で、 箱が 1 つの図では置く先が無い
|
|
5682
6946
|
posX: "位置 (座標)",
|
|
@@ -5686,7 +6950,6 @@ const 放射で描けない欄の名前: Record<放射で描けない欄, string
|
|
|
5686
6950
|
scale: "倍率",
|
|
5687
6951
|
scaleKeys: "倍率",
|
|
5688
6952
|
posRel: "位置 (相対)",
|
|
5689
|
-
nodes: "中の箱ごとの指定",
|
|
5690
6953
|
layoutPos: "配置のずらし",
|
|
5691
6954
|
shape: "箱の中の図形",
|
|
5692
6955
|
visibleIf: "出す条件",
|
|
@@ -5729,7 +6992,8 @@ function 放射に出す文字(a: DslActor): { title: string; subtitle?: string
|
|
|
5729
6992
|
}
|
|
5730
6993
|
|
|
5731
6994
|
function compileMind(doc: DslDocument, onNotice?: (n: CompileNotice) => void): CdlDiagram {
|
|
5732
|
-
|
|
6995
|
+
// 図の型は描画側の組み立て関数 `mindMap()` と同じ綴り (cdl 0.63.0 で `mindmap` から `mind` に揃った)
|
|
6996
|
+
const b = diagram(slugify(doc.title), { topic: doc.title, type: "mind" });
|
|
5733
6997
|
const { w: W, h: H } = 図表の大きさ.mind;
|
|
5734
6998
|
|
|
5735
6999
|
const 伝える = (kind: CompileNotice["kind"], 名: string, message: string, line = 0): void => {
|
|
@@ -6025,8 +7289,8 @@ function applyV05Extensions(
|
|
|
6025
7289
|
// 組み立てだけで渡すと、同じ記法でも静止図では指定が消える。
|
|
6026
7290
|
//
|
|
6027
7291
|
// **幅が図に出るかは縦列との大小で決まる**。 縦列に収まれば箱だけが変わり、
|
|
6028
|
-
// 縦列より広ければ縦列ごと押し広げる (実測 =
|
|
6029
|
-
//
|
|
7292
|
+
// 縦列より広ければ縦列ごと押し広げる (実測 = 状態遷移図の 320 は縦列 370 に収まって
|
|
7293
|
+
// 図が変わらないが、入れ子の状態遷移図の 280 に対し既定 640 は縦列 330 を押し広げた)。
|
|
6030
7294
|
// #1260 で 1 件だけ見て「見た目に出ない」 と判断し配線を外した = 同じ誤りを繰り返さない
|
|
6031
7295
|
if (a.posW !== undefined) node.w = a.posW;
|
|
6032
7296
|
if (a.posH !== undefined) node.h = a.posH;
|
|
@@ -6190,7 +7454,7 @@ function injectPhasesFallback(diagram: CdlDiagram, doc: DslDocument): void {
|
|
|
6190
7454
|
for (const p of doc.animate.phases) {
|
|
6191
7455
|
const activateIds: string[] = [...resolveIds(p.highlight ?? [])];
|
|
6192
7456
|
// `draw:` を描画側の欄へ写す (#1312 / #1314 / #1318)。 相手は 1 箱で図全体を描く
|
|
6193
|
-
//
|
|
7457
|
+
// 対象の箱で、必ず 1 つに決まるため名前を書かせずに引ける。
|
|
6194
7458
|
//
|
|
6195
7459
|
// **`activate` と兼ねない**。 描画側は焦点と別集合で持つ (`cdl#512`) = 焦点が当たり
|
|
6196
7460
|
// 続ける図で毎段引き直しになるため。 書いた段だけが欄を持つ
|
|
@@ -6311,23 +7575,27 @@ function slugLookup(byName: ReadonlyMap<string, string>, wanted: string): string
|
|
|
6311
7575
|
return hit;
|
|
6312
7576
|
}
|
|
6313
7577
|
|
|
6314
|
-
function compileFlow(
|
|
7578
|
+
function compileFlow(
|
|
7579
|
+
doc: DslDocument,
|
|
7580
|
+
/** 鎖に並べない部品を決める一覧 (#1987) */
|
|
7581
|
+
partsCatalog?: Record<string, CdlDiagram>,
|
|
7582
|
+
): CdlDiagram {
|
|
6315
7583
|
// v0.4 ... animation あり時 builder 直接経路で複数 phase 注入
|
|
6316
7584
|
// **縦列を書いた形は動きの有無に関わらず generic 経路へ** (#1263)。 動く図だけで効かせると、
|
|
6317
7585
|
// 同じ記法でも静止図では指定が黙って消える (実測 = 縦列 3 本のはずが 1 本になり知らせも出ない)
|
|
6318
7586
|
// **向きを書いた形も generic 経路へ** (#1494)。 静止図の経路は並びを固定で持つので、
|
|
6319
7587
|
// ここを通さないと書いた向きが黙って消える (縦列を書いた形と同じ理由)
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
doc.direction !== undefined
|
|
6324
|
-
) {
|
|
7588
|
+
//
|
|
7589
|
+
// 振り分けは行の対応と端の知らせと同じ判定で行う (#1986)
|
|
7590
|
+
if (!鎖でつなぐ形か(doc)) {
|
|
6325
7591
|
return compileGenericWithAnimate(doc, { kind: "flow", laneId: "main", laneWidth: 400 });
|
|
6326
7592
|
}
|
|
6327
7593
|
// 登場人物が 0 人なら枠も作らない。 描画側の `flow()` は枠を必ず 1 つ作るため、 そのまま
|
|
6328
7594
|
// 通すと中身の無い枠が残る (実測 = `title` と `type` だけの本文で枠 `flow` が空)。
|
|
6329
|
-
// 枠を持たない図として返す = `swimlane` / `c4` が 0 人で枠 0
|
|
6330
|
-
|
|
7595
|
+
// 枠を持たない図として返す = `swimlane` / `c4` が 0 人で枠 0 になるのと揃う。
|
|
7596
|
+
// 鎖に並べる登場人物で見る = 行に書かれていない部品しか居ない図も枠を作らない (#1987)
|
|
7597
|
+
const 並び = 鎖に並べる登場人物(doc, partsCatalog);
|
|
7598
|
+
if (並び.length === 0) {
|
|
6331
7599
|
return diagram(slugify(doc.title), { topic: doc.title, type: "flow" }).build();
|
|
6332
7600
|
}
|
|
6333
7601
|
// flow preset は actors を順に step として配置、 step 間に edge auto
|
|
@@ -6336,8 +7604,9 @@ function compileFlow(doc: DslDocument): CdlDiagram {
|
|
|
6336
7604
|
topic: doc.title,
|
|
6337
7605
|
});
|
|
6338
7606
|
// 各 actor を step として登録、 edge label は流れ から拾う
|
|
6339
|
-
for (
|
|
6340
|
-
|
|
7607
|
+
for (const a of 並び) {
|
|
7608
|
+
// 名前から id を作れない時の番号は、書いた順の番号にする (部品を飛ばしても変わらない)
|
|
7609
|
+
const i = doc.actors.indexOf(a);
|
|
6341
7610
|
// 直前の step との edge label = この actor を to に持つ flow から拾う
|
|
6342
7611
|
const incomingEdge = doc.flow.find((s) => s.to === a.name);
|
|
6343
7612
|
const edgeLabel = incomingEdge?.label;
|