@cardenelabs/cdl 0.122.0 → 0.123.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 (64) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/README.md +64 -0
  3. package/dist/index.cjs +1065 -617
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +38 -26
  6. package/dist/index.d.ts +38 -26
  7. package/dist/index.js +1065 -618
  8. package/dist/index.js.map +1 -1
  9. package/dist/react.cjs +834 -403
  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 +834 -403
  14. package/dist/react.js.map +1 -1
  15. package/dist/{render-U_pC-Ksh.d.cts → render-CVczwfRb.d.cts} +20 -2
  16. package/dist/{render-U_pC-Ksh.d.ts → render-CVczwfRb.d.ts} +20 -2
  17. package/package.json +9 -12
  18. package/src/builder.ts +5 -0
  19. package/src/index.ts +2 -0
  20. package/src/kinds/box-lines.ts +17 -4
  21. package/src/kinds/chart-bar.tsx +4 -0
  22. package/src/kinds/chart-gauge.tsx +20 -9
  23. package/src/kinds/chart-pie.tsx +67 -17
  24. package/src/kinds/chart-radial.tsx +7 -2
  25. package/src/kinds/chart-slope.tsx +26 -7
  26. package/src/kinds/chart-stacked-bar.tsx +8 -4
  27. package/src/kinds/chart-stat.tsx +19 -4
  28. package/src/kinds/chart-waffle.tsx +7 -2
  29. package/src/kinds/funnel.tsx +9 -2
  30. package/src/kinds/generic.tsx +5 -2
  31. package/src/kinds/mind-map.tsx +26 -10
  32. package/src/kinds/quadrant.tsx +14 -4
  33. package/src/kinds/row-align.ts +19 -7
  34. package/src/kinds/sequence-board-metrics.ts +13 -3
  35. package/src/kinds/sequence-board.tsx +4 -2
  36. package/src/kinds/shape-basement.tsx +57 -32
  37. package/src/kinds/shape-blockchain.tsx +83 -34
  38. package/src/kinds/shape-commerce.tsx +65 -24
  39. package/src/kinds/shape-finance.tsx +90 -26
  40. package/src/kinds/shape-hardware.tsx +48 -17
  41. package/src/kinds/shape-region.tsx +16 -6
  42. package/src/kinds/shape-software.tsx +30 -8
  43. package/src/kinds/text-width.ts +17 -3
  44. package/src/kinds/tree.tsx +25 -7
  45. package/src/layout/collisions.ts +21 -8
  46. package/src/layout/curve-path.ts +3 -2
  47. package/src/layout/edges.ts +70 -25
  48. package/src/layout/head-label.ts +3 -1
  49. package/src/layout/lanes.ts +3 -2
  50. package/src/layout/metro-path.ts +3 -2
  51. package/src/layout/nodes.ts +14 -6
  52. package/src/layout/predict-bbox.ts +6 -3
  53. package/src/layout/self-loop.ts +10 -4
  54. package/src/layout/spec.ts +20 -6
  55. package/src/layout/text-width.ts +25 -8
  56. package/src/layout.ts +39 -15
  57. package/src/presets.ts +69 -43
  58. package/src/render/edges.tsx +18 -3
  59. package/src/render/interactive-panel.tsx +34 -7
  60. package/src/render/nodes.tsx +97 -48
  61. package/src/render/stage.tsx +3 -0
  62. package/src/text-metrics.ts +9 -0
  63. package/src/types.ts +19 -0
  64. package/src/visual-validate.ts +67 -17
