@cardenelabs/dragon 0.15.0 → 0.16.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardenelabs/dragon",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Dragon — Mermaid 感覚で animated SVG を生成する Text DSL。 cdl engine を内部利用。",
5
5
  "license": "MIT",
6
6
  "author": "cardene777",
@@ -46,8 +46,18 @@
46
46
  "node": ">=18"
47
47
  },
48
48
  "sideEffects": false,
49
+ "scripts": {
50
+ "build": "tsup",
51
+ "dev": "tsup --watch",
52
+ "typecheck": "tsc --noEmit",
53
+ "typecheck:test": "tsc --noEmit -p tsconfig.test.json",
54
+ "test": "cd ../.. && vitest run packages/dragon",
55
+ "test:watch": "cd ../.. && vitest packages/dragon",
56
+ "lint": "tsc --noEmit",
57
+ "prepublishOnly": "pnpm run typecheck && pnpm run test && pnpm run build"
58
+ },
49
59
  "dependencies": {
50
- "@cardenelabs/cdl": "^0.14.0"
60
+ "@cardenelabs/cdl": "^0.15.0"
51
61
  },
52
62
  "peerDependencies": {
53
63
  "react": "^19.0.0",
@@ -66,14 +76,5 @@
66
76
  "react": "^19.1.0",
67
77
  "tsup": "^8.5.1",
68
78
  "typescript": "^5.8.0"
69
- },
70
- "scripts": {
71
- "build": "tsup",
72
- "dev": "tsup --watch",
73
- "typecheck": "tsc --noEmit",
74
- "typecheck:test": "tsc --noEmit -p tsconfig.test.json",
75
- "test": "cd ../.. && vitest run packages/dragon",
76
- "test:watch": "cd ../.. && vitest packages/dragon",
77
- "lint": "tsc --noEmit"
78
79
  }
79
- }
80
+ }
package/src/compile.ts CHANGED
@@ -255,6 +255,21 @@ export function compileToCdl(doc: DslDocument, opts?: CompileToCdlOpts): CdlDiag
255
255
  case "line":
256
256
  diagram = compileValueChart(doc, "line", "chart-line", opts?.onNotice);
257
257
  break;
258
+ case "gauge":
259
+ diagram = compileValueChart(doc, "gauge", "chart-gauge", opts?.onNotice);
260
+ break;
261
+ case "radial":
262
+ diagram = compileValueChart(doc, "radial", "chart-radial", opts?.onNotice);
263
+ break;
264
+ case "stat":
265
+ diagram = compileValueChart(doc, "stat", "chart-stat", opts?.onNotice);
266
+ break;
267
+ case "waffle":
268
+ diagram = compileValueChart(doc, "waffle", "chart-waffle", opts?.onNotice);
269
+ break;
270
+ case "stacked":
271
+ diagram = compileValueChart(doc, "stacked", "chart-stacked-bar", opts?.onNotice);
272
+ break;
258
273
  case "funnel":
259
274
  diagram = compileFunnel(doc, opts?.onNotice);
260
275
  break;
@@ -700,6 +715,11 @@ const 図種の作り: Record<PresetType, "登場人物ごとに箱" | "図全
700
715
  pie: "図全体を 1 箱",
701
716
  bar: "図全体を 1 箱",
702
717
  line: "図全体を 1 箱",
718
+ gauge: "図全体を 1 箱",
719
+ radial: "図全体を 1 箱",
720
+ stat: "図全体を 1 箱",
721
+ waffle: "図全体を 1 箱",
722
+ stacked: "図全体を 1 箱",
703
723
  funnel: "図全体を 1 箱",
704
724
  tree: "図全体を 1 箱",
705
725
  journey: "図全体を 1 箱",
