@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
|
@@ -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
|
@@ -7,6 +7,7 @@ import { FunctionNode } from "../kinds/function";
|
|
|
7
7
|
import { StorageNode } from "../kinds/storage";
|
|
8
8
|
import { GenericNode } from "../kinds/generic";
|
|
9
9
|
import { MarkStartNode, MarkEndNode } from "../kinds/terminal-mark";
|
|
10
|
+
import { StationNode } from "../kinds/station";
|
|
10
11
|
import { DynShapeNode } from "../kinds/dyn-shape";
|
|
11
12
|
import { ChartLineNode, type ChartLineScale } from "../kinds/chart-line";
|
|
12
13
|
import { ChartPieNode } from "../kinds/chart-pie";
|
|
@@ -86,7 +87,7 @@ import {
|
|
|
86
87
|
ShapeTraderNode,
|
|
87
88
|
ShapeCustomerServiceNode,
|
|
88
89
|
} from "../kinds/shape-people";
|
|
89
|
-
import type { LaidNode } from "../types";
|
|
90
|
+
import type { CdlTextMetrics, LaidNode } from "../types";
|
|
90
91
|
import type { CdlLang } from "../lang";
|
|
91
92
|
import { hasUnresolvedRef, interpolate, resolveNumericAttr } from "./utils";
|
|
92
93
|
import { drawsFigureTitle } from "../layout/spec";
|
|
@@ -120,6 +121,7 @@ export function CdlNodeView({
|
|
|
120
121
|
chartLineScale,
|
|
121
122
|
関係の受け口,
|
|
122
123
|
lang,
|
|
124
|
+
textMetrics,
|
|
123
125
|
値の名前,
|
|
124
126
|
}: {
|
|
125
127
|
node: LaidNode;
|
|
@@ -131,6 +133,8 @@ export function CdlNodeView({
|
|
|
131
133
|
値の名前?: 値の名前の表;
|
|
132
134
|
/** 描画側の固定の字の言語 (#847)。 字を持つ形の部品へ渡す。 渡さない呼び方は英語で描く */
|
|
133
135
|
lang?: CdlLang;
|
|
136
|
+
/** 図全体の字幅規則。測る描画部品へ明示的に渡す */
|
|
137
|
+
textMetrics?: CdlTextMetrics;
|
|
134
138
|
/**
|
|
135
139
|
* この phase の `draw` に載っているか (cdl#512)。 true の間だけ、 対象の種別が
|
|
136
140
|
* phase の進み (`progress`) に合わせて左の起点から描かれる。
|
|
@@ -200,7 +204,13 @@ export function CdlNodeView({
|
|
|
200
204
|
return <CardNode node={resolvedNode} active={active} />;
|
|
201
205
|
// 時系列のやり取り (#578)。 1 つの箱が図を丸ごと描く = 縦が時間で、参加者は箱を持たない
|
|
202
206
|
case "sequence-board":
|
|
203
|
-
return
|
|
207
|
+
return (
|
|
208
|
+
<SequenceBoardNode
|
|
209
|
+
node={resolvedNode}
|
|
210
|
+
stateValues={stateValues}
|
|
211
|
+
textMetrics={textMetrics}
|
|
212
|
+
/>
|
|
213
|
+
);
|
|
204
214
|
// 図表 9 種は payload が `{signal}` を取れるので stateValues を渡す
|
|
205
215
|
// (`render/payload-binding.ts` が描画直前に解決する)。 取れる欄は種別で違い、
|
|
206
216
|
// 数と語を取る 7 種に対し、mind-map / tree-hierarchy は **名前だけ** を取る (#467)。
|
|
@@ -224,6 +234,7 @@ export function CdlNodeView({
|
|
|
224
234
|
stateValues={stateValues}
|
|
225
235
|
drawing={drawing}
|
|
226
236
|
progress={progress}
|
|
237
|
+
textMetrics={textMetrics}
|
|
227
238
|
/>
|
|
228
239
|
);
|
|
229
240
|
case "chart-gauge":
|
|
@@ -235,6 +246,7 @@ export function CdlNodeView({
|
|
|
235
246
|
stateValues={stateValues}
|
|
236
247
|
drawing={drawing}
|
|
237
248
|
progress={progress}
|
|
249
|
+
textMetrics={textMetrics}
|
|
238
250
|
/>
|
|
239
251
|
);
|
|
240
252
|
case "chart-stacked-bar":
|
|
@@ -247,6 +259,7 @@ export function CdlNodeView({
|
|
|
247
259
|
stateValues={stateValues}
|
|
248
260
|
drawing={drawing}
|
|
249
261
|
progress={progress}
|
|
262
|
+
textMetrics={textMetrics}
|
|
250
263
|
/>
|
|
251
264
|
);
|
|
252
265
|
case "chart-slope":
|
|
@@ -258,6 +271,7 @@ export function CdlNodeView({
|
|
|
258
271
|
stateValues={stateValues}
|
|
259
272
|
drawing={drawing}
|
|
260
273
|
progress={progress}
|
|
274
|
+
textMetrics={textMetrics}
|
|
261
275
|
/>
|
|
262
276
|
);
|
|
263
277
|
case "chart-waffle":
|
|
@@ -269,11 +283,19 @@ export function CdlNodeView({
|
|
|
269
283
|
stateValues={stateValues}
|
|
270
284
|
drawing={drawing}
|
|
271
285
|
progress={progress}
|
|
286
|
+
textMetrics={textMetrics}
|
|
272
287
|
/>
|
|
273
288
|
);
|
|
274
289
|
case "chart-stat":
|
|
275
290
|
// 数値が主役で、弧は輪郭だけの補助。 段の進みで数値が動く演出は既存の tween が担う
|
|
276
|
-
return
|
|
291
|
+
return (
|
|
292
|
+
<ChartStatNode
|
|
293
|
+
node={resolvedNode}
|
|
294
|
+
active={active}
|
|
295
|
+
stateValues={stateValues}
|
|
296
|
+
textMetrics={textMetrics}
|
|
297
|
+
/>
|
|
298
|
+
);
|
|
277
299
|
case "chart-radial":
|
|
278
300
|
// 段の進みでは各輪が 12 時から開く (cdl#715)
|
|
279
301
|
return (
|
|
@@ -283,6 +305,7 @@ export function CdlNodeView({
|
|
|
283
305
|
stateValues={stateValues}
|
|
284
306
|
drawing={drawing}
|
|
285
307
|
progress={progress}
|
|
308
|
+
textMetrics={textMetrics}
|
|
286
309
|
/>
|
|
287
310
|
);
|
|
288
311
|
case "chart-bar":
|
|
@@ -313,6 +336,7 @@ export function CdlNodeView({
|
|
|
313
336
|
stateValues={stateValues}
|
|
314
337
|
drawing={drawing}
|
|
315
338
|
progress={progress}
|
|
339
|
+
textMetrics={textMetrics}
|
|
316
340
|
/>
|
|
317
341
|
);
|
|
318
342
|
case "funnel-stages":
|
|
@@ -323,10 +347,18 @@ export function CdlNodeView({
|
|
|
323
347
|
stateValues={stateValues}
|
|
324
348
|
drawing={drawing}
|
|
325
349
|
progress={progress}
|
|
350
|
+
textMetrics={textMetrics}
|
|
326
351
|
/>
|
|
327
352
|
);
|
|
328
353
|
case "quadrant-matrix":
|
|
329
|
-
return
|
|
354
|
+
return (
|
|
355
|
+
<QuadrantNode
|
|
356
|
+
node={resolvedNode}
|
|
357
|
+
active={active}
|
|
358
|
+
stateValues={stateValues}
|
|
359
|
+
textMetrics={textMetrics}
|
|
360
|
+
/>
|
|
361
|
+
);
|
|
330
362
|
case "tree-hierarchy":
|
|
331
363
|
return (
|
|
332
364
|
<TreeHierarchyNode
|
|
@@ -335,6 +367,7 @@ export function CdlNodeView({
|
|
|
335
367
|
stateValues={stateValues}
|
|
336
368
|
drawing={drawing}
|
|
337
369
|
progress={progress}
|
|
370
|
+
textMetrics={textMetrics}
|
|
338
371
|
/>
|
|
339
372
|
);
|
|
340
373
|
case "journey-map":
|
|
@@ -348,91 +381,91 @@ export function CdlNodeView({
|
|
|
348
381
|
/>
|
|
349
382
|
);
|
|
350
383
|
case "shape-file":
|
|
351
|
-
return <ShapeFileNode node={resolvedNode} active={active} />;
|
|
384
|
+
return <ShapeFileNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
352
385
|
case "shape-folder":
|
|
353
|
-
return <ShapeFolderNode node={resolvedNode} active={active} />;
|
|
386
|
+
return <ShapeFolderNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
354
387
|
case "shape-cloud":
|
|
355
|
-
return <ShapeCloudNode node={resolvedNode} active={active} />;
|
|
388
|
+
return <ShapeCloudNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
356
389
|
case "shape-cylinder":
|
|
357
|
-
return <ShapeCylinderNode node={resolvedNode} active={active} />;
|
|
390
|
+
return <ShapeCylinderNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
358
391
|
case "shape-hexagon":
|
|
359
|
-
return <ShapeHexagonNode node={resolvedNode} active={active} />;
|
|
392
|
+
return <ShapeHexagonNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
360
393
|
case "shape-diamond":
|
|
361
|
-
return <ShapeDiamondNode node={resolvedNode} active={active} />;
|
|
394
|
+
return <ShapeDiamondNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
362
395
|
case "shape-stack":
|
|
363
|
-
return <ShapeStackNode node={resolvedNode} active={active} />;
|
|
396
|
+
return <ShapeStackNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
364
397
|
case "shape-person":
|
|
365
|
-
return <ShapePersonNode node={resolvedNode} active={active} />;
|
|
398
|
+
return <ShapePersonNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
366
399
|
case "shape-window":
|
|
367
400
|
return <ShapeWindowNode node={resolvedNode} active={active} />;
|
|
368
401
|
case "shape-terminal":
|
|
369
402
|
return <ShapeTerminalNode node={resolvedNode} active={active} lang={lang} />;
|
|
370
403
|
case "shape-code-block":
|
|
371
|
-
return <ShapeCodeBlockNode node={resolvedNode} active={active} lang={lang} />;
|
|
404
|
+
return <ShapeCodeBlockNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
372
405
|
case "shape-kanban-card":
|
|
373
|
-
return <ShapeKanbanCardNode node={resolvedNode} active={active} lang={lang} />;
|
|
406
|
+
return <ShapeKanbanCardNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
374
407
|
case "shape-message-bubble":
|
|
375
408
|
return <ShapeMessageBubbleNode node={resolvedNode} active={active} />;
|
|
376
409
|
case "shape-gear":
|
|
377
410
|
return <ShapeGearNode node={resolvedNode} active={active} />;
|
|
378
411
|
case "shape-server-rack":
|
|
379
|
-
return <ShapeServerRackNode node={resolvedNode} active={active} />;
|
|
412
|
+
return <ShapeServerRackNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
380
413
|
case "shape-network-node":
|
|
381
|
-
return <ShapeNetworkNodeNode node={resolvedNode} active={active} />;
|
|
414
|
+
return <ShapeNetworkNodeNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
382
415
|
case "shape-mobile-device":
|
|
383
|
-
return <ShapeMobileDeviceNode node={resolvedNode} active={active} />;
|
|
416
|
+
return <ShapeMobileDeviceNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
384
417
|
case "shape-iot-sensor":
|
|
385
418
|
return <ShapeIotSensorNode node={resolvedNode} active={active} />;
|
|
386
419
|
case "shape-robot-arm":
|
|
387
420
|
return <ShapeRobotArmNode node={resolvedNode} active={active} />;
|
|
388
421
|
case "shape-satellite":
|
|
389
|
-
return <ShapeSatelliteNode node={resolvedNode} active={active} />;
|
|
422
|
+
return <ShapeSatelliteNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
390
423
|
case "shape-smart-contract":
|
|
391
|
-
return <ShapeSmartContractNode node={resolvedNode} active={active} lang={lang} />;
|
|
424
|
+
return <ShapeSmartContractNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
392
425
|
case "shape-blockchain-block":
|
|
393
|
-
return <ShapeBlockchainBlockNode node={resolvedNode} active={active} lang={lang} />;
|
|
426
|
+
return <ShapeBlockchainBlockNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
394
427
|
case "shape-rpc-node":
|
|
395
|
-
return <ShapeRpcNodeNode node={resolvedNode} active={active} lang={lang} />;
|
|
428
|
+
return <ShapeRpcNodeNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
396
429
|
case "shape-wallet":
|
|
397
|
-
return <ShapeWalletNode node={resolvedNode} active={active} />;
|
|
430
|
+
return <ShapeWalletNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
398
431
|
case "shape-nft":
|
|
399
|
-
return <ShapeNftNode node={resolvedNode} active={active} />;
|
|
432
|
+
return <ShapeNftNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
400
433
|
case "shape-token":
|
|
401
|
-
return <ShapeTokenNode node={resolvedNode} active={active} />;
|
|
434
|
+
return <ShapeTokenNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
402
435
|
case "shape-blockchain":
|
|
403
|
-
return <ShapeBlockchainNode node={resolvedNode} active={active} />;
|
|
436
|
+
return <ShapeBlockchainNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
404
437
|
case "shape-bitcoin-chain":
|
|
405
|
-
return <ShapeBitcoinChainNode node={resolvedNode} active={active} />;
|
|
438
|
+
return <ShapeBitcoinChainNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
406
439
|
case "shape-ethereum-chain":
|
|
407
|
-
return <ShapeEthereumChainNode node={resolvedNode} active={active} />;
|
|
440
|
+
return <ShapeEthereumChainNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
408
441
|
case "shape-blockchain-node":
|
|
409
|
-
return <ShapeBlockchainNodeNode node={resolvedNode} active={active} />;
|
|
442
|
+
return <ShapeBlockchainNodeNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
410
443
|
case "shape-bank":
|
|
411
|
-
return <ShapeBankNode node={resolvedNode} active={active} />;
|
|
444
|
+
return <ShapeBankNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
412
445
|
case "shape-trust-bank":
|
|
413
|
-
return <ShapeTrustBankNode node={resolvedNode} active={active} lang={lang} />;
|
|
446
|
+
return <ShapeTrustBankNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
414
447
|
case "shape-payment-provider":
|
|
415
|
-
return <ShapePaymentProviderNode node={resolvedNode} active={active} lang={lang} />;
|
|
448
|
+
return <ShapePaymentProviderNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
416
449
|
case "shape-credit-card":
|
|
417
|
-
return <ShapeCreditCardNode node={resolvedNode} active={active} />;
|
|
450
|
+
return <ShapeCreditCardNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
418
451
|
case "shape-brokerage":
|
|
419
|
-
return <ShapeBrokerageNode node={resolvedNode} active={active} lang={lang} />;
|
|
452
|
+
return <ShapeBrokerageNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
420
453
|
case "shape-exchange":
|
|
421
|
-
return <ShapeExchangeNode node={resolvedNode} active={active} lang={lang} />;
|
|
454
|
+
return <ShapeExchangeNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
422
455
|
case "shape-atm":
|
|
423
|
-
return <ShapeAtmNode node={resolvedNode} active={active} lang={lang} />;
|
|
456
|
+
return <ShapeAtmNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
424
457
|
case "shape-website":
|
|
425
|
-
return <ShapeWebsiteNode node={resolvedNode} active={active} />;
|
|
458
|
+
return <ShapeWebsiteNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
426
459
|
case "shape-storefront":
|
|
427
|
-
return <ShapeStorefrontNode node={resolvedNode} active={active} lang={lang} />;
|
|
460
|
+
return <ShapeStorefrontNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
428
461
|
case "shape-warehouse":
|
|
429
|
-
return <ShapeWarehouseNode node={resolvedNode} active={active} lang={lang} />;
|
|
462
|
+
return <ShapeWarehouseNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
430
463
|
case "shape-online-shop":
|
|
431
|
-
return <ShapeOnlineShopNode node={resolvedNode} active={active} />;
|
|
464
|
+
return <ShapeOnlineShopNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
432
465
|
case "shape-cdn-edge":
|
|
433
|
-
return <ShapeCdnEdgeNode node={resolvedNode} active={active} />;
|
|
466
|
+
return <ShapeCdnEdgeNode node={resolvedNode} active={active} textMetrics={textMetrics} />;
|
|
434
467
|
case "shape-api-gateway":
|
|
435
|
-
return <ShapeApiGatewayNode node={resolvedNode} active={active} lang={lang} />;
|
|
468
|
+
return <ShapeApiGatewayNode node={resolvedNode} active={active} lang={lang} textMetrics={textMetrics} />;
|
|
436
469
|
case "shape-auditor":
|
|
437
470
|
return <ShapeAuditorNode node={resolvedNode} active={active} />;
|
|
438
471
|
case "shape-regulator":
|
|
@@ -449,9 +482,20 @@ export function CdlNodeView({
|
|
|
449
482
|
return <MarkStartNode node={resolvedNode} active={active} />;
|
|
450
483
|
case "mark-end":
|
|
451
484
|
return <MarkEndNode node={resolvedNode} active={active} />;
|
|
485
|
+
case "station":
|
|
486
|
+
return <StationNode node={resolvedNode} active={active} />;
|
|
452
487
|
default:
|
|
453
488
|
// 新 NodeKind 25 種は GenericNode で汎用描画 (kind 別 icon / color / shape)
|
|
454
|
-
return
|
|
489
|
+
return (
|
|
490
|
+
<GenericNode
|
|
491
|
+
node={resolvedNode}
|
|
492
|
+
active={active}
|
|
493
|
+
value={node.value}
|
|
494
|
+
stateValues={stateValues}
|
|
495
|
+
値の名前={値の名前}
|
|
496
|
+
textMetrics={textMetrics}
|
|
497
|
+
/>
|
|
498
|
+
);
|
|
455
499
|
}
|
|
456
500
|
})();
|
|
457
501
|
// node bounding box を node.w/h に固定するための invisible rect。
|
|
@@ -529,7 +573,7 @@ export function CdlNodeView({
|
|
|
529
573
|
pointerEvents="none"
|
|
530
574
|
/>
|
|
531
575
|
{inner}
|
|
532
|
-
{箱の上の語(resolvedNode)}
|
|
576
|
+
{箱の上の語(resolvedNode, textMetrics ?? "proportional")}
|
|
533
577
|
</g>
|
|
534
578
|
);
|
|
535
579
|
}
|
|
@@ -545,8 +589,13 @@ const FIGURE_EYEBROW_SIZE = 11;
|
|
|
545
589
|
const FIGURE_EYEBROW_LIFT = 1.1;
|
|
546
590
|
|
|
547
591
|
/** 箱の幅に収める倍率。 収めないと長い語が箱の外へ出る */
|
|
548
|
-
function 幅に収めた大きさ(
|
|
549
|
-
|
|
592
|
+
function 幅に収めた大きさ(
|
|
593
|
+
語: string,
|
|
594
|
+
素の大きさ: number,
|
|
595
|
+
幅: number,
|
|
596
|
+
textMetrics: CdlTextMetrics = "proportional",
|
|
597
|
+
): number {
|
|
598
|
+
const 実幅 = textWidth(語, 素の大きさ, 0, textMetrics);
|
|
550
599
|
return 実幅 > 幅 ? (素の大きさ * 幅) / 実幅 : 素の大きさ;
|
|
551
600
|
}
|
|
552
601
|
|
|
@@ -567,13 +616,16 @@ function 幅に収めた大きさ(語: string, 素の大きさ: number, 幅: num
|
|
|
567
616
|
* 題を書かず小見出しだけを書いた図では、小見出しを題の位置に置く。 題の有無に紐付けて
|
|
568
617
|
* 消すと、題を書かない図で小見出しが出なくなる。
|
|
569
618
|
*/
|
|
570
|
-
function 箱の上の語(
|
|
619
|
+
function 箱の上の語(
|
|
620
|
+
node: LaidNode,
|
|
621
|
+
textMetrics: CdlTextMetrics = "proportional",
|
|
622
|
+
): JSX.Element | null {
|
|
571
623
|
if (!drawsFigureTitle(node.kind)) return null;
|
|
572
624
|
const 名前 = (node.title ?? "").trim();
|
|
573
625
|
const 小見出し = (node.eyebrow ?? "").trim();
|
|
574
626
|
if (名前 === "" && 小見出し === "") return null;
|
|
575
627
|
const 題のy = node.cy - node.h / 2 - FIGURE_TITLE_GAP;
|
|
576
|
-
const 題の大きさ = 幅に収めた大きさ(名前, FIGURE_TITLE_SIZE, node.w);
|
|
628
|
+
const 題の大きさ = 幅に収めた大きさ(名前, FIGURE_TITLE_SIZE, node.w, textMetrics);
|
|
577
629
|
const 小見出しのy = 名前 === "" ? 題のy : 題のy - 題の大きさ * FIGURE_EYEBROW_LIFT;
|
|
578
630
|
return (
|
|
579
631
|
<>
|
|
@@ -583,7 +635,7 @@ function 箱の上の語(node: LaidNode): JSX.Element | null {
|
|
|
583
635
|
x={node.cx}
|
|
584
636
|
y={小見出しのy}
|
|
585
637
|
textAnchor="middle"
|
|
586
|
-
fontSize={幅に収めた大きさ(小見出し, FIGURE_EYEBROW_SIZE, node.w)}
|
|
638
|
+
fontSize={幅に収めた大きさ(小見出し, FIGURE_EYEBROW_SIZE, node.w, textMetrics)}
|
|
587
639
|
fontWeight={600}
|
|
588
640
|
letterSpacing={0.6}
|
|
589
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、
|
|
@@ -186,6 +195,8 @@ export const NODE_KINDS = [
|
|
|
186
195
|
// 状態遷移図が使う。 印そのものが意味を持つので文字は描かない
|
|
187
196
|
"mark-start",
|
|
188
197
|
"mark-end",
|
|
198
|
+
// 路線図の駅 (#996) ... 線路上の点を小さな丸で示し、名前と説明は丸の外に置く
|
|
199
|
+
"station",
|
|
189
200
|
// 時系列のやり取り (#578) ... 1 つの箱が図を丸ごと描く。 縦が時間で、参加者は箱を持たない
|
|
190
201
|
"sequence-board",
|
|
191
202
|
] as const;
|
|
@@ -207,6 +218,18 @@ export type Side = "top" | "right" | "bottom" | "left";
|
|
|
207
218
|
*/
|
|
208
219
|
export type EdgeStyle = "solid" | "dotted-flow" | "dashed";
|
|
209
220
|
|
|
221
|
+
/**
|
|
222
|
+
* 線の道筋。
|
|
223
|
+
*
|
|
224
|
+
* - `"default"` ... 自動。 箱を避け、角を丸めた直角の線を引く。 書かない時も同じ
|
|
225
|
+
* - `"back-detour"` ... 箱の上を回って前の箱へ戻る
|
|
226
|
+
* - `"curve"` ... 始点から水平に出て終点へ水平に入る 3 次曲線。 途中の箱は避けず、
|
|
227
|
+
* 2 点が同じ高さなら直線になる。 終点の矢印は水平を向く
|
|
228
|
+
* - `"metro"` ... 横線と傾き ±1 の斜線だけで結び、角を半径 r の円弧で丸める。
|
|
229
|
+
* 途中の箱は避けず、2 点が同じ高さなら横 1 本になる
|
|
230
|
+
*/
|
|
231
|
+
export type EdgeRouting = "default" | "back-detour" | "curve" | "metro";
|
|
232
|
+
|
|
210
233
|
/** 辺の役目 (#618)。 `main` = 図の主となる道 */
|
|
211
234
|
export type EdgeRole = "main";
|
|
212
235
|
|
|
@@ -506,8 +529,8 @@ export type CdlNode = {
|
|
|
506
529
|
* 変わるのは kind の既定色を使って描かれる部分で、 枠線 / eyebrow の文字色 / icon /
|
|
507
530
|
* 強調表示中の行の値。 本文の文字色と背景は変わらない。
|
|
508
531
|
*
|
|
509
|
-
* 効くのは `actor` / `function` / `storage` / `event` / `card
|
|
510
|
-
*
|
|
532
|
+
* 効くのは `actor` / `function` / `storage` / `event` / `card`、`person` 以降の汎用 kind、
|
|
533
|
+
* 流れの印、路線図の駅。 それ以外は自前の配色を持つため指定しても変わらず、検査も通す。
|
|
511
534
|
* 一覧は `test/node-styling.test.tsx` が実装から計算した集合と突き合わせている。
|
|
512
535
|
*
|
|
513
536
|
* 既定色と同じ名前を明示指定しても色が変わる kind がある (既定色の hex が `render/tone.ts`
|
|
@@ -867,8 +890,12 @@ export type CdlEdge = {
|
|
|
867
890
|
labelOffsetX?: number;
|
|
868
891
|
/** label の y 方向 fine offset (default 0、 + で下、 - で上) */
|
|
869
892
|
labelOffsetY?: number;
|
|
870
|
-
/**
|
|
871
|
-
|
|
893
|
+
/**
|
|
894
|
+
* 線の道筋。 `default` は箱を避ける自動経路、 `back-detour` は箱の上を回る戻り線、
|
|
895
|
+
* `curve` は途中の箱を避けず、両端で水平になる 3 次曲線。 `metro` は途中の箱を避けず、
|
|
896
|
+
* 横線と傾き ±1 の斜線を半径 r の円弧で結ぶ。2 点が同じ高さなら横 1 本。書かなければ `default`。
|
|
897
|
+
*/
|
|
898
|
+
routing?: EdgeRouting;
|
|
872
899
|
/**
|
|
873
900
|
* CAR-549 SSOT ... label を edge path 上に「被せる」 か「離す」 かの制御 flag。
|
|
874
901
|
* default (false or undefined) = LABEL_INIT_CLEARANCE (34 world、 CAR-630 で 26 → 34 復帰) 離して配置。
|
|
@@ -2736,6 +2763,14 @@ export type CdlDiagram = {
|
|
|
2736
2763
|
* 型の外の値は `layout` が既定に落とす (記法から文字列で届く場合の書き損じ)。
|
|
2737
2764
|
*/
|
|
2738
2765
|
lang?: CdlLang;
|
|
2766
|
+
/**
|
|
2767
|
+
* 図の字幅を測る規則。`"proportional"` または `"monospace"` を取り、既定は
|
|
2768
|
+
* `"proportional"`。
|
|
2769
|
+
*
|
|
2770
|
+
* 消費側が等幅書体で描く図では、比例幅の配置計算と実際の描画幅が食い違うため指定する。
|
|
2771
|
+
* 型の外の値は `layout` が既定へ落とす。
|
|
2772
|
+
*/
|
|
2773
|
+
textMetrics?: CdlTextMetrics;
|
|
2739
2774
|
/**
|
|
2740
2775
|
* この図がどの種類か (#645)。 書かない図は種類を持たない。
|
|
2741
2776
|
*
|
|
@@ -2968,6 +3003,8 @@ export type LaidDiagram = {
|
|
|
2968
3003
|
* 無い時は `"en"` で描く。
|
|
2969
3004
|
*/
|
|
2970
3005
|
lang?: CdlLang;
|
|
3006
|
+
/** 等幅で測る図だけが持つ。無い時は `"proportional"` */
|
|
3007
|
+
textMetrics?: "monospace";
|
|
2971
3008
|
/** どの種類の図か (#645)。 `CdlDiagram.type` をそのまま持ち越す */
|
|
2972
3009
|
type?: string;
|
|
2973
3010
|
viewBox: { x: number; y: number; w: number; h: number };
|