@cardenelabs/cdl 0.38.0 → 0.39.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.
@@ -2347,11 +2347,15 @@ type CdlPhase = {
2347
2347
  * | `chart-line` | 左端 | 線が右へ伸びる | 点と数字は線の先が届いた時 |
2348
2348
  * | `chart-bar` | 横軸 | 棒が上へ伸びる | 数字は棒の頭に付いて動く |
2349
2349
  * | `chart-pie` | 12 時 | 扇が時計回りに開く | 凡例はその扇が開き始めた時 |
2350
+ * | `chart-slope` | 左の軸 | 線が右の軸へ伸びる | 右の値は線が右の軸に着いた時 |
2350
2351
  * | `journey-map` | 左端 | 気持ちの線が右へ伸びる | 段はその点に線の先が届いた時 |
2351
2352
  * | `mind-map` | 中心 | 枝が段ごとに外へ伸びる | 箱はその枝が届いた時 |
2352
2353
  * | `tree-hierarchy` | 根 | 枝が段ごとに下へ伸びる | 箱はその枝が届いた時 |
2353
2354
  * | `gantt-timeline` | 各帯の始端 | 帯が右へ伸びる | 依存の矢印は帯が出揃った時 |
2354
2355
  * | `funnel-stages` | 上端 | 段が上から順に積まれる | 件数と離脱率はその段が出切った時 |
2356
+ * | `chart-gauge` | 9 時 | 弧が上を通って 3 時へ伸びる | 合計と内訳は最初から出る |
2357
+ * | `chart-radial` | 12 時 | 各輪が時計回りに開く | 軌道と一覧は最初から出る |
2358
+ * | `chart-stacked-bar` | 左端 | 帯が右へ伸びる | 区画の字はその区画が広がった時 |
2355
2359
  *
2356
2360
  * 対象種別の一覧は `validate.ts` の `DRAW_KINDS` が持つ。
2357
2361
  *
@@ -2347,11 +2347,15 @@ type CdlPhase = {
2347
2347
  * | `chart-line` | 左端 | 線が右へ伸びる | 点と数字は線の先が届いた時 |
2348
2348
  * | `chart-bar` | 横軸 | 棒が上へ伸びる | 数字は棒の頭に付いて動く |
2349
2349
  * | `chart-pie` | 12 時 | 扇が時計回りに開く | 凡例はその扇が開き始めた時 |
2350
+ * | `chart-slope` | 左の軸 | 線が右の軸へ伸びる | 右の値は線が右の軸に着いた時 |
2350
2351
  * | `journey-map` | 左端 | 気持ちの線が右へ伸びる | 段はその点に線の先が届いた時 |
2351
2352
  * | `mind-map` | 中心 | 枝が段ごとに外へ伸びる | 箱はその枝が届いた時 |
2352
2353
  * | `tree-hierarchy` | 根 | 枝が段ごとに下へ伸びる | 箱はその枝が届いた時 |
2353
2354
  * | `gantt-timeline` | 各帯の始端 | 帯が右へ伸びる | 依存の矢印は帯が出揃った時 |
2354
2355
  * | `funnel-stages` | 上端 | 段が上から順に積まれる | 件数と離脱率はその段が出切った時 |
2356
+ * | `chart-gauge` | 9 時 | 弧が上を通って 3 時へ伸びる | 合計と内訳は最初から出る |
2357
+ * | `chart-radial` | 12 時 | 各輪が時計回りに開く | 軌道と一覧は最初から出る |
2358
+ * | `chart-stacked-bar` | 左端 | 帯が右へ伸びる | 区画の字はその区画が広がった時 |
2355
2359
  *
2356
2360
  * 対象種別の一覧は `validate.ts` の `DRAW_KINDS` が持つ。
2357
2361
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardenelabs/cdl",
3
- "version": "0.38.0",
3
+ "version": "0.39.0",
4
4
  "description": "CDL (Chainome Diagram Language). Mermaid-like declarative DSL that compiles to animated SVG diagrams. Built for blockchain / Solidity flows, generic enough for sequence / flow / state / ER / topology diagrams.",
5
5
  "license": "MIT",
6
6
  "author": "cardene777",
@@ -1,9 +1,11 @@
1
- import { type JSX } from "react";
1
+ import { useId, type JSX } from "react";
2
2
  import type { LaidNode, Tone } from "../types";
3
3
  import { TONE, chartSeriesColor } from "../render/tone";
4
4
  import { resolveChartData } from "../render/payload-binding";
5
5
  import { textWidth } from "./text-width";
6
6
  import { 実値表記, 割合表記 } from "./chart-number";
7
+ import { 進みを畳む } from "./draw-progress";
8
+ import { 扇形の切り抜き } from "./draw-arc";
7
9
 
8
10
  /**
9
11
  * chart-gauge kind ... 上半分の弧で内訳を示し、 合計を主役に置く図 (cdl#549)。
@@ -27,15 +29,28 @@ import { 実値表記, 割合表記 } from "./chart-number";
27
29
  *
28
30
  * 内訳には実値と割合の両方を置く。 合計が主役の図で内訳が割合しか持たないと、 「合計の何割」
29
31
  * は分かっても「いくつ」 が分からず、 合計との関係を読み手が暗算することになる。
32
+ *
33
+ * ## 段の `draw` では 9 時から弧が伸びる (cdl#715)
34
+ *
35
+ * 伸びる向きは弧の向きそのもので、 図の意味から一意に決まる。
36
+ *
37
+ * **切り抜くのは弧だけ**。 合計の字と内訳の段は最初から出す = 合計はこの種別の主役なので、
38
+ * 左から半分ずつ現れると読めない。 内訳は弧の外にある別の一覧で、 弧の開きに従う理由がない。
30
39
  */
31
40
  export function ChartGaugeNode({
32
41
  node,
33
42
  active,
34
43
  stateValues = {},
44
+ drawing = false,
45
+ progress = 1,
35
46
  }: {
36
47
  node: LaidNode;
37
48
  active: boolean;
38
49
  stateValues?: Record<string, string>;
50
+ /** この phase の `draw` に載っているか (cdl#715) */
51
+ drawing?: boolean;
52
+ /** phase の進み (0→1)。 `drawing` が true の時だけ読む */
53
+ progress?: number;
39
54
  }): JSX.Element {
40
55
  const x0 = node.cx - node.w / 2;
41
56
  const y0 = node.cy - node.h / 2;
@@ -112,6 +127,24 @@ export function ChartGaugeNode({
112
127
  const 内訳の幅 = 使える幅 / Math.max(data.length, 1);
113
128
  const 内訳のy = cy + 弧と内訳の間;
114
129
 
130
+ /** 弧がどこまで伸びたか (0..1、 cdl#715)。 畳み方は `draw-progress.ts` が持つ */
131
+ const 伸び = 進みを畳む(drawing, progress);
132
+ // SVG の id は document 全体の名前空間。 node id は図の中でだけ一意なので instance id で分ける
133
+ const 切り抜きid = `cdl-gauge-draw-${useId().replace(/[^A-Za-z0-9_-]/g, "")}`;
134
+ const 弧たち = slices.map((s, idx) => (
135
+ <path
136
+ key={`arc-${idx}`}
137
+ data-cdl-role="chart-gauge-arc"
138
+ data-cdl-unresolved={s.d.unresolved ? "true" : undefined}
139
+ d={s.path}
140
+ fill={arcColor(s.d.tone, idx)}
141
+ // 区切りは地の色の余白で取る。 線だと弧が分断されて 1 つの量に見えない
142
+ stroke="var(--cdl-node-fill, #ffffff)"
143
+ strokeWidth={3}
144
+ strokeLinejoin="round"
145
+ />
146
+ ));
147
+
115
148
  return (
116
149
  <g transform={`translate(${x0} ${y0})`}>
117
150
  <rect
@@ -126,19 +159,21 @@ export function ChartGaugeNode({
126
159
  strokeWidth={active ? 3 : 1.25}
127
160
  />
128
161
 
129
- {slices.map((s, idx) => (
130
- <path
131
- key={`arc-${idx}`}
132
- data-cdl-role="chart-gauge-arc"
133
- data-cdl-unresolved={s.d.unresolved ? "true" : undefined}
134
- d={s.path}
135
- fill={arcColor(s.d.tone, idx)}
136
- // 区切りは地の色の余白で取る。 線だと弧が分断されて 1 つの量に見えない
137
- stroke="var(--cdl-node-fill, #ffffff)"
138
- strokeWidth={3}
139
- strokeLinejoin="round"
140
- />
141
- ))}
162
+ {drawing && (
163
+ <defs>
164
+ {/*
165
+ 9 時から上を通って 3 時へ開く切り抜き (cdl#715)。 半径は弧の外側に少し足す =
166
+ 弧の縁が切り抜きの縁と重なると、 開き切った時に外周が痩せて見える。
167
+
168
+ **弧の `d` は触らない** = 形から量を測る利用側が、 進みの途中で別の値を読まない。
169
+ */}
170
+ <clipPath id={切り抜きid}>
171
+ {扇形の切り抜き(cx, cy, radius + 2, 伸び, Math.PI, Math.PI)}
172
+ </clipPath>
173
+ </defs>
174
+ )}
175
+ {/* 描く指定が無い図には包みを足さず、 従来の DOM 構造を保つ */}
176
+ {drawing ? <g clipPath={`url(#${切り抜きid})`}>{弧たち}</g> : 弧たち}
142
177
 
143
178
  {/*
144
179
  合計。 この種別の主役なので図の中でいちばん大きい字にするが、 **弧の内側に収める**。
@@ -3,6 +3,7 @@ import type { ChartPieForm, LaidNode, Tone } from "../types";
3
3
  import { TONE, chartSeriesColor } from "../render/tone";
4
4
  import { resolveChartData, type ResolvedChartDatum } from "../render/payload-binding";
5
5
  import { 進みを畳む } from "./draw-progress";
6
+ import { 扇形の切り抜き } from "./draw-arc";
6
7
  import { textWidth } from "./text-width";
7
8
  import { 実値表記, 割合表記 } from "./chart-number";
8
9
 
@@ -687,22 +688,6 @@ function 輪の道(cx: number, cy: number, R: number, r: number, a0: number, a1:
687
688
  );
688
689
  }
689
690
 
690
- /**
691
- * 12 時から時計回りに開く扇形 (cdl#516)。 円を切り抜く形に使う。
692
- *
693
- * 1 周ちょうどは扇形で表せない (始点と終点が同じ点になり、 弧の向きが決まらない)。
694
- * その時だけ円をそのまま返す = 進み 1 で図が消える形を避ける。
695
- */
696
- function 扇形の切り抜き(cx: number, cy: number, r: number, 開き: number): JSX.Element | null {
697
- if (開き <= 0) return null;
698
- if (開き >= 1) return <circle cx={cx} cy={cy} r={r} />;
699
- const a0 = -Math.PI / 2;
700
- const a1 = a0 + 開き * Math.PI * 2;
701
- const large = 開き > 0.5 ? 1 : 0;
702
- const d = `M ${cx} ${cy} L ${cx + r * Math.cos(a0)} ${cy + r * Math.sin(a0)} A ${r} ${r} 0 ${large} 1 ${cx + r * Math.cos(a1)} ${cy + r * Math.sin(a1)} Z`;
703
- return <path d={d} />;
704
- }
705
-
706
691
  /**
707
692
  * 扇の色 (cdl#546)。
708
693
  *
@@ -1,9 +1,11 @@
1
- import { type JSX } from "react";
1
+ import { useId, type JSX } from "react";
2
2
  import type { LaidNode, Tone } from "../types";
3
3
  import { TONE, chartSeriesColor } from "../render/tone";
4
4
  import { resolveChartData } from "../render/payload-binding";
5
5
  import { textWidth } from "./text-width";
6
6
  import { 実値表記, 割合表記 } from "./chart-number";
7
+ import { 進みを畳む } from "./draw-progress";
8
+ import { 扇形の切り抜き } from "./draw-arc";
7
9
 
8
10
  /**
9
11
  * chart-radial kind ... 1 件を 1 本の弧にして内側から重ね、 長さで比べる図 (cdl#550)。
@@ -27,15 +29,29 @@ import { 実値表記, 割合表記 } from "./chart-number";
27
29
  *
28
30
  * 弧のそばに置くと、 内側の弧ほど場所が狭くなって字が入らない。 左端に縦へ並べると件数が
29
31
  * 増えても一覧性が落ちず、 弧の並び順と行の並び順が対応する。
32
+ *
33
+ * ## 段の `draw` では 12 時から各輪が開く (cdl#715)
34
+ *
35
+ * 弧の向きそのままで、 全部の輪が同時に開く。 外から順に開く案は採らない = 内側の輪だけ
36
+ * 軌道が空のまま残る時間が長くなり、 「まだ描いていない」 と「本当に 0」 が見分けられない。
37
+ *
38
+ * **切り抜くのは取り分の弧だけ**。 軌道と一覧は最初から出す = 軌道は「あとどれだけで一周か」
39
+ * を示す枠なので、 一緒に伸ばすと比べる相手が無いまま弧だけが伸びる。
30
40
  */
31
41
  export function ChartRadialNode({
32
42
  node,
33
43
  active,
34
44
  stateValues = {},
45
+ drawing = false,
46
+ progress = 1,
35
47
  }: {
36
48
  node: LaidNode;
37
49
  active: boolean;
38
50
  stateValues?: Record<string, string>;
51
+ /** この phase の `draw` に載っているか (cdl#715) */
52
+ drawing?: boolean;
53
+ /** phase の進み (0→1)。 `drawing` が true の時だけ読む */
54
+ progress?: number;
39
55
  }): JSX.Element {
40
56
  const x0 = node.cx - node.w / 2;
41
57
  const y0 = node.cy - node.h / 2;
@@ -99,6 +115,22 @@ export function ChartRadialNode({
99
115
 
100
116
  const 一覧の始まり = PAD + (使える高さ - 行の高さ * data.length) / 2 + 行の高さ / 2;
101
117
 
118
+ /** 弧がどこまで開いたか (0..1、 cdl#715)。 畳み方は `draw-progress.ts` が持つ */
119
+ const 開き = 進みを畳む(drawing, progress);
120
+ // SVG の id は document 全体の名前空間。 node id は図の中でだけ一意なので instance id で分ける
121
+ const 切り抜きid = `cdl-radial-draw-${useId().replace(/[^A-Za-z0-9_-]/g, "")}`;
122
+ const 取り分たち = rings.map((r, idx) =>
123
+ r.取り分 === null ? null : (
124
+ <path
125
+ key={`arc-${idx}`}
126
+ data-cdl-role="chart-radial-arc"
127
+ data-cdl-unresolved={r.d.unresolved ? "true" : undefined}
128
+ d={r.取り分}
129
+ fill={arcColor(r.d.tone, idx)}
130
+ />
131
+ ),
132
+ );
133
+
102
134
  return (
103
135
  <g transform={`translate(${x0} ${y0})`}>
104
136
  <rect
@@ -127,17 +159,19 @@ export function ChartRadialNode({
127
159
  />
128
160
  ))}
129
161
 
130
- {rings.map((r, idx) =>
131
- r.取り分 === null ? null : (
132
- <path
133
- key={`arc-${idx}`}
134
- data-cdl-role="chart-radial-arc"
135
- data-cdl-unresolved={r.d.unresolved ? "true" : undefined}
136
- d={r.取り分}
137
- fill={arcColor(r.d.tone, idx)}
138
- />
139
- ),
162
+ {drawing && (
163
+ <defs>
164
+ {/*
165
+ 12 時から時計回りに開く切り抜き (cdl#715)。 半径はいちばん外の帯の外側まで取る =
166
+ 全部の輪が同じ 1 つの切り抜きで開く。
167
+
168
+ **弧の `d` は触らない** = 形から量を測る利用側が、 進みの途中で別の値を読まない。
169
+ */}
170
+ <clipPath id={切り抜きid}>{扇形の切り抜き(弧のcx, cy, 外半径 + 帯の幅, 開き)}</clipPath>
171
+ </defs>
140
172
  )}
173
+ {/* 描く指定が無い図には包みを足さず、 従来の DOM 構造を保つ */}
174
+ {drawing ? <g clipPath={`url(#${切り抜きid})`}>{取り分たち}</g> : 取り分たち}
141
175
 
142
176
  {/* 一覧。 行の並びが弧の並び (外から内へ) と対応する */}
143
177
  {data.map((d, idx) => {
@@ -1,9 +1,10 @@
1
- import { type JSX } from "react";
1
+ import { useId, type JSX } from "react";
2
2
  import type { LaidNode, Tone } from "../types";
3
3
  import { TONE, chartSeriesColor } from "../render/tone";
4
4
  import { resolveChartData, type ResolvedChartDatum } from "../render/payload-binding";
5
5
  import { textWidth } from "./text-width";
6
6
  import { 実値表記, 割合表記 } from "./chart-number";
7
+ import { 進みを畳む } from "./draw-progress";
7
8
 
8
9
  /**
9
10
  * chart-stacked-bar kind ... 1 本の帯を割合で区切り、時点を並べて変化を見せる図 (cdl#551)。
@@ -27,6 +28,15 @@ import { 実値表記, 割合表記 } from "./chart-number";
27
28
  *
28
29
  * 細い区画に字を詰めると隣の区画へはみ出して、どちらの値か読めなくなる。
29
30
  * 入らない区画は字を描かず、名前は下の一覧が受け持つ。
31
+ *
32
+ * ## 段の `draw` では左から帯が伸びる (cdl#715)
33
+ *
34
+ * 帯は左から右へ割合で区切る図なので、伸びる向きは並びそのもの。 棒グラフと起点を揃えている。
35
+ * 帯が 2 本ある図では 2 本とも同時に伸びる = 上下で同じ位置を見比べる図なので、
36
+ * 片方だけ先に出ると比べる相手がいない時間ができる。
37
+ *
38
+ * **区画の字は区画と一緒に切り抜く**。 字は区画の上に乗っているので、切り抜かないと
39
+ * まだ出ていない区画の割合だけが宙に浮く。 時点の名前と下の一覧は最初から出す。
30
40
  */
31
41
 
32
42
  /** 区画に割合を描くために要る幅。 これ未満の区画は字を描かない */
@@ -39,10 +49,16 @@ export function ChartStackedBarNode({
39
49
  node,
40
50
  active,
41
51
  stateValues = {},
52
+ drawing = false,
53
+ progress = 1,
42
54
  }: {
43
55
  node: LaidNode;
44
56
  active: boolean;
45
57
  stateValues?: Record<string, string>;
58
+ /** この phase の `draw` に載っているか (cdl#715) */
59
+ drawing?: boolean;
60
+ /** phase の進み (0→1)。 `drawing` が true の時だけ読む */
61
+ progress?: number;
46
62
  }): JSX.Element {
47
63
  const x0 = node.cx - node.w / 2;
48
64
  const y0 = node.cy - node.h / 2;
@@ -67,6 +83,11 @@ export function ChartStackedBarNode({
67
83
  const 全体の高さ = 帯たち.length * 帯の高さ + (帯たち.length - 1) * 帯の間 + 16 + 一覧の高さ;
68
84
  const 上端 = Math.max(PAD, (node.h - 全体の高さ) / 2);
69
85
 
86
+ /** 帯がどこまで伸びたか (0..1、 cdl#715)。 畳み方は `draw-progress.ts` が持つ */
87
+ const 伸び = 進みを畳む(drawing, progress);
88
+ // SVG の id は document 全体の名前空間。 node id は図の中でだけ一意なので instance id で分ける
89
+ const 切り抜きid = `cdl-stacked-bar-draw-${useId().replace(/[^A-Za-z0-9_-]/g, "")}`;
90
+
70
91
  return (
71
92
  <g transform={`translate(${x0} ${y0})`}>
72
93
  <rect
@@ -81,6 +102,23 @@ export function ChartStackedBarNode({
81
102
  strokeWidth={active ? 3 : 1.25}
82
103
  />
83
104
 
105
+ {drawing && (
106
+ <defs>
107
+ {/*
108
+ 左から右へ広がる切り抜き (cdl#715)。 帯が 2 本ある図でも 1 つで足りる =
109
+ 2 本とも同じ左端から同じ幅で伸びる。
110
+
111
+ **右へ余白を足さない** = 伸びる向きそのものなので、足すと進み 0 で区画の左端が
112
+ 見えてしまう。 切り抜きは境界を含むため、伸び切った時に区画の右端は欠けない。
113
+
114
+ **区画の `width` は触らない** = 形から量を測る利用側が、進みの途中で別の値を読まない。
115
+ */}
116
+ <clipPath id={切り抜きid}>
117
+ <rect x={0} y={0} width={帯の左 + 帯の幅 * 伸び} height={node.h} />
118
+ </clipPath>
119
+ </defs>
120
+ )}
121
+
84
122
  {帯たち.map((帯, 段) => {
85
123
  const y = 上端 + 段 * (帯の高さ + 帯の間);
86
124
  let 左 = 帯の左;
@@ -100,55 +138,59 @@ export function ChartStackedBarNode({
100
138
  </text>
101
139
  )}
102
140
 
103
- {帯.区画.map((s, idx) => {
104
- const w = 帯の幅 * s.割合;
105
- const x = 左;
106
- 左 += w;
107
- const 色 = 印の色(s.tone, idx);
108
- return (
109
- <g key={`slice-${idx}`}>
110
- <rect
111
- data-cdl-role="chart-stacked-bar-slice"
112
- data-cdl-unresolved={s.unresolved ? "true" : undefined}
113
- x={x}
114
- y={y}
115
- width={Math.max(0, w)}
116
- height={帯の高さ}
117
- fill={色}
118
- />
119
- {/*
141
+ {(() => {
142
+ const 区画たち = 帯.区画.map((s, idx) => {
143
+ const w = 帯の幅 * s.割合;
144
+ const x = 左;
145
+ 左 += w;
146
+ const 色 = 印の色(s.tone, idx);
147
+ return (
148
+ <g key={`slice-${idx}`}>
149
+ <rect
150
+ data-cdl-role="chart-stacked-bar-slice"
151
+ data-cdl-unresolved={s.unresolved ? "true" : undefined}
152
+ x={x}
153
+ y={y}
154
+ width={Math.max(0, w)}
155
+ height={帯の高さ}
156
+ fill={色}
157
+ />
158
+ {/*
120
159
  区画の字。 **入る時だけ描く** = 細い区画に詰めると隣へはみ出して
121
160
  どちらの値か読めなくなる。 名前は下の一覧が受け持つ
122
161
  */}
123
- {w >= SLICE_MIN_W_FOR_TEXT && (
124
- <text
125
- data-cdl-role="chart-stacked-bar-slice-text"
126
- x={x + w / 2}
127
- y={y + 帯の高さ / 2 + (w >= SLICE_MIN_W_FOR_VALUE ? -2 : 4)}
128
- fontSize={区画の級}
129
- fontWeight={700}
130
- textAnchor="middle"
131
- fill="#ffffff"
132
- >
133
- {割合表記(s.割合)}
134
- </text>
135
- )}
136
- {w >= SLICE_MIN_W_FOR_VALUE && (
137
- <text
138
- data-cdl-role="chart-stacked-bar-slice-value"
139
- x={x + w / 2}
140
- y={y + 帯の高さ / 2 + 13}
141
- fontSize={区画の級 - 1}
142
- textAnchor="middle"
143
- fill="#ffffff"
144
- fillOpacity={0.85}
145
- >
146
- {実値表記(s.実値)}
147
- </text>
148
- )}
149
- </g>
150
- );
151
- })}
162
+ {w >= SLICE_MIN_W_FOR_TEXT && (
163
+ <text
164
+ data-cdl-role="chart-stacked-bar-slice-text"
165
+ x={x + w / 2}
166
+ y={y + 帯の高さ / 2 + (w >= SLICE_MIN_W_FOR_VALUE ? -2 : 4)}
167
+ fontSize={区画の級}
168
+ fontWeight={700}
169
+ textAnchor="middle"
170
+ fill="#ffffff"
171
+ >
172
+ {割合表記(s.割合)}
173
+ </text>
174
+ )}
175
+ {w >= SLICE_MIN_W_FOR_VALUE && (
176
+ <text
177
+ data-cdl-role="chart-stacked-bar-slice-value"
178
+ x={x + w / 2}
179
+ y={y + 帯の高さ / 2 + 13}
180
+ fontSize={区画の級 - 1}
181
+ textAnchor="middle"
182
+ fill="#ffffff"
183
+ fillOpacity={0.85}
184
+ >
185
+ {実値表記(s.実値)}
186
+ </text>
187
+ )}
188
+ </g>
189
+ );
190
+ });
191
+ // 描く指定が無い図には包みを足さず、 従来の DOM 構造を保つ
192
+ return drawing ? <g clipPath={`url(#${切り抜きid})`}>{区画たち}</g> : 区画たち;
193
+ })()}
152
194
  </g>
153
195
  );
154
196
  })}
@@ -156,16 +198,16 @@ export function ChartStackedBarNode({
156
198
  {/* 一覧。 区画に字が入らなかった件もここで名前と色が引ける */}
157
199
  {一覧の並び(data, 帯の左, node.w - PAD, 名前の級).map(({ d, idx, x, 行 }) => {
158
200
  const y =
159
- 上端 + 帯たち.length * (帯の高さ + 帯の間) - 帯の間 + 16 + 一覧の高さ / 2 + 行 * 一覧の高さ;
201
+ 上端 +
202
+ 帯たち.length * (帯の高さ + 帯の間) -
203
+ 帯の間 +
204
+ 16 +
205
+ 一覧の高さ / 2 +
206
+ 行 * 一覧の高さ;
160
207
  return (
161
208
  <g key={`legend-${idx}`} data-cdl-role="chart-stacked-bar-legend">
162
209
  <rect x={x} y={y - 5} width={10} height={10} rx={2} fill={印の色(d.tone, idx)} />
163
- <text
164
- x={x + 16}
165
- y={y + 4}
166
- fontSize={名前の級}
167
- fill="var(--cdl-text, #1a1f2a)"
168
- >
210
+ <text x={x + 16} y={y + 4} fontSize={名前の級} fill="var(--cdl-text, #1a1f2a)">
169
211
  {d.label}
170
212
  </text>
171
213
  </g>
@@ -0,0 +1,42 @@
1
+ import { type JSX } from "react";
2
+
3
+ /**
4
+ * 弧で量を表す図を、起点から開いて見せるための切り抜き (cdl#516 / cdl#715)。
5
+ *
6
+ * 円グラフが持っていた私物の関数を、起点と回る量を引数に取る形へ広げた。 半円ゲージ (9 時から
7
+ * 半周) と放射 (12 時から 1 周) と円グラフ (12 時から 1 周) が同じ計算を使う。
8
+ *
9
+ * **形 (`d`) は触らず、切り抜きで隠す**。 弧の道を切り詰めると、形から量を測る利用側が進みの
10
+ * 途中で別の値を読む (`draw-progress.ts` が `path` について決めたのと同じ)。
11
+ *
12
+ * ここに置くのは切り抜きの形だけで、進みの畳み方は `draw-progress.ts` の `進みを畳む` が持つ。
13
+ * 2 つを 1 つの file に混ぜないのは、こちらが JSX を返すため = `.ts` のままにしておくと
14
+ * 進みの計算まで `.tsx` へ引きずられる。
15
+ */
16
+
17
+ /**
18
+ * `起点` から時計回りに `開き` ぶん開く扇形。 開いていなければ **何も返さない**。
19
+ *
20
+ * @param 開き 0..1。 1 で `全周` ぶん開き切る
21
+ * @param 起点 弧が始まる角度 (`-Math.PI / 2` = 12 時、`Math.PI` = 9 時)
22
+ * @param 全周 開き切った時に回る量 (`Math.PI * 2` = 1 周、`Math.PI` = 半周)
23
+ */
24
+ export function 扇形の切り抜き(
25
+ cx: number,
26
+ cy: number,
27
+ r: number,
28
+ 開き: number,
29
+ 起点: number = -Math.PI / 2,
30
+ 全周: number = Math.PI * 2,
31
+ ): JSX.Element | null {
32
+ if (開き <= 0) return null;
33
+ // 1 周ちょうどは扇形で表せない (始点と終点が同じ点になり、弧の向きが決まらない)。
34
+ // その時だけ円をそのまま返す = 進み 1 で図が消える形を避ける。
35
+ // **半周までの図はこの形にならない** = 両端が別の点なので、扇形のまま描ける。
36
+ if (開き >= 1 && 全周 >= Math.PI * 2) return <circle cx={cx} cy={cy} r={r} />;
37
+ const a0 = 起点;
38
+ const a1 = a0 + Math.min(1, 開き) * 全周;
39
+ const large = a1 - a0 > Math.PI ? 1 : 0;
40
+ const d = `M ${cx} ${cy} L ${cx + r * Math.cos(a0)} ${cy + r * Math.sin(a0)} A ${r} ${r} 0 ${large} 1 ${cx + r * Math.cos(a1)} ${cy + r * Math.sin(a1)} Z`;
41
+ return <path d={d} />;
42
+ }
@@ -194,11 +194,28 @@ export function CdlNodeView({
194
194
  />
195
195
  );
196
196
  case "chart-gauge":
197
- // 段の進みで弧が開く動きは持たない (cdl#549 で対象外にした)
198
- return <ChartGaugeNode node={resolvedNode} active={active} stateValues={stateValues} />;
197
+ // 段の進みでは 9 時から 3 時へ弧が伸びる (cdl#715)
198
+ return (
199
+ <ChartGaugeNode
200
+ node={resolvedNode}
201
+ active={active}
202
+ stateValues={stateValues}
203
+ drawing={drawing}
204
+ progress={progress}
205
+ />
206
+ );
199
207
  case "chart-stacked-bar":
200
- // 帯を割合で区切る。 前の時点を書けば 2 本になる (cdl#551)
201
- return <ChartStackedBarNode node={resolvedNode} active={active} stateValues={stateValues} />;
208
+ // 帯を割合で区切る。 前の時点を書けば 2 本になる (cdl#551)。
209
+ // 段の進みでは左から帯が伸びる (cdl#715)
210
+ return (
211
+ <ChartStackedBarNode
212
+ node={resolvedNode}
213
+ active={active}
214
+ stateValues={stateValues}
215
+ drawing={drawing}
216
+ progress={progress}
217
+ />
218
+ );
202
219
  case "chart-slope":
203
220
  // 2 時点を直線でつなぐ。 前の時点を書かない図は軸 1 本の点の並びになる (cdl#688)
204
221
  return (
@@ -217,8 +234,16 @@ export function CdlNodeView({
217
234
  // 数値が主役で、弧は輪郭だけの補助。 段の進みで数値が動く演出は既存の tween が担う
218
235
  return <ChartStatNode node={resolvedNode} active={active} stateValues={stateValues} />;
219
236
  case "chart-radial":
220
- // 段の進みで弧が伸びる動きは持たない (cdl#550 で対象外にした)
221
- return <ChartRadialNode node={resolvedNode} active={active} stateValues={stateValues} />;
237
+ // 段の進みでは各輪が 12 時から開く (cdl#715)
238
+ return (
239
+ <ChartRadialNode
240
+ node={resolvedNode}
241
+ active={active}
242
+ stateValues={stateValues}
243
+ drawing={drawing}
244
+ progress={progress}
245
+ />
246
+ );
222
247
  case "chart-bar":
223
248
  return (
224
249
  <ChartBarNode
package/src/types.ts CHANGED
@@ -2424,11 +2424,15 @@ export type CdlPhase = {
2424
2424
  * | `chart-line` | 左端 | 線が右へ伸びる | 点と数字は線の先が届いた時 |
2425
2425
  * | `chart-bar` | 横軸 | 棒が上へ伸びる | 数字は棒の頭に付いて動く |
2426
2426
  * | `chart-pie` | 12 時 | 扇が時計回りに開く | 凡例はその扇が開き始めた時 |
2427
+ * | `chart-slope` | 左の軸 | 線が右の軸へ伸びる | 右の値は線が右の軸に着いた時 |
2427
2428
  * | `journey-map` | 左端 | 気持ちの線が右へ伸びる | 段はその点に線の先が届いた時 |
2428
2429
  * | `mind-map` | 中心 | 枝が段ごとに外へ伸びる | 箱はその枝が届いた時 |
2429
2430
  * | `tree-hierarchy` | 根 | 枝が段ごとに下へ伸びる | 箱はその枝が届いた時 |
2430
2431
  * | `gantt-timeline` | 各帯の始端 | 帯が右へ伸びる | 依存の矢印は帯が出揃った時 |
2431
2432
  * | `funnel-stages` | 上端 | 段が上から順に積まれる | 件数と離脱率はその段が出切った時 |
2433
+ * | `chart-gauge` | 9 時 | 弧が上を通って 3 時へ伸びる | 合計と内訳は最初から出る |
2434
+ * | `chart-radial` | 12 時 | 各輪が時計回りに開く | 軌道と一覧は最初から出る |
2435
+ * | `chart-stacked-bar` | 左端 | 帯が右へ伸びる | 区画の字はその区画が広がった時 |
2432
2436
  *
2433
2437
  * 対象種別の一覧は `validate.ts` の `DRAW_KINDS` が持つ。
2434
2438
  *
package/src/validate.ts CHANGED
@@ -23,6 +23,9 @@ const TONES = new Set<string>(TONE_NAMES);
23
23
  * | `tree-hierarchy` | 根 | 枝が上の段から下の段へ伸びる |
24
24
  * | `gantt-timeline` | 各帯の始端 | 帯が右へ伸びる |
25
25
  * | `funnel-stages` | 上端 | 段が上から順に積まれる |
26
+ * | `chart-gauge` | 9 時 | 弧が上を通って 3 時へ伸びる |
27
+ * | `chart-radial` | 12 時 | 各輪が時計回りに開く |
28
+ * | `chart-stacked-bar` | 左端 | 帯が右へ伸びる |
26
29
  */
27
30
  export const DRAW_KINDS: ReadonlySet<string> = new Set([
28
31
  "chart-line",
@@ -34,6 +37,9 @@ export const DRAW_KINDS: ReadonlySet<string> = new Set([
34
37
  "tree-hierarchy",
35
38
  "gantt-timeline",
36
39
  "funnel-stages",
40
+ "chart-gauge",
41
+ "chart-radial",
42
+ "chart-stacked-bar",
37
43
  ]);
38
44
  const KINDS = new Set<string>(NODE_KINDS);
39
45
  const ENG_BANNED = /\b(FUNCTION|STORAGE|EVENT LOG|BALANCES MAPPING|READ|WRITE|EMIT|CALL)\b/;