@cardenelabs/dragon 0.30.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 +314 -12
- package/dist/index.cjs +6966 -1438
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2150 -118
- package/dist/index.d.ts +2150 -118
- package/dist/index.js +6950 -1440
- package/dist/index.js.map +1 -1
- package/package.json +12 -13
- package/src/compile/actor-ids.ts +6 -15
- package/src/compile/actor-option-notice.ts +379 -0
- package/src/compile/actors.ts +32 -3
- package/src/compile/c4.ts +0 -3
- package/src/compile/chain.ts +43 -4
- package/src/compile/chart-fields.ts +16 -1
- package/src/compile/direction.ts +50 -11
- package/src/compile/edge-option-notice.ts +112 -0
- package/src/compile/flowchart.ts +344 -0
- package/src/compile/funnel.ts +14 -2
- package/src/compile/gantt.ts +141 -36
- package/src/compile/generic.ts +473 -61
- package/src/compile/journey.ts +14 -5
- package/src/compile/kinds.ts +5 -13
- package/src/compile/lanes.ts +95 -13
- package/src/compile/metro.ts +271 -0
- package/src/compile/mind.ts +55 -5
- package/src/compile/notice.ts +51 -4
- package/src/compile/order.ts +62 -0
- package/src/compile/parts.ts +107 -33
- package/src/compile/placement-apply.ts +27 -11
- package/src/compile/quadrant.ts +171 -16
- package/src/compile/record.ts +75 -0
- package/src/compile/row-marks.ts +128 -0
- package/src/compile/sequence.ts +105 -26
- package/src/compile/slug.ts +9 -1
- package/src/compile/stages.ts +247 -0
- package/src/compile/subtitle.ts +15 -2
- package/src/compile/swimlane-marks.ts +87 -0
- package/src/compile/swimlane.ts +13 -14
- package/src/compile/timeline.ts +213 -0
- package/src/compile/topology.ts +3 -9
- package/src/compile/tree.ts +13 -4
- package/src/compile/value-chart.ts +179 -25
- package/src/compile.ts +1550 -370
- package/src/focus.ts +2 -2
- package/src/index.ts +34 -2
- package/src/json-parser.ts +1407 -48
- package/src/keywords.ts +251 -17
- package/src/notation-lint.ts +41 -64
- package/src/schemas/diagram.json +643 -20
- package/src/types.ts +336 -35
- package/src/v05/parser.ts +2451 -271
- package/src/compile/class.ts +0 -100
- package/src/compile/er.ts +0 -44
- package/src/compile/solidity.ts +0 -39
- package/src/compile/state.ts +0 -71
package/src/compile/gantt.ts
CHANGED
|
@@ -3,19 +3,19 @@ import type { CdlDiagram } from "@cardenelabs/cdl";
|
|
|
3
3
|
import type { DslDocument } from "../types";
|
|
4
4
|
|
|
5
5
|
import { actorRefTable } from "./actors";
|
|
6
|
-
import {
|
|
6
|
+
import { 効かない矢印の欄を並べる, 向きだけを使う図が伝えない矢印の欄 } from "./edge-option-notice";
|
|
7
7
|
import type { CompileNotice } from "./notice";
|
|
8
8
|
import { slugify } from "./slug";
|
|
9
9
|
import { 箱の題 } from "./node-title";
|
|
10
10
|
import { truncateForMessage, 図の小見出し } from "./subtitle";
|
|
11
|
+
import { 図表の大きさを読む } from "./chart-fields";
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
+
* ガントチャート (`type: gantt`) の組み立て (#2030 で `compile.ts` から移した)。
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
export function compileGantt(doc: DslDocument, onNotice?: (n: CompileNotice) => void): CdlDiagram {
|
|
16
17
|
const b = diagram(slugify(doc.title), { topic: doc.title, type: "gantt" });
|
|
17
|
-
const
|
|
18
|
-
b.lane("gantt", { width: CHART_W });
|
|
18
|
+
const 既定W = 720;
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* 書いたのに効かない形を伝える。 **`console.warn` だけにしない** (#2111) = 編集画面は知らせだけを
|
|
@@ -28,7 +28,8 @@ export function compileGantt(doc: DslDocument, onNotice?: (n: CompileNotice) =>
|
|
|
28
28
|
|
|
29
29
|
// 目盛りは **書かれた順** に並べる。 以前は `Q1=200 / Q2=600 / ...` の決め打ちで、 Q1-Q4 以外は
|
|
30
30
|
// 全て同じ位置に落ちていた。 順に並べれば月名でも週番号でも同じ規則で置ける
|
|
31
|
-
const
|
|
31
|
+
const 明示した目盛り = doc.ticks !== undefined;
|
|
32
|
+
const 目盛り: string[] = doc.ticks === undefined ? [] : [...doc.ticks];
|
|
32
33
|
const 目盛りなし: string[] = [];
|
|
33
34
|
// 最初に時期を読めなかった項目の行。 画面が案内できるようにする (値の図と同じ)
|
|
34
35
|
let 目盛りなし行 = 0;
|
|
@@ -40,6 +41,11 @@ export function compileGantt(doc: DslDocument, onNotice?: (n: CompileNotice) =>
|
|
|
40
41
|
tone?: DslDocument["actors"][number]["tone"];
|
|
41
42
|
owner?: string;
|
|
42
43
|
end?: string;
|
|
44
|
+
startLabel?: string;
|
|
45
|
+
milestone?: boolean;
|
|
46
|
+
emphasis?: "primary";
|
|
47
|
+
startFraction: number;
|
|
48
|
+
precise: boolean;
|
|
43
49
|
}[] = [];
|
|
44
50
|
for (const a of doc.actors) {
|
|
45
51
|
const label = (a.value ?? a.subtitle ?? "").trim();
|
|
@@ -48,16 +54,40 @@ export function compileGantt(doc: DslDocument, onNotice?: (n: CompileNotice) =>
|
|
|
48
54
|
目盛りなし.push(a.name);
|
|
49
55
|
continue;
|
|
50
56
|
}
|
|
51
|
-
|
|
57
|
+
const 始まり = 時期を読む(label);
|
|
58
|
+
if (始まり === null) {
|
|
59
|
+
伝える(
|
|
60
|
+
"chart-value-unreadable",
|
|
61
|
+
a.name,
|
|
62
|
+
`type: gantt で ${truncateForMessage(a.name)} の始まり (${truncateForMessage(label)}) が読めません (帯に載せません)。 月の中の位置は \`6月+0.55\` の形で 0..1 を書いてください`,
|
|
63
|
+
a.pos?.line ?? 0,
|
|
64
|
+
);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (!明示した目盛り && !目盛り.includes(始まり.label)) 目盛り.push(始まり.label);
|
|
68
|
+
if (明示した目盛り && !目盛り.includes(始まり.label)) {
|
|
69
|
+
伝える(
|
|
70
|
+
"chart-value-unreadable",
|
|
71
|
+
a.name,
|
|
72
|
+
`type: gantt で ${truncateForMessage(a.name)} の始まり (${truncateForMessage(始まり.label)}) は ticks にありません (帯に載せません)。 ticks = ${目盛り.map(truncateForMessage).join(" / ")}`,
|
|
73
|
+
a.pos?.line ?? 0,
|
|
74
|
+
);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
52
77
|
// 色は帯にそのまま渡す。 箱が 1 つになっても、 書いた色が消えないようにする
|
|
53
78
|
タスク.push({
|
|
54
79
|
name: a.name,
|
|
55
80
|
title: 箱の題(a),
|
|
56
81
|
label,
|
|
82
|
+
startFraction: 始まり.fraction,
|
|
83
|
+
precise: 明示した目盛り || 始まり.written || (a.end?.includes("+") ?? false),
|
|
57
84
|
line: a.pos?.line ?? 0,
|
|
58
85
|
...(a.tone !== undefined ? { tone: a.tone } : {}),
|
|
59
86
|
...(a.owner !== undefined ? { owner: a.owner } : {}),
|
|
60
87
|
...(a.end !== undefined ? { end: a.end } : {}),
|
|
88
|
+
...(a.startLabel !== undefined ? { startLabel: a.startLabel } : {}),
|
|
89
|
+
...(a.milestone !== undefined ? { milestone: a.milestone } : {}),
|
|
90
|
+
...(a.emphasis !== undefined ? { emphasis: a.emphasis } : {}),
|
|
61
91
|
});
|
|
62
92
|
}
|
|
63
93
|
if (目盛りなし.length > 0) {
|
|
@@ -75,7 +105,7 @@ export function compileGantt(doc: DslDocument, onNotice?: (n: CompileNotice) =>
|
|
|
75
105
|
const タスク名 = new Set(タスク.map((t) => t.name));
|
|
76
106
|
// 矢印の端の名前を引く表。 全ての図種に共通の「居ない名前」 の知らせと同じ表を使う
|
|
77
107
|
const 名前の表 = actorRefTable(doc);
|
|
78
|
-
const 依存元 = new Map<string, string>();
|
|
108
|
+
const 依存元 = new Map<string, string[]>();
|
|
79
109
|
for (const s of doc.flow) {
|
|
80
110
|
const line = s.pos?.line ?? 0;
|
|
81
111
|
const 矢印 = `${truncateForMessage(s.from)} -> ${truncateForMessage(s.to)}`;
|
|
@@ -92,20 +122,26 @@ export function compileGantt(doc: DslDocument, onNotice?: (n: CompileNotice) =>
|
|
|
92
122
|
);
|
|
93
123
|
continue;
|
|
94
124
|
}
|
|
95
|
-
依存元.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
125
|
+
const 前工程 = 依存元.get(s.to) ?? [];
|
|
126
|
+
if (!前工程.includes(s.from)) {
|
|
127
|
+
前工程.push(s.from);
|
|
128
|
+
依存元.set(s.to, 前工程);
|
|
129
|
+
}
|
|
130
|
+
/*
|
|
131
|
+
* 帯の依存は「どちらが先か」 だけを持つ。 矢印に書いた飾りは描けないので伝える。
|
|
132
|
+
*
|
|
133
|
+
* **伝える欄を並べない** (#2366)。 以前は 文字 / 色 / 線種 / 多重度 の 4 種を手で並べて
|
|
134
|
+
* いたため、後から足した項目がどこにも入らなかった (実測 = 矢印に書ける 21 項目のうち
|
|
135
|
+
* 17 件が、図も変わらず知らせも出ないまま落ちていた)。 矢印が使う欄を除いた残り全部を伝える。
|
|
136
|
+
*
|
|
137
|
+
* 多重度もここに含める = 多重度の知らせ (#2107) と同じ行に 2 件並べない。
|
|
138
|
+
*/
|
|
139
|
+
const 効かない = 効かない矢印の欄を並べる(s, 向きだけを使う図が伝えない矢印の欄);
|
|
104
140
|
if (効かない.length > 0) {
|
|
105
141
|
伝える(
|
|
106
142
|
"edge-option-not-honored",
|
|
107
143
|
s.from,
|
|
108
|
-
`type: gantt で ${矢印} に書いた ${効かない.join(" / ")} は描けません (
|
|
144
|
+
`type: gantt で ${矢印} に書いた ${効かない.join(" / ")} は描けません (ガントチャートの矢印は前後の関係だけを使います)`,
|
|
109
145
|
line,
|
|
110
146
|
);
|
|
111
147
|
}
|
|
@@ -113,7 +149,9 @@ export function compileGantt(doc: DslDocument, onNotice?: (n: CompileNotice) =>
|
|
|
113
149
|
|
|
114
150
|
// 高さは件数から決める。 描画側は 1 行 28 以上 + 行間 20 で積み、 上下に 32 / 44 の余白を取る
|
|
115
151
|
// (`kinds/gantt.tsx`)。 360 の固定だと 8 件目から最後の帯が枠の外に出る (実測 = 8 件で 56 はみ出す)
|
|
116
|
-
const
|
|
152
|
+
const 既定H = Math.max(360, 48 * タスク.length + 96);
|
|
153
|
+
const { w: CHART_W, h: CHART_H } = 図表の大きさを読む(doc, { w: 既定W, h: 既定H });
|
|
154
|
+
b.lane("gantt", { width: CHART_W });
|
|
117
155
|
|
|
118
156
|
b.node(`${slugify(doc.title) || "gantt"}-chart`, {
|
|
119
157
|
lane: "gantt",
|
|
@@ -124,24 +162,47 @@ export function compileGantt(doc: DslDocument, onNotice?: (n: CompileNotice) =>
|
|
|
124
162
|
w: CHART_W,
|
|
125
163
|
h: CHART_H,
|
|
126
164
|
ganttData: タスク.map((t) => {
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
const
|
|
165
|
+
const startName = 時期を読む(t.label)?.label ?? t.label;
|
|
166
|
+
const idx = 目盛り.indexOf(startName);
|
|
167
|
+
const startIdx = idx + t.startFraction;
|
|
168
|
+
const dependencies = (依存元.get(t.name) ?? []).map((from) => slugify(from) || from);
|
|
169
|
+
const [firstDependency] = dependencies;
|
|
170
|
+
const dependsOn = firstDependency === undefined
|
|
171
|
+
? undefined
|
|
172
|
+
: dependencies.length === 1 ? firstDependency : dependencies;
|
|
173
|
+
// 帯の誤りは、その項目を書いた行で伝える。 知らせの種類は呼ばれる側が決める (#2392)
|
|
174
|
+
const 帯を伝える = (種類: CompileNotice["kind"], 名: string, message: string): void =>
|
|
175
|
+
伝える(種類, 名, message, t.line);
|
|
176
|
+
const 終わり = 終わる位置(
|
|
177
|
+
t.end,
|
|
178
|
+
startIdx,
|
|
179
|
+
目盛り,
|
|
180
|
+
t.name,
|
|
181
|
+
帯を伝える,
|
|
182
|
+
doc,
|
|
183
|
+
t.precise,
|
|
184
|
+
);
|
|
185
|
+
const startLabel = t.startLabel ?? (t.precise ? 位置の目盛り(startIdx, 目盛り) : t.label);
|
|
133
186
|
return {
|
|
134
187
|
id: slugify(t.name) || t.name,
|
|
135
188
|
title: t.title,
|
|
136
|
-
startIdx
|
|
189
|
+
startIdx,
|
|
137
190
|
endIdx: 終わり.idx,
|
|
138
|
-
startLabel
|
|
139
|
-
endLabel: 終わり.label ??
|
|
191
|
+
startLabel,
|
|
192
|
+
endLabel: 終わり.label ?? startLabel,
|
|
140
193
|
...(t.owner !== undefined ? { owner: t.owner } : {}),
|
|
141
|
-
...(
|
|
194
|
+
...(dependsOn !== undefined ? { dependsOn } : {}),
|
|
142
195
|
...(t.tone !== undefined ? { tone: t.tone } : {}),
|
|
196
|
+
...(t.emphasis !== undefined ? { emphasis: t.emphasis } : {}),
|
|
197
|
+
...(t.milestone === true ? { milestone: true as const } : {}),
|
|
143
198
|
};
|
|
144
199
|
}),
|
|
200
|
+
...(明示した目盛り ? { ganttAxisMax: 目盛り.length } : {}),
|
|
201
|
+
...(doc.ganttToday !== undefined ? { ganttToday: doc.ganttToday } : {}),
|
|
202
|
+
...(doc.ganttTickLabels !== undefined ? { ganttTickLabels: doc.ganttTickLabels } : {}),
|
|
203
|
+
...(doc.ganttBarEnd !== undefined ? { ganttBarEnd: doc.ganttBarEnd } : {}),
|
|
204
|
+
...(doc.ganttBarThickness !== undefined ? { ganttBarThickness: doc.ganttBarThickness } : {}),
|
|
205
|
+
...(doc.ganttSize !== undefined ? { ganttSize: doc.ganttSize } : {}),
|
|
145
206
|
});
|
|
146
207
|
|
|
147
208
|
return b.build();
|
|
@@ -191,15 +252,18 @@ function 状態が取る値(参照: string, doc: DslDocument): number[] {
|
|
|
191
252
|
* 帯の端に出す字が段ごとに変わるわけではない。
|
|
192
253
|
*
|
|
193
254
|
* 時期の名前を書いたら、その名前の位置に終わる。 書いた名前が目盛りに無い形は始まりと同じに
|
|
194
|
-
*
|
|
255
|
+
* 倒し、**読めないことを伝える** (#2392) = 目盛りは書かれた順に作るため載っていない名前は
|
|
256
|
+
* 位置を持たないが、黙って倒すと「書いたのに 1 コマのまま」 になる。
|
|
257
|
+
* 始まりより前を書いた時 (下の分岐) と同じ扱いにする。
|
|
195
258
|
*/
|
|
196
259
|
function 終わる位置(
|
|
197
260
|
end: string | undefined,
|
|
198
261
|
始まり: number,
|
|
199
262
|
目盛り: readonly string[],
|
|
200
263
|
名前: string,
|
|
201
|
-
伝える: (名: string, message: string) => void,
|
|
264
|
+
伝える: (種類: CompileNotice["kind"], 名: string, message: string) => void,
|
|
202
265
|
doc: DslDocument,
|
|
266
|
+
precise = false,
|
|
203
267
|
): { idx: number | string; label?: string } {
|
|
204
268
|
if (end === undefined) return { idx: 始まり };
|
|
205
269
|
if (/^\{\w+\}$/.test(end)) {
|
|
@@ -211,22 +275,63 @@ function 終わる位置(
|
|
|
211
275
|
const 低い = 状態が取る値(end, doc).filter((v) => v < 始まり);
|
|
212
276
|
if (低い.length > 0) {
|
|
213
277
|
伝える(
|
|
278
|
+
"gantt-end-before-start",
|
|
214
279
|
名前,
|
|
215
280
|
`type: gantt で ${truncateForMessage(名前)} の終わり (${truncateForMessage(end)}) が始まりより前になる値を取ります (${[...new Set(低い)].join(", ")})。 始まりは ${始まり} 番目です`,
|
|
216
281
|
);
|
|
217
282
|
}
|
|
218
|
-
return {
|
|
283
|
+
return {
|
|
284
|
+
idx: end,
|
|
285
|
+
...(precise ? { label: 状態の目盛り(end, doc, 目盛り) ?? 位置の目盛り(始まり, 目盛り) } : {}),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
const 時点 = 時期を読む(end);
|
|
289
|
+
const i = 時点 === null ? -1 : 目盛り.indexOf(時点.label);
|
|
290
|
+
// 目盛りは箱に書いた時期から作るので、どの箱も書いていない時期は位置を持たない。
|
|
291
|
+
// 読めた時期を並べて伝える = 目盛りは図ごとに変わるため、固定の一覧を文に書けない
|
|
292
|
+
if (i < 0 || 時点 === null) {
|
|
293
|
+
伝える(
|
|
294
|
+
"chart-value-unreadable",
|
|
295
|
+
名前,
|
|
296
|
+
`type: gantt で ${truncateForMessage(名前)} の終わり (${truncateForMessage(end)}) は目盛りにありません (帯を伸ばしません)。` +
|
|
297
|
+
` 目盛りは箱に書いた時期から作ります = ${目盛り.map((x) => truncateForMessage(x)).join(" / ")} のどれかを書いてください`,
|
|
298
|
+
);
|
|
299
|
+
return { idx: 始まり };
|
|
219
300
|
}
|
|
220
|
-
const i = 目盛り.indexOf(end);
|
|
221
|
-
if (i < 0) return { idx: 始まり };
|
|
222
301
|
// 始まりより前に終わる帯は描けない。 そのまま渡すと横幅が負になり、帯が始まりの位置から
|
|
223
302
|
// 左へはみ出す。 始まりと同じに倒して伝える (黙って倒すと「書いたのに 1 コマのまま」 になる)
|
|
224
|
-
|
|
303
|
+
const endIdx = precise ? i + (時点.written ? 時点.fraction : 1) - 1 : i;
|
|
304
|
+
if (endIdx < 始まり) {
|
|
225
305
|
伝える(
|
|
306
|
+
"gantt-end-before-start",
|
|
226
307
|
名前,
|
|
227
308
|
`type: gantt で ${truncateForMessage(名前)} の終わり (${truncateForMessage(end)}) が始まりより前です (始まりと同じに倒しました)`,
|
|
228
309
|
);
|
|
229
|
-
return { idx:
|
|
310
|
+
return { idx: 始まり, ...(precise ? { label: 位置の目盛り(始まり, 目盛り) } : {}) };
|
|
230
311
|
}
|
|
231
|
-
return { idx:
|
|
312
|
+
return { idx: endIdx, label: precise ? 位置の目盛り(endIdx, 目盛り) : end };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** `6月+0.55` を目盛り名と、その目盛り内の位置に分ける。 */
|
|
316
|
+
function 時期を読む(raw: string): { label: string; fraction: number; written: boolean } | null {
|
|
317
|
+
const m = raw.trim().match(/^(.+?)(?:\+([+-]?(?:\d+(?:\.\d+)?|\.\d+)))?$/);
|
|
318
|
+
if (m === null) return null;
|
|
319
|
+
const fraction = m[2] === undefined ? 0 : Number(m[2]);
|
|
320
|
+
if (!Number.isFinite(fraction) || fraction < 0 || fraction > 1) return null;
|
|
321
|
+
return { label: m[1]!.trim(), fraction, written: m[2] !== undefined };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function 位置の目盛り(idx: number, 目盛り: readonly string[]): string {
|
|
325
|
+
const rounded = Math.max(0, Math.min(目盛り.length - 1, Math.round(idx)));
|
|
326
|
+
return 目盛り[rounded] ?? "";
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function 状態の目盛り(
|
|
330
|
+
ref: string,
|
|
331
|
+
doc: DslDocument,
|
|
332
|
+
目盛り: readonly string[],
|
|
333
|
+
): string | undefined {
|
|
334
|
+
const values = 状態が取る値(ref, doc);
|
|
335
|
+
if (values.length === 0) return undefined;
|
|
336
|
+
return 位置の目盛り(Math.max(...values), 目盛り);
|
|
232
337
|
}
|