@cardenelabs/cdl 0.32.0 → 0.34.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.
@@ -94,7 +94,7 @@ declare function sequenceBoardMetrics(data: SequenceBoardData): SequenceBoardMet
94
94
  declare const TONES: readonly ["accent", "teal", "success", "error", "warning", "info"];
95
95
  type Tone = (typeof TONES)[number];
96
96
  /** 描画できる箱の種類。 検査・記法一覧・型はこの 1 つの列を出所にする。 */
97
- declare const NODE_KINDS: readonly ["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon", "actor", "function", "storage", "event", "card", "person", "user-group", "admin", "developer", "external-user", "database", "cache", "queue", "message-bus", "cloud", "cdn", "service", "api", "frontend", "backend", "webhook", "microservice", "signer", "oracle", "merkle-tree", "decision", "chart-pie", "chart-line", "chart-bar", "chart-gauge", "chart-radial", "chart-stat", "chart-waffle", "chart-stacked-bar", "gantt-timeline", "mind-map", "funnel-stages", "quadrant-matrix", "tree-hierarchy", "journey-map", "shape-file", "shape-folder", "shape-cloud", "shape-cylinder", "shape-hexagon", "shape-diamond", "shape-stack", "shape-person", "shape-window", "shape-terminal", "shape-code-block", "shape-kanban-card", "shape-message-bubble", "shape-gear", "shape-server-rack", "shape-network-node", "shape-mobile-device", "shape-iot-sensor", "shape-robot-arm", "shape-satellite", "shape-smart-contract", "shape-blockchain-block", "shape-rpc-node", "shape-wallet", "shape-nft", "shape-token", "shape-blockchain", "shape-bitcoin-chain", "shape-ethereum-chain", "shape-blockchain-node", "shape-bank", "shape-trust-bank", "shape-payment-provider", "shape-credit-card", "shape-brokerage", "shape-exchange", "shape-atm", "shape-website", "shape-storefront", "shape-warehouse", "shape-online-shop", "shape-cdn-edge", "shape-api-gateway", "shape-auditor", "shape-regulator", "shape-notary", "shape-lawyer", "shape-trader", "shape-customer-service", "mark-start", "mark-end", "sequence-board"];
97
+ declare const NODE_KINDS: readonly ["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon", "actor", "function", "storage", "event", "card", "person", "user-group", "admin", "developer", "external-user", "database", "cache", "queue", "message-bus", "cloud", "cdn", "service", "api", "frontend", "backend", "webhook", "microservice", "signer", "oracle", "merkle-tree", "decision", "chart-pie", "chart-line", "chart-bar", "chart-gauge", "chart-radial", "chart-stat", "chart-waffle", "chart-stacked-bar", "chart-slope", "gantt-timeline", "mind-map", "funnel-stages", "quadrant-matrix", "tree-hierarchy", "journey-map", "shape-file", "shape-folder", "shape-cloud", "shape-cylinder", "shape-hexagon", "shape-diamond", "shape-stack", "shape-person", "shape-window", "shape-terminal", "shape-code-block", "shape-kanban-card", "shape-message-bubble", "shape-gear", "shape-server-rack", "shape-network-node", "shape-mobile-device", "shape-iot-sensor", "shape-robot-arm", "shape-satellite", "shape-smart-contract", "shape-blockchain-block", "shape-rpc-node", "shape-wallet", "shape-nft", "shape-token", "shape-blockchain", "shape-bitcoin-chain", "shape-ethereum-chain", "shape-blockchain-node", "shape-bank", "shape-trust-bank", "shape-payment-provider", "shape-credit-card", "shape-brokerage", "shape-exchange", "shape-atm", "shape-website", "shape-storefront", "shape-warehouse", "shape-online-shop", "shape-cdn-edge", "shape-api-gateway", "shape-auditor", "shape-regulator", "shape-notary", "shape-lawyer", "shape-trader", "shape-customer-service", "mark-start", "mark-end", "sequence-board"];
98
98
  type NodeKind = (typeof NODE_KINDS)[number];
99
99
  type Side = "top" | "right" | "bottom" | "left";
100
100
  /**
@@ -94,7 +94,7 @@ declare function sequenceBoardMetrics(data: SequenceBoardData): SequenceBoardMet
94
94
  declare const TONES: readonly ["accent", "teal", "success", "error", "warning", "info"];
95
95
  type Tone = (typeof TONES)[number];
96
96
  /** 描画できる箱の種類。 検査・記法一覧・型はこの 1 つの列を出所にする。 */
