@cardenelabs/cdl 0.40.5 → 0.40.7
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 +52 -1
- package/dist/index.cjs +55 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +55 -16
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +55 -16
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +55 -16
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
- package/src/kinds/chart-bar.tsx +89 -16
- package/src/validate.ts +27 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cardenelabs/cdl",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.7",
|
|
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/kinds/chart-bar.tsx
CHANGED
|
@@ -3,9 +3,41 @@ import type { LaidNode } from "../types";
|
|
|
3
3
|
import { resolveChartData } from "../render/payload-binding";
|
|
4
4
|
import { 進みを畳む } from "./draw-progress";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* 目盛りの刻みを、人が読む数から選ぶ (cdl#747)。
|
|
8
|
+
*
|
|
9
|
+
* `1 / 2 / 2.5 / 5` に 10 の累乗を掛けた中から、目盛りが目安の本数前後になるものを取る。
|
|
10
|
+
* 最大値をそのまま割ると `680 / 4 = 170` のような数が目盛りに出て、機械が割った数字に見える。
|
|
11
|
+
*
|
|
12
|
+
* 最大が 0 以下の図では刻みを決められないので 0 を返す (呼ぶ側が今までの割り方に落ちる)。
|
|
13
|
+
*/
|
|
14
|
+
export function 目盛りの刻み(最大: number, 目安の本数: number): number {
|
|
15
|
+
if (!(最大 > 0) || !(目安の本数 > 0)) return 0;
|
|
16
|
+
const 粗い = 最大 / 目安の本数;
|
|
17
|
+
const 桁 = 10 ** Math.floor(Math.log10(粗い));
|
|
18
|
+
const 正規 = 粗い / 桁;
|
|
19
|
+
const 候補 = 正規 <= 1 ? 1 : 正規 <= 2 ? 2 : 正規 <= 2.5 ? 2.5 : 正規 <= 5 ? 5 : 10;
|
|
20
|
+
return 候補 * 桁;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* その数を書き切るのに要る小数の桁数。
|
|
25
|
+
*
|
|
26
|
+
* `String(0.0000025)` は指数の形になり `indexOf(".")` では数えられないため、
|
|
27
|
+
* 仮数と指数に分けて数える (`2.5e-7` = 仮数の小数 1 桁 - 指数 -7 = 8 桁)。
|
|
28
|
+
*/
|
|
29
|
+
function 小数の桁(n: number): number {
|
|
30
|
+
const [仮数, 指数] = n.toExponential().split("e");
|
|
31
|
+
const 小数部 = (仮数 ?? "").split(".")[1]?.length ?? 0;
|
|
32
|
+
return Math.min(20, Math.max(0, 小数部 - Number(指数)));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 刻みの倍数を足し込んだ時に出る誤差 (`0.1 * 3 = 0.30000000000000004`) を落とす */
|
|
36
|
+
const 桁で丸める = (v: number, 桁: number): number => Number(v.toFixed(桁));
|
|
37
|
+
|
|
6
38
|
/**
|
|
7
39
|
* chart-bar kind ... 1 node に datum 配列 (chartData) を保持し、 SVG rect で棒グラフ描画する。
|
|
8
|
-
* layout は node bbox 内側の canvas に縦棒を等間隔配置、 縦軸 = value 0
|
|
40
|
+
* layout は node bbox 内側の canvas に縦棒を等間隔配置、 縦軸 = value 0-天井、 横軸 = datum label。
|
|
9
41
|
* 各 value は数でも `{signal}` でもよく、 後者は状態が動くたび棒の高さが追随する。
|
|
10
42
|
*
|
|
11
43
|
* phase の `draw` にこの node の id が載っている間は、 棒が横軸から上へ伸びる (cdl#516)。
|
|
@@ -32,7 +64,8 @@ export function ChartBarNode({
|
|
|
32
64
|
const y0 = node.cy - node.h / 2;
|
|
33
65
|
const data = resolveChartData(node.chartData ?? [], stateValues);
|
|
34
66
|
|
|
35
|
-
|
|
67
|
+
// 値札を枠に重ねない余白 (cdl#747)。 天井まで届いた棒の数字がここに収まる
|
|
68
|
+
const PAD_TOP = 34;
|
|
36
69
|
const PAD_RIGHT = 24;
|
|
37
70
|
const PAD_BOTTOM = 44;
|
|
38
71
|
const PAD_LEFT = 60;
|
|
@@ -40,21 +73,55 @@ export function ChartBarNode({
|
|
|
40
73
|
const canvasH = node.h - PAD_TOP - PAD_BOTTOM;
|
|
41
74
|
|
|
42
75
|
const vMax = data.length > 0 ? Math.max(...data.map((d) => d.value)) : 1;
|
|
43
|
-
const vRange = vMax || 1;
|
|
44
76
|
|
|
45
|
-
const
|
|
77
|
+
const 目盛りの目安 = 4;
|
|
78
|
+
/**
|
|
79
|
+
* 目盛りを引く元の高さ。 値が 0 以下しか無い図 (と値を読めない図) では 1 を使う =
|
|
80
|
+
* 最大値をそのまま使うと 0 が 5 つ並ぶ縦軸になり、何も読めない。
|
|
81
|
+
*/
|
|
82
|
+
const 天井のもと = vMax > 0 ? vMax : 1;
|
|
83
|
+
const 刻み = 目盛りの刻み(天井のもと, 目盛りの目安);
|
|
84
|
+
const 刻みの桁 = 小数の桁(刻み);
|
|
85
|
+
/**
|
|
86
|
+
* 縦軸の天井。 **最大値をそのまま天井にしない** (cdl#747)。
|
|
87
|
+
* 刻みの倍数へ切り上げるので、目盛りが人の読む数になる。
|
|
88
|
+
*
|
|
89
|
+
* **天井を水増しして余白を作らない** = 最大 100 の図は `0 / 25 / 50 / 75 / 100` が
|
|
90
|
+
* 正しい形で、余白のために `125` へ伸ばすと目盛りが不自然になる。
|
|
91
|
+
* 値札が枠に重ならない余白は `PAD_TOP` が持つ。
|
|
92
|
+
*/
|
|
93
|
+
const 天井 = 桁で丸める(Math.ceil(天井のもと / 刻み - 1e-9) * 刻み, 刻みの桁);
|
|
94
|
+
|
|
95
|
+
/** 棒 1 本に対する隙間の割合。 棒 : 隙間 = この比 (cdl#747) */
|
|
96
|
+
const 棒と隙間の比 = 2.2;
|
|
97
|
+
/** 棒の太さの上限。 本数が少ない図で棒が塀のように太るのを止める */
|
|
98
|
+
const 棒の上限 = 96;
|
|
46
99
|
const barCount = data.length || 1;
|
|
47
|
-
|
|
100
|
+
// 隙間 1 つ分を単位に、棒 barCount 本 + 隙間 (barCount + 1) 個で canvas を割る
|
|
101
|
+
const 単位 = canvasW / (barCount * 棒と隙間の比 + (barCount + 1));
|
|
102
|
+
const barW = Math.max(0, Math.min(単位 * 棒と隙間の比, 棒の上限));
|
|
103
|
+
// 上限で余った幅は隙間へ均等に配る = 棒の並びが片寄らない
|
|
104
|
+
const barGap = (canvasW - barW * barCount) / (barCount + 1);
|
|
48
105
|
|
|
49
|
-
const yAt = (v: number) => PAD_TOP + canvasH - (v /
|
|
106
|
+
const yAt = (v: number) => PAD_TOP + canvasH - (v / 天井) * canvasH;
|
|
50
107
|
const xAt = (idx: number) => PAD_LEFT + barGap + idx * (barW + barGap);
|
|
51
108
|
|
|
52
|
-
const
|
|
53
|
-
const gridTicks = Array.from({ length:
|
|
54
|
-
const v = (
|
|
109
|
+
const 目盛りの本数 = Math.round(天井 / 刻み);
|
|
110
|
+
const gridTicks = Array.from({ length: 目盛りの本数 + 1 }, (_, i) => {
|
|
111
|
+
const v = 桁で丸める(刻み * i, 刻みの桁);
|
|
55
112
|
return { y: yAt(v), value: v };
|
|
56
113
|
});
|
|
57
114
|
|
|
115
|
+
/**
|
|
116
|
+
* 棒の濃さ。 **いちばん大きい棒だけ濃く出す** (cdl#747)。
|
|
117
|
+
*
|
|
118
|
+
* 色は 1 つのまま濃さだけを変える = 棒グラフは同じ量どうしの比較で、色は量を表さない。
|
|
119
|
+
* 4 本に 4 色を配ると「色に意味がある」 と誤読する余地が残る。
|
|
120
|
+
*/
|
|
121
|
+
const 濃さ = (v: number): number => (vMax > 0 ? (v >= vMax ? 1 : 0.45) : 0.9);
|
|
122
|
+
/** 角丸は棒の太さに合わせる。 太い棒に固定値を当てると直角に見える */
|
|
123
|
+
const 角丸 = Math.min(barW * 0.06, 8);
|
|
124
|
+
|
|
58
125
|
/** 棒がどこまで伸びたか (0..1、 cdl#516)。 畳み方は `draw-progress.ts` が持つ */
|
|
59
126
|
const 伸び = 進みを畳む(drawing, progress);
|
|
60
127
|
/** 棒の足元。 ここを動かさない点として上へ伸ばす */
|
|
@@ -86,13 +153,14 @@ export function ChartBarNode({
|
|
|
86
153
|
opacity={0.65}
|
|
87
154
|
/>
|
|
88
155
|
<text
|
|
156
|
+
data-cdl-role="chart-bar-tick"
|
|
89
157
|
x={PAD_LEFT - 8}
|
|
90
158
|
y={t.y + 4}
|
|
91
159
|
fontSize={11}
|
|
92
160
|
textAnchor="end"
|
|
93
161
|
fill="var(--cdl-text-dim, #5a6270)"
|
|
94
162
|
>
|
|
95
|
-
{
|
|
163
|
+
{t.value}
|
|
96
164
|
</text>
|
|
97
165
|
</g>
|
|
98
166
|
))}
|
|
@@ -109,7 +177,8 @@ export function ChartBarNode({
|
|
|
109
177
|
{data.map((d, idx) => {
|
|
110
178
|
const bx = xAt(idx);
|
|
111
179
|
const by = yAt(d.value);
|
|
112
|
-
|
|
180
|
+
// 負の値は下へ伸ばす軸を持たないので足元で止める (`rect` に負の高さを書かない)
|
|
181
|
+
const bh = Math.max(0, PAD_TOP + canvasH - by);
|
|
113
182
|
// 見えている棒の頭。 数字はここに付いて動く = 空いた場所に数字だけ浮かない
|
|
114
183
|
const 見えている頭 = 横軸のy - (横軸のy - by) * 伸び;
|
|
115
184
|
/**
|
|
@@ -124,9 +193,9 @@ export function ChartBarNode({
|
|
|
124
193
|
y={by}
|
|
125
194
|
width={barW}
|
|
126
195
|
height={bh}
|
|
127
|
-
rx={
|
|
196
|
+
rx={角丸}
|
|
128
197
|
fill="var(--cdl-tone-accent, #2d6a8f)"
|
|
129
|
-
fillOpacity={
|
|
198
|
+
fillOpacity={濃さ(d.value)}
|
|
130
199
|
/>
|
|
131
200
|
);
|
|
132
201
|
return (
|
|
@@ -139,19 +208,23 @@ export function ChartBarNode({
|
|
|
139
208
|
) : (
|
|
140
209
|
棒
|
|
141
210
|
)}
|
|
211
|
+
{/* 値のほうが読ませたい数。 横軸の名前より大きく太くする (cdl#747) */}
|
|
142
212
|
<text
|
|
213
|
+
data-cdl-role="chart-bar-value"
|
|
143
214
|
x={bx + barW / 2}
|
|
144
|
-
y={見えている頭 -
|
|
145
|
-
fontSize={
|
|
215
|
+
y={見えている頭 - 8}
|
|
216
|
+
fontSize={12}
|
|
217
|
+
fontWeight={600}
|
|
146
218
|
textAnchor="middle"
|
|
147
219
|
fill="var(--cdl-text, #1a1f2a)"
|
|
148
220
|
>
|
|
149
221
|
{d.value.toLocaleString()}
|
|
150
222
|
</text>
|
|
151
223
|
<text
|
|
224
|
+
data-cdl-role="chart-bar-label"
|
|
152
225
|
x={bx + barW / 2}
|
|
153
226
|
y={PAD_TOP + canvasH + 18}
|
|
154
|
-
fontSize={
|
|
227
|
+
fontSize={11}
|
|
155
228
|
textAnchor="middle"
|
|
156
229
|
fill="var(--cdl-text-dim, #5a6270)"
|
|
157
230
|
>
|
package/src/validate.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CdlDiagram } from "./types";
|
|
1
|
+
import type { CdlDiagram, CdlNode } from "./types";
|
|
2
2
|
import { TONES as TONE_NAMES, NODE_KINDS } from "./types";
|
|
3
3
|
import { templateRefIds } from "./render/utils";
|
|
4
4
|
import { nodeTemplateTexts } from "./render/template-fields";
|
|
@@ -46,6 +46,31 @@ export const DRAW_KINDS: ReadonlySet<string> = new Set([
|
|
|
46
46
|
const KINDS = new Set<string>(NODE_KINDS);
|
|
47
47
|
const ENG_BANNED = /\b(FUNCTION|STORAGE|EVENT LOG|BALANCES MAPPING|READ|WRITE|EMIT|CALL)\b/;
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* 図表の荷 (payload) を持つ節の欄 (cdl#743)。
|
|
51
|
+
*
|
|
52
|
+
* これらの欄を持つ節は **その節自体が図** で、段が指し示す相手ではない。 記法にも図表の節を
|
|
53
|
+
* activate する書き方が無いため、「どの phase でも activate されていない」 は書き手に直せない。
|
|
54
|
+
*
|
|
55
|
+
* **種別の一覧を持たない** = 図表の種別は増える (実測で 15 種)。 一覧を手で書くと、種別を
|
|
56
|
+
* 足した時にここへの追記を忘れて警告が復活する。 荷の欄で判定すれば自動で追従する
|
|
57
|
+
* (`rules/quality.md § 導出可能記述は人手で書かない` の経路 1)。
|
|
58
|
+
*/
|
|
59
|
+
const 図表の荷の欄 = [
|
|
60
|
+
"chartData",
|
|
61
|
+
"ganttData",
|
|
62
|
+
"mindData",
|
|
63
|
+
"funnelData",
|
|
64
|
+
"quadrantData",
|
|
65
|
+
"treeData",
|
|
66
|
+
"journeyData",
|
|
67
|
+
] as const satisfies readonly (keyof CdlNode)[];
|
|
68
|
+
|
|
69
|
+
/** 図表の荷を 1 つでも持つか。 持つ節は「使われていない」 の判定から外す */
|
|
70
|
+
function 図表の節(n: CdlNode): boolean {
|
|
71
|
+
return 図表の荷の欄.some((欄) => n[欄] !== undefined);
|
|
72
|
+
}
|
|
73
|
+
|
|
49
74
|
/**
|
|
50
75
|
* cdl が compile される前に通る validator。
|
|
51
76
|
* 構造的破綻 (重複 id / 不存在 id 参照 / 必須要素 0 件 等) は throw、
|
|
@@ -263,7 +288,7 @@ export function validate(diag: CdlDiagram): void {
|
|
|
263
288
|
for (const id of p.activate) allActivated.add(id);
|
|
264
289
|
}
|
|
265
290
|
for (const n of diag.nodes) {
|
|
266
|
-
if (!allActivated.has(n.id)) {
|
|
291
|
+
if (!allActivated.has(n.id) && !図表の節(n)) {
|
|
267
292
|
warnings.push(`node "${n.id}" がどの phase でも activate されていない (unused、 typo の可能性)`);
|
|
268
293
|
}
|
|
269
294
|
}
|