@cardenelabs/cdl 0.5.0 → 0.6.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/README.md +3 -3
- package/dist/index.cjs +1350 -1258
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -48
- package/dist/index.d.ts +43 -48
- package/dist/index.js +1349 -1258
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +2732 -2592
- 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 +2732 -2592
- package/dist/react.js.map +1 -1
- package/dist/{render-C78lIXeC.d.cts → render-DquCvgOB.d.cts} +65 -14
- package/dist/{render-C78lIXeC.d.ts → render-DquCvgOB.d.ts} +65 -14
- package/package.json +1 -1
- package/src/formula/parser.ts +35 -0
- package/src/index.ts +8 -4
- package/src/kinds/chart-bar.tsx +13 -2
- package/src/kinds/chart-line.tsx +13 -3
- package/src/kinds/chart-pie.tsx +13 -2
- package/src/kinds/dyn-shape.tsx +3 -2
- package/src/kinds/funnel.tsx +16 -3
- package/src/kinds/gantt.tsx +16 -3
- package/src/kinds/generic.tsx +9 -5
- package/src/kinds/quadrant.tsx +16 -3
- package/src/kinds/user-journey.tsx +24 -9
- package/src/layout/spec.ts +1 -2
- package/src/layout/tokens.ts +1 -2
- package/src/layout.ts +1 -0
- package/src/presets.ts +7 -100
- package/src/render/derived-values.ts +261 -0
- package/src/render/nodes.tsx +14 -12
- package/src/render/payload-binding.ts +196 -0
- package/src/render/utils.ts +47 -3
- package/src/template-name.ts +43 -0
- package/src/types.ts +72 -18
- package/src/validate.ts +42 -9
- package/src/visual-validate.ts +10 -10
- package/src/kinds/mind-radial.tsx +0 -209
|
@@ -33,7 +33,7 @@ declare function computed<T>(fn: () => T): Computed<T>;
|
|
|
33
33
|
declare const TONES: readonly ["accent", "teal", "success", "error", "warning", "info"];
|
|
34
34
|
type Tone = (typeof TONES)[number];
|
|
35
35
|
/** 描画できる箱の種類。 検査・記法一覧・型はこの 1 つの列を出所にする。 */
|
|
36
|
-
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", "gantt-timeline", "mind-map", "
|
|
36
|
+
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", "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"];
|
|
37
37
|
type NodeKind = (typeof NODE_KINDS)[number];
|
|
38
38
|
type Side = "top" | "right" | "bottom" | "left";
|
|
39
39
|
/**
|
|
@@ -209,8 +209,8 @@ type CdlNode = {
|
|
|
209
209
|
*/
|
|
210
210
|
ganttData?: GanttTaskPayload[];
|
|
211
211
|
/**
|
|
212
|
-
* mind-map
|
|
213
|
-
* kinds/mind-map.tsx
|
|
212
|
+
* mind-map 専用の branch payload。 tree 構造を 1 node に格納し、
|
|
213
|
+
* kinds/mind-map.tsx が中心 + 放射で SVG 描画する。
|
|
214
214
|
*/
|
|
215
215
|
mindData?: MindBranchPayload;
|
|
216
216
|
/** funnel-stages 専用の stage 配列 payload、 逆三角形 stage 描画に使う */
|
|
@@ -222,20 +222,41 @@ type CdlNode = {
|
|
|
222
222
|
/** journey-map 専用の step 配列 payload、 感情曲線 + touchpoint 描画に使う */
|
|
223
223
|
journeyData?: JourneyStepPayload[];
|
|
224
224
|
};
|
|
225
|
+
/**
|
|
226
|
+
* 図表 payload の数値欄。 数を直接書くか、 `{signal}` を書いて状態から読む。
|
|
227
|
+
*
|
|
228
|
+
* 文字列を許すのは、 図表が状態を読む唯一の経路だから (`render/payload-binding.ts` が
|
|
229
|
+
* 描画直前に stateValues で解決する)。 **解決できない欄でも項目は落とさず**、 既定値で
|
|
230
|
+
* 描いて `data-cdl-unresolved="true"` を付ける (落とすと配列の長さが変わって図の形が
|
|
231
|
+
* 別物になる)。
|
|
232
|
+
*
|
|
233
|
+
* 既定値は欄ごとに違う。 数の欄は 0 だが、 `gantt-timeline` の終端は自分の始端
|
|
234
|
+
* (0 にすると帯が軸の左端まで伸びて別の図に見える)、 `quadrant-matrix` は左下、
|
|
235
|
+
* `journey-map` は「普通」。 いずれも `render/payload-binding.ts` が持つ。
|
|
236
|
+
*/
|
|
237
|
+
type BoundNumber = number | string;
|
|
238
|
+
/**
|
|
239
|
+
* 図表 payload の語の欄。 決まった語を直接書くか、 `{signal}` を書いて状態から読む。
|
|
240
|
+
*
|
|
241
|
+
* `(string & {})` は「決まった語の候補を出しつつ任意の文字列も許す」 ための書き方。
|
|
242
|
+
* 素の `string` と union すると候補が消えるため、 補完を残す目的で挟んでいる。
|
|
243
|
+
*/
|
|
244
|
+
type BoundEnum<T extends string> = T | (string & {});
|
|
225
245
|
/** Chart 系 kind の 1 datum payload。 label + value + optional tone (color) */
|
|
226
246
|
type ChartDatumPayload = {
|
|
227
247
|
label: string;
|
|
228
|
-
|
|
248
|
+
/** 棒 / 折れ線 / 扇の大きさ。 `{signal}` で状態に追随する */
|
|
249
|
+
value: BoundNumber;
|
|
229
250
|
tone?: Tone;
|
|
230
251
|
};
|
|
231
252
|
/** gantt-timeline kind の 1 task payload。 label + start/end + owner + dependsOn */
|
|
232
253
|
type GanttTaskPayload = {
|
|
233
254
|
id: string;
|
|
234
255
|
title: string;
|
|
235
|
-
/** 開始 index (0-based 相対位置) */
|
|
236
|
-
startIdx:
|
|
237
|
-
/** 終了 index (0-based 相対位置、 startIdx と同じなら 1 cell 幅) */
|
|
238
|
-
endIdx:
|
|
256
|
+
/** 開始 index (0-based 相対位置)。 `{signal}` で状態に追随する */
|
|
257
|
+
startIdx: BoundNumber;
|
|
258
|
+
/** 終了 index (0-based 相対位置、 startIdx と同じなら 1 cell 幅)。 `{signal}` 可 */
|
|
259
|
+
endIdx: BoundNumber;
|
|
239
260
|
/** 開始 label (例 "Q1" / "2026-01") */
|
|
240
261
|
startLabel: string;
|
|
241
262
|
/** 終了 label (例 "Q2" / "2026-03") */
|
|
@@ -244,7 +265,7 @@ type GanttTaskPayload = {
|
|
|
244
265
|
dependsOn?: string;
|
|
245
266
|
tone?: Tone;
|
|
246
267
|
};
|
|
247
|
-
/** mind-map
|
|
268
|
+
/** mind-map kind の branch tree payload。 rootId + branch 配列 */
|
|
248
269
|
type MindBranchPayload = {
|
|
249
270
|
rootId: string;
|
|
250
271
|
rootTitle: string;
|
|
@@ -262,7 +283,8 @@ type MindBranchNode = {
|
|
|
262
283
|
type FunnelStagePayload = {
|
|
263
284
|
id: string;
|
|
264
285
|
title: string;
|
|
265
|
-
|
|
286
|
+
/** 段の人数 / 件数。 `{signal}` で状態に追随する */
|
|
287
|
+
count: BoundNumber;
|
|
266
288
|
subtitle?: string;
|
|
267
289
|
};
|
|
268
290
|
/** quadrant-matrix kind の 2 軸 + item 配列 payload */
|
|
@@ -283,11 +305,14 @@ type QuadrantPayload = {
|
|
|
283
305
|
};
|
|
284
306
|
items: QuadrantItemPayload[];
|
|
285
307
|
};
|
|
308
|
+
/** quadrant-matrix の象限 4 種 */
|
|
309
|
+
type QuadrantKey = "topLeft" | "topRight" | "bottomLeft" | "bottomRight";
|
|
286
310
|
/** quadrant-matrix の 1 item payload */
|
|
287
311
|
type QuadrantItemPayload = {
|
|
288
312
|
id: string;
|
|
289
313
|
title: string;
|
|
290
|
-
|
|
314
|
+
/** どの象限に居るか。 `{signal}` を書くと項目が象限を移る */
|
|
315
|
+
quadrant: BoundEnum<QuadrantKey>;
|
|
291
316
|
subtitle?: string;
|
|
292
317
|
};
|
|
293
318
|
/** tree-hierarchy kind の 1 node payload */
|
|
@@ -298,12 +323,14 @@ type TreeNodePayload = {
|
|
|
298
323
|
subtitle?: string;
|
|
299
324
|
eyebrow?: string;
|
|
300
325
|
};
|
|
326
|
+
/** journey-map の気持ち 5 段階 */
|
|
327
|
+
type JourneyEmotion = "delighted" | "happy" | "neutral" | "frustrated" | "angry";
|
|
301
328
|
/** journey-map kind の 1 step payload */
|
|
302
329
|
type JourneyStepPayload = {
|
|
303
330
|
id: string;
|
|
304
331
|
title: string;
|
|
305
|
-
/** emotion 5 段階 (delighted / happy / neutral / frustrated / angry) */
|
|
306
|
-
emotion:
|
|
332
|
+
/** emotion 5 段階 (delighted / happy / neutral / frustrated / angry)。 `{signal}` で動く */
|
|
333
|
+
emotion: BoundEnum<JourneyEmotion>;
|
|
307
334
|
touchpoint?: string;
|
|
308
335
|
opportunity?: string;
|
|
309
336
|
};
|
|
@@ -2128,6 +2155,13 @@ type CdlDiagram = {
|
|
|
2128
2155
|
* 省略時は空 array 相当で下位互換。
|
|
2129
2156
|
*/
|
|
2130
2157
|
readouts?: CdlReadout[];
|
|
2158
|
+
/**
|
|
2159
|
+
* 他の値から自動で決まる値。 段の補間中も毎 frame 決まり直す (`computeStateValues`)。
|
|
2160
|
+
*
|
|
2161
|
+
* 入力欄の式 (`formulas`) とは経路が別。 あちらは入力欄の signal を読む reactive graph に
|
|
2162
|
+
* 載り、 こちらは段が動かす状態を読む。 省略時は空 array 相当で下位互換。
|
|
2163
|
+
*/
|
|
2164
|
+
derived?: CdlDerivedValue[];
|
|
2131
2165
|
lanes: CdlLane[];
|
|
2132
2166
|
nodes: CdlNode[];
|
|
2133
2167
|
edges: CdlEdge[];
|
|
@@ -2136,6 +2170,21 @@ type CdlDiagram = {
|
|
|
2136
2170
|
/** v0.5+ ... canvas 全体仕様 (viewport.width / height で全体 size を author が宣言) */
|
|
2137
2171
|
viewport?: CdlViewport;
|
|
2138
2172
|
};
|
|
2173
|
+
/**
|
|
2174
|
+
* 他の値から自動で決まる値の宣言。
|
|
2175
|
+
*
|
|
2176
|
+
* 式は `{名前}` で他の値 (`states` / 他の `derived`) を読み、 四則 / 括弧 / 比較 /
|
|
2177
|
+
* `min` / `max` が書ける。 比較の結果は真 = 1 / 偽 = 0 の数として載る。
|
|
2178
|
+
*
|
|
2179
|
+
* 書いた順は結果に影響しない (参照から順序を解く)。 解けない値 (輪 / 存在しない名前 /
|
|
2180
|
+
* 0 除算 / 数でない値) はその値だけ止まり、 残りは動く。
|
|
2181
|
+
*/
|
|
2182
|
+
type CdlDerivedValue = {
|
|
2183
|
+
/** 値の名前。 図からは `{名前}` で読む */
|
|
2184
|
+
id: string;
|
|
2185
|
+
/** 式。 例 `"{inflow} - {done}"` */
|
|
2186
|
+
expression: string;
|
|
2187
|
+
};
|
|
2139
2188
|
/** Canvas 全体仕様 (v0.5+ syntax `viewport: { width, height, scale, gap, laneGap, nodeGap, labelMargin }`)。
|
|
2140
2189
|
* 未指定時は layout が node 配置から自動算出する従来挙動。
|
|
2141
2190
|
*
|
|
@@ -2279,6 +2328,8 @@ type LaidDiagram = {
|
|
|
2279
2328
|
edges: LaidEdge[];
|
|
2280
2329
|
states: CdlState[];
|
|
2281
2330
|
phases: CdlPhase[];
|
|
2331
|
+
/** 他の値から自動で決まる値 (`CdlDiagram.derived` をそのまま引き継ぐ) */
|
|
2332
|
+
derived?: CdlDerivedValue[];
|
|
2282
2333
|
/**
|
|
2283
2334
|
* キャンバス全要素の bounding box list。
|
|
2284
2335
|
* engine が「ここに何があるか」 を全部把握、 衝突検証 / debug 表示 / validation で使う。
|
|
@@ -2446,4 +2497,4 @@ type CdlDiagramViewProps = {
|
|
|
2446
2497
|
};
|
|
2447
2498
|
declare function CdlDiagramView({ diagram, laid: laidProp, hideHeader, hideMiniPhaseIndicator, focusPhaseId, debug, emitGeometryWarn, headingLevel, interactiveHandlers }: CdlDiagramViewProps): JSX.Element;
|
|
2448
2499
|
|
|
2449
|
-
export {
|
|
2500
|
+
export { computeScrollTriggerKey as A, type BBox as B, type CdlDiagram as C, computed as D, type EdgeStyle as E, createInteractiveSignalsAccessor as F, createScrollProgressHandle as G, createScrollProgressSignals as H, type InteractiveHandlerMap as I, type JourneyEmotion as J, hasInteractivePrimitives as K, type LaidDiagram as L, signal as M, type NodeKind as N, type QuadrantKey as Q, type Signal as S, type Tone as T, type CdlInput as a, type CdlLane as b, type CdlNode as c, type Side as d, type CdlState as e, type CdlEventTarget as f, type Computed as g, type CdlDerivedValue as h, type CdlScrollTrigger as i, type CdlDiagramViewProps as j, type LaidEdge as k, type LaidNode as l, type CdlEdge as m, CdlDiagramView as n, type CdlEventBinding as o, type CdlEventKind as p, type CdlFormula as q, type CdlPhase as r, type InteractiveSignalsAccessor as s, type LaidLane as t, NODE_KINDS as u, type ScrollProgressHandle as v, TONES as w, computeEventBindingKey as x, computeFormulaKey as y, computeInputSignalKey as z };
|
|
@@ -33,7 +33,7 @@ declare function computed<T>(fn: () => T): Computed<T>;
|
|
|
33
33
|
declare const TONES: readonly ["accent", "teal", "success", "error", "warning", "info"];
|
|
34
34
|
type Tone = (typeof TONES)[number];
|
|
35
35
|
/** 描画できる箱の種類。 検査・記法一覧・型はこの 1 つの列を出所にする。 */
|
|
36
|
-
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", "gantt-timeline", "mind-map", "
|
|
36
|
+
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", "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"];
|
|
37
37
|
type NodeKind = (typeof NODE_KINDS)[number];
|
|
38
38
|
type Side = "top" | "right" | "bottom" | "left";
|
|
39
39
|
/**
|
|
@@ -209,8 +209,8 @@ type CdlNode = {
|
|
|
209
209
|
*/
|
|
210
210
|
ganttData?: GanttTaskPayload[];
|
|
211
211
|
/**
|
|
212
|
-
* mind-map
|
|
213
|
-
* kinds/mind-map.tsx
|
|
212
|
+
* mind-map 専用の branch payload。 tree 構造を 1 node に格納し、
|
|
213
|
+
* kinds/mind-map.tsx が中心 + 放射で SVG 描画する。
|
|
214
214
|
*/
|
|
215
215
|
mindData?: MindBranchPayload;
|
|
216
216
|
/** funnel-stages 専用の stage 配列 payload、 逆三角形 stage 描画に使う */
|
|
@@ -222,20 +222,41 @@ type CdlNode = {
|
|
|
222
222
|
/** journey-map 専用の step 配列 payload、 感情曲線 + touchpoint 描画に使う */
|
|
223
223
|
journeyData?: JourneyStepPayload[];
|
|
224
224
|
};
|
|
225
|
+
/**
|
|
226
|
+
* 図表 payload の数値欄。 数を直接書くか、 `{signal}` を書いて状態から読む。
|
|
227
|
+
*
|
|
228
|
+
* 文字列を許すのは、 図表が状態を読む唯一の経路だから (`render/payload-binding.ts` が
|
|
229
|
+
* 描画直前に stateValues で解決する)。 **解決できない欄でも項目は落とさず**、 既定値で
|
|
230
|
+
* 描いて `data-cdl-unresolved="true"` を付ける (落とすと配列の長さが変わって図の形が
|
|
231
|
+
* 別物になる)。
|
|
232
|
+
*
|
|
233
|
+
* 既定値は欄ごとに違う。 数の欄は 0 だが、 `gantt-timeline` の終端は自分の始端
|
|
234
|
+
* (0 にすると帯が軸の左端まで伸びて別の図に見える)、 `quadrant-matrix` は左下、
|
|
235
|
+
* `journey-map` は「普通」。 いずれも `render/payload-binding.ts` が持つ。
|
|
236
|
+
*/
|
|
237
|
+
type BoundNumber = number | string;
|
|
238
|
+
/**
|
|
239
|
+
* 図表 payload の語の欄。 決まった語を直接書くか、 `{signal}` を書いて状態から読む。
|
|
240
|
+
*
|
|
241
|
+
* `(string & {})` は「決まった語の候補を出しつつ任意の文字列も許す」 ための書き方。
|
|
242
|
+
* 素の `string` と union すると候補が消えるため、 補完を残す目的で挟んでいる。
|
|
243
|
+
*/
|
|
244
|
+
type BoundEnum<T extends string> = T | (string & {});
|
|
225
245
|
/** Chart 系 kind の 1 datum payload。 label + value + optional tone (color) */
|
|
226
246
|
type ChartDatumPayload = {
|
|
227
247
|
label: string;
|
|
228
|
-
|
|
248
|
+
/** 棒 / 折れ線 / 扇の大きさ。 `{signal}` で状態に追随する */
|
|
249
|
+
value: BoundNumber;
|
|
229
250
|
tone?: Tone;
|
|
230
251
|
};
|
|
231
252
|
/** gantt-timeline kind の 1 task payload。 label + start/end + owner + dependsOn */
|
|
232
253
|
type GanttTaskPayload = {
|
|
233
254
|
id: string;
|
|
234
255
|
title: string;
|
|
235
|
-
/** 開始 index (0-based 相対位置) */
|
|
236
|
-
startIdx:
|
|
237
|
-
/** 終了 index (0-based 相対位置、 startIdx と同じなら 1 cell 幅) */
|
|
238
|
-
endIdx:
|
|
256
|
+
/** 開始 index (0-based 相対位置)。 `{signal}` で状態に追随する */
|
|
257
|
+
startIdx: BoundNumber;
|
|
258
|
+
/** 終了 index (0-based 相対位置、 startIdx と同じなら 1 cell 幅)。 `{signal}` 可 */
|
|
259
|
+
endIdx: BoundNumber;
|
|
239
260
|
/** 開始 label (例 "Q1" / "2026-01") */
|
|
240
261
|
startLabel: string;
|
|
241
262
|
/** 終了 label (例 "Q2" / "2026-03") */
|
|
@@ -244,7 +265,7 @@ type GanttTaskPayload = {
|
|
|
244
265
|
dependsOn?: string;
|
|
245
266
|
tone?: Tone;
|
|
246
267
|
};
|
|
247
|
-
/** mind-map
|
|
268
|
+
/** mind-map kind の branch tree payload。 rootId + branch 配列 */
|
|
248
269
|
type MindBranchPayload = {
|
|
249
270
|
rootId: string;
|
|
250
271
|
rootTitle: string;
|
|
@@ -262,7 +283,8 @@ type MindBranchNode = {
|
|
|
262
283
|
type FunnelStagePayload = {
|
|
263
284
|
id: string;
|
|
264
285
|
title: string;
|
|
265
|
-
|
|
286
|
+
/** 段の人数 / 件数。 `{signal}` で状態に追随する */
|
|
287
|
+
count: BoundNumber;
|
|
266
288
|
subtitle?: string;
|
|
267
289
|
};
|
|
268
290
|
/** quadrant-matrix kind の 2 軸 + item 配列 payload */
|
|
@@ -283,11 +305,14 @@ type QuadrantPayload = {
|
|
|
283
305
|
};
|
|
284
306
|
items: QuadrantItemPayload[];
|
|
285
307
|
};
|
|
308
|
+
/** quadrant-matrix の象限 4 種 */
|
|
309
|
+
type QuadrantKey = "topLeft" | "topRight" | "bottomLeft" | "bottomRight";
|
|
286
310
|
/** quadrant-matrix の 1 item payload */
|
|
287
311
|
type QuadrantItemPayload = {
|
|
288
312
|
id: string;
|
|
289
313
|
title: string;
|
|
290
|
-
|
|
314
|
+
/** どの象限に居るか。 `{signal}` を書くと項目が象限を移る */
|
|
315
|
+
quadrant: BoundEnum<QuadrantKey>;
|
|
291
316
|
subtitle?: string;
|
|
292
317
|
};
|
|
293
318
|
/** tree-hierarchy kind の 1 node payload */
|
|
@@ -298,12 +323,14 @@ type TreeNodePayload = {
|
|
|
298
323
|
subtitle?: string;
|
|
299
324
|
eyebrow?: string;
|
|
300
325
|
};
|
|
326
|
+
/** journey-map の気持ち 5 段階 */
|
|
327
|
+
type JourneyEmotion = "delighted" | "happy" | "neutral" | "frustrated" | "angry";
|
|
301
328
|
/** journey-map kind の 1 step payload */
|
|
302
329
|
type JourneyStepPayload = {
|
|
303
330
|
id: string;
|
|
304
331
|
title: string;
|
|
305
|
-
/** emotion 5 段階 (delighted / happy / neutral / frustrated / angry) */
|
|
306
|
-
emotion:
|
|
332
|
+
/** emotion 5 段階 (delighted / happy / neutral / frustrated / angry)。 `{signal}` で動く */
|
|
333
|
+
emotion: BoundEnum<JourneyEmotion>;
|
|
307
334
|
touchpoint?: string;
|
|
308
335
|
opportunity?: string;
|
|
309
336
|
};
|
|
@@ -2128,6 +2155,13 @@ type CdlDiagram = {
|
|
|
2128
2155
|
* 省略時は空 array 相当で下位互換。
|
|
2129
2156
|
*/
|
|
2130
2157
|
readouts?: CdlReadout[];
|
|
2158
|
+
/**
|
|
2159
|
+
* 他の値から自動で決まる値。 段の補間中も毎 frame 決まり直す (`computeStateValues`)。
|
|
2160
|
+
*
|
|
2161
|
+
* 入力欄の式 (`formulas`) とは経路が別。 あちらは入力欄の signal を読む reactive graph に
|
|
2162
|
+
* 載り、 こちらは段が動かす状態を読む。 省略時は空 array 相当で下位互換。
|
|
2163
|
+
*/
|
|
2164
|
+
derived?: CdlDerivedValue[];
|
|
2131
2165
|
lanes: CdlLane[];
|
|
2132
2166
|
nodes: CdlNode[];
|
|
2133
2167
|
edges: CdlEdge[];
|
|
@@ -2136,6 +2170,21 @@ type CdlDiagram = {
|
|
|
2136
2170
|
/** v0.5+ ... canvas 全体仕様 (viewport.width / height で全体 size を author が宣言) */
|
|
2137
2171
|
viewport?: CdlViewport;
|
|
2138
2172
|
};
|
|
2173
|
+
/**
|
|
2174
|
+
* 他の値から自動で決まる値の宣言。
|
|
2175
|
+
*
|
|
2176
|
+
* 式は `{名前}` で他の値 (`states` / 他の `derived`) を読み、 四則 / 括弧 / 比較 /
|
|
2177
|
+
* `min` / `max` が書ける。 比較の結果は真 = 1 / 偽 = 0 の数として載る。
|
|
2178
|
+
*
|
|
2179
|
+
* 書いた順は結果に影響しない (参照から順序を解く)。 解けない値 (輪 / 存在しない名前 /
|
|
2180
|
+
* 0 除算 / 数でない値) はその値だけ止まり、 残りは動く。
|
|
2181
|
+
*/
|
|
2182
|
+
type CdlDerivedValue = {
|
|
2183
|
+
/** 値の名前。 図からは `{名前}` で読む */
|
|
2184
|
+
id: string;
|
|
2185
|
+
/** 式。 例 `"{inflow} - {done}"` */
|
|
2186
|
+
expression: string;
|
|
2187
|
+
};
|
|
2139
2188
|
/** Canvas 全体仕様 (v0.5+ syntax `viewport: { width, height, scale, gap, laneGap, nodeGap, labelMargin }`)。
|
|
2140
2189
|
* 未指定時は layout が node 配置から自動算出する従来挙動。
|
|
2141
2190
|
*
|
|
@@ -2279,6 +2328,8 @@ type LaidDiagram = {
|
|
|
2279
2328
|
edges: LaidEdge[];
|
|
2280
2329
|
states: CdlState[];
|
|
2281
2330
|
phases: CdlPhase[];
|
|
2331
|
+
/** 他の値から自動で決まる値 (`CdlDiagram.derived` をそのまま引き継ぐ) */
|
|
2332
|
+
derived?: CdlDerivedValue[];
|
|
2282
2333
|
/**
|
|
2283
2334
|
* キャンバス全要素の bounding box list。
|
|
2284
2335
|
* engine が「ここに何があるか」 を全部把握、 衝突検証 / debug 表示 / validation で使う。
|
|
@@ -2446,4 +2497,4 @@ type CdlDiagramViewProps = {
|
|
|
2446
2497
|
};
|
|
2447
2498
|
declare function CdlDiagramView({ diagram, laid: laidProp, hideHeader, hideMiniPhaseIndicator, focusPhaseId, debug, emitGeometryWarn, headingLevel, interactiveHandlers }: CdlDiagramViewProps): JSX.Element;
|
|
2448
2499
|
|
|
2449
|
-
export {
|
|
2500
|
+
export { computeScrollTriggerKey as A, type BBox as B, type CdlDiagram as C, computed as D, type EdgeStyle as E, createInteractiveSignalsAccessor as F, createScrollProgressHandle as G, createScrollProgressSignals as H, type InteractiveHandlerMap as I, type JourneyEmotion as J, hasInteractivePrimitives as K, type LaidDiagram as L, signal as M, type NodeKind as N, type QuadrantKey as Q, type Signal as S, type Tone as T, type CdlInput as a, type CdlLane as b, type CdlNode as c, type Side as d, type CdlState as e, type CdlEventTarget as f, type Computed as g, type CdlDerivedValue as h, type CdlScrollTrigger as i, type CdlDiagramViewProps as j, type LaidEdge as k, type LaidNode as l, type CdlEdge as m, CdlDiagramView as n, type CdlEventBinding as o, type CdlEventKind as p, type CdlFormula as q, type CdlPhase as r, type InteractiveSignalsAccessor as s, type LaidLane as t, NODE_KINDS as u, type ScrollProgressHandle as v, TONES as w, computeEventBindingKey as x, computeFormulaKey as y, computeInputSignalKey as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cardenelabs/cdl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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/formula/parser.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ALLOWED_MATH_FNS, type BinaryOp, type FormulaAst, type MathFnName } from "./ast";
|
|
2
|
+
import { isValidTemplateName } from "../template-name";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* formula expression parser (CAR #244)。
|
|
@@ -71,6 +72,30 @@ function tokenize(src: string): Token[] {
|
|
|
71
72
|
tokens.push({ kind: "num", value: num, pos: start });
|
|
72
73
|
continue;
|
|
73
74
|
}
|
|
75
|
+
// 波括弧で囲んだ名前 (`{waiting}`)。 図の他の場所と同じ書き方で値を参照する形。
|
|
76
|
+
// 中身は前後の空白を落としてから名前として扱う。
|
|
77
|
+
if (c === "{") {
|
|
78
|
+
const start = i;
|
|
79
|
+
const close = src.indexOf("}", i + 1);
|
|
80
|
+
if (close === -1) {
|
|
81
|
+
throw new Error(`unclosed "{" at position ${start} (write a name like {waiting})`);
|
|
82
|
+
}
|
|
83
|
+
const name = src.slice(i + 1, close).trim();
|
|
84
|
+
if (name === "") {
|
|
85
|
+
throw new Error(`empty name "{}" at position ${start}`);
|
|
86
|
+
}
|
|
87
|
+
// 名前として使える文字は engine 全体で 1 つの定義に従う (`isValidTemplateName`)。
|
|
88
|
+
// ここだけ緩めると、 式では読めるのに図のどこからも読めない名前ができる
|
|
89
|
+
// (`{v.done}` は `.` が accessor の始まりなので読み手が拾えない)。
|
|
90
|
+
if (!isValidTemplateName(name)) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`invalid name "{${name}}" at position ${start} (空白 / { } / . / [ ] は名前に使えない)`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
tokens.push({ kind: "id", name, pos: start });
|
|
96
|
+
i = close + 1;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
74
99
|
// identifier / Math.<fn>
|
|
75
100
|
if ((c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || c === "_" || c === "$") {
|
|
76
101
|
const start = i;
|
|
@@ -115,6 +140,16 @@ function tokenize(src: string): Token[] {
|
|
|
115
140
|
tokens.push({ kind: "math", fn: `Math.${fnName}` as MathFnName, pos: start });
|
|
116
141
|
continue;
|
|
117
142
|
}
|
|
143
|
+
// 許可済 Math 関数は prefix 無しでも書ける (`min(a, b)`)。 直後が `(` の時だけ関数として
|
|
144
|
+
// 読むので、 同名の値を参照する形 (`min * 2`) は今までどおり identifier のまま。
|
|
145
|
+
//
|
|
146
|
+
// 6 つ全てを対象にするのは、 2 つだけ許すと「どれが prefix 無しで書けるか」 を
|
|
147
|
+
// 覚える必要が出るため。 呼び出し側が式に書ける範囲を狭めたい場合は、 呼び出し側で
|
|
148
|
+
// 自分の記法として絞る。
|
|
149
|
+
if (ALLOWED_MATH_FNS.has(name) && src[i] === "(") {
|
|
150
|
+
tokens.push({ kind: "math", fn: `Math.${name}` as MathFnName, pos: start });
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
118
153
|
// property access on other identifiers is禁止
|
|
119
154
|
if (src[i] === ".") {
|
|
120
155
|
throw new Error(
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,13 @@ export {
|
|
|
22
22
|
export type { FormulaAst, FormulaContext, BinaryOp, MathFnName } from "./formula";
|
|
23
23
|
export type { CdlFormula } from "./types";
|
|
24
24
|
|
|
25
|
+
// 他の値から自動で決まる値 (cdl#453)。 入力欄の式 (formulas) と違い、 段が動かす状態を読み、
|
|
26
|
+
// `computeStateValues` の中で毎 frame 解かれる。 呼び出し側が止まった値を自分で伝えたい時だけ
|
|
27
|
+
// `applyDerivedValues` を直接使う。
|
|
28
|
+
export { applyDerivedValues, withDerivedValues } from "./render/derived-values";
|
|
29
|
+
export type { DerivedResult, DerivedNotice, DerivedNoticeKind } from "./render/derived-values";
|
|
30
|
+
export type { CdlDerivedValue } from "./types";
|
|
31
|
+
|
|
25
32
|
// scroll-driven trigger (CAR #245) — builder chain `.animation.scroll(id, opts)` で scroll trigger を
|
|
26
33
|
// 追加、 createScrollProgressSignals(diagram) で { progress signal, attach(element), detach } を得て
|
|
27
34
|
// DOM element に IntersectionObserver 経由で bind、 signal.value で 0..1 progress を read。
|
|
@@ -186,7 +193,7 @@ export {
|
|
|
186
193
|
LABEL_SIZE_TOLERANCE_WORLD,
|
|
187
194
|
bboxDiffWorld,
|
|
188
195
|
} from "./layout/predict-bbox";
|
|
189
|
-
export { swimlane, flow, sequence, topology, er, stateMachine, infrastructure, classDiagram, tree, userJourney, mindMap,
|
|
196
|
+
export { swimlane, flow, sequence, topology, er, stateMachine, infrastructure, classDiagram, tree, userJourney, mindMap, funnel, quadrant, chart, gantt, flowchart, network, stateMachine2 } from "./presets";
|
|
190
197
|
// Text DSL は @cardenelabs/dragon に切出済 (cdl は engine only)
|
|
191
198
|
export type {
|
|
192
199
|
SwimlanePreset,
|
|
@@ -229,9 +236,6 @@ export type {
|
|
|
229
236
|
MindMapPreset,
|
|
230
237
|
MindMapBuilder,
|
|
231
238
|
MindBranch,
|
|
232
|
-
MindMapRadialPreset,
|
|
233
|
-
MindMapRadialBuilder,
|
|
234
|
-
MindMapRadialBranch,
|
|
235
239
|
FunnelPreset,
|
|
236
240
|
FunnelBuilder,
|
|
237
241
|
FunnelStage,
|
package/src/kinds/chart-bar.tsx
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import type { JSX } from "react";
|
|
2
2
|
import type { LaidNode } from "../types";
|
|
3
|
+
import { resolveChartData } from "../render/payload-binding";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* chart-bar kind ... 1 node に datum 配列 (chartData) を保持し、 SVG rect で棒グラフ描画する。
|
|
6
7
|
* layout は node bbox 内側の canvas に縦棒を等間隔配置、 縦軸 = value 0-max、 横軸 = datum label。
|
|
8
|
+
* 各 value は数でも `{signal}` でもよく、 後者は状態が動くたび棒の高さが追随する。
|
|
7
9
|
*/
|
|
8
|
-
export function ChartBarNode({
|
|
10
|
+
export function ChartBarNode({
|
|
11
|
+
node,
|
|
12
|
+
active,
|
|
13
|
+
stateValues = {},
|
|
14
|
+
}: {
|
|
15
|
+
node: LaidNode;
|
|
16
|
+
active: boolean;
|
|
17
|
+
stateValues?: Record<string, string>;
|
|
18
|
+
}): JSX.Element {
|
|
9
19
|
const x0 = node.cx - node.w / 2;
|
|
10
20
|
const y0 = node.cy - node.h / 2;
|
|
11
|
-
const data = node.chartData ?? [];
|
|
21
|
+
const data = resolveChartData(node.chartData ?? [], stateValues);
|
|
12
22
|
|
|
13
23
|
const PAD_TOP = 20;
|
|
14
24
|
const PAD_RIGHT = 24;
|
|
@@ -87,6 +97,7 @@ export function ChartBarNode({ node, active }: { node: LaidNode; active: boolean
|
|
|
87
97
|
<g key={`bar-${idx}`}>
|
|
88
98
|
<rect
|
|
89
99
|
data-cdl-role="chart-bar"
|
|
100
|
+
data-cdl-unresolved={d.unresolved ? "true" : undefined}
|
|
90
101
|
x={bx}
|
|
91
102
|
y={by}
|
|
92
103
|
width={barW}
|
package/src/kinds/chart-line.tsx
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import type { JSX } from "react";
|
|
2
2
|
import type { LaidNode } from "../types";
|
|
3
|
+
import { resolveChartData } from "../render/payload-binding";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* chart-line kind ... 1 node に datum 配列 (chartData) を保持し、 SVG polyline で折れ線描画する。
|
|
6
7
|
* layout は node bbox (node.w × node.h) 内側に padding (top/right/bottom/left) を確保し、
|
|
7
8
|
* その内側 rect を chart canvas とみなす。 縦軸 = value min-max、 横軸 = datum index。
|
|
9
|
+
* 各 value は数でも `{signal}` でもよく、 後者は状態が動くたび折れ線が追随する。
|
|
8
10
|
*/
|
|
9
|
-
export function ChartLineNode({
|
|
11
|
+
export function ChartLineNode({
|
|
12
|
+
node,
|
|
13
|
+
active,
|
|
14
|
+
stateValues = {},
|
|
15
|
+
}: {
|
|
16
|
+
node: LaidNode;
|
|
17
|
+
active: boolean;
|
|
18
|
+
stateValues?: Record<string, string>;
|
|
19
|
+
}): JSX.Element {
|
|
10
20
|
const x0 = node.cx - node.w / 2;
|
|
11
21
|
const y0 = node.cy - node.h / 2;
|
|
12
|
-
const data = node.chartData ?? [];
|
|
22
|
+
const data = resolveChartData(node.chartData ?? [], stateValues);
|
|
13
23
|
|
|
14
24
|
const PAD_TOP = 36;
|
|
15
25
|
const PAD_RIGHT = 40;
|
|
@@ -120,7 +130,7 @@ export function ChartLineNode({ node, active }: { node: LaidNode; active: boolea
|
|
|
120
130
|
const labelAbove = !isValley;
|
|
121
131
|
const labelY = labelAbove ? cy - 12 : cy + 18;
|
|
122
132
|
return (
|
|
123
|
-
<g key={`pt-${idx}`}>
|
|
133
|
+
<g key={`pt-${idx}`} data-cdl-unresolved={d.unresolved ? "true" : undefined}>
|
|
124
134
|
<circle
|
|
125
135
|
cx={cx}
|
|
126
136
|
cy={cy}
|
package/src/kinds/chart-pie.tsx
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import type { JSX } from "react";
|
|
2
2
|
import type { LaidNode, Tone } from "../types";
|
|
3
3
|
import { TONE } from "../render/tone";
|
|
4
|
+
import { resolveChartData } from "../render/payload-binding";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* chart-pie kind ... 1 node に datum 配列 (chartData) を保持し、 SVG path arc で円グラフ描画する。
|
|
7
8
|
* layout は node bbox 内側の左半分に円を配置、 右半分に凡例 (label + %) を stack 表示する。
|
|
9
|
+
* 各 value は数でも `{signal}` でもよく、 後者は状態が動くたび扇の角度が追随する。
|
|
8
10
|
*/
|
|
9
|
-
export function ChartPieNode({
|
|
11
|
+
export function ChartPieNode({
|
|
12
|
+
node,
|
|
13
|
+
active,
|
|
14
|
+
stateValues = {},
|
|
15
|
+
}: {
|
|
16
|
+
node: LaidNode;
|
|
17
|
+
active: boolean;
|
|
18
|
+
stateValues?: Record<string, string>;
|
|
19
|
+
}): JSX.Element {
|
|
10
20
|
const x0 = node.cx - node.w / 2;
|
|
11
21
|
const y0 = node.cy - node.h / 2;
|
|
12
|
-
const data = node.chartData ?? [];
|
|
22
|
+
const data = resolveChartData(node.chartData ?? [], stateValues);
|
|
13
23
|
const total = data.reduce((acc, d) => acc + d.value, 0) || 1;
|
|
14
24
|
|
|
15
25
|
const PAD_TOP = 20;
|
|
@@ -58,6 +68,7 @@ export function ChartPieNode({ node, active }: { node: LaidNode; active: boolean
|
|
|
58
68
|
<path
|
|
59
69
|
key={`slice-${idx}`}
|
|
60
70
|
data-cdl-role="chart-pie-slice"
|
|
71
|
+
data-cdl-unresolved={s.d.unresolved ? "true" : undefined}
|
|
61
72
|
d={s.path}
|
|
62
73
|
fill={sliceColor(s.d.tone, idx)}
|
|
63
74
|
fillOpacity={active ? 0.95 : 0.85}
|
package/src/kinds/dyn-shape.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect, useState, type JSX } from "react";
|
|
2
2
|
import type { CdlDynShape, LaidNode } from "../types";
|
|
3
|
-
import { resolveNumericAttr } from "../render/utils";
|
|
3
|
+
import { interpolate, resolveNumericAttr } from "../render/utils";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* time-driven tick hook = ~30fps throttle で performance.now() を state に反映。
|
|
@@ -390,5 +390,6 @@ function interpolateColor(
|
|
|
390
390
|
fallback: string,
|
|
391
391
|
): string {
|
|
392
392
|
if (!template) return fallback;
|
|
393
|
-
|
|
393
|
+
// engine 共通の経路で解決する。 独自に書くと名前の字種がここだけずれる
|
|
394
|
+
return interpolate(template, stateValues);
|
|
394
395
|
}
|
package/src/kinds/funnel.tsx
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
import type { JSX } from "react";
|
|
2
2
|
import type { LaidNode, Tone } from "../types";
|
|
3
3
|
import { TONE } from "../render/tone";
|
|
4
|
+
import { resolveFunnelData } from "../render/payload-binding";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* funnel-stages kind ... 1 node に stage 配列 (funnelData) を保持し、 逆三角形 funnel を SVG 描画する。
|
|
7
8
|
* layout = 全体で自然な逆三角形になるように上端 100% → 下端 40% で線形補間、
|
|
8
9
|
* 各 stage は同じ縦幅 + 上下辺の幅を段階的に減らして「じょうご」 形状で表現。
|
|
9
10
|
* count / drop rate は polygon の右横 (side legend) に配置して図形と数値を分離。
|
|
11
|
+
* count は数でも `{signal}` でもよく、 後者は状態が動くたび件数と離脱率が追随する。
|
|
10
12
|
*/
|
|
11
|
-
export function FunnelNode({
|
|
13
|
+
export function FunnelNode({
|
|
14
|
+
node,
|
|
15
|
+
active,
|
|
16
|
+
stateValues = {},
|
|
17
|
+
}: {
|
|
18
|
+
node: LaidNode;
|
|
19
|
+
active: boolean;
|
|
20
|
+
stateValues?: Record<string, string>;
|
|
21
|
+
}): JSX.Element {
|
|
12
22
|
const x0 = node.cx - node.w / 2;
|
|
13
23
|
const y0 = node.cy - node.h / 2;
|
|
14
|
-
const stages = node.funnelData ?? [];
|
|
24
|
+
const stages = resolveFunnelData(node.funnelData ?? [], stateValues);
|
|
15
25
|
|
|
16
26
|
const PAD_TOP = 32;
|
|
17
27
|
const PAD_RIGHT = 24;
|
|
@@ -48,7 +58,9 @@ export function FunnelNode({ node, active }: { node: LaidNode; active: boolean }
|
|
|
48
58
|
|
|
49
59
|
{stages.map((s, idx) => {
|
|
50
60
|
const prev = idx > 0 ? stages[idx - 1]! : null;
|
|
51
|
-
|
|
61
|
+
// 前段が 0 件なら離脱率は決まらない (0 で割ることになる)。 静止画では起きなかったが、
|
|
62
|
+
// count が状態を読むようになると動き始めの 0 で必ず通る。
|
|
63
|
+
const dropRate = prev && prev.count > 0 ? ((prev.count - s.count) / prev.count) * 100 : 0;
|
|
52
64
|
const y = PAD_TOP + idx * (stageH + gap);
|
|
53
65
|
const topW = widthAtStep(idx);
|
|
54
66
|
const bottomW = widthAtStep(idx + 1);
|
|
@@ -62,6 +74,7 @@ export function FunnelNode({ node, active }: { node: LaidNode; active: boolean }
|
|
|
62
74
|
<g key={`stage-${s.id}`}>
|
|
63
75
|
<polygon
|
|
64
76
|
data-cdl-role="funnel-stage"
|
|
77
|
+
data-cdl-unresolved={s.unresolved ? "true" : undefined}
|
|
65
78
|
points={points}
|
|
66
79
|
fill={tone}
|
|
67
80
|
fillOpacity={0.9}
|