@cardenelabs/cdl 0.14.0 → 0.16.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +249 -1
  2. package/README.md +20 -1
  3. package/dist/index.cjs +1243 -92
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +27 -5
  6. package/dist/index.d.ts +27 -5
  7. package/dist/index.js +1242 -93
  8. package/dist/index.js.map +1 -1
  9. package/dist/react.cjs +1208 -88
  10. package/dist/react.cjs.map +1 -1
  11. package/dist/react.d.cts +1 -1
  12. package/dist/react.d.ts +1 -1
  13. package/dist/react.js +1208 -88
  14. package/dist/react.js.map +1 -1
  15. package/dist/{render-B5JHcUrb.d.cts → render-BS1E6Mlo.d.cts} +55 -2
  16. package/dist/{render-B5JHcUrb.d.ts → render-BS1E6Mlo.d.ts} +55 -2
  17. package/package.json +1 -1
  18. package/src/builder.ts +24 -0
  19. package/src/index.ts +2 -1
  20. package/src/kinds/chart-gauge.tsx +241 -0
  21. package/src/kinds/chart-pie.tsx +214 -53
  22. package/src/kinds/chart-radial.tsx +210 -0
  23. package/src/kinds/chart-stacked-bar.tsx +272 -0
  24. package/src/kinds/chart-stat.tsx +231 -0
  25. package/src/kinds/chart-waffle.tsx +197 -0
  26. package/src/kinds/draw-ratio.ts +44 -0
  27. package/src/kinds/dyn-shape.tsx +3 -6
  28. package/src/kinds/generic.tsx +18 -6
  29. package/src/kinds/terminal-mark.tsx +96 -0
  30. package/src/layout/edges.ts +30 -1
  31. package/src/layout/self-loop.ts +217 -0
  32. package/src/layout/spec.ts +81 -4
  33. package/src/layout/tokens.ts +15 -0
  34. package/src/presets.ts +68 -4
  35. package/src/render/edge-head.ts +45 -0
  36. package/src/render/edges.tsx +4 -2
  37. package/src/render/interactive-panel.tsx +2 -2
  38. package/src/render/nodes.tsx +25 -0
  39. package/src/render/payload-binding.ts +14 -3
  40. package/src/render/stage.tsx +41 -18
  41. package/src/render/tone.ts +52 -0
  42. package/src/types.ts +66 -2
  43. package/src/validate.ts +13 -5
  44. package/src/visual-validate.ts +48 -1
@@ -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", "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"];
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", "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"];
37
37
  type NodeKind = (typeof NODE_KINDS)[number];
38
38
  type Side = "top" | "right" | "bottom" | "left";
39
39
  /**
@@ -43,6 +43,23 @@ type Side = "top" | "right" | "bottom" | "left";
43
43
  * path が経由 node を貫通する場合は自動で粒子が node 中心を貫通する動きに切替。
44
44
  */
45
45
  type EdgeStyle = "solid" | "dotted-flow";