@@ -1062,6 +1082,11 @@ const 空の形の逃げ先: Record<PresetType, string[]> = {
1062
1082
  pie: [],
1063
1083
  bar: [],
1064
1084
  line: [],
1085
+ gauge: [],
1086
+ radial: [],
1087
+ stat: [],
1088
+ waffle: [],
1089
+ stacked: [],
1065
1090
  funnel: [],
1066
1091
  tree: [],
1067
1092
  journey: [],
@@ -4591,8 +4616,16 @@ function 参照する名前(value: number | string | null): string | null {
4591
4616
  */
4592
4617
  function compileValueChart(
4593
4618
  doc: DslDocument,
4594
- 型: "pie" | "bar" | "line",
4595
- kind: "chart-pie" | "chart-bar" | "chart-line",
4619
+ 型: "pie" | "bar" | "line" | "gauge" | "radial" | "stat" | "waffle" | "stacked",
4620
+ kind:
4621
+ | "chart-pie"
4622
+ | "chart-bar"
4623
+ | "chart-line"
4624
+ | "chart-gauge"
4625
+ | "chart-radial"
4626
+ | "chart-stat"
4627
+ | "chart-waffle"
4628
+ | "chart-stacked-bar",
4596
4629
  onNotice?: (notice: CompileNotice) => void,
4597
4630
  ): CdlDiagram {
4598
4631
  const b = diagram(slugify(doc.title), { topic: doc.title });
@@ -4600,11 +4633,19 @@ function compileValueChart(
4600
4633
  // **高さは型で違い、 格子に載せる**。 描画側 (`cdl` の `chart()` preset) は `pie` を 320、
4601
4634
  // 棒と折れ線を 360 とした上で **16 の倍数へ切り上げる** (360 は 16 で割り切れないので 368)。
4602
4635
  // 切り上げないと下端が格子から外れ、 全図で位置の警告が出る (review 指摘)
4603
- const CHART_H = === "pie" ? 320 : 368;
4636
+ // 半円と弧は縦を使わないので円と同じ 320。 描画側 (`cdl` `chart()` preset)
4637
+ // `pie` / `gauge` / `radial` を 320、棒と折れ線を 360 とし、16 の倍数へ切り上げる
4638
+ // 縦に余白が要らない型。 描画側 (`cdl` の `chart()` preset) と揃える。
4639
+ // 半円 / 弧 / 割合の印 は縦を使わず、値 1 つを大きく示す図も縦に伸びない
4640
+ const 低い型 =
4641
+ 型 === "pie" || 型 === "gauge" || 型 === "radial" || 型 === "waffle" || 型 === "stat";
4642
+ const CHART_H = 低い型 ? 320 : 368;
4604
4643
  b.lane("chart", { width: CHART_W + 64 });
4605
4644
 
4606
4645
  const data: NonNullable<CdlDiagram["nodes"][number]["chartData"]> = [];
4607
4646
  const 読めない: string[] = [];
4647
+ const 前が読めない: string[] = [];
4648
+ let 前が読めない行 = 0;
4608
4649
  const 未宣言: string[] = [];
4609
4650
  let 未宣言行 = 0;
4610
4651
  const 参照できる = 数の欄から参照できる名前(doc);
@@ -4631,16 +4672,37 @@ function compileValueChart(
4631
4672
  continue;
4632
4673
  }
4633
4674
  // 色はそのまま渡す。 箱が 1 つになっても、 書いた色が消えないようにする
4634
- data.push({ label: 箱の題(a), value, ...(a.tone !== undefined ? { tone: a.tone } : {}) });
4675
+ // 前の時点の値 (#1450) 読めない形は黙って捨てず知らせる = 書いたのに 2 本目の帯が
4676
+ // 出ない状態になり、手掛かりが残らない
4677
+ const 前 = a.previous === undefined ? null : parseChartValue(a.previous);
4678
+ if (a.previous !== undefined && 前 === null) {
4679
+ if (前が読めない.length === 0) 前が読めない行 = a.pos?.line ?? 0;
4680
+ 前が読めない.push(a.name);
4681
+ }
4682
+ data.push({
4683
+ label: 箱の題(a),
4684
+ value,
4685
+ ...(前 === null ? {} : { previous: 前 }),
4686
+ ...(a.tone !== undefined ? { tone: a.tone } : {}),
4687
+ });
4635
4688
  }
4636
4689
  // 案内の言葉は型ごとに変える。 共通化した時に `pie` の「割合 / 円 / 45%」 が「値 / 図 / 45」 に
4637
4690
  // 薄まり、 既存の案内が後退した (review 指摘)。 何を書けばよいかは型ごとに違う
4638
- const =
4639
- 型 === "pie"
4640
- ? { 量: "割合", 図: "円", 例: '"45%"' }
4641
- : === "bar"
4642
- ? { 量: "値", 図: "棒", 例: '"420"' }
4643
- : { 量: "", 図: "折れ線", 例: '"180"' };
4691
+ // 何を書けばよいかは型ごとに違う。 まとめると「割合 / 円 / 45%」 が「値 / 図 / 45」 に薄まる
4692
+ //
4693
+ // **表で持つ**。 三項の連鎖にすると、型が増えるたびに深さが増え、最後の枝が
4694
+ // 「それ以外」 になるため型検査が新しい型の漏れを教えてくれない
4695
+ const 語の表: Record<typeof 型, { 量: string; 図: string; 例: string }> = {
4696
+ pie: { 量: "割合", 図: "", 例: '"45%"' },
4697
+ bar: { 量: "値", 図: "棒", 例: '"420"' },
4698
+ line: { 量: "値", 図: "折れ線", 例: '"180"' },
4699
+ gauge: { 量: "値", 図: "半円", 例: '"680"' },
4700
+ radial: { 量: "値", 図: "弧", 例: '"72"' },
4701
+ stat: { 量: "値", 図: "大きな数字", 例: '"1200"' },
4702
+ waffle: { 量: "割合", 図: "100 個の印", 例: '"45%"' },
4703
+ stacked: { 量: "内訳の値", 図: "帯", 例: '"320"' },
4704
+ };
4705
+ const 語 = 語の表[型];
4644
4706
 
4645
4707
  /**
4646
4708
  * 利用者に伝える。 **`console.warn` だけにしない**。 エディタは受け取った notice を画面に
@@ -4669,6 +4731,15 @@ function compileValueChart(
4669
4731
  未宣言行,
4670
4732
  );
4671
4733
  }
4734
+ if (前が読めない.length > 0) {
4735
+ 伝える(
4736
+ "chart-value-unreadable",
4737
+ 前が読めない[0]!,
4738
+ `type: ${型} で前の時点の値を読めない項目があります (前の値を使いません): ${前が読めない.join(", ")}。` +
4739
+ " `- 名前: { value: " + 語.例 + ", previous: " + 語.例 + " }` の形で書いてください",
4740
+ 前が読めない行,
4741
+ );
4742
+ }
4672
4743
  if (doc.flow.length > 0) {
4673
4744
  伝える(
4674
4745
  "chart-edge-dropped",
@@ -5493,6 +5564,8 @@ type 放射で描けない欄 =
5493
5564
  | "stack"
5494
5565
  | "initial"
5495
5566
  | "final"
5567
+ // 前の時点の値 (#1450)。 放射の枝は 1 時点しか描かない = 2 本目の帯に当たるものが無い
5568
+ | "previous"
5496
5569
  | "colorHex"
5497
5570
  | "stateOverride"
5498
5571
  | "posX"
@@ -5556,6 +5629,7 @@ const 放射で描けない欄の名前: Record<放射で描けない欄, string
5556
5629
  stack: "積む順",
5557
5630
  initial: "始まり / 終わり の印",
5558
5631
  final: "始まり / 終わり の印",
5632
+ previous: "前の時点の値",
5559
5633
  colorHex: "色番号",
5560
5634
  stateOverride: "状態の上書き",
5561
5635
  // 位置は「登場人物ごとの箱をどこに置くか」 の指定で、 箱が 1 つの図では置く先が無い
@@ -6174,6 +6248,9 @@ function injectPhasesFallback(diagram: CdlDiagram, doc: DslDocument): void {
6174
6248
  body: p.body ?? "",
6175
6249
  activate: activateIds,
6176
6250
  ...(drawIds.length > 0 ? { draw: drawIds } : {}),
6251
+ // 描く速さ (#1441)。 **描く相手が決まった段にだけ載せる** = 語が効かない図種
6252
+ // (`drawIds` が空) で割合だけ渡すと、描画側が「draw が空なのに割合がある」 と知らせる
6253
+ ...(drawIds.length > 0 && p.drawRatio !== undefined ? { drawRatio: p.drawRatio } : {}),
6177
6254
  tweens: (p.tweens ?? []).map((t) => ({ stateId: t.state, from: t.from, to: t.to })),
6178
6255
  sets: (p.sets ?? []).map((s) => ({ stateId: s.state, value: s.value })),
6179
6256
  ...(p.badge ? { badge: p.badge } : {}),
@@ -6570,9 +6647,35 @@ function 始まりと終わりの決め方(doc: DslDocument): {
6570
6647
  };
6571
6648
  }
6572
6649
 
6650
+ /** 状態の図で `stateMachine` preset が全ての箱に使う種類 */
6651
+ const 状態の図の既定の種類: NodeKind = "card";
6652
+
6653
+ /**
6654
+ * 状態の図で、既定と違う種類を書いた登場人物がいるか (#1450)。
6655
+ *
6656
+ * `stateMachine` preset は全ての状態を `card` で描くため、書いた `kind:` が黙って消える。
6657
+ * 始点終点の印 (`mark-start` / `mark-end`) を書けるようにするには、書いた形を効かせる必要がある。
6658
+ *
6659
+ * **「書いたか」 だけでは広すぎる**。 記法だけの種類 (`kind: state` 等) は `card` に読み替わり、
6660
+ * preset の出す形と同じになる = 書いても何も変わらないので経路を切り替える理由が無い。
6661
+ * 切り替えると既存の図の id と枠の作りが変わる (実測で golden 8 件が落ちた)。
6662
+ *
6663
+ * 読み替えた後の種類が既定と違う時だけ切り替える。 こうすると「書いたのに効かない」 は消え、
6664
+ * 「書いたが結果が同じ」 は従来の経路に留まる。
6665
+ */
6666
+ function 既定と違う種類を書いた(doc: DslDocument): boolean {
6667
+ return doc.actors.some(
6668
+ (a) => a.kindWritten === true && 描ける種別(a.kind) !== 状態の図の既定の種類,
6669
+ );
6670
+ }
6671
+
6573
6672
  function compileState(doc: DslDocument): CdlDiagram {
6574
6673
  // v0.4 ... animation あり時 builder 直接経路 (各 state を lane で配置、 transition を edge)
6575
- if (doc.animate && doc.animate.phases.length > 0) {
6674
+ //
6675
+ // **種類を書いた形は動きの有無に関わらず generic 経路へ** (#1450、 #1263 と同じ理由)。
6676
+ // 動く図だけで効かせると、同じ記法でも静止図では指定が黙って消える
6677
+ // (実測 = `kind: mark-start` を書いた箱が `card` になり知らせも出ない)
6678
+ if ((doc.animate && doc.animate.phases.length > 0) || 既定と違う種類を書いた(doc)) {
6576
6679
  return compileGenericWithAnimate(doc, { kind: "state", laneWidth: 360 });
6577
6680
  }
6578
6681
  // stateMachine preset ... actors を state に、 流れ を transition に
@@ -212,6 +212,13 @@ export interface JsonActor {
212
212
  subtitle?: string;
213
213
  eyebrow?: string;
214
214
  value?: string;
215
+ /**
216
+ * 前の時点の値 (#1450)。 記法の `{ value: "320", previous: "280" }` と同じ。
217
+ *
218
+ * 内訳の変化を帯で示す図 (`type: stacked`) が 2 本目の帯として描き、
219
+ * 値 1 つを大きく示す図 (`type: stat`) が差として出す。 書かない図は 1 本のまま。
220
+ */
221
+ previous?: string;
215
222
  rows?: string[];
216
223
  lane?: string;
217
224
  stack?: number;
@@ -354,6 +361,13 @@ export interface JsonPhase {
354
361
  * 受ける 8 語は `DRAW_WORDS` が持つ。 語と同じ図種で、その段の間に図が起点から現れる。
355
362
  */
356
363
  draw?: string;
364
+ /**
365
+ * 描き終えるまでに段の何割を使うか (#1441)。 記法の `draw: line 0.4` の後半と同じ。
366
+ *
367
+ * 0 より大きく 1 以下。 書かなければ段の終わりに描き終わる。 `draw` が無い段に書いても
368
+ * 何も起きない (描く相手が決まらないため、写す時に外す)。
369
+ */
370
+ drawRatio?: number;
357
371
  /**
358
372
  * 段の中で値を動かす (#1186)。 記法の `tween: name 100 -> 90` と同じ。
359
373
  *
@@ -444,6 +458,8 @@ export const ACCEPTED_KEYS = {
444
458
  "subtitle",
445
459
  "eyebrow",
446
460
  "value",
461
+ // 前の時点の値 (#1450)。 内訳の変化を帯で示す図と、値 1 つを大きく示す図が読む
462
+ "previous",
447
463
  "rows",
448
464
  "lane",
449
465
  "stack",
@@ -496,7 +512,7 @@ export const ACCEPTED_KEYS = {
496
512
  "overlay",
497
513
  "pos",
498
514
  ],
499
- phase: ["step", "duration", "focus", "body", "badge", "tween", "set", "draw"],
515
+ phase: ["step", "duration", "focus", "body", "badge", "tween", "set", "draw", "drawRatio"],
500
516
  viewport: ["width", "height", "scale", "laneWidth", "gap", "laneGap", "nodeGap", "labelMargin"],
501
517
  lane: ["x", "width", "label", "contain", "lifeline", "pos"],
502
518
  group: ["label", "lanes"],
@@ -583,6 +599,7 @@ export const 欄の型表 = {
583
599
  subtitle: "文字列",
584
600
  eyebrow: "文字列",
585
601
  value: "文字列",
602
+ previous: "文字列",
586
603
  rows: "文字列の並び",
587
604
  lane: "文字列",
588
605
  stack: "数",
@@ -644,6 +661,7 @@ export const 欄の型表 = {
644
661
  tween: "object",
645
662
  set: "object",
646
663
  draw: "描くもの",
664
+ drawRatio: "数",
647
665
  },
648
666
  viewport: {
649
667
  width: "数",
@@ -2161,6 +2179,7 @@ export function jsonToDoc(json: DragonJson): DslDocument {
2161
2179
  subtitle: a.subtitle,
2162
2180
  eyebrow: a.eyebrow,
2163
2181
  value: a.value,
2182
+ previous: a.previous,
2164
2183
  rows: a.rows,
2165
2184
  lane: a.lane,
2166
2185
  stack: a.stack,
@@ -2286,6 +2305,9 @@ export function jsonToDoc(json: DragonJson): DslDocument {
2286
2305
  badge: p.badge,
2287
2306
  // 書いた段だけが欄を持つ。 空文字を置くと「書いた」 と「書いていない」 が同じ形になる
2288
2307
  ...(p.draw !== undefined ? { draw: p.draw, drawPos: p0 } : {}),
2308
+ // 割合は `draw` がある段にだけ写す。 単独で書いても描く相手が決まらず何も起きないので、
2309
+ // 記法側 (同じ行に書かせる形) と同じ状態に揃える
2310
+ ...(p.draw !== undefined && p.drawRatio !== undefined ? { drawRatio: p.drawRatio } : {}),
2289
2311
  // 段の中で動かす分 (#1186)。 記法側の `tweens` / `sets` と同じ形に写す。
2290
2312
  // 空の配列を置かないのは、記法側が「無ければ field ごと持たない」 形だから
2291
2313
  ...(p.tween && Object.keys(p.tween).length > 0
@@ -27,6 +27,11 @@
27
27
  "pie",
28
28
  "bar",
29
29
  "line",
30
+ "gauge",
31
+ "radial",
32
+ "stat",
33
+ "waffle",
34
+ "stacked",
30
35
  "funnel",
31
36
  "tree",
32
37
  "journey",
@@ -34,11 +39,11 @@
34
39
  "c4",
35
40
  "mind"
36
41
  ],
37
- "description": "図の種類。 型ごとに actors の書き方が違う。 [関係を描く] sequence (時系列の呼び出し) / flow (処理の流れ) / swimlane (責務ごとの流れ) / er (DB の schema) / state (状態の遷移) / topology (network) / solidity (contract) / class (UML の class) / c4 (architecture) / mind (発想の枝分かれ) は actors に名前を並べ flow に矢印を書く。 [値を描く] pie (割合) / bar (棒の高さ) / line (線の高さ、 書いた順に並ぶ) は actors に `- 名前: \"45\"` の形で数を書く。 funnel (段ごとに減る数) も同じ形。 [語を描く] journey (体験の起伏) は `- 登録: \"不満\"` の形で 最高 / 満足 / 普通 / 不満 / 怒り のどれかを書く。 quadrant (2 軸の仕分け) は `- 重複削除: \"左上\"` の形で 左上 / 右上 / 左下 / 右下 のどれかを書く。 [時期を描く] gantt (工程の並び) は `- 設計: \"1月\"` の形で始まりの時期を書き、 flow の矢印で前後関係を書く。 [親子を描く] tree (親子の入れ子) は actors に名前を並べ flow の矢印で親子を書く (矢印の先が子)"
42
+ "description": "図の種類。 型ごとに actors の書き方が違う。 [関係を描く] sequence (時系列の呼び出し) / flow (処理の流れ) / swimlane (責務ごとの流れ) / er (DB の schema) / state (状態の遷移) / topology (network) / solidity (contract) / class (UML の class) / c4 (architecture) / mind (発想の枝分かれ) は actors に名前を並べ flow に矢印を書く。 [値を描く] pie (割合) / bar (棒の高さ) / line (線の高さ、 書いた順に並ぶ) / gauge (合計を半円で示す) / radial (弧の長さで比べる) / stat (値 1 つを大きく示す) / waffle (割合を 100 個の印で示す) / stacked (内訳を帯で示す) は actors に `- 名前: \"45\"` の形で数を書く。 stat と stacked は `- 名前: { value: \"320\", previous: \"280\" }` の形で前の時点の値も書ける。 funnel (段ごとに減る数) も同じ形。 [語を描く] journey (体験の起伏) は `- 登録: \"不満\"` の形で 最高 / 満足 / 普通 / 不満 / 怒り のどれかを書く。 quadrant (2 軸の仕分け) は `- 重複削除: \"左上\"` の形で 左上 / 右上 / 左下 / 右下 のどれかを書く。 [時期を描く] gantt (工程の並び) は `- 設計: \"1月\"` の形で始まりの時期を書き、 flow の矢印で前後関係を書く。 [親子を描く] tree (親子の入れ子) は actors に名前を並べ flow の矢印で親子を書く (矢印の先が子)"
38
43
  },
39
44
  "eyebrow": {
40
45
  "type": "string",
41
- "description": "図表の箱の上に出す小見出し (省略可)。 効くのは図全体を 1 箱にする型 (pie / bar / line / funnel / tree / journey / quadrant / mind / gantt) だけ。 箱ごとに分かれる型では相手が決まらないため、 actors[].eyebrow に書く"
46
+ "description": "図表の箱の上に出す小見出し (省略可)。 効くのは図全体を 1 箱にする型 (pie / bar / line / gauge / radial / stat / waffle / stacked / funnel / tree / journey / quadrant / mind / gantt) だけ。 箱ごとに分かれる型では相手が決まらないため、 actors[].eyebrow に書く"
42
47
  },
43
48
  "axes": {
44
49
  "type": "object",
@@ -107,6 +112,10 @@
107
112
  "type": "string",
108
113
  "description": "actor 数値表示 (kind: actor 用)"
109
114
  },
115
+ "previous": {
116
+ "type": "string",
117
+ "description": "前の時点の値。 type: stacked が 2 本目の帯として描き、type: stat が差として出す。 書かない図は 1 本のまま"
118
+ },
110
119
  "rows": {
111
120
  "type": "array",
112
121
  "items": {
@@ -584,6 +593,12 @@
584
593
  "enum": ["line", "bar", "pie", "journey", "mind", "tree", "gantt", "funnel"],
585
594
  "description": "この phase で起点から描くもの。 語は図種と揃える。 line / journey = 線が左端から右へ伸びる / bar = 棒が横軸から上へ伸びる / pie = 扇が 12 時から時計回りに開く / mind = 枝が中心から外へ伸びる / tree = 枝が根から下へ伸びる / gantt = 帯が各始端から右へ伸びる / funnel = 段が上端から順に積まれる"
586
595
  },
596
+ "drawRatio": {
597
+ "type": "number",
598
+ "exclusiveMinimum": 0,
599
+ "maximum": 1,
600
+ "description": "描き終えるまでに phase の何割を使うか。 0.4 なら進みが 0.4 に達した時点で描き終わり、残りは描き終わった姿のまま値だけが動く。 書かなければ phase の終わりに描き終わる。 draw と一緒に書く (draw が無いと何も起きない)"
601
+ },
587
602
  "tween": {
588
603
  "type": "object",
589
604
  "description": "この phase で補間する状態。 状態名 -> [開始値, 終了値]。 名前は英字か _ で始め、 英数字と _ だけを使う",
package/src/types.ts CHANGED
@@ -71,6 +71,11 @@ export type PresetType =
71
71
  | "pie"
72
72
  | "bar"
73
73
  | "line"
74
+ | "gauge"
75
+ | "radial"
76
+ | "stat"
77
+ | "waffle"
78
+ | "stacked"
74
79
  | "funnel"
75
80
  | "tree"
76
81
  | "journey"
@@ -214,6 +219,16 @@ export type DslActor = {
214
219
  subtitle?: string;
215
220
  eyebrow?: string;
216
221
  value?: string;
222
+ /**
223
+ * 前の時点の値 (#1450)。
224
+ *
225
+ * 内訳の変化を帯で示す図 (`type: stacked`) が 2 本目の帯として描き、値 1 つを大きく示す図
226
+ * (`type: stat`) が差として出す。 書かない図は 1 本のまま (従来と同じ)。
227
+ *
228
+ * **3 時点以上は持たない**。 描画側が 2 時点で設計されており (`cdl#551`)、足すなら欄の形ごと
229
+ * 決め直すことになる。
230
+ */
231
+ previous?: string;
217
232
  rows?: string[];
218
233
  /**
219
234
  * 箱の中に描く図形 (`shape:`、 #1374)。 水位や角度を状態で動かせる。
@@ -546,6 +561,20 @@ export type DslPhase = {
546
561
  * 相手の名前は書かせない = 対象の 8 図種はいずれも図全体を 1 箱で描くため相手が決まる。
547
562
  */
548
563
  draw?: string;
564
+ /**
565
+ * 描き終えるまでに段の何割を使うか (#1441)。
566
+ *
567
+ * `draw: line 0.4` のように語の後ろに書く。 0 より大きく 1 以下。
568
+ * 書かなければ段の終わりに描き終わる (従来どおり)。
569
+ *
570
+ * **段の長さと描く速さを切り離すための欄**。 伸び具合は段の進みそのものなので、
571
+ * これが無いと「線はゆっくり引きたいが値の移りは短くしたい」 が書けず、描く速さのために
572
+ * 段の長さを動かすことになる。 段の長さを動かすと同じ段の `tween` まで遅くなる。
573
+ *
574
+ * **`draw` と同じ行に書かせる**。 別の項目にすると「割合だけ書いて `draw` が無い段」 が
575
+ * 書けてしまい、何も起きない指定になる。 同じ行なら書けない。
576
+ */
577
+ drawRatio?: number;
549
578
  /**
550
579
  * `draw` を書いた行 (#1312)。 知らせの行番号に使う。
551
580
  *
package/src/v05/parser.ts CHANGED
@@ -249,6 +249,11 @@ export const PRESET_TYPES: ReadonlySet<PresetType> = new Set([
249
249
  "pie",
250
250
  "bar",
251
251
  "line",
252
+ "gauge",
253
+ "radial",
254
+ "stat",
255
+ "waffle",
256
+ "stacked",
252
257
  "funnel",
253
258
  "tree",
254
259
  "journey",
@@ -2376,6 +2381,11 @@ function applyContinuationLines(actor: DslActor, rest: Line[], errors: DslError[
2376
2381
  case "値":
2377
2382
  out.value = stripQuotes(raw);
2378
2383
  break;
2384
+ // 前の時点の値 (#1450)。 内訳を帯で示す図と値 1 つを大きく示す図が読む
2385
+ case "previous":
2386
+ case "前の値":
2387
+ out.previous = stripQuotes(raw);
2388
+ break;
2379
2389
  case "shape":
2380
2390
  case "図形":
2381
2391
  out.shape = 図形として読む(raw, ln.no, errors);
@@ -2572,6 +2582,9 @@ export const ACTOR_ITEM_KEYS: ReadonlySet<string> = new Set([
2572
2582
  "補足",
2573
2583
  "value",
2574
2584
  "値",
2585
+ // 前の時点の値 (#1450)。 内訳の変化を帯で示す図と、値 1 つを大きく示す図が読む
2586
+ "previous",
2587
+ "前の値",
2575
2588
  "rows",
2576
2589
  "行",
2577
2590
  // 箱の中に描く図形 (#1374)
@@ -2736,6 +2749,7 @@ const ACTOR_RESERVED_FIELDS: ReadonlySet<string> = new Set([
2736
2749
  "subtitle",
2737
2750
  "eyebrow",
2738
2751
  "value",
2752
+ "previous",
2739
2753
  "rows",
2740
2754
  "lane",
2741
2755
  "stack",
@@ -2888,6 +2902,7 @@ export const INLINE_ACTOR_ALIASES: Record<string, string> = {
2888
2902
  種類: "kind",
2889
2903
  補足: "subtitle",
2890
2904
  値: "value",
2905
+ 前の値: "previous",
2891
2906
  行: "rows",
2892
2907
  };
2893
2908
 
@@ -2910,6 +2925,7 @@ export const INLINE_ACTOR_KEYS: ReadonlySet<string> = new Set([
2910
2925
  "subtitle",
2911
2926
  "eyebrow",
2912
2927
  "value",
2928
+ "previous",
2913
2929
  "rows",
2914
2930
  "lane",
2915
2931
  "stack",
@@ -3080,6 +3096,7 @@ function parseActor(line: Line, errors: DslError[]): DslActor | null {
3080
3096
  owner: isPart ? undefined : opts.owner,
3081
3097
  end: isPart ? undefined : opts.end,
3082
3098
  value: opts.value,
3099
+ previous: opts.previous,
3083
3100
  rows: opts.rows
3084
3101
  ? opts.rows
3085
3102
  .replace(/^\[|\]$/g, "")
@@ -3512,7 +3529,10 @@ function parsePhase(block: Line[], errors: DslError[]): DslPhase | null {
3512
3529
  continue;
3513
3530
  }
3514
3531
  if (key === "draw") {
3515
- const = stripQuotes(value).trim();
3532
+ // `draw: line 0.4` = 語の後ろに「描き終えるまでに段の何割を使うか」 を書ける (#1441)
3533
+ // 割合を別の項目にすると「割合だけ書いて `draw` が無い段」 が書けてしまい、
3534
+ // 何も起きない指定になる。 同じ行なら書けない
3535
+ const [語 = "", 割合の文字, ...余り] = stripQuotes(value).trim().split(/\s+/);
3516
3536
  // **読めない語を黙って捨てない** (#1304 / #1306 と同じ扱い)。 受ける語は 1 つだけで、
3517
3537
  // 書き間違いはその段が何も描かない形になって手掛かりが残らない
3518
3538
  if (!DRAW_WORDS.has(語)) {
@@ -3521,9 +3541,29 @@ function parsePhase(block: Line[], errors: DslError[]): DslPhase | null {
3521
3541
  message: `draw に書けない語です: "${語}"`,
3522
3542
  hint: `使える語 = ${[...DRAW_WORDS].join(", ")}`,
3523
3543
  });
3544
+ } else if (余り.length > 0) {
3545
+ errors.push({
3546
+ line: ln.no,
3547
+ message: `draw に書ける項目は語と割合の 2 つまでです: "${stripQuotes(value).trim()}"`,
3548
+ hint: "use `draw: line 0.4`",
3549
+ });
3524
3550
  } else {
3525
3551
  phase.draw = 語;
3526
3552
  phase.drawPos = { line: ln.no };
3553
+ if (割合の文字 !== undefined) {
3554
+ const 割合 = Number(割合の文字);
3555
+ // 範囲外を黙って捨てない。 描画側は 1 に落として図を出すため、
3556
+ // 知らせが無いと「書いたのに速さが変わらない」 が手掛かりなしで起きる
3557
+ if (!Number.isFinite(割合) || 割合 <= 0 || 割合 > 1) {
3558
+ errors.push({
3559
+ line: ln.no,
3560
+ message: `draw の割合が範囲外です: "${割合の文字}"`,
3561
+ hint: "0 より大きく 1 以下で書きます (例 `draw: line 0.4`)",
3562
+ });
3563
+ } else {
3564
+ phase.drawRatio = 割合;
3565
+ }
3566
+ }
3527
3567
  }
3528
3568
  i += 1;
3529
3569
  continue;