@cardenelabs/dragon 0.11.0 → 0.13.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 +74 -58
- package/dist/index.cjs +2891 -120
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +544 -2
- package/dist/index.d.ts +544 -2
- package/dist/index.js +2891 -120
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/compile.ts +457 -188
- package/src/input-size.ts +9 -1
- package/src/json-parser.ts +259 -1
- package/src/schemas/diagram.json +985 -3
- package/src/types.ts +39 -1
- package/src/v05/parser-types.ts +17 -0
- package/src/v05/parser.ts +598 -35
- package/src/v05/readout-table.generated.ts +1088 -0
package/src/compile.ts
CHANGED
|
@@ -9,16 +9,41 @@
|
|
|
9
9
|
* v0.2 ... 6 preset 全対応 (sequence / flow / swimlane / er / state / topology)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type {
|
|
13
|
-
|
|
12
|
+
import type {
|
|
13
|
+
DslActor,
|
|
14
|
+
DslDocument,
|
|
15
|
+
DslLane,
|
|
16
|
+
DslPhase,
|
|
17
|
+
DslStep,
|
|
18
|
+
DslValue,
|
|
19
|
+
PresetType,
|
|
20
|
+
} from "./types";
|
|
21
|
+
import type {
|
|
22
|
+
CdlDiagram,
|
|
23
|
+
CdlEdge,
|
|
24
|
+
ErRelationCardinality,
|
|
25
|
+
FormulaAst,
|
|
26
|
+
LaidDiagram,
|
|
27
|
+
} from "@cardenelabs/cdl";
|
|
14
28
|
import {
|
|
15
|
-
sequence,
|
|
16
|
-
|
|
17
|
-
|
|
29
|
+
sequence,
|
|
30
|
+
flow,
|
|
31
|
+
swimlane,
|
|
32
|
+
er,
|
|
33
|
+
stateMachine,
|
|
34
|
+
topology,
|
|
35
|
+
diagram,
|
|
36
|
+
layout,
|
|
37
|
+
rendersRows,
|
|
38
|
+
requiredRowsHeight,
|
|
39
|
+
requiredRowsWidth,
|
|
40
|
+
NODE_KINDS,
|
|
41
|
+
applyDerivedValues,
|
|
42
|
+
parseFormula,
|
|
18
43
|
} from "@cardenelabs/cdl";
|
|
19
44
|
import { parseFocusEntry } from "./focus";
|
|
20
45
|
import { DRAW_TARGETS } from "./v05/parser";
|
|
21
|
-
import { isColorValue, stripExternalPaint } from "./color";
|
|
46
|
+
import { isColorValue, pointsOutside, stripExternalPaint } from "./color";
|
|
22
47
|
import {
|
|
23
48
|
MAX_INPUT_ELEMENTS,
|
|
24
49
|
countDiagramElements,
|
|
@@ -279,12 +304,24 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
279
304
|
// 扱いが式形と揃う。 時計の状態と段の補間もここで足す
|
|
280
305
|
const 畳んだきっかけ = foldValueTriggers(merged, doc, opts?.onNotice);
|
|
281
306
|
attachDerivedValues(merged, doc, opts?.onNotice, inheritedDerivedSourceLines, 畳んだきっかけ);
|
|
307
|
+
// 値を見せる部品を図に載せる (#1374)。 parts が持つ部品は merge 済みなので残したまま足す。
|
|
308
|
+
// **外部参照を落とす前に載せる**。 後から足すと readout の color / colors だけが出口の検査を
|
|
309
|
+
// 迂回し、 `url(https://...)` がそのまま SVG の paint 属性へ届く。
|
|
310
|
+
if (doc.readouts && doc.readouts.length > 0) {
|
|
311
|
+
// 出口の paint 検査は diagram を直接書き換える。 doc の object を共有すると、検査が
|
|
312
|
+
// compileToCdl の入力まで書き換えて入力不変性を壊すため、nested field も含めて写す。
|
|
313
|
+
const ownReadouts = doc.readouts.map(
|
|
314
|
+
(readout) => deepRewriteStrings(readout, (value) => value) as typeof readout,
|
|
315
|
+
);
|
|
316
|
+
merged.readouts = [...(merged.readouts ?? []), ...ownReadouts];
|
|
317
|
+
}
|
|
282
318
|
// 図の外を指す値を、 色を塗る位置から落とす (#1004)。
|
|
283
319
|
//
|
|
284
320
|
// 入口ごとに塞ぐ形は採らない。 状態の上書き / phase が入れる値 / 画面が直接書く背景色 /
|
|
285
321
|
// 埋め込んだ JSON / states / values と入口が複数あり、 1 つ見落とすと穴が残る。
|
|
286
322
|
// **図への追加を全て終えた後**、 出口で 1 度だけ見る。 この後に状態を足すと検査を迂回する。
|
|
287
|
-
|
|
323
|
+
const 外した部品の配色 = stripExternalReadoutPalettes(merged);
|
|
324
|
+
for (const dropped of [...stripExternalPaint(merged), ...外した部品の配色]) {
|
|
288
325
|
opts?.onNotice?.({
|
|
289
326
|
kind: "external-paint-dropped",
|
|
290
327
|
actor: dropped.path,
|
|
@@ -299,6 +336,29 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
|
|
|
299
336
|
return merged;
|
|
300
337
|
}
|
|
301
338
|
|
|
339
|
+
/**
|
|
340
|
+
* readout の配色配列から外部参照を落とす (#1374)。
|
|
341
|
+
*
|
|
342
|
+
* 共通の `stripExternalPaint` は `color` / `fill` のような key を見るが、 `colors` の中へ
|
|
343
|
+
* 入ると配列要素には key が無い。 `heat-cell` が公開した配色だけはここで要素ごとに閉じる。
|
|
344
|
+
*/
|
|
345
|
+
function stripExternalReadoutPalettes(diagram: CdlDiagram): Array<{ path: string; value: string }> {
|
|
346
|
+
const stripped: Array<{ path: string; value: string }> = [];
|
|
347
|
+
for (let i = 0; i < (diagram.readouts?.length ?? 0); i += 1) {
|
|
348
|
+
const readout = diagram.readouts?.[i] as { colors?: readonly string[] } | undefined;
|
|
349
|
+
if (!readout?.colors) continue;
|
|
350
|
+
const colors = [...readout.colors];
|
|
351
|
+
for (let j = 0; j < colors.length; j += 1) {
|
|
352
|
+
const color = colors[j];
|
|
353
|
+
if (color === undefined || !pointsOutside(color)) continue;
|
|
354
|
+
colors[j] = "none";
|
|
355
|
+
stripped.push({ path: `readouts[${i}].colors[${j}]`, value: color });
|
|
356
|
+
}
|
|
357
|
+
readout.colors = colors;
|
|
358
|
+
}
|
|
359
|
+
return stripped;
|
|
360
|
+
}
|
|
361
|
+
|
|
302
362
|
/**
|
|
303
363
|
* 書いた状態 (`states:`) を図に載せる (#1162)。
|
|
304
364
|
*
|
|
@@ -907,7 +967,9 @@ function disambiguateActorIds(
|
|
|
907
967
|
}
|
|
908
968
|
const 新しい名前 = new Map<string, string>();
|
|
909
969
|
// 名前で並べてから配る = 書いた順に依らない
|
|
910
|
-
for (const a of [...作り替える].sort((x, y) =>
|
|
970
|
+
for (const a of [...作り替える].sort((x, y) =>
|
|
971
|
+
x.name < y.name ? -1 : x.name > y.name ? 1 : 0,
|
|
972
|
+
)) {
|
|
911
973
|
const 尾 = 名前の尾(a.name);
|
|
912
974
|
// **id の長さの上限のぶん、 元の名前を先に切る** (Round 1 の指摘)。 切らないと尾が
|
|
913
975
|
// 64 字で落ちて、 同じ頭を持つ長い名前どうしが元のまま重なる
|
|
@@ -1453,10 +1515,7 @@ function applyCanvasPivotPositions(diagram: CdlDiagram, doc: DslDocument): void
|
|
|
1453
1515
|
for (const [subKey, override] of Object.entries(actor.nodes)) {
|
|
1454
1516
|
if (override.posX === undefined || override.posY === undefined) continue;
|
|
1455
1517
|
for (const node of diagram.nodes) {
|
|
1456
|
-
if (
|
|
1457
|
-
node.id === `${aliasSlug}-${subKey}` ||
|
|
1458
|
-
node.id === `${subKey}-${aliasSlug}`
|
|
1459
|
-
) {
|
|
1518
|
+
if (node.id === `${aliasSlug}-${subKey}` || node.id === `${subKey}-${aliasSlug}`) {
|
|
1460
1519
|
node.posX = override.posX;
|
|
1461
1520
|
node.posY = override.posY;
|
|
1462
1521
|
if (override.posW !== undefined) node.posW = override.posW;
|
|
@@ -1684,8 +1743,6 @@ export function partTargetScale(
|
|
|
1684
1743
|
return partScaleFactor(part, targetW, targetH, undefined);
|
|
1685
1744
|
}
|
|
1686
1745
|
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
1746
|
/**
|
|
1690
1747
|
* パーツ 1 個が図の上で占める外接矩形。
|
|
1691
1748
|
*
|
|
@@ -2009,8 +2066,7 @@ export function partsGridCenters(
|
|
|
2009
2066
|
if (items.length === 0) return out;
|
|
2010
2067
|
// 公開している関数なので、 呼出側が渡す値を入口で閉じる。 数でない箱の数や桁溢れを
|
|
2011
2068
|
// そのまま計算に入れると、 描けない座標を返すことになる
|
|
2012
|
-
const safeCount =
|
|
2013
|
-
Number.isSafeInteger(baseNodeCount) && baseNodeCount >= 0 ? baseNodeCount : 0;
|
|
2069
|
+
const safeCount = Number.isSafeInteger(baseNodeCount) && baseNodeCount >= 0 ? baseNodeCount : 0;
|
|
2014
2070
|
const top = safeCount * STACK_PITCH + PARTS_GAP * 2;
|
|
2015
2071
|
// 同じ名前が 2 度来たら先の方だけを見る。 後の分を残すと、 どちらを指したか決められない
|
|
2016
2072
|
// まま列の送り幅にも影響する
|
|
@@ -2212,9 +2268,7 @@ function partBoxes(
|
|
|
2212
2268
|
const t = partTargetSize(part, a.posW, a.posH, a.scale);
|
|
2213
2269
|
const size = partExtent(part, t.w, t.h);
|
|
2214
2270
|
const placed =
|
|
2215
|
-
a.posX !== undefined && a.posY !== undefined
|
|
2216
|
-
? { cx: a.posX, cy: a.posY }
|
|
2217
|
-
: grid.get(a.name);
|
|
2271
|
+
a.posX !== undefined && a.posY !== undefined ? { cx: a.posX, cy: a.posY } : grid.get(a.name);
|
|
2218
2272
|
// 相対で書いた分はここでは決まらない (解決側が後で埋める)
|
|
2219
2273
|
if (!placed) continue;
|
|
2220
2274
|
// 渡す座標は段の中心。 矩形の中心はそこからずれる
|
|
@@ -2234,68 +2288,68 @@ function cleanupPlaceholderActor(
|
|
|
2234
2288
|
doc: DslDocument,
|
|
2235
2289
|
a: { name: string; lane?: string },
|
|
2236
2290
|
): void {
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
}
|
|
2291
|
+
const aliasSlug = slugify(a.name);
|
|
2292
|
+
const ownedLaneIds = new Set<string>();
|
|
2293
|
+
if (doc.type === "sequence" || doc.type === "solidity") {
|
|
2294
|
+
for (const l of target.lanes) {
|
|
2295
|
+
// 明示 lane mapping (a.lane) 先は part の張替え先で actor 専用 lane ではないため除外
|
|
2296
|
+
if (a.lane !== undefined && l.id === a.lane) continue;
|
|
2297
|
+
if (l.label === a.name) ownedLaneIds.add(l.id);
|
|
2245
2298
|
}
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2299
|
+
}
|
|
2300
|
+
const ownedNodeIds = new Set<string>();
|
|
2301
|
+
for (const n of target.nodes) {
|
|
2302
|
+
if (ownedLaneIds.has(n.lane)) ownedNodeIds.add(n.id);
|
|
2303
|
+
}
|
|
2304
|
+
// actor 専用 lane を引き当てられない経路 (flow / topology 等の共有 lane preset) は従来どおり dragon
|
|
2305
|
+
// slug の prefix match に fallback する。 これらは 1 actor = 1 node (id = slug) の生成規則。
|
|
2306
|
+
const matchesAliasSlug = (id: string): boolean => {
|
|
2307
|
+
if (id === aliasSlug) return true;
|
|
2308
|
+
if (id.startsWith(`${aliasSlug}-`)) return true;
|
|
2309
|
+
// sequence step anchor = `s{N}-{aliasSlug}` pattern
|
|
2310
|
+
if (/^s\d+-/.test(id) && id.endsWith(`-${aliasSlug}`)) return true;
|
|
2311
|
+
return false;
|
|
2312
|
+
};
|
|
2313
|
+
const relatedToActor = (id: string): boolean =>
|
|
2314
|
+
ownedLaneIds.size > 0 ? ownedNodeIds.has(id) : matchesAliasSlug(id);
|
|
2315
|
+
target.nodes = target.nodes.filter((n) => !relatedToActor(n.id));
|
|
2316
|
+
// edge も同経路で削除 (parts actor に接続していた flow を除去、 parts merge 後の flow は user が
|
|
2317
|
+
// 別途書く経路になる)。 削除した edge の id は phase.activate に残ると dangling 参照になるため回収する。
|
|
2318
|
+
const removedEdgeIds = new Set<string>();
|
|
2319
|
+
target.edges = target.edges.filter((e) => {
|
|
2320
|
+
const drop = relatedToActor(e.from) || relatedToActor(e.to);
|
|
2321
|
+
if (drop) removedEdgeIds.add(e.id);
|
|
2322
|
+
return !drop;
|
|
2323
|
+
});
|
|
2324
|
+
// lane も削除 = sequence preset は parts actor 用に lane (id = aliasSlug、 label = actor 名) を
|
|
2325
|
+
// 生成する。 node/edge だけ消して lane を残すと、 merge 後の part 側 lane (label = alias) と 2 本が
|
|
2326
|
+
// 同じ label を lane-label として描画し二重表示になる (actor ラベル二重表示 bug の root cause)。
|
|
2327
|
+
//
|
|
2328
|
+
// 削除は seq-like preset (sequence / solidity = compileSequence 経由) に限定する。 これらは
|
|
2329
|
+
// 1 actor = 1 lane (lane.label === a.name、 lane.id は actor 名の slug) の生成規則が成立し、
|
|
2330
|
+
// parts actor 用 lane を安全に削除できる。 他 preset (flow / topology / class / pie 等) は複数
|
|
2331
|
+
// actor が共有 lane (id = "main" 等) を参照するため、 一致 lane を消すと通常 actor の node が
|
|
2332
|
+
// 削除済 lane を参照する不正 diagram になる (cc-codex MAJOR 指摘)。
|
|
2333
|
+
//
|
|
2334
|
+
// leftover lane の特定は lane.label === a.name を第一に使う。 seq-like preset は非 animate 経路
|
|
2335
|
+
// (cdl preset の slugify) と animate 経路 (dragon の slugify) で lane.id の slug 規則が異なり
|
|
2336
|
+
// (`_`/全角の扱い等)、 aliasSlug (dragon slugify) と lane.id が不一致になる actor 名がある。 lane.label
|
|
2337
|
+
// は両経路とも a.name 生値なので slug 差の影響を受けず確実に一致する。 id === aliasSlug は
|
|
2338
|
+
// label 未設定 preset への fallback (exact match のみ、 prefix は false match risk のため付けない)。
|
|
2339
|
+
if (doc.type === "sequence" || doc.type === "solidity") {
|
|
2340
|
+
target.lanes = target.lanes.filter((l) => {
|
|
2341
|
+
// 明示 lane mapping (a.lane) 先は part の張替え先なので保持する。
|
|
2342
|
+
if (a.lane !== undefined && l.id === a.lane) return true;
|
|
2343
|
+
if (l.label === a.name) return false;
|
|
2344
|
+
if (l.id === aliasSlug) return false;
|
|
2345
|
+
return true;
|
|
2269
2346
|
});
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
// parts actor 用 lane を安全に削除できる。 他 preset (flow / topology / class / pie 等) は複数
|
|
2277
|
-
// actor が共有 lane (id = "main" 等) を参照するため、 一致 lane を消すと通常 actor の node が
|
|
2278
|
-
// 削除済 lane を参照する不正 diagram になる (cc-codex MAJOR 指摘)。
|
|
2279
|
-
//
|
|
2280
|
-
// leftover lane の特定は lane.label === a.name を第一に使う。 seq-like preset は非 animate 経路
|
|
2281
|
-
// (cdl preset の slugify) と animate 経路 (dragon の slugify) で lane.id の slug 規則が異なり
|
|
2282
|
-
// (`_`/全角の扱い等)、 aliasSlug (dragon slugify) と lane.id が不一致になる actor 名がある。 lane.label
|
|
2283
|
-
// は両経路とも a.name 生値なので slug 差の影響を受けず確実に一致する。 id === aliasSlug は
|
|
2284
|
-
// label 未設定 preset への fallback (exact match のみ、 prefix は false match risk のため付けない)。
|
|
2285
|
-
if (doc.type === "sequence" || doc.type === "solidity") {
|
|
2286
|
-
target.lanes = target.lanes.filter((l) => {
|
|
2287
|
-
// 明示 lane mapping (a.lane) 先は part の張替え先なので保持する。
|
|
2288
|
-
if (a.lane !== undefined && l.id === a.lane) return true;
|
|
2289
|
-
if (l.label === a.name) return false;
|
|
2290
|
-
if (l.id === aliasSlug) return false;
|
|
2291
|
-
return true;
|
|
2292
|
-
});
|
|
2293
|
-
}
|
|
2294
|
-
// 削除された node / edge を activate 参照している既存 phase の cleanup (node 削除と同じ判定経路
|
|
2295
|
-
// = 取りこぼすと存在しない id が activate に残り dangling 参照になる、 #873)
|
|
2296
|
-
for (const phase of target.phases) {
|
|
2297
|
-
phase.activate = phase.activate.filter((id) => !relatedToActor(id) && !removedEdgeIds.has(id));
|
|
2298
|
-
}
|
|
2347
|
+
}
|
|
2348
|
+
// 削除された node / edge を activate 参照している既存 phase の cleanup (node 削除と同じ判定経路
|
|
2349
|
+
// = 取りこぼすと存在しない id が activate に残り dangling 参照になる、 #873)
|
|
2350
|
+
for (const phase of target.phases) {
|
|
2351
|
+
phase.activate = phase.activate.filter((id) => !relatedToActor(id) && !removedEdgeIds.has(id));
|
|
2352
|
+
}
|
|
2299
2353
|
}
|
|
2300
2354
|
|
|
2301
2355
|
/**
|
|
@@ -2362,7 +2416,9 @@ function mergePartsFromActors(
|
|
|
2362
2416
|
const part = found;
|
|
2363
2417
|
if (!part) {
|
|
2364
2418
|
if (typeof console !== "undefined" && console.warn) {
|
|
2365
|
-
console.warn(
|
|
2419
|
+
console.warn(
|
|
2420
|
+
`[dragon] parts kind "${partId}" not found in partsCatalog (actor: ${actor.name})`,
|
|
2421
|
+
);
|
|
2366
2422
|
}
|
|
2367
2423
|
continue;
|
|
2368
2424
|
}
|
|
@@ -2450,7 +2506,8 @@ function resolveStateOverride(
|
|
|
2450
2506
|
override: number | string | boolean | undefined,
|
|
2451
2507
|
): { initial: number | string; rejected: boolean } {
|
|
2452
2508
|
if (override === undefined) return { initial: original, rejected: false };
|
|
2453
|
-
if (isColorValue(original) && !isColorValue(override))
|
|
2509
|
+
if (isColorValue(original) && !isColorValue(override))
|
|
2510
|
+
return { initial: original, rejected: true };
|
|
2454
2511
|
return { initial: override as number | string, rejected: false };
|
|
2455
2512
|
}
|
|
2456
2513
|
|
|
@@ -2615,9 +2672,8 @@ function mergePartIntoDiagram(
|
|
|
2615
2672
|
// directive で廃止 (2026-07-21)。 重なりは user の意図位置を優先し、 手動移動で回避する経路。
|
|
2616
2673
|
// 未指定 (座標なし fallback) 時のみ existingMax + gap で右外配置 (通常経路は drop/click で座標を渡す)。
|
|
2617
2674
|
const PARTS_LANE_GAP = 300;
|
|
2618
|
-
const existingLaneMaxX =
|
|
2619
|
-
? Math.max(...target.lanes.map((l) => (l.x ?? 0) + l.width))
|
|
2620
|
-
: 0;
|
|
2675
|
+
const existingLaneMaxX =
|
|
2676
|
+
target.lanes.length > 0 ? Math.max(...target.lanes.map((l) => (l.x ?? 0) + l.width)) : 0;
|
|
2621
2677
|
// parts 全体 resize (I2 forensic): user が SE handle drag で targetW/H 指定 = actor.posW/H。
|
|
2622
2678
|
// scale 基準は part 全体の bbox 幅 (全 lane の最左端〜最右端) にする。 lane[0] 幅だけを基準にすると
|
|
2623
2679
|
// multi-lane part (複数 lane を横に並べた part) で全体幅を過小評価し、 非先頭 lane の node が自 lane
|
|
@@ -2649,9 +2705,10 @@ function mergePartIntoDiagram(
|
|
|
2649
2705
|
// 変換し、 lane.x = mapLaneX(元 lane 左端) にすることで全 lane / 全 node が一貫して drop 座標を中心に
|
|
2650
2706
|
// scale 配置される (cc-codex #879 の mapPartX と同じ発想を lane push まで前倒し、 #880 root fix)。
|
|
2651
2707
|
const partOrigBboxCenterX = partMinLaneX + partsBboxW / 2;
|
|
2652
|
-
const dropCenterX =
|
|
2653
|
-
|
|
2654
|
-
|
|
2708
|
+
const dropCenterX =
|
|
2709
|
+
offsetX !== undefined
|
|
2710
|
+
? offsetX
|
|
2711
|
+
: existingLaneMaxX + PARTS_LANE_GAP + (partsBboxW * laneScaleX) / 2;
|
|
2655
2712
|
const mapLaneX = (x: number): number => (x - partOrigBboxCenterX) * laneScaleX + dropCenterX;
|
|
2656
2713
|
|
|
2657
2714
|
for (const laneOrig of part.lanes) {
|
|
@@ -2688,9 +2745,10 @@ function mergePartIntoDiagram(
|
|
|
2688
2745
|
const shouldForcePos = offsetX !== undefined || offsetY !== undefined;
|
|
2689
2746
|
// target 側の現在 max stack + isolation offset で parts node の stack を shift、
|
|
2690
2747
|
// sequence の rowH 計算と完全分離 (D2 fix、 posX/posY 明示との 2 段防御)。
|
|
2691
|
-
const targetMaxStack =
|
|
2692
|
-
|
|
2693
|
-
|
|
2748
|
+
const targetMaxStack =
|
|
2749
|
+
shouldForcePos && target.nodes.length > 0
|
|
2750
|
+
? Math.max(...target.nodes.map((n) => n.stack ?? 0))
|
|
2751
|
+
: 0;
|
|
2694
2752
|
const stackShiftBase = shouldForcePos ? targetMaxStack + STACK_ISOLATION_OFFSET : 0;
|
|
2695
2753
|
// parts 全体 resize scale (I2 forensic 対応): targetW / targetH 指定時、 parts の元 total size
|
|
2696
2754
|
// に対する比率 = scale 係数、 全 sub-node の w / h + cx / cy 相対位置に scale 反映。
|
|
@@ -2758,8 +2816,10 @@ function mergePartIntoDiagram(
|
|
|
2758
2816
|
// (cc-codex #879 Round 2/3 MAJOR + #880)。 mapLaneX は part bbox 中心 → drop 座標の scale 変換で、
|
|
2759
2817
|
// lane / node / 明示 posX / auto-layout の全経路がこの 1 式を共有するため、 lane.x != 0 でも
|
|
2760
2818
|
// multi-lane でも node 中心と自 lane 中心が一致する。
|
|
2761
|
-
let nodePosX: number | undefined =
|
|
2762
|
-
|
|
2819
|
+
let nodePosX: number | undefined =
|
|
2820
|
+
nodeOrig.posX !== undefined ? mapLaneX(nodeOrig.posX) : undefined;
|
|
2821
|
+
let nodePosY: number | undefined =
|
|
2822
|
+
nodeOrig.posY !== undefined ? nodeOrig.posY + (offsetY ?? 0) : undefined;
|
|
2763
2823
|
if (shouldForcePos && nodePosX === undefined) {
|
|
2764
2824
|
// posX を持たない node は所属 lane の中央 (auto layout の cx 相当) を同じ mapLaneX で変換する。
|
|
2765
2825
|
const geom = partLaneGeom.get(nodeOrig.lane) ?? { x: 0, w: 320 };
|
|
@@ -2775,12 +2835,10 @@ function mergePartIntoDiagram(
|
|
|
2775
2835
|
// 座標が非有限になって図が描けない (実測 = 箱の中心が NaN になった)
|
|
2776
2836
|
const rawNodeW = nodeOrig.w !== undefined ? positiveOr(nodeOrig.w, 200) : undefined;
|
|
2777
2837
|
const rawNodeH = nodeOrig.h !== undefined ? positiveOr(nodeOrig.h, 200) : undefined;
|
|
2778
|
-
const nodeW =
|
|
2779
|
-
? rawNodeW * scaleX
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
? rawNodeH * scaleY
|
|
2783
|
-
: rawNodeH;
|
|
2838
|
+
const nodeW =
|
|
2839
|
+
rawNodeW !== undefined && (scaleX !== 1 || scaleY !== 1) ? rawNodeW * scaleX : rawNodeW;
|
|
2840
|
+
const nodeH =
|
|
2841
|
+
rawNodeH !== undefined && (scaleX !== 1 || scaleY !== 1) ? rawNodeH * scaleY : rawNodeH;
|
|
2784
2842
|
target.nodes.push({
|
|
2785
2843
|
...nodeOrig,
|
|
2786
2844
|
id: prefix(nodeOrig.id),
|
|
@@ -2800,7 +2858,10 @@ function mergePartIntoDiagram(
|
|
|
2800
2858
|
|
|
2801
2859
|
// state merge = id prefix + initial override
|
|
2802
2860
|
for (const stateOrig of part.states) {
|
|
2803
|
-
const { initial, rejected } = resolveStateOverride(
|
|
2861
|
+
const { initial, rejected } = resolveStateOverride(
|
|
2862
|
+
stateOrig.initial,
|
|
2863
|
+
stateOverride[stateOrig.id],
|
|
2864
|
+
);
|
|
2804
2865
|
if (rejected) {
|
|
2805
2866
|
onNotice?.({
|
|
2806
2867
|
kind: "state-override-rejected",
|
|
@@ -2844,7 +2905,10 @@ function mergePartIntoDiagram(
|
|
|
2844
2905
|
if (part.readouts && part.readouts.length > 0) {
|
|
2845
2906
|
if (!target.readouts) target.readouts = [];
|
|
2846
2907
|
for (const readoutOrig of part.readouts) {
|
|
2847
|
-
const rewritten = deepRewriteStrings(
|
|
2908
|
+
const rewritten = deepRewriteStrings(
|
|
2909
|
+
readoutOrig as unknown,
|
|
2910
|
+
rewriteTemplate,
|
|
2911
|
+
) as CdlDiagram["readouts"] extends readonly (infer R)[] ? R : never;
|
|
2848
2912
|
// id は shape 全 walk で rewrite されないので個別に prefix
|
|
2849
2913
|
target.readouts.push({
|
|
2850
2914
|
...(rewritten as { id: string }),
|
|
@@ -2882,8 +2946,14 @@ function mergePartIntoDiagram(
|
|
|
2882
2946
|
const partPhase = part.phases[i]!;
|
|
2883
2947
|
targetPhase.duration = Math.max(targetPhase.duration, partPhase.duration);
|
|
2884
2948
|
targetPhase.activate = [...targetPhase.activate, ...partPhase.activate.map(prefix)];
|
|
2885
|
-
targetPhase.tweens = [
|
|
2886
|
-
|
|
2949
|
+
targetPhase.tweens = [
|
|
2950
|
+
...targetPhase.tweens,
|
|
2951
|
+
...partPhase.tweens.map((t) => ({ ...t, stateId: valuePrefix(t.stateId) })),
|
|
2952
|
+
];
|
|
2953
|
+
targetPhase.sets = [
|
|
2954
|
+
...targetPhase.sets,
|
|
2955
|
+
...partPhase.sets.map((s) => ({ ...s, stateId: valuePrefix(s.stateId) })),
|
|
2956
|
+
];
|
|
2887
2957
|
}
|
|
2888
2958
|
// parts phase 余剰は append (target より parts が長い場合)
|
|
2889
2959
|
for (let i = commonLen; i < partsLen; i++) {
|
|
@@ -2993,11 +3063,11 @@ function 矢印へ書き写す(target: CdlEdge, s: DslStep, doc: DslDocument): v
|
|
|
2993
3063
|
target.cardinality = s.cardinality;
|
|
2994
3064
|
// ER preset の場合 label に "(1:N)" 形式で併記 (既に含まれていればスキップ)
|
|
2995
3065
|
if (doc.type === "er" && !target.label.includes(s.cardinality)) {
|
|
2996
|
-
target.label = target.label
|
|
2997
|
-
? `${target.label} (${s.cardinality})`
|
|
2998
|
-
: `(${s.cardinality})`;
|
|
3066
|
+
target.label = target.label ? `${target.label} (${s.cardinality})` : `(${s.cardinality})`;
|
|
2999
3067
|
}
|
|
3000
3068
|
}
|
|
3069
|
+
// 矢印がどの辺から出るか (#1385)。 書かなければ描画側が自動で選ぶ
|
|
3070
|
+
if (s.side !== undefined) target.side = s.side;
|
|
3001
3071
|
if (s.labelOffsetX !== undefined) target.labelOffsetX = s.labelOffsetX;
|
|
3002
3072
|
if (s.labelOffsetY !== undefined) target.labelOffsetY = s.labelOffsetY;
|
|
3003
3073
|
if (s.overlay !== undefined) target.overlay = s.overlay;
|
|
@@ -3090,7 +3160,8 @@ function 式に書く数(n: number): string {
|
|
|
3090
3160
|
const decimalAt = whole.length + Number(exponentText);
|
|
3091
3161
|
let expanded: string;
|
|
3092
3162
|
if (decimalAt <= 0) expanded = `0.${"0".repeat(-decimalAt)}${digits}`;
|
|
3093
|
-
else if (decimalAt >= digits.length)
|
|
3163
|
+
else if (decimalAt >= digits.length)
|
|
3164
|
+
expanded = `${digits}${"0".repeat(decimalAt - digits.length)}`;
|
|
3094
3165
|
else expanded = `${digits.slice(0, decimalAt)}.${digits.slice(decimalAt)}`;
|
|
3095
3166
|
return negative ? `-${expanded}` : expanded;
|
|
3096
3167
|
}
|
|
@@ -3109,13 +3180,20 @@ type 動く区間 = { 段: number; start: number; end: number; dur: number; from
|
|
|
3109
3180
|
function 境目を通る時刻(区間: 動く区間, op: string, 境目: number): number | null {
|
|
3110
3181
|
const 満たす = (x: number): boolean => {
|
|
3111
3182
|
switch (op) {
|
|
3112
|
-
case ">=":
|
|
3113
|
-
|
|
3114
|
-
case "
|
|
3115
|
-
|
|
3116
|
-
case "
|
|
3117
|
-
|
|
3118
|
-
|
|
3183
|
+
case ">=":
|
|
3184
|
+
return x >= 境目;
|
|
3185
|
+
case ">":
|
|
3186
|
+
return x > 境目;
|
|
3187
|
+
case "<=":
|
|
3188
|
+
return x <= 境目;
|
|
3189
|
+
case "<":
|
|
3190
|
+
return x < 境目;
|
|
3191
|
+
case "==":
|
|
3192
|
+
return x === 境目;
|
|
3193
|
+
case "!=":
|
|
3194
|
+
return x !== 境目;
|
|
3195
|
+
default:
|
|
3196
|
+
return false;
|
|
3119
3197
|
}
|
|
3120
3198
|
};
|
|
3121
3199
|
if (満たす(区間.from)) return 区間.start;
|
|
@@ -3196,7 +3274,13 @@ function foldValueTriggers(
|
|
|
3196
3274
|
const 解決中 = new Set<string>();
|
|
3197
3275
|
|
|
3198
3276
|
const 知らせる = (v: DslValue, message: string, hint: string): void => {
|
|
3199
|
-
onNotice?.({
|
|
3277
|
+
onNotice?.({
|
|
3278
|
+
kind: "value-trigger-unresolved",
|
|
3279
|
+
actor: v.name,
|
|
3280
|
+
line: v.pos?.line ?? 0,
|
|
3281
|
+
message,
|
|
3282
|
+
hint,
|
|
3283
|
+
});
|
|
3200
3284
|
};
|
|
3201
3285
|
|
|
3202
3286
|
const 解く = (name: string): 動く区間 | null => {
|
|
@@ -3207,7 +3291,11 @@ function foldValueTriggers(
|
|
|
3207
3291
|
if (!v || !v.trigger) return null;
|
|
3208
3292
|
if (解決中.has(name)) {
|
|
3209
3293
|
解けない.add(name);
|
|
3210
|
-
知らせる(
|
|
3294
|
+
知らせる(
|
|
3295
|
+
v,
|
|
3296
|
+
`"${name}" のきっかけが一周しています`,
|
|
3297
|
+
"どれか 1 つを `trigger: step ...` に変える",
|
|
3298
|
+
);
|
|
3211
3299
|
return null;
|
|
3212
3300
|
}
|
|
3213
3301
|
解決中.add(name);
|
|
@@ -3231,7 +3319,11 @@ function foldValueTriggers(
|
|
|
3231
3319
|
if (trigger.kind === "step") {
|
|
3232
3320
|
const idx = 段の番号.get(trigger.step);
|
|
3233
3321
|
if (idx === undefined) {
|
|
3234
|
-
知らせる(
|
|
3322
|
+
知らせる(
|
|
3323
|
+
v,
|
|
3324
|
+
`"${trigger.step}" という段がありません`,
|
|
3325
|
+
"`animation:` にその名前の段を書くか、 段の名前に合わせる",
|
|
3326
|
+
);
|
|
3235
3327
|
return null;
|
|
3236
3328
|
}
|
|
3237
3329
|
段 = idx;
|
|
@@ -3287,7 +3379,8 @@ function foldValueTriggers(
|
|
|
3287
3379
|
段ごとの時計.set(段, 名前);
|
|
3288
3380
|
diagram.states.push({ id: 名前, initial: 0 });
|
|
3289
3381
|
const 段の中身 = diagram.phases[段];
|
|
3290
|
-
if (段の中身)
|
|
3382
|
+
if (段の中身)
|
|
3383
|
+
段の中身.tweens = [...段の中身.tweens, { stateId: 名前, from: 0, to: 段の中身.duration }];
|
|
3291
3384
|
return 名前;
|
|
3292
3385
|
};
|
|
3293
3386
|
|
|
@@ -3555,9 +3648,15 @@ const VALUE_NOTICE_HINT: Readonly<Record<string, string>> = {
|
|
|
3555
3648
|
* **今は記法からも到達する** (一覧へ戻す作業が要らなかったのはこのため)。
|
|
3556
3649
|
*/
|
|
3557
3650
|
const SINGLE_BOX_KINDS: ReadonlySet<string> = new Set([
|
|
3558
|
-
"chart-pie",
|
|
3559
|
-
"
|
|
3560
|
-
"
|
|
3651
|
+
"chart-pie",
|
|
3652
|
+
"chart-line",
|
|
3653
|
+
"chart-bar",
|
|
3654
|
+
"gantt-timeline",
|
|
3655
|
+
"mind-map",
|
|
3656
|
+
"funnel-stages",
|
|
3657
|
+
"quadrant-matrix",
|
|
3658
|
+
"tree-hierarchy",
|
|
3659
|
+
"journey-map",
|
|
3561
3660
|
]);
|
|
3562
3661
|
|
|
3563
3662
|
/** 記法の種別を描画の種別に直す。 描けない種別のままなら `undefined`。 */
|
|
@@ -3666,14 +3765,36 @@ const LABEL_MIN_H: Readonly<Record<string, number>> = {
|
|
|
3666
3765
|
*/
|
|
3667
3766
|
const LABEL_NEVER_FITS: ReadonlySet<string> = new Set([
|
|
3668
3767
|
// 左右にはみ出す 24 種。 横幅は高さで変わらないため直らない
|
|
3669
|
-
"shape-api-gateway",
|
|
3670
|
-
"shape-
|
|
3671
|
-
"shape-
|
|
3672
|
-
"shape-
|
|
3673
|
-
"shape-
|
|
3674
|
-
"shape-
|
|
3768
|
+
"shape-api-gateway",
|
|
3769
|
+
"shape-atm",
|
|
3770
|
+
"shape-auditor",
|
|
3771
|
+
"shape-bank",
|
|
3772
|
+
"shape-bitcoin-chain",
|
|
3773
|
+
"shape-blockchain",
|
|
3774
|
+
"shape-blockchain-block",
|
|
3775
|
+
"shape-blockchain-node",
|
|
3776
|
+
"shape-brokerage",
|
|
3777
|
+
"shape-code-block",
|
|
3778
|
+
"shape-customer-service",
|
|
3779
|
+
"shape-ethereum-chain",
|
|
3780
|
+
"shape-hexagon",
|
|
3781
|
+
"shape-kanban-card",
|
|
3782
|
+
"shape-lawyer",
|
|
3783
|
+
"shape-network-node",
|
|
3784
|
+
"shape-nft",
|
|
3785
|
+
"shape-notary",
|
|
3786
|
+
"shape-regulator",
|
|
3787
|
+
"shape-satellite",
|
|
3788
|
+
"shape-smart-contract",
|
|
3789
|
+
"shape-terminal",
|
|
3790
|
+
"shape-trader",
|
|
3791
|
+
"shape-trust-bank",
|
|
3675
3792
|
// 下のはみ出しが高さに依らない 6 種
|
|
3676
|
-
"shape-cylinder",
|
|
3793
|
+
"shape-cylinder",
|
|
3794
|
+
"shape-diamond",
|
|
3795
|
+
"shape-file",
|
|
3796
|
+
"shape-folder",
|
|
3797
|
+
"shape-mobile-device",
|
|
3677
3798
|
"shape-stack",
|
|
3678
3799
|
// 上へ出る絵が名札の大きさでは読めない。 `#1067` では「上は何ともぶつからない」 として残したが、
|
|
3679
3800
|
// 実際には絵が小さく潰れて名前と重なり、 横に並べた時も 1 本だけ頭が浮く (user 実機確認)
|
|
@@ -3979,6 +4100,7 @@ function compileGantt(doc: DslDocument): CdlDiagram {
|
|
|
3979
4100
|
const 目盛りなし: string[] = [];
|
|
3980
4101
|
const タスク: {
|
|
3981
4102
|
name: string;
|
|
4103
|
+
title: string;
|
|
3982
4104
|
label: string;
|
|
3983
4105
|
tone?: DslDocument["actors"][number]["tone"];
|
|
3984
4106
|
owner?: string;
|
|
@@ -3994,6 +4116,7 @@ function compileGantt(doc: DslDocument): CdlDiagram {
|
|
|
3994
4116
|
// 色は帯にそのまま渡す。 箱が 1 つになっても、 書いた色が消えないようにする
|
|
3995
4117
|
タスク.push({
|
|
3996
4118
|
name: a.name,
|
|
4119
|
+
title: 箱の題(a),
|
|
3997
4120
|
label,
|
|
3998
4121
|
...(a.tone !== undefined ? { tone: a.tone } : {}),
|
|
3999
4122
|
...(a.owner !== undefined ? { owner: a.owner } : {}),
|
|
@@ -4020,7 +4143,12 @@ function compileGantt(doc: DslDocument): CdlDiagram {
|
|
|
4020
4143
|
}
|
|
4021
4144
|
依存元.set(s.to, s.from);
|
|
4022
4145
|
// 帯の依存は「どちらが先か」 だけを持つ。 矢印に書いた文字や色は描けないので伝える
|
|
4023
|
-
if (
|
|
4146
|
+
if (
|
|
4147
|
+
(s.label ?? "") !== "" ||
|
|
4148
|
+
(s.sub ?? "") !== "" ||
|
|
4149
|
+
s.tone !== undefined ||
|
|
4150
|
+
s.style !== undefined
|
|
4151
|
+
) {
|
|
4024
4152
|
装飾つき.push(`${s.from} -> ${s.to}`);
|
|
4025
4153
|
}
|
|
4026
4154
|
}
|
|
@@ -4059,7 +4187,7 @@ function compileGantt(doc: DslDocument): CdlDiagram {
|
|
|
4059
4187
|
const 終わり = 終わる位置(t.end, idx, 目盛り, t.name, 逆向きを伝える, doc);
|
|
4060
4188
|
return {
|
|
4061
4189
|
id: slugify(t.name) || t.name,
|
|
4062
|
-
title: t.
|
|
4190
|
+
title: t.title,
|
|
4063
4191
|
startIdx: idx,
|
|
4064
4192
|
endIdx: 終わり.idx,
|
|
4065
4193
|
startLabel: t.label,
|
|
@@ -4110,7 +4238,7 @@ function compileClass(doc: DslDocument): CdlDiagram {
|
|
|
4110
4238
|
lane: laneId,
|
|
4111
4239
|
stack: 0,
|
|
4112
4240
|
kind: "storage",
|
|
4113
|
-
title: a
|
|
4241
|
+
title: 箱の題(a),
|
|
4114
4242
|
w: CLASS_W,
|
|
4115
4243
|
});
|
|
4116
4244
|
});
|
|
@@ -4121,6 +4249,7 @@ function compileClass(doc: DslDocument): CdlDiagram {
|
|
|
4121
4249
|
b.edge(fromId, toId, {
|
|
4122
4250
|
label: s.label,
|
|
4123
4251
|
...(s.sub ? { sub: s.sub } : {}),
|
|
4252
|
+
...(s.side ? { side: s.side } : {}),
|
|
4124
4253
|
...(s.tone ? { tone: s.tone } : {}),
|
|
4125
4254
|
...(s.style ? { style: s.style } : {}),
|
|
4126
4255
|
});
|
|
@@ -4185,7 +4314,6 @@ function parseChartValue(raw: string | undefined): number | string | null {
|
|
|
4185
4314
|
return parseBoundValue(raw);
|
|
4186
4315
|
}
|
|
4187
4316
|
|
|
4188
|
-
|
|
4189
4317
|
/** 状態を読む欄が指している名前 (`{v.sum}` なら `v`)。 欄でなければ null */
|
|
4190
4318
|
function 参照する名前(value: number | string | null): string | null {
|
|
4191
4319
|
if (typeof value !== "string") return null;
|
|
@@ -4251,7 +4379,7 @@ function compileValueChart(
|
|
|
4251
4379
|
continue;
|
|
4252
4380
|
}
|
|
4253
4381
|
// 色はそのまま渡す。 箱が 1 つになっても、 書いた色が消えないようにする
|
|
4254
|
-
data.push({ label: a
|
|
4382
|
+
data.push({ label: 箱の題(a), value, ...(a.tone !== undefined ? { tone: a.tone } : {}) });
|
|
4255
4383
|
}
|
|
4256
4384
|
// 案内の言葉は型ごとに変える。 共通化した時に `pie` の「割合 / 円 / 45%」 が「値 / 図 / 45」 に
|
|
4257
4385
|
// 薄まり、 既存の案内が後退した (review 指摘)。 何を書けばよいかは型ごとに違う
|
|
@@ -4313,7 +4441,6 @@ function compileValueChart(
|
|
|
4313
4441
|
return b.build();
|
|
4314
4442
|
}
|
|
4315
4443
|
|
|
4316
|
-
|
|
4317
4444
|
/**
|
|
4318
4445
|
* 図表 4 種の組立て (#1154 段 2 / 段 3)。
|
|
4319
4446
|
*
|
|
@@ -4415,7 +4542,11 @@ const 区画 = new Map<string, "topLeft" | "topRight" | "bottomLeft" | "bottomRi
|
|
|
4415
4542
|
*/
|
|
4416
4543
|
function 図表の欄から参照できる名前(
|
|
4417
4544
|
doc: DslDocument,
|
|
4418
|
-
欄: {
|
|
4545
|
+
欄: {
|
|
4546
|
+
読めるか: (v: number | string) => boolean;
|
|
4547
|
+
補間で壊れるか: boolean;
|
|
4548
|
+
自動の値を許すか: boolean;
|
|
4549
|
+
},
|
|
4419
4550
|
): Set<string> {
|
|
4420
4551
|
// 同じ名前を 2 回宣言した時は後ろが効く。 描画側が後の宣言を有効値として扱うため、
|
|
4421
4552
|
// 前の宣言で判定すると「読めると判定したのに読めない値が入る」 状態になる (実測)
|
|
@@ -4536,7 +4667,7 @@ function compileFunnel(doc: DslDocument, onNotice?: (n: CompileNotice) => void):
|
|
|
4536
4667
|
未宣言.push(a.name);
|
|
4537
4668
|
continue;
|
|
4538
4669
|
}
|
|
4539
|
-
data.push({ id: slugify(a.name), title: a
|
|
4670
|
+
data.push({ id: slugify(a.name), title: 箱の題(a), count: v });
|
|
4540
4671
|
}
|
|
4541
4672
|
if (未宣言.length > 0) {
|
|
4542
4673
|
const m3 = `type: funnel で数にならない値を参照した項目があります (段に載せません): ${未宣言.join(", ")}。 \`states:\` にその名前を数で書いてください`;
|
|
@@ -4551,11 +4682,23 @@ function compileFunnel(doc: DslDocument, onNotice?: (n: CompileNotice) => void):
|
|
|
4551
4682
|
// 矢印は描けない。 書かれていたら伝える (黙って捨てると「書いたのに効かない」 が残る)
|
|
4552
4683
|
if (doc.flow.length > 0) {
|
|
4553
4684
|
const m2 = `type: funnel では矢印を描けません (${doc.flow.length} 本を無視しました)。 関係を描くなら type: flow を使ってください`;
|
|
4554
|
-
onNotice?.({
|
|
4685
|
+
onNotice?.({
|
|
4686
|
+
kind: "chart-edge-dropped",
|
|
4687
|
+
actor: doc.flow[0]?.from ?? "",
|
|
4688
|
+
line: doc.flow[0]?.pos?.line ?? 0,
|
|
4689
|
+
message: m2,
|
|
4690
|
+
});
|
|
4555
4691
|
if (typeof console !== "undefined" && console.warn) console.warn(`[dragon] ${m2}`);
|
|
4556
4692
|
}
|
|
4557
4693
|
b.node(`${slugify(doc.title) || "funnel"}-chart`, {
|
|
4558
|
-
lane: "chart",
|
|
4694
|
+
lane: "chart",
|
|
4695
|
+
stack: 0,
|
|
4696
|
+
kind: "funnel-stages",
|
|
4697
|
+
title: doc.title,
|
|
4698
|
+
...図の小見出し(doc),
|
|
4699
|
+
w: W,
|
|
4700
|
+
h: H,
|
|
4701
|
+
funnelData: data,
|
|
4559
4702
|
});
|
|
4560
4703
|
return b.build();
|
|
4561
4704
|
}
|
|
@@ -4582,12 +4725,20 @@ function 矢印から親を決める(
|
|
|
4582
4725
|
const 子 = slugify(f.to);
|
|
4583
4726
|
const 親名 = slugify(f.from);
|
|
4584
4727
|
if (!名前.has(親名)) {
|
|
4585
|
-
伝える(
|
|
4728
|
+
伝える(
|
|
4729
|
+
f.from,
|
|
4730
|
+
`type: ${図種} で書いていない名前を親にしています: ${f.from} -> ${f.to}`,
|
|
4731
|
+
f.pos?.line ?? 0,
|
|
4732
|
+
);
|
|
4586
4733
|
continue;
|
|
4587
4734
|
}
|
|
4588
4735
|
// 子の側も見る。 書いていない名前への矢印は、 黙って捨てると図から関係が消える
|
|
4589
4736
|
if (!名前.has(子)) {
|
|
4590
|
-
伝える(
|
|
4737
|
+
伝える(
|
|
4738
|
+
f.to,
|
|
4739
|
+
`type: ${図種} で書いていない名前を子にしています: ${f.from} -> ${f.to}`,
|
|
4740
|
+
f.pos?.line ?? 0,
|
|
4741
|
+
);
|
|
4591
4742
|
continue;
|
|
4592
4743
|
}
|
|
4593
4744
|
if (子 === 親名) {
|
|
@@ -4596,7 +4747,11 @@ function 矢印から親を決める(
|
|
|
4596
4747
|
}
|
|
4597
4748
|
const 既存 = 親.get(子);
|
|
4598
4749
|
if (既存 !== undefined && 既存 !== 親名) {
|
|
4599
|
-
伝える(
|
|
4750
|
+
伝える(
|
|
4751
|
+
f.to,
|
|
4752
|
+
`type: ${図種} で ${f.to} に親が 2 つあります (後の ${f.from} は使いません)`,
|
|
4753
|
+
f.pos?.line ?? 0,
|
|
4754
|
+
);
|
|
4600
4755
|
continue;
|
|
4601
4756
|
}
|
|
4602
4757
|
親.set(子, 親名);
|
|
@@ -4638,7 +4793,10 @@ function compileTree(doc: DslDocument, onNotice?: (n: CompileNotice) => void): C
|
|
|
4638
4793
|
};
|
|
4639
4794
|
for (const [k, 群] of slug別) {
|
|
4640
4795
|
if (群.length > 1) {
|
|
4641
|
-
伝える(
|
|
4796
|
+
伝える(
|
|
4797
|
+
群[0]!,
|
|
4798
|
+
`type: tree で ${群.join(" / ")} が同じ id (${k}) になります。 名前を変えてください`,
|
|
4799
|
+
);
|
|
4642
4800
|
}
|
|
4643
4801
|
}
|
|
4644
4802
|
const 親 = 矢印から親を決める(doc, "tree", 名前, 伝える);
|
|
@@ -4650,7 +4808,14 @@ function compileTree(doc: DslDocument, onNotice?: (n: CompileNotice) => void): C
|
|
|
4650
4808
|
return { id, ...放射に出す文字(a), ...(p3 !== undefined ? { parent: p3 } : {}) };
|
|
4651
4809
|
});
|
|
4652
4810
|
b.node(`${slugify(doc.title) || "tree"}-chart`, {
|
|
4653
|
-
lane: "chart",
|
|
4811
|
+
lane: "chart",
|
|
4812
|
+
stack: 0,
|
|
4813
|
+
kind: "tree-hierarchy",
|
|
4814
|
+
title: doc.title,
|
|
4815
|
+
...図の小見出し(doc),
|
|
4816
|
+
w: W,
|
|
4817
|
+
h: H,
|
|
4818
|
+
treeData: data,
|
|
4654
4819
|
});
|
|
4655
4820
|
return b.build();
|
|
4656
4821
|
}
|
|
@@ -4677,19 +4842,25 @@ function 道筋の欄(a: DslActor): { touchpoint?: string; opportunity?: string
|
|
|
4677
4842
|
* `type: mind` では伝えない = `compileMind` が描けない欄をまとめて 1 件で伝えており、
|
|
4678
4843
|
* そこに 2 つとも入っている (`放射で描けない欄`)。 二重に伝えない (#1246 と同じ扱い)。
|
|
4679
4844
|
*/
|
|
4680
|
-
function reportChartFieldsNotHonored(
|
|
4845
|
+
function reportChartFieldsNotHonored(
|
|
4846
|
+
doc: DslDocument,
|
|
4847
|
+
onNotice?: (n: CompileNotice) => void,
|
|
4848
|
+
): void {
|
|
4681
4849
|
if (!onNotice) return;
|
|
4682
4850
|
if (doc.type === "mind") return;
|
|
4683
4851
|
for (const a of doc.actors) {
|
|
4684
4852
|
if (a.partId !== undefined) continue;
|
|
4685
|
-
const 道筋 =
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4853
|
+
const 道筋 =
|
|
4854
|
+
doc.type === "journey"
|
|
4855
|
+
? []
|
|
4856
|
+
: [
|
|
4857
|
+
...(a.touchpoint !== undefined ? ["touchpoint"] : []),
|
|
4858
|
+
...(a.opportunity !== undefined ? ["opportunity"] : []),
|
|
4859
|
+
];
|
|
4860
|
+
const 工程 =
|
|
4861
|
+
doc.type === "gantt"
|
|
4862
|
+
? []
|
|
4863
|
+
: [...(a.owner !== undefined ? ["owner"] : []), ...(a.end !== undefined ? ["end"] : [])];
|
|
4693
4864
|
if (道筋.length > 0) {
|
|
4694
4865
|
onNotice({
|
|
4695
4866
|
kind: "chart-value-unreadable",
|
|
@@ -4727,7 +4898,7 @@ function compileJourney(doc: DslDocument, onNotice?: (n: CompileNotice) => void)
|
|
|
4727
4898
|
読めない.push(a.name);
|
|
4728
4899
|
continue;
|
|
4729
4900
|
}
|
|
4730
|
-
data.push({ id: slugify(a.name), title: a
|
|
4901
|
+
data.push({ id: slugify(a.name), title: 箱の題(a), emotion: 語 as never, ...道筋の欄(a) });
|
|
4731
4902
|
continue;
|
|
4732
4903
|
}
|
|
4733
4904
|
const e = 気持ち.get(語);
|
|
@@ -4735,7 +4906,7 @@ function compileJourney(doc: DslDocument, onNotice?: (n: CompileNotice) => void)
|
|
|
4735
4906
|
読めない.push(a.name);
|
|
4736
4907
|
continue;
|
|
4737
4908
|
}
|
|
4738
|
-
data.push({ id: slugify(a.name), title: a
|
|
4909
|
+
data.push({ id: slugify(a.name), title: 箱の題(a), emotion: e, ...道筋の欄(a) });
|
|
4739
4910
|
}
|
|
4740
4911
|
if (読めない.length > 0) {
|
|
4741
4912
|
const m = `type: journey で気持ちを読めない項目があります (道筋に載せません): ${読めない.join(", ")}。 \`- 登録: "不満"\` の形で、 ${[...気持ち.keys()].join(" / ")} のどれかを書いてください`;
|
|
@@ -4745,11 +4916,23 @@ function compileJourney(doc: DslDocument, onNotice?: (n: CompileNotice) => void)
|
|
|
4745
4916
|
// 矢印は描けない。 書かれていたら伝える (黙って捨てると「書いたのに効かない」 が残る)
|
|
4746
4917
|
if (doc.flow.length > 0) {
|
|
4747
4918
|
const m2 = `type: journey では矢印を描けません (${doc.flow.length} 本を無視しました)。 関係を描くなら type: flow を使ってください`;
|
|
4748
|
-
onNotice?.({
|
|
4919
|
+
onNotice?.({
|
|
4920
|
+
kind: "chart-edge-dropped",
|
|
4921
|
+
actor: doc.flow[0]?.from ?? "",
|
|
4922
|
+
line: doc.flow[0]?.pos?.line ?? 0,
|
|
4923
|
+
message: m2,
|
|
4924
|
+
});
|
|
4749
4925
|
if (typeof console !== "undefined" && console.warn) console.warn(`[dragon] ${m2}`);
|
|
4750
4926
|
}
|
|
4751
4927
|
b.node(`${slugify(doc.title) || "journey"}-chart`, {
|
|
4752
|
-
lane: "chart",
|
|
4928
|
+
lane: "chart",
|
|
4929
|
+
stack: 0,
|
|
4930
|
+
kind: "journey-map",
|
|
4931
|
+
title: doc.title,
|
|
4932
|
+
...図の小見出し(doc),
|
|
4933
|
+
w: W,
|
|
4934
|
+
h: H,
|
|
4935
|
+
journeyData: data,
|
|
4753
4936
|
});
|
|
4754
4937
|
return b.build();
|
|
4755
4938
|
}
|
|
@@ -4828,7 +5011,7 @@ function compileQuadrant(doc: DslDocument, onNotice?: (n: CompileNotice) => void
|
|
|
4828
5011
|
読めない.push(a.name);
|
|
4829
5012
|
continue;
|
|
4830
5013
|
}
|
|
4831
|
-
items.push({ id: slugify(a.name), title: a
|
|
5014
|
+
items.push({ id: slugify(a.name), title: 箱の題(a), quadrant: 語 as never });
|
|
4832
5015
|
continue;
|
|
4833
5016
|
}
|
|
4834
5017
|
const q = 区画.get(語);
|
|
@@ -4836,7 +5019,7 @@ function compileQuadrant(doc: DslDocument, onNotice?: (n: CompileNotice) => void
|
|
|
4836
5019
|
読めない.push(a.name);
|
|
4837
5020
|
continue;
|
|
4838
5021
|
}
|
|
4839
|
-
items.push({ id: slugify(a.name), title: a
|
|
5022
|
+
items.push({ id: slugify(a.name), title: 箱の題(a), quadrant: q });
|
|
4840
5023
|
}
|
|
4841
5024
|
if (読めない.length > 0) {
|
|
4842
5025
|
const m = `type: quadrant で区画を読めない項目があります (図に載せません): ${読めない.join(", ")}。 \`- 重複削除: "左上"\` の形で、 ${[...区画.keys()].join(" / ")} のどれかを書いてください`;
|
|
@@ -4846,11 +5029,22 @@ function compileQuadrant(doc: DslDocument, onNotice?: (n: CompileNotice) => void
|
|
|
4846
5029
|
// 矢印は描けない。 書かれていたら伝える (黙って捨てると「書いたのに効かない」 が残る)
|
|
4847
5030
|
if (doc.flow.length > 0) {
|
|
4848
5031
|
const m2 = `type: quadrant では矢印を描けません (${doc.flow.length} 本を無視しました)。 関係を描くなら type: flow を使ってください`;
|
|
4849
|
-
onNotice?.({
|
|
5032
|
+
onNotice?.({
|
|
5033
|
+
kind: "chart-edge-dropped",
|
|
5034
|
+
actor: doc.flow[0]?.from ?? "",
|
|
5035
|
+
line: doc.flow[0]?.pos?.line ?? 0,
|
|
5036
|
+
message: m2,
|
|
5037
|
+
});
|
|
4850
5038
|
if (typeof console !== "undefined" && console.warn) console.warn(`[dragon] ${m2}`);
|
|
4851
5039
|
}
|
|
4852
5040
|
b.node(`${slugify(doc.title) || "quadrant"}-chart`, {
|
|
4853
|
-
lane: "chart",
|
|
5041
|
+
lane: "chart",
|
|
5042
|
+
stack: 0,
|
|
5043
|
+
kind: "quadrant-matrix",
|
|
5044
|
+
title: doc.title,
|
|
5045
|
+
...図の小見出し(doc),
|
|
5046
|
+
w: W,
|
|
5047
|
+
h: H,
|
|
4854
5048
|
quadrantData: { ...軸と区画の名前(doc), items },
|
|
4855
5049
|
});
|
|
4856
5050
|
return b.build();
|
|
@@ -4879,7 +5073,10 @@ function 段を読み取る(subtitle: string | undefined): { 段: number; 説明
|
|
|
4879
5073
|
const m = 元.match(/^L([123])(?![0-9A-Za-z])/i);
|
|
4880
5074
|
if (m === null) return { 段: 1, 説明: subtitle };
|
|
4881
5075
|
// 目印と、 その直後の区切り (`:` / 全角コロン / 空白) を落とす
|
|
4882
|
-
const 残り =
|
|
5076
|
+
const 残り = 元
|
|
5077
|
+
.slice(m[0].length)
|
|
5078
|
+
.replace(/^[::\s]+/, "")
|
|
5079
|
+
.trim();
|
|
4883
5080
|
return { 段: Number(m[1]), 説明: 残り === "" ? undefined : 残り };
|
|
4884
5081
|
}
|
|
4885
5082
|
|
|
@@ -4943,6 +5140,7 @@ function compileC4(doc: DslDocument): CdlDiagram {
|
|
|
4943
5140
|
b.edge(fromId, toId, {
|
|
4944
5141
|
label: s.label,
|
|
4945
5142
|
...(s.sub ? { sub: s.sub } : {}),
|
|
5143
|
+
...(s.side ? { side: s.side } : {}),
|
|
4946
5144
|
...(s.tone ? { tone: s.tone } : {}),
|
|
4947
5145
|
...(s.style ? { style: s.style } : {}),
|
|
4948
5146
|
});
|
|
@@ -4998,6 +5196,8 @@ function compileC4(doc: DslDocument): CdlDiagram {
|
|
|
4998
5196
|
type 放射で描ける欄 =
|
|
4999
5197
|
/** 中心の名前 / 枝の名前になる */
|
|
5000
5198
|
| "name"
|
|
5199
|
+
/** 名前と分けて中心 / 枝に出す題。 書かなければ名前を出す */
|
|
5200
|
+
| "title"
|
|
5001
5201
|
/**
|
|
5002
5202
|
* 名前とは分けて補足に出す 2 欄。
|
|
5003
5203
|
*
|
|
@@ -5018,6 +5218,16 @@ type 放射で描ける欄 =
|
|
|
5018
5218
|
/** 本文の行番号。 知らせに載せるために使う */
|
|
5019
5219
|
| "pos";
|
|
5020
5220
|
|
|
5221
|
+
/**
|
|
5222
|
+
* 箱に出す題 (#1381)。 書いていなければ名前をそのまま使う。
|
|
5223
|
+
*
|
|
5224
|
+
* 名前は図の中で 1 つに決まる必要がある (`focus:` と `flow:` が名前で指す) 一方、題は
|
|
5225
|
+
* 重なってよい。 同じ題の箱を並べる図と、題を持たない箱は、名前と切り離さないと書けない。
|
|
5226
|
+
*/
|
|
5227
|
+
function 箱の題(a: DslActor): string {
|
|
5228
|
+
return a.title ?? a.name;
|
|
5229
|
+
}
|
|
5230
|
+
|
|
5021
5231
|
/** 放射では描けない欄。 書かれていたら伝える */
|
|
5022
5232
|
type 放射で描けない欄 =
|
|
5023
5233
|
| "kind"
|
|
@@ -5041,7 +5251,11 @@ type 放射で描けない欄 =
|
|
|
5041
5251
|
| "scaleKeys"
|
|
5042
5252
|
| "posRel"
|
|
5043
5253
|
| "nodes"
|
|
5044
|
-
| "layoutPos"
|
|
5254
|
+
| "layoutPos"
|
|
5255
|
+
// 箱の中に描く図形 (#1374)。 放射の枝は箱の中に図形を持たない
|
|
5256
|
+
| "shape"
|
|
5257
|
+
// 出す条件 (#1381)。 放射の枝は個別に出し分けられない
|
|
5258
|
+
| "visibleIf";
|
|
5045
5259
|
|
|
5046
5260
|
/** 引数が `never` でなければ型検査が落ちる */
|
|
5047
5261
|
type 空であること<T extends never> = T;
|
|
@@ -5096,6 +5310,8 @@ const 放射で描けない欄の名前: Record<放射で描けない欄, string
|
|
|
5096
5310
|
posRel: "位置 (相対)",
|
|
5097
5311
|
nodes: "中の箱ごとの指定",
|
|
5098
5312
|
layoutPos: "配置のずらし",
|
|
5313
|
+
shape: "箱の中の図形",
|
|
5314
|
+
visibleIf: "出す条件",
|
|
5099
5315
|
};
|
|
5100
5316
|
|
|
5101
5317
|
/**
|
|
@@ -5126,7 +5342,7 @@ function 放射で描けない欄を書いたか(a: DslActor, 欄: 放射で描
|
|
|
5126
5342
|
*/
|
|
5127
5343
|
function 放射に出す文字(a: DslActor): { title: string; subtitle?: string } {
|
|
5128
5344
|
const 続き = [a.subtitle, a.value].map((x) => x?.trim()).filter((x): x is string => !!x);
|
|
5129
|
-
return { title: a
|
|
5345
|
+
return { title: 箱の題(a), ...(続き.length > 0 ? { subtitle: 続き.join(" ") } : {}) };
|
|
5130
5346
|
}
|
|
5131
5347
|
|
|
5132
5348
|
function compileMind(doc: DslDocument, onNotice?: (n: CompileNotice) => void): CdlDiagram {
|
|
@@ -5226,7 +5442,8 @@ function compileMind(doc: DslDocument, onNotice?: (n: CompileNotice) => void): C
|
|
|
5226
5442
|
const 使った = new Set<string>([rootId]);
|
|
5227
5443
|
記録する(root);
|
|
5228
5444
|
// 中心は色の欄を持たない (`MindBranchPayload` に `tone` が無い)
|
|
5229
|
-
if (root.tone)
|
|
5445
|
+
if (root.tone)
|
|
5446
|
+
消えた欄.set(root.name, [...(消えた欄.get(root.name) ?? []), "色 (中心は持てない)"]);
|
|
5230
5447
|
|
|
5231
5448
|
見本でない.slice(1).forEach((a, i) => {
|
|
5232
5449
|
const id = slugify(a.name) || `leaf-${i}`;
|
|
@@ -5304,10 +5521,7 @@ function compileMind(doc: DslDocument, onNotice?: (n: CompileNotice) => void): C
|
|
|
5304
5521
|
* 後から来た見本の縦列に書いた値を移し、先に作った空の方を外す。 書いた人から見れば
|
|
5305
5522
|
* 「id を書けば効く」 が成り立つ。
|
|
5306
5523
|
*/
|
|
5307
|
-
function mergeDuplicateDeclaredLanes(
|
|
5308
|
-
diagram: CdlDiagram,
|
|
5309
|
-
追加した縦列: readonly DslLane[],
|
|
5310
|
-
): void {
|
|
5524
|
+
function mergeDuplicateDeclaredLanes(diagram: CdlDiagram, 追加した縦列: readonly DslLane[]): void {
|
|
5311
5525
|
for (const 宣言 of 追加した縦列) {
|
|
5312
5526
|
const { id } = 宣言;
|
|
5313
5527
|
const 同一idの縦列 = diagram.lanes.filter((l) => l.id === id);
|
|
@@ -5399,14 +5613,31 @@ function applyV05Extensions(
|
|
|
5399
5613
|
// `s{idx}-{laneId}` が actor 名末尾 "Header" (slug `...-header`) で誤マッチし、 option が invisible
|
|
5400
5614
|
// な step anchor にも copy される (cc-codex #883 MAJOR)。 lane id との構造 exact 一致で header だけを
|
|
5401
5615
|
// 引くことで step box / spacer / footer を排除する。
|
|
5402
|
-
primaryNodes =
|
|
5403
|
-
|
|
5404
|
-
|
|
5616
|
+
primaryNodes =
|
|
5617
|
+
ownedLaneIds.size > 0
|
|
5618
|
+
? diagram.nodes.filter((n) => ownedLaneIds.has(n.lane) && n.id === `${n.lane}-header`)
|
|
5619
|
+
: diagram.nodes.filter((n) => n.id === `${dragonSlug}-header`);
|
|
5405
5620
|
} else {
|
|
5406
5621
|
// 非 seq preset は 1 actor = 1 node (id = dragon slug) で node id と dragon slug が一致する。
|
|
5407
5622
|
primaryNodes = diagram.nodes.filter((n) => n.id === dragonSlug);
|
|
5408
5623
|
}
|
|
5409
5624
|
for (const node of primaryNodes) {
|
|
5625
|
+
// 識別に使う名前と、箱に出す題を分ける (#1381)。 preset が actor 名で
|
|
5626
|
+
// node を作る経路 (sequence / solidity / swimlane / c4) もここで書き換える。
|
|
5627
|
+
if (a.title !== undefined) {
|
|
5628
|
+
node.title = a.title;
|
|
5629
|
+
if (isSeqLike) {
|
|
5630
|
+
const footer = diagram.nodes.find((n) => n.id === `${node.lane}-footer`);
|
|
5631
|
+
if (footer) footer.title = a.title;
|
|
5632
|
+
|
|
5633
|
+
// 名前で決めた preset の幅を題に合わせる。 明示の大きさは後段が優先する。
|
|
5634
|
+
if (a.posW === undefined) {
|
|
5635
|
+
const titleW = Math.max(140, a.title.length * 22 + 52);
|
|
5636
|
+
node.w = titleW;
|
|
5637
|
+
if (footer) footer.w = titleW;
|
|
5638
|
+
}
|
|
5639
|
+
}
|
|
5640
|
+
}
|
|
5410
5641
|
// `type: c4` では説明の先頭に段の目印 (`L1` / `L2` / `L3`) を書く。 目印は組み立てに
|
|
5411
5642
|
// 段を伝えるためのもので読む人に意味を持たず、 段の名前は枠のラベルが既に出している。
|
|
5412
5643
|
// ここで落とさないと、 組み立てが読み取った目印がそのまま箱の説明として出る (#1098)
|
|
@@ -5482,6 +5713,30 @@ function applyV05Extensions(
|
|
|
5482
5713
|
const footer = diagram.nodes.find((n) => n.id === `${node.lane}-footer`);
|
|
5483
5714
|
if (footer && a.posW !== undefined) footer.w = a.posW;
|
|
5484
5715
|
}
|
|
5716
|
+
// 箱の中に描く図形 (#1374)。 renderer が shape を描くのは dyn-* kind だけなので、
|
|
5717
|
+
// shape 自身を SSOT にして対応する kind へ揃える。 card 等のまま shape だけ渡すと、指定を
|
|
5718
|
+
// 保持しているのに画面には何も出ない。 paint 検査が入力を mutate しないよう object も写す。
|
|
5719
|
+
if (a.shape !== undefined) {
|
|
5720
|
+
const dynamicKind = `dyn-${a.shape.kind}` as typeof node.kind;
|
|
5721
|
+
node.kind = dynamicKind;
|
|
5722
|
+
node.shape = { ...a.shape };
|
|
5723
|
+
if (isSeqLike) {
|
|
5724
|
+
const footer = diagram.nodes.find((n) => n.id === `${node.lane}-footer`);
|
|
5725
|
+
if (footer) {
|
|
5726
|
+
footer.kind = dynamicKind;
|
|
5727
|
+
footer.shape = { ...a.shape };
|
|
5728
|
+
}
|
|
5729
|
+
}
|
|
5730
|
+
}
|
|
5731
|
+
// その箱を出すかどうかの条件 (#1381)。 名札と足にも同じ条件を渡す = 片方だけ隠すと
|
|
5732
|
+
// 順序図で縦線の頭と足が食い違う
|
|
5733
|
+
if (a.visibleIf !== undefined) {
|
|
5734
|
+
node.visibleIf = a.visibleIf;
|
|
5735
|
+
if (isSeqLike) {
|
|
5736
|
+
const footer = diagram.nodes.find((n) => n.id === `${node.lane}-footer`);
|
|
5737
|
+
if (footer) footer.visibleIf = a.visibleIf;
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5485
5740
|
}
|
|
5486
5741
|
}
|
|
5487
5742
|
// 名札の高さを揃える。 kind ごとに高さが変わると縦線の始まる位置がばらけ、 順序図の
|
|
@@ -5599,7 +5854,12 @@ function injectPhasesFallback(diagram: CdlDiagram, doc: DslDocument): void {
|
|
|
5599
5854
|
}
|
|
5600
5855
|
// 線を持たない種類 (帯の依存等) では矢印が edge にならない。 両端が実在するなら
|
|
5601
5856
|
// その箱を光らせる = 矢印を指した段で何も光らないより意図に近い (#1077)
|
|
5602
|
-
if (
|
|
5857
|
+
if (
|
|
5858
|
+
!見つかった &&
|
|
5859
|
+
singleBoxNode !== undefined &&
|
|
5860
|
+
knownNames.has(entry.from) &&
|
|
5861
|
+
knownNames.has(entry.to)
|
|
5862
|
+
) {
|
|
5603
5863
|
out.push(singleBoxNode.id);
|
|
5604
5864
|
}
|
|
5605
5865
|
continue;
|
|
@@ -5629,9 +5889,7 @@ function injectPhasesFallback(diagram: CdlDiagram, doc: DslDocument): void {
|
|
|
5629
5889
|
// **`activate` と兼ねない**。 描画側は焦点と別集合で持つ (`cdl#512`) = 焦点が当たり
|
|
5630
5890
|
// 続ける図で毎段引き直しになるため。 書いた段だけが欄を持つ
|
|
5631
5891
|
const drawIds =
|
|
5632
|
-
p.draw !== undefined &&
|
|
5633
|
-
DRAW_TARGETS.get(p.draw) === doc.type &&
|
|
5634
|
-
singleBoxNode !== undefined
|
|
5892
|
+
p.draw !== undefined && DRAW_TARGETS.get(p.draw) === doc.type && singleBoxNode !== undefined
|
|
5635
5893
|
? [singleBoxNode.id]
|
|
5636
5894
|
: [];
|
|
5637
5895
|
diagram.phases.push({
|
|
@@ -5666,6 +5924,7 @@ function compileSequence(doc: DslDocument): CdlDiagram {
|
|
|
5666
5924
|
to: s.to,
|
|
5667
5925
|
label: s.label,
|
|
5668
5926
|
...(s.sub ? { sub: s.sub } : {}),
|
|
5927
|
+
...(s.side ? { side: s.side } : {}),
|
|
5669
5928
|
...(s.tone ? { tone: s.tone } : {}),
|
|
5670
5929
|
...(s.style ? { style: s.style } : {}),
|
|
5671
5930
|
});
|
|
@@ -5705,8 +5964,8 @@ function compileSequenceWithAnimate(doc: DslDocument): CdlDiagram {
|
|
|
5705
5964
|
const headerId = `${id}-header`;
|
|
5706
5965
|
// header/footer 幅を title 長に応じて auto-size (text-readability warning 解消)。
|
|
5707
5966
|
// formula = 22px/char + 52px padding (visualValidate text-readability と完全一致)、 min 140 で従来 sample 互換維持。
|
|
5708
|
-
const actorW = Math.max(140, a.
|
|
5709
|
-
b.node(headerId, { lane: id, stack: 0, kind: "card", title: a
|
|
5967
|
+
const actorW = Math.max(140, 箱の題(a).length * 22 + 52);
|
|
5968
|
+
b.node(headerId, { lane: id, stack: 0, kind: "card", title: 箱の題(a), w: actorW, h: 72 });
|
|
5710
5969
|
headerNodeIds.push(headerId);
|
|
5711
5970
|
const spacerId = `${id}-spacer`;
|
|
5712
5971
|
b.node(spacerId, { lane: id, stack: 1, kind: "card", title: "", w: 2, h: 40 });
|
|
@@ -5733,6 +5992,7 @@ function compileSequenceWithAnimate(doc: DslDocument): CdlDiagram {
|
|
|
5733
5992
|
id: edgeId,
|
|
5734
5993
|
label: s.label,
|
|
5735
5994
|
...(s.sub ? { sub: s.sub } : {}),
|
|
5995
|
+
...(s.side ? { side: s.side } : {}),
|
|
5736
5996
|
...(s.tone ? { tone: s.tone } : {}),
|
|
5737
5997
|
...(s.style ? { style: s.style } : {}),
|
|
5738
5998
|
});
|
|
@@ -5744,7 +6004,7 @@ function compileSequenceWithAnimate(doc: DslDocument): CdlDiagram {
|
|
|
5744
6004
|
doc.actors.forEach((a) => {
|
|
5745
6005
|
const laneId = actorIds.get(a.name) ?? slugify(a.name);
|
|
5746
6006
|
const footerId = `${laneId}-footer`;
|
|
5747
|
-
const actorW = Math.max(140, a.
|
|
6007
|
+
const actorW = Math.max(140, 箱の題(a).length * 22 + 52);
|
|
5748
6008
|
// `role` を付ける (#1273)。 付けないと生命線の終わりが footer より 100 下まで伸びる
|
|
5749
6009
|
// (実測 = 組立て API は `y2=848`、記法は `y2=948`)。 枠の大きさは同じなので
|
|
5750
6010
|
// 描いた図の大きさの比較では捕まらない
|
|
@@ -5752,7 +6012,7 @@ function compileSequenceWithAnimate(doc: DslDocument): CdlDiagram {
|
|
|
5752
6012
|
lane: laneId,
|
|
5753
6013
|
stack: footerStack,
|
|
5754
6014
|
kind: "card",
|
|
5755
|
-
title: a
|
|
6015
|
+
title: 箱の題(a),
|
|
5756
6016
|
w: actorW,
|
|
5757
6017
|
h: 72,
|
|
5758
6018
|
role: "lifeline-footer",
|
|
@@ -5901,7 +6161,7 @@ function compileFlow(doc: DslDocument): CdlDiagram {
|
|
|
5901
6161
|
{
|
|
5902
6162
|
id: slugify(a.name) || `n${i}`,
|
|
5903
6163
|
kind: a.kind,
|
|
5904
|
-
title: a
|
|
6164
|
+
title: 箱の題(a),
|
|
5905
6165
|
},
|
|
5906
6166
|
edgeLabel,
|
|
5907
6167
|
);
|
|
@@ -5951,6 +6211,7 @@ function compileSwimlane(doc: DslDocument): CdlDiagram {
|
|
|
5951
6211
|
id: `e${edgeIdx++}-${fromId}-${toId}`,
|
|
5952
6212
|
label: s.label,
|
|
5953
6213
|
...(s.sub ? { sub: s.sub } : {}),
|
|
6214
|
+
...(s.side ? { side: s.side } : {}),
|
|
5954
6215
|
...(s.tone ? { tone: s.tone } : {}),
|
|
5955
6216
|
...(s.style ? { style: s.style } : {}),
|
|
5956
6217
|
});
|
|
@@ -5970,7 +6231,7 @@ function compileSwimlane(doc: DslDocument): CdlDiagram {
|
|
|
5970
6231
|
lane: swim.laneId(a.name),
|
|
5971
6232
|
stack: 0,
|
|
5972
6233
|
kind: a.kind ?? "actor",
|
|
5973
|
-
title: a
|
|
6234
|
+
title: 箱の題(a),
|
|
5974
6235
|
});
|
|
5975
6236
|
});
|
|
5976
6237
|
}
|
|
@@ -5995,7 +6256,7 @@ function compileEr(doc: DslDocument): CdlDiagram {
|
|
|
5995
6256
|
// v0.3 で「列定義」 ブロックを追加検討
|
|
5996
6257
|
erBuilder.entity({
|
|
5997
6258
|
id: slugify(a.name) || a.name,
|
|
5998
|
-
title: a
|
|
6259
|
+
title: 箱の題(a),
|
|
5999
6260
|
rows: [], // v0.2 では rows なし
|
|
6000
6261
|
});
|
|
6001
6262
|
}
|
|
@@ -6052,7 +6313,7 @@ function compileState(doc: DslDocument): CdlDiagram {
|
|
|
6052
6313
|
const final = 決め方.終わり(a, i);
|
|
6053
6314
|
fsm.state({
|
|
6054
6315
|
id: slugify(a.name) || `s${i}`,
|
|
6055
|
-
title: a
|
|
6316
|
+
title: 箱の題(a),
|
|
6056
6317
|
...(initial ? { initial: true } : {}),
|
|
6057
6318
|
...(final ? { final: true } : {}),
|
|
6058
6319
|
});
|
|
@@ -6093,13 +6354,14 @@ function compileTopology(doc: DslDocument): CdlDiagram {
|
|
|
6093
6354
|
groupBuilder.add({
|
|
6094
6355
|
id: slugify(a.name) || a.name,
|
|
6095
6356
|
kind: a.kind,
|
|
6096
|
-
title: a
|
|
6357
|
+
title: 箱の題(a),
|
|
6097
6358
|
});
|
|
6098
6359
|
}
|
|
6099
6360
|
for (const s of doc.flow) {
|
|
6100
6361
|
topo.connect(slugify(s.from), slugify(s.to), {
|
|
6101
6362
|
label: s.label,
|
|
6102
6363
|
...(s.sub ? { sub: s.sub } : {}),
|
|
6364
|
+
...(s.side ? { side: s.side } : {}),
|
|
6103
6365
|
...(s.tone ? { tone: s.tone } : {}),
|
|
6104
6366
|
...(s.style ? { style: s.style } : {}),
|
|
6105
6367
|
});
|
|
@@ -6196,16 +6458,20 @@ function compileGenericWithAnimate(doc: DslDocument, opts: GenericOpts): CdlDiag
|
|
|
6196
6458
|
const lid = a.lane!;
|
|
6197
6459
|
const stack = 積んだ数.get(lid) ?? 0;
|
|
6198
6460
|
積んだ数.set(lid, stack + 1);
|
|
6199
|
-
b.node(id, { lane: lid, stack, kind: a.kind, title: a
|
|
6461
|
+
b.node(id, { lane: lid, stack, kind: a.kind, title: 箱の題(a) });
|
|
6200
6462
|
});
|
|
6201
6463
|
} else if (kind === "flow" || kind === "topology") {
|
|
6202
6464
|
// 1 lane に全 actor を縦 stack
|
|
6203
6465
|
const lid = opts.laneId ?? "main";
|
|
6204
|
-
b.lane(lid, {
|
|
6466
|
+
b.lane(lid, {
|
|
6467
|
+
width: laneWidth,
|
|
6468
|
+
label: doc.title,
|
|
6469
|
+
...(kind === "topology" ? { contain: true } : {}),
|
|
6470
|
+
});
|
|
6205
6471
|
doc.actors.forEach((a, idx) => {
|
|
6206
6472
|
const id = slugify(a.name) || `n${idx}`;
|
|
6207
6473
|
actorToNodeId.set(a.name, id);
|
|
6208
|
-
b.node(id, { lane: lid, stack: idx, kind: a.kind, title: a
|
|
6474
|
+
b.node(id, { lane: lid, stack: idx, kind: a.kind, title: 箱の題(a) });
|
|
6209
6475
|
});
|
|
6210
6476
|
} else {
|
|
6211
6477
|
// swimlane / er / state ... actor ごとに 1 lane (横並び)
|
|
@@ -6231,7 +6497,7 @@ function compileGenericWithAnimate(doc: DslDocument, opts: GenericOpts): CdlDiag
|
|
|
6231
6497
|
lane: lid,
|
|
6232
6498
|
stack: 0,
|
|
6233
6499
|
kind: a.kind,
|
|
6234
|
-
title: a
|
|
6500
|
+
title: 箱の題(a),
|
|
6235
6501
|
...(isInitial ? { eyebrow: "初期" } : {}),
|
|
6236
6502
|
...(isFinal ? { eyebrow: "最終" } : {}),
|
|
6237
6503
|
});
|
|
@@ -6264,8 +6530,11 @@ function compileGenericWithAnimate(doc: DslDocument, opts: GenericOpts): CdlDiag
|
|
|
6264
6530
|
b.edge(fromId, toId, {
|
|
6265
6531
|
id: edgeId,
|
|
6266
6532
|
label: labelWithCard,
|
|
6267
|
-
|
|
6533
|
+
...(後ろへ戻る矢印か(kind, fromId, toId, 箱の並び)
|
|
6534
|
+
? { routing: "back-detour" as const }
|
|
6535
|
+
: {}),
|
|
6268
6536
|
...(s.sub ? { sub: s.sub } : {}),
|
|
6537
|
+
...(s.side ? { side: s.side } : {}),
|
|
6269
6538
|
...(s.tone ? { tone: s.tone } : {}),
|
|
6270
6539
|
...(s.style ? { style: s.style } : {}),
|
|
6271
6540
|
});
|