97
- declare const NODE_KINDS: readonly ["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon", "actor", "function", "storage", "event", "card", "person", "user-group", "admin", "developer", "external-user", "database", "cache", "queue", "message-bus", "cloud", "cdn", "service", "api", "frontend", "backend", "webhook", "microservice", "signer", "oracle", "merkle-tree", "decision", "chart-pie", "chart-line", "chart-bar", "chart-gauge", "chart-radial", "chart-stat", "chart-waffle", "chart-stacked-bar", "gantt-timeline", "mind-map", "funnel-stages", "quadrant-matrix", "tree-hierarchy", "journey-map", "shape-file", "shape-folder", "shape-cloud", "shape-cylinder", "shape-hexagon", "shape-diamond", "shape-stack", "shape-person", "shape-window", "shape-terminal", "shape-code-block", "shape-kanban-card", "shape-message-bubble", "shape-gear", "shape-server-rack", "shape-network-node", "shape-mobile-device", "shape-iot-sensor", "shape-robot-arm", "shape-satellite", "shape-smart-contract", "shape-blockchain-block", "shape-rpc-node", "shape-wallet", "shape-nft", "shape-token", "shape-blockchain", "shape-bitcoin-chain", "shape-ethereum-chain", "shape-blockchain-node", "shape-bank", "shape-trust-bank", "shape-payment-provider", "shape-credit-card", "shape-brokerage", "shape-exchange", "shape-atm", "shape-website", "shape-storefront", "shape-warehouse", "shape-online-shop", "shape-cdn-edge", "shape-api-gateway", "shape-auditor", "shape-regulator", "shape-notary", "shape-lawyer", "shape-trader", "shape-customer-service", "mark-start", "mark-end", "sequence-board"];
97
+ declare const NODE_KINDS: readonly ["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon", "actor", "function", "storage", "event", "card", "person", "user-group", "admin", "developer", "external-user", "database", "cache", "queue", "message-bus", "cloud", "cdn", "service", "api", "frontend", "backend", "webhook", "microservice", "signer", "oracle", "merkle-tree", "decision", "chart-pie", "chart-line", "chart-bar", "chart-gauge", "chart-radial", "chart-stat", "chart-waffle", "chart-stacked-bar", "chart-slope", "gantt-timeline", "mind-map", "funnel-stages", "quadrant-matrix", "tree-hierarchy", "journey-map", "shape-file", "shape-folder", "shape-cloud", "shape-cylinder", "shape-hexagon", "shape-diamond", "shape-stack", "shape-person", "shape-window", "shape-terminal", "shape-code-block", "shape-kanban-card", "shape-message-bubble", "shape-gear", "shape-server-rack", "shape-network-node", "shape-mobile-device", "shape-iot-sensor", "shape-robot-arm", "shape-satellite", "shape-smart-contract", "shape-blockchain-block", "shape-rpc-node", "shape-wallet", "shape-nft", "shape-token", "shape-blockchain", "shape-bitcoin-chain", "shape-ethereum-chain", "shape-blockchain-node", "shape-bank", "shape-trust-bank", "shape-payment-provider", "shape-credit-card", "shape-brokerage", "shape-exchange", "shape-atm", "shape-website", "shape-storefront", "shape-warehouse", "shape-online-shop", "shape-cdn-edge", "shape-api-gateway", "shape-auditor", "shape-regulator", "shape-notary", "shape-lawyer", "shape-trader", "shape-customer-service", "mark-start", "mark-end", "sequence-board"];
98
98
  type NodeKind = (typeof NODE_KINDS)[number];
99
99
  type Side = "top" | "right" | "bottom" | "left";
