@cardenelabs/cdl 0.122.0 → 0.124.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 +28 -1
- package/README.md +70 -1
- package/dist/index.cjs +1125 -618
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -26
- package/dist/index.d.ts +38 -26
- package/dist/index.js +1125 -619
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +894 -404
- 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 +894 -404
- package/dist/react.js.map +1 -1
- package/dist/{render-U_pC-Ksh.d.cts → render-U6omSNH2.d.cts} +23 -4
- package/dist/{render-U_pC-Ksh.d.ts → render-U6omSNH2.d.ts} +23 -4
- package/package.json +9 -12
- package/src/builder.ts +5 -0
- package/src/index.ts +2 -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/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/text-width.ts +17 -3
- package/src/kinds/tree.tsx +25 -7
- package/src/layout/collisions.ts +21 -8
- package/src/layout/curve-path.ts +3 -2
- package/src/layout/edges.ts +173 -29
- package/src/layout/head-label.ts +3 -1
- package/src/layout/lanes.ts +3 -2
- package/src/layout/metro-path.ts +3 -2
- 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 +20 -6
- package/src/layout/text-width.ts +25 -8
- package/src/layout.ts +39 -15
- package/src/presets.ts +69 -43
- package/src/render/edges.tsx +18 -3
- package/src/render/interactive-panel.tsx +34 -7
- package/src/render/nodes.tsx +97 -48
- package/src/render/stage.tsx +3 -0
- package/src/text-metrics.ts +9 -0
- package/src/types.ts +22 -2
- package/src/visual-validate.ts +67 -17
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
帯で切る,
|
|
11
11
|
} from "./shape-region";
|
|
12
12
|
import { MONO_ADVANCE_RATIO, displayWidth } from "./row-align";
|
|
13
|
-
import type { LaidNode } from "../types";
|
|
13
|
+
import type { CdlTextMetrics, LaidNode } from "../types";
|
|
14
14
|
import { 字引きを作る, type CdlLang } from "../lang";
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -29,6 +29,7 @@ type ShapeProps = {
|
|
|
29
29
|
active: boolean;
|
|
30
30
|
/** 描画側の固定の字の言語 (#847)。 渡さない呼び方は英語で描く */
|
|
31
31
|
lang?: CdlLang;
|
|
32
|
+
textMetrics?: CdlTextMetrics;
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
const commonFrame = (active: boolean) => ({
|
|
@@ -82,7 +83,13 @@ function 字の比(c: string): number {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
/** 字の並びの幅の見積り。 字ごとの比を足して、 字間を字数分足す。 */
|
|
85
|
-
function 字幅(
|
|
86
|
+
function 字幅(
|
|
87
|
+
文字列: string,
|
|
88
|
+
大きさ: number,
|
|
89
|
+
字間: number,
|
|
90
|
+
textMetrics: CdlTextMetrics = "proportional",
|
|
91
|
+
): number {
|
|
92
|
+
if (textMetrics === "monospace") return textWidth(文字列, 大きさ, 字間, textMetrics);
|
|
86
93
|
let 幅 = 0;
|
|
87
94
|
let 字数 = 0;
|
|
88
95
|
for (const c of 文字列) {
|
|
@@ -101,28 +108,55 @@ function 字幅(文字列: string, 大きさ: number, 字間: number): number {
|
|
|
101
108
|
*
|
|
102
109
|
* 字間も同じ割合で詰める = 大きさだけ縮めても字間が残ると幅が比例して縮まない。
|
|
103
110
|
*/
|
|
104
|
-
function 幅に収める(
|
|
105
|
-
|
|
111
|
+
function 幅に収める(
|
|
112
|
+
文字列: string,
|
|
113
|
+
大きさ: number,
|
|
114
|
+
字間: number,
|
|
115
|
+
箱幅: number,
|
|
116
|
+
題か = false,
|
|
117
|
+
textMetrics: CdlTextMetrics = "proportional",
|
|
118
|
+
) {
|
|
119
|
+
const 測る = (t: string, s: number, ls: number): number => 字幅(t, s, ls, textMetrics);
|
|
120
|
+
const 幅 = 測る(文字列, 大きさ, 字間);
|
|
106
121
|
// 縮めは読める下限で止める (#953)。 下限が無いと長い名前が点にしか見えない大きさになる。
|
|
107
122
|
// 止めた行は箱に入らないので、入るところまで切って `…` を付ける (#433 の決まりを保つ)。
|
|
108
123
|
//
|
|
109
124
|
// **下限を当てるのは題の行だけ** (#957)。 補いの行に当てると、収まっている行を
|
|
110
125
|
// 大きくしてから切ることになる = 書いた字を落として得るのが 1px に満たない
|
|
111
|
-
const 下限 = 題か ? 切って収まる下限(文字列, 大きさ, 字間, 箱幅,
|
|
126
|
+
const 下限 = 題か ? 切って収まる下限(文字列, 大きさ, 字間, 箱幅, 測る) : 0;
|
|
112
127
|
const 倍率 = 幅 > 0 && 箱幅 > 0 ? Math.min(1, Math.max(箱幅 / 幅, 下限)) : 1;
|
|
113
128
|
const size = 大きさ * 倍率;
|
|
114
129
|
const spacing = 字間 * 倍率;
|
|
115
|
-
return { size, spacing, text: 帯で切る(文字列, size, 箱幅, (u, f) =>
|
|
130
|
+
return { size, spacing, text: 帯で切る(文字列, size, 箱幅, (u, f) => 測る(u, f, spacing)) };
|
|
116
131
|
}
|
|
117
132
|
|
|
118
133
|
/**
|
|
119
134
|
* 名前の塊を描く。 縦の位置と大きさは `labelPlan` が決め (箱の下端から積み上げる、 #433)、
|
|
120
135
|
* 横は `幅に収める` が箱の幅に合わせて縮める。
|
|
121
136
|
*/
|
|
122
|
-
function bottomLabel(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
137
|
+
function bottomLabel(
|
|
138
|
+
node: LaidNode,
|
|
139
|
+
cx: number,
|
|
140
|
+
plan: ReturnType<typeof labelPlan>,
|
|
141
|
+
textMetrics: CdlTextMetrics = "proportional",
|
|
142
|
+
) {
|
|
143
|
+
const 肩 = 幅に収める(
|
|
144
|
+
node.eyebrow ?? "",
|
|
145
|
+
plan.肩書?.size ?? 0,
|
|
146
|
+
LABEL_EYEBROW_SPACING,
|
|
147
|
+
node.w,
|
|
148
|
+
false,
|
|
149
|
+
textMetrics,
|
|
150
|
+
);
|
|
151
|
+
const 名 = 幅に収める(node.title ?? "", plan.名前.size, 0, node.w, true, textMetrics);
|
|
152
|
+
const 説 = 幅に収める(
|
|
153
|
+
node.subtitle ?? "",
|
|
154
|
+
plan.説明?.size ?? 0,
|
|
155
|
+
0,
|
|
156
|
+
node.w,
|
|
157
|
+
false,
|
|
158
|
+
textMetrics,
|
|
159
|
+
);
|
|
126
160
|
return (
|
|
127
161
|
<>
|
|
128
162
|
{node.eyebrow && plan.肩書 && (
|
|
@@ -160,7 +194,7 @@ const WEBSITE_W_MIN = 160;
|
|
|
160
194
|
const WEBSITE_W_MAX = 280;
|
|
161
195
|
|
|
162
196
|
/** shape-website = ブラウザ (シンプル + シンプルレイアウト) */
|
|
163
|
-
export function ShapeWebsiteNode({ node, active }: ShapeProps): JSX.Element {
|
|
197
|
+
export function ShapeWebsiteNode({ node, active, textMetrics = "proportional" }: ShapeProps): JSX.Element {
|
|
164
198
|
const frame = commonFrame(active);
|
|
165
199
|
const r = shapeRegion(node);
|
|
166
200
|
const cx = r.cx;
|
|
@@ -194,13 +228,13 @@ export function ShapeWebsiteNode({ node, active }: ShapeProps): JSX.Element {
|
|
|
194
228
|
return (
|
|
195
229
|
<g>
|
|
196
230
|
{fitArt(r, 広がり, 絵)}
|
|
197
|
-
{bottomLabel(node, r.cx, r.label)}
|
|
231
|
+
{bottomLabel(node, r.cx, r.label, textMetrics)}
|
|
198
232
|
</g>
|
|
199
233
|
);
|
|
200
234
|
}
|
|
201
235
|
|
|
202
236
|
/** shape-storefront = S-A シンプル shop (平屋根 + SHOP 看板 + 大 window (商品) + 中央 door + OPEN sign) */
|
|
203
|
-
export function ShapeStorefrontNode({ node, active, lang }: ShapeProps): JSX.Element {
|
|
237
|
+
export function ShapeStorefrontNode({ node, active, lang, textMetrics = "proportional" }: ShapeProps): JSX.Element {
|
|
204
238
|
const 字引 = 字引きを作る(lang);
|
|
205
239
|
const frame = commonFrame(active);
|
|
206
240
|
const r = shapeRegion(node);
|
|
@@ -258,7 +292,7 @@ export function ShapeStorefrontNode({ node, active, lang }: ShapeProps): JSX.Ele
|
|
|
258
292
|
<circle cx={doorX + 6} cy={doorY + doorH * 0.55} r={1.5} fill="#c15a4a" />
|
|
259
293
|
{/* base 床 (前 storefront 直下のみ) */}
|
|
260
294
|
<rect x={x - 6} y={y + bh - 6} width={bw + 12} height={8} fill="#5a6270" stroke="none" />
|
|
261
|
-
{bottomLabel(node, r.cx, r.label)}
|
|
295
|
+
{bottomLabel(node, r.cx, r.label, textMetrics)}
|
|
262
296
|
</g>
|
|
263
297
|
);
|
|
264
298
|
}
|
|
@@ -286,7 +320,7 @@ const WAREHOUSE_W_MIN = 160;
|
|
|
286
320
|
const WAREHOUSE_W_MAX = 240;
|
|
287
321
|
|
|
288
322
|
/** shape-warehouse = W-A 大 shutter (平屋根 + WAREHOUSE 看板 + 中央大 shutter 10 rib + loading dock) */
|
|
289
|
-
export function ShapeWarehouseNode({ node, active, lang }: ShapeProps): JSX.Element {
|
|
323
|
+
export function ShapeWarehouseNode({ node, active, lang, textMetrics = "proportional" }: ShapeProps): JSX.Element {
|
|
290
324
|
const 字引 = 字引きを作る(lang);
|
|
291
325
|
const frame = commonFrame(active);
|
|
292
326
|
const r = shapeRegion(node);
|
|
@@ -353,7 +387,7 @@ export function ShapeWarehouseNode({ node, active, lang }: ShapeProps): JSX.Elem
|
|
|
353
387
|
return (
|
|
354
388
|
<g>
|
|
355
389
|
{fitArt(r, 広がり, 絵)}
|
|
356
|
-
{bottomLabel(node, r.cx, r.label)}
|
|
390
|
+
{bottomLabel(node, r.cx, r.label, textMetrics)}
|
|
357
391
|
</g>
|
|
358
392
|
);
|
|
359
393
|
}
|
|
@@ -372,7 +406,7 @@ const HANDLE_GRIP_R = 4;
|
|
|
372
406
|
/** 影の楕円が中心から左右へ出る距離。 絵の中で一番右まで来る部品 */
|
|
373
407
|
const CART_SHADOW_RX = 80;
|
|
374
408
|
|
|
375
|
-
export function ShapeOnlineShopNode({ node, active }: ShapeProps): JSX.Element {
|
|
409
|
+
export function ShapeOnlineShopNode({ node, active, textMetrics = "proportional" }: ShapeProps): JSX.Element {
|
|
376
410
|
const frame = commonFrame(active);
|
|
377
411
|
const r = shapeRegion(node);
|
|
378
412
|
const cy = r.cy + 10;
|
|
@@ -445,13 +479,13 @@ export function ShapeOnlineShopNode({ node, active }: ShapeProps): JSX.Element {
|
|
|
445
479
|
<circle cx={wheelR} cy={wheelY} r={16 * s} fill="#0f1830" stroke="#000" strokeWidth={2 * s} />
|
|
446
480
|
<circle cx={wheelR} cy={wheelY} r={8 * s} fill="#5a6270" stroke="#0f1830" strokeWidth={1.5 * s} />
|
|
447
481
|
<circle cx={wheelR} cy={wheelY} r={2 * s} fill="#0f1830" />
|
|
448
|
-
{bottomLabel(node, r.cx, r.label)}
|
|
482
|
+
{bottomLabel(node, r.cx, r.label, textMetrics)}
|
|
449
483
|
</g>
|
|
450
484
|
);
|
|
451
485
|
}
|
|
452
486
|
|
|
453
487
|
/** shape-cdn-edge = 地球儀 (globe + 5 edge dots + pulse ring + lightning at center) */
|
|
454
|
-
export function ShapeCdnEdgeNode({ node, active }: ShapeProps): JSX.Element {
|
|
488
|
+
export function ShapeCdnEdgeNode({ node, active, textMetrics = "proportional" }: ShapeProps): JSX.Element {
|
|
455
489
|
const frame = commonFrame(active);
|
|
456
490
|
const r = shapeRegion(node);
|
|
457
491
|
const cx = r.cx;
|
|
@@ -523,7 +557,7 @@ export function ShapeCdnEdgeNode({ node, active }: ShapeProps): JSX.Element {
|
|
|
523
557
|
return (
|
|
524
558
|
<g>
|
|
525
559
|
{fitArt(r, { 上: 広がり, 下: 広がり, 左: 広がり, 右: 広がり }, 絵)}
|
|
526
|
-
{bottomLabel(node, r.cx, r.label)}
|
|
560
|
+
{bottomLabel(node, r.cx, r.label, textMetrics)}
|
|
527
561
|
</g>
|
|
528
562
|
);
|
|
529
563
|
}
|
|
@@ -553,7 +587,7 @@ const API_W_MIN = 200;
|
|
|
553
587
|
const API_W_MAX = 300;
|
|
554
588
|
|
|
555
589
|
/** shape-api-gateway = SaaS API panel (dark bg + method + path endpoints + status) */
|
|
556
|
-
export function ShapeApiGatewayNode({ node, active, lang }: ShapeProps): JSX.Element {
|
|
590
|
+
export function ShapeApiGatewayNode({ node, active, lang, textMetrics = "proportional" }: ShapeProps): JSX.Element {
|
|
557
591
|
const 字引 = 字引きを作る(lang);
|
|
558
592
|
const frame = commonFrame(active);
|
|
559
593
|
const r = shapeRegion(node);
|
|
@@ -570,7 +604,14 @@ export function ShapeApiGatewayNode({ node, active, lang }: ShapeProps): JSX.Ele
|
|
|
570
604
|
// 11 のまま入り、長い綴り (日本語の辞書の `DELETE`) だけを縮める。 見積りは等幅と比例の大きい方
|
|
571
605
|
// (`shape-blockchain.tsx` の `字の幅` と同じ考え方で、どちらの書体で描かれても箱からはみ出さない)
|
|
572
606
|
const 種類の字 = (s: string): number =>
|
|
573
|
-
Math.min(
|
|
607
|
+
Math.min(
|
|
608
|
+
11,
|
|
609
|
+
(11 * API_METHOD_TEXT_W) /
|
|
610
|
+
Math.max(
|
|
611
|
+
displayWidth(s) * 11 * MONO_ADVANCE_RATIO,
|
|
612
|
+
textWidth(s, 11, 0, textMetrics),
|
|
613
|
+
),
|
|
614
|
+
);
|
|
574
615
|
const rowPitch = Math.max(
|
|
575
616
|
API_ROW_PITCH_MIN,
|
|
576
617
|
(bh - API_ROW_TOP - API_ROW_H - API_ROW_BOTTOM_PAD) / (API_ROWS - 1),
|
|
@@ -595,7 +636,7 @@ export function ShapeApiGatewayNode({ node, active, lang }: ShapeProps): JSX.Ele
|
|
|
595
636
|
帯: { 幅: bw - 32 - 64 - 16, 高さ: API_ROW_H },
|
|
596
637
|
}));
|
|
597
638
|
const 絵 = (倍率: number): JSX.Element => {
|
|
598
|
-
const 倍 = 打ち消しの倍率(倍率,
|
|
639
|
+
const 倍 = 打ち消しの倍率(倍率, 打ち消す字ら, textMetrics);
|
|
599
640
|
return (
|
|
600
641
|
<g>
|
|
601
642
|
<rect x={x + DROP_SHADOW_D} y={y + DROP_SHADOW_D} width={bw} height={bh} rx={10} fill={frame.stroke} opacity={0.2} stroke="none" />
|
|
@@ -625,7 +666,7 @@ export function ShapeApiGatewayNode({ node, active, lang }: ShapeProps): JSX.Ele
|
|
|
625
666
|
return (
|
|
626
667
|
<g>
|
|
627
668
|
{fitArt(r, 広がり, 絵)}
|
|
628
|
-
{bottomLabel(node, r.cx, r.label)}
|
|
669
|
+
{bottomLabel(node, r.cx, r.label, textMetrics)}
|
|
629
670
|
</g>
|
|
630
671
|
);
|
|
631
672
|
}
|
|
@@ -3,12 +3,13 @@ import {
|
|
|
3
3
|
shapeRegion,
|
|
4
4
|
labelPlan,
|
|
5
5
|
fitArt,
|
|
6
|
+
textWidth,
|
|
6
7
|
打ち消しの倍率,
|
|
7
8
|
type 打ち消す字,
|
|
8
9
|
切って収まる下限,
|
|
9
10
|
帯で切る,
|
|
10
11
|
} from "./shape-region";
|
|
11
|
-
import type { LaidNode } from "../types";
|
|
12
|
+
import type { CdlTextMetrics, LaidNode } from "../types";
|
|
12
13
|
import { 字引きを作る, type CdlLang } from "../lang";
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -26,6 +27,7 @@ import { 字引きを作る, type CdlLang } from "../lang";
|
|
|
26
27
|
type ShapeProps = {
|
|
27
28
|
node: LaidNode;
|
|
28
29
|
active: boolean;
|
|
30
|
+
textMetrics?: CdlTextMetrics;
|
|
29
31
|
/** 描画側の固定の字の言語 (#847)。 渡さない呼び方は英語で描く */
|
|
30
32
|
lang?: CdlLang;
|
|
31
33
|
};
|
|
@@ -81,7 +83,13 @@ function 字の比(c: string): number {
|
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
/** 字の並びの幅の見積り。 字ごとの比を足して、 字間を字数分足す。 */
|
|
84
|
-
function 字幅(
|
|
86
|
+
function 字幅(
|
|
87
|
+
文字列: string,
|
|
88
|
+
大きさ: number,
|
|
89
|
+
字間: number,
|
|
90
|
+
textMetrics: CdlTextMetrics = "proportional",
|
|
91
|
+
): number {
|
|
92
|
+
if (textMetrics === "monospace") return textWidth(文字列, 大きさ, 字間, textMetrics);
|
|
85
93
|
let 幅 = 0;
|
|
86
94
|
let 字数 = 0;
|
|
87
95
|
for (const c of 文字列) {
|
|
@@ -100,28 +108,55 @@ function 字幅(文字列: string, 大きさ: number, 字間: number): number {
|
|
|
100
108
|
*
|
|
101
109
|
* 字間も同じ割合で詰める = 大きさだけ縮めても字間が残ると幅が比例して縮まない。
|
|
102
110
|
*/
|
|
103
|
-
function 幅に収める(
|
|
104
|
-
|
|
111
|
+
function 幅に収める(
|
|
112
|
+
文字列: string,
|
|
113
|
+
大きさ: number,
|
|
114
|
+
字間: number,
|
|
115
|
+
箱幅: number,
|
|
116
|
+
題か = false,
|
|
117
|
+
textMetrics: CdlTextMetrics = "proportional",
|
|
118
|
+
) {
|
|
119
|
+
const 測る = (t: string, s: number, ls: number): number => 字幅(t, s, ls, textMetrics);
|
|
120
|
+
const 幅 = 測る(文字列, 大きさ, 字間);
|
|
105
121
|
// 縮めは読める下限で止める (#953)。 下限が無いと長い名前が点にしか見えない大きさになる。
|
|
106
122
|
// 止めた行は箱に入らないので、入るところまで切って `…` を付ける (#433 の決まりを保つ)。
|
|
107
123
|
//
|
|
108
124
|
// **下限を当てるのは題の行だけ** (#957)。 補いの行に当てると、収まっている行を
|
|
109
125
|
// 大きくしてから切ることになる = 書いた字を落として得るのが 1px に満たない
|
|
110
|
-
const 下限 = 題か ? 切って収まる下限(文字列, 大きさ, 字間, 箱幅,
|
|
126
|
+
const 下限 = 題か ? 切って収まる下限(文字列, 大きさ, 字間, 箱幅, 測る) : 0;
|
|
111
127
|
const 倍率 = 幅 > 0 && 箱幅 > 0 ? Math.min(1, Math.max(箱幅 / 幅, 下限)) : 1;
|
|
112
128
|
const size = 大きさ * 倍率;
|
|
113
129
|
const spacing = 字間 * 倍率;
|
|
114
|
-
return { size, spacing, text: 帯で切る(文字列, size, 箱幅, (u, f) =>
|
|
130
|
+
return { size, spacing, text: 帯で切る(文字列, size, 箱幅, (u, f) => 測る(u, f, spacing)) };
|
|
115
131
|
}
|
|
116
132
|
|
|
117
133
|
/**
|
|
118
134
|
* 名前の塊を描く。 縦の位置と大きさは `labelPlan` が決め (箱の下端から積み上げる、 #433)、
|
|
119
135
|
* 横は `幅に収める` が箱の幅に合わせて縮める。
|
|
120
136
|
*/
|
|
121
|
-
function bottomLabel(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
137
|
+
function bottomLabel(
|
|
138
|
+
node: LaidNode,
|
|
139
|
+
cx: number,
|
|
140
|
+
plan: ReturnType<typeof labelPlan>,
|
|
141
|
+
textMetrics: CdlTextMetrics = "proportional",
|
|
142
|
+
) {
|
|
143
|
+
const 肩 = 幅に収める(
|
|
144
|
+
node.eyebrow ?? "",
|
|
145
|
+
plan.肩書?.size ?? 0,
|
|
146
|
+
LABEL_EYEBROW_SPACING,
|
|
147
|
+
node.w,
|
|
148
|
+
false,
|
|
149
|
+
textMetrics,
|
|
150
|
+
);
|
|
151
|
+
const 名 = 幅に収める(node.title ?? "", plan.名前.size, 0, node.w, true, textMetrics);
|
|
152
|
+
const 説 = 幅に収める(
|
|
153
|
+
node.subtitle ?? "",
|
|
154
|
+
plan.説明?.size ?? 0,
|
|
155
|
+
0,
|
|
156
|
+
node.w,
|
|
157
|
+
false,
|
|
158
|
+
textMetrics,
|
|
159
|
+
);
|
|
125
160
|
return (
|
|
126
161
|
<>
|
|
127
162
|
{node.eyebrow && plan.肩書 && (
|
|
@@ -174,7 +209,7 @@ const BANK_BLD_W_MIN = 120;
|
|
|
174
209
|
const BANK_BLD_W_MAX = 280;
|
|
175
210
|
|
|
176
211
|
/** shape-bank = 銀行 (Greek 神殿 pediment + $ + 4 columns + base、 中央配置) */
|
|
177
|
-
export function ShapeBankNode({ node, active }: ShapeProps): JSX.Element {
|
|
212
|
+
export function ShapeBankNode({ node, active, textMetrics = "proportional" }: ShapeProps): JSX.Element {
|
|
178
213
|
const frame = commonFrame(active);
|
|
179
214
|
const r = shapeRegion(node);
|
|
180
215
|
const 線 = frame.strokeWidth / 2;
|
|
@@ -231,7 +266,7 @@ export function ShapeBankNode({ node, active }: ShapeProps): JSX.Element {
|
|
|
231
266
|
return (
|
|
232
267
|
<g>
|
|
233
268
|
{fitArt(r, 広がり, 絵)}
|
|
234
|
-
{bottomLabel(node, r.cx, r.label)}
|
|
269
|
+
{bottomLabel(node, r.cx, r.label, textMetrics)}
|
|
235
270
|
</g>
|
|
236
271
|
);
|
|
237
272
|
}
|
|
@@ -252,7 +287,12 @@ const TRUST_BLD_W_MIN = 140;
|
|
|
252
287
|
const TRUST_BLD_W_MAX = 280;
|
|
253
288
|
|
|
254
289
|
/** shape-trust-bank = 信託銀行 (王冠 + 神殿 3 columns、 中央柱に余計装飾なし) */
|
|
255
|
-
export function ShapeTrustBankNode({
|
|
290
|
+
export function ShapeTrustBankNode({
|
|
291
|
+
node,
|
|
292
|
+
active,
|
|
293
|
+
lang,
|
|
294
|
+
textMetrics = "proportional",
|
|
295
|
+
}: ShapeProps): JSX.Element {
|
|
256
296
|
const 字引 = 字引きを作る(lang);
|
|
257
297
|
const frame = commonFrame(active);
|
|
258
298
|
const r = shapeRegion(node);
|
|
@@ -316,13 +356,18 @@ export function ShapeTrustBankNode({ node, active, lang }: ShapeProps): JSX.Elem
|
|
|
316
356
|
return (
|
|
317
357
|
<g>
|
|
318
358
|
{fitArt(r, 広がり, 絵)}
|
|
319
|
-
{bottomLabel(node, r.cx, r.label)}
|
|
359
|
+
{bottomLabel(node, r.cx, r.label, textMetrics)}
|
|
320
360
|
</g>
|
|
321
361
|
);
|
|
322
362
|
}
|
|
323
363
|
|
|
324
364
|
/** shape-payment-provider = P2-A base (中央 provider box + PAY header + processing spinner + PROCESSING label) */
|
|
325
|
-
export function ShapePaymentProviderNode({
|
|
365
|
+
export function ShapePaymentProviderNode({
|
|
366
|
+
node,
|
|
367
|
+
active,
|
|
368
|
+
lang,
|
|
369
|
+
textMetrics = "proportional",
|
|
370
|
+
}: ShapeProps): JSX.Element {
|
|
326
371
|
const 字引 = 字引きを作る(lang);
|
|
327
372
|
const frame = commonFrame(active);
|
|
328
373
|
const r = shapeRegion(node);
|
|
@@ -355,13 +400,17 @@ export function ShapePaymentProviderNode({ node, active, lang }: ShapeProps): JS
|
|
|
355
400
|
</g>
|
|
356
401
|
{/* PROCESSING label */}
|
|
357
402
|
<text x={cx} y={y + boxH - 16} fontSize={11} fontWeight={700} letterSpacing={2} textAnchor="middle" fill="#a4b5f2">{字引("paymentProvider.status")}</text>
|
|
358
|
-
{bottomLabel(node, r.cx, r.label)}
|
|
403
|
+
{bottomLabel(node, r.cx, r.label, textMetrics)}
|
|
359
404
|
</g>
|
|
360
405
|
);
|
|
361
406
|
}
|
|
362
407
|
|
|
363
408
|
/** shape-credit-card = クレジットカード (シンプル、 chip + NFC + 4 digit block + brand) */
|
|
364
|
-
export function ShapeCreditCardNode({
|
|
409
|
+
export function ShapeCreditCardNode({
|
|
410
|
+
node,
|
|
411
|
+
active,
|
|
412
|
+
textMetrics = "proportional",
|
|
413
|
+
}: ShapeProps): JSX.Element {
|
|
365
414
|
const frame = commonFrame(active);
|
|
366
415
|
const r = shapeRegion(node);
|
|
367
416
|
const cardW = Math.min(r.availW - 30, 260);
|
|
@@ -394,7 +443,7 @@ export function ShapeCreditCardNode({ node, active }: ShapeProps): JSX.Element {
|
|
|
394
443
|
{/* brand mark */}
|
|
395
444
|
<circle cx={x + cardW - 48} cy={y + cardH - 24} r={15} fill="#c9302c" opacity={0.9} stroke="none" />
|
|
396
445
|
<circle cx={x + cardW - 30} cy={y + cardH - 24} r={15} fill="#e5c07b" opacity={0.9} stroke="none" />
|
|
397
|
-
{bottomLabel(node, r.cx, r.label)}
|
|
446
|
+
{bottomLabel(node, r.cx, r.label, textMetrics)}
|
|
398
447
|
</g>
|
|
399
448
|
);
|
|
400
449
|
}
|
|
@@ -447,7 +496,12 @@ const BROKERAGE_W_MAX = 140;
|
|
|
447
496
|
const BROKERAGE_H_MIN = BROKERAGE_TICK_H + BROKERAGE_LOGO_H + BROKERAGE_BASE_H;
|
|
448
497
|
|
|
449
498
|
/** shape-brokerage = B2-A 大和証券風 (縦長本社 + 上部電光 + $ SECURITIES ロゴ帯 + window grid + entrance 回転扉) */
|
|
450
|
-
export function ShapeBrokerageNode({
|
|
499
|
+
export function ShapeBrokerageNode({
|
|
500
|
+
node,
|
|
501
|
+
active,
|
|
502
|
+
lang,
|
|
503
|
+
textMetrics = "proportional",
|
|
504
|
+
}: ShapeProps): JSX.Element {
|
|
451
505
|
const 字引 = 字引きを作る(lang);
|
|
452
506
|
const frame = commonFrame(active);
|
|
453
507
|
const r = shapeRegion(node);
|
|
@@ -494,7 +548,7 @@ export function ShapeBrokerageNode({ node, active, lang }: ShapeProps): JSX.Elem
|
|
|
494
548
|
},
|
|
495
549
|
];
|
|
496
550
|
const 絵 = (倍率: number): JSX.Element => {
|
|
497
|
-
const 倍 = 打ち消しの倍率(倍率,
|
|
551
|
+
const 倍 = 打ち消しの倍率(倍率, 打ち消す字ら, textMetrics);
|
|
498
552
|
return (
|
|
499
553
|
<g>
|
|
500
554
|
{/* drop shadow */}
|
|
@@ -538,7 +592,7 @@ export function ShapeBrokerageNode({ node, active, lang }: ShapeProps): JSX.Elem
|
|
|
538
592
|
return (
|
|
539
593
|
<g>
|
|
540
594
|
{fitArt(r, 広がり, 絵)}
|
|
541
|
-
{bottomLabel(node, r.cx, r.label)}
|
|
595
|
+
{bottomLabel(node, r.cx, r.label, textMetrics)}
|
|
542
596
|
</g>
|
|
543
597
|
);
|
|
544
598
|
}
|
|
@@ -549,7 +603,12 @@ const EXCHANGE_SUB_SIZE = 11;
|
|
|
549
603
|
/** 中央の六角形の外接半径。 看板の字 (指定 11、 字間 1 で幅 83.7) が入る大きさから決めた (#888)。 */
|
|
550
604
|
const EXCHANGE_HEX_R = 52;
|
|
551
605
|
|
|
552
|
-
export function ShapeExchangeNode({
|
|
606
|
+
export function ShapeExchangeNode({
|
|
607
|
+
node,
|
|
608
|
+
active,
|
|
609
|
+
lang,
|
|
610
|
+
textMetrics = "proportional",
|
|
611
|
+
}: ShapeProps): JSX.Element {
|
|
553
612
|
const 字引 = 字引きを作る(lang);
|
|
554
613
|
const frame = commonFrame(active);
|
|
555
614
|
const r = shapeRegion(node);
|
|
@@ -620,7 +679,7 @@ export function ShapeExchangeNode({ node, active, lang }: ShapeProps): JSX.Eleme
|
|
|
620
679
|
},
|
|
621
680
|
];
|
|
622
681
|
const 絵 = (倍率: number): JSX.Element => {
|
|
623
|
-
const 倍 = 打ち消しの倍率(倍率,
|
|
682
|
+
const 倍 = 打ち消しの倍率(倍率, 打ち消す字ら, textMetrics);
|
|
624
683
|
return (
|
|
625
684
|
<g>
|
|
626
685
|
{/* 8 dashed radial lines */}
|
|
@@ -654,7 +713,7 @@ export function ShapeExchangeNode({ node, active, lang }: ShapeProps): JSX.Eleme
|
|
|
654
713
|
return (
|
|
655
714
|
<g>
|
|
656
715
|
{fitArt(r, 広がり, 絵)}
|
|
657
|
-
{bottomLabel(node, r.cx, r.label)}
|
|
716
|
+
{bottomLabel(node, r.cx, r.label, textMetrics)}
|
|
658
717
|
</g>
|
|
659
718
|
);
|
|
660
719
|
}
|
|
@@ -700,7 +759,12 @@ const ATM_W_MAX = ATM_PANEL_W;
|
|
|
700
759
|
const ATM_H_MIN = ATM_SIGN_H + ATM_SIGN_GAP + 30 + ATM_FLOOR_RESERVE;
|
|
701
760
|
|
|
702
761
|
/** shape-atm = A3-C1 (支店内 ATM 単体、 上部 ATM CORNER 天井看板 + 中央 ATM 大 + 直下床帯) */
|
|
703
|
-
export function ShapeAtmNode({
|
|
762
|
+
export function ShapeAtmNode({
|
|
763
|
+
node,
|
|
764
|
+
active,
|
|
765
|
+
lang,
|
|
766
|
+
textMetrics = "proportional",
|
|
767
|
+
}: ShapeProps): JSX.Element {
|
|
704
768
|
const 字引 = 字引きを作る(lang);
|
|
705
769
|
const frame = commonFrame(active);
|
|
706
770
|
const r = shapeRegion(node);
|
|
@@ -800,7 +864,7 @@ export function ShapeAtmNode({ node, active, lang }: ShapeProps): JSX.Element {
|
|
|
800
864
|
return (
|
|
801
865
|
<g>
|
|
802
866
|
{fitArt(r, 広がり, 絵)}
|
|
803
|
-
{bottomLabel(node, r.cx, r.label)}
|
|
867
|
+
{bottomLabel(node, r.cx, r.label, textMetrics)}
|
|
804
868
|
</g>
|
|
805
869
|
);
|
|
806
870
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { JSX } from "react";
|
|
2
2
|
import { shapeRegion, labelPlan, fitArt, textWidth, 切って収まる下限, 帯で切る, type ArtExtent } from "./shape-region";
|
|
3
|
-
import type { LaidNode } from "../types";
|
|
3
|
+
import type { CdlTextMetrics, LaidNode } from "../types";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Shape-driven hardware 6 kinds (CAR-1111 Phase 2-C)。
|
|
@@ -45,6 +45,10 @@ type ShapeProps = {
|
|
|
45
45
|
active: boolean;
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
type TextMeasuredShapeProps = ShapeProps & {
|
|
49
|
+
textMetrics?: CdlTextMetrics;
|
|
50
|
+
};
|
|
51
|
+
|
|
48
52
|
const commonFrame = (active: boolean) => ({
|
|
49
53
|
fill: "var(--cdl-node-fill, #ffffff)",
|
|
50
54
|
stroke: active ? "var(--cdl-tone-accent, #2d6a8f)" : "var(--cdl-divider, #d4d4d8)",
|
|
@@ -104,18 +108,26 @@ const LABEL_EYEBROW_SPACING = 2;
|
|
|
104
108
|
* 字幅の見積りは共有の `textWidth` (実測を階級で丸め上げた表) が持つ。 字間も同じ割合で
|
|
105
109
|
* 詰める = 大きさだけ縮めても字間が残ると幅が比例して縮まない。
|
|
106
110
|
*/
|
|
107
|
-
function 幅に収める(
|
|
108
|
-
|
|
111
|
+
function 幅に収める(
|
|
112
|
+
文字列: string,
|
|
113
|
+
大きさ: number,
|
|
114
|
+
字間: number,
|
|
115
|
+
箱幅: number,
|
|
116
|
+
題か = false,
|
|
117
|
+
textMetrics: CdlTextMetrics = "proportional",
|
|
118
|
+
) {
|
|
119
|
+
const 測る = (t: string, s: number, ls: number): number => textWidth(t, s, ls, textMetrics);
|
|
120
|
+
const 幅 = 測る(文字列, 大きさ, 字間);
|
|
109
121
|
// 縮めは読める下限で止める (#953)。 下限が無いと長い名前が点にしか見えない大きさになる。
|
|
110
122
|
// 止めた行は箱に入らないので、入るところまで切って `…` を付ける (#433 の決まりを保つ)。
|
|
111
123
|
//
|
|
112
124
|
// **下限を当てるのは題の行だけ** (#957)。 補いの行に当てると、収まっている行を
|
|
113
125
|
// 大きくしてから切ることになる = 書いた字を落として得るのが 1px に満たない
|
|
114
|
-
const 下限 = 題か ? 切って収まる下限(文字列, 大きさ, 字間, 箱幅,
|
|
126
|
+
const 下限 = 題か ? 切って収まる下限(文字列, 大きさ, 字間, 箱幅, 測る) : 0;
|
|
115
127
|
const 倍率 = 幅 > 0 && 箱幅 > 0 ? Math.min(1, Math.max(箱幅 / 幅, 下限)) : 1;
|
|
116
128
|
const size = 大きさ * 倍率;
|
|
117
129
|
const spacing = 字間 * 倍率;
|
|
118
|
-
return { size, spacing, text: 帯で切る(文字列, size, 箱幅, (u, f) =>
|
|
130
|
+
return { size, spacing, text: 帯で切る(文字列, size, 箱幅, (u, f) => 測る(u, f, spacing)) };
|
|
119
131
|
}
|
|
120
132
|
|
|
121
133
|
/**
|
|
@@ -124,10 +136,29 @@ function 幅に収める(文字列: string, 大きさ: number, 字間: number,
|
|
|
124
136
|
*
|
|
125
137
|
* `externalLabel` と違い、 **箱の中に必ず収まる**。 字の大きさも箱の高さに追随する。
|
|
126
138
|
*/
|
|
127
|
-
function bottomLabel(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
139
|
+
function bottomLabel(
|
|
140
|
+
node: LaidNode,
|
|
141
|
+
cx: number,
|
|
142
|
+
plan: ReturnType<typeof labelPlan>,
|
|
143
|
+
textMetrics: CdlTextMetrics = "proportional",
|
|
144
|
+
) {
|
|
145
|
+
const 肩 = 幅に収める(
|
|
146
|
+
node.eyebrow ?? "",
|
|
147
|
+
plan.肩書?.size ?? 0,
|
|
148
|
+
LABEL_EYEBROW_SPACING,
|
|
149
|
+
node.w,
|
|
150
|
+
false,
|
|
151
|
+
textMetrics,
|
|
152
|
+
);
|
|
153
|
+
const 名 = 幅に収める(node.title ?? "", plan.名前.size, 0, node.w, true, textMetrics);
|
|
154
|
+
const 説 = 幅に収める(
|
|
155
|
+
node.subtitle ?? "",
|
|
156
|
+
plan.説明?.size ?? 0,
|
|
157
|
+
0,
|
|
158
|
+
node.w,
|
|
159
|
+
false,
|
|
160
|
+
textMetrics,
|
|
161
|
+
);
|
|
131
162
|
return (
|
|
132
163
|
<>
|
|
133
164
|
{node.eyebrow && plan.肩書 && (
|
|
@@ -171,7 +202,7 @@ const RACK_H =
|
|
|
171
202
|
const RACK_W_MIN = 120;
|
|
172
203
|
|
|
173
204
|
/** shape-server-rack = 19 inch rack (top vent + 4 U rack mount server + power indicator) */
|
|
174
|
-
export function ShapeServerRackNode({ node, active }:
|
|
205
|
+
export function ShapeServerRackNode({ node, active, textMetrics = "proportional" }: TextMeasuredShapeProps): JSX.Element {
|
|
175
206
|
const r = shapeRegion(node);
|
|
176
207
|
const frame = commonFrame(active);
|
|
177
208
|
const 線 = frame.strokeWidth / 2;
|
|
@@ -260,7 +291,7 @@ export function ShapeServerRackNode({ node, active }: ShapeProps): JSX.Element {
|
|
|
260
291
|
})}
|
|
261
292
|
</g>
|
|
262
293
|
))}
|
|
263
|
-
{bottomLabel(node, r.cx, r.label)}
|
|
294
|
+
{bottomLabel(node, r.cx, r.label, textMetrics)}
|
|
264
295
|
</g>
|
|
265
296
|
);
|
|
266
297
|
}
|
|
@@ -281,7 +312,7 @@ const WIFI_ARC_R = [10, 16, 22];
|
|
|
281
312
|
const WIFI_BASE_Y = 5;
|
|
282
313
|
|
|
283
314
|
/** shape-network-node = 六角 hub (network signal cone icon + 6-arm radial + node dots) */
|
|
284
|
-
export function ShapeNetworkNodeNode({ node, active }:
|
|
315
|
+
export function ShapeNetworkNodeNode({ node, active, textMetrics = "proportional" }: TextMeasuredShapeProps): JSX.Element {
|
|
285
316
|
const r = shapeRegion(node);
|
|
286
317
|
const cx = r.cx;
|
|
287
318
|
const cy = r.cy;
|
|
@@ -358,7 +389,7 @@ export function ShapeNetworkNodeNode({ node, active }: ShapeProps): JSX.Element
|
|
|
358
389
|
<circle cx={cx} cy={cy + WIFI_BASE_Y + 3} r={2.5} fill="var(--cdl-tone-accent, #2d6a8f)" stroke="none" />
|
|
359
390
|
</g>
|
|
360
391
|
))}
|
|
361
|
-
{bottomLabel(node, r.cx, r.label)}
|
|
392
|
+
{bottomLabel(node, r.cx, r.label, textMetrics)}
|
|
362
393
|
</g>
|
|
363
394
|
);
|
|
364
395
|
}
|
|
@@ -378,7 +409,7 @@ const PHONE_H = 240;
|
|
|
378
409
|
const PHONE_W = 110;
|
|
379
410
|
|
|
380
411
|
/** shape-mobile-device = スマホ (rounded rect + speaker slit + home button) */
|
|
381
|
-
export function ShapeMobileDeviceNode({ node, active }:
|
|
412
|
+
export function ShapeMobileDeviceNode({ node, active, textMetrics = "proportional" }: TextMeasuredShapeProps): JSX.Element {
|
|
382
413
|
const r = shapeRegion(node);
|
|
383
414
|
const frame = commonFrame(active);
|
|
384
415
|
const 線 = frame.strokeWidth / 2;
|
|
@@ -436,7 +467,7 @@ export function ShapeMobileDeviceNode({ node, active }: ShapeProps): JSX.Element
|
|
|
436
467
|
/>
|
|
437
468
|
</g>
|
|
438
469
|
))}
|
|
439
|
-
{bottomLabel(node, r.cx, r.label)}
|
|
470
|
+
{bottomLabel(node, r.cx, r.label, textMetrics)}
|
|
440
471
|
</g>
|
|
441
472
|
);
|
|
442
473
|
}
|
|
@@ -692,7 +723,7 @@ const SAT_MAST_LEN = 22;
|
|
|
692
723
|
const SAT_MAST_TIP_R = 4;
|
|
693
724
|
|
|
694
725
|
/** shape-satellite = 人工衛星 (body + 左右 solar panel + dish antenna + orbit arc) */
|
|
695
|
-
export function ShapeSatelliteNode({ node, active }:
|
|
726
|
+
export function ShapeSatelliteNode({ node, active, textMetrics = "proportional" }: TextMeasuredShapeProps): JSX.Element {
|
|
696
727
|
const r = shapeRegion(node);
|
|
697
728
|
const cx = r.cx;
|
|
698
729
|
const cy = r.cy;
|
|
@@ -864,7 +895,7 @@ export function ShapeSatelliteNode({ node, active }: ShapeProps): JSX.Element {
|
|
|
864
895
|
</circle>
|
|
865
896
|
</g>
|
|
866
897
|
))}
|
|
867
|
-
{bottomLabel(node, r.cx, r.label)}
|
|
898
|
+
{bottomLabel(node, r.cx, r.label, textMetrics)}
|
|
868
899
|
</g>
|
|
869
900
|
);
|
|
870
901
|
}
|