@cardenelabs/dragon 0.28.0 → 0.32.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 +391 -16
- package/dist/index.cjs +13479 -7403
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2360 -208
- package/dist/index.d.ts +2360 -208
- package/dist/index.js +13462 -7405
- package/dist/index.js.map +1 -1
- package/package.json +12 -13
- package/src/compile/actor-ids.ts +297 -0
- package/src/compile/actor-option-notice.ts +379 -0
- package/src/compile/actors.ts +103 -0
- package/src/compile/c4.ts +74 -0
- package/src/compile/chain.ts +93 -0
- package/src/compile/chart-fields.ts +193 -0
- package/src/compile/deep-rewrite.ts +30 -0
- package/src/compile/derived-source.ts +18 -0
- package/src/compile/direction.ts +102 -0
- package/src/compile/edge-option-notice.ts +112 -0
- package/src/compile/er-relation.ts +200 -0
- package/src/compile/flow.ts +54 -0
- package/src/compile/flowchart.ts +344 -0
- package/src/compile/funnel.ts +87 -0
- package/src/compile/gantt.ts +337 -0
- package/src/compile/generic.ts +696 -0
- package/src/compile/hierarchy.ts +92 -0
- package/src/compile/journey.ts +93 -0
- package/src/compile/kinds.ts +148 -0
- package/src/compile/lanes.ts +138 -0
- package/src/compile/metro.ts +271 -0
- package/src/compile/mind.ts +393 -0
- package/src/compile/node-title.ts +24 -0
- package/src/compile/notice.ts +141 -0
- package/src/compile/order.ts +62 -0
- package/src/compile/parts-lookup.ts +17 -0
- package/src/compile/parts.ts +2709 -0
- package/src/compile/placement-apply.ts +689 -0
- package/src/compile/placement.ts +15 -0
- package/src/compile/quadrant.ts +265 -0
- package/src/compile/record.ts +75 -0
- package/src/compile/row-marks.ts +128 -0
- package/src/compile/rows.ts +40 -0
- package/src/compile/sequence.ts +305 -0
- package/src/compile/slug.ts +37 -0
- package/src/compile/stages.ts +247 -0
- package/src/compile/subtitle.ts +35 -0
- package/src/compile/swimlane-marks.ts +87 -0
- package/src/compile/swimlane.ts +82 -0
- package/src/compile/timeline.ts +213 -0
- package/src/compile/topology.ts +44 -0
- package/src/compile/tree.ts +69 -0
- package/src/compile/value-chart.ts +337 -0
- package/src/compile/values.ts +432 -0
- package/src/compile/word-state.ts +82 -0
- package/src/compile.ts +1806 -6971
- package/src/focus.ts +2 -2
- package/src/index.ts +42 -9
- package/src/json-line-map.ts +30 -0
- package/src/json-parser.ts +1760 -168
- package/src/keywords.ts +251 -17
- package/src/near-name.ts +42 -0
- package/src/notation-lint.ts +41 -64
- package/src/relative-pos.ts +71 -2
- package/src/schema.ts +1 -1
- package/src/schemas/diagram.json +680 -23
- package/src/types.ts +337 -36
- package/src/v05/parser.ts +2553 -354
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 置けたかどうかの判定に使う幅 (#2034 で `compile.ts` から移した)。
|
|
3
|
+
*
|
|
4
|
+
* **葉に置く** = 配置を確かめる側 (`verifyPlacement` / `applyLayoutOffsets`) と
|
|
5
|
+
* 部品の取り込み側 (`縦列に置いた部品を揃える`) の両方が読む。 どちらかに置くと、
|
|
6
|
+
* もう片方から逆向きの取り込みが生まれて輪になる。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 書いた通りに置けたと見なす誤差。
|
|
11
|
+
*
|
|
12
|
+
* 補正が効いた図種では実測 0.0 で一致する。 効かない向き (順序図の縦) は数百ずれるので、
|
|
13
|
+
* その間で切る。 丸めと配置計算の揺れを吸収する幅として 1 を取る。
|
|
14
|
+
*/
|
|
15
|
+
export const PLACEMENT_TOLERANCE = 1;
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { diagram } from "@cardenelabs/cdl";
|
|
2
|
+
import type { CdlDiagram } from "@cardenelabs/cdl";
|
|
3
|
+
import type { DslDocument } from "../types";
|
|
4
|
+
import { 図表の大きさ, 図表の大きさを読む, 数の欄から参照できる名前, 語の欄から参照できる名前 } from "./chart-fields";
|
|
5
|
+
import { 箱の題 } from "./node-title";
|
|
6
|
+
import type { CompileNotice } from "./notice";
|
|
7
|
+
import { slugify } from "./slug";
|
|
8
|
+
import { 図の小見出し } from "./subtitle";
|
|
9
|
+
import { 区画 } from "./word-state";
|
|
10
|
+
/**
|
|
11
|
+
* 四象限 (`type: quadrant`) の組み立て (#1154)。 描画側に 1 つの箱で渡す。
|
|
12
|
+
*
|
|
13
|
+
* 登場人物ごとに **どの区画か** を読む (`- 重複削除: "左上"`)。 使える語は `区画` が持つ。
|
|
14
|
+
*/
|
|
15
|
+
export function compileQuadrant(
|
|
16
|
+
doc: DslDocument,
|
|
17
|
+
onNotice?: (n: CompileNotice) => void,
|
|
18
|
+
): CdlDiagram {
|
|
19
|
+
const b = diagram(slugify(doc.title), { topic: doc.title, type: "quadrant" });
|
|
20
|
+
const { w: W, h: H } = 図表の大きさを読む(doc, 図表の大きさ.quadrant);
|
|
21
|
+
b.lane("chart", { width: W + 64 });
|
|
22
|
+
const items: NonNullable<CdlDiagram["nodes"][number]["quadrantData"]>["items"] = [];
|
|
23
|
+
const 読めない: string[] = [];
|
|
24
|
+
const 参照できる = 語の欄から参照できる名前(doc, 区画);
|
|
25
|
+
const 数を参照できる = 数の欄から参照できる名前(doc);
|
|
26
|
+
const 伝える = (a: DslDocument["actors"][number], message: string): void => {
|
|
27
|
+
onNotice?.({
|
|
28
|
+
kind: "chart-value-unreadable",
|
|
29
|
+
actor: a.name,
|
|
30
|
+
line: a.atPos?.line ?? a.pos?.line ?? 0,
|
|
31
|
+
message,
|
|
32
|
+
});
|
|
33
|
+
if (typeof console !== "undefined" && console.warn) console.warn(`[dragon] ${message}`);
|
|
34
|
+
};
|
|
35
|
+
for (const a of doc.actors) {
|
|
36
|
+
const 語 = (a.value ?? a.subtitle ?? "").trim();
|
|
37
|
+
if (a.at !== undefined) {
|
|
38
|
+
const 座標 = 四象限の座標(a.at, 数を参照できる);
|
|
39
|
+
if (座標 === null) {
|
|
40
|
+
伝える(
|
|
41
|
+
a,
|
|
42
|
+
`type: quadrant で ${a.name} の at (${a.at.raw}) が読めません (図に載せません)。 at: [0.2, 0.8] の形で 0..1 の数か {状態名} を 2 つ書いてください`,
|
|
43
|
+
);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const 範囲外 = [
|
|
47
|
+
座標.x,
|
|
48
|
+
座標.y,
|
|
49
|
+
...[座標.x, 座標.y].flatMap((v) =>
|
|
50
|
+
typeof v === "string" ? 状態が取る値(v, doc) : [],
|
|
51
|
+
),
|
|
52
|
+
].filter(
|
|
53
|
+
(v): v is number => typeof v === "number" && (v < 0 || v > 1),
|
|
54
|
+
);
|
|
55
|
+
if (範囲外.length > 0) {
|
|
56
|
+
伝える(
|
|
57
|
+
a,
|
|
58
|
+
`type: quadrant で ${a.name} の at (${a.at.raw}) が 0..1 の範囲外です (描画時に最寄りの端へ寄せます)`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const q = 区画.get(語);
|
|
62
|
+
const 座標の区画 = 数の象限(座標);
|
|
63
|
+
if (q !== undefined && 座標の区画 !== undefined && q !== 座標の区画) {
|
|
64
|
+
伝える(
|
|
65
|
+
a,
|
|
66
|
+
`type: quadrant で ${a.name} の区画 (${語}) と at (${a.at.raw}) の象限が食い違います (at を採ります)`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
items.push({ id: slugify(a.name), title: 箱の題(a), at: 座標 });
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const 参照 = 語.match(/^\{(\w+)\}$/);
|
|
73
|
+
if (参照) {
|
|
74
|
+
if (!参照できる.has(参照[1]!)) {
|
|
75
|
+
読めない.push(a.name);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
items.push({ id: slugify(a.name), title: 箱の題(a), quadrant: 語 });
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const q = 区画.get(語);
|
|
82
|
+
if (q === undefined) {
|
|
83
|
+
読めない.push(a.name);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
items.push({ id: slugify(a.name), title: 箱の題(a), quadrant: q });
|
|
87
|
+
}
|
|
88
|
+
if (読めない.length > 0) {
|
|
89
|
+
const m = `type: quadrant で区画を読めない項目があります (図に載せません): ${読めない.join(", ")}。 \`- 重複削除: "左上"\` の形で、 ${[...区画.keys()].join(" / ")} のどれかを書いてください`;
|
|
90
|
+
onNotice?.({ kind: "chart-value-unreadable", actor: 読めない[0]!, line: 0, message: m });
|
|
91
|
+
if (typeof console !== "undefined" && console.warn) console.warn(`[dragon] ${m}`);
|
|
92
|
+
}
|
|
93
|
+
// 矢印は描けない。 書かれていたら伝える (黙って捨てると「書いたのに効かない」 が残る)
|
|
94
|
+
if (doc.flow.length > 0) {
|
|
95
|
+
const m2 = `type: quadrant では矢印を描けません (${doc.flow.length} 本を無視しました)。 関係を描くなら type: flow を使ってください`;
|
|
96
|
+
onNotice?.({
|
|
97
|
+
kind: "chart-edge-dropped",
|
|
98
|
+
actor: doc.flow[0]?.from ?? "",
|
|
99
|
+
line: doc.flow[0]?.pos?.line ?? 0,
|
|
100
|
+
message: m2,
|
|
101
|
+
});
|
|
102
|
+
if (typeof console !== "undefined" && console.warn) console.warn(`[dragon] ${m2}`);
|
|
103
|
+
}
|
|
104
|
+
b.node(`${slugify(doc.title) || "quadrant"}-chart`, {
|
|
105
|
+
lane: "chart",
|
|
106
|
+
stack: 0,
|
|
107
|
+
kind: "quadrant-matrix",
|
|
108
|
+
title: doc.title,
|
|
109
|
+
...図の小見出し(doc),
|
|
110
|
+
w: W,
|
|
111
|
+
h: H,
|
|
112
|
+
quadrantData: { ...軸と区画の名前(doc), items },
|
|
113
|
+
...(doc.quadrantPointLabelSide !== undefined
|
|
114
|
+
? { quadrantPointLabelSide: doc.quadrantPointLabelSide }
|
|
115
|
+
: {}),
|
|
116
|
+
...(doc.quadrantLabelPosition !== undefined ? { quadrantLabelPosition: doc.quadrantLabelPosition } : {}),
|
|
117
|
+
...(doc.quadrantSize !== undefined ? { quadrantSize: doc.quadrantSize } : {}),
|
|
118
|
+
});
|
|
119
|
+
return b.build();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function 四象限の座標(
|
|
123
|
+
at: NonNullable<DslDocument["actors"][number]["at"]>,
|
|
124
|
+
参照できる: ReadonlySet<string>,
|
|
125
|
+
): { x: number | string; y: number | string } | null {
|
|
126
|
+
if (!at.raw.startsWith("[") || !at.raw.endsWith("]") || at.x === undefined || at.y === undefined)
|
|
127
|
+
return null;
|
|
128
|
+
if (at.raw.slice(1, -1).split(",").length !== 2) return null;
|
|
129
|
+
const 読める = (v: number | string): boolean => {
|
|
130
|
+
if (typeof v === "number") return Number.isFinite(v);
|
|
131
|
+
const m = v.match(/^\{([\w-]+)\}$/);
|
|
132
|
+
return m !== null && 参照できる.has(m[1]!);
|
|
133
|
+
};
|
|
134
|
+
return 読める(at.x) && 読める(at.y) ? { x: at.x, y: at.y } : null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** 座標の `{状態名}` が、記法に書かれた範囲で取り得る数を集める。 */
|
|
138
|
+
function 状態が取る値(参照: string, doc: DslDocument): number[] {
|
|
139
|
+
const m = 参照.match(/^\{([\w-]+)\}$/);
|
|
140
|
+
if (m === null) return [];
|
|
141
|
+
const 名前 = m[1]!;
|
|
142
|
+
const values: number[] = [];
|
|
143
|
+
const 数を加える = (value: unknown): void => {
|
|
144
|
+
if (typeof value === "number" && Number.isFinite(value)) values.push(value);
|
|
145
|
+
else if (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value)))
|
|
146
|
+
values.push(Number(value));
|
|
147
|
+
};
|
|
148
|
+
let 初期値: unknown;
|
|
149
|
+
let 初期値あり = false;
|
|
150
|
+
for (const state of doc.animate?.states ?? []) {
|
|
151
|
+
if (state.name !== 名前) continue;
|
|
152
|
+
初期値 = state.initial;
|
|
153
|
+
初期値あり = true;
|
|
154
|
+
}
|
|
155
|
+
if (初期値あり) 数を加える(初期値);
|
|
156
|
+
for (const phase of doc.animate?.phases ?? []) {
|
|
157
|
+
for (const tween of phase.tweens ?? []) {
|
|
158
|
+
if (tween.state !== 名前) continue;
|
|
159
|
+
数を加える(tween.from);
|
|
160
|
+
数を加える(tween.to);
|
|
161
|
+
}
|
|
162
|
+
for (const set of phase.sets ?? []) if (set.state === 名前) 数を加える(set.value);
|
|
163
|
+
}
|
|
164
|
+
return values;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function 数の象限(at: { x: number | string; y: number | string }):
|
|
168
|
+
| "topLeft"
|
|
169
|
+
| "topRight"
|
|
170
|
+
| "bottomLeft"
|
|
171
|
+
| "bottomRight"
|
|
172
|
+
| undefined {
|
|
173
|
+
if (typeof at.x !== "number" || typeof at.y !== "number") return undefined;
|
|
174
|
+
return `${at.y >= 0.5 ? "top" : "bottom"}${at.x >= 0.5 ? "Right" : "Left"}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** 軸を書かなかった時の名前。 何の軸か分からないため、位置をそのまま出す */
|
|
178
|
+
const 軸の既定 = {
|
|
179
|
+
xAxis: { left: "小さい", right: "大きい" },
|
|
180
|
+
yAxis: { bottom: "小さい", top: "大きい" },
|
|
181
|
+
quadrantLabels: { topLeft: "左上", topRight: "右上", bottomLeft: "左下", bottomRight: "右下" },
|
|
182
|
+
} as const;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 2 軸で仕分ける図の軸と区画の名前を決める (#1251)。
|
|
186
|
+
*
|
|
187
|
+
* `axes:` を書かなければ従来どおり位置の名前 (`左上` 等) を出す = 既に描いてある図が動かない。
|
|
188
|
+
*
|
|
189
|
+
* 書いたら区画の名前は **軸の名前から決める** (`{上} × {右}`)。 組立て API 側が同じ規則で
|
|
190
|
+
* 導いており (実測 = `xAxis: {left: "L", right: "R"}` / `yAxis: {bottom: "B", top: "T"}` で
|
|
191
|
+
* `topLeft: "T × L"`)、 別の規則にすると同じ内容を書いても図が食い違う。
|
|
192
|
+
*
|
|
193
|
+
* 片側だけ書いた形では、書かなかった側は既定のままにする。 空文字を渡すと名前の無い軸が描かれる。
|
|
194
|
+
*/
|
|
195
|
+
function 軸と区画の名前(
|
|
196
|
+
doc: DslDocument,
|
|
197
|
+
): Omit<NonNullable<CdlDiagram["nodes"][number]["quadrantData"]>, "items"> {
|
|
198
|
+
const 端 = 軸の名前(doc);
|
|
199
|
+
const x = doc.axes?.x;
|
|
200
|
+
const y = doc.axes?.y;
|
|
201
|
+
const xAxis = x !== undefined && "direction" in x && x.direction === true
|
|
202
|
+
? { label: x.label, direction: true as const }
|
|
203
|
+
: 端.xAxis;
|
|
204
|
+
const yAxis = y !== undefined && "direction" in y && y.direction === true
|
|
205
|
+
? { label: y.label, direction: true as const }
|
|
206
|
+
: 端.yAxis;
|
|
207
|
+
return { xAxis, yAxis, quadrantLabels: 区画の名前(doc, 端) };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** 軸の両端の名前。 書かなかった側は既定のまま = 空文字を渡すと名前の無い軸が描かれる */
|
|
211
|
+
function 軸の名前(doc: DslDocument): {
|
|
212
|
+
xAxis: { left: string; right: string };
|
|
213
|
+
yAxis: { bottom: string; top: string };
|
|
214
|
+
} {
|
|
215
|
+
if (doc.axes === undefined) return { xAxis: 軸の既定.xAxis, yAxis: 軸の既定.yAxis };
|
|
216
|
+
const x = doc.axes.x;
|
|
217
|
+
const y = doc.axes.y;
|
|
218
|
+
return {
|
|
219
|
+
xAxis: {
|
|
220
|
+
left: x !== undefined && "left" in x ? x.left ?? 軸の既定.xAxis.left : 軸の既定.xAxis.left,
|
|
221
|
+
right: x !== undefined && "right" in x ? x.right ?? 軸の既定.xAxis.right : 軸の既定.xAxis.right,
|
|
222
|
+
},
|
|
223
|
+
yAxis: {
|
|
224
|
+
bottom: y !== undefined && "bottom" in y ? y.bottom ?? 軸の既定.yAxis.bottom : 軸の既定.yAxis.bottom,
|
|
225
|
+
top: y !== undefined && "top" in y ? y.top ?? 軸の既定.yAxis.top : 軸の既定.yAxis.top,
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* 区画の中に出す名前 (#2667)。
|
|
232
|
+
*
|
|
233
|
+
* 決め方は 3 段で、書いたものが勝つ。
|
|
234
|
+
*
|
|
235
|
+
* | 何を書いたか | 出る名前 |
|
|
236
|
+
* |---|---|
|
|
237
|
+
* | `regions:` にその区画を書いた | 書いた名前 |
|
|
238
|
+
* | `axes:` を書いた | 軸の掛け合わせ (`{上} × {左}`) |
|
|
239
|
+
* | どちらも書かない | 位置の名前 (`左上` 等) |
|
|
240
|
+
*
|
|
241
|
+
* **区画ごとに独立して決める**。 `regions:` を 1 つだけ書いた図で、残り 3 つが
|
|
242
|
+
* 位置の名前に戻ると「書いた所だけ変わる」 という読み方ができなくなる。
|
|
243
|
+
*/
|
|
244
|
+
function 区画の名前(
|
|
245
|
+
doc: DslDocument,
|
|
246
|
+
軸: { xAxis: { left: string; right: string }; yAxis: { bottom: string; top: string } },
|
|
247
|
+
): { topLeft: string; topRight: string; bottomLeft: string; bottomRight: string } {
|
|
248
|
+
const 掛け合わせ =
|
|
249
|
+
doc.axes === undefined
|
|
250
|
+
? 軸の既定.quadrantLabels
|
|
251
|
+
: {
|
|
252
|
+
topLeft: `${軸.yAxis.top} × ${軸.xAxis.left}`,
|
|
253
|
+
topRight: `${軸.yAxis.top} × ${軸.xAxis.right}`,
|
|
254
|
+
bottomLeft: `${軸.yAxis.bottom} × ${軸.xAxis.left}`,
|
|
255
|
+
bottomRight: `${軸.yAxis.bottom} × ${軸.xAxis.right}`,
|
|
256
|
+
};
|
|
257
|
+
const r = doc.regions;
|
|
258
|
+
if (r === undefined) return { ...掛け合わせ };
|
|
259
|
+
return {
|
|
260
|
+
topLeft: r.topLeft ?? 掛け合わせ.topLeft,
|
|
261
|
+
topRight: r.topRight ?? 掛け合わせ.topRight,
|
|
262
|
+
bottomLeft: r.bottomLeft ?? 掛け合わせ.bottomLeft,
|
|
263
|
+
bottomRight: r.bottomRight ?? 掛け合わせ.bottomRight,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { CLASS_RELATION_LOOK } from "@cardenelabs/cdl";
|
|
2
|
+
import type { CdlDiagram, CdlEdge, ClassRelationType, Tone } from "@cardenelabs/cdl";
|
|
3
|
+
import type { DslDocument, DslStep } from "../types";
|
|
4
|
+
|
|
5
|
+
import { ERの関係の指定を作る, ERの関係の矢印 } from "./er-relation";
|
|
6
|
+
import { compileGenericWithAnimate } from "./generic";
|
|
7
|
+
|
|
8
|
+
/** UML の関係を、印の形と同じ 3 群の色へ寄せる。描画側の classDiagram と同じ決まり。 */
|
|
9
|
+
const クラスの関係の色: Record<ClassRelationType, Tone> = {
|
|
10
|
+
extends: "accent",
|
|
11
|
+
implements: "accent",
|
|
12
|
+
aggregates: "teal",
|
|
13
|
+
composes: "teal",
|
|
14
|
+
associates: "success",
|
|
15
|
+
uses: "success",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 箱が行を持つ図の組み立て (#2782)。
|
|
20
|
+
*
|
|
21
|
+
* 畳む前は `er` と `state` の 2 つに分かれており、それぞれが **2 つの経路** を持っていた。
|
|
22
|
+
* 動きか縦列 (表の図) か種類 (移り変わりの図) を書いた図は共通の経路へ、書かない図は
|
|
23
|
+
* 組み立て器 (`er()` / `stateMachine()`) の経路へ落ちていた。
|
|
24
|
+
*
|
|
25
|
+
* **経路は共通の 1 本だけにする**。 組み立て器の経路は書いた行を捨てており
|
|
26
|
+
* (`rows: []` を渡していた)、記法に行を書いても図に出なかった。 動きを 1 段足すだけで
|
|
27
|
+
* 行が現れる形になっていたので、同じ記法が同じ図になるほうへ倒す。
|
|
28
|
+
*/
|
|
29
|
+
export function compileRecord(doc: DslDocument): CdlDiagram {
|
|
30
|
+
return compileGenericWithAnimate(doc, {
|
|
31
|
+
kind: "record",
|
|
32
|
+
laneWidth: 縦列の幅(doc),
|
|
33
|
+
edgeDefaults: 関係の既定,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* record の関係の既定 (#2783)。
|
|
39
|
+
*
|
|
40
|
+
* `relation` がある時だけ UML の関係として読み、`sub` は行き先、`tailSub` は出どころの
|
|
41
|
+
* 多重度にする。 relation の無い `sub` は従来どおり線の名前の下の行として残す。
|
|
42
|
+
* relation の有無を境にすることで、既存の record の補足を壊さず、class から移した本文も
|
|
43
|
+
* 同じ欄のまま同じ端へ置ける。
|
|
44
|
+
*/
|
|
45
|
+
function 関係の既定(s: DslStep): Partial<CdlEdge> {
|
|
46
|
+
if (s.relation === undefined) {
|
|
47
|
+
return ERの関係の矢印(ERの関係の指定を作る(s));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const 見た目 = CLASS_RELATION_LOOK[s.relation];
|
|
51
|
+
return {
|
|
52
|
+
label: s.label,
|
|
53
|
+
tone: クラスの関係の色[s.relation],
|
|
54
|
+
style: 見た目.style,
|
|
55
|
+
head: 見た目.head,
|
|
56
|
+
headFill: 見た目.fill,
|
|
57
|
+
tailHead: 見た目.tailHead,
|
|
58
|
+
tailHeadFill: 見た目.tailFill,
|
|
59
|
+
...(s.sub !== undefined ? { headLabel: s.sub } : {}),
|
|
60
|
+
...(s.tailSub !== undefined ? { tailLabel: s.tailSub } : {}),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 縦列の幅 (#2782)。
|
|
66
|
+
*
|
|
67
|
+
* **行を持つ箱があるかで決める**。 行を持つ箱は名前と型が左右に分かれて並ぶので幅が要り、
|
|
68
|
+
* 持たない箱は題だけなので要らない。 畳む前は図種ごとに持っていた値 (表の図 450 /
|
|
69
|
+
* 移り変わりの図 360) で、どちらも「行を持つか」 の言い換えだった。
|
|
70
|
+
*
|
|
71
|
+
* 書いた `lanes:` の幅はこの既定より強い (`compile/lanes.ts` が後から重ねる)。
|
|
72
|
+
*/
|
|
73
|
+
function 縦列の幅(doc: DslDocument): number {
|
|
74
|
+
return doc.actors.some((a) => (a.rows?.length ?? 0) > 0) ? 450 : 360;
|
|
75
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { RowMark } from "@cardenelabs/cdl";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 行頭の印を dragon の語で決める (#2782)。
|
|
5
|
+
*
|
|
6
|
+
* 描画側が持つ印は 3 軸しかない。 形 (四角 / 山形) と 塗り (塗る / 中空) と
|
|
7
|
+
* 名前の下線で、この 3 軸に 1 対 1 で当たる語を dragon 自身が持つ。
|
|
8
|
+
*
|
|
9
|
+
* | 語 | 軸 | 意味 |
|
|
10
|
+
* |---|---|---|
|
|
11
|
+
* | `鍵` | 名前に下線を引く | その行で箱を見分ける |
|
|
12
|
+
* | `外` | 行頭を山形にする | その行は箱の外とつながる |
|
|
13
|
+
* | `条件` | 行頭を中空にする | その行は条件が揃った時だけ効く |
|
|
14
|
+
*
|
|
15
|
+
* 畳む前は 図種ごとに語を持っていた (表の図は `pk` / `fk` / `opt`、移り変わりの図は
|
|
16
|
+
* `entry` / `exit` / `do` / `internal`)。 2 つの語彙は同じ 2 軸の格子を別の名前で
|
|
17
|
+
* 呼んでいただけで、格子の 4 隅は 1 対 1 に重なる。
|
|
18
|
+
*
|
|
19
|
+
* | | 塗る | 中空 |
|
|
20
|
+
* |---|---|---|
|
|
21
|
+
* | 四角 | 語なし / `do` | `条件` / `opt` / `internal` |
|
|
22
|
+
* | 山形 | `外` / `fk` / `entry` | `外 条件` / `fk opt` / `exit` |
|
|
23
|
+
*
|
|
24
|
+
* 古い語から dragon の語への読み替えは本 file の `行頭の語へ読み替える` が持ち、
|
|
25
|
+
* 読み取りの入口 (記法 / JSON の 2 経路) が通す。 印にする時点で古い語は 1 つも残っていない。
|
|
26
|
+
*/
|
|
27
|
+
export const 行頭の語 = ["鍵", "外", "条件"] as const;
|
|
28
|
+
|
|
29
|
+
export type 行頭の語 = (typeof 行頭の語)[number];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 行頭の印の古い語から dragon の 3 語への読み替え (#2782)。
|
|
33
|
+
*
|
|
34
|
+
* **ここは図種の別名と向きが違う**。 図種の `er` / `state` は綴りごと消したが (畳んだ先の
|
|
35
|
+
* `record` が別の名前なので、古い綴りを残すと「名前を無くす」 という目的が達たない)、
|
|
36
|
+
* 行頭の語は残す = 語が指す印は 1 つに決まっており、`pk` と `鍵` は同じ絵になる。
|
|
37
|
+
*
|
|
38
|
+
* 読み替え先は空白区切りの語。 `exit` だけが 2 語になる = 山形 + 中空 の 2 軸を同時に動かす。
|
|
39
|
+
*/
|
|
40
|
+
const 行頭の語の別名: ReadonlyMap<string, string> = new Map([
|
|
41
|
+
["pk", "鍵"],
|
|
42
|
+
["fk", "外"],
|
|
43
|
+
["opt", "条件"],
|
|
44
|
+
["entry", "外"],
|
|
45
|
+
["exit", "外 条件"],
|
|
46
|
+
["do", ""],
|
|
47
|
+
["internal", "条件"],
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 1 行分の印の語を dragon の 3 語に揃える (#2782)。
|
|
52
|
+
*
|
|
53
|
+
* 1 行に語を 2 つ以上書ける (`pk fk` の形) ので、語ごとに引いて繋ぎ直す。
|
|
54
|
+
* 知らない語はそのまま通す = 組み立て側が無視するため、書いた人が気付ける形は変わらない。
|
|
55
|
+
*
|
|
56
|
+
* **読み取りの入口は 3 経路ある** (縦に並べた形 / 中括弧の形 / JSON)。 3 つとも本 file の
|
|
57
|
+
* この関数を通す = 1 経路でも素通りさせると、その書き方だけ古い語が印を持たないまま通る
|
|
58
|
+
* (中括弧の形が実際にそうなっていた)。
|
|
59
|
+
*/
|
|
60
|
+
export function 行頭の語へ読み替える(値: string): string {
|
|
61
|
+
const 語 = 値.trim().split(/\s+/).filter(Boolean);
|
|
62
|
+
if (語.length === 0) return 値;
|
|
63
|
+
return 語
|
|
64
|
+
.map((w) => 行頭の語の別名.get(w) ?? w)
|
|
65
|
+
.filter((w) => w !== "")
|
|
66
|
+
.join(" ");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 書いた語を行頭の印にする (#2782)。
|
|
71
|
+
*
|
|
72
|
+
* **語を書かない行にも既定の印を付ける**。 四角 + 塗る + 下線なしで、箱の中で完結して
|
|
73
|
+
* 常に在る行を表す。
|
|
74
|
+
*
|
|
75
|
+
* 印なしにすると、書かなかった行だけ行頭が空いて **群の区切りと見分けが付かなくなる**
|
|
76
|
+
* (群の区切りは行頭の印が持っている)。 畳む前の表の図が同じ理由で既定を配っていた。
|
|
77
|
+
*/
|
|
78
|
+
export function 行頭の印にする(
|
|
79
|
+
marks: readonly (string | { mark: string; tone?: "primary" | "muted" })[],
|
|
80
|
+
行数: number,
|
|
81
|
+
): (RowMark | null)[] {
|
|
82
|
+
return Array.from({ length: 行数 }, (_, i) => {
|
|
83
|
+
const 値 = marks[i] ?? "";
|
|
84
|
+
const 語 = (typeof 値 === "string" ? 値 : 値.mark).trim().split(/\s+/).filter(Boolean);
|
|
85
|
+
return {
|
|
86
|
+
shape: 語.includes("外") ? ("chevron" as const) : ("square" as const),
|
|
87
|
+
filled: !語.includes("条件"),
|
|
88
|
+
...(typeof 値 === "object" && 値.tone !== undefined ? { tone: 値.tone } : {}),
|
|
89
|
+
...(語.includes("鍵") ? { underline: true } : {}),
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 箱を見分ける行を上にまとめる (#2782)。
|
|
96
|
+
*
|
|
97
|
+
* `鍵` を書いた行を先に、残りを後に置く。 畳む前の表の図が同じ並べ替えをしていた
|
|
98
|
+
* (組み立て器 `er()` と同じ形)。 `鍵` を 1 つも書かない図では並びが 1 行も動かない。
|
|
99
|
+
*
|
|
100
|
+
* **空の行では群を分けない**。 群の区切りは行頭の印が持っており、空の行を挟むと
|
|
101
|
+
* 行の間隔が不揃いになる (描画側が cdl 0.23.0 で空の行をやめた)。
|
|
102
|
+
*/
|
|
103
|
+
export function 鍵の行を上にまとめる(
|
|
104
|
+
rows: readonly string[],
|
|
105
|
+
印: readonly (RowMark | null)[],
|
|
106
|
+
): { rows: string[]; rowMarks: (RowMark | null)[] } {
|
|
107
|
+
const 鍵: number[] = [];
|
|
108
|
+
const 値: number[] = [];
|
|
109
|
+
rows.forEach((_, i) => ((印[i]?.underline === true ? 鍵 : 値).push(i)));
|
|
110
|
+
const 並び = [...鍵, ...値];
|
|
111
|
+
return {
|
|
112
|
+
rows: 並び.map((i) => rows[i] ?? ""),
|
|
113
|
+
rowMarks: 並び.map((i) => 印[i] ?? null),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 行頭の印を読む図種か (#2782)。
|
|
119
|
+
*
|
|
120
|
+
* **図種の名前を案内の文に写さない**。 読む図種を足した日に案内だけが古くなるため、
|
|
121
|
+
* 知らせを組む側はここに聞く (`compile.ts` の `行頭の印を読む図種`)。
|
|
122
|
+
*
|
|
123
|
+
* 畳む前は 2 図種 (表の図 / 移り変わりの図) が別の語彙で読んでいた。 クラス図は行を
|
|
124
|
+
* 組み立て器が作るので読まない。
|
|
125
|
+
*/
|
|
126
|
+
export function 行頭の印を読むか(type: string): boolean {
|
|
127
|
+
return type === "record";
|
|
128
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 箱に書いた行を読み取る (#2030 で `compile.ts` から移した)。
|
|
3
|
+
*
|
|
4
|
+
* `compileC4` と、`compile.ts` に残る v0.5 の拡張の両方が呼ぶ。
|
|
5
|
+
*
|
|
6
|
+
* **葉に置く** = どちらかの側に置くと、もう片方から逆向きの取り込みが生まれる。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 段の目印を読み取る。 目印と、 それを落とした残りの説明を返す (#1098)。
|
|
11
|
+
*
|
|
12
|
+
* 目印 (`L1` / `L2` / `L3`) は「どの段に置くか」 を組み立てに伝えるためのもので、 読む人には
|
|
13
|
+
* 意味を持たない。 段の名前は枠のラベル (`全体の見取り図` 等) が出すので二重でもある。
|
|
14
|
+
* 読み取ったら説明から落とす = 書いた人が説明として書いた部分だけが箱に出る。
|
|
15
|
+
*
|
|
16
|
+
* | 書いた文字 | 段 | 残る説明 |
|
|
17
|
+
* |---|---|---|
|
|
18
|
+
* | `"L1"` | 1 | (無し) |
|
|
19
|
+
* | `"L2: container"` | 2 | `container` |
|
|
20
|
+
* | `"L1 利用者"` | 1 | `利用者` |
|
|
21
|
+
* | `"利用者"` | 1 (既定) | `利用者` |
|
|
22
|
+
*
|
|
23
|
+
* 目印の直後の区切り (`:` と空白) も落とす。 残さないと `: container` のように区切りだけが
|
|
24
|
+
* 先頭に残る。 `L2X` のような別の語を目印と読み違えないよう、 数字の直後が英数字でないことを
|
|
25
|
+
* 条件にする。
|
|
26
|
+
*/
|
|
27
|
+
export function 段を読み取る(subtitle: string | undefined): {
|
|
28
|
+
段: number;
|
|
29
|
+
説明: string | undefined;
|
|
30
|
+
} {
|
|
31
|
+
const 元 = (subtitle ?? "").trim();
|
|
32
|
+
const m = 元.match(/^L([123])(?![0-9A-Za-z])/i);
|
|
33
|
+
if (m === null) return { 段: 1, 説明: subtitle };
|
|
34
|
+
// 目印と、 その直後の区切り (`:` / 全角コロン / 空白) を落とす
|
|
35
|
+
const 残り = 元
|
|
36
|
+
.slice(m[0].length)
|
|
37
|
+
.replace(/^[::\s]+/, "")
|
|
38
|
+
.trim();
|
|
39
|
+
return { 段: Number(m[1]), 説明: 残り === "" ? undefined : 残り };
|
|
40
|
+
}
|