100
100
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardenelabs/cdl",
3
- "version": "0.32.0",
3
+ "version": "0.34.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",
@@ -0,0 +1,20 @@
1
+ /**
2
+ * 図表に置く数の表記 (cdl#688)。
3
+ *
4
+ * **同じ表記を種別ごとに書き写さない**。 いま `実値表記` は 7 つの種別に同じ本体で置かれて
5
+ * おり、片方だけ直せば図によって桁区切りの有無が変わる。 新しい種別はここから取る。
6
+ *
7
+ * 既存の 7 つをここへ寄せるのは #689 で行う = 種別を新設する変更と、既にある種別の
8
+ * 内部を組み替える変更を 1 つの取り込みに混ぜない。
9
+ */
10
+
11
+ /**
12
+ * 実値の表記。 桁区切りを入れる。
13
+ *
14
+ * 小数は 1 桁までに丸める。 図に置く数なので、桁を尽くすより並びが揃う方が読みやすい。
15
+ */
16
+ export function 実値表記(v: number): string {
17
+ return Number.isInteger(v)
18
+ ? v.toLocaleString("en-US")
19
+ : v.toLocaleString("en-US", { maximumFractionDigits: 1 });
20
+ }
@@ -0,0 +1,327 @@
1
+ import { type JSX } from "react";
2
+ import type { LaidNode, Tone } from "../types";
3
+ import { TONE, chartSeriesColor } from "../render/tone";
4
+ import { resolveChartData } from "../render/payload-binding";
5
+ import { textWidth } from "./text-width";
6
+ import { 実値表記 } from "./chart-number";
7
+ import { 進みを畳む, 伸ばすdash } from "./draw-progress";
8
+
9
+ /**
10
+ * chart-slope kind ... 2 時点を 1 本の直線でつなぎ、増減と順位の入れ替わりを見せる図 (cdl#688)。
11
+ *
12
+ * ## 何のための種別か
13
+ *
14
+ * 2 時点を並べる図は既に 2 つあるが、どちらもこの読み方を持たない。
15
+ *
16
+ * | 種別 | 見せるもの | 足りないもの |
17
+ * |---|---|---|
18
+ * | `chart-stacked-bar` (cdl#551) | 2 時点の構成比 | 全体を 100% に正規化するので、全体が伸びた / 縮んだが消える |
19
+ * | `chart-line` (cdl#513) | 3 点以上の推移 | 横軸が datum の並びなので 1 node = 1 本になり、系列ごとに 1 本にならない |
20
+ *
21
+ * 左右に縦軸を置いて系列ごとに 2 点を直線で結ぶと、3 つが同時に読める。
22
+ * 線の向きが増減、線の傾きが動いた量、線の交差が順位の入れ替わり。
23
+ *
24
+ * ## 前の時点は datum の `previous` で持つ
25
+ *
26
+ * `chart-stacked-bar` と同じ欄をそのまま読む。 新しい欄は足さない。
27
+ *
28
+ * **1 件も持たない図は線を引かない**。 2 点が無いと傾きが定義できないので、軸 1 本に点を
29
+ * 並べた形で出す。 持たない件だけを 0 として立ち上げるのは、他の件が前を持つ図に限る =
30
+ * その図では「前は無かった」 が読みとして成立する (`chart-stacked-bar` の扱いと揃える)。
31
+ *
32
+ * ## 尺は 0 に固定しない
33
+ *
34
+ * 近い値どうしの差を傾きで読ませる図なので、0 まで伸ばすと線が全部水平に潰れる。
35
+ * 両時点の全値の最小と最大から取る。 全部同じ値の図では縦の中央に置く (0 で割らない)。
36
+ *
37
+ * ## 名札は値を軸側に置く
38
+ *
39
+ * 左は 名前 → 値、右は 値 → 名前 の順で軸から外へ並べる。 値を軸に近い側へ置くと、
40
+ * 左右とも値の列が縦に揃って読み比べられる。
41
+ *
42
+ * 値と名前は **別の役割** で出す (`chart-slope-value` / `chart-slope-name`、cdl#693)。
43
+ * 1 つにまとめると使う側が値だけに字体を当てられず、寸法を記録する側も 2 つの大きさを
44
+ * 1 行に潰す。 他の図表 (`chart-stat` / `chart-stacked-bar`) も字を役割で分けている。
45
+ */
46
+
47
+ /** 線を引くのに最低限要る横幅。 名札が長い図でもこれだけは残す */
48
+ export const 線の最小幅 = 120;
49
+
50
+ type 系列 = {
51
+ 名前: string;
52
+ 前: number;
53
+ 今: number;
54
+ 色: string;
55
+ 未解決: boolean;
56
+ };
57
+
58
+ export function ChartSlopeNode({
59
+ node,
60
+ active,
61
+ stateValues = {},
62
+ drawing = false,
63
+ progress = 1,
64
+ }: {
65
+ node: LaidNode;
66
+ active: boolean;
67
+ stateValues?: Record<string, string>;
68
+ /** 段の `draw` にこの node が載っている間だけ true */
69
+ drawing?: boolean;
70
+ progress?: number;
71
+ }): JSX.Element {
72
+ const x0 = node.cx - node.w / 2;
73
+ const y0 = node.cy - node.h / 2;
74
+ const data = resolveChartData(node.chartData ?? [], stateValues);
75
+
76
+ const PAD = 24;
77
+ const 時点の級 = 12;
78
+ const 値の級 = 14;
79
+ const 名前の上限級 = 13;
80
+ const 名前の下限級 = 9;
81
+ const 軸から値まで = 10;
82
+ const 値から名前まで = 8;
83
+
84
+ const 前あり = data.some((d) => d.previous !== undefined);
85
+ const 系列たち: 系列[] = data.map((d, i) => ({
86
+ 名前: d.label ?? "",
87
+ // 前を持たない件は 0 から立ち上げる。 件を落とすと左右で系列の数が変わり、色の並びが
88
+ // 対応しなくなる (`chart-stacked-bar` と同じ判断)
89
+ 前: d.previous ?? 0,
90
+ 今: d.value,
91
+ 色: 印の色(d.tone, i),
92
+ 未解決: d.unresolved === true,
93
+ }));
94
+
95
+ const 全部の値 = 前あり
96
+ ? 系列たち.flatMap((s) => [s.前, s.今])
97
+ : 系列たち.map((s) => s.今);
98
+ const 最小 = 全部の値.length > 0 ? Math.min(...全部の値) : 0;
99
+ const 最大 = 全部の値.length > 0 ? Math.max(...全部の値) : 0;
100
+ const 尺の幅 = 最大 - 最小;
101
+
102
+ const 上端 = PAD + (前あり ? 時点の級 + 12 : 0) + 6;
103
+ const 下端 = Math.max(上端 + 10, node.h - PAD - 6);
104
+ const yを取る = (v: number): number =>
105
+ 尺の幅 > 0 ? 下端 - ((v - 最小) / 尺の幅) * (下端 - 上端) : (上端 + 下端) / 2;
106
+
107
+ const 左の字 = 系列たち.map((s) => 実値表記(s.前));
108
+ const 右の字 = 系列たち.map((s) => 実値表記(s.今));
109
+ const 左値幅 = Math.max(0, ...左の字.map((t) => textWidth(t, 値の級)));
110
+ const 右値幅 = Math.max(0, ...右の字.map((t) => textWidth(t, 値の級)));
111
+
112
+ /** 名札に回せる横幅。 線の幅を先に取り分けてから、残りを左右へ半分ずつ配る */
113
+ const 名前に使える = Math.max(
114
+ 0,
115
+ (node.w -
116
+ PAD * 2 -
117
+ 線の最小幅 -
118
+ (左値幅 + 軸から値まで + 値から名前まで) -
119
+ (右値幅 + 軸から値まで + 値から名前まで)) /
120
+ 2,
121
+ );
122
+ const 名前の級 = 収める級(系列たち.map((s) => s.名前), 名前に使える, 名前の上限級, 名前の下限級);
123
+ const 名前幅 = Math.min(
124
+ 名前に使える,
125
+ Math.max(0, ...系列たち.map((s) => textWidth(s.名前, 名前の級))),
126
+ );
127
+
128
+ const 左の余白 = 名前幅 + 値から名前まで + 左値幅 + 軸から値まで;
129
+ const 右の余白 = 軸から値まで + 右値幅 + 値から名前まで + 名前幅;
130
+
131
+ /**
132
+ * 軸の位置。 名札が長い図では余白の言い値どおりに置くと軸が交差するため、
133
+ * **線の幅を先に確保してから** 内側へ寄せる。 箱の内側から出さないことを優先する。
134
+ */
135
+ const 左軸x = 前あり
136
+ ? Math.max(PAD, Math.min(PAD + 左の余白, node.w / 2 - 線の最小幅 / 2))
137
+ : Math.max(PAD, Math.min(node.w / 2, node.w - PAD));
138
+ const 右軸x = 前あり
139
+ ? Math.min(node.w - PAD, Math.max(node.w - PAD - 右の余白, node.w / 2 + 線の最小幅 / 2))
140
+ : 左軸x;
141
+
142
+ const 伸び = 進みを畳む(drawing, progress);
143
+ /** 右の点と右の名札は、線が届いてから出る */
144
+ const 右を出す = !drawing || 伸び >= 1;
145
+
146
+ return (
147
+ <g transform={`translate(${x0} ${y0})`}>
148
+ <rect
149
+ data-cdl-role="node-body"
150
+ x={0}
151
+ y={0}
152
+ width={node.w}
153
+ height={node.h}
154
+ rx={16}
155
+ fill="var(--cdl-node-fill, #ffffff)"
156
+ stroke={active ? "var(--cdl-tone-accent, #2d6a8f)" : "var(--cdl-divider, #d4d4d8)"}
157
+ strokeWidth={active ? 3 : 1.25}
158
+ />
159
+
160
+ {(前あり ? [左軸x, 右軸x] : [左軸x]).map((x, i) => (
161
+ <line
162
+ key={`axis-${i}`}
163
+ data-cdl-role="chart-slope-axis"
164
+ x1={x}
165
+ y1={上端 - 6}
166
+ x2={x}
167
+ y2={下端 + 6}
168
+ stroke="var(--cdl-divider, #d4d4d8)"
169
+ strokeWidth={1}
170
+ />
171
+ ))}
172
+
173
+ {/* 時点の名前。 軸が 1 本の図では描かない (比べる相手がいない) */}
174
+ {前あり && (
175
+ <>
176
+ <text
177
+ data-cdl-role="chart-slope-period"
178
+ x={左軸x}
179
+ y={PAD + 時点の級}
180
+ textAnchor="middle"
181
+ fontSize={時点の級}
182
+ fontWeight={700}
183
+ fill="var(--cdl-text-dim, #5a6270)"
184
+ >
185
+ 前
186
+ </text>
187
+ <text
188
+ data-cdl-role="chart-slope-period"
189
+ x={右軸x}
190
+ y={PAD + 時点の級}
191
+ textAnchor="middle"
192
+ fontSize={時点の級}
193
+ fontWeight={700}
194
+ fill="var(--cdl-text-dim, #5a6270)"
195
+ >
196
+ 今
197
+ </text>
198
+ </>
199
+ )}
200
+
201
+ {系列たち.map((s, i) => {
202
+ const 左y = yを取る(s.前);
203
+ const 右y = yを取る(s.今);
204
+ const 未解決 = s.未解決 ? "true" : undefined;
205
+ return (
206
+ <g key={`slope-${i}`}>
207
+ {前あり && (
208
+ <path
209
+ data-cdl-role="chart-slope-line"
210
+ data-cdl-unresolved={未解決}
211
+ d={`M ${左軸x} ${左y} L ${右軸x} ${右y}`}
212
+ fill="none"
213
+ stroke={s.色}
214
+ strokeWidth={2.5}
215
+ strokeLinecap="round"
216
+ {...伸ばすdash(drawing, 伸び)}
217
+ />
218
+ )}
219
+ {前あり && (
220
+ <circle
221
+ data-cdl-role="chart-slope-dot"
222
+ data-cdl-unresolved={未解決}
223
+ cx={左軸x}
224
+ cy={左y}
225
+ r={4}
226
+ fill={s.色}
227
+ />
228
+ )}
229
+ {右を出す && (
230
+ <circle
231
+ data-cdl-role="chart-slope-dot"
232
+ data-cdl-unresolved={未解決}
233
+ cx={右軸x}
234
+ cy={右y}
235
+ r={4}
236
+ fill={s.色}
237
+ />
238
+ )}
239
+
240
+ {/* 左の名札。 軸が 1 本の図では名前だけを左に置く */}
241
+ {前あり ? (
242
+ <>
243
+ <text
244
+ data-cdl-role="chart-slope-value"
245
+ x={左軸x - 軸から値まで}
246
+ y={左y + 5}
247
+ textAnchor="end"
248
+ fontSize={値の級}
249
+ fontWeight={600}
250
+ fill="var(--cdl-text, #1a1f2a)"
251
+ >
252
+ {左の字[i]}
253
+ </text>
254
+ <text
255
+ data-cdl-role="chart-slope-name"
256
+ x={左軸x - 軸から値まで - 左値幅 - 値から名前まで}
257
+ y={左y + 5}
258
+ textAnchor="end"
259
+ fontSize={名前の級}
260
+ fill="var(--cdl-text-dim, #5a6270)"
261
+ >
262
+ {s.名前}
263
+ </text>
264
+ </>
265
+ ) : (
266
+ <text
267
+ data-cdl-role="chart-slope-name"
268
+ x={左軸x - 軸から値まで}
269
+ y={右y + 5}
270
+ textAnchor="end"
271
+ fontSize={名前の級}
272
+ fill="var(--cdl-text-dim, #5a6270)"
273
+ >
274
+ {s.名前}
275
+ </text>
276
+ )}
277
+
278
+ {右を出す && (
279
+ <>
280
+ <text
281
+ data-cdl-role="chart-slope-value"
282
+ x={右軸x + 軸から値まで}
283
+ y={右y + 5}
284
+ fontSize={値の級}
285
+ fontWeight={600}
286
+ fill="var(--cdl-text, #1a1f2a)"
287
+ >
288
+ {右の字[i]}
289
+ </text>
290
+ {前あり && (
291
+ <text
292
+ data-cdl-role="chart-slope-name"
293
+ x={右軸x + 軸から値まで + 右値幅 + 値から名前まで}
294
+ y={右y + 5}
295
+ fontSize={名前の級}
296
+ fill="var(--cdl-text-dim, #5a6270)"
297
+ >
298
+ {s.名前}
299
+ </text>
300
+ )}
301
+ </>
302
+ )}
303
+ </g>
304
+ );
305
+ })}
306
+ </g>
307
+ );
308
+ }
309
+
310
+ /** 印の色。 明示した色が優先され、無ければ図表の系列の色を順に配る */
311
+ function 印の色(tone: Tone | undefined, idx: number): string {
312
+ if (tone) return TONE[tone];
313
+ return chartSeriesColor(idx);
314
+ }
315
+
316
+ /**
317
+ * 名前の字の大きさ。 **全部を同じ大きさに揃える** = 名前ごとに変えると列の見え方が乱れる。
318
+ *
319
+ * 最長の名前が枠に収まるまで 1 ずつ下げ、下限で止める。 下限でも収まらない図では
320
+ * 名札が枠から出るが、線と軸は箱の内側に残る。
321
+ */
322
+ function 収める級(名前たち: readonly string[], 幅: number, 上限: number, 下限: number): number {
323
+ if (名前たち.length === 0) return 上限;
324
+ let 級 = 上限;
325
+ while (級 > 下限 && Math.max(...名前たち.map((n) => textWidth(n, 級))) > 幅) 級 -= 1;
326
+ return 級;
327
+ }
@@ -90,6 +90,7 @@ const KINDS_WITHOUT_TITLE_TEXT: ReadonlySet<string> = new Set([
90
90
  "chart-stat",
91
91
  "chart-waffle",
92
92
  "chart-stacked-bar",
93
+ "chart-slope",
93
94
  "gantt-timeline",
94
95
  "mind-map",
95
96
  "funnel-stages",
@@ -62,6 +62,8 @@ export const TOKENS = {
62
62
  "chart-waffle": { w: 640, h: 320 },
63
63
  // 帯を最大 2 本 + 下に一覧。 縦は帯 2 本ぶんで足りる (cdl#551)
64
64
  "chart-stacked-bar": { w: 640, h: 320 },
65
+ // 左右 2 本の縦軸と、その外に名札。 縦は系列が縦に並ぶぶん要る (cdl#688)
66
+ "chart-slope": { w: 640, h: 360 },
65
67
  // Timeline / Mind 系 (CAR-994 Phase B)
66
68
  "gantt-timeline": { w: 720, h: 360 },
67
69
  "mind-map": { w: 720, h: 480 },
package/src/presets.ts CHANGED
@@ -2033,7 +2033,8 @@ export type ChartType =
2033
2033
  | "radial"
2034
2034
  | "stat"
2035
2035
  | "waffle"
2036
- | "stacked-bar";
2036
+ | "stacked-bar"
2037
+ | "slope";
2037
2038
 
2038
2039
  export type ChartDatum = {
2039
2040
  id: string;
@@ -2100,6 +2101,7 @@ export function chart(preset: ChartPreset): ChartBuilder {
2100
2101
  stat: "chart-stat" as const,
2101
2102
  waffle: "chart-waffle" as const,
2102
2103
  "stacked-bar": "chart-stacked-bar" as const,
2104
+ slope: "chart-slope" as const,
2103
2105
  };
2104
2106
 
2105
2107
  const api: ChartBuilder = {
@@ -15,6 +15,7 @@ import { ChartRadialNode } from "../kinds/chart-radial";
15
15
  import { ChartStatNode } from "../kinds/chart-stat";
16
16
  import { ChartWaffleNode } from "../kinds/chart-waffle";
17
17
  import { ChartStackedBarNode } from "../kinds/chart-stacked-bar";
18
+ import { ChartSlopeNode } from "../kinds/chart-slope";
18
19
  import { ChartBarNode } from "../kinds/chart-bar";
19
20
  import { GanttNode } from "../kinds/gantt";
20
21
  import { MindMapNode } from "../kinds/mind-map";
@@ -198,6 +199,17 @@ export function CdlNodeView({
198
199
  case "chart-stacked-bar":
199
200
  // 帯を割合で区切る。 前の時点を書けば 2 本になる (cdl#551)
200
201
  return <ChartStackedBarNode node={resolvedNode} active={active} stateValues={stateValues} />;
202
+ case "chart-slope":
203
+ // 2 時点を直線でつなぐ。 前の時点を書かない図は軸 1 本の点の並びになる (cdl#688)
204
+ return (
205
+ <ChartSlopeNode
206
+ node={resolvedNode}
207
+ active={active}
208
+ stateValues={stateValues}
209
+ drawing={drawing}
210
+ progress={progress}
211
+ />
212
+ );
201
213
  case "chart-waffle":
202
214
  // 1 個 = 1% の印を 100 個。 段の進みで印が埋まる動きは別 Issue (cdl#552 で対象外)
203
215
  return <ChartWaffleNode node={resolvedNode} active={active} stateValues={stateValues} />;
package/src/types.ts CHANGED
@@ -80,7 +80,8 @@ export const NODE_KINDS = [
80
80
  // chart-waffle は 100 個の印で割合を数えて確かめる形 (cdl#552)
81
81
  "chart-pie", "chart-line", "chart-bar", "chart-gauge", "chart-radial", "chart-stat",
82
82
  // chart-stacked-bar は帯を割合で区切り時点を並べる形 (cdl#551)
83
- "chart-waffle", "chart-stacked-bar",
83
+ // chart-slope は 2 時点を直線でつなぎ増減と順位の入れ替わりを見せる形 (cdl#688)
84
+ "chart-waffle", "chart-stacked-bar", "chart-slope",
84
85
  // Timeline / Mind 系 (2、 CAR-994 Phase B) ... 1 node に payload 配列、 SVG geometry render
85
86
  "gantt-timeline", "mind-map",
86
87
  // Analytic 系 (4、 CAR-994 Phase C) ... 1 node に payload 配列、 SVG geometry render
package/src/validate.ts CHANGED
@@ -17,6 +17,7 @@ const TONES = new Set<string>(TONE_NAMES);
17
17
  * | `chart-line` | 左端 | 線が右へ伸びる |
18
18
  * | `chart-bar` | 横軸 | 棒が上へ伸びる |
19
19
  * | `chart-pie` | 12 時 | 扇が時計回りに開く |
20
+ * | `chart-slope` | 左の軸 | 線が右の軸へ伸びる |
20
21
  * | `journey-map` | 左端 | 気持ちの線が右へ伸びる |
21
22
  * | `mind-map` | 中心 | 枝が中心から外へ伸びる |
22
23
  * | `tree-hierarchy` | 根 | 枝が上の段から下の段へ伸びる |
@@ -27,6 +28,7 @@ export const DRAW_KINDS: ReadonlySet<string> = new Set([
27
28
  "chart-line",
28
29
  "chart-bar",
29
30
  "chart-pie",
31
+ "chart-slope",
30
32
  "journey-map",
31
33
  "mind-map",
32
34
  "tree-hierarchy",
@@ -246,6 +246,7 @@ export const GRID_ALIGNED_KINDS: ReadonlySet<NodeKind> = new Set([
246
246
  "chart-stat",
247
247
  "chart-waffle",
248
248
  "chart-stacked-bar",
249
+ "chart-slope",
249
250
  "funnel-stages",
250
251
  "quadrant-matrix",
251
252
  ]);