46
+ /**
47
+ * 関係の **端の形** (#560)。 線の種類 (`EdgeStyle`) とは別の軸で、線の先端に何を置くかを決める。
48
+ *
49
+ * - `"triangle"` ... 塗った三角。 **既定**。 書かなければこれになる
50
+ * - `"diamond"` ... 塗った菱。 「持っている」 側を指す
51
+ * - `"open"` ... 閉じない 2 本の腕。 向きだけを示し、待たない
52
+ * - `"crow"` ... 三又。 「多」 を示す (ER の鳥の足)
53
+ *
54
+ * 線の種類と分けて持つのは、両者が独立に組み合わさるため。 実線 × 三角 (継ぐ) と
55
+ * 破線 × 三角 (満たす) は線種で分かれ、実線 × 三角 (継ぐ) と実線 × 菱 (持つ) は端で分かれる。
56
+ * 1 つの欄に畳むと 2 軸の積を名前で列挙することになり、組合せが増えるたびに語が増える。
57
+ */
58
+ type EdgeHead = "triangle" | "diamond" | "open" | "crow";
59
+ /** 書かなかった時の端の形。 既存の図はすべてこれで描かれている */
60
+ declare const EDGE_HEAD_DEFAULT: EdgeHead;
61
+ /** 端の形の全種。 marker の定義と検査の走査がこの並びを引く (書き写すとずれる) */
62
+ declare const EDGE_HEADS: readonly EdgeHead[];
46
63
  type CdlLane = {
47
64
  id: string;
48
65
  /** lane の x 座標 (optional、 未指定なら engine が前 lane 右端 + gap で auto 計算) */
@@ -271,6 +288,16 @@ type ChartDatumPayload = {
271
288
  label: string;
272
289
  /** 棒 / 折れ線 / 扇の大きさ。 `{signal}` で状態に追随する */
273
290
  value: BoundNumber;
291
+ /**
292
+ * **前の時点の値** (cdl#551)。 書くと `chart-stacked-bar` が帯を 2 本描き、
293
+ * 内訳の変化がそのまま見える。 書かない図は 1 本のまま。
294
+ *
295
+ * 節を時点ごとに置く案は採らない = 図表は「1 節に datum 配列を持ち、種別が全 datum を
296
+ * 描く」 構造で揃っており、節を増やすと配置と組み立て器の両方が別経路になる。
297
+ *
298
+ * 3 時点以上は持たない (cdl#551 で対象外)。 足す時は欄の形ごと決め直す。
299
+ */
300
+ previous?: BoundNumber;
274
301
  tone?: Tone;
275
302
  };
276
303
  /** gantt-timeline kind の 1 task payload。 label + start/end + owner + dependsOn */
@@ -383,6 +410,8 @@ type CdlEdge = {
383
410
  side?: Side;
384
411
  /** visual style、 default "solid" */
385
412
  style?: EdgeStyle;
413
+ /** 端の形、 default "triangle" (#560)。 線の種類とは別の軸 */
414
+ head?: EdgeHead;
386
415
  /**
387
416
  * FSM transition の guard 条件 (例 "if validated")。
388
417
  * stateMachine preset では sub に併合される、 text-dsl v0.5 では guard 単独で渡せる。
@@ -2081,6 +2110,30 @@ type CdlPhase = {
2081
2110
  * 書かない phase では従来どおり全長で描く (DOM に dash 属性も付かない)。
2082
2111
  */
2083
2112
  draw?: string[];
2113
+ /**
2114
+ * `draw` の対象を描き終えるまでに、 phase の何割を使うか (cdl#570)。
2115
+ *
2116
+ * 0 より大きく 1 以下の割合で書く。 `0.4` なら phase の進みが 0.4 に達した時点で
2117
+ * 描き終わり、 残りの 0.6 は描き終わった図のまま値だけが動く。 既定 (書かない) は 1 で、
2118
+ * 従来どおり phase の終わりに描き終わる。
2119
+ *
2120
+ * **描く速さと phase の長さを切り離すための欄**。 進みは phase の進みそのものなので、
2121
+ * これが無いと「線はゆっくり引きたいが値の移りは短くしたい」 が書けず、 描く速さのために
2122
+ * phase の長さを動かすことになる。 phase の長さを動かすと同じ phase の `tweens` まで
2123
+ * 遅くなり、 値が移るだけの phase を長くする形の回避が利用側で要る。
2124
+ *
2125
+ * **割合で持ち、 秒数では持たない**。 割合なら phase に収まるので、 再生速度の切替
2126
+ * (0.5x / 2x) がそのまま効き、 phase をはみ出した時の扱いを決めずに済む。 秒数だと
2127
+ * 切替との掛かり方とはみ出しの扱いを新たに決めることになる。
2128
+ *
2129
+ * 掛かる相手は `draw` に載せた node だけ。 同じ phase の他の node と edge、 および
2130
+ * `tweens` は元の進みのまま動く。
2131
+ *
2132
+ * 範囲外 (0 以下 / 1 超 / NaN / Infinity) は **1 として扱う**。 描く動きは見え方であって
2133
+ * 図の意味ではないため、 書き間違いで図が出なくなる方が損が大きい。 書き間違いは
2134
+ * `validate` が warning で知らせる。
2135
+ */
2136
+ drawRatio?: number;
2084
2137
  /** 数値 state の線形補間 (write phase で 100→90 等) */
2085
2138
  tweens: Array<{
2086
2139
  stateId: string;
@@ -2562,4 +2615,4 @@ type CdlDiagramViewProps = {
2562
2615
  };
2563
2616
  declare function CdlDiagramView({ diagram, laid: laidProp, hideHeader, hideMiniPhaseIndicator, focusPhaseId, debug, emitGeometryWarn, headingLevel, interactiveHandlers }: CdlDiagramViewProps): JSX.Element;
2564
2617
 
2565
- 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 };
2618
+ export { computeEventBindingKey as A, type BBox as B, type CdlDiagram as C, computeFormulaKey as D, type EdgeStyle as E, computeInputSignalKey as F, computeScrollTriggerKey as G, computed as H, type InteractiveHandlerMap as I, type JourneyEmotion as J, createInteractiveSignalsAccessor as K, type LaidDiagram as L, createScrollProgressHandle as M, type NodeKind as N, createScrollProgressSignals as O, hasInteractivePrimitives as P, type QuadrantKey as Q, signal as R, 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 EdgeHead as e, type CdlState as f, type CdlEventTarget as g, type Computed as h, type CdlDerivedValue as i, type CdlScrollTrigger as j, type CdlDiagramViewProps as k, type LaidEdge as l, type LaidNode as m, type CdlEdge as n, CdlDiagramView as o, type CdlEventBinding as p, type CdlEventKind as q, type CdlFormula as r, type CdlPhase as s, EDGE_HEADS as t, EDGE_HEAD_DEFAULT as u, type InteractiveSignalsAccessor as v, type LaidLane as w, NODE_KINDS as x, type ScrollProgressHandle as y, TONES 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", "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"];
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", "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"];
37
37
  type NodeKind = (typeof NODE_KINDS)[number];
38
38
  type Side = "top" | "right" | "bottom" | "left";
39
39
  /**
@@ -43,6 +43,23 @@ type Side = "top" | "right" | "bottom" | "left";
43
43
  * path が経由 node を貫通する場合は自動で粒子が node 中心を貫通する動きに切替。
44
44
  */
45
45
  type EdgeStyle = "solid" | "dotted-flow";
46
+ /**
47
+ * 関係の **端の形** (#560)。 線の種類 (`EdgeStyle`) とは別の軸で、線の先端に何を置くかを決める。
48
+ *
49
+ * - `"triangle"` ... 塗った三角。 **既定**。 書かなければこれになる
50
+ * - `"diamond"` ... 塗った菱。 「持っている」 側を指す
51
+ * - `"open"` ... 閉じない 2 本の腕。 向きだけを示し、待たない
52
+ * - `"crow"` ... 三又。 「多」 を示す (ER の鳥の足)
53
+ *
54
+ * 線の種類と分けて持つのは、両者が独立に組み合わさるため。 実線 × 三角 (継ぐ) と
55
+ * 破線 × 三角 (満たす) は線種で分かれ、実線 × 三角 (継ぐ) と実線 × 菱 (持つ) は端で分かれる。
56
+ * 1 つの欄に畳むと 2 軸の積を名前で列挙することになり、組合せが増えるたびに語が増える。
57
+ */
58
+ type EdgeHead = "triangle" | "diamond" | "open" | "crow";
59
+ /** 書かなかった時の端の形。 既存の図はすべてこれで描かれている */
60
+ declare const EDGE_HEAD_DEFAULT: EdgeHead;
61
+ /** 端の形の全種。 marker の定義と検査の走査がこの並びを引く (書き写すとずれる) */
62
+ declare const EDGE_HEADS: readonly EdgeHead[];
46
63
  type CdlLane = {
47
64
  id: string;
48
65
  /** lane の x 座標 (optional、 未指定なら engine が前 lane 右端 + gap で auto 計算) */
@@ -271,6 +288,16 @@ type ChartDatumPayload = {
271
288
  label: string;
272
289
  /** 棒 / 折れ線 / 扇の大きさ。 `{signal}` で状態に追随する */
273
290
  value: BoundNumber;
291
+ /**
292
+ * **前の時点の値** (cdl#551)。 書くと `chart-stacked-bar` が帯を 2 本描き、
293
+ * 内訳の変化がそのまま見える。 書かない図は 1 本のまま。
294
+ *
295
+ * 節を時点ごとに置く案は採らない = 図表は「1 節に datum 配列を持ち、種別が全 datum を
296
+ * 描く」 構造で揃っており、節を増やすと配置と組み立て器の両方が別経路になる。
297
+ *
298
+ * 3 時点以上は持たない (cdl#551 で対象外)。 足す時は欄の形ごと決め直す。
299
+ */
300
+ previous?: BoundNumber;
274
301
  tone?: Tone;
275
302
  };
276
303
  /** gantt-timeline kind の 1 task payload。 label + start/end + owner + dependsOn */
@@ -383,6 +410,8 @@ type CdlEdge = {
383
410
  side?: Side;
384
411
  /** visual style、 default "solid" */
385
412
  style?: EdgeStyle;
413
+ /** 端の形、 default "triangle" (#560)。 線の種類とは別の軸 */
414
+ head?: EdgeHead;
386
415
  /**
387
416
  * FSM transition の guard 条件 (例 "if validated")。
388
417
  * stateMachine preset では sub に併合される、 text-dsl v0.5 では guard 単独で渡せる。
@@ -2081,6 +2110,30 @@ type CdlPhase = {
2081
2110
  * 書かない phase では従来どおり全長で描く (DOM に dash 属性も付かない)。
2082
2111
  */
2083
2112
  draw?: string[];
2113
+ /**
2114
+ * `draw` の対象を描き終えるまでに、 phase の何割を使うか (cdl#570)。
2115
+ *
2116
+ * 0 より大きく 1 以下の割合で書く。 `0.4` なら phase の進みが 0.4 に達した時点で
2117
+ * 描き終わり、 残りの 0.6 は描き終わった図のまま値だけが動く。 既定 (書かない) は 1 で、
2118
+ * 従来どおり phase の終わりに描き終わる。
2119
+ *
2120
+ * **描く速さと phase の長さを切り離すための欄**。 進みは phase の進みそのものなので、
2121
+ * これが無いと「線はゆっくり引きたいが値の移りは短くしたい」 が書けず、 描く速さのために
2122
+ * phase の長さを動かすことになる。 phase の長さを動かすと同じ phase の `tweens` まで
2123
+ * 遅くなり、 値が移るだけの phase を長くする形の回避が利用側で要る。
2124
+ *
2125
+ * **割合で持ち、 秒数では持たない**。 割合なら phase に収まるので、 再生速度の切替
2126
+ * (0.5x / 2x) がそのまま効き、 phase をはみ出した時の扱いを決めずに済む。 秒数だと
2127
+ * 切替との掛かり方とはみ出しの扱いを新たに決めることになる。
2128
+ *
2129
+ * 掛かる相手は `draw` に載せた node だけ。 同じ phase の他の node と edge、 および
2130
+ * `tweens` は元の進みのまま動く。
2131
+ *
2132
+ * 範囲外 (0 以下 / 1 超 / NaN / Infinity) は **1 として扱う**。 描く動きは見え方であって
2133
+ * 図の意味ではないため、 書き間違いで図が出なくなる方が損が大きい。 書き間違いは
2134
+ * `validate` が warning で知らせる。
2135
+ */
2136
+ drawRatio?: number;
2084
2137
  /** 数値 state の線形補間 (write phase で 100→90 等) */
2085
2138
  tweens: Array<{
2086
2139
  stateId: string;
@@ -2562,4 +2615,4 @@ type CdlDiagramViewProps = {
2562
2615
  };
2563
2616
  declare function CdlDiagramView({ diagram, laid: laidProp, hideHeader, hideMiniPhaseIndicator, focusPhaseId, debug, emitGeometryWarn, headingLevel, interactiveHandlers }: CdlDiagramViewProps): JSX.Element;
2564
2617
 
2565
- 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 };
2618
+ export { computeEventBindingKey as A, type BBox as B, type CdlDiagram as C, computeFormulaKey as D, type EdgeStyle as E, computeInputSignalKey as F, computeScrollTriggerKey as G, computed as H, type InteractiveHandlerMap as I, type JourneyEmotion as J, createInteractiveSignalsAccessor as K, type LaidDiagram as L, createScrollProgressHandle as M, type NodeKind as N, createScrollProgressSignals as O, hasInteractivePrimitives as P, type QuadrantKey as Q, signal as R, 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 EdgeHead as e, type CdlState as f, type CdlEventTarget as g, type Computed as h, type CdlDerivedValue as i, type CdlScrollTrigger as j, type CdlDiagramViewProps as k, type LaidEdge as l, type LaidNode as m, type CdlEdge as n, CdlDiagramView as o, type CdlEventBinding as p, type CdlEventKind as q, type CdlFormula as r, type CdlPhase as s, EDGE_HEADS as t, EDGE_HEAD_DEFAULT as u, type InteractiveSignalsAccessor as v, type LaidLane as w, NODE_KINDS as x, type ScrollProgressHandle as y, TONES as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardenelabs/cdl",
3
- "version": "0.14.0",
3
+ "version": "0.16.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
@@ -12,6 +12,7 @@ import type {
12
12
  CdlReadout,
13
13
  CdlScrollTrigger,
14
14
  CdlState,
15
+ EdgeHead,
15
16
  EdgeStyle,
16
17
  NodeKind,
17
18
  Side,
@@ -1360,6 +1361,16 @@ export type PhaseBuilder = {
1360
1361
  * 対象は phase の進み (0→1) に合わせて描かれる。 現在の対象種別は `chart-line`。
1361
1362
  */
1362
1363
  draw: (...ids: string[]) => PhaseBuilder;
1364
+ /**
1365
+ * `draw` の対象を描き終えるまでに、 phase の何割を使うか (cdl#570)。
1366
+ *
1367
+ * 0 より大きく 1 以下。 `0.4` なら進みが 0.4 に達した時点で描き終わり、 残りは描き終わった
1368
+ * 姿のまま値だけが動く。 書かなければ従来どおり phase の終わりに描き終わる。
1369
+ *
1370
+ * 掛かる相手は `draw` に載せた node だけで、 同じ phase の `tweens` は元の速さのまま。
1371
+ * 範囲外の値は 1 として扱い、 `validate` が warning で知らせる。
1372
+ */
1373
+ drawRatio: (ratio: number) => PhaseBuilder;
1363
1374
  /** 数値 state の線形補間 (write phase で 100→90 等) */
1364
1375
  tween: (stateId: string, from: number, to: number) => PhaseBuilder;
1365
1376
  /** 任意型 state を即時切替 (この phase 到達で value 上書き、 lerp なし) */
@@ -1370,6 +1381,7 @@ export type PhaseBuilder = {
1370
1381
  type PhaseAcc = {
1371
1382
  activate: string[];
1372
1383
  draw: string[];
1384
+ drawRatio?: number;
1373
1385
  tweens: Array<{ stateId: string; from: number; to: number }>;
1374
1386
  sets: Array<{ stateId: string; value: string | number }>;
1375
1387
  badge?: string;
@@ -1385,6 +1397,10 @@ function makePhaseBuilder(acc: PhaseAcc): PhaseBuilder {
1385
1397
  acc.draw.push(...ids);
1386
1398
  return api;
1387
1399
  },
1400
+ drawRatio(ratio) {
1401
+ acc.drawRatio = ratio;
1402
+ return api;
1403
+ },
1388
1404
  tween(stateId, from, to) {
1389
1405
  acc.tweens.push({ stateId, from, to });
1390
1406
  return api;
@@ -1418,6 +1434,8 @@ export type DiagramBuilder = {
1418
1434
  tone?: Tone;
1419
1435
  side?: Side;
1420
1436
  style?: EdgeStyle;
1437
+ /** 端の形 (#560)、 既定は塗った三角。 線の種類とは別の軸 */
1438
+ head?: EdgeHead;
1421
1439
  /** FSM guard 条件、 text-dsl v0.5 inline option (`guard: "..."`) を CdlEdge.guard へ透過 */
1422
1440
  guard?: string;
1423
1441
  /** ER 関係 cardinality、 text-dsl v0.5 inline option (`cardinality: "1:N"`) を CdlEdge.cardinality へ透過 */
@@ -1520,6 +1538,8 @@ export type DiagramBuilder = {
1520
1538
  tone?: Tone;
1521
1539
  side?: Side;
1522
1540
  style?: EdgeStyle;
1541
+ /** 端の形 (#560)、 既定は塗った三角 */
1542
+ head?: EdgeHead;
1523
1543
  guard?: string;
1524
1544
  cardinality?: string;
1525
1545
  labelOffsetX?: number;
@@ -1722,6 +1742,7 @@ export function diagram(
1722
1742
  tone: opts.tone ?? "accent",
1723
1743
  side: opts.side,
1724
1744
  style: opts.style,
1745
+ head: opts.head,
1725
1746
  guard: opts.guard,
1726
1747
  cardinality: opts.cardinality,
1727
1748
  labelOffsetX: opts.labelOffsetX,
@@ -1852,6 +1873,7 @@ export function diagram(
1852
1873
  tone: d.tone ?? "accent",
1853
1874
  side: d.side,
1854
1875
  style: d.style,
1876
+ head: d.head,
1855
1877
  guard: d.guard,
1856
1878
  cardinality: d.cardinality,
1857
1879
  labelOffsetX: d.labelOffsetX,
@@ -3710,6 +3732,8 @@ export function diagram(
3710
3732
  activate: acc.activate,
3711
3733
  // 空なら欄ごと置かない。 置くと `draw` を使わない図の JSON の形が変わる
3712
3734
  ...(acc.draw.length > 0 ? { draw: acc.draw } : {}),
3735
+ // 書かない phase では欄ごと出さない = 従来の図の JSON が 1 byte も変わらない
3736
+ ...(acc.drawRatio === undefined ? {} : { drawRatio: acc.drawRatio }),
3713
3737
  tweens: acc.tweens,
3714
3738
  sets: acc.sets,
3715
3739
  badge: acc.badge,
package/src/index.ts CHANGED
@@ -272,6 +272,7 @@ export type {
272
272
  CdlNode,
273
273
  CdlPhase,
274
274
  CdlState,
275
+ EdgeHead,
275
276
  EdgeStyle,
276
277
  LaidDiagram,
277
278
  LaidEdge,
@@ -285,7 +286,7 @@ export type {
285
286
  // 手書きせずに済ませるため公開する。
286
287
  // 値としての tone / kind 一覧。 downstream (dragon の記法一覧 / parser 等) が
287
288
  // 受理できる値の集合を手書きせずに済ませるため公開する。
288
- export { TONES, NODE_KINDS } from "./types";
289
+ export { TONES, NODE_KINDS, EDGE_HEADS, EDGE_HEAD_DEFAULT } from "./types";
289
290
 
290
291
  /**
291
292
  * 段ごとの実効値を出す 2 つ。 downstream が「この段で箱に何の字が出るか」 を
@@ -0,0 +1,241 @@
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
+
7
+ /**
8
+ * chart-gauge kind ... 上半分の弧で内訳を示し、 合計を主役に置く図 (cdl#549)。
9
+ *
10
+ * ## 何のための種別か
11
+ *
12
+ * `chart-pie` は **内訳の比較** が主役で、 合計を置ける場所が中央しか無い。 「達成率」
13
+ * 「進捗」 「使用量」 のように **1 つの合計を大きく見せ、 内訳は補助で添える** 形が書けなかった。
14
+ *
15
+ * 円で代用すると 2 つ困る。 合計を大きく取れないことと、 円が縦に高さを使うため横長の枠では
16
+ * 上下が余ることである。
17
+ *
18
+ * ## 上半分だけを使う
19
+ *
20
+ * 弧が浅くなるぶん横に広がるので、 横長の枠と相性が良い。 空いた下半分に内訳を横並びで置く =
21
+ * 円のように名札を四方へ散らさずに済み、 件数が増えても行が揃う。
22
+ *
23
+ * 弧は 9 時から 3 時へ、 上を通って時計回りに進む。 左から右へ読む向きに合わせている。
24
+ *
25
+ * ## 割合ではなく実値も出す
26
+ *
27
+ * 内訳には実値と割合の両方を置く。 合計が主役の図で内訳が割合しか持たないと、 「合計の何割」
28
+ * は分かっても「いくつ」 が分からず、 合計との関係を読み手が暗算することになる。
29
+ */
30
+ export function ChartGaugeNode({
31
+ node,
32
+ active,
33
+ stateValues = {},
34
+ }: {
35
+ node: LaidNode;
36
+ active: boolean;
37
+ stateValues?: Record<string, string>;
38
+ }): JSX.Element {
39
+ const x0 = node.cx - node.w / 2;
40
+ const y0 = node.cy - node.h / 2;
41
+ const data = resolveChartData(node.chartData ?? [], stateValues);
42
+ const total = data.reduce((acc, d) => acc + d.value, 0) || 1;
43
+
44
+ const PAD_TOP = 20;
45
+ const PAD_BOTTOM = 20;
46
+ const PAD_SIDE = 20;
47
+ const 使える高さ = node.h - PAD_TOP - PAD_BOTTOM;
48
+ const 使える幅 = node.w - PAD_SIDE * 2;
49
+
50
+ const 名前の級 = 14;
51
+ const 数値の級 = 13;
52
+ /** 内訳の 1 件が縦に使う高さ (印 + 名前 + 数値) */
53
+ const 内訳の高さ = 56;
54
+ /** 弧と内訳の間 */
55
+ const 弧と内訳の間 = 16;
56
+
57
+ /**
58
+ * 弧の外半径。
59
+ *
60
+ * 上半分だけなので **高さは半径 1 つぶん**、 幅は 2 つぶん要る。 内訳の段を先に取り分け、
61
+ * 残りに収める。 極端に狭い枠では弧が消えるため下限を置く。
62
+ */
63
+ const 弧に使える高さ = 使える高さ - 内訳の高さ - 弧と内訳の間;
64
+ const radius = Math.max(24, Math.min(弧に使える高さ, 使える幅 / 2));
65
+ /** 中央を抜く半径。 合計を置く広さを残しつつ、 弧が細くなりすぎない比 */
66
+ const 内半径 = radius * 0.62;
67
+
68
+ const cx = node.w / 2;
69
+ /** 弧の中心。 上半分だけを描くので、 中心は弧の下端に来る */
70
+ const cy = PAD_TOP + radius;
71
+
72
+ let cumFrac = 0;
73
+ const slices = data.map((d) => {
74
+ const frac = d.value / total;
75
+ // 9 時 (π) から 3 時 (2π) へ、 上を通って進む
76
+ const a0 = Math.PI + cumFrac * Math.PI;
77
+ const a1 = a0 + frac * Math.PI;
78
+ cumFrac += frac;
79
+ const 外1 = 極(cx, cy, radius, a0);
80
+ const 外2 = 極(cx, cy, radius, a1);
81
+ const 内2 = 極(cx, cy, 内半径, a1);
82
+ const 内1 = 極(cx, cy, 内半径, a0);
83
+ // 半円の中では 1 つの扇が半周を超えないため、 大きい弧の指定は常に 0
84
+ const path =
85
+ `M ${外1.x} ${外1.y} A ${radius} ${radius} 0 0 1 ${外2.x} ${外2.y} ` +
86
+ `L ${内2.x} ${内2.y} A ${内半径} ${内半径} 0 0 0 ${内1.x} ${内1.y} Z`;
87
+ return { d, frac, path };
88
+ });
89
+
90
+ /**
91
+ * 合計の字の級。
92
+ *
93
+ * **内側の空きに収める**。 弧の内側は半径 `内半径` の半円で、中央に置ける横幅は
94
+ * `内半径 * 2`、縦は `内半径` しかない。 桁数で必要な幅が変わるため、
95
+ * 実際の文字列を `textWidth` で測って逆算する。
96
+ *
97
+ * 縦は見出し (「合計」) の行も入るので、2 行ぶんが半円の高さに収まる比で切る。
98
+ */
99
+ const 合計の文字 = 実値表記(Math.round(total));
100
+ const 横の上限 = 内半径 * 2 * 0.86;
101
+ const 級の候補 = Math.min(
102
+ // 横 = 実際の字幅から逆算する
103
+ (横の上限 / Math.max(textWidth(合計の文字, 100), 1)) * 100,
104
+ // 縦 = 合計 1 行 + 見出し 1 行が内側の高さに収まる比
105
+ 内半径 * 0.52,
106
+ );
107
+ const 合計の級 = Math.max(11, Math.round(級の候補));
108
+ const 見出しの級 = Math.max(9, Math.round(合計の級 * 0.38));
109
+
110
+ /** 内訳 1 件あたりの幅。 等分して左から並べる */
111
+ const 内訳の幅 = 使える幅 / Math.max(data.length, 1);
112
+ const 内訳のy = cy + 弧と内訳の間;
113
+
114
+ return (
115
+ <g transform={`translate(${x0} ${y0})`}>
116
+ <rect
117
+ data-cdl-role="node-body"
118
+ x={0}
119
+ y={0}
120
+ width={node.w}
121
+ height={node.h}
122
+ rx={16}
123
+ fill="var(--cdl-node-fill, #ffffff)"
124
+ stroke={active ? "var(--cdl-tone-accent, #2d6a8f)" : "var(--cdl-divider, #d4d4d8)"}
125
+ strokeWidth={active ? 3 : 1.25}
126
+ />
127
+
128
+ {slices.map((s, idx) => (
129
+ <path
130
+ key={`arc-${idx}`}
131
+ data-cdl-role="chart-gauge-arc"
132
+ data-cdl-unresolved={s.d.unresolved ? "true" : undefined}
133
+ d={s.path}
134
+ fill={arcColor(s.d.tone, idx)}
135
+ // 区切りは地の色の余白で取る。 線だと弧が分断されて 1 つの量に見えない
136
+ stroke="var(--cdl-node-fill, #ffffff)"
137
+ strokeWidth={3}
138
+ strokeLinejoin="round"
139
+ />
140
+ ))}
141
+
142
+ {/*
143
+ 合計。 この種別の主役なので図の中でいちばん大きい字にするが、 **弧の内側に収める**。
144
+
145
+ 級を半径の比だけで決めると、桁数が増えた時に横へはみ出す (実測 = 5 桁で弧の外へ出て
146
+ 下段の内訳に重なった)。 内側の空きの幅と高さの両方から上限を出し、小さい方を採る。
147
+ */}
148
+ <text
149
+ x={cx}
150
+ y={cy - 合計の級 * 0.5}
151
+ textAnchor="middle"
152
+ fontSize={合計の級}
153
+ fontWeight={600}
154
+ fill="var(--cdl-text, #1a1f2a)"
155
+ >
156
+ {合計の文字}
157
+ </text>
158
+ <text
159
+ x={cx}
160
+ y={cy - 合計の級 * 0.5 + 見出しの級 * 1.5}
161
+ textAnchor="middle"
162
+ fontSize={見出しの級}
163
+ fill="var(--cdl-text-dim, #5a6270)"
164
+ >
165
+ 合計
166
+ </text>
167
+
168
+ {/*
169
+ 内訳の段。 1 件 = 印 + 名前 + 数値 の縦組みで、 左から等分に並べる。
170
+
171
+ 字は区画の左端に揃える。 中央揃えにすると名前の長さで位置が動き、 縦の列が揃わない。
172
+ */}
173
+ {data.map((d, idx) => {
174
+ const x = PAD_SIDE + 内訳の幅 * idx;
175
+ const 数値 = `${実値表記(d.value)} ${割合表記(d.value / total)}`;
176
+ return (
177
+ <g key={`legend-${idx}`} data-cdl-role="chart-gauge-item">
178
+ <rect x={x} y={内訳のy} width={30} height={4} rx={2} fill={arcColor(d.tone, idx)} />
179
+ <text
180
+ x={x}
181
+ y={内訳のy + 26}
182
+ fontSize={名前の級}
183
+ fontWeight={700}
184
+ fill="var(--cdl-text, #1a1f2a)"
185
+ >
186
+ {切り詰める(d.label ?? "", 内訳の幅 - 12, 名前の級)}
187
+ </text>
188
+ <text x={x} y={内訳のy + 46} fontSize={数値の級} fill="var(--cdl-text-dim, #5a6270)">
189
+ {数値}
190
+ </text>
191
+ </g>
192
+ );
193
+ })}
194
+ </g>
195
+ );
196
+ }
197
+
198
+ /** 中心から角度と距離で点を出す */
199
+ function 極(cx: number, cy: number, r: number, a: number): { x: number; y: number } {
200
+ return { x: cx + r * Math.cos(a), y: cy + r * Math.sin(a) };
201
+ }
202
+
203
+ /**
204
+ * 区画に収まらない名前を末尾で切る。
205
+ *
206
+ * 切らないと隣の区画の字に重なる = どちらの件の名前かが読めなくなる。 切ったことが分かるよう
207
+ * `…` を付ける。
208
+ */
209
+ function 切り詰める(s: string, 上限: number, 級: number): string {
210
+ if (上限 <= 0 || textWidth(s, 級) <= 上限) return s;
211
+ const 記号幅 = textWidth("…", 級);
212
+ let out = "";
213
+ for (const c of s) {
214
+ if (textWidth(out + c, 級) + 記号幅 > 上限) break;
215
+ out += c;
216
+ }
217
+ return `${out}…`;
218
+ }
219
+
220
+ /**
221
+ * 実値の表記。 桁区切りを入れる (`chart-pie` と揃える)。
222
+ *
223
+ * 小数は 1 桁までに丸める。 図に置く数なので、 桁を尽くすより並びが揃う方が読みやすい。
224
+ */
225
+ function 実値表記(v: number): string {
226
+ return Number.isInteger(v)
227
+ ? v.toLocaleString("en-US")
228
+ : v.toLocaleString("en-US", { maximumFractionDigits: 1 });
229
+ }
230
+
231
+ /** 取り分の表記。 整数のとき小数を出さない (`chart-pie` と揃える) */
232
+ function 割合表記(frac: number): string {
233
+ const v = Math.round(frac * 1000) / 10;
234
+ return `${Number.isInteger(v) ? v : v.toFixed(1)}%`;
235
+ }
236
+
237
+ /** 弧の色。 明示した色が優先され、 無ければ図表の系列の色を順に配る */
238
+ function arcColor(tone: Tone | undefined, idx: number): string {
239
+ if (tone) return TONE[tone];
240
+ return chartSeriesColor(idx);
241
+ }