@cardenelabs/cdl 0.9.0 → 0.11.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/CHANGELOG.md +65 -1
- package/SPEC.md +22 -1
- package/dist/index.cjs +162 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +163 -65
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +155 -63
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +156 -64
- package/dist/react.js.map +1 -1
- package/dist/{render-hmFRAabt.d.cts → render-DwH8v5Mb.d.cts} +21 -0
- package/dist/{render-hmFRAabt.d.ts → render-DwH8v5Mb.d.ts} +21 -0
- package/package.json +1 -1
- package/src/builder.ts +13 -1
- package/src/kinds/chart-bar.tsx +49 -12
- package/src/kinds/chart-line.tsx +94 -25
- package/src/kinds/chart-pie.tsx +80 -15
- package/src/render/nodes.tsx +33 -3
- package/src/render/stage.tsx +4 -0
- package/src/types.ts +21 -0
- package/src/validate.ts +34 -4
|
@@ -2047,6 +2047,27 @@ type CdlPhase = {
|
|
|
2047
2047
|
title: string;
|
|
2048
2048
|
body: string;
|
|
2049
2049
|
activate: string[];
|
|
2050
|
+
/**
|
|
2051
|
+
* この phase で「左の起点から描き始める」 node の id (cdl#512)。
|
|
2052
|
+
*
|
|
2053
|
+
* `activate` と同じ形で node id を並べる。 対象の node は phase の進み (0→1) に合わせて
|
|
2054
|
+
* 描かれ、 進みが 1 に達した時点で全体が出る。 起点は種別ごとに違う。
|
|
2055
|
+
*
|
|
2056
|
+
* | kind | 起点 | 動き | 添える値 |
|
|
2057
|
+
* |---|---|---|---|
|
|
2058
|
+
* | `chart-line` | 左端 | 線が右へ伸びる | 点と数字は線の先が届いた時 |
|
|
2059
|
+
* | `chart-bar` | 横軸 | 棒が上へ伸びる | 数字は棒の頭に付いて動く |
|
|
2060
|
+
* | `chart-pie` | 12 時 | 扇が時計回りに開く | 凡例はその扇が開き始めた時 |
|
|
2061
|
+
*
|
|
2062
|
+
* 対象種別の一覧は `validate.ts` の `DRAW_KINDS` が持つ。
|
|
2063
|
+
*
|
|
2064
|
+
* **`activate` を兼ねさせない**。 1 箱で図全体を描く種別 (`chart-*` 等) では焦点が
|
|
2065
|
+
* その箱に解決されるため、 連続する phase で光り続ける図が実在する。 「光っていたら
|
|
2066
|
+
* 描く」 にすると、 値が動くだけの phase でも線を引き直すことになる。
|
|
2067
|
+
*
|
|
2068
|
+
* 書かない phase では従来どおり全長で描く (DOM に dash 属性も付かない)。
|
|
2069
|
+
*/
|
|
2070
|
+
draw?: string[];
|
|
2050
2071
|
/** 数値 state の線形補間 (write phase で 100→90 等) */
|
|
2051
2072
|
tweens: Array<{
|
|
2052
2073
|
stateId: string;
|
|
@@ -2047,6 +2047,27 @@ type CdlPhase = {
|
|
|
2047
2047
|
title: string;
|
|
2048
2048
|
body: string;
|
|
2049
2049
|
activate: string[];
|
|
2050
|
+
/**
|
|
2051
|
+
* この phase で「左の起点から描き始める」 node の id (cdl#512)。
|
|
2052
|
+
*
|
|
2053
|
+
* `activate` と同じ形で node id を並べる。 対象の node は phase の進み (0→1) に合わせて
|
|
2054
|
+
* 描かれ、 進みが 1 に達した時点で全体が出る。 起点は種別ごとに違う。
|
|
2055
|
+
*
|
|
2056
|
+
* | kind | 起点 | 動き | 添える値 |
|
|
2057
|
+
* |---|---|---|---|
|
|
2058
|
+
* | `chart-line` | 左端 | 線が右へ伸びる | 点と数字は線の先が届いた時 |
|
|
2059
|
+
* | `chart-bar` | 横軸 | 棒が上へ伸びる | 数字は棒の頭に付いて動く |
|
|
2060
|
+
* | `chart-pie` | 12 時 | 扇が時計回りに開く | 凡例はその扇が開き始めた時 |
|
|
2061
|
+
*
|
|
2062
|
+
* 対象種別の一覧は `validate.ts` の `DRAW_KINDS` が持つ。
|
|
2063
|
+
*
|
|
2064
|
+
* **`activate` を兼ねさせない**。 1 箱で図全体を描く種別 (`chart-*` 等) では焦点が
|
|
2065
|
+
* その箱に解決されるため、 連続する phase で光り続ける図が実在する。 「光っていたら
|
|
2066
|
+
* 描く」 にすると、 値が動くだけの phase でも線を引き直すことになる。
|
|
2067
|
+
*
|
|
2068
|
+
* 書かない phase では従来どおり全長で描く (DOM に dash 属性も付かない)。
|
|
2069
|
+
*/
|
|
2070
|
+
draw?: string[];
|
|
2050
2071
|
/** 数値 state の線形補間 (write phase で 100→90 等) */
|
|
2051
2072
|
tweens: Array<{
|
|
2052
2073
|
stateId: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cardenelabs/cdl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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",
|
package/src/builder.ts
CHANGED
|
@@ -1355,6 +1355,11 @@ export type InputChain = {
|
|
|
1355
1355
|
|
|
1356
1356
|
export type PhaseBuilder = {
|
|
1357
1357
|
activate: (...ids: string[]) => PhaseBuilder;
|
|
1358
|
+
/**
|
|
1359
|
+
* この phase で左の起点から描き始める node を指定する (cdl#512)。
|
|
1360
|
+
* 対象は phase の進み (0→1) に合わせて描かれる。 現在の対象種別は `chart-line`。
|
|
1361
|
+
*/
|
|
1362
|
+
draw: (...ids: string[]) => PhaseBuilder;
|
|
1358
1363
|
/** 数値 state の線形補間 (write phase で 100→90 等) */
|
|
1359
1364
|
tween: (stateId: string, from: number, to: number) => PhaseBuilder;
|
|
1360
1365
|
/** 任意型 state を即時切替 (この phase 到達で value 上書き、 lerp なし) */
|
|
@@ -1364,6 +1369,7 @@ export type PhaseBuilder = {
|
|
|
1364
1369
|
|
|
1365
1370
|
type PhaseAcc = {
|
|
1366
1371
|
activate: string[];
|
|
1372
|
+
draw: string[];
|
|
1367
1373
|
tweens: Array<{ stateId: string; from: number; to: number }>;
|
|
1368
1374
|
sets: Array<{ stateId: string; value: string | number }>;
|
|
1369
1375
|
badge?: string;
|
|
@@ -1375,6 +1381,10 @@ function makePhaseBuilder(acc: PhaseAcc): PhaseBuilder {
|
|
|
1375
1381
|
acc.activate.push(...ids);
|
|
1376
1382
|
return api;
|
|
1377
1383
|
},
|
|
1384
|
+
draw(...ids) {
|
|
1385
|
+
acc.draw.push(...ids);
|
|
1386
|
+
return api;
|
|
1387
|
+
},
|
|
1378
1388
|
tween(stateId, from, to) {
|
|
1379
1389
|
acc.tweens.push({ stateId, from, to });
|
|
1380
1390
|
return api;
|
|
@@ -3690,7 +3700,7 @@ export function diagram(
|
|
|
3690
3700
|
},
|
|
3691
3701
|
},
|
|
3692
3702
|
phase(phaseId, opts, build) {
|
|
3693
|
-
const acc: PhaseAcc = { activate: [], tweens: [], sets: [] };
|
|
3703
|
+
const acc: PhaseAcc = { activate: [], draw: [], tweens: [], sets: [] };
|
|
3694
3704
|
build(makePhaseBuilder(acc));
|
|
3695
3705
|
phases.push({
|
|
3696
3706
|
id: phaseId,
|
|
@@ -3698,6 +3708,8 @@ export function diagram(
|
|
|
3698
3708
|
title: opts.title,
|
|
3699
3709
|
body: opts.body,
|
|
3700
3710
|
activate: acc.activate,
|
|
3711
|
+
// 空なら欄ごと置かない。 置くと `draw` を使わない図の JSON の形が変わる
|
|
3712
|
+
...(acc.draw.length > 0 ? { draw: acc.draw } : {}),
|
|
3701
3713
|
tweens: acc.tweens,
|
|
3702
3714
|
sets: acc.sets,
|
|
3703
3715
|
badge: acc.badge,
|
package/src/kinds/chart-bar.tsx
CHANGED
|
@@ -6,15 +6,26 @@ import { resolveChartData } from "../render/payload-binding";
|
|
|
6
6
|
* chart-bar kind ... 1 node に datum 配列 (chartData) を保持し、 SVG rect で棒グラフ描画する。
|
|
7
7
|
* layout は node bbox 内側の canvas に縦棒を等間隔配置、 縦軸 = value 0-max、 横軸 = datum label。
|
|
8
8
|
* 各 value は数でも `{signal}` でもよく、 後者は状態が動くたび棒の高さが追随する。
|
|
9
|
+
*
|
|
10
|
+
* phase の `draw` にこの node の id が載っている間は、 棒が横軸から上へ伸びる (cdl#516)。
|
|
11
|
+
* 伸ばすのは棒を包む `<g>` の `transform` で、 `rect` の `x` / `y` / `width` / `height` は
|
|
12
|
+
* 進みに関わらず実値のまま。 値の数字は棒の頭に付いて動く。
|
|
13
|
+
* 軸 / 格子 / 横軸の名前は最初から出る。
|
|
9
14
|
*/
|
|
10
15
|
export function ChartBarNode({
|
|
11
16
|
node,
|
|
12
17
|
active,
|
|
13
18
|
stateValues = {},
|
|
19
|
+
drawing = false,
|
|
20
|
+
progress = 1,
|
|
14
21
|
}: {
|
|
15
22
|
node: LaidNode;
|
|
16
23
|
active: boolean;
|
|
17
24
|
stateValues?: Record<string, string>;
|
|
25
|
+
/** この phase の `draw` に載っているか (cdl#516) */
|
|
26
|
+
drawing?: boolean;
|
|
27
|
+
/** phase の進み (0→1)。 `drawing` が true の時だけ読む */
|
|
28
|
+
progress?: number;
|
|
18
29
|
}): JSX.Element {
|
|
19
30
|
const x0 = node.cx - node.w / 2;
|
|
20
31
|
const y0 = node.cy - node.h / 2;
|
|
@@ -43,6 +54,16 @@ export function ChartBarNode({
|
|
|
43
54
|
return { y: yAt(v), value: v };
|
|
44
55
|
});
|
|
45
56
|
|
|
57
|
+
/**
|
|
58
|
+
* 棒がどこまで伸びたか (0..1、 cdl#516)。 描く指定が無ければ常に 1 = 全高。
|
|
59
|
+
*
|
|
60
|
+
* 進みは呼出側から来る値なので範囲の外 (負 / 1 超 / NaN) が入りうる。 そのまま倍率に
|
|
61
|
+
* 渡すと棒が上下反転したり突き抜けたりするため、 ここで畳む。
|
|
62
|
+
*/
|
|
63
|
+
const 伸び = drawing ? Math.min(1, Math.max(0, Number.isFinite(progress) ? progress : 1)) : 1;
|
|
64
|
+
/** 棒の足元。 ここを動かさない点として上へ伸ばす */
|
|
65
|
+
const 横軸のy = PAD_TOP + canvasH;
|
|
66
|
+
|
|
46
67
|
return (
|
|
47
68
|
<g transform={`translate(${x0} ${y0})`}>
|
|
48
69
|
<rect
|
|
@@ -93,22 +114,38 @@ export function ChartBarNode({
|
|
|
93
114
|
const bx = xAt(idx);
|
|
94
115
|
const by = yAt(d.value);
|
|
95
116
|
const bh = PAD_TOP + canvasH - by;
|
|
117
|
+
// 見えている棒の頭。 数字はここに付いて動く = 空いた場所に数字だけ浮かない
|
|
118
|
+
const 見えている頭 = 横軸のy - (横軸のy - by) * 伸び;
|
|
119
|
+
/**
|
|
120
|
+
* **`rect` の属性は触らない** (cdl#516)。 高さを直接動かすと、棒の高さで値を測る
|
|
121
|
+
* 利用側の検査が進みの途中で別の値を読む。 足元を動かさない点にした倍率で包む。
|
|
122
|
+
*/
|
|
123
|
+
const 棒 = (
|
|
124
|
+
<rect
|
|
125
|
+
data-cdl-role="chart-bar"
|
|
126
|
+
data-cdl-unresolved={d.unresolved ? "true" : undefined}
|
|
127
|
+
x={bx}
|
|
128
|
+
y={by}
|
|
129
|
+
width={barW}
|
|
130
|
+
height={bh}
|
|
131
|
+
rx={2}
|
|
132
|
+
fill="var(--cdl-tone-accent, #2d6a8f)"
|
|
133
|
+
fillOpacity={0.9}
|
|
134
|
+
/>
|
|
135
|
+
);
|
|
96
136
|
return (
|
|
97
137
|
<g key={`bar-${idx}`}>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
fill="var(--cdl-tone-accent, #2d6a8f)"
|
|
107
|
-
fillOpacity={0.9}
|
|
108
|
-
/>
|
|
138
|
+
{/* 描く指定が無い図には包みを足さない = 既存の図の DOM を 1 要素も変えない */}
|
|
139
|
+
{drawing ? (
|
|
140
|
+
<g transform={`translate(0 ${横軸のy}) scale(1 ${伸び}) translate(0 ${-横軸のy})`}>
|
|
141
|
+
{棒}
|
|
142
|
+
</g>
|
|
143
|
+
) : (
|
|
144
|
+
棒
|
|
145
|
+
)}
|
|
109
146
|
<text
|
|
110
147
|
x={bx + barW / 2}
|
|
111
|
-
y={
|
|
148
|
+
y={見えている頭 - 6}
|
|
112
149
|
fontSize={11}
|
|
113
150
|
textAnchor="middle"
|
|
114
151
|
fill="var(--cdl-text, #1a1f2a)"
|
package/src/kinds/chart-line.tsx
CHANGED
|
@@ -7,15 +7,29 @@ import { resolveChartData } from "../render/payload-binding";
|
|
|
7
7
|
* layout は node bbox (node.w × node.h) 内側に padding (top/right/bottom/left) を確保し、
|
|
8
8
|
* その内側 rect を chart canvas とみなす。 縦軸 = value min-max、 横軸 = datum index。
|
|
9
9
|
* 各 value は数でも `{signal}` でもよく、 後者は状態が動くたび折れ線が追随する。
|
|
10
|
+
*
|
|
11
|
+
* phase の `draw` にこの node の id が載っている間は、 折れ線が左の起点から伸びる (cdl#512)。
|
|
12
|
+
* 伸ばし方は dash (`pathLength` / `strokeDasharray` / `strokeDashoffset`) で、 `points` は
|
|
13
|
+
* 進みに関わらず全点を持つ。 点と値の数字は線の先が自分の位置に届いた時に現れ、
|
|
14
|
+
* 軸 / 格子 / 横軸の名前は最初から出る。
|
|
10
15
|
*/
|
|
11
16
|
export function ChartLineNode({
|
|
12
17
|
node,
|
|
13
18
|
active,
|
|
14
19
|
stateValues = {},
|
|
20
|
+
drawing = false,
|
|
21
|
+
progress = 1,
|
|
15
22
|
}: {
|
|
16
23
|
node: LaidNode;
|
|
17
24
|
active: boolean;
|
|
18
25
|
stateValues?: Record<string, string>;
|
|
26
|
+
/**
|
|
27
|
+
* この phase の `draw` に載っているか (cdl#512)。 true の間だけ折れ線が左の起点から
|
|
28
|
+
* 伸び、 点と値の数字は線の先が自分の位置に届いた時に現れる。
|
|
29
|
+
*/
|
|
30
|
+
drawing?: boolean;
|
|
31
|
+
/** phase の進み (0→1)。 `drawing` が true の時だけ読む */
|
|
32
|
+
progress?: number;
|
|
19
33
|
}): JSX.Element {
|
|
20
34
|
const x0 = node.cx - node.w / 2;
|
|
21
35
|
const y0 = node.cy - node.h / 2;
|
|
@@ -45,6 +59,44 @@ export function ChartLineNode({
|
|
|
45
59
|
|
|
46
60
|
const points = data.map((d, idx) => `${xAt(idx)},${yAt(d.value)}`).join(" ");
|
|
47
61
|
|
|
62
|
+
// dash は polyline の実際の経路長で進むため、 点の表示判定も同じ経路長に揃える。
|
|
63
|
+
// 点番号の比率では、 急傾斜と平坦な区間が混ざる線で先端と点がずれる。
|
|
64
|
+
const 点までの長さ: number[] = [];
|
|
65
|
+
let 線の全長 = 0;
|
|
66
|
+
let 前の点: { x: number; y: number } | undefined;
|
|
67
|
+
for (const [idx, datum] of data.entries()) {
|
|
68
|
+
const 今の点 = { x: xAt(idx), y: yAt(datum.value) };
|
|
69
|
+
if (前の点) {
|
|
70
|
+
線の全長 += Math.hypot(今の点.x - 前の点.x, 今の点.y - 前の点.y);
|
|
71
|
+
}
|
|
72
|
+
点までの長さ.push(線の全長);
|
|
73
|
+
前の点 = 今の点;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 線がどこまで伸びたか (0..1、 cdl#512)。 描く指定が無ければ常に 1 = 全長。
|
|
78
|
+
*
|
|
79
|
+
* 進みは呼出側から来る値なので、 範囲の外 (負 / 1 超 / NaN) が入りうる。 そのまま
|
|
80
|
+
* `strokeDashoffset` に渡すと線が消えたり逆向きに縮んだりするため、 ここで畳む。
|
|
81
|
+
*/
|
|
82
|
+
const 伸び = drawing ? Math.min(1, Math.max(0, Number.isFinite(progress) ? progress : 1)) : 1;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* その点まで線の先が届いたか。
|
|
86
|
+
*
|
|
87
|
+
* 点の位置は polyline の累積経路長で判定する。 **左端は進み 0 でも出す** = そこが
|
|
88
|
+
* 線の起点で、 起点が無いと線がどこから伸びているのか読めない。
|
|
89
|
+
*
|
|
90
|
+
* 点が 1 つの図では線を引かない (下の `data.length > 1`) ため、 割り算の分母が 0 に
|
|
91
|
+
* なる形には入らないが、 呼出の順に依らず成り立つよう先に返す。
|
|
92
|
+
*/
|
|
93
|
+
const 線の先が届いた = (idx: number): boolean => {
|
|
94
|
+
if (!drawing) return true;
|
|
95
|
+
if (data.length <= 1) return true;
|
|
96
|
+
if (線の全長 === 0) return true;
|
|
97
|
+
return 伸び >= (点までの長さ[idx] ?? 0) / 線の全長;
|
|
98
|
+
};
|
|
99
|
+
|
|
48
100
|
const gridCount = 4;
|
|
49
101
|
const gridTicks = Array.from({ length: gridCount + 1 }, (_, i) => {
|
|
50
102
|
const v = vMin + (vRange * i) / gridCount;
|
|
@@ -114,6 +166,16 @@ export function ChartLineNode({
|
|
|
114
166
|
strokeWidth={2.5}
|
|
115
167
|
strokeLinejoin="round"
|
|
116
168
|
strokeLinecap="round"
|
|
169
|
+
// 伸ばすのは dash で行い、 **`points` は触らない** (cdl#512)。 点を削る形にすると
|
|
170
|
+
// 利用側が数えている点の数が進みの途中で変わる (dragon の `kind-geometry-check`)。
|
|
171
|
+
//
|
|
172
|
+
// `pathLength={1}` を置くのは、 実長を測るには DOM の ref が要り、 静的な書き出しでは
|
|
173
|
+
// 測れないため。 長さを 1 に正規化すれば進みをそのまま offset に使える。
|
|
174
|
+
//
|
|
175
|
+
// 描く指定が無い図には属性を 1 つも足さない = 既存の図の DOM を変えない
|
|
176
|
+
{...(drawing
|
|
177
|
+
? { pathLength: 1, strokeDasharray: 1, strokeDashoffset: 1 - 伸び }
|
|
178
|
+
: {})}
|
|
117
179
|
/>
|
|
118
180
|
)}
|
|
119
181
|
{data.map((d, idx) => {
|
|
@@ -129,33 +191,40 @@ export function ChartLineNode({
|
|
|
129
191
|
(prev === undefined || d.value <= prev) && (next === undefined || d.value <= next);
|
|
130
192
|
const labelAbove = !isValley;
|
|
131
193
|
const labelY = labelAbove ? cy - 12 : cy + 18;
|
|
194
|
+
// 点と値の数字は線の先に合わせて現れる。 **横軸の名前は最初から出す** =
|
|
195
|
+
// 名前は図の枠 (軸 / 格子) の側で、 これが順に増えると軸そのものが伸びて見える
|
|
196
|
+
const 出す = 線の先が届いた(idx);
|
|
132
197
|
return (
|
|
133
198
|
<g key={`pt-${idx}`} data-cdl-unresolved={d.unresolved ? "true" : undefined}>
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
199
|
+
{出す && (
|
|
200
|
+
<>
|
|
201
|
+
<circle
|
|
202
|
+
cx={cx}
|
|
203
|
+
cy={cy}
|
|
204
|
+
r={5}
|
|
205
|
+
fill="var(--cdl-node-fill, #ffffff)"
|
|
206
|
+
stroke="var(--cdl-tone-accent, #2d6a8f)"
|
|
207
|
+
strokeWidth={2.5}
|
|
208
|
+
/>
|
|
209
|
+
<circle
|
|
210
|
+
cx={cx}
|
|
211
|
+
cy={cy}
|
|
212
|
+
r={2.5}
|
|
213
|
+
fill="var(--cdl-tone-accent, #2d6a8f)"
|
|
214
|
+
/>
|
|
215
|
+
<text
|
|
216
|
+
data-cdl-role="chart-line-value"
|
|
217
|
+
x={cx}
|
|
218
|
+
y={labelY}
|
|
219
|
+
fontSize={11}
|
|
220
|
+
fontWeight={600}
|
|
221
|
+
textAnchor="middle"
|
|
222
|
+
fill="var(--cdl-text, #1a1f2a)"
|
|
223
|
+
>
|
|
224
|
+
{d.value.toLocaleString()}
|
|
225
|
+
</text>
|
|
226
|
+
</>
|
|
227
|
+
)}
|
|
159
228
|
<text
|
|
160
229
|
x={cx}
|
|
161
230
|
y={PAD_TOP + canvasH + 20}
|
package/src/kinds/chart-pie.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { useId, type JSX } from "react";
|
|
2
2
|
import type { LaidNode, Tone } from "../types";
|
|
3
3
|
import { TONE } from "../render/tone";
|
|
4
4
|
import { resolveChartData } from "../render/payload-binding";
|
|
@@ -7,16 +7,29 @@ import { resolveChartData } from "../render/payload-binding";
|
|
|
7
7
|
* chart-pie kind ... 1 node に datum 配列 (chartData) を保持し、 SVG path arc で円グラフ描画する。
|
|
8
8
|
* layout は node bbox 内側の左半分に円を配置、 右半分に凡例 (label + %) を stack 表示する。
|
|
9
9
|
* 各 value は数でも `{signal}` でもよく、 後者は状態が動くたび扇の角度が追随する。
|
|
10
|
+
*
|
|
11
|
+
* phase の `draw` にこの node の id が載っている間は、 円が 12 時から時計回りに開く (cdl#516)。
|
|
12
|
+
* 開き方は扇形の切り抜き (`clipPath`) で、 各扇の `d` は進みに関わらず実値のまま。
|
|
13
|
+
* 凡例はその扇が開き始めた時に出る。
|
|
10
14
|
*/
|
|
11
15
|
export function ChartPieNode({
|
|
12
16
|
node,
|
|
13
17
|
active,
|
|
14
18
|
stateValues = {},
|
|
19
|
+
drawing = false,
|
|
20
|
+
progress = 1,
|
|
15
21
|
}: {
|
|
16
22
|
node: LaidNode;
|
|
17
23
|
active: boolean;
|
|
18
24
|
stateValues?: Record<string, string>;
|
|
25
|
+
/** この phase の `draw` に載っているか (cdl#516) */
|
|
26
|
+
drawing?: boolean;
|
|
27
|
+
/** phase の進み (0→1)。 `drawing` が true の時だけ読む */
|
|
28
|
+
progress?: number;
|
|
19
29
|
}): JSX.Element {
|
|
30
|
+
// node id は diagram 内だけで一意。 同じ page に複数 diagram がある時も SVG の参照先が
|
|
31
|
+
// ぶつからないよう、 React が component instance ごとに割り当てる id を使う。
|
|
32
|
+
const instanceId = useId().replace(/[^A-Za-z0-9_-]/g, "");
|
|
20
33
|
const x0 = node.cx - node.w / 2;
|
|
21
34
|
const y0 = node.cy - node.h / 2;
|
|
22
35
|
const data = resolveChartData(node.chartData ?? [], stateValues);
|
|
@@ -31,12 +44,16 @@ export function ChartPieNode({
|
|
|
31
44
|
const cy = PAD_TOP + chartAreaH / 2;
|
|
32
45
|
|
|
33
46
|
let cumAngle = -Math.PI / 2;
|
|
47
|
+
/** その扇が始まるまでの取り分の累計。 開き始めたかの判定に使う (cdl#516) */
|
|
48
|
+
let cumFrac = 0;
|
|
34
49
|
const slices = data.map((d) => {
|
|
35
50
|
const frac = d.value / total;
|
|
36
51
|
const angle = frac * Math.PI * 2;
|
|
37
52
|
const a0 = cumAngle;
|
|
38
53
|
const a1 = cumAngle + angle;
|
|
39
54
|
cumAngle = a1;
|
|
55
|
+
const startFrac = cumFrac;
|
|
56
|
+
cumFrac += frac;
|
|
40
57
|
|
|
41
58
|
const x1 = cx + radius * Math.cos(a0);
|
|
42
59
|
const y1 = cy + radius * Math.sin(a0);
|
|
@@ -44,9 +61,21 @@ export function ChartPieNode({
|
|
|
44
61
|
const y2 = cy + radius * Math.sin(a1);
|
|
45
62
|
const large = angle > Math.PI ? 1 : 0;
|
|
46
63
|
const path = `M ${cx} ${cy} L ${x1} ${y1} A ${radius} ${radius} 0 ${large} 1 ${x2} ${y2} Z`;
|
|
47
|
-
return { d, frac, path };
|
|
64
|
+
return { d, frac, path, startFrac };
|
|
48
65
|
});
|
|
49
66
|
|
|
67
|
+
/**
|
|
68
|
+
* 円がどこまで開いたか (0..1、 cdl#516)。 描く指定が無ければ常に 1 = 全周。
|
|
69
|
+
*
|
|
70
|
+
* 進みは呼出側から来る値なので範囲の外 (負 / 1 超 / NaN) が入りうる。 そのまま角度に
|
|
71
|
+
* 掛けると 1 周を超えて切り抜きが壊れるため、 ここで畳む。
|
|
72
|
+
*/
|
|
73
|
+
const 開き = drawing ? Math.min(1, Math.max(0, Number.isFinite(progress) ? progress : 1)) : 1;
|
|
74
|
+
// SVG の id は document 全体の名前空間。 node id ではなく instance id で分ける
|
|
75
|
+
const 切り抜きid = `cdl-pie-draw-${instanceId}`;
|
|
76
|
+
/** その扇が開き始めたか。 開き始めた扇の凡例だけを出す */
|
|
77
|
+
const 開き始めた = (startFrac: number): boolean => !drawing || 開き > startFrac;
|
|
78
|
+
|
|
50
79
|
const legendX = pieAreaW + 8;
|
|
51
80
|
const legendGap = Math.min(28, chartAreaH / Math.max(data.length, 1));
|
|
52
81
|
const legendStartY = PAD_TOP + (chartAreaH - legendGap * data.length) / 2 + legendGap / 2;
|
|
@@ -64,20 +93,40 @@ export function ChartPieNode({
|
|
|
64
93
|
stroke={active ? "var(--cdl-tone-accent, #2d6a8f)" : "var(--cdl-divider, #d4d4d8)"}
|
|
65
94
|
strokeWidth={active ? 3 : 1.25}
|
|
66
95
|
/>
|
|
67
|
-
{
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
))}
|
|
96
|
+
{drawing && (
|
|
97
|
+
<defs>
|
|
98
|
+
<clipPath id={切り抜きid}>{扇形の切り抜き(cx, cy, radius + 2, 開き)}</clipPath>
|
|
99
|
+
</defs>
|
|
100
|
+
)}
|
|
101
|
+
{/*
|
|
102
|
+
**扇の `d` は触らない** (cdl#516)。 進みで再計算すると、扇の形から取り分を測る
|
|
103
|
+
利用側が進みの途中で別の値を読む。 見える範囲だけを切り抜きで変える。
|
|
104
|
+
|
|
105
|
+
描く指定が無い図には包みも `clip-path` も足さない = 既存の図の DOM を変えない
|
|
106
|
+
*/}
|
|
107
|
+
<g {...(drawing ? { clipPath: `url(#${切り抜きid})` } : {})}>
|
|
108
|
+
{slices.map((s, idx) => (
|
|
109
|
+
<path
|
|
110
|
+
key={`slice-${idx}`}
|
|
111
|
+
data-cdl-role="chart-pie-slice"
|
|
112
|
+
data-cdl-unresolved={s.d.unresolved ? "true" : undefined}
|
|
113
|
+
d={s.path}
|
|
114
|
+
fill={sliceColor(s.d.tone, idx)}
|
|
115
|
+
fillOpacity={active ? 0.95 : 0.85}
|
|
116
|
+
stroke="var(--cdl-node-fill, #ffffff)"
|
|
117
|
+
strokeWidth={1.5}
|
|
118
|
+
/>
|
|
119
|
+
))}
|
|
120
|
+
</g>
|
|
79
121
|
|
|
80
|
-
{
|
|
122
|
+
{/*
|
|
123
|
+
行の位置は `idx` から決まる。 いまの開き方は必ず先頭からの連続なので、開いた扇だけを
|
|
124
|
+
`filter` で抜いても位置は同じになる (変異試験で実測 = 差が出ない)。
|
|
125
|
+
|
|
126
|
+
それでも `idx` を保つ形で書くのは、位置の計算が抜き方に依存しないため。 開く順が
|
|
127
|
+
先頭からでなくなった時に、この場所を直し忘れても位置がずれない
|
|
128
|
+
*/}
|
|
129
|
+
{slices.map((s, idx) => !開き始めた(s.startFrac) ? null : (
|
|
81
130
|
<g key={`legend-${idx}`} transform={`translate(${legendX} ${legendStartY + idx * legendGap})`}>
|
|
82
131
|
<rect
|
|
83
132
|
x={0}
|
|
@@ -110,6 +159,22 @@ export function ChartPieNode({
|
|
|
110
159
|
}
|
|
111
160
|
|
|
112
161
|
|
|
162
|
+
/**
|
|
163
|
+
* 12 時から時計回りに開く扇形 (cdl#516)。 円を切り抜く形に使う。
|
|
164
|
+
*
|
|
165
|
+
* 1 周ちょうどは扇形で表せない (始点と終点が同じ点になり、 弧の向きが決まらない)。
|
|
166
|
+
* その時だけ円をそのまま返す = 進み 1 で図が消える形を避ける。
|
|
167
|
+
*/
|
|
168
|
+
function 扇形の切り抜き(cx: number, cy: number, r: number, 開き: number): JSX.Element | null {
|
|
169
|
+
if (開き <= 0) return null;
|
|
170
|
+
if (開き >= 1) return <circle cx={cx} cy={cy} r={r} />;
|
|
171
|
+
const a0 = -Math.PI / 2;
|
|
172
|
+
const a1 = a0 + 開き * Math.PI * 2;
|
|
173
|
+
const large = 開き > 0.5 ? 1 : 0;
|
|
174
|
+
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`;
|
|
175
|
+
return <path d={d} />;
|
|
176
|
+
}
|
|
177
|
+
|
|
113
178
|
const FALLBACK_ORDER: Tone[] = ["accent", "teal", "warning", "success", "info", "error"];
|
|
114
179
|
|
|
115
180
|
function sliceColor(tone: Tone | undefined, idx: number): string {
|
package/src/render/nodes.tsx
CHANGED
|
@@ -84,12 +84,18 @@ import { hasUnresolvedRef, interpolate, resolveNumericAttr } from "./utils";
|
|
|
84
84
|
export function CdlNodeView({
|
|
85
85
|
node,
|
|
86
86
|
active,
|
|
87
|
+
drawing = false,
|
|
87
88
|
progress,
|
|
88
89
|
stateValues,
|
|
89
90
|
currentPhaseId,
|
|
90
91
|
}: {
|
|
91
92
|
node: LaidNode;
|
|
92
93
|
active: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* この phase の `draw` に載っているか (cdl#512)。 true の間だけ、 対象の種別が
|
|
96
|
+
* phase の進み (`progress`) に合わせて左の起点から描かれる。
|
|
97
|
+
*/
|
|
98
|
+
drawing?: boolean;
|
|
93
99
|
progress: number;
|
|
94
100
|
stateValues: Record<string, string>;
|
|
95
101
|
currentPhaseId: string | undefined;
|
|
@@ -154,11 +160,35 @@ export function CdlNodeView({
|
|
|
154
160
|
// 数と語を取る 7 種に対し、mind-map / tree-hierarchy は **名前だけ** を取る (#467)。
|
|
155
161
|
// 親子の繋がりと、描かれない欄 (補足) は解決しない。
|
|
156
162
|
case "chart-line":
|
|
157
|
-
return
|
|
163
|
+
return (
|
|
164
|
+
<ChartLineNode
|
|
165
|
+
node={resolvedNode}
|
|
166
|
+
active={active}
|
|
167
|
+
stateValues={stateValues}
|
|
168
|
+
drawing={drawing}
|
|
169
|
+
progress={progress}
|
|
170
|
+
/>
|
|
171
|
+
);
|
|
158
172
|
case "chart-pie":
|
|
159
|
-
return
|
|
173
|
+
return (
|
|
174
|
+
<ChartPieNode
|
|
175
|
+
node={resolvedNode}
|
|
176
|
+
active={active}
|
|
177
|
+
stateValues={stateValues}
|
|
178
|
+
drawing={drawing}
|
|
179
|
+
progress={progress}
|
|
180
|
+
/>
|
|
181
|
+
);
|
|
160
182
|
case "chart-bar":
|
|
161
|
-
return
|
|
183
|
+
return (
|
|
184
|
+
<ChartBarNode
|
|
185
|
+
node={resolvedNode}
|
|
186
|
+
active={active}
|
|
187
|
+
stateValues={stateValues}
|
|
188
|
+
drawing={drawing}
|
|
189
|
+
progress={progress}
|
|
190
|
+
/>
|
|
191
|
+
);
|
|
162
192
|
case "gantt-timeline":
|
|
163
193
|
return <GanttNode node={resolvedNode} active={active} stateValues={stateValues} />;
|
|
164
194
|
case "mind-map":
|
package/src/render/stage.tsx
CHANGED
|
@@ -25,6 +25,9 @@ export function CdlStage({
|
|
|
25
25
|
svgRef?: React.RefObject<SVGSVGElement | null>;
|
|
26
26
|
}): JSX.Element {
|
|
27
27
|
const activeSet = new Set(currentPhase?.activate ?? []);
|
|
28
|
+
// この phase で左の起点から描き始める node (cdl#512)。 `activate` とは別集合で持つ =
|
|
29
|
+
// 焦点が当たり続ける図で毎 phase 引き直すことになるため、 光っているかを引き金にしない
|
|
30
|
+
const drawSet = new Set(currentPhase?.draw ?? []);
|
|
28
31
|
// 縦線の終点は全 lane で 1 つに揃える (等長保証)。 値の決め方と、 1 lane の異常が図全体から
|
|
29
32
|
// 縦線を消していた形の対処は `uniformLifelineEndY` の説明。
|
|
30
33
|
//
|
|
@@ -208,6 +211,7 @@ export function CdlStage({
|
|
|
208
211
|
<CdlNodeView
|
|
209
212
|
node={node}
|
|
210
213
|
active={activeSet.has(node.id)}
|
|
214
|
+
drawing={drawSet.has(node.id)}
|
|
211
215
|
progress={progress}
|
|
212
216
|
stateValues={stateValues}
|
|
213
217
|
currentPhaseId={currentPhase?.id}
|
package/src/types.ts
CHANGED
|
@@ -2124,6 +2124,27 @@ export type CdlPhase = {
|
|
|
2124
2124
|
title: string;
|
|
2125
2125
|
body: string;
|
|
2126
2126
|
activate: string[];
|
|
2127
|
+
/**
|
|
2128
|
+
* この phase で「左の起点から描き始める」 node の id (cdl#512)。
|
|
2129
|
+
*
|
|
2130
|
+
* `activate` と同じ形で node id を並べる。 対象の node は phase の進み (0→1) に合わせて
|
|
2131
|
+
* 描かれ、 進みが 1 に達した時点で全体が出る。 起点は種別ごとに違う。
|
|
2132
|
+
*
|
|
2133
|
+
* | kind | 起点 | 動き | 添える値 |
|
|
2134
|
+
* |---|---|---|---|
|
|
2135
|
+
* | `chart-line` | 左端 | 線が右へ伸びる | 点と数字は線の先が届いた時 |
|
|
2136
|
+
* | `chart-bar` | 横軸 | 棒が上へ伸びる | 数字は棒の頭に付いて動く |
|
|
2137
|
+
* | `chart-pie` | 12 時 | 扇が時計回りに開く | 凡例はその扇が開き始めた時 |
|
|
2138
|
+
*
|
|
2139
|
+
* 対象種別の一覧は `validate.ts` の `DRAW_KINDS` が持つ。
|
|
2140
|
+
*
|
|
2141
|
+
* **`activate` を兼ねさせない**。 1 箱で図全体を描く種別 (`chart-*` 等) では焦点が
|
|
2142
|
+
* その箱に解決されるため、 連続する phase で光り続ける図が実在する。 「光っていたら
|
|
2143
|
+
* 描く」 にすると、 値が動くだけの phase でも線を引き直すことになる。
|
|
2144
|
+
*
|
|
2145
|
+
* 書かない phase では従来どおり全長で描く (DOM に dash 属性も付かない)。
|
|
2146
|
+
*/
|
|
2147
|
+
draw?: string[];
|
|
2127
2148
|
/** 数値 state の線形補間 (write phase で 100→90 等) */
|
|
2128
2149
|
tweens: Array<{ stateId: string; from: number; to: number }>;
|
|
2129
2150
|
/** 任意型 state の即時切替 (この phase に到達したら値を上書き、 lerp なし) */
|