@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.
- package/CHANGELOG.md +16 -1
- package/README.md +64 -0
- package/dist/index.cjs +1065 -617
- 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 +1065 -618
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +834 -403
- 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 +834 -403
- package/dist/react.js.map +1 -1
- package/dist/{render-U_pC-Ksh.d.cts → render-CVczwfRb.d.cts} +20 -2
- package/dist/{render-U_pC-Ksh.d.ts → render-CVczwfRb.d.ts} +20 -2
- 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 +70 -25
- 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 +19 -0
- package/src/visual-validate.ts +67 -17
|
@@ -2,7 +2,8 @@ import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useStat
|
|
|
2
2
|
import type { JSX } from "react";
|
|
3
3
|
import { CDL_TEXT, resolveLang, 字を差し込む, type CdlLang, type CdlTextKey } from "../lang";
|
|
4
4
|
import { assertNever } from "../assert-never";
|
|
5
|
-
import type { CdlDiagram, CdlInput, CdlReadout } from "../types";
|
|
5
|
+
import type { CdlDiagram, CdlInput, CdlReadout, CdlTextMetrics } from "../types";
|
|
6
|
+
import { resolveTextMetrics } from "../text-metrics";
|
|
6
7
|
import { createInputSignals, inputDefaultValue } from "../input-signals";
|
|
7
8
|
import { 選択肢の値, 選択肢の名前 } from "../input-option";
|
|
8
9
|
import { createFormulaComputeds } from "../formula";
|
|
@@ -19,7 +20,7 @@ import type { Signal, Computed } from "../reactive";
|
|
|
19
20
|
import { effect } from "../reactive";
|
|
20
21
|
import { textWidth } from "../kinds/text-width";
|
|
21
22
|
import { 桁で割る, 桁で省く } from "../kinds/row-align";
|
|
22
|
-
import {
|
|
23
|
+
import { 幅で切る, 箱の字を測る } from "../kinds/box-lines";
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* interactive primitive (input widget / formula / scroll trigger / event handler) を SVG diagram に
|
|
@@ -685,7 +686,12 @@ export function useInteractiveIntegration(
|
|
|
685
686
|
{readouts.length > 0 && (
|
|
686
687
|
<div className="cdl-ip-group">
|
|
687
688
|
{readouts.map((r) => (
|
|
688
|
-
<ReadoutWidget
|
|
689
|
+
<ReadoutWidget
|
|
690
|
+
key={r.id}
|
|
691
|
+
readout={r}
|
|
692
|
+
stateOverrides={mergedStateValues}
|
|
693
|
+
textMetrics={resolveTextMetrics(diagram.textMetrics)}
|
|
694
|
+
/>
|
|
689
695
|
))}
|
|
690
696
|
</div>
|
|
691
697
|
)}
|
|
@@ -1173,9 +1179,11 @@ function StepperWidget({
|
|
|
1173
1179
|
function ReadoutWidget({
|
|
1174
1180
|
readout,
|
|
1175
1181
|
stateOverrides,
|
|
1182
|
+
textMetrics = "proportional",
|
|
1176
1183
|
}: {
|
|
1177
1184
|
readout: CdlReadout;
|
|
1178
1185
|
stateOverrides: Record<string, number | string | boolean>;
|
|
1186
|
+
textMetrics?: CdlTextMetrics;
|
|
1179
1187
|
}): JSX.Element {
|
|
1180
1188
|
// stacked-bar は sourceA / sourceB を持ち source を持たない、 その場合は rawValue = undefined 扱い
|
|
1181
1189
|
const rawValue = "source" in readout ? stateOverrides[readout.source] : undefined;
|
|
@@ -1414,6 +1422,7 @@ function ReadoutWidget({
|
|
|
1414
1422
|
viewW={readout.viewW ?? 280}
|
|
1415
1423
|
viewH={readout.viewH ?? 60}
|
|
1416
1424
|
color={readout.color ?? "#2563eb"}
|
|
1425
|
+
textMetrics={textMetrics}
|
|
1417
1426
|
/>
|
|
1418
1427
|
);
|
|
1419
1428
|
case "radar": {
|
|
@@ -2166,6 +2175,7 @@ function ReadoutWidget({
|
|
|
2166
2175
|
{ status: "error", color: "#ef4444" },
|
|
2167
2176
|
]}
|
|
2168
2177
|
max={readout.max ?? 10}
|
|
2178
|
+
textMetrics={textMetrics}
|
|
2169
2179
|
/>
|
|
2170
2180
|
);
|
|
2171
2181
|
case "calendar-week":
|
|
@@ -3559,8 +3569,14 @@ function ProgressGroupReadout({
|
|
|
3559
3569
|
* 札の幅は描く前に分からないので `textWidth` で見積もる。 見積りは実物以上に倒してあるため、
|
|
3560
3570
|
* 収まる札を端に寄せることはあっても、はみ出す札を中央に残すことは無い。
|
|
3561
3571
|
*/
|
|
3562
|
-
function sequenceLabelAnchor(
|
|
3563
|
-
|
|
3572
|
+
function sequenceLabelAnchor(
|
|
3573
|
+
x: number,
|
|
3574
|
+
name: string,
|
|
3575
|
+
fontSize: number,
|
|
3576
|
+
viewW: number,
|
|
3577
|
+
textMetrics: CdlTextMetrics = "proportional",
|
|
3578
|
+
): "start" | "middle" | "end" {
|
|
3579
|
+
const half = textWidth(name, fontSize, 0, textMetrics) / 2;
|
|
3564
3580
|
if (x - half < 0) return "start";
|
|
3565
3581
|
if (x + half > viewW) return "end";
|
|
3566
3582
|
return "middle";
|
|
@@ -3575,6 +3591,7 @@ function SequenceTimelineReadout({
|
|
|
3575
3591
|
viewW,
|
|
3576
3592
|
viewH,
|
|
3577
3593
|
color,
|
|
3594
|
+
textMetrics = "proportional",
|
|
3578
3595
|
}: {
|
|
3579
3596
|
id: string;
|
|
3580
3597
|
label: string;
|
|
@@ -3584,6 +3601,7 @@ function SequenceTimelineReadout({
|
|
|
3584
3601
|
viewW: number;
|
|
3585
3602
|
viewH: number;
|
|
3586
3603
|
color: string;
|
|
3604
|
+
textMetrics?: CdlTextMetrics;
|
|
3587
3605
|
}): JSX.Element {
|
|
3588
3606
|
// event parse: [[t, name], ...] または [t, t, t, ...]
|
|
3589
3607
|
const events: { t: number; name: string }[] = [];
|
|
@@ -3636,7 +3654,13 @@ function SequenceTimelineReadout({
|
|
|
3636
3654
|
<circle cx={x} cy={trackY} r={5} fill={color} stroke="#fff" strokeWidth={1.5}>
|
|
3637
3655
|
<字の見出し k="readout.sequenceTimeline.title" v={{ name: e.name, t: e.t }} />
|
|
3638
3656
|
</circle>
|
|
3639
|
-
<text
|
|
3657
|
+
<text
|
|
3658
|
+
x={x}
|
|
3659
|
+
y={trackY - 12}
|
|
3660
|
+
textAnchor={sequenceLabelAnchor(x, e.name, 10, viewW, textMetrics)}
|
|
3661
|
+
fontSize={10}
|
|
3662
|
+
fill="var(--v4-ink, #1a1f2a)"
|
|
3663
|
+
>
|
|
3640
3664
|
{e.name}
|
|
3641
3665
|
</text>
|
|
3642
3666
|
<text x={x} y={trackY + 18} textAnchor="middle" fontSize={9} fill="var(--v4-mute, #64748b)">
|
|
@@ -7715,12 +7739,14 @@ function StatusTimelineReadout({
|
|
|
7715
7739
|
value,
|
|
7716
7740
|
colorMap,
|
|
7717
7741
|
max,
|
|
7742
|
+
textMetrics = "proportional",
|
|
7718
7743
|
}: {
|
|
7719
7744
|
id: string;
|
|
7720
7745
|
label: string;
|
|
7721
7746
|
value: string;
|
|
7722
7747
|
colorMap: Array<{ status: string; color: string }>;
|
|
7723
7748
|
max: number;
|
|
7749
|
+
textMetrics?: CdlTextMetrics;
|
|
7724
7750
|
}): JSX.Element {
|
|
7725
7751
|
type Event = { timestamp: string; status: string };
|
|
7726
7752
|
const events: Event[] = [];
|
|
@@ -7757,6 +7783,7 @@ function StatusTimelineReadout({
|
|
|
7757
7783
|
status === "active" || status === "idle" || status === "error"
|
|
7758
7784
|
? 引く(`readout.statusTimeline.${status}`)
|
|
7759
7785
|
: status.toUpperCase();
|
|
7786
|
+
const 測る = 箱の字を測る(textMetrics);
|
|
7760
7787
|
return (
|
|
7761
7788
|
<div className="cdl-ip-item cdl-ip-readout-status-timeline" data-cdl-readout={id}>
|
|
7762
7789
|
<span className="cdl-ip-label">{label}</span>
|
|
@@ -7771,7 +7798,7 @@ function StatusTimelineReadout({
|
|
|
7771
7798
|
const c = colorLookup.get(ev.status) ?? "#cbd5e1";
|
|
7772
7799
|
const 字 = 区間の字(ev.status);
|
|
7773
7800
|
// 左右に 3px ずつ余白を取る (区間の枠は幅 - 2 で描く)
|
|
7774
|
-
const 描く字 = 幅で切る(字, 10, cellW - 8);
|
|
7801
|
+
const 描く字 = 幅で切る(字, 10, cellW - 8, 測る);
|
|
7775
7802
|
return (
|
|
7776
7803
|
<g key={i} data-cdl-status-event={i} data-cdl-status={ev.status}>
|
|
7777
7804
|
<rect x={x} y={10} width={cellW - 2} height={stripHeight} fill={c} rx={2} />
|
package/src/render/nodes.tsx
CHANGED
|
@@ -87,7 +87,7 @@ import {
|
|
|
87
87
|
ShapeTraderNode,
|
|
88
88
|
ShapeCustomerServiceNode,
|
|
89
89
|
} from "../kinds/shape-people";
|
|
90
|
-
import type { LaidNode } from "../types";
|
|
90
|
+
import type { CdlTextMetrics, LaidNode } from "../types";
|
|
91
91
|
import type { CdlLang } from "../lang";
|
|
92
92
|
import { hasUnresolvedRef, interpolate, resolveNumericAttr } from "./utils";
|
|
93
93
|
import { drawsFigureTitle } from "../layout/spec";
|
|
@@ -121,6 +121,7 @@ export function CdlNodeView({
|
|
|
121
121
|
chartLineScale,
|
|
122
122
|
関係の受け口,
|
|
123
123
|
lang,
|
|
124
|
+
textMetrics,
|
|
124
125
|
値の名前,
|
|
125
126
|
}: {
|
|
126
127
|
node: LaidNode;
|
|
@@ -132,6 +133,8 @@ export function CdlNodeView({
|
|
|
132
133
|
値の名前?: 値の名前の表;
|
|
133
134
|
/** 描画側の固定の字の言語 (#847)。 字を持つ形の部品へ渡す。 渡さない呼び方は英語で描く */
|
|
134
135
|
lang?: CdlLang;
|
|
136
|
+
/** 図全体の字幅規則。測る描画部品へ明示的に渡す */
|
|
137
|
+
textMetrics?: CdlTextMetrics;
|
|
135
138
|
/**
|
|
136
139
|
* この phase の `draw` に載っているか (cdl#512)。 true の間だけ、 対象の種別が
|
|
137
140
|
* phase の進み (`progress`) に合わせて左の起点から描かれる。
|
|
@@ -201,7 +204,13 @@ export function CdlNodeView({
|
|
|
201
204
|
return <CardNode node={resolvedNode} active={active} />;
|
|
202
205
|
// 時系列のやり取り (#578)。 1 つの箱が図を丸ごと描く = 縦が時間で、参加者は箱を持たない
|
|
203
206
|
case "sequence-board":
|
|
204
|
-
return
|
|
207
|
+
return (
|
|
208
|
+
<SequenceBoardNode
|
|
209
|
+
node={resolvedNode}
|
|
210
|
+
stateValues={stateValues}
|
|
211
|
+
textMetrics={textMetrics}
|
|
212
|
+
/>
|
|
213
|
+
);
|
|
205
214
|
// 図表 9 種は payload が `{signal}` を取れるので stateValues を渡す
|
|
206
215
|
// (`render/payload-binding.ts` が描画直前に解決する)。 取れる欄は種別で違い、
|
|
207
216
|
// 数と語を取る 7 種に対し、mind-map / tree-hierarchy は **名前だけ** を取る (#467)。
|
|
@@ -225,6 +234,7 @@ export function CdlNodeView({
|
|
|
225
234
|
stateValues={stateValues}
|
|
226
235
|
drawing={drawing}
|
|
227
236
|
progress={progress}
|
|
237
|
+
textMetrics={textMetrics}
|
|
228
238
|
/>
|
|
229
239
|
);
|
|
230
240
|
case "chart-gauge":
|
|
@@ -236,6 +246,7 @@ export function CdlNodeView({
|
|
|
236
246
|
stateValues={stateValues}
|
|
237
247
|
drawing={drawing}
|
|
238
248
|
progress={progress}
|
|
249
|
+
textMetrics={textMetrics}
|
|
239
250
|
/>
|
|
240
251
|
);
|
|
241
252
|
case "chart-stacked-bar":
|
|
@@ -248,6 +259,7 @@ export function CdlNodeView({
|
|
|
248
259
|
stateValues={stateValues}
|
|
249
260
|
drawing={drawing}
|
|
250
261
|
progress={progress}
|
|
262
|
+
textMetrics={textMetrics}
|
|
251
263
|
/>
|
|
252
264
|
);
|
|
253
265
|
case "chart-slope":
|
|
@@ -259,6 +271,7 @@ export function CdlNodeView({
|
|
|
259
271
|
stateValues={stateValues}
|
|
260
272
|
drawing={drawing}
|
|
261
273
|
progress={progress}
|
|
274
|
+
textMetrics={textMetrics}
|
|
262
275
|
/>
|
|
263
276
|
);
|
|
264
277
|
case "chart-waffle":
|
|
@@ -270,11 +283,19 @@ export function CdlNodeView({
|
|
|
270
283
|
stateValues={stateValues}
|
|
271
284
|
drawing={drawing}
|
|
272
285
|
progress={progress}
|
|
286
|
+
textMetrics={textMetrics}
|
|
273
287
|
/>
|
|
274
288
|
);
|
|
275
289
|
case "chart-stat":
|
|
276
290
|
// 数値が主役で、弧は輪郭だけの補助。 段の進みで数値が動く演出は既存の tween が担う
|
|
277
|
-
return
|
|
291
|
+
return (
|
|
292
|
+
<ChartStatNode
|
|
293
|
+
node={resolvedNode}
|
|
294
|
+
active={active}
|
|
295
|
+
stateValues={stateValues}
|
|
296
|
+
textMetrics={textMetrics}
|
|
297
|
+
/>
|
|
298
|
+
);
|
|
278
299
|
case "chart-radial":
|
|
279
300
|
// 段の進みでは各輪が 12 時から開く (cdl#715)
|
|
280
301
|
return (
|
|
@@ -284,6 +305,7 @@ export function CdlNodeView({
|
|
|
284
305
|
stateValues={stateValues}
|
|
285
306
|
drawing={drawing}
|
|
286
307
|
progress={progress}
|
|
308
|
+
textMetrics={textMetrics}
|
|
287
309
|
/>
|
|
288
310
|
);
|
|
289
311
|
case "chart-bar":
|
|
@@ -314,6 +336,7 @@ export function CdlNodeView({
|
|
|
314
336
|
stateValues={stateValues}
|
|
315
337
|
drawing={drawing}
|
|
316
338
|
progress={progress}
|
|
339
|
+
textMetrics={textMetrics}
|
|
317
340
|
/>
|
|
318
341
|
);
|
|
319
342
|
case "funnel-stages":
|
|
@@ -324,10 +347,18 @@ export function CdlNodeView({
|
|
|
324
347
|
stateValues={stateValues}
|
|
325
348
|
drawing={drawing}
|
|
326
349
|
progress={progress}
|
|
350
|
+
textMetrics={textMetrics}
|
|
327
351
|
/>
|
|
328
352
|
);
|
|
329
353
|
case "quadrant-matrix":
|
|
330
|
-
return
|
|
354
|
+
return (
|
|
355
|
+
<QuadrantNode
|
|
356
|
+
node={resolvedNode}
|
|
357
|
+
active={active}
|
|
358
|
+
stateValues={stateValues}
|
|
359
|
+
textMetrics={textMetrics}
|
|
360
|
+
/>
|
|
361
|
+
);
|
|
331
362
|
case "tree-hierarchy":
|
|
332
363
|
return (
|
|
333
364
|
<TreeHierarchyNode
|
|
@@ -336,6 +367,7 @@ export function CdlNodeView({
|
|
|
336
367
|
stateValues={stateValues}
|
|
337
368
|
drawing={drawing}
|
|
338
369
|
progress={progress}
|
|
370
|
+
textMetrics={textMetrics}
|
|
339
371
|
/>
|
|
340
372
|
);
|
|
341
373
|
case "journey-map":
|
|
@@ -349,91 +381,91 @@ export function CdlNodeView({
|
|
|
349
381
|
/>
|
|
350
382
|
);
|
|
351
383
|
case "shape-file":
|
|
352
|
-
return <ShapeFileNode node={resolvedNode} active={active} />;
|
|
384
|
+
return <ShapeFileNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
353
385
|
case "shape-folder":
|
|
354
|
-
return <ShapeFolderNode node={resolvedNode} active={active} />;
|
|
386
|
+
return <ShapeFolderNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
355
387
|
case "shape-cloud":
|
|
356
|
-
return <ShapeCloudNode node={resolvedNode} active={active} />;
|
|
388
|
+
return <ShapeCloudNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
357
389
|
case "shape-cylinder":
|
|
358
|
-
return <ShapeCylinderNode node={resolvedNode} active={active} />;
|
|
390
|
+
return <ShapeCylinderNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
359
391
|
case "shape-hexagon":
|
|
360
|
-
return <ShapeHexagonNode node={resolvedNode} active={active} />;
|
|
392
|
+
return <ShapeHexagonNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
361
393
|
case "shape-diamond":
|
|
362
|
-
return <ShapeDiamondNode node={resolvedNode} active={active} />;
|
|
394
|
+
return <ShapeDiamondNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
363
395
|
case "shape-stack":
|
|
364
|
-
return <ShapeStackNode node={resolvedNode} active={active} />;
|
|
396
|
+
return <ShapeStackNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
365
397
|
case "shape-person":
|
|
366
|
-
return <ShapePersonNode node={resolvedNode} active={active} />;
|
|
398
|
+
return <ShapePersonNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
367
399
|
case "shape-window":
|
|
368
400
|
return <ShapeWindowNode node={resolvedNode} active={active} />;
|
|
369
401
|
case "shape-terminal":
|
|
370
402
|
return <ShapeTerminalNode node={resolvedNode} active={active} lang={lang} />;
|
|
371
403
|
case "shape-code-block":
|
|
372
|
-
return <ShapeCodeBlockNode node={resolvedNode} active={active} lang={lang} />;
|
|
404
|
+
return <ShapeCodeBlockNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
373
405
|
case "shape-kanban-card":
|
|
374
|
-
return <ShapeKanbanCardNode node={resolvedNode} active={active} lang={lang} />;
|
|
406
|
+
return <ShapeKanbanCardNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
375
407
|
case "shape-message-bubble":
|
|
376
408
|
return <ShapeMessageBubbleNode node={resolvedNode} active={active} />;
|
|
377
409
|
case "shape-gear":
|
|
378
410
|
return <ShapeGearNode node={resolvedNode} active={active} />;
|
|
379
411
|
case "shape-server-rack":
|
|
380
|
-
return <ShapeServerRackNode node={resolvedNode} active={active} />;
|
|
412
|
+
return <ShapeServerRackNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
381
413
|
case "shape-network-node":
|
|
382
|
-
return <ShapeNetworkNodeNode node={resolvedNode} active={active} />;
|
|
414
|
+
return <ShapeNetworkNodeNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
383
415
|
case "shape-mobile-device":
|
|
384
|
-
return <ShapeMobileDeviceNode node={resolvedNode} active={active} />;
|
|
416
|
+
return <ShapeMobileDeviceNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
385
417
|
case "shape-iot-sensor":
|
|
386
418
|
return <ShapeIotSensorNode node={resolvedNode} active={active} />;
|
|
387
419
|
case "shape-robot-arm":
|
|
388
420
|
return <ShapeRobotArmNode node={resolvedNode} active={active} />;
|
|
389
421
|
case "shape-satellite":
|
|
390
|
-
return <ShapeSatelliteNode node={resolvedNode} active={active} />;
|
|
422
|
+
return <ShapeSatelliteNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
391
423
|
case "shape-smart-contract":
|
|
392
|
-
return <ShapeSmartContractNode node={resolvedNode} active={active} lang={lang} />;
|
|
424
|
+
return <ShapeSmartContractNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
393
425
|
case "shape-blockchain-block":
|
|
394
|
-
return <ShapeBlockchainBlockNode node={resolvedNode} active={active} lang={lang} />;
|
|
426
|
+
return <ShapeBlockchainBlockNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
395
427
|
case "shape-rpc-node":
|
|
396
|
-
return <ShapeRpcNodeNode node={resolvedNode} active={active} lang={lang} />;
|
|
428
|
+
return <ShapeRpcNodeNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
397
429
|
case "shape-wallet":
|
|
398
|
-
return <ShapeWalletNode node={resolvedNode} active={active} />;
|
|
430
|
+
return <ShapeWalletNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
399
431
|
case "shape-nft":
|
|
400
|
-
return <ShapeNftNode node={resolvedNode} active={active} />;
|
|
432
|
+
return <ShapeNftNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
401
433
|
case "shape-token":
|
|
402
|
-
return <ShapeTokenNode node={resolvedNode} active={active} />;
|
|
434
|
+
return <ShapeTokenNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
403
435
|
case "shape-blockchain":
|
|
404
|
-
return <ShapeBlockchainNode node={resolvedNode} active={active} />;
|
|
436
|
+
return <ShapeBlockchainNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
405
437
|
case "shape-bitcoin-chain":
|
|
406
|
-
return <ShapeBitcoinChainNode node={resolvedNode} active={active} />;
|
|
438
|
+
return <ShapeBitcoinChainNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
407
439
|
case "shape-ethereum-chain":
|
|
408
|
-
return <ShapeEthereumChainNode node={resolvedNode} active={active} />;
|
|
440
|
+
return <ShapeEthereumChainNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
409
441
|
case "shape-blockchain-node":
|
|
410
|
-
return <ShapeBlockchainNodeNode node={resolvedNode} active={active} />;
|
|
442
|
+
return <ShapeBlockchainNodeNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
411
443
|
case "shape-bank":
|
|
412
|
-
return <ShapeBankNode node={resolvedNode} active={active} />;
|
|
444
|
+
return <ShapeBankNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
413
445
|
case "shape-trust-bank":
|
|
414
|
-
return <ShapeTrustBankNode node={resolvedNode} active={active} lang={lang} />;
|
|
446
|
+
return <ShapeTrustBankNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
415
447
|
case "shape-payment-provider":
|
|
416
|
-
return <ShapePaymentProviderNode node={resolvedNode} active={active} lang={lang} />;
|
|
448
|
+
return <ShapePaymentProviderNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
417
449
|
case "shape-credit-card":
|
|
418
|
-
return <ShapeCreditCardNode node={resolvedNode} active={active} />;
|
|
450
|
+
return <ShapeCreditCardNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
419
451
|
case "shape-brokerage":
|
|
420
|
-
return <ShapeBrokerageNode node={resolvedNode} active={active} lang={lang} />;
|
|
452
|
+
return <ShapeBrokerageNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
421
453
|
case "shape-exchange":
|
|
422
|
-
return <ShapeExchangeNode node={resolvedNode} active={active} lang={lang} />;
|
|
454
|
+
return <ShapeExchangeNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
423
455
|
case "shape-atm":
|
|
424
|
-
return <ShapeAtmNode node={resolvedNode} active={active} lang={lang} />;
|
|
456
|
+
return <ShapeAtmNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
425
457
|
case "shape-website":
|
|
426
|
-
return <ShapeWebsiteNode node={resolvedNode} active={active} />;
|
|
458
|
+
return <ShapeWebsiteNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
427
459
|
case "shape-storefront":
|
|
428
|
-
return <ShapeStorefrontNode node={resolvedNode} active={active} lang={lang} />;
|
|
460
|
+
return <ShapeStorefrontNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
429
461
|
case "shape-warehouse":
|
|
430
|
-
return <ShapeWarehouseNode node={resolvedNode} active={active} lang={lang} />;
|
|
462
|
+
return <ShapeWarehouseNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
431
463
|
case "shape-online-shop":
|
|
432
|
-
return <ShapeOnlineShopNode node={resolvedNode} active={active} />;
|
|
464
|
+
return <ShapeOnlineShopNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
433
465
|
case "shape-cdn-edge":
|
|
434
|
-
return <ShapeCdnEdgeNode node={resolvedNode} active={active} />;
|
|
466
|
+
return <ShapeCdnEdgeNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
435
467
|
case "shape-api-gateway":
|
|
436
|
-
return <ShapeApiGatewayNode node={resolvedNode} active={active} lang={lang} />;
|
|
468
|
+
return <ShapeApiGatewayNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
437
469
|
case "shape-auditor":
|
|
438
470
|
return <ShapeAuditorNode node={resolvedNode} active={active} />;
|
|
439
471
|
case "shape-regulator":
|
|
@@ -454,7 +486,16 @@ export function CdlNodeView({
|
|
|
454
486
|
return <StationNode node={resolvedNode} active={active} />;
|
|
455
487
|
default:
|
|
456
488
|
// 新 NodeKind 25 種は GenericNode で汎用描画 (kind 別 icon / color / shape)
|
|
457
|
-
return
|
|
489
|
+
return (
|
|
490
|
+
<GenericNode
|
|
491
|
+
node={resolvedNode}
|
|
492
|
+
active={active}
|
|
493
|
+
value={node.value}
|
|
494
|
+
stateValues={stateValues}
|
|
495
|
+
値の名前={値の名前}
|
|
496
|
+
textMetrics={textMetrics}
|
|
497
|
+
/>
|
|
498
|
+
);
|
|
458
499
|
}
|
|
459
500
|
})();
|
|
460
501
|
// node bounding box を node.w/h に固定するための invisible rect。
|
|
@@ -532,7 +573,7 @@ export function CdlNodeView({
|
|
|
532
573
|
pointerEvents="none"
|
|
533
574
|
/>
|
|
534
575
|
{inner}
|
|
535
|
-
{箱の上の語(resolvedNode)}
|
|
576
|
+
{箱の上の語(resolvedNode, textMetrics ?? "proportional")}
|
|
536
577
|
</g>
|
|
537
578
|
);
|
|
538
579
|
}
|
|
@@ -548,8 +589,13 @@ const FIGURE_EYEBROW_SIZE = 11;
|
|
|
548
589
|
const FIGURE_EYEBROW_LIFT = 1.1;
|
|
549
590
|
|
|
550
591
|
/** 箱の幅に収める倍率。 収めないと長い語が箱の外へ出る */
|
|
551
|
-
function 幅に収めた大きさ(
|
|
552
|
-
|
|
592
|
+
function 幅に収めた大きさ(
|
|
593
|
+
語: string,
|
|
594
|
+
素の大きさ: number,
|
|
595
|
+
幅: number,
|
|
596
|
+
textMetrics: CdlTextMetrics = "proportional",
|
|
597
|
+
): number {
|
|
598
|
+
const 実幅 = textWidth(語, 素の大きさ, 0, textMetrics);
|
|
553
599
|
return 実幅 > 幅 ? (素の大きさ * 幅) / 実幅 : 素の大きさ;
|
|
554
600
|
}
|
|
555
601
|
|
|
@@ -570,13 +616,16 @@ function 幅に収めた大きさ(語: string, 素の大きさ: number, 幅: num
|
|
|
570
616
|
* 題を書かず小見出しだけを書いた図では、小見出しを題の位置に置く。 題の有無に紐付けて
|
|
571
617
|
* 消すと、題を書かない図で小見出しが出なくなる。
|
|
572
618
|
*/
|
|
573
|
-
function 箱の上の語(
|
|
619
|
+
function 箱の上の語(
|
|
620
|
+
node: LaidNode,
|
|
621
|
+
textMetrics: CdlTextMetrics = "proportional",
|
|
622
|
+
): JSX.Element | null {
|
|
574
623
|
if (!drawsFigureTitle(node.kind)) return null;
|
|
575
624
|
const 名前 = (node.title ?? "").trim();
|
|
576
625
|
const 小見出し = (node.eyebrow ?? "").trim();
|
|
577
626
|
if (名前 === "" && 小見出し === "") return null;
|
|
578
627
|
const 題のy = node.cy - node.h / 2 - FIGURE_TITLE_GAP;
|
|
579
|
-
const 題の大きさ = 幅に収めた大きさ(名前, FIGURE_TITLE_SIZE, node.w);
|
|
628
|
+
const 題の大きさ = 幅に収めた大きさ(名前, FIGURE_TITLE_SIZE, node.w, textMetrics);
|
|
580
629
|
const 小見出しのy = 名前 === "" ? 題のy : 題のy - 題の大きさ * FIGURE_EYEBROW_LIFT;
|
|
581
630
|
return (
|
|
582
631
|
<>
|
|
@@ -586,7 +635,7 @@ function 箱の上の語(node: LaidNode): JSX.Element | null {
|
|
|
586
635
|
x={node.cx}
|
|
587
636
|
y={小見出しのy}
|
|
588
637
|
textAnchor="middle"
|
|
589
|
-
fontSize={幅に収めた大きさ(小見出し, FIGURE_EYEBROW_SIZE, node.w)}
|
|
638
|
+
fontSize={幅に収めた大きさ(小見出し, FIGURE_EYEBROW_SIZE, node.w, textMetrics)}
|
|
590
639
|
fontWeight={600}
|
|
591
640
|
letterSpacing={0.6}
|
|
592
641
|
fill="var(--cdl-text-dim, #5a6270)"
|
package/src/render/stage.tsx
CHANGED
|
@@ -455,6 +455,7 @@ export function CdlStage({
|
|
|
455
455
|
active={activeSet.has(node.id)}
|
|
456
456
|
// 描画側の固定の字の言語 (#847)。 `layout` が既定以外の時だけ `lang` を持たせる
|
|
457
457
|
lang={laid.lang}
|
|
458
|
+
textMetrics={laid.textMetrics}
|
|
458
459
|
関係の受け口={箱の受け口}
|
|
459
460
|
drawing={描く}
|
|
460
461
|
progress={描く ? 描く進み : progress}
|
|
@@ -480,6 +481,7 @@ export function CdlStage({
|
|
|
480
481
|
key={`label-${edge.id}`}
|
|
481
482
|
edge={edge}
|
|
482
483
|
active={activeSet.has(edge.id)}
|
|
484
|
+
textMetrics={laid.textMetrics}
|
|
483
485
|
/>
|
|
484
486
|
))}
|
|
485
487
|
|
|
@@ -493,6 +495,7 @@ export function CdlStage({
|
|
|
493
495
|
edge={edge}
|
|
494
496
|
active={activeSet.has(edge.id)}
|
|
495
497
|
端={端}
|
|
498
|
+
textMetrics={laid.textMetrics}
|
|
496
499
|
/>
|
|
497
500
|
)),
|
|
498
501
|
)}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CdlTextMetrics } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 記法などから届いた値を字幅の測り方へ直す。
|
|
5
|
+
* 完全一致する `"monospace"` 以外は既定の `"proportional"` に落とす。
|
|
6
|
+
*/
|
|
7
|
+
export function resolveTextMetrics(value: unknown): CdlTextMetrics {
|
|
8
|
+
return value === "monospace" ? "monospace" : "proportional";
|
|
9
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -20,6 +20,15 @@ export const TONES = ["accent", "teal", "success", "error", "warning", "info"] a
|
|
|
20
20
|
|
|
21
21
|
export type Tone = (typeof TONES)[number];
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* 図の字幅をどの規則で測るか。`"proportional"` は現在の Inter 比例幅表、`"monospace"` は
|
|
25
|
+
* 等幅書体向けの幅を使う。既定は `"proportional"`。
|
|
26
|
+
*
|
|
27
|
+
* 消費側が図を等幅書体で描く場合、比例幅で配置すると英数字の描画幅と箱・札の計算幅が
|
|
28
|
+
* 食い違うため、図単位で測り方を揃えられるようにする。
|
|
29
|
+
*/
|
|
30
|
+
export type CdlTextMetrics = "proportional" | "monospace";
|
|
31
|
+
|
|
23
32
|
/**
|
|
24
33
|
* cdl SVG に付与される data-cdl-role attribute の SSOT 型 (現在 12 role)。
|
|
25
34
|
* consumer app (dragon 等) は `[data-cdl-role="<role>"]` selector で CSS を宛てる。 role を追加する時は本 union と該当 render 箇所 (kinds/*.tsx、
|
|
@@ -2754,6 +2763,14 @@ export type CdlDiagram = {
|
|
|
2754
2763
|
* 型の外の値は `layout` が既定に落とす (記法から文字列で届く場合の書き損じ)。
|
|
2755
2764
|
*/
|
|
2756
2765
|
lang?: CdlLang;
|
|
2766
|
+
/**
|
|
2767
|
+
* 図の字幅を測る規則。`"proportional"` または `"monospace"` を取り、既定は
|
|
2768
|
+
* `"proportional"`。
|
|
2769
|
+
*
|
|
2770
|
+
* 消費側が等幅書体で描く図では、比例幅の配置計算と実際の描画幅が食い違うため指定する。
|
|
2771
|
+
* 型の外の値は `layout` が既定へ落とす。
|
|
2772
|
+
*/
|
|
2773
|
+
textMetrics?: CdlTextMetrics;
|
|
2757
2774
|
/**
|
|
2758
2775
|
* この図がどの種類か (#645)。 書かない図は種類を持たない。
|
|
2759
2776
|
*
|
|
@@ -2986,6 +3003,8 @@ export type LaidDiagram = {
|
|
|
2986
3003
|
* 無い時は `"en"` で描く。
|
|
2987
3004
|
*/
|
|
2988
3005
|
lang?: CdlLang;
|
|
3006
|
+
/** 等幅で測る図だけが持つ。無い時は `"proportional"` */
|
|
3007
|
+
textMetrics?: "monospace";
|
|
2989
3008
|
/** どの種類の図か (#645)。 `CdlDiagram.type` をそのまま持ち越す */
|
|
2990
3009
|
type?: string;
|
|
2991
3010
|
viewBox: { x: number; y: number; w: number; h: number };
|