@@ -88,7 +88,7 @@ type SequenceBoardMetrics = {
88
88
  * 幅は **語の桁 + 列 × 面の数**。 語を線の脇ではなく左の桁に置くので、線の長さが語の長さに
89
89
  * 引きずられない = 面の数だけで横幅が決まる。
90
90
  */
91
- declare function sequenceBoardMetrics(data: SequenceBoardData): SequenceBoardMetrics;
91
+ declare function sequenceBoardMetrics(data: SequenceBoardData, textMetrics?: CdlTextMetrics): SequenceBoardMetrics;
92
92
 
93
93
  /**
94
94
  * 描画側が書く固定の字の言語 (#847)。
@@ -117,6 +117,14 @@ declare function resolveLang(value: unknown): CdlLang;
117
117
  /** 意味付きの色の名前。 検査・描画・test はこの 1 つの列を出所にする。 */
118
118
  declare const TONES: readonly ["accent", "teal", "success", "error", "warning", "info"];
119
119
  type Tone = (typeof TONES)[number];
120
+ /**
121
+ * 図の字幅をどの規則で測るか。`"proportional"` は現在の Inter 比例幅表、`"monospace"` は
122
+ * 等幅書体向けの幅を使う。既定は `"proportional"`。
123
+ *
124
+ * 消費側が図を等幅書体で描く場合、比例幅で配置すると英数字の描画幅と箱・札の計算幅が
125
+ * 食い違うため、図単位で測り方を揃えられるようにする。
126
+ */
127
+ type CdlTextMetrics = "proportional" | "monospace";
120
128
  /** 描画できる箱の種類。 検査・記法一覧・型はこの 1 つの列を出所にする。 */
121
129
  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", "station", "sequence-board"];
122
130
  type NodeKind = (typeof NODE_KINDS)[number];
@@ -2623,6 +2631,14 @@ type CdlDiagram = {
2623
2631
  * 型の外の値は `layout` が既定に落とす (記法から文字列で届く場合の書き損じ)。
2624
2632
  */
2625
2633
  lang?: CdlLang;
2634
+ /**
2635
+ * 図の字幅を測る規則。`"proportional"` または `"monospace"` を取り、既定は
2636
+ * `"proportional"`。
2637
+ *
2638
+ * 消費側が等幅書体で描く図では、比例幅の配置計算と実際の描画幅が食い違うため指定する。
2639
+ * 型の外の値は `layout` が既定へ落とす。
2640
+ */
2641
+ textMetrics?: CdlTextMetrics;
2626
2642
  /**
2627
2643
  * この図がどの種類か (#645)。 書かない図は種類を持たない。
2628
2644
  *
@@ -2848,6 +2864,8 @@ type LaidDiagram = {
2848
2864
  * 無い時は `"en"` で描く。
2849
2865
  */
2850
2866
  lang?: CdlLang;
2867
+ /** 等幅で測る図だけが持つ。無い時は `"proportional"` */
2868
+ textMetrics?: "monospace";
2851
2869
  /** どの種類の図か (#645)。 `CdlDiagram.type` をそのまま持ち越す */
2852
2870
  type?: string;
2853
2871
  viewBox: {
@@ -3050,4 +3068,4 @@ type CdlDiagramViewProps = {
3050
3068
  };
3051
3069
  declare function CdlDiagramView({ diagram, laid: laidProp, hideHeader, hideMiniPhaseIndicator, focusPhaseId, debug, emitGeometryWarn, headingLevel, interactiveHandlers }: CdlDiagramViewProps): JSX.Element;
3052
3070
 
3053
- export { TONES as $, type ChartSlopeForm as A, type BBox as B, type CdlDiagram as C, EDGE_HEADS as D, type EdgeStyle as E, EDGE_HEAD_DEFAULT as F, EDGE_HEAD_FILLS as G, EDGE_HEAD_FILL_DEFAULT as H, EDGE_REVEALS as I, type JourneyEmotion as J, EDGE_REVEAL_DEFAULT as K, type LaidDiagram as L, EDGE_STYLES as M, type NodeKind as N, type EdgeReveal as O, type InteractiveHandlerMap as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, type InteractiveSignalsAccessor as U, type LaidLane as V, NODE_KINDS as W, RELATION_FOCUSES as X, RELATION_FOCUS_DEFAULT as Y, type RelationFocus as Z, type ScrollProgressHandle as _, type CdlInput as a, computeEventBindingKey as a0, computeFormulaKey as a1, computeInputSignalKey as a2, computeScrollTriggerKey as a3, computed as a4, createInteractiveSignalsAccessor as a5, createScrollProgressHandle as a6, createScrollProgressSignals as a7, hasInteractivePrimitives as a8, resolveLang as a9, sequenceBoardMetrics as aa, sequenceStepId as ab, signal as ac, type CdlLane as b, type CdlNode as c, type Side as d, type EdgeRole as e, type EdgeHead as f, type EdgeHeadFill as g, type EdgeRouting as h, type CdlState as i, type CdlInputOption as j, type CdlEventTarget as k, type CdlLang as l, type Computed as m, type CdlDerivedValue as n, type CdlScrollTrigger as o, type CdlDiagramViewProps as p, type LaidEdge as q, type LaidNode as r, type CdlEdge as s, type TreeNodePayload as t, CdlDiagramView as u, type CdlEventBinding as v, type CdlEventKind as w, type CdlFormula as x, type CdlPhase as y, type ChartPieForm as z };
3071
+ export { type ScrollProgressHandle as $, type ChartPieForm as A, type BBox as B, type CdlDiagram as C, type ChartSlopeForm as D, type EdgeStyle as E, EDGE_HEADS as F, EDGE_HEAD_DEFAULT as G, EDGE_HEAD_FILLS as H, EDGE_HEAD_FILL_DEFAULT as I, type JourneyEmotion as J, EDGE_REVEALS as K, type LaidDiagram as L, EDGE_REVEAL_DEFAULT as M, type NodeKind as N, EDGE_STYLES as O, type EdgeReveal as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, type InteractiveHandlerMap as U, type InteractiveSignalsAccessor as V, type LaidLane as W, NODE_KINDS as X, RELATION_FOCUSES as Y, RELATION_FOCUS_DEFAULT as Z, type RelationFocus as _, type CdlInput as a, TONES as a0, computeEventBindingKey as a1, computeFormulaKey as a2, computeInputSignalKey as a3, computeScrollTriggerKey as a4, computed as a5, createInteractiveSignalsAccessor as a6, createScrollProgressHandle as a7, createScrollProgressSignals as a8, hasInteractivePrimitives as a9, resolveLang as aa, sequenceBoardMetrics as ab, sequenceStepId as ac, signal as ad, type CdlLane as b, type CdlNode as c, type Side as d, type EdgeRole as e, type EdgeHead as f, type EdgeHeadFill as g, type EdgeRouting as h, type CdlState as i, type CdlInputOption as j, type CdlEventTarget as k, type CdlLang as l, type CdlTextMetrics as m, type Computed as n, type CdlDerivedValue as o, type CdlScrollTrigger as p, type CdlDiagramViewProps as q, type LaidEdge as r, type LaidNode as s, type CdlEdge as t, type TreeNodePayload as u, CdlDiagramView as v, type CdlEventBinding as w, type CdlEventKind as x, type CdlFormula as y, type CdlPhase as z };
@@ -88,7 +88,7 @@ type SequenceBoardMetrics = {
88
88
  * 幅は **語の桁 + 列 × 面の数**。 語を線の脇ではなく左の桁に置くので、線の長さが語の長さに
89
89
  * 引きずられない = 面の数だけで横幅が決まる。
90
90
  */
91
- declare function sequenceBoardMetrics(data: SequenceBoardData): SequenceBoardMetrics;
91
+ declare function sequenceBoardMetrics(data: SequenceBoardData, textMetrics?: CdlTextMetrics): SequenceBoardMetrics;
92
92
 
93
93
  /**
94
94
  * 描画側が書く固定の字の言語 (#847)。
@@ -117,6 +117,14 @@ declare function resolveLang(value: unknown): CdlLang;
117
117
  /** 意味付きの色の名前。 検査・描画・test はこの 1 つの列を出所にする。 */
118
118
  declare const TONES: readonly ["accent", "teal", "success", "error", "warning", "info"];
119
119
  type Tone = (typeof TONES)[number];
120
+ /**
121
+ * 図の字幅をどの規則で測るか。`"proportional"` は現在の Inter 比例幅表、`"monospace"` は
122
+ * 等幅書体向けの幅を使う。既定は `"proportional"`。
123
+ *
124
+ * 消費側が図を等幅書体で描く場合、比例幅で配置すると英数字の描画幅と箱・札の計算幅が
125
+ * 食い違うため、図単位で測り方を揃えられるようにする。
126
+ */
127
+ type CdlTextMetrics = "proportional" | "monospace";
120
128
  /** 描画できる箱の種類。 検査・記法一覧・型はこの 1 つの列を出所にする。 */
121
129
  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", "station", "sequence-board"];
122
130
  type NodeKind = (typeof NODE_KINDS)[number];
@@ -2623,6 +2631,14 @@ type CdlDiagram = {
2623
2631
  * 型の外の値は `layout` が既定に落とす (記法から文字列で届く場合の書き損じ)。
2624
2632
  */
2625
2633
  lang?: CdlLang;
2634
+ /**
2635
+ * 図の字幅を測る規則。`"proportional"` または `"monospace"` を取り、既定は
2636
+ * `"proportional"`。
2637
+ *
2638
+ * 消費側が等幅書体で描く図では、比例幅の配置計算と実際の描画幅が食い違うため指定する。
2639
+ * 型の外の値は `layout` が既定へ落とす。
2640
+ */
2641
+ textMetrics?: CdlTextMetrics;
2626
2642
  /**
2627
2643
  * この図がどの種類か (#645)。 書かない図は種類を持たない。
2628
2644
  *
@@ -2848,6 +2864,8 @@ type LaidDiagram = {
2848
2864
  * 無い時は `"en"` で描く。
2849
2865
  */
2850
2866
  lang?: CdlLang;
2867
+ /** 等幅で測る図だけが持つ。無い時は `"proportional"` */
2868
+ textMetrics?: "monospace";
2851
2869
  /** どの種類の図か (#645)。 `CdlDiagram.type` をそのまま持ち越す */
2852
2870
  type?: string;
2853
2871
  viewBox: {
@@ -3050,4 +3068,4 @@ type CdlDiagramViewProps = {
3050
3068
  };
3051
3069
  declare function CdlDiagramView({ diagram, laid: laidProp, hideHeader, hideMiniPhaseIndicator, focusPhaseId, debug, emitGeometryWarn, headingLevel, interactiveHandlers }: CdlDiagramViewProps): JSX.Element;
3052
3070
 
3053
- export { TONES as $, type ChartSlopeForm as A, type BBox as B, type CdlDiagram as C, EDGE_HEADS as D, type EdgeStyle as E, EDGE_HEAD_DEFAULT as F, EDGE_HEAD_FILLS as G, EDGE_HEAD_FILL_DEFAULT as H, EDGE_REVEALS as I, type JourneyEmotion as J, EDGE_REVEAL_DEFAULT as K, type LaidDiagram as L, EDGE_STYLES as M, type NodeKind as N, type EdgeReveal as O, type InteractiveHandlerMap as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, type InteractiveSignalsAccessor as U, type LaidLane as V, NODE_KINDS as W, RELATION_FOCUSES as X, RELATION_FOCUS_DEFAULT as Y, type RelationFocus as Z, type ScrollProgressHandle as _, type CdlInput as a, computeEventBindingKey as a0, computeFormulaKey as a1, computeInputSignalKey as a2, computeScrollTriggerKey as a3, computed as a4, createInteractiveSignalsAccessor as a5, createScrollProgressHandle as a6, createScrollProgressSignals as a7, hasInteractivePrimitives as a8, resolveLang as a9, sequenceBoardMetrics as aa, sequenceStepId as ab, signal as ac, type CdlLane as b, type CdlNode as c, type Side as d, type EdgeRole as e, type EdgeHead as f, type EdgeHeadFill as g, type EdgeRouting as h, type CdlState as i, type CdlInputOption as j, type CdlEventTarget as k, type CdlLang as l, type Computed as m, type CdlDerivedValue as n, type CdlScrollTrigger as o, type CdlDiagramViewProps as p, type LaidEdge as q, type LaidNode as r, type CdlEdge as s, type TreeNodePayload as t, CdlDiagramView as u, type CdlEventBinding as v, type CdlEventKind as w, type CdlFormula as x, type CdlPhase as y, type ChartPieForm as z };
3071
+ export { type ScrollProgressHandle as $, type ChartPieForm as A, type BBox as B, type CdlDiagram as C, type ChartSlopeForm as D, type EdgeStyle as E, EDGE_HEADS as F, EDGE_HEAD_DEFAULT as G, EDGE_HEAD_FILLS as H, EDGE_HEAD_FILL_DEFAULT as I, type JourneyEmotion as J, EDGE_REVEALS as K, type LaidDiagram as L, EDGE_REVEAL_DEFAULT as M, type NodeKind as N, EDGE_STYLES as O, type EdgeReveal as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, type InteractiveHandlerMap as U, type InteractiveSignalsAccessor as V, type LaidLane as W, NODE_KINDS as X, RELATION_FOCUSES as Y, RELATION_FOCUS_DEFAULT as Z, type RelationFocus as _, type CdlInput as a, TONES as a0, computeEventBindingKey as a1, computeFormulaKey as a2, computeInputSignalKey as a3, computeScrollTriggerKey as a4, computed as a5, createInteractiveSignalsAccessor as a6, createScrollProgressHandle as a7, createScrollProgressSignals as a8, hasInteractivePrimitives as a9, resolveLang as aa, sequenceBoardMetrics as ab, sequenceStepId as ac, signal as ad, type CdlLane as b, type CdlNode as c, type Side as d, type EdgeRole as e, type EdgeHead as f, type EdgeHeadFill as g, type EdgeRouting as h, type CdlState as i, type CdlInputOption as j, type CdlEventTarget as k, type CdlLang as l, type CdlTextMetrics as m, type Computed as n, type CdlDerivedValue as o, type CdlScrollTrigger as p, type CdlDiagramViewProps as q, type LaidEdge as r, type LaidNode as s, type CdlEdge as t, type TreeNodePayload as u, CdlDiagramView as v, type CdlEventBinding as w, type CdlEventKind as x, type CdlFormula as y, type CdlPhase as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardenelabs/cdl",
3
- "version": "0.122.0",
3
+ "version": "0.123.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",
@@ -57,16 +57,6 @@
57
57
  "node": ">=18"
58
58
  },
59
59
  "sideEffects": false,
60
- "scripts": {
61
- "build": "tsup",
62
- "dev": "tsup --watch",
63
- "typecheck": "tsc --noEmit",
64
- "test": "cd ../.. && vitest run packages/cdl",
65
- "lint": "tsc --noEmit",
66
- "prepublishOnly": "pnpm run typecheck && pnpm run build",
67
- "prepack": "node scripts/prepack-check.mjs",
68
- "postpack": "node scripts/prepack-check.mjs"
69
- },
70
60
  "peerDependencies": {
71
61
  "react": "^19.0.0",
72
62
  "react-dom": "^19.0.0"
@@ -78,5 +68,12 @@
78
68
  "typedoc": "^0.28.19",
79
69
  "typedoc-plugin-markdown": "^4.12.0",
80
70
  "typescript": "^5.8.0"
71
+ },
72
+ "scripts": {
73
+ "build": "tsup",
74
+ "dev": "tsup --watch",
75
+ "typecheck": "tsc --noEmit",
76
+ "test": "cd ../.. && vitest run packages/cdl",
77
+ "lint": "tsc --noEmit"
81
78
  }
82
- }
79
+ }
package/src/builder.ts CHANGED
@@ -15,6 +15,7 @@ import type {
15
15
  CdlReadout,
16
16
  CdlScrollTrigger,
17
17
  CdlState,
18
+ CdlTextMetrics,
18
19
  EdgeHead,
19
20
  EdgeHeadFill,
20
21
  EdgeRouting,
@@ -1663,6 +1664,8 @@ export function diagram(
1663
1664
  * 描画側が書く固定の字の言語 (#847)。 詳しくは `CdlDiagram.lang` の説明を参照。
1664
1665
  */
1665
1666
  lang?: CdlLang;
1667
+ /** 図の字幅を測る規則。詳しくは `CdlDiagram.textMetrics` の説明を参照。 */
1668
+ textMetrics?: CdlTextMetrics;
1666
1669
  /**
1667
1670
  * この図がどの種類か (#645)。 詳しくは `CdlDiagram.type` の説明を参照。
1668
1671
  */
@@ -3818,6 +3821,8 @@ export function diagram(
3818
3821
  ...(options.palette !== undefined ? { palette: options.palette } : {}),
3819
3822
  // 言語も素通しする。 型の外の値を既定に落とすのは `layout` の 1 箇所に置く
3820
3823
  ...(options.lang !== undefined ? { lang: options.lang } : {}),
3824
+ // 字幅の測り方も素通しする。型の外の値を既定に落とすのは `layout` の 1 箇所に置く
3825
+ ...(options.textMetrics !== undefined ? { textMetrics: options.textMetrics } : {}),
3821
3826
  // 種類の名前も同じ経路で渡し、空文字の扱いを `layout` の 1 箇所に揃える
3822
3827
  ...(options.type !== undefined ? { type: options.type } : {}),
3823
3828
  // inputs / formulas / scrollTriggers / eventBindings は空なら省略 (static diagram の JSON 形状維持)
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ export type { CdlInput, CdlInputOption } from "./types";
15
15
  // 字を直すたびに利用側が壊れる
16
16
  export { resolveLang } from "./lang";
17
17
  export type { CdlLang } from "./lang";
18
+ export { resolveTextMetrics } from "./text-metrics";
18
19
 
19
20
  // formula primitive (CAR #244) — builder chain `.formula(id, "gasBase * 2")` で safe subset の
20
21
  // algebraic expression を追加、 createFormulaComputeds(diagram, signals) で computed を得て
@@ -309,6 +310,7 @@ export type {
309
310
  CdlNode,
310
311
  CdlPhase,
311
312
  CdlState,
313
+ CdlTextMetrics,
312
314
  EdgeHead,
313
315
  EdgeHeadFill,
314
316
  EdgeReveal,
@@ -1,3 +1,4 @@
1
+ import type { CdlTextMetrics } from "../types";
1
2
  import { textWidth } from "./text-width";
2
3
  import { MIN_TEXT_SIZE } from "./compact-title";
3
4
 
@@ -50,6 +51,13 @@ export type 積んだ行 = 積む行 & {
50
51
  dy: number;
51
52
  };
52
53
 
54
+ /** tree の箱を描画と検査で同じ字幅規則にそろえる。 */
55
+ export function 箱の字を測る(
56
+ textMetrics: CdlTextMetrics = "proportional",
57
+ ): (text: string, fontSize: number) => number {
58
+ return (text, fontSize) => textWidth(text, fontSize, 0, textMetrics);
59
+ }
60
+
53
61
  /**
54
62
  * 幅に収まるところまでで切り、 切った時は末尾に `…` を付ける。
55
63
  *
@@ -63,7 +71,7 @@ export function 幅で切る(
63
71
  text: string,
64
72
  fontSize: number,
65
73
  使える幅: number,
66
- 測る: (s: string, size: number) => number = textWidth,
74
+ 測る: (s: string, size: number) => number,
67
75
  ): string {
68
76
  if (使える幅 <= 0) return "";
69
77
  if (測る(text, fontSize) <= 使える幅) return text;
@@ -112,7 +120,7 @@ export function 幅に収める(
112
120
  text: string,
113
121
  fontSize: number,
114
122
  使える幅: number,
115
- 測る: (s: string, size: number) => number = textWidth,
123
+ 測る: (s: string, size: number) => number,
116
124
  ): { text: string; fontSize: number } {
117
125
  let size = fontSize;
118
126
  while (size > 縮めの下限 && 測る(text, size) > 使える幅) size -= 1;
@@ -143,7 +151,12 @@ function 書記素に分ける(text: string): string[] {
143
151
  *
144
152
  * 返す `dy` は箱の中心から見たずれで、 行の塊を上下の中央に置いた時の baseline になる。
145
153
  */
146
- export function 箱に積む(行: 積む行[], 箱の幅: number, 箱の高さ: number): 積んだ行[] {
154
+ export function 箱に積む(
155
+ 行: 積む行[],
156
+ 箱の幅: number,
157
+ 箱の高さ: number,
158
+ 測る: (s: string, size: number) => number,
159
+ ): 積んだ行[] {
147
160
  const 候補 = 行.filter((r, i) => i === 0 || r.text.trim() !== "");
148
161
  if (候補.length === 0) return [];
149
162
 
@@ -159,7 +172,7 @@ export function 箱に積む(行: 積む行[], 箱の幅: number, 箱の高さ:
159
172
  const 幅 = 使える幅(箱の幅);
160
173
  // **幅に収めるのを先に済ませる** (#534)。 縮めると行の高さも変わるため、後から縮めると
161
174
  // `dy` が縮める前の大きさで出て、行の塊が箱の上下の中央からずれる
162
- const 収めた = 残す.map((r) => ({ ...r, ...幅に収める(r.text, r.fontSize, 幅) }));
175
+ const 収めた = 残す.map((r) => ({ ...r, ...幅に収める(r.text, r.fontSize, 幅, 測る) }));
163
176
 
164
177
  const 総高 = 高さ(収めた);
165
178
  let 積み = -総高 / 2;
@@ -120,6 +120,9 @@ export function ChartBarNode({
120
120
  * 4 本に 4 色を配ると「色に意味がある」 と誤読する余地が残る。
121
121
  */
122
122
  const 濃さ = (v: number): number => (vMax > 0 ? (v >= vMax ? 1 : 0.45) : 0.9);
123
+ /** `data-cdl-emphasis` はいちばん大きい棒すべてに付ける。 `濃さ` が 1 の棒と同じ条件で、
124
+ * `chart-bar-look.test.tsx` の `(#1008)` が両方を同じ並びで見張る。 */
125
+ const 主役の棒か = (v: number): boolean => vMax > 0 && v >= vMax;
123
126
  /** 角丸は棒の太さに合わせる。 太い棒に固定値を当てると直角に見える */
124
127
  const 角丸 = Math.min(barW * 0.06, 8);
125
128
 
@@ -190,6 +193,7 @@ export function ChartBarNode({
190
193
  <rect
191
194
  data-cdl-role="chart-bar"
192
195
  data-cdl-unresolved={d.unresolved ? "true" : undefined}
196
+ data-cdl-emphasis={主役の棒か(d.value) ? "primary" : undefined}
193
197
  x={bx}
194
198
  y={by}
195
199
  width={barW}
@@ -1,5 +1,5 @@
1
1
  import { useId, type JSX } from "react";
2
- import type { LaidNode, Tone } from "../types";
2
+ import type { CdlTextMetrics, LaidNode, Tone } from "../types";
3
3
  import { TONE, chartSeriesColor } from "../render/tone";
4
4
  import { resolveChartData } from "../render/payload-binding";
5
5
  import { textWidth } from "./text-width";
@@ -44,6 +44,7 @@ export function ChartGaugeNode({
44
44
  stateValues = {},
45
45
  drawing = false,
46
46
  progress = 1,
47
+ textMetrics = "proportional",
47
48
  }: {
48
49
  node: LaidNode;
49
50
  active: boolean;
@@ -52,6 +53,7 @@ export function ChartGaugeNode({
52
53
  drawing?: boolean;
53
54
  /** phase の進み (0→1)。 `drawing` が true の時だけ読む */
54
55
  progress?: number;
56
+ textMetrics?: CdlTextMetrics;
55
57
  }): JSX.Element {
56
58
  const x0 = node.cx - node.w / 2;
57
59
  const y0 = node.cy - node.h / 2;
@@ -99,7 +101,11 @@ export function ChartGaugeNode({
99
101
  * 防ぐ下限として置いている。 万一この関係が崩れても印は区画に合わせて縮む (下の `Math.min`)。
100
102
  */
101
103
  const 割り付ける = (件たち: typeof data): { 行の件数: number; 段数: number } => {
102
- const 幅 = Math.max(印の幅, ...件たち.map((d) => textWidth(数値の行(d), 数値の級))) + 区画の間;
104
+ const 幅 =
105
+ Math.max(
106
+ 印の幅,
107
+ ...件たち.map((d) => textWidth(数値の行(d), 数値の級, 0, textMetrics)),
108
+ ) + 区画の間;
103
109
  const 行の件数 = Math.min(
104
110
  Math.max(1, Math.floor(使える幅 / Math.max(幅, 1))),
105
111
  Math.max(件たち.length, 1),
@@ -256,7 +262,7 @@ export function ChartGaugeNode({
256
262
  */
257
263
  const 合計の文字 = 実値表記(Math.round(total));
258
264
  /** 級 1 あたりの字幅 */
259
- const 単位幅 = Math.max(textWidth(合計の文字, 100), 1) / 100;
265
+ const 単位幅 = Math.max(textWidth(合計の文字, 100, 0, textMetrics), 1) / 100;
260
266
  const 級の候補 = Math.min(
261
267
  // 横 = 字の上の角が内側の円に入る大きさ
262
268
  内半径 / Math.sqrt((単位幅 / 2) ** 2 + 1.25 ** 2),
@@ -395,7 +401,7 @@ export function ChartGaugeNode({
395
401
  * 切れ目が値の途中に来た行だけ色が付かない形になる。
396
402
  */
397
403
  const 数の行 = 内訳の数値[idx] ?? "";
398
- const 切った数の行 = 切り詰める(数の行, 収まる幅, 数値の級);
404
+ const 切った数の行 = 切り詰める(数の行, 収まる幅, 数値の級, textMetrics);
399
405
  return (
400
406
  <g key={`legend-${idx}`} data-cdl-role="chart-gauge-item">
401
407
  <rect
@@ -413,7 +419,7 @@ export function ChartGaugeNode({
413
419
  fontWeight={700}
414
420
  fill="var(--cdl-text, #1a1f2a)"
415
421
  >
416
- {切り詰める(d.label ?? "", 収まる幅, 名前の級)}
422
+ {切り詰める(d.label ?? "", 収まる幅, 名前の級, textMetrics)}
417
423
  </text>
418
424
  {/* **数値も切り詰める** = 切らないと隣の区画の字とくっつく (cdl#731) */}
419
425
  <text x={x} y={y + 46} fontSize={数値の級} fill="var(--cdl-text-dim, #5a6270)">
@@ -445,12 +451,17 @@ function 極(cx: number, cy: number, r: number, a: number): { x: number; y: numb
445
451
  * 切らないと隣の区画の字に重なる = どちらの件の名前かが読めなくなる。 切ったことが分かるよう
446
452
  * `…` を付ける。
447
453
  */
448
- function 切り詰める(s: string, 上限: number, 級: number): string {
449
- if (上限 <= 0 || textWidth(s, 級) <= 上限) return s;
450
- const 記号幅 = textWidth("…", 級);
454
+ function 切り詰める(
455
+ s: string,
456
+ 上限: number,
457
+ 級: number,
458
+ textMetrics: CdlTextMetrics = "proportional",
459
+ ): string {
460
+ if (上限 <= 0 || textWidth(s, 級, 0, textMetrics) <= 上限) return s;
461
+ const 記号幅 = textWidth("…", 級, 0, textMetrics);
451
462
  let out = "";
452
463
  for (const c of s) {
453
- if (textWidth(out + c, 級) + 記号幅 > 上限) break;
464
+ if (textWidth(out + c, 級, 0, textMetrics) + 記号幅 > 上限) break;
454
465
  out += c;
455
466
  }
456
467
  return `${out}…`;
@@ -1,5 +1,5 @@
1
1
  import { useId, type JSX } from "react";
2
- import type { ChartPieForm, LaidNode, Tone } from "../types";
2
+ import type { CdlTextMetrics, ChartPieForm, LaidNode, Tone } from "../types";
3
3
  import { TONE, chartSeriesColor } from "../render/tone";
4
4
  import { resolveChartData, type ResolvedChartDatum } from "../render/payload-binding";
5
5
  import { 進みを畳む } from "./draw-progress";
@@ -17,8 +17,14 @@ const 穴に対する上限 = 0.8;
17
17
  * 下限まで戻し、**穴に入らない時だけ下限を割る** = 入らない大きさで描くと数字が輪へ
18
18
  * 重なる (#891)。
19
19
  */
20
- function 中央の字(比例した大きさ: number, 穴の半径: number, 文: string): number {
21
- const 入る大きさ = (穴の半径 * 2 * 穴に対する上限) / Math.max(textWidth(文, 1), 1e-6);
20
+ function 中央の字(
21
+ 比例した大きさ: number,
22
+ 穴の半径: number,
23
+ 文: string,
24
+ textMetrics: CdlTextMetrics = "proportional",
25
+ ): number {
26
+ const 入る大きさ =
27
+ (穴の半径 * 2 * 穴に対する上限) / Math.max(textWidth(文, 1, 0, textMetrics), 1e-6);
22
28
  return Math.min(Math.max(比例した大きさ, 縮んだ絵の字の下限), 入る大きさ);
23
29
  }
24
30
  import { 実値表記, 割合表記 } from "./chart-number";
@@ -70,6 +76,7 @@ export function ChartPieNode({
70
76
  stateValues = {},
71
77
  drawing = false,
72
78
  progress = 1,
79
+ textMetrics = "proportional",
73
80
  }: {
74
81
  node: LaidNode;
75
82
  active: boolean;
@@ -78,6 +85,7 @@ export function ChartPieNode({
78
85
  drawing?: boolean;
79
86
  /** phase の進み (0→1)。 `drawing` が true の時だけ読む */
80
87
  progress?: number;
88
+ textMetrics?: CdlTextMetrics;
81
89
  }): JSX.Element {
82
90
  // node id は diagram 内だけで一意。 同じ page に複数 diagram がある時も SVG の参照先が
83
91
  // ぶつからないよう、 React が component instance ごとに割り当てる id を使う。
@@ -151,7 +159,18 @@ export function ChartPieNode({
151
159
  });
152
160
 
153
161
  if (形 !== "ring") {
154
- const 共通 = { node, active, data, 割合: 今の割合, x0, y0, drawing, progress, instanceId };
162
+ const 共通 = {
163
+ node,
164
+ active,
165
+ data,
166
+ 割合: 今の割合,
167
+ x0,
168
+ y0,
169
+ drawing,
170
+ progress,
171
+ instanceId,
172
+ textMetrics,
173
+ };
155
174
  return 形 === "arcs" ? 弧の形(共通) : 表の形(共通);
156
175
  }
157
176
 
@@ -165,8 +184,8 @@ export function ChartPieNode({
165
184
  0,
166
185
  ...data.map((_d, i) =>
167
186
  Math.max(
168
- textWidth(割合の文字[i] ?? "", 割合の級),
169
- textWidth(名前の文字[i] ?? "", 名前の級),
187
+ textWidth(割合の文字[i] ?? "", 割合の級, 0, textMetrics),
188
+ textWidth(名前の文字[i] ?? "", 名前の級, 0, textMetrics),
170
189
  ),
171
190
  ),
172
191
  );
@@ -203,8 +222,8 @@ export function ChartPieNode({
203
222
  */
204
223
  const 穴 = 前あり ? 前の内半径 : 外の内半径;
205
224
  const 合計の文 = 実値表記(Math.round(前あり ? 今の合計 : total));
206
- const 合計の字 = 中央の字(Math.round(radius * 0.26), 穴, 合計の文);
207
- const 見出しの字 = 中央の字(Math.round(radius * 0.12), 穴, "合計");
225
+ const 合計の字 = 中央の字(Math.round(radius * 0.26), 穴, 合計の文, textMetrics);
226
+ const 見出しの字 = 中央の字(Math.round(radius * 0.12), 穴, "合計", textMetrics);
208
227
  const 合計の下 = Math.max(Math.round(radius * 0.2), 合計の字 * 0.25 + 見出しの字 * 0.8);
209
228
  const slices = 輪を組む(data, cx, cy, radius, 外の内半径, 今の割合);
210
229
 
@@ -450,6 +469,7 @@ type 形の引数 = {
450
469
  drawing: boolean;
451
470
  progress: number;
452
471
  instanceId: string;
472
+ textMetrics: CdlTextMetrics;
453
473
  };
454
474
 
455
475
  /** 3 つの見せ方が共通で持つ枠 (cdl#684) */
@@ -480,7 +500,17 @@ function 枠(node: LaidNode, active: boolean): JSX.Element {
480
500
  *
481
501
  * 中心に合計は置かない。 弧は合計を形で示さないので、置くと形と数が食い違う。
482
502
  */
483
- function 弧の形({ node, active, data, 割合, x0, y0, drawing, progress }: 形の引数): JSX.Element {
503
+ function 弧の形({
504
+ node,
505
+ active,
506
+ data,
507
+ 割合,
508
+ x0,
509
+ y0,
510
+ drawing,
511
+ progress,
512
+ textMetrics,
513
+ }: 形の引数): JSX.Element {
484
514
  const PAD = 20;
485
515
  const 名前の級 = 14;
486
516
  const 割合の級 = 24;
@@ -491,9 +521,9 @@ function 弧の形({ node, active, data, 割合, x0, y0, drawing, progress }:
491
521
  const 一覧幅 =
492
522
  10 +
493
523
  印から名前まで +
494
- Math.max(0, ...data.map((d) => textWidth(d.label ?? "", 名前の級))) +
524
+ Math.max(0, ...data.map((d) => textWidth(d.label ?? "", 名前の級, 0, textMetrics))) +
495
525
  名前から割合まで +
496
- Math.max(0, ...割合の文字.map((t) => textWidth(t, 割合の級)));
526
+ Math.max(0, ...割合の文字.map((t) => textWidth(t, 割合の級, 0, textMetrics)));
497
527
 
498
528
  const 縦から = (node.h - PAD * 2) / 2;
499
529
  const 横から = (node.w - PAD * 2 - 一覧幅 - 28) / 2;
@@ -596,7 +626,18 @@ function 弧の形({ node, active, data, 割合, x0, y0, drawing, progress }:
596
626
  * 引き出し線を引かない = 対応は表の色の印が担う。 線と表を両方置くと、同じ対応を
597
627
  * 2 つの仕掛けで示すことになる。
598
628
  */
599
- function 表の形({ node, active, data, 割合, x0, y0, drawing, progress, instanceId }: 形の引数): JSX.Element {
629
+ function 表の形({
630
+ node,
631
+ active,
632
+ data,
633
+ 割合,
634
+ x0,
635
+ y0,
636
+ drawing,
637
+ progress,
638
+ instanceId,
639
+ textMetrics,
640
+ }: 形の引数): JSX.Element {
600
641
  const PAD = 20;
601
642
  const 見出しの級 = 11;
602
643
  const 名前の級 = 15;
@@ -606,9 +647,18 @@ function 表の形({ node, active, data, 割合, x0, y0, drawing, progress, inst
606
647
 
607
648
  const 割合の文字 = 割合.map(割合表記);
608
649
  const 実数の文字 = data.map((d) => 実値表記(d.value));
609
- const 名前幅 = Math.max(0, ...data.map((d) => textWidth(d.label ?? "", 名前の級)));
610
- const 割合幅 = Math.max(textWidth("割合", 見出しの級), ...割合の文字.map((t) => textWidth(t, 割合の級)));
611
- const 実数幅 = Math.max(textWidth("値", 見出しの級), ...実数の文字.map((t) => textWidth(t, 実数の級)));
650
+ const 名前幅 = Math.max(
651
+ 0,
652
+ ...data.map((d) => textWidth(d.label ?? "", 名前の級, 0, textMetrics)),
653
+ );
654
+ const 割合幅 = Math.max(
655
+ textWidth("割合", 見出しの級, 0, textMetrics),
656
+ ...割合の文字.map((t) => textWidth(t, 割合の級, 0, textMetrics)),
657
+ );
658
+ const 実数幅 = Math.max(
659
+ textWidth("値", 見出しの級, 0, textMetrics),
660
+ ...実数の文字.map((t) => textWidth(t, 実数の級, 0, textMetrics)),
661
+ );
612
662
  const 表幅 = 10 + 印から名前まで + 名前幅 + 28 + 割合幅 + 24 + 実数幅;
613
663
 
614
664
  const 縦から = (node.h - PAD * 2) / 2;
@@ -619,8 +669,8 @@ function 表の形({ node, active, data, 割合, x0, y0, drawing, progress, inst
619
669
  const cy = node.h / 2;
620
670
  // 中央の 2 行。 輪の layout と同じ決め方 (#891)
621
671
  const 合計の文 = 実値表記(Math.round(data.reduce((acc, d) => acc + d.value, 0)));
622
- const 合計の字 = 中央の字(Math.round(R * 0.26), 内半径, 合計の文);
623
- const 見出しの字 = 中央の字(Math.round(R * 0.12), 内半径, "合計");
672
+ const 合計の字 = 中央の字(Math.round(R * 0.26), 内半径, 合計の文, textMetrics);
673
+ const 見出しの字 = 中央の字(Math.round(R * 0.12), 内半径, "合計", textMetrics);
624
674
  const 合計の下 = Math.max(Math.round(R * 0.2), 合計の字 * 0.25 + 見出しの字 * 0.8);
625
675
 
626
676
  const 表左 = node.w - PAD - 表幅;
@@ -1,5 +1,5 @@
1
1
  import { useId, type JSX } from "react";
2
- import type { LaidNode, Tone } from "../types";
2
+ import type { CdlTextMetrics, LaidNode, Tone } from "../types";
3
3
  import { TONE, chartSeriesColor } from "../render/tone";
4
4
  import { resolveChartData } from "../render/payload-binding";
5
5
  import { textWidth } from "./text-width";
@@ -45,6 +45,7 @@ export function ChartRadialNode({
45
45
  stateValues = {},
46
46
  drawing = false,
47
47
  progress = 1,
48
+ textMetrics = "proportional",
48
49
  }: {
49
50
  node: LaidNode;
50
51
  active: boolean;
@@ -53,6 +54,7 @@ export function ChartRadialNode({
53
54
  drawing?: boolean;
54
55
  /** phase の進み (0→1)。 `drawing` が true の時だけ読む */
55
56
  progress?: number;
57
+ textMetrics?: CdlTextMetrics;
56
58
  }): JSX.Element {
57
59
  const x0 = node.cx - node.w / 2;
58
60
  const y0 = node.cy - node.h / 2;
@@ -78,7 +80,10 @@ export function ChartRadialNode({
78
80
  Math.max(
79
81
  0,
80
82
  ...data.map((d, i) =>
81
- Math.max(textWidth(d.label ?? "", 名前の級), textWidth(数値の文字[i] ?? "", 数値の級)),
83
+ Math.max(
84
+ textWidth(d.label ?? "", 名前の級, 0, textMetrics),
85
+ textWidth(数値の文字[i] ?? "", 数値の級, 0, textMetrics),
86
+ ),
82
87
  ),
83
88
  );
84
89
 
@@ -1,5 +1,5 @@
1
1
  import { type JSX } from "react";
2
- import type { ChartSlopeForm, LaidNode, Tone } from "../types";
2
+ import type { CdlTextMetrics, ChartSlopeForm, LaidNode, Tone } from "../types";
3
3
  import { TONE, chartSeriesColor } from "../render/tone";
4
4
  import { resolveChartData } from "../render/payload-binding";
5
5
  import { textWidth } from "./text-width";
@@ -99,6 +99,7 @@ export function ChartSlopeNode({
99
99
  stateValues = {},
100
100
  drawing = false,
101
101
  progress = 1,
102
+ textMetrics = "proportional",
102
103
  }: {
103
104
  node: LaidNode;
104
105
  active: boolean;
@@ -106,6 +107,7 @@ export function ChartSlopeNode({
106
107
  /** 段の `draw` にこの node が載っている間だけ true */
107
108
  drawing?: boolean;
108
109
  progress?: number;
110
+ textMetrics?: CdlTextMetrics;
109
111
  }): JSX.Element {
110
112
  const x0 = node.cx - node.w / 2;
111
113
  const y0 = node.cy - node.h / 2;
@@ -193,8 +195,8 @@ export function ChartSlopeNode({
193
195
  const 右の字 = 系列たち.map((s) =>
194
196
  形 === "delta" ? 増減表記(s.前, s.今) : 実値表記(s.今),
195
197
  );
196
- const 左値幅 = Math.max(0, ...左の字.map((t) => textWidth(t, 値の級)));
197
- const 右値幅 = Math.max(0, ...右の字.map((t) => textWidth(t, 値の級)));
198
+ const 左値幅 = Math.max(0, ...左の字.map((t) => textWidth(t, 値の級, 0, textMetrics)));
199
+ const 右値幅 = Math.max(0, ...右の字.map((t) => textWidth(t, 値の級, 0, textMetrics)));
198
200
 
199
201
  /** 名札に回せる横幅。 線の幅を先に取り分けてから、残りを左右へ半分ずつ配る */
200
202
  const 名前に使える = Math.max(
@@ -206,10 +208,16 @@ export function ChartSlopeNode({
206
208
  (右値幅 + 軸から値まで + 値から名前まで)) /
207
209
  2,
208
210
  );
209
- const 名前の級 = 収める級(系列たち.map((s) => s.名前), 名前に使える, 名前の上限級, 名前の下限級);
211
+ const 名前の級 = 収める級(
212
+ 系列たち.map((s) => s.名前),
213
+ 名前に使える,
214
+ 名前の上限級,
215
+ 名前の下限級,
216
+ textMetrics,
217
+ );
210
218
  const 名前幅 = Math.min(
211
219
  名前に使える,
212
- Math.max(0, ...系列たち.map((s) => textWidth(s.名前, 名前の級))),
220
+ Math.max(0, ...系列たち.map((s) => textWidth(s.名前, 名前の級, 0, textMetrics))),
213
221
  );
214
222
 
215
223
  const 左の余白 = 名前幅 + 値から名前まで + 左値幅 + 軸から値まで;
@@ -427,9 +435,20 @@ function 印の色(tone: Tone | undefined, idx: number): string {
427
435
  * 最長の名前が枠に収まるまで 1 ずつ下げ、下限で止める。 下限でも収まらない図では
428
436
  * 名札が枠から出るが、線と軸は箱の内側に残る。
429
437
  */
430
- function 収める級(名前たち: readonly string[], 幅: number, 上限: number, 下限: number): number {
438
+ function 収める級(
439
+ 名前たち: readonly string[],
440
+ 幅: number,
441
+ 上限: number,
442
+ 下限: number,
443
+ textMetrics: CdlTextMetrics = "proportional",
444
+ ): number {
431
445
  if (名前たち.length === 0) return 上限;
432
446
  let 級 = 上限;
433
- while (級 > 下限 && Math.max(...名前たち.map((n) => textWidth(n, 級))) > 幅) 級 -= 1;
447
+ while (
448
+ 級 > 下限 &&
449
+ Math.max(...名前たち.map((n) => textWidth(n, 級, 0, textMetrics))) > 幅
450
+ ) {
451
+ 級 -= 1;
452
+ }
434
453
  return 級;
435
454
  }