@cardenelabs/cdl 0.121.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.
- package/CHANGELOG.md +51 -1
- package/README.md +137 -0
- package/SPEC.md +1 -1
- package/dist/index.cjs +1513 -667
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -33
- package/dist/index.d.ts +51 -33
- package/dist/index.js +1513 -668
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1271 -444
- 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 +1271 -444
- package/dist/react.js.map +1 -1
- package/dist/{render-A5AKjNuy.d.cts → render-CVczwfRb.d.cts} +40 -7
- package/dist/{render-A5AKjNuy.d.ts → render-CVczwfRb.d.ts} +40 -7
- package/package.json +9 -12
- package/src/builder.ts +8 -2
- package/src/index.ts +3 -0
- package/src/kinds/box-lines.ts +17 -4
- package/src/kinds/chart-bar.tsx +4 -0
- package/src/kinds/chart-gauge.tsx +20 -9
- package/src/kinds/chart-pie.tsx +67 -17
- package/src/kinds/chart-radial.tsx +7 -2
- package/src/kinds/chart-slope.tsx +26 -7
- package/src/kinds/chart-stacked-bar.tsx +8 -4
- package/src/kinds/chart-stat.tsx +19 -4
- package/src/kinds/chart-waffle.tsx +7 -2
- package/src/kinds/dyn-shape.tsx +22 -9
- package/src/kinds/funnel.tsx +9 -2
- package/src/kinds/generic.tsx +5 -2
- package/src/kinds/mind-map.tsx +26 -10
- package/src/kinds/quadrant.tsx +14 -4
- package/src/kinds/row-align.ts +19 -7
- package/src/kinds/sequence-board-metrics.ts +13 -3
- package/src/kinds/sequence-board.tsx +4 -2
- package/src/kinds/shape-basement.tsx +57 -32
- package/src/kinds/shape-blockchain.tsx +83 -34
- package/src/kinds/shape-commerce.tsx +65 -24
- package/src/kinds/shape-finance.tsx +90 -26
- package/src/kinds/shape-hardware.tsx +48 -17
- package/src/kinds/shape-region.tsx +16 -6
- package/src/kinds/shape-software.tsx +30 -8
- package/src/kinds/station.tsx +29 -0
- package/src/kinds/text-width.ts +17 -3
- package/src/kinds/tree.tsx +25 -7
- package/src/layout/collisions.ts +50 -28
- package/src/layout/curve-path.ts +126 -0
- package/src/layout/edges.ts +271 -58
- package/src/layout/head-label.ts +11 -5
- package/src/layout/lanes.ts +3 -2
- package/src/layout/metro-path.ts +185 -0
- package/src/layout/nodes.ts +14 -6
- package/src/layout/predict-bbox.ts +6 -3
- package/src/layout/self-loop.ts +10 -4
- package/src/layout/spec.ts +36 -7
- package/src/layout/text-width.ts +25 -8
- package/src/layout/tokens.ts +2 -0
- package/src/layout-with-validation.ts +10 -5
- package/src/layout.ts +55 -21
- package/src/presets.ts +69 -43
- package/src/render/edges.tsx +19 -3
- package/src/render/interactive-panel.tsx +34 -7
- package/src/render/nodes.tsx +100 -48
- package/src/render/stage.tsx +3 -0
- package/src/text-metrics.ts +9 -0
- package/src/types.ts +41 -4
- package/src/visual-validate.ts +91 -21
package/src/kinds/chart-pie.tsx
CHANGED
|
@@ -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 中央の字(
|
|
21
|
-
|
|
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 共通 = {
|
|
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 弧の形({
|
|
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 表の形({
|
|
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(
|
|
610
|
-
|
|
611
|
-
|
|
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(
|
|
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 名前の級 = 収める級(
|
|
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 収める級(
|
|
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 (
|
|
447
|
+
while (
|
|
448
|
+
級 > 下限 &&
|
|
449
|
+
Math.max(...名前たち.map((n) => textWidth(n, 級, 0, textMetrics))) > 幅
|
|
450
|
+
) {
|
|
451
|
+
級 -= 1;
|
|
452
|
+
}
|
|
434
453
|
return 級;
|
|
435
454
|
}
|
|
@@ -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, type ResolvedChartDatum } from "../render/payload-binding";
|
|
5
5
|
import { textWidth } from "./text-width";
|
|
@@ -51,6 +51,7 @@ export function ChartStackedBarNode({
|
|
|
51
51
|
stateValues = {},
|
|
52
52
|
drawing = false,
|
|
53
53
|
progress = 1,
|
|
54
|
+
textMetrics = "proportional",
|
|
54
55
|
}: {
|
|
55
56
|
node: LaidNode;
|
|
56
57
|
active: boolean;
|
|
@@ -59,6 +60,7 @@ export function ChartStackedBarNode({
|
|
|
59
60
|
drawing?: boolean;
|
|
60
61
|
/** phase の進み (0→1)。 `drawing` が true の時だけ読む */
|
|
61
62
|
progress?: number;
|
|
63
|
+
textMetrics?: CdlTextMetrics;
|
|
62
64
|
}): JSX.Element {
|
|
63
65
|
const x0 = node.cx - node.w / 2;
|
|
64
66
|
const y0 = node.cy - node.h / 2;
|
|
@@ -72,7 +74,8 @@ export function ChartStackedBarNode({
|
|
|
72
74
|
const 時点の級 = 13;
|
|
73
75
|
/** 時点の名前を置く左の幅 */
|
|
74
76
|
const 時点の幅 =
|
|
75
|
-
Math.max(0, ...帯たち.map((b) => textWidth(b.名前,
|
|
77
|
+
Math.max(0, ...帯たち.map((b) => textWidth(b.名前, 時点の級, 0, textMetrics))) +
|
|
78
|
+
(帯たち.length > 1 ? 16 : 0);
|
|
76
79
|
const 帯の左 = PAD + 時点の幅;
|
|
77
80
|
const 帯の幅 = Math.max(40, node.w - 帯の左 - PAD);
|
|
78
81
|
const 帯の高さ = 44;
|
|
@@ -196,7 +199,7 @@ export function ChartStackedBarNode({
|
|
|
196
199
|
})}
|
|
197
200
|
|
|
198
201
|
{/* 一覧。 区画に字が入らなかった件もここで名前と色が引ける */}
|
|
199
|
-
{一覧の並び(data, 帯の左, node.w - PAD,
|
|
202
|
+
{一覧の並び(data, 帯の左, node.w - PAD, 名前の級, textMetrics).map(({ d, idx, x, 行 }) => {
|
|
200
203
|
const y =
|
|
201
204
|
上端 +
|
|
202
205
|
帯たち.length * (帯の高さ + 帯の間) -
|
|
@@ -284,12 +287,13 @@ export function 一覧の並び(
|
|
|
284
287
|
左: number,
|
|
285
288
|
右端: number,
|
|
286
289
|
級: number,
|
|
290
|
+
textMetrics: CdlTextMetrics = "proportional",
|
|
287
291
|
): Array<{ d: ResolvedChartDatum; idx: number; x: number; 行: number }> {
|
|
288
292
|
const out: Array<{ d: ResolvedChartDatum; idx: number; x: number; 行: number }> = [];
|
|
289
293
|
let x = 左;
|
|
290
294
|
let 行 = 0;
|
|
291
295
|
data.forEach((d, idx) => {
|
|
292
|
-
const 幅 = 印から名前まで + textWidth(d.label ?? "",
|
|
296
|
+
const 幅 = 印から名前まで + textWidth(d.label ?? "", 級, 0, textMetrics);
|
|
293
297
|
// 行の先頭では折り返さない = 折り返しても同じ位置に戻るだけで、空の行が増える
|
|
294
298
|
if (x > 左 && x + 幅 > 右端) {
|
|
295
299
|
行 += 1;
|
package/src/kinds/chart-stat.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { 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";
|
|
@@ -35,10 +35,12 @@ export function ChartStatNode({
|
|
|
35
35
|
node,
|
|
36
36
|
active,
|
|
37
37
|
stateValues = {},
|
|
38
|
+
textMetrics = "proportional",
|
|
38
39
|
}: {
|
|
39
40
|
node: LaidNode;
|
|
40
41
|
active: boolean;
|
|
41
42
|
stateValues?: Record<string, string>;
|
|
43
|
+
textMetrics?: CdlTextMetrics;
|
|
42
44
|
}): JSX.Element {
|
|
43
45
|
const x0 = node.cx - node.w / 2;
|
|
44
46
|
const y0 = node.cy - node.h / 2;
|
|
@@ -87,7 +89,13 @@ export function ChartStatNode({
|
|
|
87
89
|
const 色 = 印の色(d.tone, idx);
|
|
88
90
|
const 数値 = 実値表記(d.value);
|
|
89
91
|
// 数値は区画に収まるまで小さくする。 収まらないまま描くと隣の区画へはみ出す
|
|
90
|
-
const 数値の級 = 収める級(
|
|
92
|
+
const 数値の級 = 収める級(
|
|
93
|
+
数値,
|
|
94
|
+
枠.w - 区画.余白 * 2,
|
|
95
|
+
区画.数値の級,
|
|
96
|
+
区画.名前の級 + 1,
|
|
97
|
+
textMetrics,
|
|
98
|
+
);
|
|
91
99
|
return (
|
|
92
100
|
<g
|
|
93
101
|
key={`stat-${idx}`}
|
|
@@ -147,6 +155,7 @@ export function ChartStatNode({
|
|
|
147
155
|
枠.w - 区画.余白 * 2,
|
|
148
156
|
区画.前の級,
|
|
149
157
|
8,
|
|
158
|
+
textMetrics,
|
|
150
159
|
)}
|
|
151
160
|
textAnchor="middle"
|
|
152
161
|
fill="var(--cdl-text-dim, #5a6270)"
|
|
@@ -291,9 +300,15 @@ function 区画の割付(
|
|
|
291
300
|
* 中身 (数値が主役) が崩れるため。 下限でも入らない場合は入らないまま描く = 勝手に字を
|
|
292
301
|
* 落とすと数値が別の値に読める。
|
|
293
302
|
*/
|
|
294
|
-
export function 収める級(
|
|
303
|
+
export function 収める級(
|
|
304
|
+
字: string,
|
|
305
|
+
使える幅: number,
|
|
306
|
+
初期: number,
|
|
307
|
+
下限: number,
|
|
308
|
+
textMetrics: CdlTextMetrics = "proportional",
|
|
309
|
+
): number {
|
|
295
310
|
let 級 = 初期;
|
|
296
|
-
while (級 > 下限 && textWidth(字,
|
|
311
|
+
while (級 > 下限 && textWidth(字, 級, 0, textMetrics) > 使える幅) 級 -= 1;
|
|
297
312
|
return 級;
|
|
298
313
|
}
|
|
299
314
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { 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";
|
|
@@ -84,6 +84,7 @@ export function ChartWaffleNode({
|
|
|
84
84
|
stateValues = {},
|
|
85
85
|
drawing = false,
|
|
86
86
|
progress = 1,
|
|
87
|
+
textMetrics = "proportional",
|
|
87
88
|
}: {
|
|
88
89
|
node: LaidNode;
|
|
89
90
|
active: boolean;
|
|
@@ -92,6 +93,7 @@ export function ChartWaffleNode({
|
|
|
92
93
|
drawing?: boolean;
|
|
93
94
|
/** phase の進み (0→1)。 `drawing` が true の時だけ読む */
|
|
94
95
|
progress?: number;
|
|
96
|
+
textMetrics?: CdlTextMetrics;
|
|
95
97
|
}): JSX.Element {
|
|
96
98
|
const x0 = node.cx - node.w / 2;
|
|
97
99
|
const y0 = node.cy - node.h / 2;
|
|
@@ -116,7 +118,10 @@ export function ChartWaffleNode({
|
|
|
116
118
|
Math.max(
|
|
117
119
|
0,
|
|
118
120
|
...data.map((d, i) =>
|
|
119
|
-
Math.max(
|
|
121
|
+
Math.max(
|
|
122
|
+
textWidth(d.label ?? "", 名前の級, 0, textMetrics),
|
|
123
|
+
textWidth(数値の文字[i] ?? "", 数値の級, 0, textMetrics),
|
|
124
|
+
),
|
|
120
125
|
),
|
|
121
126
|
);
|
|
122
127
|
|
package/src/kinds/dyn-shape.tsx
CHANGED
|
@@ -75,24 +75,37 @@ export function DynShapeNode({
|
|
|
75
75
|
}
|
|
76
76
|
})();
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
const 揃え = 箱いっぱいに描く(shape.kind) ? "start" : "middle";
|
|
79
|
+
return renderShapeWithOutsideLabels(形, node, 揃え);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 名前と値は箱の外に置く (#925)。 中に置くと、内側の塗りが値で動くたびに文字と重なる。
|
|
84
|
+
* 駅は塗りが動かないが、丸が小さく名前が丸より広いので外に置く。
|
|
85
|
+
*
|
|
86
|
+
* **差し込みはここで通さない**。 `render/nodes.tsx` が `{名前}` を解決した節を渡してくるので、
|
|
87
|
+
* ここで通しても何も変わらない (実測 = 通さない形に変えても検査が 1 件も落ちなかった)。
|
|
88
|
+
* しかも向こうは値ごとの呼び名の表を持っており、ここには無い = 二重に通すと、
|
|
89
|
+
* 呼び名を持つ図だけ結果が変わる余地を作る。
|
|
90
|
+
*/
|
|
91
|
+
export function renderShapeWithOutsideLabels(
|
|
92
|
+
形: JSX.Element,
|
|
93
|
+
node: LaidNode,
|
|
94
|
+
揃え: "start" | "middle",
|
|
95
|
+
): JSX.Element {
|
|
96
|
+
const cx = node.cx;
|
|
97
|
+
const y = node.cy - node.h / 2;
|
|
84
98
|
const 名前 = (node.title ?? "").trim();
|
|
85
99
|
const 値 = (node.subtitle ?? "").trim();
|
|
86
100
|
// どちらも書かれていない節では包む `<g>` も足さない = 書かない図の DOM を 1 要素も変えない
|
|
87
101
|
if (名前 === "" && 値 === "") return 形;
|
|
88
|
-
const 揃え = 箱いっぱいに描く(shape.kind) ? "start" : "middle";
|
|
89
102
|
return (
|
|
90
103
|
<g>
|
|
91
104
|
{形}
|
|
92
105
|
{名前 !== "" && (
|
|
93
106
|
<text
|
|
94
107
|
data-cdl-role="shape-name"
|
|
95
|
-
x={揃え === "start" ?
|
|
108
|
+
x={揃え === "start" ? node.cx - node.w / 2 : cx}
|
|
96
109
|
y={y - NAME_GAP}
|
|
97
110
|
textAnchor={揃え}
|
|
98
111
|
fontSize={13}
|
|
@@ -106,7 +119,7 @@ export function DynShapeNode({
|
|
|
106
119
|
<text
|
|
107
120
|
data-cdl-role="shape-value"
|
|
108
121
|
x={cx}
|
|
109
|
-
y={y + h + VALUE_GAP}
|
|
122
|
+
y={y + node.h + VALUE_GAP}
|
|
110
123
|
textAnchor="middle"
|
|
111
124
|
fontSize={12}
|
|
112
125
|
fill="var(--cdl-text-dim, #5a6270)"
|
package/src/kinds/funnel.tsx
CHANGED
|
@@ -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 } from "../render/tone";
|
|
4
4
|
import { resolveFunnelData } from "../render/payload-binding";
|
|
5
5
|
import { 収める倍率 } from "../layout/text-width";
|
|
@@ -18,6 +18,7 @@ export function FunnelNode({
|
|
|
18
18
|
stateValues = {},
|
|
19
19
|
drawing = false,
|
|
20
20
|
progress = 1,
|
|
21
|
+
textMetrics = "proportional",
|
|
21
22
|
}: {
|
|
22
23
|
node: LaidNode;
|
|
23
24
|
active: boolean;
|
|
@@ -26,6 +27,7 @@ export function FunnelNode({
|
|
|
26
27
|
drawing?: boolean;
|
|
27
28
|
/** phase の進み (0→1)。 `drawing` が true の時だけ読む */
|
|
28
29
|
progress?: number;
|
|
30
|
+
textMetrics?: CdlTextMetrics;
|
|
29
31
|
}): JSX.Element {
|
|
30
32
|
const x0 = node.cx - node.w / 2;
|
|
31
33
|
const y0 = node.cy - node.h / 2;
|
|
@@ -109,7 +111,12 @@ export function FunnelNode({
|
|
|
109
111
|
const 補足の枠字 = 補足の字 * 縦倍;
|
|
110
112
|
const 収まる幅 = Math.max(0, bottomW - 16);
|
|
111
113
|
const 補足の実字 =
|
|
112
|
-
補足の枠字 *
|
|
114
|
+
補足の枠字 *
|
|
115
|
+
収める倍率(
|
|
116
|
+
補足,
|
|
117
|
+
{ fontSize: 補足の枠字, fontWeight: 500, textMetrics },
|
|
118
|
+
収まる幅,
|
|
119
|
+
);
|
|
113
120
|
// 2 行の塊を段の中央に積む。 縦の位置は枠の字 (縮める前) で決める = 幅で縮めた分だけ
|
|
114
121
|
// 行が上下に動くと、段ごとに行の高さが揃わない
|
|
115
122
|
const 塊の上 = y + stageH / 2 - (名前の実字 * 1.2 + 補足の枠字 * 1.25) / 2;
|
package/src/kinds/generic.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { 等幅の字 } from "../render/mono-text";
|
|
2
2
|
import type { JSX } from "react";
|
|
3
|
-
import type { LaidNode, NodeKind } from "../types";
|
|
3
|
+
import type { CdlTextMetrics, LaidNode, NodeKind } from "../types";
|
|
4
4
|
import { toneColor } from "./node-tone";
|
|
5
5
|
import { interpolate } from "../render/utils";
|
|
6
6
|
import type { 値の名前の表 } from "../input-option";
|
|
@@ -237,6 +237,7 @@ export function GenericNode({
|
|
|
237
237
|
value,
|
|
238
238
|
stateValues,
|
|
239
239
|
値の名前,
|
|
240
|
+
textMetrics = "proportional",
|
|
240
241
|
}: {
|
|
241
242
|
node: LaidNode;
|
|
242
243
|
active: boolean;
|
|
@@ -244,6 +245,7 @@ export function GenericNode({
|
|
|
244
245
|
stateValues: Record<string, string>;
|
|
245
246
|
/** 入力欄の値ごとの名前 (#855)。 値と行の差し込みに使う */
|
|
246
247
|
値の名前?: 値の名前の表;
|
|
248
|
+
textMetrics?: CdlTextMetrics;
|
|
247
249
|
}): JSX.Element {
|
|
248
250
|
const base = STYLES[node.kind] ?? FALLBACK;
|
|
249
251
|
// `tone` 指定があれば kind の既定色を置き換える。 色を決める式は `node-tone.ts` に集約。
|
|
@@ -458,7 +460,8 @@ export function GenericNode({
|
|
|
458
460
|
// 左列は比例の書体で描く (`font-family` を指定していないので継承する)。 字数に一定を
|
|
459
461
|
// 掛ける形では実物に届かず、 右列が左列の字に重なる (`#425`)。 字ごとの幅で見積もる。
|
|
460
462
|
const leftMaxWidth = splits.reduce(
|
|
461
|
-
(m, s) =>
|
|
463
|
+
(m, s) =>
|
|
464
|
+
Math.max(m, rowColumnWidth(s.left, GENERIC_ROW_LEFT_SIZE_PX, false, textMetrics)),
|
|
462
465
|
0,
|
|
463
466
|
);
|
|
464
467
|
const rightColX = ROW_PAD_X_PX + leftMaxWidth + ROW_COL_GAP_PX;
|