@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/json-parser.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
EDGE_HEAD_VALUES,
|
|
25
25
|
NODE_KIND_VALID,
|
|
26
26
|
PRESET_TYPES,
|
|
27
|
+
TYPE_ALIASES,
|
|
27
28
|
STYLE_VALID,
|
|
28
29
|
resolveNodeKind,
|
|
29
30
|
resolveTone,
|
|
@@ -36,15 +37,67 @@ import {
|
|
|
36
37
|
EVENT_KINDS,
|
|
37
38
|
EVENT_TARGET_KEYS,
|
|
38
39
|
EVENT_REQUIRED_KEYS,
|
|
40
|
+
CHART_LINE_DASH_VALUES,
|
|
41
|
+
CHART_LINE_MARKER_VALUES,
|
|
42
|
+
CHART_PIE_TABLE_COLUMN_VALUES,
|
|
43
|
+
CHART_PIE_RING_WIDTH_VALUES,
|
|
44
|
+
CHART_STACKED_LEGEND_POSITION_VALUES,
|
|
45
|
+
CHART_WAFFLE_LEGEND_POSITION_VALUES,
|
|
46
|
+
FUNNEL_FORM_VALUES,
|
|
47
|
+
FUNNEL_RATE_VALUES,
|
|
48
|
+
QUADRANT_POINT_LABEL_SIDE_VALUES,
|
|
49
|
+
TREE_NODE_FORM_VALUES,
|
|
50
|
+
TREE_EDGE_TONE_VALUES,
|
|
51
|
+
TREE_EDGE_HEAD_VALUES,
|
|
52
|
+
MIND_FORM_VALUES,
|
|
53
|
+
JOURNEY_FORM_VALUES,
|
|
54
|
+
JOURNEY_LINE_FORM_VALUES,
|
|
55
|
+
JOURNEY_OPPORTUNITY_POSITION_VALUES,
|
|
56
|
+
JOURNEY_LABEL_KEYS,
|
|
57
|
+
CORNER_FORM_VALUES,
|
|
58
|
+
CHART_BAR_SHAPE_VALUES,
|
|
59
|
+
QUADRANT_LABEL_POSITION_VALUES,
|
|
60
|
+
LABEL_COLLISION_VALUES,
|
|
61
|
+
GANTT_TASK_LABEL_ALIGN_VALUES,
|
|
62
|
+
CDL_ICON_VALUES,
|
|
63
|
+
KIND_FORM_VALUES,
|
|
64
|
+
TITLE_ALIGN_VALUES,
|
|
65
|
+
LEGEND_LINE_STYLE_VALUES,
|
|
66
|
+
LEGEND_NOTE_ALIGN_VALUES,
|
|
67
|
+
ROW_MARK_FORM_VALUES,
|
|
68
|
+
ROW_MARK_TONE_VALUES,
|
|
69
|
+
ROW_DIVIDER_FORM_VALUES,
|
|
70
|
+
ROW_WELL_KEYS,
|
|
71
|
+
SEQUENCE_ACTOR_FORM_VALUES,
|
|
72
|
+
SEQUENCE_LABEL_FORM_VALUES,
|
|
73
|
+
SEQUENCE_RETURN_HEAD_VALUES,
|
|
74
|
+
SEQUENCE_KIND_LABEL_KEYS,
|
|
75
|
+
SEQUENCE_SELF_LOOP_KEYS,
|
|
39
76
|
type 図形の定義,
|
|
40
77
|
} from "./v05/parser";
|
|
41
|
-
//
|
|
78
|
+
// 図の意匠 (#1553 / #2790)。 記法の読み手と同じ解決を通す = 別名の受け方が
|
|
42
79
|
// 記法と JSON でずれない
|
|
43
|
-
import {
|
|
44
|
-
|
|
80
|
+
import {
|
|
81
|
+
PHASE_BODY_KEYS,
|
|
82
|
+
DIAGRAM_SHAPES,
|
|
83
|
+
THEMES,
|
|
84
|
+
THEME_ALIAS,
|
|
85
|
+
resolveOrder,
|
|
86
|
+
resolveDiagramShape,
|
|
87
|
+
shapesForDiagramType,
|
|
88
|
+
resolveTheme,
|
|
89
|
+
resolveLegendMark,
|
|
90
|
+
} from "./keywords";
|
|
91
|
+
// 区画の語の表 (#2667)。 記法と同じ表から直す = 同じ区画を 2 通りで呼ばない
|
|
92
|
+
import { 区画 } from "./compile/word-state";
|
|
93
|
+
// 行頭の印の古い語の読み替え (#2782)。 記法と同じ表を通す = JSON だけ古い語が印を持たない
|
|
94
|
+
// 形にならない
|
|
95
|
+
import { 行頭の語へ読み替える } from "./compile/row-marks";
|
|
96
|
+
import type { DslDiagramShape, DslShape } from "./keywords";
|
|
45
97
|
import type { CompileToCdlOpts } from "./compile";
|
|
46
98
|
import type {
|
|
47
99
|
CdlDiagram,
|
|
100
|
+
CdlLegendItem,
|
|
48
101
|
NodeKind,
|
|
49
102
|
Tone,
|
|
50
103
|
EdgeStyle,
|
|
@@ -58,6 +111,8 @@ import type {
|
|
|
58
111
|
import { extractIdentifiers, parseFormula } from "@cardenelabs/cdl";
|
|
59
112
|
import type {
|
|
60
113
|
DslDocument,
|
|
114
|
+
DslStageHeader,
|
|
115
|
+
DslRegions,
|
|
61
116
|
DslActor,
|
|
62
117
|
DslStep,
|
|
63
118
|
DslAnimate,
|
|
@@ -83,18 +138,93 @@ import {
|
|
|
83
138
|
type RelativePos,
|
|
84
139
|
} from "./relative-pos";
|
|
85
140
|
|
|
141
|
+
/** 古い綴りで書いた図種 (#2655 / #2657)。 受ける値は `TYPE_ALIASES` が SSOT */
|
|
142
|
+
type 図種の別名 = "solidity" | DslShape;
|
|
143
|
+
|
|
86
144
|
/**
|
|
87
145
|
* LLM 向け JSON DSL の入力 shape。 YAML DSL と 1:1 対応、 top-level は flat な object。
|
|
88
146
|
*/
|
|
89
147
|
export interface DragonJson {
|
|
90
148
|
/** 図の title (必須) */
|
|
91
149
|
title: string;
|
|
92
|
-
/**
|
|
93
|
-
|
|
150
|
+
/**
|
|
151
|
+
* preset type (必須)。 受け付ける値は `PRESET_TYPES` と `TYPE_ALIASES` が SSOT。
|
|
152
|
+
*
|
|
153
|
+
* 別名 (#2655 / #2657) は読み取りで図種と語の 2 つに開く = `solidity` と書いた JSON は
|
|
154
|
+
* 順序図 + 種類で並べ替え、`pie` と書いた JSON は 数を描く図 + 円の形 として読まれる。
|
|
155
|
+
*/
|
|
156
|
+
type: PresetType | 図種の別名;
|
|
157
|
+
/** 円グラフの見せ方。 */
|
|
158
|
+
form?: string;
|
|
159
|
+
/** `form` の和名。両方あれば `form` を採る。 */
|
|
160
|
+
見せ方?: string;
|
|
161
|
+
/** ガントチャートの目盛り。 */
|
|
162
|
+
ticks?: string[];
|
|
163
|
+
/** `ticks` の和名。両方あれば `ticks` を採る。 */
|
|
164
|
+
目盛り?: string[];
|
|
165
|
+
/** ガントチャートの今日の線の位置と札。 */
|
|
166
|
+
ganttToday?: { index: number; label: string };
|
|
167
|
+
/** 工程の端とは独立した目盛り名。 */
|
|
168
|
+
ganttTickLabels?: string[];
|
|
169
|
+
ganttBarEnd?: "position";
|
|
170
|
+
ganttBarThickness?: "thin";
|
|
171
|
+
chartSeriesSkipMuted?: DslDocument["chartSeriesSkipMuted"];
|
|
172
|
+
chartLineSeries?: DslDocument["chartLineSeries"];
|
|
173
|
+
chartPieCenterLabel?: DslDocument["chartPieCenterLabel"];
|
|
174
|
+
chartPieTableColumns?: DslDocument["chartPieTableColumns"];
|
|
175
|
+
chartPieRingWidth?: DslDocument["chartPieRingWidth"];
|
|
176
|
+
chartSlopePeriods?: DslDocument["chartSlopePeriods"];
|
|
177
|
+
chartSlopeEmphasisIds?: DslDocument["chartSlopeEmphasisIds"];
|
|
178
|
+
chartSlopeUnit?: DslDocument["chartSlopeUnit"];
|
|
179
|
+
chartStackedPeriods?: DslDocument["chartStackedPeriods"];
|
|
180
|
+
chartStackedRateId?: DslDocument["chartStackedRateId"];
|
|
181
|
+
chartStackedLegendPosition?: DslDocument["chartStackedLegendPosition"];
|
|
182
|
+
chartGaugeValue?: DslDocument["chartGaugeValue"];
|
|
183
|
+
chartWaffleLegendPosition?: DslDocument["chartWaffleLegendPosition"];
|
|
184
|
+
funnelForm?: DslDocument["funnelForm"];
|
|
185
|
+
funnelRate?: DslDocument["funnelRate"];
|
|
186
|
+
quadrantPointLabelSide?: DslDocument["quadrantPointLabelSide"];
|
|
187
|
+
treeNodeForm?: DslDocument["treeNodeForm"];
|
|
188
|
+
treeSize?: DslDocument["treeSize"];
|
|
189
|
+
treeEdgeTone?: DslDocument["treeEdgeTone"];
|
|
190
|
+
treeEdgeHead?: DslDocument["treeEdgeHead"];
|
|
191
|
+
mindForm?: DslDocument["mindForm"];
|
|
192
|
+
mindSize?: DslDocument["mindSize"];
|
|
193
|
+
edgeEndGap?: DslDocument["edgeEndGap"];
|
|
194
|
+
journeyForm?: DslDocument["journeyForm"];
|
|
195
|
+
journeyLineForm?: DslDocument["journeyLineForm"];
|
|
196
|
+
journeyLabels?: DslDocument["journeyLabels"];
|
|
197
|
+
journeySize?: DslDocument["journeySize"];
|
|
198
|
+
sequenceActorForm?: DslDocument["sequenceActorForm"];
|
|
199
|
+
sequenceLabelForm?: DslDocument["sequenceLabelForm"];
|
|
200
|
+
sequenceReturnHead?: DslDocument["sequenceReturnHead"];
|
|
201
|
+
sequenceKindLabels?: DslDocument["sequenceKindLabels"];
|
|
202
|
+
sequenceRowGap?: DslDocument["sequenceRowGap"];
|
|
203
|
+
sequenceGutter?: DslDocument["sequenceGutter"];
|
|
204
|
+
sequenceBandWidth?: DslDocument["sequenceBandWidth"];
|
|
205
|
+
sequenceSelfLoop?: DslDocument["sequenceSelfLoop"];
|
|
206
|
+
figureCard?: DslDocument["figureCard"];
|
|
207
|
+
figureSize?: DslDocument["figureSize"];
|
|
208
|
+
terminalDecoration?: DslDocument["terminalDecoration"];
|
|
209
|
+
cornerForm?: DslDocument["cornerForm"];
|
|
210
|
+
figureKindLabel?: DslDocument["figureKindLabel"];
|
|
211
|
+
figureInset?: DslDocument["figureInset"];
|
|
212
|
+
chartBarSize?: DslDocument["chartBarSize"];
|
|
213
|
+
chartBarShape?: DslDocument["chartBarShape"];
|
|
214
|
+
chartLineSize?: DslDocument["chartLineSize"];
|
|
215
|
+
chartPieSize?: DslDocument["chartPieSize"];
|
|
216
|
+
chartWaffleSize?: DslDocument["chartWaffleSize"];
|
|
217
|
+
chartSlopeLabelTemplate?: DslDocument["chartSlopeLabelTemplate"];
|
|
218
|
+
chartSlopeSize?: DslDocument["chartSlopeSize"];
|
|
219
|
+
chartStackedLayout?: DslDocument["chartStackedLayout"];
|
|
220
|
+
chartGaugeSize?: DslDocument["chartGaugeSize"];
|
|
221
|
+
ganttSize?: DslDocument["ganttSize"];
|
|
222
|
+
quadrantLabelPosition?: DslDocument["quadrantLabelPosition"];
|
|
223
|
+
quadrantSize?: DslDocument["quadrantSize"];
|
|
94
224
|
/**
|
|
95
225
|
* 図表の箱の上に出す小見出し (optional)。 記法の最上位 `eyebrow:` と同じ (#1247)。
|
|
96
226
|
*
|
|
97
|
-
* 効くのは図全体を 1 箱にする図種 (`
|
|
227
|
+
* 効くのは図全体を 1 箱にする図種 (`chart` / `funnel` / `tree` / `journey` /
|
|
98
228
|
* `quadrant` / `mind` / `gantt`) だけ。 箱ごとに分かれる図種では相手が決まらないため、
|
|
99
229
|
* 組み立て側が知らせを出す。 そちらは `actors[].eyebrow` に書く。
|
|
100
230
|
*/
|
|
@@ -108,6 +238,19 @@ export interface DragonJson {
|
|
|
108
238
|
* 他の図種には軸が無いため、書かれていたら組み立て側が知らせる。
|
|
109
239
|
*/
|
|
110
240
|
axes?: JsonAxes;
|
|
241
|
+
/**
|
|
242
|
+
* 2 軸で仕分ける図の区画の名前 (#2667)。 記法の `regions:` と同じ。
|
|
243
|
+
*
|
|
244
|
+
* 鍵は項目の欄と同じ 4 語 (`左上` / `右上` / `左下` / `右下`)。 別の言い方を足すと
|
|
245
|
+
* 同じ区画を 2 通りで呼ぶことになる。
|
|
246
|
+
*/
|
|
247
|
+
regions?: JsonRegions;
|
|
248
|
+
/** 図の凡例の字の大きさ。 */
|
|
249
|
+
legendFontSize?: number;
|
|
250
|
+
legendOffset?: DslDocument["legendOffset"];
|
|
251
|
+
/** 段の箱で全意匠に共通して使う見出しの寸法。 */
|
|
252
|
+
stageHeader?: DslStageHeader;
|
|
253
|
+
stageLayout?: DslDocument["stageLayout"];
|
|
111
254
|
/** 登場人物 (必須): 文字列 or { name, kind, ... } object */
|
|
112
255
|
actors: (string | JsonActor)[];
|
|
113
256
|
/** flow step 配列 (必須): { from, to, label, ... } */
|
|
@@ -184,6 +327,8 @@ export interface DragonJson {
|
|
|
184
327
|
x?: number;
|
|
185
328
|
width?: number;
|
|
186
329
|
label?: string;
|
|
330
|
+
/** 路線図の担当の名札で、名前の下へ出す補足。 */
|
|
331
|
+
subtitle?: string;
|
|
187
332
|
contain?: boolean;
|
|
188
333
|
lifeline?: boolean;
|
|
189
334
|
/**
|
|
@@ -209,16 +354,16 @@ export interface DragonJson {
|
|
|
209
354
|
relations?: RelationFocus;
|
|
210
355
|
/** 図の並ぶ向き (#1494)。 記法の最上位 `direction:` と同じ。 JSON は英語の語で書く */
|
|
211
356
|
direction?: "vertical" | "horizontal";
|
|
212
|
-
/**
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
357
|
+
/** 箱を並べ替える軸 (#2655)。 記法の最上位 `order:` と同じ。 JSON は英語の語で書く */
|
|
358
|
+
order?: "kind";
|
|
359
|
+
/** 図種ごとに選ぶ形 (#2657 / #2797)。 記法の最上位 `shape:` と同じ */
|
|
360
|
+
shape?: DslDiagramShape;
|
|
361
|
+
/** 図の意匠 (#1553 / #2790)。 記法の最上位 `theme:` と同じ。 */
|
|
362
|
+
theme?: string;
|
|
363
|
+
/** `theme` の別名。 両方書いた時は `theme` を使う。 */
|
|
364
|
+
palette?: string;
|
|
365
|
+
/** 図の下へ置く凡例。 印は記法と同じ英語名または和名で書く。 */
|
|
366
|
+
legend?: CdlLegendItem[];
|
|
222
367
|
}
|
|
223
368
|
|
|
224
369
|
/**
|
|
@@ -276,6 +421,22 @@ export interface JsonActor {
|
|
|
276
421
|
*/
|
|
277
422
|
kind?: NodeKind | (string & {});
|
|
278
423
|
subtitle?: string;
|
|
424
|
+
kindForm?: "icon";
|
|
425
|
+
icon?: DslActor["icon"];
|
|
426
|
+
emptyRowsNote?: string;
|
|
427
|
+
titleAlign?: "center";
|
|
428
|
+
rowRules?: boolean;
|
|
429
|
+
rowStripe?: boolean;
|
|
430
|
+
rowLayout?: DslActor["rowLayout"];
|
|
431
|
+
rowDividerForm?: DslActor["rowDividerForm"];
|
|
432
|
+
rowWell?: DslActor["rowWell"];
|
|
433
|
+
rowMarkForm?: "tone";
|
|
434
|
+
nestIn?: string;
|
|
435
|
+
figureCard?: DslActor["figureCard"];
|
|
436
|
+
stationNamePosition?: "bottom";
|
|
437
|
+
subtitlePlacement?: "right";
|
|
438
|
+
titleFontSize?: number;
|
|
439
|
+
markGap?: number;
|
|
279
440
|
eyebrow?: string;
|
|
280
441
|
value?: string;
|
|
281
442
|
/**
|
|
@@ -286,6 +447,8 @@ export interface JsonActor {
|
|
|
286
447
|
*/
|
|
287
448
|
previous?: string;
|
|
288
449
|
rows?: string[];
|
|
450
|
+
/** 段階ごとの箱で、この箱を入れる段階。 記法の `stage` / `段階` と同じ (#2797)。 */
|
|
451
|
+
stage?: string;
|
|
289
452
|
lane?: string;
|
|
290
453
|
stack?: number;
|
|
291
454
|
initial?: boolean;
|
|
@@ -341,14 +504,22 @@ export interface JsonActor {
|
|
|
341
504
|
* (記法側と同じ扱い)。
|
|
342
505
|
*/
|
|
343
506
|
end?: string;
|
|
507
|
+
startLabel?: string;
|
|
508
|
+
milestone?: boolean;
|
|
509
|
+
emphasis?: "primary";
|
|
510
|
+
/** 四象限の座標 `[x, y]`。 */
|
|
511
|
+
at?: [number | string, number | string];
|
|
512
|
+
/** `at` の和名。両方あれば `at` を採る。 */
|
|
513
|
+
点の位置?: [number | string, number | string];
|
|
344
514
|
/**
|
|
345
|
-
*
|
|
515
|
+
* ユーザージャーニー (`type: journey`) で、その段階が起きる場所 (#1294)。 記法の `touchpoint:` と同じ。
|
|
346
516
|
*/
|
|
347
517
|
touchpoint?: string;
|
|
348
518
|
/**
|
|
349
|
-
*
|
|
519
|
+
* ユーザージャーニー (`type: journey`) で、その段階の改善の余地 (#1294)。 記法の `opportunity:` と同じ。
|
|
350
520
|
*/
|
|
351
521
|
opportunity?: string;
|
|
522
|
+
opportunityPosition?: DslActor["opportunityPosition"];
|
|
352
523
|
/**
|
|
353
524
|
* 箱を置く絶対座標と大きさ (#1294)。 記法の `posX:` / `posY:` / `posW:` / `posH:` と同じ。
|
|
354
525
|
*
|
|
@@ -377,13 +548,21 @@ export interface JsonActor {
|
|
|
377
548
|
*/
|
|
378
549
|
phase?: boolean;
|
|
379
550
|
/** 行頭の印 (#1466)。 行と対で読む。 語の意味は図の種類が決める */
|
|
380
|
-
marks?: string
|
|
551
|
+
marks?: Array<string | { mark: string; tone?: "primary" | "muted" }>;
|
|
381
552
|
}
|
|
382
553
|
|
|
383
554
|
/** 2 軸で仕分ける図の軸の名前 (#1294)。 記法の `axes:` と同じ形 */
|
|
384
555
|
export interface JsonAxes {
|
|
385
|
-
x?: { left?: string; right?: string };
|
|
386
|
-
y?: { bottom?: string; top?: string };
|
|
556
|
+
x?: { left?: string; right?: string; label?: never; direction?: never } | { label: string; direction: true };
|
|
557
|
+
y?: { bottom?: string; top?: string; label?: never; direction?: never } | { label: string; direction: true };
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** 2 軸で仕分ける図の区画の名前 (#2667)。 記法の `regions:` と同じ形 */
|
|
561
|
+
export interface JsonRegions {
|
|
562
|
+
左上?: string;
|
|
563
|
+
右上?: string;
|
|
564
|
+
左下?: string;
|
|
565
|
+
右下?: string;
|
|
387
566
|
}
|
|
388
567
|
|
|
389
568
|
export interface JsonStep {
|
|
@@ -393,6 +572,8 @@ export interface JsonStep {
|
|
|
393
572
|
sub?: string;
|
|
394
573
|
/** 矢印がどの辺から出るか (#1385)。 記法の `side:` と同じ */
|
|
395
574
|
side?: "top" | "right" | "bottom" | "left";
|
|
575
|
+
fromSide?: "top" | "right" | "bottom" | "left";
|
|
576
|
+
toSide?: "top" | "right" | "bottom" | "left";
|
|
396
577
|
/**
|
|
397
578
|
* 矢印の先の形 (#1462)。 記法の `{ head: triangle }` と同じ。
|
|
398
579
|
*
|
|
@@ -404,7 +585,7 @@ export interface JsonStep {
|
|
|
404
585
|
tailHead?: EdgeHead;
|
|
405
586
|
headFill?: EdgeHeadFill;
|
|
406
587
|
tailHeadFill?: EdgeHeadFill;
|
|
407
|
-
/**
|
|
588
|
+
/** UML 関係の出どころ側の多重度 (#1771 / #2783)。記法の `{ tailSub: "1" }` と同じ */
|
|
408
589
|
tailSub?: string;
|
|
409
590
|
/**
|
|
410
591
|
* 辺の役目 (cdl#618)。 記法の `{ role: main }` と同じ。
|
|
@@ -414,7 +595,7 @@ export interface JsonStep {
|
|
|
414
595
|
role?: "main";
|
|
415
596
|
/** 名前の下地を敷くか (cdl#618)。 記法の `{ labelPlate: false }` と同じ。 既定は敷く */
|
|
416
597
|
labelPlate?: boolean;
|
|
417
|
-
/**
|
|
598
|
+
/** record の UML 関係の語 (#1466 / #2783)。書くと端の形 / 塗り / 線種がまとめて決まる */
|
|
418
599
|
relation?: ClassRelationType;
|
|
419
600
|
/** 順序図の言づての種類 (#1466)。 `call` / `return` / `fire` */
|
|
420
601
|
kind?: SequenceMessageKind;
|
|
@@ -436,6 +617,13 @@ export interface JsonStep {
|
|
|
436
617
|
dashOffsetBind?: string;
|
|
437
618
|
labelOffsetX?: number;
|
|
438
619
|
labelOffsetY?: number;
|
|
620
|
+
posX1?: number;
|
|
621
|
+
posY1?: number;
|
|
622
|
+
posX2?: number;
|
|
623
|
+
posY2?: number;
|
|
624
|
+
waypoints?: DslStep["waypoints"];
|
|
625
|
+
labelCollision?: DslStep["labelCollision"];
|
|
626
|
+
labelSize?: DslStep["labelSize"];
|
|
439
627
|
/** true で説明文を矢印の線の上に重ねる。 分岐図の条件ラベル用。 */
|
|
440
628
|
overlay?: boolean;
|
|
441
629
|
/**
|
|
@@ -459,6 +647,13 @@ export interface JsonPhase {
|
|
|
459
647
|
focus?: string[];
|
|
460
648
|
/** body 説明文 */
|
|
461
649
|
body?: string;
|
|
650
|
+
/**
|
|
651
|
+
* 説明文の別名 (#2621)。 記法が `description:` を受けるので JSON でも受ける。
|
|
652
|
+
*
|
|
653
|
+
* **正の語は `body`**。 両方書いた段は `body` を採る = 正の語を優先すると決めておくと、
|
|
654
|
+
* 読み手が「どちらが効くのか」 を実装を読まずに判断できる。
|
|
655
|
+
*/
|
|
656
|
+
description?: string;
|
|
462
657
|
/** badge label */
|
|
463
658
|
badge?: string;
|
|
464
659
|
/**
|
|
@@ -523,6 +718,13 @@ const VALID_KIND_SET: ReadonlySet<string> = NODE_KIND_VALID;
|
|
|
523
718
|
*/
|
|
524
719
|
const VALID_PRESETS: readonly PresetType[] = [...PRESET_TYPES];
|
|
525
720
|
|
|
721
|
+
/**
|
|
722
|
+
* 受け付ける図種の別名 (#2655)。 **記法側と同じ表を使う** (`v05/parser.ts` の `TYPE_ALIASES`)。
|
|
723
|
+
*
|
|
724
|
+
* 写すと片方だけ古くなる。 図種の一覧を 1 か所に寄せたのと同じ理由。
|
|
725
|
+
*/
|
|
726
|
+
const VALID_ALIASES: readonly string[] = [...TYPE_ALIASES.keys()];
|
|
727
|
+
|
|
526
728
|
/**
|
|
527
729
|
* 受け付ける項目の一覧 (#1295)。 **知らない項目を誤りにする判定と、公開 schema との
|
|
528
730
|
* 突き合わせが、どちらもここを見る**。
|
|
@@ -540,8 +742,70 @@ export const ACCEPTED_KEYS = {
|
|
|
540
742
|
root: [
|
|
541
743
|
"title",
|
|
542
744
|
"type",
|
|
745
|
+
"form",
|
|
746
|
+
"見せ方",
|
|
747
|
+
"ticks",
|
|
748
|
+
"目盛り",
|
|
749
|
+
"ganttToday",
|
|
750
|
+
"ganttTickLabels",
|
|
751
|
+
"ganttBarEnd",
|
|
752
|
+
"ganttBarThickness",
|
|
753
|
+
"chartSeriesSkipMuted",
|
|
754
|
+
"chartLineSeries",
|
|
755
|
+
"chartPieCenterLabel",
|
|
756
|
+
"chartPieTableColumns",
|
|
757
|
+
"chartPieRingWidth",
|
|
758
|
+
"chartSlopePeriods",
|
|
759
|
+
"chartSlopeEmphasisIds",
|
|
760
|
+
"chartSlopeUnit",
|
|
761
|
+
"chartStackedPeriods",
|
|
762
|
+
"chartStackedRateId",
|
|
763
|
+
"chartStackedLegendPosition",
|
|
764
|
+
"chartGaugeValue",
|
|
765
|
+
"chartWaffleLegendPosition",
|
|
766
|
+
"funnelForm",
|
|
767
|
+
"funnelRate",
|
|
768
|
+
"quadrantPointLabelSide",
|
|
769
|
+
"treeNodeForm",
|
|
770
|
+
"treeSize",
|
|
771
|
+
"treeEdgeTone",
|
|
772
|
+
"treeEdgeHead",
|
|
773
|
+
"mindForm",
|
|
774
|
+
"mindSize",
|
|
775
|
+
"edgeEndGap",
|
|
776
|
+
"journeyForm",
|
|
777
|
+
"journeyLineForm",
|
|
778
|
+
"journeyLabels",
|
|
779
|
+
"journeySize",
|
|
780
|
+
"sequenceActorForm",
|
|
781
|
+
"sequenceLabelForm",
|
|
782
|
+
"sequenceReturnHead",
|
|
783
|
+
"sequenceKindLabels",
|
|
784
|
+
"sequenceRowGap",
|
|
785
|
+
"sequenceGutter",
|
|
786
|
+
"sequenceBandWidth",
|
|
787
|
+
"sequenceSelfLoop",
|
|
788
|
+
"figureCard",
|
|
789
|
+
"figureSize",
|
|
790
|
+
"terminalDecoration",
|
|
791
|
+
"cornerForm",
|
|
792
|
+
"figureKindLabel",
|
|
793
|
+
"figureInset",
|
|
794
|
+
"chartBarSize",
|
|
795
|
+
"chartBarShape",
|
|
796
|
+
"chartLineSize",
|
|
797
|
+
"chartPieSize",
|
|
798
|
+
"chartWaffleSize",
|
|
799
|
+
"chartSlopeLabelTemplate",
|
|
800
|
+
"chartSlopeSize",
|
|
801
|
+
"chartStackedLayout",
|
|
802
|
+
"chartGaugeSize",
|
|
803
|
+
"ganttSize",
|
|
804
|
+
"quadrantLabelPosition",
|
|
805
|
+
"quadrantSize",
|
|
543
806
|
"eyebrow",
|
|
544
807
|
"axes",
|
|
808
|
+
"regions",
|
|
545
809
|
"actors",
|
|
546
810
|
"flow",
|
|
547
811
|
"states",
|
|
@@ -567,18 +831,45 @@ export const ACCEPTED_KEYS = {
|
|
|
567
831
|
"relations",
|
|
568
832
|
// 図の並ぶ向き (#1494)
|
|
569
833
|
"direction",
|
|
570
|
-
//
|
|
834
|
+
// 箱を並べ替える軸 (#2655)
|
|
835
|
+
"order",
|
|
836
|
+
// 数を描く図の形 (#2657)
|
|
837
|
+
"shape",
|
|
838
|
+
// 図の意匠とその別名 (#1553 / #2790)
|
|
839
|
+
"theme",
|
|
571
840
|
"palette",
|
|
841
|
+
"legend",
|
|
842
|
+
"legendFontSize",
|
|
843
|
+
"legendOffset",
|
|
844
|
+
"stageHeader",
|
|
845
|
+
"stageLayout",
|
|
572
846
|
],
|
|
573
847
|
actor: [
|
|
574
848
|
"name",
|
|
575
849
|
"kind",
|
|
576
850
|
"subtitle",
|
|
851
|
+
"kindForm",
|
|
852
|
+
"icon",
|
|
853
|
+
"emptyRowsNote",
|
|
854
|
+
"titleAlign",
|
|
855
|
+
"rowRules",
|
|
856
|
+
"rowStripe",
|
|
857
|
+
"rowLayout",
|
|
858
|
+
"rowDividerForm",
|
|
859
|
+
"rowWell",
|
|
860
|
+
"rowMarkForm",
|
|
861
|
+
"nestIn",
|
|
862
|
+
"figureCard",
|
|
863
|
+
"stationNamePosition",
|
|
864
|
+
"subtitlePlacement",
|
|
865
|
+
"titleFontSize",
|
|
866
|
+
"markGap",
|
|
577
867
|
"eyebrow",
|
|
578
868
|
"value",
|
|
579
869
|
// 前の時点の値 (#1450)。 内訳の変化を帯で示す図と、値 1 つを大きく示す図が読む
|
|
580
870
|
"previous",
|
|
581
871
|
"rows",
|
|
872
|
+
"stage",
|
|
582
873
|
"lane",
|
|
583
874
|
"stack",
|
|
584
875
|
"initial",
|
|
@@ -587,8 +878,14 @@ export const ACCEPTED_KEYS = {
|
|
|
587
878
|
"color",
|
|
588
879
|
"owner",
|
|
589
880
|
"end",
|
|
881
|
+
"startLabel",
|
|
882
|
+
"milestone",
|
|
883
|
+
"emphasis",
|
|
884
|
+
"at",
|
|
885
|
+
"点の位置",
|
|
590
886
|
"touchpoint",
|
|
591
887
|
"opportunity",
|
|
888
|
+
"opportunityPosition",
|
|
592
889
|
"posX",
|
|
593
890
|
"posY",
|
|
594
891
|
"posW",
|
|
@@ -622,13 +919,15 @@ export const ACCEPTED_KEYS = {
|
|
|
622
919
|
"sub",
|
|
623
920
|
// 矢印がどの辺から出るか (#1385)
|
|
624
921
|
"side",
|
|
922
|
+
"fromSide",
|
|
923
|
+
"toSide",
|
|
625
924
|
// 矢印の先の形 (#1462)
|
|
626
925
|
"head",
|
|
627
926
|
// 端の印の残り 3 欄と、関係の語 / 言づての種類 (#1466)
|
|
628
927
|
"tailHead",
|
|
629
928
|
"headFill",
|
|
630
929
|
"tailHeadFill",
|
|
631
|
-
//
|
|
930
|
+
// UML 関係の出どころ側の多重度 (#1771 / #2783)
|
|
632
931
|
"tailSub",
|
|
633
932
|
"relation",
|
|
634
933
|
// 辺の役目と名前の下地 (cdl#618)
|
|
@@ -645,19 +944,29 @@ export const ACCEPTED_KEYS = {
|
|
|
645
944
|
"dashOffsetBind",
|
|
646
945
|
"labelOffsetX",
|
|
647
946
|
"labelOffsetY",
|
|
947
|
+
"posX1",
|
|
948
|
+
"posY1",
|
|
949
|
+
"posX2",
|
|
950
|
+
"posY2",
|
|
951
|
+
"waypoints",
|
|
952
|
+
"labelCollision",
|
|
953
|
+
"labelSize",
|
|
648
954
|
"overlay",
|
|
649
955
|
"pos",
|
|
650
956
|
// 部品の端で繋ぐ要素の id (#1979)
|
|
651
957
|
"fromPartNode",
|
|
652
958
|
"toPartNode",
|
|
653
959
|
],
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
960
|
+
// 説明の語は `PHASE_BODY_KEYS` から導く (#2621)。 書き写すと記法の入口とずれる
|
|
961
|
+
phase: ["step", "duration", "focus", ...PHASE_BODY_KEYS, "badge", "tween", "set", "draw", "drawRatio"],
|
|
962
|
+
viewport: ["width", "height", "contentOffsetX", "scale", "laneWidth", "gap", "laneGap", "nodeGap", "labelMargin"],
|
|
963
|
+
lane: ["x", "width", "label", "subtitle", "contain", "lifeline", "pos"],
|
|
657
964
|
group: ["label", "lanes"],
|
|
658
965
|
axes: ["x", "y"],
|
|
659
|
-
axesX: ["left", "right"],
|
|
660
|
-
axesY: ["bottom", "top"],
|
|
966
|
+
axesX: ["left", "right", "label", "direction"],
|
|
967
|
+
axesY: ["bottom", "top", "label", "direction"],
|
|
968
|
+
// 区画を指す語は項目の欄と同じ 4 語 (#2667)
|
|
969
|
+
regions: ["左上", "右上", "左下", "右下"],
|
|
661
970
|
layoutPos: ["x", "y"],
|
|
662
971
|
// 位置を他の要素からの相対で書く指定 (#2039)。 間隔は書かなくてよい
|
|
663
972
|
posRel: ["anchor", "dir", "gap"],
|
|
@@ -706,6 +1015,14 @@ export type 欄の型 =
|
|
|
706
1015
|
| "必須の向き"
|
|
707
1016
|
| "色か色番号"
|
|
708
1017
|
| "描くもの"
|
|
1018
|
+
| "意匠"
|
|
1019
|
+
| "図の形"
|
|
1020
|
+
| "ガントの帯の終わり"
|
|
1021
|
+
| "ガントの帯の厚み"
|
|
1022
|
+
| "札の角"
|
|
1023
|
+
| "四象限の区画名位置"
|
|
1024
|
+
| "線札の衝突"
|
|
1025
|
+
| "主役"
|
|
709
1026
|
| "必須の図種"
|
|
710
1027
|
| "object"
|
|
711
1028
|
| "並び"
|
|
@@ -716,8 +1033,70 @@ export const 欄の型表 = {
|
|
|
716
1033
|
root: {
|
|
717
1034
|
title: "必須の非空文字列",
|
|
718
1035
|
type: "必須の図種",
|
|
1036
|
+
form: "非空の文字列",
|
|
1037
|
+
見せ方: "非空の文字列",
|
|
1038
|
+
ticks: "文字列の並び",
|
|
1039
|
+
目盛り: "文字列の並び",
|
|
1040
|
+
ganttToday: "object",
|
|
1041
|
+
ganttTickLabels: "文字列の並び",
|
|
1042
|
+
ganttBarEnd: "ガントの帯の終わり",
|
|
1043
|
+
ganttBarThickness: "ガントの帯の厚み",
|
|
1044
|
+
chartSeriesSkipMuted: "真偽",
|
|
1045
|
+
chartLineSeries: "並び",
|
|
1046
|
+
chartPieCenterLabel: "文字列",
|
|
1047
|
+
chartPieTableColumns: "文字列",
|
|
1048
|
+
chartPieRingWidth: "文字列",
|
|
1049
|
+
chartSlopePeriods: "文字列の並び",
|
|
1050
|
+
chartSlopeEmphasisIds: "文字列の並び",
|
|
1051
|
+
chartSlopeUnit: "文字列",
|
|
1052
|
+
chartStackedPeriods: "並び",
|
|
1053
|
+
chartStackedRateId: "文字列",
|
|
1054
|
+
chartStackedLegendPosition: "文字列",
|
|
1055
|
+
chartGaugeValue: "object",
|
|
1056
|
+
chartWaffleLegendPosition: "文字列",
|
|
1057
|
+
funnelForm: "文字列",
|
|
1058
|
+
funnelRate: "文字列",
|
|
1059
|
+
quadrantPointLabelSide: "文字列",
|
|
1060
|
+
treeNodeForm: "文字列",
|
|
1061
|
+
treeSize: "object",
|
|
1062
|
+
treeEdgeTone: "文字列",
|
|
1063
|
+
treeEdgeHead: "文字列",
|
|
1064
|
+
mindForm: "文字列",
|
|
1065
|
+
mindSize: "object",
|
|
1066
|
+
edgeEndGap: "数",
|
|
1067
|
+
journeyForm: "文字列",
|
|
1068
|
+
journeyLineForm: "文字列",
|
|
1069
|
+
journeyLabels: "object",
|
|
1070
|
+
journeySize: "object",
|
|
1071
|
+
sequenceActorForm: "非空の文字列",
|
|
1072
|
+
sequenceLabelForm: "非空の文字列",
|
|
1073
|
+
sequenceReturnHead: "非空の文字列",
|
|
1074
|
+
sequenceKindLabels: "object",
|
|
1075
|
+
sequenceRowGap: "数",
|
|
1076
|
+
sequenceGutter: "数",
|
|
1077
|
+
sequenceBandWidth: "数",
|
|
1078
|
+
sequenceSelfLoop: "object",
|
|
1079
|
+
figureCard: "object",
|
|
1080
|
+
figureSize: "object",
|
|
1081
|
+
terminalDecoration: "object",
|
|
1082
|
+
cornerForm: "札の角",
|
|
1083
|
+
figureKindLabel: "object",
|
|
1084
|
+
figureInset: "object",
|
|
1085
|
+
chartBarSize: "object",
|
|
1086
|
+
chartBarShape: "非空の文字列",
|
|
1087
|
+
chartLineSize: "object",
|
|
1088
|
+
chartPieSize: "object",
|
|
1089
|
+
chartWaffleSize: "object",
|
|
1090
|
+
chartSlopeLabelTemplate: "object",
|
|
1091
|
+
chartSlopeSize: "object",
|
|
1092
|
+
chartStackedLayout: "object",
|
|
1093
|
+
chartGaugeSize: "object",
|
|
1094
|
+
ganttSize: "object",
|
|
1095
|
+
quadrantLabelPosition: "四象限の区画名位置",
|
|
1096
|
+
quadrantSize: "object",
|
|
719
1097
|
eyebrow: "文字列",
|
|
720
1098
|
axes: "object",
|
|
1099
|
+
regions: "object",
|
|
721
1100
|
actors: "必須の非空の並び",
|
|
722
1101
|
flow: "必須の並び",
|
|
723
1102
|
states: "object",
|
|
@@ -743,17 +1122,44 @@ export const 欄の型表 = {
|
|
|
743
1122
|
relations: "非空の文字列",
|
|
744
1123
|
// 図の並ぶ向き (#1494)
|
|
745
1124
|
direction: "非空の文字列",
|
|
746
|
-
//
|
|
747
|
-
|
|
1125
|
+
// 箱を並べ替える軸 (#2655)
|
|
1126
|
+
order: "非空の文字列",
|
|
1127
|
+
// 図種ごとの形 (#2657 / #2797)
|
|
1128
|
+
shape: "図の形",
|
|
1129
|
+
// 図の意匠とその別名 (#1553 / #2790)
|
|
1130
|
+
theme: "意匠",
|
|
1131
|
+
palette: "意匠",
|
|
1132
|
+
legend: "並び",
|
|
1133
|
+
legendFontSize: "数",
|
|
1134
|
+
legendOffset: "object",
|
|
1135
|
+
stageHeader: "object",
|
|
1136
|
+
stageLayout: "object",
|
|
748
1137
|
},
|
|
749
1138
|
actor: {
|
|
750
1139
|
name: "必須の非空文字列",
|
|
751
1140
|
kind: "非空の文字列",
|
|
752
1141
|
subtitle: "文字列",
|
|
1142
|
+
kindForm: "非空の文字列",
|
|
1143
|
+
icon: "非空の文字列",
|
|
1144
|
+
emptyRowsNote: "文字列",
|
|
1145
|
+
titleAlign: "非空の文字列",
|
|
1146
|
+
rowRules: "真偽",
|
|
1147
|
+
rowStripe: "真偽",
|
|
1148
|
+
rowLayout: "object",
|
|
1149
|
+
rowDividerForm: "非空の文字列",
|
|
1150
|
+
rowWell: "object",
|
|
1151
|
+
rowMarkForm: "非空の文字列",
|
|
1152
|
+
nestIn: "非空の文字列",
|
|
1153
|
+
figureCard: "object",
|
|
1154
|
+
stationNamePosition: "非空の文字列",
|
|
1155
|
+
subtitlePlacement: "非空の文字列",
|
|
1156
|
+
titleFontSize: "数",
|
|
1157
|
+
markGap: "数",
|
|
753
1158
|
eyebrow: "文字列",
|
|
754
1159
|
value: "文字列",
|
|
755
1160
|
previous: "文字列",
|
|
756
1161
|
rows: "文字列の並び",
|
|
1162
|
+
stage: "非空の文字列",
|
|
757
1163
|
lane: "文字列",
|
|
758
1164
|
stack: "数",
|
|
759
1165
|
initial: "真偽",
|
|
@@ -762,8 +1168,14 @@ export const 欄の型表 = {
|
|
|
762
1168
|
color: "色か色番号",
|
|
763
1169
|
owner: "文字列",
|
|
764
1170
|
end: "文字列",
|
|
1171
|
+
startLabel: "文字列",
|
|
1172
|
+
milestone: "真偽",
|
|
1173
|
+
emphasis: "主役",
|
|
1174
|
+
at: "並び",
|
|
1175
|
+
点の位置: "並び",
|
|
765
1176
|
touchpoint: "文字列",
|
|
766
1177
|
opportunity: "文字列",
|
|
1178
|
+
opportunityPosition: "文字列",
|
|
767
1179
|
posX: "数",
|
|
768
1180
|
posY: "数",
|
|
769
1181
|
posW: "数",
|
|
@@ -784,7 +1196,7 @@ export const 欄の型表 = {
|
|
|
784
1196
|
renderOffsetX: "数か文字列",
|
|
785
1197
|
renderOffsetY: "数か文字列",
|
|
786
1198
|
// 行頭の印 (#1466)
|
|
787
|
-
marks: "
|
|
1199
|
+
marks: "並び",
|
|
788
1200
|
// 相対で置く指定 (#2039)。 中身の形は `validateRelativePos` が見る
|
|
789
1201
|
posRel: "object",
|
|
790
1202
|
// 見本が持つ段を使うか (#2125)
|
|
@@ -797,12 +1209,14 @@ export const 欄の型表 = {
|
|
|
797
1209
|
sub: "文字列",
|
|
798
1210
|
// 矢印がどの辺から出るか (#1385)
|
|
799
1211
|
side: "辺",
|
|
1212
|
+
fromSide: "辺",
|
|
1213
|
+
toSide: "辺",
|
|
800
1214
|
head: "端の形",
|
|
801
1215
|
// 端の印の残り 3 欄と、関係の語 / 言づての種類 (#1466)
|
|
802
1216
|
tailHead: "端の形",
|
|
803
1217
|
headFill: "非空の文字列",
|
|
804
1218
|
tailHeadFill: "非空の文字列",
|
|
805
|
-
//
|
|
1219
|
+
// UML 関係の出どころ側の多重度 (#1771 / #2783)
|
|
806
1220
|
tailSub: "文字列",
|
|
807
1221
|
relation: "非空の文字列",
|
|
808
1222
|
// 辺の役目と名前の下地 (cdl#618)
|
|
@@ -819,6 +1233,13 @@ export const 欄の型表 = {
|
|
|
819
1233
|
dashOffsetBind: "非空の文字列",
|
|
820
1234
|
labelOffsetX: "数",
|
|
821
1235
|
labelOffsetY: "数",
|
|
1236
|
+
posX1: "数",
|
|
1237
|
+
posY1: "数",
|
|
1238
|
+
posX2: "数",
|
|
1239
|
+
posY2: "数",
|
|
1240
|
+
waypoints: "並び",
|
|
1241
|
+
labelCollision: "線札の衝突",
|
|
1242
|
+
labelSize: "object",
|
|
822
1243
|
overlay: "真偽",
|
|
823
1244
|
pos: "object",
|
|
824
1245
|
// 部品の端で繋ぐ要素の id (#1979)。 空の id は要素を指せないので受けない
|
|
@@ -829,7 +1250,9 @@ export const 欄の型表 = {
|
|
|
829
1250
|
step: "必須の非空文字列",
|
|
830
1251
|
duration: "数",
|
|
831
1252
|
focus: "文字列の並び",
|
|
1253
|
+
// 説明は正の語と別名のどちらでも同じ型。 表の欄は `ACCEPTED_KEYS.phase` と揃える (#2621)
|
|
832
1254
|
body: "文字列",
|
|
1255
|
+
description: "文字列",
|
|
833
1256
|
badge: "文字列",
|
|
834
1257
|
tween: "object",
|
|
835
1258
|
set: "object",
|
|
@@ -839,6 +1262,7 @@ export const 欄の型表 = {
|
|
|
839
1262
|
viewport: {
|
|
840
1263
|
width: "数",
|
|
841
1264
|
height: "数",
|
|
1265
|
+
contentOffsetX: "数",
|
|
842
1266
|
scale: "数",
|
|
843
1267
|
laneWidth: "数",
|
|
844
1268
|
gap: "数",
|
|
@@ -850,14 +1274,16 @@ export const 欄の型表 = {
|
|
|
850
1274
|
x: "数",
|
|
851
1275
|
width: "数",
|
|
852
1276
|
label: "文字列",
|
|
1277
|
+
subtitle: "文字列",
|
|
853
1278
|
contain: "真偽",
|
|
854
1279
|
lifeline: "真偽",
|
|
855
1280
|
pos: "object",
|
|
856
1281
|
},
|
|
857
1282
|
group: { label: "文字列", lanes: "文字列の並び" },
|
|
858
1283
|
axes: { x: "object", y: "object" },
|
|
859
|
-
axesX: { left: "文字列", right: "文字列" },
|
|
860
|
-
axesY: { bottom: "文字列", top: "文字列" },
|
|
1284
|
+
axesX: { left: "文字列", right: "文字列", label: "文字列", direction: "真偽" },
|
|
1285
|
+
axesY: { bottom: "文字列", top: "文字列", label: "文字列", direction: "真偽" },
|
|
1286
|
+
regions: { 左上: "文字列", 右上: "文字列", 左下: "文字列", 右下: "文字列" },
|
|
861
1287
|
// 位置は書けば x と y の両方が要る。 片方だけでは寄せ幅が決まらない
|
|
862
1288
|
layoutPos: { x: "必須の数", y: "必須の数" },
|
|
863
1289
|
// 相対で置く指定 (#2039)。 相手と向きは必ず要り、間隔は書かなければ既定で置く
|
|
@@ -929,10 +1355,28 @@ export const 見本にしか効かない欄 = [
|
|
|
929
1355
|
*/
|
|
930
1356
|
export const 見本に効かない欄 = [
|
|
931
1357
|
"tone",
|
|
1358
|
+
"kindForm",
|
|
1359
|
+
"icon",
|
|
1360
|
+
"emptyRowsNote",
|
|
1361
|
+
"titleAlign",
|
|
1362
|
+
"rowRules",
|
|
1363
|
+
"rowStripe",
|
|
1364
|
+
"rowLayout",
|
|
1365
|
+
"rowDividerForm",
|
|
1366
|
+
"rowWell",
|
|
1367
|
+
"rowMarkForm",
|
|
1368
|
+
"nestIn",
|
|
1369
|
+
"figureCard",
|
|
1370
|
+
"at",
|
|
932
1371
|
"owner",
|
|
933
1372
|
"end",
|
|
1373
|
+
"startLabel",
|
|
1374
|
+
"milestone",
|
|
1375
|
+
"emphasis",
|
|
934
1376
|
"touchpoint",
|
|
935
1377
|
"opportunity",
|
|
1378
|
+
"opportunityPosition",
|
|
1379
|
+
"stage",
|
|
936
1380
|
// 箱の中に描く図形 (#1374)。 見本は自分の形を持つため、外から図形を差し替えられない
|
|
937
1381
|
"shape",
|
|
938
1382
|
// その箱を出すかどうかの条件 (#1381)。 見本は自分の出方を持つ
|
|
@@ -1031,6 +1475,36 @@ function 値を検査(
|
|
|
1031
1475
|
if (v === undefined) return;
|
|
1032
1476
|
if (typeof v !== "boolean") 型違い("true or false if present");
|
|
1033
1477
|
return;
|
|
1478
|
+
case "ガントの帯の終わり":
|
|
1479
|
+
if (v === undefined) return;
|
|
1480
|
+
if (v !== "position") 型違い('"position" if present');
|
|
1481
|
+
return;
|
|
1482
|
+
case "ガントの帯の厚み":
|
|
1483
|
+
if (v === undefined) return;
|
|
1484
|
+
if (v !== "thin") 型違い('"thin" if present');
|
|
1485
|
+
return;
|
|
1486
|
+
case "札の角":
|
|
1487
|
+
if (v === undefined) return;
|
|
1488
|
+
if (!(CORNER_FORM_VALUES as readonly unknown[]).includes(v)) {
|
|
1489
|
+
型違い(`one of: ${CORNER_FORM_VALUES.join(", ")}`);
|
|
1490
|
+
}
|
|
1491
|
+
return;
|
|
1492
|
+
case "四象限の区画名位置":
|
|
1493
|
+
if (v === undefined) return;
|
|
1494
|
+
if (!(QUADRANT_LABEL_POSITION_VALUES as readonly unknown[]).includes(v)) {
|
|
1495
|
+
型違い(`one of: ${QUADRANT_LABEL_POSITION_VALUES.join(", ")}`);
|
|
1496
|
+
}
|
|
1497
|
+
return;
|
|
1498
|
+
case "線札の衝突":
|
|
1499
|
+
if (v === undefined) return;
|
|
1500
|
+
if (!(LABEL_COLLISION_VALUES as readonly unknown[]).includes(v)) {
|
|
1501
|
+
型違い(`one of: ${LABEL_COLLISION_VALUES.join(", ")}`);
|
|
1502
|
+
}
|
|
1503
|
+
return;
|
|
1504
|
+
case "主役":
|
|
1505
|
+
if (v === undefined) return;
|
|
1506
|
+
if (v !== "primary") 型違い('"primary" if present');
|
|
1507
|
+
return;
|
|
1034
1508
|
case "色":
|
|
1035
1509
|
if (v === undefined) return;
|
|
1036
1510
|
if (typeof v !== "string") {
|
|
@@ -1100,6 +1574,28 @@ function 値を検査(
|
|
|
1100
1574
|
});
|
|
1101
1575
|
}
|
|
1102
1576
|
return;
|
|
1577
|
+
case "意匠":
|
|
1578
|
+
if (v === undefined) return;
|
|
1579
|
+
if (typeof v !== "string" || resolveTheme(v) === null) {
|
|
1580
|
+
errors.push({
|
|
1581
|
+
path,
|
|
1582
|
+
message: `${名前} must be one of: ${THEMES.join(", ")}`,
|
|
1583
|
+
hint: `使える語 = ${Object.keys(THEME_ALIAS).join(" / ")} (got ${
|
|
1584
|
+
typeof v === "string" ? JSON.stringify(v) : typeof v
|
|
1585
|
+
})`,
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
return;
|
|
1589
|
+
case "図の形":
|
|
1590
|
+
if (v === undefined) return;
|
|
1591
|
+
if (typeof v !== "string" || resolveDiagramShape(v) === null) {
|
|
1592
|
+
errors.push({
|
|
1593
|
+
path,
|
|
1594
|
+
message: `${名前} must be one of: ${Object.values(DIAGRAM_SHAPES).flat().join(", ")}`,
|
|
1595
|
+
hint: `使える語 = ${Object.values(DIAGRAM_SHAPES).flat().join(" / ")}`,
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
return;
|
|
1103
1599
|
case "色か色番号":
|
|
1104
1600
|
if (v === undefined) return;
|
|
1105
1601
|
if (typeof v !== "string") {
|
|
@@ -1126,10 +1622,13 @@ function 値を検査(
|
|
|
1126
1622
|
}
|
|
1127
1623
|
return;
|
|
1128
1624
|
case "必須の図種":
|
|
1129
|
-
if (
|
|
1625
|
+
if (
|
|
1626
|
+
typeof v !== "string" ||
|
|
1627
|
+
!(VALID_PRESETS.includes(v as PresetType) || VALID_ALIASES.includes(v))
|
|
1628
|
+
) {
|
|
1130
1629
|
errors.push({
|
|
1131
1630
|
path,
|
|
1132
|
-
message: `${名前} must be one of: ${VALID_PRESETS.join(", ")}`,
|
|
1631
|
+
message: `${名前} must be one of: ${[...VALID_PRESETS, ...VALID_ALIASES].join(", ")}`,
|
|
1133
1632
|
hint: typeof v === "string" ? `got "${v}"` : undefined,
|
|
1134
1633
|
});
|
|
1135
1634
|
}
|
|
@@ -1519,6 +2018,91 @@ function validateBands(v: unknown, errors: JsonDslError[]): void {
|
|
|
1519
2018
|
});
|
|
1520
2019
|
}
|
|
1521
2020
|
|
|
2021
|
+
const LEGEND_KEYS = ["mark", "text", "tone", "lineStyle", "align", "lead"] as const;
|
|
2022
|
+
const LEGEND_LINE_MARKS = ["line-triangle-hollow", "line-diamond-hollow", "line-diamond-solid"] as const;
|
|
2023
|
+
|
|
2024
|
+
/** JSON の凡例を、外側の並びから各項目の必須値まで検査する。 */
|
|
2025
|
+
function validateLegend(v: unknown, errors: JsonDslError[]): void {
|
|
2026
|
+
if (v === undefined) return;
|
|
2027
|
+
if (!Array.isArray(v)) {
|
|
2028
|
+
errors.push({ path: "$.legend", message: "legend must be an array" });
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
v.forEach((item, index) => {
|
|
2032
|
+
const path = `$.legend[${index}]`;
|
|
2033
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
2034
|
+
errors.push({ path, message: "legend item must be an object" });
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
const o = item as Record<string, unknown>;
|
|
2038
|
+
for (const key of Object.keys(o)) {
|
|
2039
|
+
if ((LEGEND_KEYS as readonly string[]).includes(key)) continue;
|
|
2040
|
+
errors.push({
|
|
2041
|
+
path: `${path}.${key}`,
|
|
2042
|
+
message: `unknown key "${key}"`,
|
|
2043
|
+
hint: `使える項目 = ${LEGEND_KEYS.join(", ")}`,
|
|
2044
|
+
});
|
|
2045
|
+
}
|
|
2046
|
+
const mark = typeof o.mark === "string" ? resolveLegendMark(o.mark) : undefined;
|
|
2047
|
+
if (o.mark !== undefined && (typeof o.mark !== "string" || mark === null)) {
|
|
2048
|
+
errors.push({ path: `${path}.mark`, message: "legend mark is not recognized" });
|
|
2049
|
+
}
|
|
2050
|
+
if (typeof o.text !== "string" || o.text.trim().length === 0) {
|
|
2051
|
+
errors.push({ path: `${path}.text`, message: "legend text must be a non-empty string" });
|
|
2052
|
+
}
|
|
2053
|
+
if (o.mark === undefined) {
|
|
2054
|
+
if (o.tone !== undefined || o.lineStyle !== undefined) errors.push({ path, message: "legend note cannot have tone or lineStyle" });
|
|
2055
|
+
if (o.align !== undefined && !(LEGEND_NOTE_ALIGN_VALUES as readonly unknown[]).includes(o.align)) errors.push({ path: `${path}.align`, message: `align must be ${LEGEND_NOTE_ALIGN_VALUES.join(" or ")}` });
|
|
2056
|
+
if (o.lead !== undefined && typeof o.lead !== "string") errors.push({ path: `${path}.lead`, message: "lead must be a string" });
|
|
2057
|
+
} else {
|
|
2058
|
+
if (o.align !== undefined || o.lead !== undefined) errors.push({ path, message: "marked legend item cannot have align or lead" });
|
|
2059
|
+
if (o.tone !== undefined && (typeof o.tone !== "string" || resolveTone(o.tone) === undefined)) errors.push({ path: `${path}.tone`, message: "tone is not recognized" });
|
|
2060
|
+
if (o.lineStyle !== undefined) {
|
|
2061
|
+
if (!(LEGEND_LINE_STYLE_VALUES as readonly unknown[]).includes(o.lineStyle)) errors.push({ path: `${path}.lineStyle`, message: `lineStyle must be ${LEGEND_LINE_STYLE_VALUES.join(" or ")}` });
|
|
2062
|
+
if (mark === undefined || mark === null || !(LEGEND_LINE_MARKS as readonly string[]).includes(mark)) errors.push({ path: `${path}.lineStyle`, message: "lineStyle requires a line-end legend mark" });
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
const STAGE_HEADER_KEYS = ["leftPad", "topPad", "numberSize", "gap", "nameSize", "bottomPad"] as const;
|
|
2069
|
+
const REQUIRED_STAGE_HEADER_KEYS = ["topPad", "numberSize", "gap", "nameSize", "bottomPad"] as const;
|
|
2070
|
+
|
|
2071
|
+
/** JSON の段の見出し寸法を、閉じた欄・必須の正数まで検査する。 */
|
|
2072
|
+
function validateStageHeader(v: unknown, errors: JsonDslError[]): void {
|
|
2073
|
+
if (v === undefined) return;
|
|
2074
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) {
|
|
2075
|
+
errors.push({ path: "$.stageHeader", message: "stageHeader must be an object" });
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
const path = "$.stageHeader";
|
|
2079
|
+
const o = v as Record<string, unknown>;
|
|
2080
|
+
for (const key of Object.keys(o)) {
|
|
2081
|
+
if ((STAGE_HEADER_KEYS as readonly string[]).includes(key)) continue;
|
|
2082
|
+
errors.push({
|
|
2083
|
+
path: `${path}.${key}`,
|
|
2084
|
+
message: `unknown key "${key}"`,
|
|
2085
|
+
hint: `使える項目 = ${STAGE_HEADER_KEYS.join(", ")}`,
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
2088
|
+
for (const key of REQUIRED_STAGE_HEADER_KEYS) {
|
|
2089
|
+
if (o[key] === undefined) {
|
|
2090
|
+
errors.push({ path: `${path}.${key}`, message: `${key} is required` });
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
for (const key of STAGE_HEADER_KEYS) {
|
|
2094
|
+
const value = o[key];
|
|
2095
|
+
if (value === undefined) continue;
|
|
2096
|
+
const zeroAllowed = key === "leftPad" || key === "topPad" || key === "gap" || key === "bottomPad";
|
|
2097
|
+
if (typeof value !== "number" || !Number.isFinite(value) || (zeroAllowed ? value < 0 : value <= 0)) {
|
|
2098
|
+
errors.push({
|
|
2099
|
+
path: `${path}.${key}`,
|
|
2100
|
+
message: `${key} must be ${zeroAllowed ? "a non-negative" : "a positive"} number`,
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
|
|
1522
2106
|
/**
|
|
1523
2107
|
* 値を見せる部品の並びを検査する (#1374)。
|
|
1524
2108
|
*
|
|
@@ -2019,7 +2603,448 @@ function validateAxes(v: unknown, errors: JsonDslError[]): void {
|
|
|
2019
2603
|
const o = 一方 as Record<string, unknown>;
|
|
2020
2604
|
checkUnknownKeys(o, 層の名[名], `$.axes.${名}`, errors);
|
|
2021
2605
|
表で検査(o, 層の名[名], `$.axes.${名}`, `axes.${名}`, errors);
|
|
2606
|
+
if (o.direction === true) {
|
|
2607
|
+
if (typeof o.label !== "string") {
|
|
2608
|
+
errors.push({ path: `$.axes.${名}.label`, message: `axes.${名}.label must be a string for a directional axis` });
|
|
2609
|
+
}
|
|
2610
|
+
} else if (o.label !== undefined || o.direction !== undefined) {
|
|
2611
|
+
errors.push({ path: `$.axes.${名}.direction`, message: `axes.${名}.direction must be true when label is used` });
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
/** #2854 で足した図表の複合欄と閉じた語を検査する。 */
|
|
2617
|
+
function validateChartPresentationFields(j: Record<string, unknown>, errors: JsonDslError[]): void {
|
|
2618
|
+
const enumField = (name: string, allowed: readonly string[]): void => {
|
|
2619
|
+
const value = j[name];
|
|
2620
|
+
if (value === undefined || typeof value !== "string") return;
|
|
2621
|
+
if (!allowed.includes(value)) {
|
|
2622
|
+
errors.push({ path: `$.${name}`, message: `${name} must be one of: ${allowed.join(", ")}` });
|
|
2623
|
+
}
|
|
2624
|
+
};
|
|
2625
|
+
enumField("chartPieTableColumns", CHART_PIE_TABLE_COLUMN_VALUES);
|
|
2626
|
+
enumField("chartPieRingWidth", CHART_PIE_RING_WIDTH_VALUES);
|
|
2627
|
+
enumField("chartStackedLegendPosition", CHART_STACKED_LEGEND_POSITION_VALUES);
|
|
2628
|
+
enumField("chartWaffleLegendPosition", CHART_WAFFLE_LEGEND_POSITION_VALUES);
|
|
2629
|
+
enumField("funnelForm", FUNNEL_FORM_VALUES);
|
|
2630
|
+
enumField("funnelRate", FUNNEL_RATE_VALUES);
|
|
2631
|
+
enumField("quadrantPointLabelSide", QUADRANT_POINT_LABEL_SIDE_VALUES);
|
|
2632
|
+
enumField("treeNodeForm", TREE_NODE_FORM_VALUES);
|
|
2633
|
+
enumField("treeEdgeTone", TREE_EDGE_TONE_VALUES);
|
|
2634
|
+
enumField("treeEdgeHead", TREE_EDGE_HEAD_VALUES);
|
|
2635
|
+
enumField("mindForm", MIND_FORM_VALUES);
|
|
2636
|
+
enumField("journeyForm", JOURNEY_FORM_VALUES);
|
|
2637
|
+
enumField("journeyLineForm", JOURNEY_LINE_FORM_VALUES);
|
|
2638
|
+
enumField("sequenceActorForm", SEQUENCE_ACTOR_FORM_VALUES);
|
|
2639
|
+
enumField("sequenceLabelForm", SEQUENCE_LABEL_FORM_VALUES);
|
|
2640
|
+
enumField("sequenceReturnHead", SEQUENCE_RETURN_HEAD_VALUES);
|
|
2641
|
+
enumField("cornerForm", CORNER_FORM_VALUES);
|
|
2642
|
+
enumField("chartBarShape", CHART_BAR_SHAPE_VALUES);
|
|
2643
|
+
enumField("quadrantLabelPosition", QUADRANT_LABEL_POSITION_VALUES);
|
|
2644
|
+
|
|
2645
|
+
const closedObject = (
|
|
2646
|
+
name: "journeyLabels" | "figureCard" | "figureSize" | "treeSize" | "mindSize" |
|
|
2647
|
+
"terminalDecoration" | "figureKindLabel" | "figureInset" | "journeySize" | "chartBarSize" |
|
|
2648
|
+
"chartLineSize" | "chartPieSize" | "chartWaffleSize" | "chartSlopeLabelTemplate" | "chartSlopeSize" |
|
|
2649
|
+
"chartStackedLayout" | "chartGaugeSize" | "ganttSize" | "quadrantSize" | "legendOffset" | "stageLayout",
|
|
2650
|
+
keys: readonly string[],
|
|
2651
|
+
): Record<string, unknown> | undefined => {
|
|
2652
|
+
const value = j[name];
|
|
2653
|
+
if (value === undefined) return undefined;
|
|
2654
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2655
|
+
errors.push({ path: `$.${name}`, message: `${name} must be an object` });
|
|
2656
|
+
return undefined;
|
|
2657
|
+
}
|
|
2658
|
+
const object = value as Record<string, unknown>;
|
|
2659
|
+
for (const key of Object.keys(object)) {
|
|
2660
|
+
if (!keys.includes(key)) errors.push({ path: `$.${name}.${key}`, message: `unknown key "${key}"` });
|
|
2661
|
+
}
|
|
2662
|
+
return object;
|
|
2663
|
+
};
|
|
2664
|
+
const labels = closedObject("journeyLabels", JOURNEY_LABEL_KEYS);
|
|
2665
|
+
if (labels !== undefined) {
|
|
2666
|
+
for (const key of JOURNEY_LABEL_KEYS) {
|
|
2667
|
+
if (typeof labels[key] !== "string" || labels[key].trim() === "") {
|
|
2668
|
+
errors.push({ path: `$.journeyLabels.${key}`, message: `${key} must be a non-empty string` });
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
if (j.sequenceKindLabels !== undefined) {
|
|
2673
|
+
if (!j.sequenceKindLabels || typeof j.sequenceKindLabels !== "object" || Array.isArray(j.sequenceKindLabels)) {
|
|
2674
|
+
errors.push({ path: "$.sequenceKindLabels", message: "sequenceKindLabels must be an object" });
|
|
2675
|
+
} else {
|
|
2676
|
+
const labels = j.sequenceKindLabels as Record<string, unknown>;
|
|
2677
|
+
for (const [key, value] of Object.entries(labels)) {
|
|
2678
|
+
if (!(SEQUENCE_KIND_LABEL_KEYS as readonly string[]).includes(key)) errors.push({ path: `$.sequenceKindLabels.${key}`, message: `unknown key "${key}"` });
|
|
2679
|
+
else if (typeof value !== "string") errors.push({ path: `$.sequenceKindLabels.${key}`, message: `${key} must be a string` });
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
if (
|
|
2684
|
+
j.sequenceRowGap !== undefined &&
|
|
2685
|
+
(typeof j.sequenceRowGap !== "number" || !Number.isFinite(j.sequenceRowGap) || j.sequenceRowGap <= 0)
|
|
2686
|
+
) {
|
|
2687
|
+
errors.push({ path: "$.sequenceRowGap", message: "sequenceRowGap must be a positive finite number" });
|
|
2688
|
+
}
|
|
2689
|
+
for (const key of ["sequenceGutter", "sequenceBandWidth"] as const) {
|
|
2690
|
+
if (j[key] !== undefined && (typeof j[key] !== "number" || !Number.isFinite(j[key]) || j[key] <= 0)) {
|
|
2691
|
+
errors.push({ path: `$.${key}`, message: `${key} must be a positive finite number` });
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
if (j.sequenceSelfLoop !== undefined) {
|
|
2695
|
+
if (!j.sequenceSelfLoop || typeof j.sequenceSelfLoop !== "object" || Array.isArray(j.sequenceSelfLoop)) {
|
|
2696
|
+
errors.push({ path: "$.sequenceSelfLoop", message: "sequenceSelfLoop must be an object" });
|
|
2697
|
+
} else {
|
|
2698
|
+
const loop = j.sequenceSelfLoop as Record<string, unknown>;
|
|
2699
|
+
for (const key of Object.keys(loop)) {
|
|
2700
|
+
if (!(SEQUENCE_SELF_LOOP_KEYS as readonly string[]).includes(key)) errors.push({ path: `$.sequenceSelfLoop.${key}`, message: `unknown key "${key}"` });
|
|
2701
|
+
}
|
|
2702
|
+
if (loop.form !== undefined && loop.form !== "orthogonal") errors.push({ path: "$.sequenceSelfLoop.form", message: "form must be orthogonal" });
|
|
2703
|
+
for (const key of ["width", "drop"] as const) {
|
|
2704
|
+
if (loop[key] !== undefined && (typeof loop[key] !== "number" || !Number.isFinite(loop[key]) || loop[key] <= 0)) errors.push({ path: `$.sequenceSelfLoop.${key}`, message: "must be a positive finite number" });
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
const card = closedObject("figureCard", ["label", "note"]);
|
|
2709
|
+
if (card !== undefined) {
|
|
2710
|
+
if (typeof card.label !== "string")
|
|
2711
|
+
errors.push({ path: "$.figureCard.label", message: "label must be a string" });
|
|
2712
|
+
if (card.note !== undefined && typeof card.note !== "string")
|
|
2713
|
+
errors.push({ path: "$.figureCard.note", message: "note must be a string" });
|
|
2714
|
+
}
|
|
2715
|
+
const size = closedObject("figureSize", ["width", "height"]);
|
|
2716
|
+
if (size !== undefined) {
|
|
2717
|
+
for (const key of ["width", "height"] as const) {
|
|
2718
|
+
if (typeof size[key] !== "number" || !Number.isFinite(size[key]) || size[key] <= 0)
|
|
2719
|
+
errors.push({ path: `$.figureSize.${key}`, message: `${key} must be a positive finite number` });
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
const positiveNumber = (value: unknown, path: string): boolean => {
|
|
2723
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) return true;
|
|
2724
|
+
errors.push({ path, message: "must be a positive finite number" });
|
|
2725
|
+
return false;
|
|
2726
|
+
};
|
|
2727
|
+
const nonNegativeNumber = (value: unknown, path: string): boolean => {
|
|
2728
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return true;
|
|
2729
|
+
errors.push({ path, message: "must be a non-negative finite number" });
|
|
2730
|
+
return false;
|
|
2731
|
+
};
|
|
2732
|
+
if (j.edgeEndGap !== undefined) nonNegativeNumber(j.edgeEndGap, "$.edgeEndGap");
|
|
2733
|
+
const treeSize = closedObject("treeSize", [
|
|
2734
|
+
"nodeWidths",
|
|
2735
|
+
"nodeHeight",
|
|
2736
|
+
"titleFontSize",
|
|
2737
|
+
"subtitleFontSize",
|
|
2738
|
+
"siblingGap",
|
|
2739
|
+
"levelGap",
|
|
2740
|
+
]);
|
|
2741
|
+
if (treeSize !== undefined) {
|
|
2742
|
+
if (treeSize.nodeWidths !== undefined) {
|
|
2743
|
+
if (!Array.isArray(treeSize.nodeWidths) || treeSize.nodeWidths.length === 0) {
|
|
2744
|
+
errors.push({ path: "$.treeSize.nodeWidths", message: "must be a non-empty array" });
|
|
2745
|
+
} else {
|
|
2746
|
+
treeSize.nodeWidths.forEach((value, index) => {
|
|
2747
|
+
positiveNumber(value, `$.treeSize.nodeWidths[${index}]`);
|
|
2748
|
+
});
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
for (const key of ["nodeHeight", "titleFontSize", "subtitleFontSize"] as const) {
|
|
2752
|
+
if (treeSize[key] !== undefined) positiveNumber(treeSize[key], `$.treeSize.${key}`);
|
|
2753
|
+
}
|
|
2754
|
+
for (const key of ["siblingGap", "levelGap"] as const) {
|
|
2755
|
+
if (treeSize[key] !== undefined) nonNegativeNumber(treeSize[key], `$.treeSize.${key}`);
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
const mindSize = closedObject("mindSize", [
|
|
2759
|
+
"contentOffset",
|
|
2760
|
+
"root",
|
|
2761
|
+
"branch",
|
|
2762
|
+
"leaf",
|
|
2763
|
+
"rootBranchGap",
|
|
2764
|
+
"branchLeafGap",
|
|
2765
|
+
"branchRowGap",
|
|
2766
|
+
"branchRowOffset",
|
|
2767
|
+
"leafRowGap",
|
|
2768
|
+
"leafRowOffset",
|
|
2769
|
+
]);
|
|
2770
|
+
if (mindSize !== undefined) {
|
|
2771
|
+
if (mindSize.contentOffset !== undefined) {
|
|
2772
|
+
const value = mindSize.contentOffset;
|
|
2773
|
+
const path = "$.mindSize.contentOffset";
|
|
2774
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2775
|
+
errors.push({ path, message: "contentOffset must be an object" });
|
|
2776
|
+
} else {
|
|
2777
|
+
const offset = value as Record<string, unknown>;
|
|
2778
|
+
for (const key of Object.keys(offset)) {
|
|
2779
|
+
if (key !== "x" && key !== "y")
|
|
2780
|
+
errors.push({ path: `${path}.${key}`, message: `unknown key "${key}"` });
|
|
2781
|
+
}
|
|
2782
|
+
for (const key of ["x", "y"] as const) {
|
|
2783
|
+
if (typeof offset[key] !== "number" || !Number.isFinite(offset[key]))
|
|
2784
|
+
errors.push({ path: `${path}.${key}`, message: "must be a finite number" });
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
const nestedSize = (
|
|
2789
|
+
key: "root" | "branch" | "leaf",
|
|
2790
|
+
allowed: readonly string[],
|
|
2791
|
+
): Record<string, unknown> | undefined => {
|
|
2792
|
+
const value = mindSize[key];
|
|
2793
|
+
if (value === undefined) return undefined;
|
|
2794
|
+
const path = `$.mindSize.${key}`;
|
|
2795
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2796
|
+
errors.push({ path, message: `${key} must be an object` });
|
|
2797
|
+
return undefined;
|
|
2798
|
+
}
|
|
2799
|
+
const object = value as Record<string, unknown>;
|
|
2800
|
+
for (const nestedKey of Object.keys(object)) {
|
|
2801
|
+
if (!allowed.includes(nestedKey))
|
|
2802
|
+
errors.push({ path: `${path}.${nestedKey}`, message: `unknown key "${nestedKey}"` });
|
|
2803
|
+
}
|
|
2804
|
+
return object;
|
|
2805
|
+
};
|
|
2806
|
+
for (const [key, allowed] of [
|
|
2807
|
+
["root", ["width", "height", "fontSize"]],
|
|
2808
|
+
["branch", ["width", "height", "fontSize"]],
|
|
2809
|
+
["leaf", ["fontSize"]],
|
|
2810
|
+
] as const) {
|
|
2811
|
+
const nested = nestedSize(key, allowed);
|
|
2812
|
+
if (nested === undefined) continue;
|
|
2813
|
+
for (const nestedKey of allowed) {
|
|
2814
|
+
if (nested[nestedKey] !== undefined)
|
|
2815
|
+
positiveNumber(nested[nestedKey], `$.mindSize.${key}.${nestedKey}`);
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
for (const key of ["rootBranchGap", "branchLeafGap", "branchRowGap", "leafRowGap"] as const) {
|
|
2819
|
+
if (mindSize[key] !== undefined) nonNegativeNumber(mindSize[key], `$.mindSize.${key}`);
|
|
2820
|
+
}
|
|
2821
|
+
for (const key of ["branchRowOffset", "leafRowOffset"] as const) {
|
|
2822
|
+
const value = mindSize[key];
|
|
2823
|
+
if (value !== undefined && (typeof value !== "number" || !Number.isFinite(value))) {
|
|
2824
|
+
errors.push({ path: `$.mindSize.${key}`, message: "must be a finite number" });
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
const specs = {
|
|
2830
|
+
terminalDecoration: ["windowControls", "cursor", "rowOrdinal"],
|
|
2831
|
+
figureKindLabel: ["text", "x", "y", "fontSize", "fontWeight", "letterSpacing"],
|
|
2832
|
+
figureInset: ["top", "right", "bottom", "left", "radius"],
|
|
2833
|
+
journeySize: ["plotPadding", "pointPadding"],
|
|
2834
|
+
chartBarSize: ["tickFontSize", "valueFontSize", "labelFontSize", "plotPadding", "barWidth", "cornerRadius"],
|
|
2835
|
+
chartLineSize: ["valueLabelOffset", "tickFontSize", "periodFontSize", "pointRadius", "plotPadding", "tickGap", "pointPadding", "periodY"],
|
|
2836
|
+
chartPieSize: ["tableWidth", "tableLabelFontSize", "tableValueFontSize", "tableRowGap", "tablePaddingInline", "outerRadius", "centerX"],
|
|
2837
|
+
chartWaffleSize: ["gridLeft", "cellPitch"],
|
|
2838
|
+
chartSlopeLabelTemplate: ["left", "right"],
|
|
2839
|
+
chartSlopeSize: ["pointRadius", "periodFontSize", "labelFontSize", "leftAxisX", "axisGap", "labelAxisGap", "plotPadding", "pointPadding"],
|
|
2840
|
+
chartStackedLayout: ["legendColumns", "legendColumnGap", "segmentGap", "barThickness", "barWidth", "legendMarkSize", "legendFontSize", "periodFontSize", "rateFontSize", "legendOffsetY"],
|
|
2841
|
+
chartGaugeSize: ["arcThickness", "outerRadius", "plotTop", "valueFontSize"],
|
|
2842
|
+
ganttSize: ["taskLabelFontSize", "taskLabelAlign", "taskLabelX", "tickFontSize", "tickCount", "barThickness", "milestoneFontSize", "milestoneRadius", "dependencyStrokeWidth", "taskColumnWidth", "timelinePaddingRight"],
|
|
2843
|
+
quadrantSize: ["plotOffset", "plotSize", "pointRadius", "pointFontSize", "pointLabelGap", "quadrantFontSize", "axisFontSize"],
|
|
2844
|
+
legendOffset: ["x", "y"],
|
|
2845
|
+
stageLayout: ["columnWidths", "columnGap", "backDetourTopGap", "backDetourSideGap", "backDetourTargetInset"],
|
|
2846
|
+
} as const;
|
|
2847
|
+
const objects = Object.fromEntries(Object.entries(specs).map(([name, keys]) => [name, closedObject(name as keyof typeof specs, keys)])) as Record<keyof typeof specs, Record<string, unknown> | undefined>;
|
|
2848
|
+
const positiveKeys: Partial<Record<keyof typeof specs, readonly string[]>> = {
|
|
2849
|
+
figureKindLabel: ["fontSize", "fontWeight"],
|
|
2850
|
+
chartBarSize: ["tickFontSize", "valueFontSize", "labelFontSize", "barWidth"],
|
|
2851
|
+
chartLineSize: ["tickFontSize", "periodFontSize", "pointRadius"],
|
|
2852
|
+
chartPieSize: ["tableWidth", "tableLabelFontSize", "tableValueFontSize", "outerRadius"],
|
|
2853
|
+
chartWaffleSize: ["cellPitch"],
|
|
2854
|
+
chartSlopeSize: ["pointRadius", "periodFontSize", "labelFontSize", "axisGap"],
|
|
2855
|
+
chartStackedLayout: ["barThickness", "barWidth", "legendMarkSize", "legendFontSize", "periodFontSize", "rateFontSize"],
|
|
2856
|
+
chartGaugeSize: ["arcThickness", "outerRadius", "valueFontSize"],
|
|
2857
|
+
ganttSize: ["taskLabelFontSize", "tickFontSize", "barThickness", "milestoneFontSize", "milestoneRadius", "dependencyStrokeWidth"],
|
|
2858
|
+
quadrantSize: ["plotSize", "pointRadius", "pointFontSize", "quadrantFontSize", "axisFontSize"],
|
|
2859
|
+
};
|
|
2860
|
+
const nonNegativeKeys: Partial<Record<keyof typeof specs, readonly string[]>> = {
|
|
2861
|
+
figureKindLabel: ["letterSpacing"], figureInset: ["top", "right", "bottom", "left", "radius"],
|
|
2862
|
+
chartBarSize: ["cornerRadius"], chartLineSize: ["tickGap", "periodY"], chartPieSize: ["tableRowGap", "tablePaddingInline", "centerX"], chartWaffleSize: ["gridLeft"], chartSlopeSize: ["leftAxisX", "labelAxisGap"],
|
|
2863
|
+
chartStackedLayout: ["legendColumnGap", "segmentGap"], chartGaugeSize: ["plotTop"],
|
|
2864
|
+
ganttSize: ["taskLabelX", "taskColumnWidth", "timelinePaddingRight"], quadrantSize: ["pointLabelGap"],
|
|
2865
|
+
stageLayout: ["columnGap", "backDetourTopGap", "backDetourSideGap", "backDetourTargetInset"],
|
|
2866
|
+
};
|
|
2867
|
+
for (const [name, object] of Object.entries(objects) as [keyof typeof specs, Record<string, unknown> | undefined][]) {
|
|
2868
|
+
if (object === undefined) continue;
|
|
2869
|
+
for (const key of positiveKeys[name] ?? []) if (object[key] !== undefined) positiveNumber(object[key], `$.${name}.${key}`);
|
|
2870
|
+
for (const key of nonNegativeKeys[name] ?? []) if (object[key] !== undefined) nonNegativeNumber(object[key], `$.${name}.${key}`);
|
|
2871
|
+
}
|
|
2872
|
+
const finite = (value: unknown, path: string): void => { if (value !== undefined && (typeof value !== "number" || !Number.isFinite(value))) errors.push({ path, message: "must be a finite number" }); };
|
|
2873
|
+
if (objects.terminalDecoration) for (const key of specs.terminalDecoration) if (objects.terminalDecoration[key] !== undefined && typeof objects.terminalDecoration[key] !== "boolean") errors.push({ path: `$.terminalDecoration.${key}`, message: "must be boolean" });
|
|
2874
|
+
if (objects.figureKindLabel) {
|
|
2875
|
+
if (typeof objects.figureKindLabel.text !== "string" || objects.figureKindLabel.text.trim() === "") errors.push({ path: "$.figureKindLabel.text", message: "must be a non-empty string" });
|
|
2876
|
+
finite(objects.figureKindLabel.x, "$.figureKindLabel.x"); finite(objects.figureKindLabel.y, "$.figureKindLabel.y");
|
|
2877
|
+
}
|
|
2878
|
+
if (objects.chartSlopeLabelTemplate) for (const key of specs.chartSlopeLabelTemplate) if (objects.chartSlopeLabelTemplate[key] !== undefined && typeof objects.chartSlopeLabelTemplate[key] !== "string") errors.push({ path: `$.chartSlopeLabelTemplate.${key}`, message: "must be a string" });
|
|
2879
|
+
if (objects.chartStackedLayout) finite(objects.chartStackedLayout.legendOffsetY, "$.chartStackedLayout.legendOffsetY");
|
|
2880
|
+
if (objects.legendOffset) {
|
|
2881
|
+
finite(objects.legendOffset.x, "$.legendOffset.x");
|
|
2882
|
+
finite(objects.legendOffset.y, "$.legendOffset.y");
|
|
2883
|
+
}
|
|
2884
|
+
const nestedNumberObject = (parent: keyof typeof specs, key: string, keys: readonly string[], positive: boolean): void => {
|
|
2885
|
+
const value = objects[parent]?.[key]; if (value === undefined) return;
|
|
2886
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) { errors.push({ path: `$.${parent}.${key}`, message: "must be an object" }); return; }
|
|
2887
|
+
const object = value as Record<string, unknown>;
|
|
2888
|
+
for (const child of Object.keys(object)) if (!keys.includes(child)) errors.push({ path: `$.${parent}.${key}.${child}`, message: `unknown key "${child}"` });
|
|
2889
|
+
for (const child of keys) if (object[child] !== undefined) (positive ? positiveNumber : nonNegativeNumber)(object[child], `$.${parent}.${key}.${child}`);
|
|
2890
|
+
};
|
|
2891
|
+
nestedNumberObject("chartBarSize", "plotPadding", ["top", "right", "bottom", "left"], false);
|
|
2892
|
+
nestedNumberObject("chartLineSize", "valueLabelOffset", ["above", "below"], false);
|
|
2893
|
+
nestedNumberObject("chartLineSize", "plotPadding", ["top", "right", "bottom", "left"], false);
|
|
2894
|
+
nestedNumberObject("chartLineSize", "pointPadding", ["left", "right"], false);
|
|
2895
|
+
nestedNumberObject("chartSlopeSize", "plotPadding", ["top", "bottom"], false);
|
|
2896
|
+
nestedNumberObject("chartSlopeSize", "pointPadding", ["top", "bottom"], false);
|
|
2897
|
+
nestedNumberObject("journeySize", "plotPadding", ["left", "right"], false);
|
|
2898
|
+
nestedNumberObject("journeySize", "pointPadding", ["left", "right"], false);
|
|
2899
|
+
nestedNumberObject("quadrantSize", "plotOffset", ["x", "y"], false);
|
|
2900
|
+
const columnWidths = objects.stageLayout?.columnWidths;
|
|
2901
|
+
if (columnWidths !== undefined) {
|
|
2902
|
+
if (!columnWidths || typeof columnWidths !== "object" || Array.isArray(columnWidths)) errors.push({ path: "$.stageLayout.columnWidths", message: "must be an object" });
|
|
2903
|
+
else for (const [key, value] of Object.entries(columnWidths)) nonNegativeNumber(value, `$.stageLayout.columnWidths.${key}`);
|
|
2904
|
+
}
|
|
2905
|
+
const stackedColumns = objects.chartStackedLayout?.legendColumns;
|
|
2906
|
+
if (stackedColumns !== undefined && (!Number.isInteger(stackedColumns) || typeof stackedColumns !== "number" || stackedColumns <= 0)) errors.push({ path: "$.chartStackedLayout.legendColumns", message: "must be a positive integer" });
|
|
2907
|
+
if (
|
|
2908
|
+
objects.ganttSize?.taskLabelAlign !== undefined &&
|
|
2909
|
+
!(GANTT_TASK_LABEL_ALIGN_VALUES as readonly unknown[]).includes(objects.ganttSize.taskLabelAlign)
|
|
2910
|
+
) errors.push({ path: "$.ganttSize.taskLabelAlign", message: "must be start or end" });
|
|
2911
|
+
const tickCount = objects.ganttSize?.tickCount;
|
|
2912
|
+
if (tickCount !== undefined && (!Number.isInteger(tickCount) || typeof tickCount !== "number" || tickCount < 2)) errors.push({ path: "$.ganttSize.tickCount", message: "must be an integer greater than or equal to 2" });
|
|
2913
|
+
|
|
2914
|
+
if (Array.isArray(j.chartSlopePeriods) &&
|
|
2915
|
+
(j.chartSlopePeriods.length !== 2 || j.chartSlopePeriods.some((v) => typeof v !== "string" || v.trim() === ""))) {
|
|
2916
|
+
errors.push({ path: "$.chartSlopePeriods", message: "chartSlopePeriods must contain exactly two non-empty strings" });
|
|
2917
|
+
}
|
|
2918
|
+
if (Array.isArray(j.chartSlopeEmphasisIds) && j.chartSlopeEmphasisIds.length === 0) {
|
|
2919
|
+
errors.push({ path: "$.chartSlopeEmphasisIds", message: "chartSlopeEmphasisIds must contain at least one id" });
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
const boundNumber = (value: unknown): boolean =>
|
|
2923
|
+
(typeof value === "number" && Number.isFinite(value)) ||
|
|
2924
|
+
(typeof value === "string" && value.trim() !== "");
|
|
2925
|
+
if (j.chartLineSeries !== undefined && !Array.isArray(j.chartLineSeries)) {
|
|
2926
|
+
errors.push({ path: "$.chartLineSeries", message: "chartLineSeries must be an array" });
|
|
2927
|
+
} else if (Array.isArray(j.chartLineSeries)) {
|
|
2928
|
+
if (j.chartLineSeries.length === 0) {
|
|
2929
|
+
errors.push({ path: "$.chartLineSeries", message: "chartLineSeries must contain at least one series" });
|
|
2930
|
+
}
|
|
2931
|
+
j.chartLineSeries.forEach((series, i) => {
|
|
2932
|
+
const path = `$.chartLineSeries[${i}]`;
|
|
2933
|
+
if (!series || typeof series !== "object" || Array.isArray(series)) {
|
|
2934
|
+
errors.push({ path, message: "chartLineSeries item must be an object" });
|
|
2935
|
+
return;
|
|
2936
|
+
}
|
|
2937
|
+
const value = series as Record<string, unknown>;
|
|
2938
|
+
for (const key of Object.keys(value)) {
|
|
2939
|
+
if (!["label", "points", "tone", "dash", "marker", "valueIndexes"].includes(key))
|
|
2940
|
+
errors.push({ path: `${path}.${key}`, message: `unknown key "${key}"` });
|
|
2941
|
+
}
|
|
2942
|
+
if (value.label !== undefined && typeof value.label !== "string")
|
|
2943
|
+
errors.push({ path: `${path}.label`, message: "label must be a string" });
|
|
2944
|
+
if (value.tone !== undefined && (typeof value.tone !== "string" || resolveTone(value.tone) === undefined))
|
|
2945
|
+
errors.push({ path: `${path}.tone`, message: "tone must be a known tone" });
|
|
2946
|
+
if (
|
|
2947
|
+
value.dash !== undefined &&
|
|
2948
|
+
(typeof value.dash !== "string" ||
|
|
2949
|
+
!(CHART_LINE_DASH_VALUES as readonly string[]).includes(value.dash))
|
|
2950
|
+
)
|
|
2951
|
+
errors.push({ path: `${path}.dash`, message: "dash must be solid or dotted" });
|
|
2952
|
+
if (
|
|
2953
|
+
value.marker !== undefined &&
|
|
2954
|
+
(typeof value.marker !== "string" ||
|
|
2955
|
+
!(CHART_LINE_MARKER_VALUES as readonly string[]).includes(value.marker))
|
|
2956
|
+
)
|
|
2957
|
+
errors.push({ path: `${path}.marker`, message: "marker must be dot, hollow, or none" });
|
|
2958
|
+
if (value.valueIndexes !== undefined &&
|
|
2959
|
+
(!Array.isArray(value.valueIndexes) || value.valueIndexes.some((v) => typeof v !== "number" || !Number.isInteger(v) || v < 0)))
|
|
2960
|
+
errors.push({ path: `${path}.valueIndexes`, message: "valueIndexes must contain non-negative integers" });
|
|
2961
|
+
if (!Array.isArray(value.points) || value.points.length === 0) {
|
|
2962
|
+
errors.push({ path: `${path}.points`, message: "points must be a non-empty array" });
|
|
2963
|
+
} else value.points.forEach((point, p) => {
|
|
2964
|
+
const pointPath = `${path}.points[${p}]`;
|
|
2965
|
+
if (!point || typeof point !== "object" || Array.isArray(point)) {
|
|
2966
|
+
errors.push({ path: pointPath, message: "point must be an object" });
|
|
2967
|
+
return;
|
|
2968
|
+
}
|
|
2969
|
+
const item = point as Record<string, unknown>;
|
|
2970
|
+
for (const key of Object.keys(item)) if (key !== "label" && key !== "value")
|
|
2971
|
+
errors.push({ path: `${pointPath}.${key}`, message: `unknown key "${key}"` });
|
|
2972
|
+
if (typeof item.label !== "string") errors.push({ path: `${pointPath}.label`, message: "label must be a string" });
|
|
2973
|
+
if (!boundNumber(item.value)) errors.push({ path: `${pointPath}.value`, message: "value must be a finite number or non-empty string" });
|
|
2974
|
+
});
|
|
2975
|
+
});
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
if (j.chartStackedPeriods !== undefined && !Array.isArray(j.chartStackedPeriods)) {
|
|
2979
|
+
errors.push({ path: "$.chartStackedPeriods", message: "chartStackedPeriods must be an array" });
|
|
2980
|
+
} else if (Array.isArray(j.chartStackedPeriods)) {
|
|
2981
|
+
if (j.chartStackedPeriods.length === 0)
|
|
2982
|
+
errors.push({ path: "$.chartStackedPeriods", message: "chartStackedPeriods must contain at least one period" });
|
|
2983
|
+
j.chartStackedPeriods.forEach((period, i) => {
|
|
2984
|
+
const path = `$.chartStackedPeriods[${i}]`;
|
|
2985
|
+
if (!period || typeof period !== "object" || Array.isArray(period)) {
|
|
2986
|
+
errors.push({ path, message: "chartStackedPeriods item must be an object" });
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
const value = period as Record<string, unknown>;
|
|
2990
|
+
for (const key of Object.keys(value)) if (key !== "label" && key !== "values")
|
|
2991
|
+
errors.push({ path: `${path}.${key}`, message: `unknown key "${key}"` });
|
|
2992
|
+
if (typeof value.label !== "string") errors.push({ path: `${path}.label`, message: "label must be a string" });
|
|
2993
|
+
if (!Array.isArray(value.values) || value.values.some((entry) => !boundNumber(entry)))
|
|
2994
|
+
errors.push({ path: `${path}.values`, message: "values must be an array of finite numbers or non-empty strings" });
|
|
2995
|
+
});
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
if (
|
|
2999
|
+
j.chartGaugeValue !== undefined &&
|
|
3000
|
+
(!j.chartGaugeValue || typeof j.chartGaugeValue !== "object" || Array.isArray(j.chartGaugeValue))
|
|
3001
|
+
) {
|
|
3002
|
+
errors.push({ path: "$.chartGaugeValue", message: "chartGaugeValue must be an object" });
|
|
3003
|
+
} else if (j.chartGaugeValue && typeof j.chartGaugeValue === "object") {
|
|
3004
|
+
const value = j.chartGaugeValue as Record<string, unknown>;
|
|
3005
|
+
for (const key of Object.keys(value)) if (!["max", "current", "target", "previous", "previousLabel"].includes(key))
|
|
3006
|
+
errors.push({ path: `$.chartGaugeValue.${key}`, message: `unknown key "${key}"` });
|
|
3007
|
+
const finite = (entry: unknown): boolean => typeof entry === "number" && Number.isFinite(entry);
|
|
3008
|
+
if (!finite(value.max) || Number(value.max) <= 0) errors.push({ path: "$.chartGaugeValue.max", message: "max must be a positive finite number" });
|
|
3009
|
+
if (!boundNumber(value.current)) errors.push({ path: "$.chartGaugeValue.current", message: "current must be a finite number or non-empty string" });
|
|
3010
|
+
for (const key of ["target", "previous"] as const) if (value[key] !== undefined && !boundNumber(value[key]))
|
|
3011
|
+
errors.push({ path: `$.chartGaugeValue.${key}`, message: `${key} must be a finite number or non-empty string` });
|
|
3012
|
+
if (value.previousLabel !== undefined && typeof value.previousLabel !== "string")
|
|
3013
|
+
errors.push({ path: "$.chartGaugeValue.previousLabel", message: "previousLabel must be a string" });
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
/**
|
|
3018
|
+
* JSON の区画の名前を、図の側の綴りへ直す (#2667)。
|
|
3019
|
+
*
|
|
3020
|
+
* 記法と JSON はどちらも `左上` のような語で書き、図の側は `topLeft` で受ける。
|
|
3021
|
+
* 直す先の表は項目の欄と共有する (`区画`) = 同じ区画を 2 通りで呼ばない。
|
|
3022
|
+
*
|
|
3023
|
+
* 中身の無い形は「書かなかった」 と同じにする (`axes` と同じ扱い)。
|
|
3024
|
+
*/
|
|
3025
|
+
function 区画の名前を直す(r: JsonRegions | undefined): DslRegions | undefined {
|
|
3026
|
+
if (r === undefined) return undefined;
|
|
3027
|
+
const 出: DslRegions = {};
|
|
3028
|
+
for (const [語, key] of 区画) {
|
|
3029
|
+
const v = (r as Record<string, unknown>)[語];
|
|
3030
|
+
if (typeof v === "string" && v.trim() !== "") 出[key] = v;
|
|
3031
|
+
}
|
|
3032
|
+
return Object.keys(出).length > 0 ? 出 : undefined;
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
/** 2 軸で仕分ける図の区画の名前を見る (#2667) */
|
|
3036
|
+
function validateRegions(v: unknown, errors: JsonDslError[]): void {
|
|
3037
|
+
if (v === undefined) return;
|
|
3038
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) {
|
|
3039
|
+
errors.push({
|
|
3040
|
+
path: "$.regions",
|
|
3041
|
+
message: 'regions must be a plain object like { "左上": "すぐやる" }',
|
|
3042
|
+
});
|
|
3043
|
+
return;
|
|
2022
3044
|
}
|
|
3045
|
+
const o = v as Record<string, unknown>;
|
|
3046
|
+
checkUnknownKeys(o, "regions", "$.regions", errors);
|
|
3047
|
+
表で検査(o, "regions", "$.regions", "regions", errors);
|
|
2023
3048
|
}
|
|
2024
3049
|
|
|
2025
3050
|
/**
|
|
@@ -2288,6 +3313,8 @@ function validateJson(
|
|
|
2288
3313
|
// 値を見せる部品の中身を、記法と同じ表で見る (#1374)
|
|
2289
3314
|
validateReadouts(j.readouts, errors);
|
|
2290
3315
|
validateBands(j.bands, errors);
|
|
3316
|
+
validateLegend(j.legend, errors);
|
|
3317
|
+
validateStageHeader(j.stageHeader, errors);
|
|
2291
3318
|
// 読む人が動かすつまみの中身も、記法と同じ表で見る (#1389)
|
|
2292
3319
|
validateInputs(j.inputs, errors);
|
|
2293
3320
|
// 式は描画側の parser に通す (#1391)
|
|
@@ -2299,6 +3326,29 @@ function validateJson(
|
|
|
2299
3326
|
// 値そのものの型は表が見る (#1304)。 図表の箱の上の小見出し (#1247) の空文字は
|
|
2300
3327
|
// 「書かなかった」 と同じ扱いにするため通す (記法側の `eyebrow:` と揃える。 落とすのは `jsonToDoc`)
|
|
2301
3328
|
表で検査(j, "root", "$", "", errors);
|
|
3329
|
+
validateDiagramShape(j, errors);
|
|
3330
|
+
for (const 欄 of ["ticks", "目盛り"] as const) {
|
|
3331
|
+
if (Array.isArray(j[欄]) && j[欄].length === 0) {
|
|
3332
|
+
errors.push({ path: `$.${欄}`, message: `${欄} must contain at least one label` });
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
if (j.ganttToday !== undefined) {
|
|
3336
|
+
const today = j.ganttToday;
|
|
3337
|
+
if (!today || typeof today !== "object" || Array.isArray(today)) {
|
|
3338
|
+
errors.push({ path: "$.ganttToday", message: "ganttToday must be an object" });
|
|
3339
|
+
} else {
|
|
3340
|
+
const value = today as Record<string, unknown>;
|
|
3341
|
+
for (const key of Object.keys(value)) {
|
|
3342
|
+
if (key !== "index" && key !== "label") errors.push({ path: `$.ganttToday.${key}`, message: `unknown key "${key}"` });
|
|
3343
|
+
}
|
|
3344
|
+
if (typeof value.index !== "number" || !Number.isFinite(value.index)) {
|
|
3345
|
+
errors.push({ path: "$.ganttToday.index", message: "ganttToday.index must be a finite number" });
|
|
3346
|
+
}
|
|
3347
|
+
if (typeof value.label !== "string") {
|
|
3348
|
+
errors.push({ path: "$.ganttToday.label", message: "ganttToday.label must be a string" });
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
2302
3352
|
|
|
2303
3353
|
if (!Array.isArray(j.actors) || j.actors.length === 0) {
|
|
2304
3354
|
errors.push({ path: "$.actors", message: "actors must be a non-empty array" });
|
|
@@ -2314,6 +3364,93 @@ function validateJson(
|
|
|
2314
3364
|
// 値そのものの型は表が見る (#1304)。 `kind` は見本 (parts) の名前も受けるため
|
|
2315
3365
|
// 非空の文字列までしか縛らない (CAR-1657 の unified syntax)
|
|
2316
3366
|
表で検査(ao, "actor", `$.actors[${i}]`, "actor", errors);
|
|
3367
|
+
const actorEnum = (key: string, allowed: readonly string[]): void => {
|
|
3368
|
+
const value = ao[key];
|
|
3369
|
+
if (value === undefined || typeof value !== "string") return;
|
|
3370
|
+
if (!allowed.includes(value)) errors.push({ path: `$.actors[${i}].${key}`, message: `${key} must be one of: ${allowed.join(", ")}` });
|
|
3371
|
+
};
|
|
3372
|
+
actorEnum("kindForm", KIND_FORM_VALUES);
|
|
3373
|
+
actorEnum("icon", CDL_ICON_VALUES);
|
|
3374
|
+
actorEnum("titleAlign", TITLE_ALIGN_VALUES);
|
|
3375
|
+
actorEnum("rowMarkForm", ROW_MARK_FORM_VALUES);
|
|
3376
|
+
actorEnum("rowDividerForm", ROW_DIVIDER_FORM_VALUES);
|
|
3377
|
+
if (ao.rowWell !== undefined) {
|
|
3378
|
+
const path = `$.actors[${i}].rowWell`;
|
|
3379
|
+
if (!ao.rowWell || typeof ao.rowWell !== "object" || Array.isArray(ao.rowWell)) {
|
|
3380
|
+
errors.push({ path, message: "rowWell must be an object" });
|
|
3381
|
+
} else {
|
|
3382
|
+
const well = ao.rowWell as Record<string, unknown>;
|
|
3383
|
+
for (const key of Object.keys(well)) {
|
|
3384
|
+
if (!(ROW_WELL_KEYS as readonly string[]).includes(key)) errors.push({ path: `${path}.${key}`, message: `unknown key "${key}"` });
|
|
3385
|
+
}
|
|
3386
|
+
for (const key of ROW_WELL_KEYS) {
|
|
3387
|
+
const value = well[key];
|
|
3388
|
+
if (value !== undefined && (typeof value !== "number" || !Number.isFinite(value) || value < 0)) errors.push({ path: `${path}.${key}`, message: "must be a non-negative finite number" });
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
if (ao.rowLayout !== undefined) {
|
|
3393
|
+
const path = `$.actors[${i}].rowLayout`;
|
|
3394
|
+
if (!ao.rowLayout || typeof ao.rowLayout !== "object" || Array.isArray(ao.rowLayout)) {
|
|
3395
|
+
errors.push({ path, message: "rowLayout must be an object" });
|
|
3396
|
+
} else {
|
|
3397
|
+
const layout = ao.rowLayout as Record<string, unknown>;
|
|
3398
|
+
for (const key of Object.keys(layout)) if (!["titleBaseline", "rowTop", "rowPitch"].includes(key)) errors.push({ path: `${path}.${key}`, message: `unknown key "${key}"` });
|
|
3399
|
+
for (const key of ["titleBaseline", "rowTop", "rowPitch"] as const) {
|
|
3400
|
+
const value = layout[key];
|
|
3401
|
+
if (value !== undefined && (typeof value !== "number" || !Number.isFinite(value) || (key === "rowPitch" ? value <= 0 : value < 0))) errors.push({ path: `${path}.${key}`, message: key === "rowPitch" ? "must be a positive finite number" : "must be a non-negative finite number" });
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
if (ao.figureCard !== undefined) {
|
|
3406
|
+
const path = `$.actors[${i}].figureCard`;
|
|
3407
|
+
if (!ao.figureCard || typeof ao.figureCard !== "object" || Array.isArray(ao.figureCard)) errors.push({ path, message: "figureCard must be an object" });
|
|
3408
|
+
else {
|
|
3409
|
+
const card = ao.figureCard as Record<string, unknown>;
|
|
3410
|
+
for (const key of Object.keys(card)) if (key !== "label" && key !== "note") errors.push({ path: `${path}.${key}`, message: `unknown key "${key}"` });
|
|
3411
|
+
if (typeof card.label !== "string") errors.push({ path: `${path}.label`, message: "label must be a string" });
|
|
3412
|
+
if (card.note !== undefined && typeof card.note !== "string") errors.push({ path: `${path}.note`, message: "note must be a string" });
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
if (ao.marks !== undefined && !Array.isArray(ao.marks)) {
|
|
3416
|
+
errors.push({ path: `$.actors[${i}].marks`, message: "marks must be an array" });
|
|
3417
|
+
} else if (Array.isArray(ao.marks)) {
|
|
3418
|
+
ao.marks.forEach((mark, markIndex) => {
|
|
3419
|
+
if (typeof mark === "string") return;
|
|
3420
|
+
const path = `$.actors[${i}].marks[${markIndex}]`;
|
|
3421
|
+
if (!mark || typeof mark !== "object" || Array.isArray(mark)) {
|
|
3422
|
+
errors.push({ path, message: "mark must be a string or object" });
|
|
3423
|
+
return;
|
|
3424
|
+
}
|
|
3425
|
+
const entry = mark as Record<string, unknown>;
|
|
3426
|
+
for (const key of Object.keys(entry)) if (key !== "mark" && key !== "tone") errors.push({ path: `${path}.${key}`, message: `unknown key "${key}"` });
|
|
3427
|
+
if (typeof entry.mark !== "string") errors.push({ path: `${path}.mark`, message: "mark must be a string" });
|
|
3428
|
+
if (entry.tone !== undefined && !(ROW_MARK_TONE_VALUES as readonly unknown[]).includes(entry.tone)) errors.push({ path: `${path}.tone`, message: `tone must be one of: ${ROW_MARK_TONE_VALUES.join(", ")}` });
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
if (
|
|
3432
|
+
typeof ao.opportunityPosition === "string" &&
|
|
3433
|
+
!(JOURNEY_OPPORTUNITY_POSITION_VALUES as readonly string[]).includes(ao.opportunityPosition)
|
|
3434
|
+
) {
|
|
3435
|
+
errors.push({
|
|
3436
|
+
path: `$.actors[${i}].opportunityPosition`,
|
|
3437
|
+
message: `opportunityPosition must be one of: ${JOURNEY_OPPORTUNITY_POSITION_VALUES.join(", ")}`,
|
|
3438
|
+
});
|
|
3439
|
+
}
|
|
3440
|
+
for (const 欄 of ["at", "点の位置"] as const) {
|
|
3441
|
+
if (ao[欄] === undefined) continue;
|
|
3442
|
+
const at = ao[欄];
|
|
3443
|
+
if (
|
|
3444
|
+
!Array.isArray(at) ||
|
|
3445
|
+
at.length !== 2 ||
|
|
3446
|
+
at.some((v) => typeof v !== "number" && typeof v !== "string")
|
|
3447
|
+
) {
|
|
3448
|
+
errors.push({
|
|
3449
|
+
path: `$.actors[${i}].${欄}`,
|
|
3450
|
+
message: `actor.${欄} must be [x, y] with number or state reference values`,
|
|
3451
|
+
});
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
2317
3454
|
// 箱の中に描く図形の中身を、記法と同じ表で見る (#1374)
|
|
2318
3455
|
validateActorShape(ao.shape, `$.actors[${i}].shape`, errors);
|
|
2319
3456
|
// 見本 (parts) にしか効かない項目は、見本でない箱に書かれたら誤りにする (#1294)。
|
|
@@ -2387,6 +3524,44 @@ function validateJson(
|
|
|
2387
3524
|
checkUnknownKeys(so, "step", `$.flow[${i}]`, errors);
|
|
2388
3525
|
// 値そのものの型は表が見る (#1304)。 色と線種は engine の一覧と突き合わせる
|
|
2389
3526
|
表で検査(so, "step", `$.flow[${i}]`, "step", errors);
|
|
3527
|
+
if (so.labelCollision !== undefined && !(LABEL_COLLISION_VALUES as readonly unknown[]).includes(so.labelCollision)) {
|
|
3528
|
+
errors.push({ path: `$.flow[${i}].labelCollision`, message: `labelCollision must be one of: ${LABEL_COLLISION_VALUES.join(", ")}` });
|
|
3529
|
+
}
|
|
3530
|
+
if (
|
|
3531
|
+
so.labelSize !== undefined &&
|
|
3532
|
+
(!so.labelSize || typeof so.labelSize !== "object" || Array.isArray(so.labelSize))
|
|
3533
|
+
) {
|
|
3534
|
+
errors.push({ path: `$.flow[${i}].labelSize`, message: "labelSize must be an object" });
|
|
3535
|
+
} else if (so.labelSize !== undefined) {
|
|
3536
|
+
const size = so.labelSize as Record<string, unknown>;
|
|
3537
|
+
for (const key of Object.keys(size)) if (!["width", "height", "fontSize"].includes(key)) errors.push({ path: `$.flow[${i}].labelSize.${key}`, message: `unknown key "${key}"` });
|
|
3538
|
+
for (const key of ["width", "height", "fontSize"]) if (size[key] !== undefined && (typeof size[key] !== "number" || !Number.isFinite(size[key]) || size[key] <= 0)) errors.push({ path: `$.flow[${i}].labelSize.${key}`, message: "must be a positive finite number" });
|
|
3539
|
+
}
|
|
3540
|
+
if (so.waypoints !== undefined) {
|
|
3541
|
+
const path = `$.flow[${i}].waypoints`;
|
|
3542
|
+
if (!Array.isArray(so.waypoints)) errors.push({ path, message: "waypoints must be an array" });
|
|
3543
|
+
else {
|
|
3544
|
+
const points: Array<{ x: number; y: number }> = [];
|
|
3545
|
+
so.waypoints.forEach((point, pointIndex) => {
|
|
3546
|
+
const pointPath = `${path}[${pointIndex}]`;
|
|
3547
|
+
if (!point || typeof point !== "object" || Array.isArray(point)) {
|
|
3548
|
+
errors.push({ path: pointPath, message: "waypoint must be an object" });
|
|
3549
|
+
return;
|
|
3550
|
+
}
|
|
3551
|
+
const value = point as Record<string, unknown>;
|
|
3552
|
+
for (const key of Object.keys(value)) if (key !== "x" && key !== "y") errors.push({ path: `${pointPath}.${key}`, message: `unknown key "${key}"` });
|
|
3553
|
+
if (typeof value.x !== "number" || !Number.isFinite(value.x) || typeof value.y !== "number" || !Number.isFinite(value.y)) errors.push({ path: pointPath, message: "waypoint x and y must be finite numbers" });
|
|
3554
|
+
else points.push({ x: value.x, y: value.y });
|
|
3555
|
+
});
|
|
3556
|
+
const ends = [so.posX1, so.posY1, so.posX2, so.posY2];
|
|
3557
|
+
if (ends.every((value) => typeof value === "number" && Number.isFinite(value)) && points.length === so.waypoints.length) {
|
|
3558
|
+
const all = [{ x: so.posX1 as number, y: so.posY1 as number }, ...points, { x: so.posX2 as number, y: so.posY2 as number }];
|
|
3559
|
+
all.forEach((point, pointIndex) => {
|
|
3560
|
+
if (pointIndex > 0 && point.x !== all[pointIndex - 1]!.x && point.y !== all[pointIndex - 1]!.y) errors.push({ path: `${path}[${Math.min(pointIndex - 1, points.length - 1)}]`, message: "each waypoint segment must be orthogonal" });
|
|
3561
|
+
});
|
|
3562
|
+
}
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
2390
3565
|
// CAR-1693 Phase 1: step DSL 表面 pos の validation
|
|
2391
3566
|
validateLayoutPos(so.pos, `$.flow[${i}].pos`, errors);
|
|
2392
3567
|
});
|
|
@@ -2423,11 +3598,38 @@ function validateJson(
|
|
|
2423
3598
|
}
|
|
2424
3599
|
validateStates(j.states, errors);
|
|
2425
3600
|
validateValues(j.values, errors);
|
|
3601
|
+
validateChartPresentationFields(j, errors);
|
|
2426
3602
|
validateAxes(j.axes, errors);
|
|
3603
|
+
validateRegions(j.regions, errors);
|
|
2427
3604
|
if (errors.length > 0) return { ok: false, errors };
|
|
2428
3605
|
return { ok: true, data: j as unknown as DragonJson };
|
|
2429
3606
|
}
|
|
2430
3607
|
|
|
3608
|
+
/** 最上位の `shape` を記法と同じ図種別の集合で検査する (#2797)。 */
|
|
3609
|
+
function validateDiagramShape(j: Record<string, unknown>, errors: JsonDslError[]): void {
|
|
3610
|
+
if (j.shape === undefined) return;
|
|
3611
|
+
if (typeof j.shape !== "string" || typeof j.type !== "string") return;
|
|
3612
|
+
const 別名 = TYPE_ALIASES.get(j.type);
|
|
3613
|
+
const type = 別名?.type ?? (PRESET_TYPES.has(j.type as PresetType) ? (j.type as PresetType) : null);
|
|
3614
|
+
if (type === null) return;
|
|
3615
|
+
const shape = resolveDiagramShape(j.shape);
|
|
3616
|
+
const 図種の語 = shapesForDiagramType(type);
|
|
3617
|
+
// 語そのものの誤りは `欄の型表` が同じ集合から伝える。 ここでは図種との組だけを見る。
|
|
3618
|
+
if (shape === null) return;
|
|
3619
|
+
if (shape !== null && (type !== "chart" || 図種の語.includes(shape))) return;
|
|
3620
|
+
const hint =
|
|
3621
|
+
図種の語.length > 0
|
|
3622
|
+
? `使える語 = ${図種の語.join(" / ")}`
|
|
3623
|
+
: `shape を書ける図種と語 = ${Object.entries(DIAGRAM_SHAPES)
|
|
3624
|
+
.map(([図種, 語]) => `type: ${図種} (${語.join(" / ")})`)
|
|
3625
|
+
.join(" / ")}`;
|
|
3626
|
+
errors.push({
|
|
3627
|
+
path: "$.shape",
|
|
3628
|
+
message: `shape is not valid for type: ${type}`,
|
|
3629
|
+
hint,
|
|
3630
|
+
});
|
|
3631
|
+
}
|
|
3632
|
+
|
|
2431
3633
|
/**
|
|
2432
3634
|
* 段の中で値を動かす指定を見る (#1186)。
|
|
2433
3635
|
*
|
|
@@ -2553,6 +3755,20 @@ function validateValues(v: unknown, errors: JsonDslError[]): void {
|
|
|
2553
3755
|
}
|
|
2554
3756
|
}
|
|
2555
3757
|
|
|
3758
|
+
/**
|
|
3759
|
+
* 段の説明を取り出す (#2621)。 受ける語は `PHASE_BODY_KEYS` が持つ。
|
|
3760
|
+
*
|
|
3761
|
+
* **正の語 (`body`) を先に見る**。 表の先頭が正の語なので、並び順のまま最初に値を持つ語を
|
|
3762
|
+
* 採れば「どちらが効くか」 の規則を別に書かなくて済む。
|
|
3763
|
+
*/
|
|
3764
|
+
function 段の説明(p: JsonPhase): string | undefined {
|
|
3765
|
+
for (const key of PHASE_BODY_KEYS) {
|
|
3766
|
+
const v = p[key];
|
|
3767
|
+
if (typeof v === "string") return v;
|
|
3768
|
+
}
|
|
3769
|
+
return undefined;
|
|
3770
|
+
}
|
|
3771
|
+
|
|
2556
3772
|
/**
|
|
2557
3773
|
* 図表の箱の上の小見出しを、 記法側と同じ形に整える (#1247)。
|
|
2558
3774
|
*
|
|
@@ -2606,6 +3822,7 @@ export function jsonToDoc(json: DragonJson, 行の表?: 書いた行の表): Dsl
|
|
|
2606
3822
|
// 色は記法と同じ振り分けを通す (#1294)。 `#` で始まれば色番号、それ以外は色の名前。
|
|
2607
3823
|
// 別々に書くと、同じ値が入口によって色番号にも色名にもなる
|
|
2608
3824
|
const 色 = a.color !== undefined ? splitColorValue(a.color) : {};
|
|
3825
|
+
const 書いた座標 = a.at ?? a.点の位置;
|
|
2609
3826
|
return {
|
|
2610
3827
|
name: a.name,
|
|
2611
3828
|
kind: isPart ? "actor" : resolveNodeKind(kindStr),
|
|
@@ -2613,12 +3830,33 @@ export function jsonToDoc(json: DragonJson, 行の表?: 書いた行の表): Dsl
|
|
|
2613
3830
|
// 「書かなかった」 と同じ扱いにする (#1058)
|
|
2614
3831
|
kindWritten: a.kind !== undefined && !isPart,
|
|
2615
3832
|
subtitle: a.subtitle,
|
|
3833
|
+
kindForm: isPart ? undefined : a.kindForm,
|
|
3834
|
+
icon: isPart ? undefined : a.icon,
|
|
3835
|
+
emptyRowsNote: isPart ? undefined : a.emptyRowsNote,
|
|
3836
|
+
titleAlign: isPart ? undefined : a.titleAlign,
|
|
3837
|
+
rowRules: isPart ? undefined : a.rowRules,
|
|
3838
|
+
rowStripe: isPart ? undefined : a.rowStripe,
|
|
3839
|
+
rowLayout: isPart ? undefined : a.rowLayout,
|
|
3840
|
+
rowDividerForm: isPart ? undefined : a.rowDividerForm,
|
|
3841
|
+
rowWell: isPart ? undefined : a.rowWell,
|
|
3842
|
+
rowMarkForm: isPart ? undefined : a.rowMarkForm,
|
|
3843
|
+
nestIn: isPart ? undefined : a.nestIn,
|
|
3844
|
+
figureCard: isPart ? undefined : a.figureCard,
|
|
3845
|
+
stationNamePosition: a.stationNamePosition,
|
|
3846
|
+
subtitlePlacement: a.subtitlePlacement,
|
|
3847
|
+
titleFontSize: a.titleFontSize,
|
|
3848
|
+
markGap: a.markGap,
|
|
2616
3849
|
eyebrow: a.eyebrow,
|
|
2617
3850
|
value: a.value,
|
|
2618
3851
|
previous: a.previous,
|
|
2619
3852
|
rows: a.rows,
|
|
2620
|
-
// 行頭の印 (#1466)。
|
|
2621
|
-
marks: a.marks
|
|
3853
|
+
// 行頭の印 (#1466)。 行と対で読む。 古い語は記法と同じ関数で読み替える (#2782)
|
|
3854
|
+
marks: a.marks?.map((m) =>
|
|
3855
|
+
typeof m === "string"
|
|
3856
|
+
? 行頭の語へ読み替える(m)
|
|
3857
|
+
: { mark: 行頭の語へ読み替える(m.mark), ...(m.tone === undefined ? {} : { tone: m.tone }) },
|
|
3858
|
+
),
|
|
3859
|
+
stage: isPart ? undefined : a.stage,
|
|
2622
3860
|
lane: a.lane,
|
|
2623
3861
|
stack: a.stack,
|
|
2624
3862
|
initial: a.initial,
|
|
@@ -2642,8 +3880,17 @@ export function jsonToDoc(json: DragonJson, 行の表?: 書いた行の表): Dsl
|
|
|
2642
3880
|
tone: resolveTone(a.tone) ?? 色.tone,
|
|
2643
3881
|
owner: a.owner,
|
|
2644
3882
|
end: a.end,
|
|
3883
|
+
startLabel: a.startLabel,
|
|
3884
|
+
milestone: a.milestone,
|
|
3885
|
+
emphasis: a.emphasis,
|
|
3886
|
+
at:
|
|
3887
|
+
書いた座標 === undefined
|
|
3888
|
+
? undefined
|
|
3889
|
+
: { x: 書いた座標[0], y: 書いた座標[1], raw: `[${書いた座標.join(", ")}]` },
|
|
3890
|
+
atPos: 書いた座標 === undefined ? undefined : 位置("actors", i, a.at ? "at" : "点の位置"),
|
|
2645
3891
|
touchpoint: a.touchpoint,
|
|
2646
3892
|
opportunity: a.opportunity,
|
|
3893
|
+
opportunityPosition: a.opportunityPosition,
|
|
2647
3894
|
}),
|
|
2648
3895
|
colorHex: 色.hex,
|
|
2649
3896
|
// 部品に書いた色の名前は効かない。 記法と同じく書いた名前として残し、組み立て側が知らせる (#1973)
|
|
@@ -2666,8 +3913,17 @@ export function jsonToDoc(json: DragonJson, 行の表?: 書いた行の表): Dsl
|
|
|
2666
3913
|
pos: 位置("actors", i),
|
|
2667
3914
|
};
|
|
2668
3915
|
});
|
|
2669
|
-
//
|
|
2670
|
-
const
|
|
3916
|
+
// 図の意匠 (#1553 / #2790)。 正の語と別名を別々に解き、順番に依らず `theme` を優先する
|
|
3917
|
+
const 意匠 = json.theme === undefined ? null : resolveTheme(json.theme);
|
|
3918
|
+
const 別名の意匠 = json.palette === undefined ? null : resolveTheme(json.palette);
|
|
3919
|
+
const 選んだ意匠 = 意匠 ?? 別名の意匠;
|
|
3920
|
+
const 並び順 = json.order === undefined ? null : resolveOrder(json.order);
|
|
3921
|
+
const 形 = json.shape === undefined ? null : resolveDiagramShape(json.shape);
|
|
3922
|
+
const legend = json.legend?.flatMap((item) => {
|
|
3923
|
+
if (item.mark === undefined) return [item];
|
|
3924
|
+
const mark = resolveLegendMark(item.mark);
|
|
3925
|
+
return mark === null ? [] : [{ ...item, mark } as CdlLegendItem];
|
|
3926
|
+
});
|
|
2671
3927
|
|
|
2672
3928
|
const flow: DslStep[] = json.flow.map((s, i) => ({
|
|
2673
3929
|
no: i + 1,
|
|
@@ -2676,13 +3932,15 @@ export function jsonToDoc(json: DragonJson, 行の表?: 書いた行の表): Dsl
|
|
|
2676
3932
|
label: s.label,
|
|
2677
3933
|
sub: s.sub,
|
|
2678
3934
|
side: s.side,
|
|
3935
|
+
fromSide: s.fromSide,
|
|
3936
|
+
toSide: s.toSide,
|
|
2679
3937
|
// 矢印の先の形 (#1462)。 読めない語は組み立てが落とす
|
|
2680
3938
|
head: s.head,
|
|
2681
3939
|
// 端の印の残り 3 欄と、関係の語 / 言づての種類 (#1466)
|
|
2682
3940
|
tailHead: s.tailHead,
|
|
2683
3941
|
headFill: s.headFill,
|
|
2684
3942
|
tailHeadFill: s.tailHeadFill,
|
|
2685
|
-
//
|
|
3943
|
+
// UML 関係の出どころ側の多重度 (#1771 / #2783)
|
|
2686
3944
|
tailSub: s.tailSub,
|
|
2687
3945
|
relation: s.relation,
|
|
2688
3946
|
// 辺の役目と名前の下地 (cdl#618)
|
|
@@ -2701,6 +3959,13 @@ export function jsonToDoc(json: DragonJson, 行の表?: 書いた行の表): Dsl
|
|
|
2701
3959
|
dashOffsetBind: s.dashOffsetBind,
|
|
2702
3960
|
labelOffsetX: s.labelOffsetX,
|
|
2703
3961
|
labelOffsetY: s.labelOffsetY,
|
|
3962
|
+
posX1: s.posX1,
|
|
3963
|
+
posY1: s.posY1,
|
|
3964
|
+
posX2: s.posX2,
|
|
3965
|
+
posY2: s.posY2,
|
|
3966
|
+
waypoints: s.waypoints,
|
|
3967
|
+
labelCollision: s.labelCollision,
|
|
3968
|
+
labelSize: s.labelSize,
|
|
2704
3969
|
overlay: s.overlay,
|
|
2705
3970
|
// 部品の端で繋ぐ要素の id (#1979)。 書かない矢印には欄を足さない = 記法と同じ形になる
|
|
2706
3971
|
...(s.fromPartNode !== undefined ? { fromPartNode: s.fromPartNode } : {}),
|
|
@@ -2763,7 +4028,7 @@ export function jsonToDoc(json: DragonJson, 行の表?: 書いた行の表): Dsl
|
|
|
2763
4028
|
name: p.step,
|
|
2764
4029
|
durationMs: Math.round((p.duration ?? 1.4) * 1000),
|
|
2765
4030
|
highlight: p.focus,
|
|
2766
|
-
body: p
|
|
4031
|
+
body: 段の説明(p),
|
|
2767
4032
|
badge: p.badge,
|
|
2768
4033
|
// 書いた段だけが欄を持つ。 空文字を置くと「書いた」 と「書いていない」 が同じ形になる
|
|
2769
4034
|
...(p.draw !== undefined ? { draw: p.draw, drawPos: 位置("animation", i, "draw") } : {}),
|
|
@@ -2799,9 +4064,82 @@ export function jsonToDoc(json: DragonJson, 行の表?: 書いた行の表): Dsl
|
|
|
2799
4064
|
// `states` を指す = どちらも「動きを書き始めた場所」 になる
|
|
2800
4065
|
{ states, phases, pos: phases.length > 0 ? 位置("animation") : 位置("states") }
|
|
2801
4066
|
: undefined;
|
|
4067
|
+
// **別名は記法と同じ表で開く** (#2655 / #2657)。 開いた後は古い綴りが残らない。
|
|
4068
|
+
//
|
|
4069
|
+
// **開く語を 1 つずつ書く** = 表の値をそのまま広げると、持たない語が `undefined` の
|
|
4070
|
+
// まま欄に載り、後ろの `...(形 !== null ? ...)` を打ち消す (実測 = `type: "pie"` の
|
|
4071
|
+
// JSON が形を持たず、書かない時の形 (棒) の図になった)
|
|
4072
|
+
const 別名 = TYPE_ALIASES.get(json.type);
|
|
4073
|
+
const form = json.form ?? json.見せ方;
|
|
4074
|
+
const ticks = json.ticks ?? json.目盛り;
|
|
2802
4075
|
return {
|
|
2803
4076
|
title: json.title,
|
|
2804
|
-
type: json.type,
|
|
4077
|
+
type: 別名?.type ?? (json.type as PresetType),
|
|
4078
|
+
...(form !== undefined
|
|
4079
|
+
? { form, formPos: 位置(json.form !== undefined ? "form" : "見せ方") }
|
|
4080
|
+
: {}),
|
|
4081
|
+
...(ticks !== undefined
|
|
4082
|
+
? { ticks: [...ticks], ticksPos: 位置(json.ticks !== undefined ? "ticks" : "目盛り") }
|
|
4083
|
+
: {}),
|
|
4084
|
+
...(json.ganttToday !== undefined ? { ganttToday: json.ganttToday } : {}),
|
|
4085
|
+
...(json.ganttTickLabels !== undefined ? { ganttTickLabels: [...json.ganttTickLabels] } : {}),
|
|
4086
|
+
...(json.ganttBarEnd !== undefined ? { ganttBarEnd: json.ganttBarEnd } : {}),
|
|
4087
|
+
...(json.ganttBarThickness !== undefined ? { ganttBarThickness: json.ganttBarThickness } : {}),
|
|
4088
|
+
...(json.chartSeriesSkipMuted !== undefined ? { chartSeriesSkipMuted: json.chartSeriesSkipMuted } : {}),
|
|
4089
|
+
...(json.chartLineSeries !== undefined ? { chartLineSeries: json.chartLineSeries } : {}),
|
|
4090
|
+
...(json.chartPieCenterLabel !== undefined ? { chartPieCenterLabel: json.chartPieCenterLabel } : {}),
|
|
4091
|
+
...(json.chartPieTableColumns !== undefined ? { chartPieTableColumns: json.chartPieTableColumns } : {}),
|
|
4092
|
+
...(json.chartPieRingWidth !== undefined ? { chartPieRingWidth: json.chartPieRingWidth } : {}),
|
|
4093
|
+
...(json.chartSlopePeriods !== undefined ? { chartSlopePeriods: json.chartSlopePeriods } : {}),
|
|
4094
|
+
...(json.chartSlopeEmphasisIds !== undefined ? { chartSlopeEmphasisIds: json.chartSlopeEmphasisIds } : {}),
|
|
4095
|
+
...(json.chartSlopeUnit !== undefined ? { chartSlopeUnit: json.chartSlopeUnit } : {}),
|
|
4096
|
+
...(json.chartStackedPeriods !== undefined ? { chartStackedPeriods: json.chartStackedPeriods } : {}),
|
|
4097
|
+
...(json.chartStackedRateId !== undefined ? { chartStackedRateId: json.chartStackedRateId } : {}),
|
|
4098
|
+
...(json.chartStackedLegendPosition !== undefined ? { chartStackedLegendPosition: json.chartStackedLegendPosition } : {}),
|
|
4099
|
+
...(json.chartGaugeValue !== undefined ? { chartGaugeValue: json.chartGaugeValue } : {}),
|
|
4100
|
+
...(json.chartWaffleLegendPosition !== undefined ? { chartWaffleLegendPosition: json.chartWaffleLegendPosition } : {}),
|
|
4101
|
+
...(json.funnelForm !== undefined ? { funnelForm: json.funnelForm } : {}),
|
|
4102
|
+
...(json.funnelRate !== undefined ? { funnelRate: json.funnelRate } : {}),
|
|
4103
|
+
...(json.quadrantPointLabelSide !== undefined ? { quadrantPointLabelSide: json.quadrantPointLabelSide } : {}),
|
|
4104
|
+
...(json.treeNodeForm !== undefined ? { treeNodeForm: json.treeNodeForm } : {}),
|
|
4105
|
+
...(json.treeSize !== undefined ? { treeSize: json.treeSize } : {}),
|
|
4106
|
+
...(json.treeEdgeTone !== undefined ? { treeEdgeTone: json.treeEdgeTone } : {}),
|
|
4107
|
+
...(json.treeEdgeHead !== undefined ? { treeEdgeHead: json.treeEdgeHead } : {}),
|
|
4108
|
+
...(json.mindForm !== undefined ? { mindForm: json.mindForm } : {}),
|
|
4109
|
+
...(json.mindSize !== undefined ? { mindSize: json.mindSize } : {}),
|
|
4110
|
+
...(json.edgeEndGap !== undefined ? { edgeEndGap: json.edgeEndGap } : {}),
|
|
4111
|
+
...(json.journeyForm !== undefined ? { journeyForm: json.journeyForm } : {}),
|
|
4112
|
+
...(json.journeyLineForm !== undefined ? { journeyLineForm: json.journeyLineForm } : {}),
|
|
4113
|
+
...(json.journeyLabels !== undefined ? { journeyLabels: json.journeyLabels } : {}),
|
|
4114
|
+
...(json.journeySize !== undefined ? { journeySize: json.journeySize } : {}),
|
|
4115
|
+
...(json.sequenceActorForm !== undefined ? { sequenceActorForm: json.sequenceActorForm } : {}),
|
|
4116
|
+
...(json.sequenceLabelForm !== undefined ? { sequenceLabelForm: json.sequenceLabelForm } : {}),
|
|
4117
|
+
...(json.sequenceReturnHead !== undefined ? { sequenceReturnHead: json.sequenceReturnHead } : {}),
|
|
4118
|
+
...(json.sequenceKindLabels !== undefined ? { sequenceKindLabels: json.sequenceKindLabels } : {}),
|
|
4119
|
+
...(json.sequenceRowGap !== undefined ? { sequenceRowGap: json.sequenceRowGap } : {}),
|
|
4120
|
+
...(json.sequenceGutter !== undefined ? { sequenceGutter: json.sequenceGutter } : {}),
|
|
4121
|
+
...(json.sequenceBandWidth !== undefined ? { sequenceBandWidth: json.sequenceBandWidth } : {}),
|
|
4122
|
+
...(json.sequenceSelfLoop !== undefined ? { sequenceSelfLoop: json.sequenceSelfLoop } : {}),
|
|
4123
|
+
...(json.figureCard !== undefined ? { figureCard: json.figureCard } : {}),
|
|
4124
|
+
...(json.figureSize !== undefined ? { figureSize: json.figureSize } : {}),
|
|
4125
|
+
...(json.terminalDecoration !== undefined ? { terminalDecoration: json.terminalDecoration } : {}),
|
|
4126
|
+
...(json.cornerForm !== undefined ? { cornerForm: json.cornerForm } : {}),
|
|
4127
|
+
...(json.figureKindLabel !== undefined ? { figureKindLabel: json.figureKindLabel } : {}),
|
|
4128
|
+
...(json.figureInset !== undefined ? { figureInset: json.figureInset } : {}),
|
|
4129
|
+
...(json.chartBarSize !== undefined ? { chartBarSize: json.chartBarSize } : {}),
|
|
4130
|
+
...(json.chartBarShape !== undefined ? { chartBarShape: json.chartBarShape } : {}),
|
|
4131
|
+
...(json.chartLineSize !== undefined ? { chartLineSize: json.chartLineSize } : {}),
|
|
4132
|
+
...(json.chartPieSize !== undefined ? { chartPieSize: json.chartPieSize } : {}),
|
|
4133
|
+
...(json.chartWaffleSize !== undefined ? { chartWaffleSize: json.chartWaffleSize } : {}),
|
|
4134
|
+
...(json.chartSlopeLabelTemplate !== undefined ? { chartSlopeLabelTemplate: json.chartSlopeLabelTemplate } : {}),
|
|
4135
|
+
...(json.chartSlopeSize !== undefined ? { chartSlopeSize: json.chartSlopeSize } : {}),
|
|
4136
|
+
...(json.chartStackedLayout !== undefined ? { chartStackedLayout: json.chartStackedLayout } : {}),
|
|
4137
|
+
...(json.chartGaugeSize !== undefined ? { chartGaugeSize: json.chartGaugeSize } : {}),
|
|
4138
|
+
...(json.ganttSize !== undefined ? { ganttSize: json.ganttSize } : {}),
|
|
4139
|
+
...(json.quadrantLabelPosition !== undefined ? { quadrantLabelPosition: json.quadrantLabelPosition } : {}),
|
|
4140
|
+
...(json.quadrantSize !== undefined ? { quadrantSize: json.quadrantSize } : {}),
|
|
4141
|
+
...(別名?.order !== undefined ? { order: 別名.order, orderPos: 位置("type") } : {}),
|
|
4142
|
+
...(別名?.shape !== undefined ? { shape: 別名.shape, shapePos: 位置("type") } : {}),
|
|
2805
4143
|
// 前後の空白を落としてから見る。 記法側 (`v05/parser.ts`) が `trim()` してから
|
|
2806
4144
|
// 空かどうかを判定するため、 揃えないと **空白だけの値で入口ごとに図が変わる**
|
|
2807
4145
|
// (記法は書かなかった扱い、 JSON は中身のない帯を描く。 Round 2 の指摘で実測)
|
|
@@ -2813,6 +4151,14 @@ export function jsonToDoc(json: DragonJson, 行の表?: 書いた行の表): Dsl
|
|
|
2813
4151
|
...(json.axes && (json.axes.x !== undefined || json.axes.y !== undefined)
|
|
2814
4152
|
? { axes: json.axes, axesPos: 位置("axes") }
|
|
2815
4153
|
: {}),
|
|
4154
|
+
// 区画の名前 (#2667)。 鍵は記法と同じ 4 語なので、描画側の綴りへここで直す
|
|
4155
|
+
...(区画の名前を直す(json.regions) === undefined
|
|
4156
|
+
? {}
|
|
4157
|
+
: { regions: 区画の名前を直す(json.regions) }),
|
|
4158
|
+
...(json.legendFontSize !== undefined ? { legendFontSize: json.legendFontSize } : {}),
|
|
4159
|
+
...(json.legendOffset !== undefined ? { legendOffset: json.legendOffset } : {}),
|
|
4160
|
+
...(json.stageHeader !== undefined ? { stageHeader: json.stageHeader } : {}),
|
|
4161
|
+
...(json.stageLayout !== undefined ? { stageLayout: json.stageLayout } : {}),
|
|
2816
4162
|
actors,
|
|
2817
4163
|
flow,
|
|
2818
4164
|
animate,
|
|
@@ -2863,9 +4209,22 @@ export function jsonToDoc(json: DragonJson, 行の表?: 書いた行の表): Dsl
|
|
|
2863
4209
|
directionPos: 位置("direction"),
|
|
2864
4210
|
}
|
|
2865
4211
|
: {}),
|
|
2866
|
-
//
|
|
2867
|
-
// 読めない語は渡さない =
|
|
2868
|
-
...(
|
|
4212
|
+
// 箱を並べ替える軸 (#2655)。 向きと同じく英語で書くので、記法と同じ解決を通して日本語に直す。
|
|
4213
|
+
// 読めない語は渡さない = 公開 schema の語の一覧が絞っているので、ここに来るのは書き間違いだけ
|
|
4214
|
+
...(並び順 !== null ? { order: 並び順, orderPos: 位置("order") } : {}),
|
|
4215
|
+
// 数を描く図の形 (#2657)。 記法と同じ解決を通す = 受ける語がずれない
|
|
4216
|
+
...(形 !== null ? { shape: 形, shapePos: 位置("shape") } : {}),
|
|
4217
|
+
// 図の意匠 (#1553 / #2790)。 dragon の文書では `theme`、cdl の図へ載せる時は `palette` とする
|
|
4218
|
+
...(選んだ意匠 !== null ? { theme: 選んだ意匠 } : {}),
|
|
4219
|
+
...(legend && legend.length > 0 ? { legend } : {}),
|
|
4220
|
+
...(意匠 !== null && 別名の意匠 !== null
|
|
4221
|
+
? {
|
|
4222
|
+
themeAlsoPalettePos: {
|
|
4223
|
+
themeLine: 位置("theme").line,
|
|
4224
|
+
paletteLine: 位置("palette").line,
|
|
4225
|
+
},
|
|
4226
|
+
}
|
|
4227
|
+
: {}),
|
|
2869
4228
|
pos: 位置(),
|
|
2870
4229
|
};
|
|
2871
4230
|
}
|