@cardenelabs/cdl 0.14.0 → 0.16.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 +249 -1
- package/README.md +20 -1
- package/dist/index.cjs +1243 -92
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -5
- package/dist/index.d.ts +27 -5
- package/dist/index.js +1242 -93
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1208 -88
- 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 +1208 -88
- package/dist/react.js.map +1 -1
- package/dist/{render-B5JHcUrb.d.cts → render-BS1E6Mlo.d.cts} +55 -2
- package/dist/{render-B5JHcUrb.d.ts → render-BS1E6Mlo.d.ts} +55 -2
- package/package.json +1 -1
- package/src/builder.ts +24 -0
- package/src/index.ts +2 -1
- package/src/kinds/chart-gauge.tsx +241 -0
- package/src/kinds/chart-pie.tsx +214 -53
- package/src/kinds/chart-radial.tsx +210 -0
- package/src/kinds/chart-stacked-bar.tsx +272 -0
- package/src/kinds/chart-stat.tsx +231 -0
- package/src/kinds/chart-waffle.tsx +197 -0
- package/src/kinds/draw-ratio.ts +44 -0
- package/src/kinds/dyn-shape.tsx +3 -6
- package/src/kinds/generic.tsx +18 -6
- package/src/kinds/terminal-mark.tsx +96 -0
- package/src/layout/edges.ts +30 -1
- package/src/layout/self-loop.ts +217 -0
- package/src/layout/spec.ts +81 -4
- package/src/layout/tokens.ts +15 -0
- package/src/presets.ts +68 -4
- package/src/render/edge-head.ts +45 -0
- package/src/render/edges.tsx +4 -2
- package/src/render/interactive-panel.tsx +2 -2
- package/src/render/nodes.tsx +25 -0
- package/src/render/payload-binding.ts +14 -3
- package/src/render/stage.tsx +41 -18
- package/src/render/tone.ts +52 -0
- package/src/types.ts +66 -2
- package/src/validate.ts +13 -5
- package/src/visual-validate.ts +48 -1
package/src/presets.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { diagram } from "./builder";
|
|
|
2
2
|
import type { DiagramBuilder } from "./builder";
|
|
3
3
|
import { LANE_PAD_MIN, NODE_SIZE } from "./layout/tokens";
|
|
4
4
|
import { requiredRowsWidth } from "./kinds/row-align";
|
|
5
|
-
import type { NodeKind, Tone, EdgeStyle, JourneyEmotion, QuadrantKey, BoundNumber } from "./types";
|
|
5
|
+
import type { NodeKind, Tone, EdgeStyle, EdgeHead, JourneyEmotion, QuadrantKey, BoundNumber } from "./types";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* 状態を読む書き方 (`{名前}`)。
|
|
@@ -984,6 +984,32 @@ export function infrastructure(preset: InfrastructurePreset): InfrastructureBuil
|
|
|
984
984
|
|
|
985
985
|
export type ClassRelationType = "extends" | "implements" | "uses" | "aggregates" | "composes";
|
|
986
986
|
|
|
987
|
+
/**
|
|
988
|
+
* 関係の種類ごとの端の形 (cdl#574)。
|
|
989
|
+
*
|
|
990
|
+
* `#560` で足した端の形は「関係の種類を示す」 ためのもので、この preset が第一の使い手。
|
|
991
|
+
* 表にしないと 5 種とも同じ塗った三角になり、線の種類と語だけで区別することになる。
|
|
992
|
+
*
|
|
993
|
+
* | 種類 | 形 | 意味 |
|
|
994
|
+
* |---|---|---|
|
|
995
|
+
* | `extends` / `implements` | 三角 | 継ぐ |
|
|
996
|
+
* | `aggregates` / `composes` | 菱 | 持つ |
|
|
997
|
+
* | `uses` | 開いた矢 | 使う |
|
|
998
|
+
*
|
|
999
|
+
* **`extends` と `implements` が同じ形になるのは意図**。 UML では線の種類 (実線 / 破線) で
|
|
1000
|
+
* 分ける決まりで、端の形は「継ぐ」 を表す 1 種しかない。 `aggregates` と `composes` も同様に
|
|
1001
|
+
* 塗りの有無で分けるので、端の形は同じ菱になる。
|
|
1002
|
+
*
|
|
1003
|
+
* `Record<ClassRelationType, EdgeHead>` なので、種類を足すと形も要る。
|
|
1004
|
+
*/
|
|
1005
|
+
export const CLASS_RELATION_HEAD: Record<ClassRelationType, EdgeHead> = {
|
|
1006
|
+
extends: "triangle",
|
|
1007
|
+
implements: "triangle",
|
|
1008
|
+
uses: "open",
|
|
1009
|
+
aggregates: "diamond",
|
|
1010
|
+
composes: "diamond",
|
|
1011
|
+
};
|
|
1012
|
+
|
|
987
1013
|
export type UmlClass = {
|
|
988
1014
|
id: string;
|
|
989
1015
|
title: string;
|
|
@@ -1004,6 +1030,12 @@ export type UmlRelation = {
|
|
|
1004
1030
|
/** 多重度 (例 "1..*" / "0..1") */
|
|
1005
1031
|
cardinality?: string;
|
|
1006
1032
|
tone?: Tone;
|
|
1033
|
+
/**
|
|
1034
|
+
* 矢印の先の形 (cdl#574)。 書かなければ種類から決まる (`CLASS_RELATION_HEAD`)。
|
|
1035
|
+
*
|
|
1036
|
+
* **書いた形が勝つ**。 表と違う形にしたい関係だけを個別に書ける。
|
|
1037
|
+
*/
|
|
1038
|
+
head?: EdgeHead;
|
|
1007
1039
|
};
|
|
1008
1040
|
|
|
1009
1041
|
export type ClassDiagramPreset = {
|
|
@@ -1087,6 +1119,8 @@ export function classDiagram(preset: ClassDiagramPreset): ClassDiagramBuilder {
|
|
|
1087
1119
|
...(sub ? { sub } : {}),
|
|
1088
1120
|
tone: r.tone ?? tone,
|
|
1089
1121
|
style: "solid",
|
|
1122
|
+
// 関係の種類から端の形を決める (cdl#574)。 書いた形が勝つ
|
|
1123
|
+
head: r.head ?? CLASS_RELATION_HEAD[r.type],
|
|
1090
1124
|
// CAR-531 SSOT ... 旧実装は `labelOffsetY: currentIdx % 2 === 0 ? -60 : 60` で
|
|
1091
1125
|
// 複数 relation label の path 沿い集中を強制分散していた。 CAR-430 で導入した
|
|
1092
1126
|
// engine 側 fan offset (FAN_STEP 40 world) が同 from / to 群を自動分散する現在、
|
|
@@ -1538,13 +1572,23 @@ export function quadrant(preset: QuadrantPreset): QuadrantBuilder {
|
|
|
1538
1572
|
|
|
1539
1573
|
// ─── chart (Pie / Bar / Line 統合) ─────────────────────────────
|
|
1540
1574
|
|
|
1541
|
-
export type ChartType =
|
|
1575
|
+
export type ChartType =
|
|
1576
|
+
| "pie"
|
|
1577
|
+
| "bar"
|
|
1578
|
+
| "line"
|
|
1579
|
+
| "gauge"
|
|
1580
|
+
| "radial"
|
|
1581
|
+
| "stat"
|
|
1582
|
+
| "waffle"
|
|
1583
|
+
| "stacked-bar";
|
|
1542
1584
|
|
|
1543
1585
|
export type ChartDatum = {
|
|
1544
1586
|
id: string;
|
|
1545
1587
|
label: string;
|
|
1546
1588
|
/** 棒 / 折れ線 / 扇の大きさ。 `{名前}` を書くと状態から読む (#468) */
|
|
1547
1589
|
value: number | BindingRef;
|
|
1590
|
+
/** 前の時点の値 (cdl#551)。 `type: stacked-bar` で書くと帯が 2 本になる */
|
|
1591
|
+
previous?: number | BindingRef;
|
|
1548
1592
|
/** 個別 tone (default は preset の defaultTone) */
|
|
1549
1593
|
tone?: Tone;
|
|
1550
1594
|
};
|
|
@@ -1580,7 +1624,17 @@ export function chart(preset: ChartPreset): ChartBuilder {
|
|
|
1580
1624
|
const canvasW = toGridSize(preset.itemWidth ?? 640);
|
|
1581
1625
|
// 高さも格子の倍数にする。 line / bar は 360 だったが 360 % 16 = 8 で下端が格子から外れ、
|
|
1582
1626
|
// 箱の 4 辺が載る条件を満たさなかった。
|
|
1583
|
-
|
|
1627
|
+
// 円と半円は縦を使わないぶん低い。 折れ線と棒は軸のぶん高さが要る
|
|
1628
|
+
const canvasH = toGridSize(
|
|
1629
|
+
preset.type === "pie" ||
|
|
1630
|
+
preset.type === "gauge" ||
|
|
1631
|
+
preset.type === "radial" ||
|
|
1632
|
+
preset.type === "stat" ||
|
|
1633
|
+
preset.type === "waffle" ||
|
|
1634
|
+
preset.type === "stacked-bar"
|
|
1635
|
+
? 320
|
|
1636
|
+
: 360,
|
|
1637
|
+
);
|
|
1584
1638
|
const b = diagram(preset.id, { topic: preset.topic });
|
|
1585
1639
|
const data: ChartDatum[] = [];
|
|
1586
1640
|
|
|
@@ -1588,6 +1642,11 @@ export function chart(preset: ChartPreset): ChartBuilder {
|
|
|
1588
1642
|
line: "chart-line" as const,
|
|
1589
1643
|
pie: "chart-pie" as const,
|
|
1590
1644
|
bar: "chart-bar" as const,
|
|
1645
|
+
gauge: "chart-gauge" as const,
|
|
1646
|
+
radial: "chart-radial" as const,
|
|
1647
|
+
stat: "chart-stat" as const,
|
|
1648
|
+
waffle: "chart-waffle" as const,
|
|
1649
|
+
"stacked-bar": "chart-stacked-bar" as const,
|
|
1591
1650
|
};
|
|
1592
1651
|
|
|
1593
1652
|
const api: ChartBuilder = {
|
|
@@ -1606,7 +1665,12 @@ export function chart(preset: ChartPreset): ChartBuilder {
|
|
|
1606
1665
|
eyebrow: preset.type,
|
|
1607
1666
|
w: canvasW,
|
|
1608
1667
|
h: canvasH,
|
|
1609
|
-
chartData: data.map((d) => ({
|
|
1668
|
+
chartData: data.map((d) => ({
|
|
1669
|
+
label: d.label,
|
|
1670
|
+
value: d.value,
|
|
1671
|
+
...(d.previous === undefined ? {} : { previous: d.previous }),
|
|
1672
|
+
tone: d.tone,
|
|
1673
|
+
})),
|
|
1610
1674
|
});
|
|
1611
1675
|
b.phase(
|
|
1612
1676
|
"chart",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { EDGE_HEAD_DEFAULT, type EdgeHead } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 関係の端の形の **描き方と marker の名前** (SSOT、 #560)。
|
|
5
|
+
*
|
|
6
|
+
* `render/stage.tsx` が marker を定義し、 `render/edges.tsx` がそれを引く。 2 箇所が別々に
|
|
7
|
+
* 名前を組み立てると、 片方だけ変えた時に線が指す marker が消えて矢頭が出なくなる
|
|
8
|
+
* (`tone-marker-ref` 軸が拾うが、 拾うまでの間は矢頭が無い絵になる)。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type EdgeHeadShape = {
|
|
12
|
+
/** 10x10 の viewBox に描く path */
|
|
13
|
+
d: string;
|
|
14
|
+
/** path の端のどこを線の先端に合わせるか */
|
|
15
|
+
refX: number;
|
|
16
|
+
/** 塗るか。 塗らない形は線で描く */
|
|
17
|
+
filled: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 4 形の描き方。
|
|
22
|
+
*
|
|
23
|
+
* 塗る形 (三角 / 菱) は線の端を隠すので、線をその手前で止める必要は無い = marker は
|
|
24
|
+
* 線の上に重なって描かれる。 塗らない形 (開いた矢 / 三又) は隠さない。
|
|
25
|
+
*
|
|
26
|
+
* 三又だけ `refX` が 10 = 三又は **開いた側が箱に触る** 形で、他の 3 形と前後が逆になる。
|
|
27
|
+
* 9 にすると開いた側が箱から 1 だけ浮く。
|
|
28
|
+
*/
|
|
29
|
+
export const EDGE_HEAD_SHAPE: Record<EdgeHead, EdgeHeadShape> = {
|
|
30
|
+
triangle: { d: "M 0 0 L 10 5 L 0 10 z", refX: 9, filled: true },
|
|
31
|
+
diamond: { d: "M 0 5 L 5 0 L 10 5 L 5 10 z", refX: 9, filled: true },
|
|
32
|
+
open: { d: "M 1 0 L 9 5 L 1 10", refX: 9, filled: false },
|
|
33
|
+
crow: { d: "M 10 0 L 0 5 M 10 5 L 0 5 M 10 10 L 0 5", refX: 10, filled: false },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* marker の名前を組み立てる。
|
|
38
|
+
*
|
|
39
|
+
* **既定の形は名前を変えない** (`cdl-arrow-<tone>` / `cdl-arrow-<tone>-sm`)。
|
|
40
|
+
* 端の形を書いていない図はすべて既定なので、名前が変わると全図の矢頭が差し替わる。
|
|
41
|
+
*/
|
|
42
|
+
export function edgeHeadMarkerId(tone: string, head: EdgeHead, small: boolean): string {
|
|
43
|
+
const 小 = small ? "-sm" : "";
|
|
44
|
+
return head === EDGE_HEAD_DEFAULT ? `cdl-arrow-${tone}${小}` : `cdl-arrow-${tone}-${head}${小}`;
|
|
45
|
+
}
|
package/src/render/edges.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { JSX } from "react";
|
|
2
|
-
import type
|
|
2
|
+
import { EDGE_HEAD_DEFAULT, type LaidEdge } from "../types";
|
|
3
|
+
import { edgeHeadMarkerId } from "./edge-head";
|
|
3
4
|
import { computeLabelBBoxWorld, hasRenderedLabel, LABEL_PILL_PAD_X } from "../layout/spec";
|
|
4
5
|
import { hasTone, toneColor } from "./tone";
|
|
5
6
|
import { EDGE_LABEL_TEXT, fontFamilyAttr, type EdgeLabelLine } from "../label-text";
|
|
@@ -43,8 +44,9 @@ export function CdlEdgeView({
|
|
|
43
44
|
// 1.0 でも 2.92-5.55)。 dragon は edge 用に濃くした色 (#0063a1 等) を CSS で当てており、
|
|
44
45
|
// その色なら 0.9 で 4.51-4.92 と 4.5:1 を満たす (0.8 では 3.69-4.04 で届かない)。
|
|
45
46
|
// 実線と点線で同じ組を使い、 光らせた側が薄くなる逆転を作らない。
|
|
47
|
+
// 端の形は線の種類と別の軸 (#560)。 書かなければ既定の塗った三角で、名前も従来のまま
|
|
46
48
|
const marker = hasTone(edge.tone)
|
|
47
|
-
?
|
|
49
|
+
? edgeHeadMarkerId(edge.tone, edge.head ?? EDGE_HEAD_DEFAULT, !active)
|
|
48
50
|
: "cdl-arrow-muted";
|
|
49
51
|
|
|
50
52
|
const style = edge.style ?? "solid";
|
|
@@ -4133,7 +4133,6 @@ function SlopeReadout({
|
|
|
4133
4133
|
}
|
|
4134
4134
|
const pad = 20;
|
|
4135
4135
|
const range = max - min || 1;
|
|
4136
|
-
const w = viewW - pad * 2;
|
|
4137
4136
|
const h = viewH - pad * 2;
|
|
4138
4137
|
const xL = pad;
|
|
4139
4138
|
const xR = viewW - pad;
|
|
@@ -8433,7 +8432,8 @@ function VoiceMessageReadout({
|
|
|
8433
8432
|
const barsX = 36;
|
|
8434
8433
|
const barsW = width - barsX - 44;
|
|
8435
8434
|
const barsH = 30;
|
|
8436
|
-
|
|
8435
|
+
// 縦位置は棒ごとに自分の高さから出す (下の `y`)。 まとめて 1 つ持つと、
|
|
8436
|
+
// 高さの違う棒が同じ位置から描かれて中央で揃わない
|
|
8437
8437
|
const bw = shown.length > 0 ? barsW / shown.length : 0;
|
|
8438
8438
|
const activeCount = Math.floor(shown.length * p);
|
|
8439
8439
|
const durMin = Math.floor(duration / 60);
|
package/src/render/nodes.tsx
CHANGED
|
@@ -5,9 +5,15 @@ import { EventNode } from "../kinds/event";
|
|
|
5
5
|
import { FunctionNode } from "../kinds/function";
|
|
6
6
|
import { StorageNode } from "../kinds/storage";
|
|
7
7
|
import { GenericNode } from "../kinds/generic";
|
|
8
|
+
import { MarkStartNode, MarkEndNode } from "../kinds/terminal-mark";
|
|
8
9
|
import { DynShapeNode } from "../kinds/dyn-shape";
|
|
9
10
|
import { ChartLineNode } from "../kinds/chart-line";
|
|
10
11
|
import { ChartPieNode } from "../kinds/chart-pie";
|
|
12
|
+
import { ChartGaugeNode } from "../kinds/chart-gauge";
|
|
13
|
+
import { ChartRadialNode } from "../kinds/chart-radial";
|
|
14
|
+
import { ChartStatNode } from "../kinds/chart-stat";
|
|
15
|
+
import { ChartWaffleNode } from "../kinds/chart-waffle";
|
|
16
|
+
import { ChartStackedBarNode } from "../kinds/chart-stacked-bar";
|
|
11
17
|
import { ChartBarNode } from "../kinds/chart-bar";
|
|
12
18
|
import { GanttNode } from "../kinds/gantt";
|
|
13
19
|
import { MindMapNode } from "../kinds/mind-map";
|
|
@@ -179,6 +185,21 @@ export function CdlNodeView({
|
|
|
179
185
|
progress={progress}
|
|
180
186
|
/>
|
|
181
187
|
);
|
|
188
|
+
case "chart-gauge":
|
|
189
|
+
// 段の進みで弧が開く動きは持たない (cdl#549 で対象外にした)
|
|
190
|
+
return <ChartGaugeNode node={resolvedNode} active={active} stateValues={stateValues} />;
|
|
191
|
+
case "chart-stacked-bar":
|
|
192
|
+
// 帯を割合で区切る。 前の時点を書けば 2 本になる (cdl#551)
|
|
193
|
+
return <ChartStackedBarNode node={resolvedNode} active={active} stateValues={stateValues} />;
|
|
194
|
+
case "chart-waffle":
|
|
195
|
+
// 1 個 = 1% の印を 100 個。 段の進みで印が埋まる動きは別 Issue (cdl#552 で対象外)
|
|
196
|
+
return <ChartWaffleNode node={resolvedNode} active={active} stateValues={stateValues} />;
|
|
197
|
+
case "chart-stat":
|
|
198
|
+
// 数値が主役で、弧は輪郭だけの補助。 段の進みで数値が動く演出は既存の tween が担う
|
|
199
|
+
return <ChartStatNode node={resolvedNode} active={active} stateValues={stateValues} />;
|
|
200
|
+
case "chart-radial":
|
|
201
|
+
// 段の進みで弧が伸びる動きは持たない (cdl#550 で対象外にした)
|
|
202
|
+
return <ChartRadialNode node={resolvedNode} active={active} stateValues={stateValues} />;
|
|
182
203
|
case "chart-bar":
|
|
183
204
|
return (
|
|
184
205
|
<ChartBarNode
|
|
@@ -339,6 +360,10 @@ export function CdlNodeView({
|
|
|
339
360
|
return <ShapeTraderNode node={resolvedNode} active={active} />;
|
|
340
361
|
case "shape-customer-service":
|
|
341
362
|
return <ShapeCustomerServiceNode node={resolvedNode} active={active} />;
|
|
363
|
+
case "mark-start":
|
|
364
|
+
return <MarkStartNode node={resolvedNode} active={active} />;
|
|
365
|
+
case "mark-end":
|
|
366
|
+
return <MarkEndNode node={resolvedNode} active={active} />;
|
|
342
367
|
default:
|
|
343
368
|
// 新 NodeKind 25 種は GenericNode で汎用描画 (kind 別 icon / color / shape)
|
|
344
369
|
return <GenericNode node={resolvedNode} active={active} value={node.value} stateValues={stateValues} />;
|
|
@@ -44,7 +44,11 @@ export type BindingResult<T> = { value: T; ok: boolean };
|
|
|
44
44
|
/** 解決できなかった欄を持つ項目に付く印。 kind 側が DOM 属性に写す */
|
|
45
45
|
export type UnresolvedMark = { unresolved?: true };
|
|
46
46
|
|
|
47
|
-
export type ResolvedChartDatum = Omit<ChartDatumPayload, "value"> & {
|
|
47
|
+
export type ResolvedChartDatum = Omit<ChartDatumPayload, "value" | "previous"> & {
|
|
48
|
+
value: number;
|
|
49
|
+
/** 前の時点の値。 書かれていない datum では `undefined` のまま */
|
|
50
|
+
previous?: number;
|
|
51
|
+
} & UnresolvedMark;
|
|
48
52
|
export type ResolvedFunnelStage = Omit<FunnelStagePayload, "count"> & { count: number } & UnresolvedMark;
|
|
49
53
|
export type ResolvedGanttTask = Omit<GanttTaskPayload, "startIdx" | "endIdx"> & {
|
|
50
54
|
startIdx: number;
|
|
@@ -139,10 +143,17 @@ export function resolveChartData(
|
|
|
139
143
|
): ResolvedChartDatum[] {
|
|
140
144
|
// 全ての値が有限な数なら配列を組み直さない。 状態を読まない図が毎 frame で項目数ぶんの
|
|
141
145
|
// 割当てを払わないようにするための近道で、 同じ配列をそのまま返すので挙動も変わらない。
|
|
142
|
-
|
|
146
|
+
//
|
|
147
|
+
// **前の時点の値も同じ判定に入れる** (cdl#551)。 入れないと `{名前}` を書いた `previous` が
|
|
148
|
+
// 近道を素通りし、状態を読まないまま文字列のまま描画へ流れる。
|
|
149
|
+
if (!data.some((d) => needsResolve(d.value) || (d.previous !== undefined && needsResolve(d.previous))))
|
|
150
|
+
return data as ResolvedChartDatum[];
|
|
143
151
|
return data.map((d) => {
|
|
144
152
|
const v = resolveBoundNumber(d.value, values, 0);
|
|
145
|
-
|
|
153
|
+
const 前 = d.previous === undefined ? undefined : resolveBoundNumber(d.previous, values, 0);
|
|
154
|
+
const 解けた = v.ok && (前 === undefined || 前.ok);
|
|
155
|
+
const 素 = { ...d, value: v.value, previous: 前?.value };
|
|
156
|
+
return 解けた ? 素 : { ...素, unresolved: true as const };
|
|
146
157
|
});
|
|
147
158
|
}
|
|
148
159
|
|
package/src/render/stage.tsx
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { useMemo, type JSX } from "react";
|
|
2
|
-
import type
|
|
2
|
+
import { EDGE_HEADS, type CdlPhase, type LaidDiagram } from "../types";
|
|
3
3
|
import { CdlEdgeView, CdlEdgeGlowView, CdlEdgeLabelView } from "./edges";
|
|
4
4
|
import { CdlNodeView } from "./nodes";
|
|
5
5
|
import { TONE, TONE_HEX } from "./tone";
|
|
6
|
+
import { EDGE_HEAD_SHAPE, edgeHeadMarkerId } from "./edge-head";
|
|
6
7
|
import { buildDiagramAltText } from "../layout/spec";
|
|
7
8
|
import { lifelineStartYs, uniformLifelineEndY } from "../layout/lifeline";
|
|
9
|
+
import { 描く進みに直す } from "../kinds/draw-ratio";
|
|
8
10
|
|
|
9
11
|
export function CdlStage({
|
|
10
12
|
laid,
|
|
@@ -28,6 +30,9 @@ export function CdlStage({
|
|
|
28
30
|
// この phase で左の起点から描き始める node (cdl#512)。 `activate` とは別集合で持つ =
|
|
29
31
|
// 焦点が当たり続ける図で毎 phase 引き直すことになるため、 光っているかを引き金にしない
|
|
30
32
|
const drawSet = new Set(currentPhase?.draw ?? []);
|
|
33
|
+
// 描く対象から見た進み (cdl#570)。 `drawRatio` を書かない phase では `progress` と同値で、
|
|
34
|
+
// 描く速さを phase の長さから切り離すためだけに使う
|
|
35
|
+
const 描く進み = 描く進みに直す(progress, currentPhase?.drawRatio);
|
|
31
36
|
// 縦線の終点は全 lane で 1 つに揃える (等長保証)。 値の決め方と、 1 lane の異常が図全体から
|
|
32
37
|
// 縦線を消していた形の対処は `uniformLifelineEndY` の説明。
|
|
33
38
|
//
|
|
@@ -97,21 +102,35 @@ export function CdlStage({
|
|
|
97
102
|
光っていない edge も tone の色で描くが、 矢頭の大きさは従来通り小さいまま保つ
|
|
98
103
|
(Issue の制約 = 色だけを足す)。 */}
|
|
99
104
|
{Object.entries(TONE).flatMap(([tone, color]) =>
|
|
100
|
-
([["", 14], ["-sm", 10]] as const).
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
105
|
+
([["", 14], ["-sm", 10]] as const).flatMap(([suffix, size]) =>
|
|
106
|
+
EDGE_HEADS.map((head) => {
|
|
107
|
+
const 形 = EDGE_HEAD_SHAPE[head];
|
|
108
|
+
return (
|
|
109
|
+
<marker
|
|
110
|
+
key={`${tone}${suffix}-${head}`}
|
|
111
|
+
id={edgeHeadMarkerId(tone, head, suffix === "-sm")}
|
|
112
|
+
viewBox="0 0 10 10"
|
|
113
|
+
refX={形.refX}
|
|
114
|
+
refY="5"
|
|
115
|
+
markerWidth={size}
|
|
116
|
+
markerHeight={size}
|
|
117
|
+
markerUnits="userSpaceOnUse"
|
|
118
|
+
orient="auto-start-reverse"
|
|
119
|
+
>
|
|
120
|
+
<path
|
|
121
|
+
data-cdl-role="edge-arrowhead"
|
|
122
|
+
data-cdl-edge-head={head}
|
|
123
|
+
d={形.d}
|
|
124
|
+
fill={形.filled ? color : "none"}
|
|
125
|
+
stroke={形.filled ? "none" : color}
|
|
126
|
+
strokeWidth={形.filled ? undefined : 1.6}
|
|
127
|
+
strokeLinecap={形.filled ? undefined : "round"}
|
|
128
|
+
strokeLinejoin={形.filled ? undefined : "round"}
|
|
129
|
+
/>
|
|
130
|
+
</marker>
|
|
131
|
+
);
|
|
132
|
+
}),
|
|
133
|
+
),
|
|
115
134
|
)}
|
|
116
135
|
{/* tone が TONE 定義に無い edge の受け皿。 visual-validate の tone-marker-ref 軸が
|
|
117
136
|
dead ref を検知するが、 検知するまでの間に矢頭が消えないようにする。 */}
|
|
@@ -207,12 +226,16 @@ export function CdlStage({
|
|
|
207
226
|
|
|
208
227
|
{/* nodes (2nd pass) */}
|
|
209
228
|
{laid.nodes.map((node) => {
|
|
229
|
+
// 描く対象だけ進みを引き伸ばす (cdl#570)。 同じ phase の他の node と edge、
|
|
230
|
+
// および `tweens` は元の進みのまま = 「線はゆっくり、 値は短く」 が 1 つの phase で
|
|
231
|
+
// 両立する。 対象外にも掛けると値の移りまで速くなる
|
|
232
|
+
const 描く = drawSet.has(node.id);
|
|
210
233
|
const view = (
|
|
211
234
|
<CdlNodeView
|
|
212
235
|
node={node}
|
|
213
236
|
active={activeSet.has(node.id)}
|
|
214
|
-
drawing={
|
|
215
|
-
progress={progress}
|
|
237
|
+
drawing={描く}
|
|
238
|
+
progress={描く ? 描く進み : progress}
|
|
216
239
|
stateValues={stateValues}
|
|
217
240
|
currentPhaseId={currentPhase?.id}
|
|
218
241
|
/>
|
package/src/render/tone.ts
CHANGED
|
@@ -30,6 +30,58 @@ export const TONE_HEX: Record<Tone, string> = {
|
|
|
30
30
|
info: "#5a8ec1",
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* 図表の系列の色 (cdl#546)。
|
|
35
|
+
*
|
|
36
|
+
* `TONE` は **意味付きの色** で、箱や矢印が「これは異常」 「これは成功」 を示すために使う。
|
|
37
|
+
* 図表の系列に要るのは意味ではなく **並べて見分けられること** で、選び方の基準が違う。
|
|
38
|
+
*
|
|
39
|
+
* 使い回していた間、2 つの困りが出ていた。 明るい画面の `warning` (`#a67a2e`) は彩度が低く
|
|
40
|
+
* 青と青緑の隣で沈み、色相の離れた暖色を置こうとすると `error` (赤) しか無いので装飾の
|
|
41
|
+
* つもりの色に「異常」 の意味が乗った。
|
|
42
|
+
*
|
|
43
|
+
* ## 選び方
|
|
44
|
+
*
|
|
45
|
+
* **色相を離す**。 6 色を色相環におおよそ等間隔で置き、隣り合う 2 色が近くならない順に
|
|
46
|
+
* 並べた。 彩度は `TONE` より高く取る = 図表は面で見せるので、線や字より彩度を上げても
|
|
47
|
+
* うるさくならない。
|
|
48
|
+
*
|
|
49
|
+
* ## 差し替え
|
|
50
|
+
*
|
|
51
|
+
* `TONE` と同じく CSS 変数にする。 dragon 側で `--cdl-chart-N` を定義すれば、明暗で別の値を
|
|
52
|
+
* 持てる。 定義しなければここの既定値が出る。
|
|
53
|
+
*
|
|
54
|
+
* datum に `tone` を明示した場合は `TONE` が優先される (意味を示したい図はそのまま書ける)。
|
|
55
|
+
*/
|
|
56
|
+
export const CHART_SERIES: readonly string[] = [
|
|
57
|
+
"var(--cdl-chart-1, #4338ca)",
|
|
58
|
+
"var(--cdl-chart-2, #0d9488)",
|
|
59
|
+
"var(--cdl-chart-3, #f59e0b)",
|
|
60
|
+
"var(--cdl-chart-4, #be185d)",
|
|
61
|
+
"var(--cdl-chart-5, #4d7c0f)",
|
|
62
|
+
"var(--cdl-chart-6, #0369a1)",
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 系列の色の hex のみ。 CSS 変数を評価できない環境 (SSR / 検査) で色を比べる時に使う。
|
|
67
|
+
*
|
|
68
|
+
* `CHART_SERIES` と **同じ並び / 同じ長さ** であることは検査が見る。 片方だけ足すと、
|
|
69
|
+
* 実物と計算が別の色を指す。
|
|
70
|
+
*/
|
|
71
|
+
export const CHART_SERIES_HEX: readonly string[] = [
|
|
72
|
+
"#4338ca",
|
|
73
|
+
"#0d9488",
|
|
74
|
+
"#f59e0b",
|
|
75
|
+
"#be185d",
|
|
76
|
+
"#4d7c0f",
|
|
77
|
+
"#0369a1",
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
/** 系列の色を順に配る。 6 色を超えたら先頭へ戻る */
|
|
81
|
+
export function chartSeriesColor(idx: number): string {
|
|
82
|
+
return CHART_SERIES[((idx % CHART_SERIES.length) + CHART_SERIES.length) % CHART_SERIES.length]!;
|
|
83
|
+
}
|
|
84
|
+
|
|
33
85
|
/** TONE に無い tone が来た時に使う色。 */
|
|
34
86
|
export const TONE_FALLBACK = "#2d6a8f";
|
|
35
87
|
|
package/src/types.ts
CHANGED
|
@@ -58,8 +58,13 @@ export const NODE_KINDS = [
|
|
|
58
58
|
"service", "api", "frontend", "backend", "webhook", "microservice",
|
|
59
59
|
// データ / 判定 (4)
|
|
60
60
|
"signer", "oracle", "merkle-tree", "decision",
|
|
61
|
-
// Chart 系 (
|
|
62
|
-
|
|
61
|
+
// Chart 系 (5) ... 1 node に datum 配列を持ち、 kind 内で全 datum を SVG 描画。
|
|
62
|
+
// chart-gauge は合計を主役に置く形 (cdl#549)、 chart-radial は弧の長さで比べる形 (cdl#550)、
|
|
63
|
+
// chart-stat は数値そのものを主役に置く形 (cdl#553)
|
|
64
|
+
// chart-waffle は 100 個の印で割合を数えて確かめる形 (cdl#552)
|
|
65
|
+
"chart-pie", "chart-line", "chart-bar", "chart-gauge", "chart-radial", "chart-stat",
|
|
66
|
+
// chart-stacked-bar は帯を割合で区切り時点を並べる形 (cdl#551)
|
|
67
|
+
"chart-waffle", "chart-stacked-bar",
|
|
63
68
|
// Timeline / Mind 系 (2、 CAR-994 Phase B) ... 1 node に payload 配列、 SVG geometry render
|
|
64
69
|
"gantt-timeline", "mind-map",
|
|
65
70
|
// Analytic 系 (4、 CAR-994 Phase C) ... 1 node に payload 配列、 SVG geometry render
|
|
@@ -89,6 +94,9 @@ export const NODE_KINDS = [
|
|
|
89
94
|
// Shape-driven people 6 (CAR-1111 Phase 2-D) ... 職種別 person 型 (監査 / 規制 / 公証 / 弁護 / トレーダ / CS)
|
|
90
95
|
"shape-auditor", "shape-regulator", "shape-notary",
|
|
91
96
|
"shape-lawyer", "shape-trader", "shape-customer-service",
|
|
97
|
+
// 流れの始まりと終わりの印 2 種 (#560) ... 塗った丸と輪で囲んだ丸。
|
|
98
|
+
// 状態遷移図が使う。 印そのものが意味を持つので文字は描かない
|
|
99
|
+
"mark-start", "mark-end",
|
|
92
100
|
] as const;
|
|
93
101
|
|
|
94
102
|
export type NodeKind = (typeof NODE_KINDS)[number];
|
|
@@ -103,6 +111,26 @@ export type Side = "top" | "right" | "bottom" | "left";
|
|
|
103
111
|
*/
|
|
104
112
|
export type EdgeStyle = "solid" | "dotted-flow";
|
|
105
113
|
|
|
114
|
+
/**
|
|
115
|
+
* 関係の **端の形** (#560)。 線の種類 (`EdgeStyle`) とは別の軸で、線の先端に何を置くかを決める。
|
|
116
|
+
*
|
|
117
|
+
* - `"triangle"` ... 塗った三角。 **既定**。 書かなければこれになる
|
|
118
|
+
* - `"diamond"` ... 塗った菱。 「持っている」 側を指す
|
|
119
|
+
* - `"open"` ... 閉じない 2 本の腕。 向きだけを示し、待たない
|
|
120
|
+
* - `"crow"` ... 三又。 「多」 を示す (ER の鳥の足)
|
|
121
|
+
*
|
|
122
|
+
* 線の種類と分けて持つのは、両者が独立に組み合わさるため。 実線 × 三角 (継ぐ) と
|
|
123
|
+
* 破線 × 三角 (満たす) は線種で分かれ、実線 × 三角 (継ぐ) と実線 × 菱 (持つ) は端で分かれる。
|
|
124
|
+
* 1 つの欄に畳むと 2 軸の積を名前で列挙することになり、組合せが増えるたびに語が増える。
|
|
125
|
+
*/
|
|
126
|
+
export type EdgeHead = "triangle" | "diamond" | "open" | "crow";
|
|
127
|
+
|
|
128
|
+
/** 書かなかった時の端の形。 既存の図はすべてこれで描かれている */
|
|
129
|
+
export const EDGE_HEAD_DEFAULT: EdgeHead = "triangle";
|
|
130
|
+
|
|
131
|
+
/** 端の形の全種。 marker の定義と検査の走査がこの並びを引く (書き写すとずれる) */
|
|
132
|
+
export const EDGE_HEADS: readonly EdgeHead[] = ["triangle", "diamond", "open", "crow"];
|
|
133
|
+
|
|
106
134
|
export type CdlLane = {
|
|
107
135
|
id: string;
|
|
108
136
|
/** lane の x 座標 (optional、 未指定なら engine が前 lane 右端 + gap で auto 計算) */
|
|
@@ -335,6 +363,16 @@ export type ChartDatumPayload = {
|
|
|
335
363
|
label: string;
|
|
336
364
|
/** 棒 / 折れ線 / 扇の大きさ。 `{signal}` で状態に追随する */
|
|
337
365
|
value: BoundNumber;
|
|
366
|
+
/**
|
|
367
|
+
* **前の時点の値** (cdl#551)。 書くと `chart-stacked-bar` が帯を 2 本描き、
|
|
368
|
+
* 内訳の変化がそのまま見える。 書かない図は 1 本のまま。
|
|
369
|
+
*
|
|
370
|
+
* 節を時点ごとに置く案は採らない = 図表は「1 節に datum 配列を持ち、種別が全 datum を
|
|
371
|
+
* 描く」 構造で揃っており、節を増やすと配置と組み立て器の両方が別経路になる。
|
|
372
|
+
*
|
|
373
|
+
* 3 時点以上は持たない (cdl#551 で対象外)。 足す時は欄の形ごと決め直す。
|
|
374
|
+
*/
|
|
375
|
+
previous?: BoundNumber;
|
|
338
376
|
tone?: Tone;
|
|
339
377
|
};
|
|
340
378
|
|
|
@@ -452,6 +490,8 @@ export type CdlEdge = {
|
|
|
452
490
|
side?: Side;
|
|
453
491
|
/** visual style、 default "solid" */
|
|
454
492
|
style?: EdgeStyle;
|
|
493
|
+
/** 端の形、 default "triangle" (#560)。 線の種類とは別の軸 */
|
|
494
|
+
head?: EdgeHead;
|
|
455
495
|
/**
|
|
456
496
|
* FSM transition の guard 条件 (例 "if validated")。
|
|
457
497
|
* stateMachine preset では sub に併合される、 text-dsl v0.5 では guard 単独で渡せる。
|
|
@@ -2158,6 +2198,30 @@ export type CdlPhase = {
|
|
|
2158
2198
|
* 書かない phase では従来どおり全長で描く (DOM に dash 属性も付かない)。
|
|
2159
2199
|
*/
|
|
2160
2200
|
draw?: string[];
|
|
2201
|
+
/**
|
|
2202
|
+
* `draw` の対象を描き終えるまでに、 phase の何割を使うか (cdl#570)。
|
|
2203
|
+
*
|
|
2204
|
+
* 0 より大きく 1 以下の割合で書く。 `0.4` なら phase の進みが 0.4 に達した時点で
|
|
2205
|
+
* 描き終わり、 残りの 0.6 は描き終わった図のまま値だけが動く。 既定 (書かない) は 1 で、
|
|
2206
|
+
* 従来どおり phase の終わりに描き終わる。
|
|
2207
|
+
*
|
|
2208
|
+
* **描く速さと phase の長さを切り離すための欄**。 進みは phase の進みそのものなので、
|
|
2209
|
+
* これが無いと「線はゆっくり引きたいが値の移りは短くしたい」 が書けず、 描く速さのために
|
|
2210
|
+
* phase の長さを動かすことになる。 phase の長さを動かすと同じ phase の `tweens` まで
|
|
2211
|
+
* 遅くなり、 値が移るだけの phase を長くする形の回避が利用側で要る。
|
|
2212
|
+
*
|
|
2213
|
+
* **割合で持ち、 秒数では持たない**。 割合なら phase に収まるので、 再生速度の切替
|
|
2214
|
+
* (0.5x / 2x) がそのまま効き、 phase をはみ出した時の扱いを決めずに済む。 秒数だと
|
|
2215
|
+
* 切替との掛かり方とはみ出しの扱いを新たに決めることになる。
|
|
2216
|
+
*
|
|
2217
|
+
* 掛かる相手は `draw` に載せた node だけ。 同じ phase の他の node と edge、 および
|
|
2218
|
+
* `tweens` は元の進みのまま動く。
|
|
2219
|
+
*
|
|
2220
|
+
* 範囲外 (0 以下 / 1 超 / NaN / Infinity) は **1 として扱う**。 描く動きは見え方であって
|
|
2221
|
+
* 図の意味ではないため、 書き間違いで図が出なくなる方が損が大きい。 書き間違いは
|
|
2222
|
+
* `validate` が warning で知らせる。
|
|
2223
|
+
*/
|
|
2224
|
+
drawRatio?: number;
|
|
2161
2225
|
/** 数値 state の線形補間 (write phase で 100→90 等) */
|
|
2162
2226
|
tweens: Array<{ stateId: string; from: number; to: number }>;
|
|
2163
2227
|
/** 任意型 state の即時切替 (この phase に到達したら値を上書き、 lerp なし) */
|
package/src/validate.ts
CHANGED
|
@@ -168,11 +168,6 @@ export function validate(diag: CdlDiagram): void {
|
|
|
168
168
|
`[cdl] unknown-ref: edge "${e.id}" の to "${e.to}" が node に存在しません${suggest(e.to, nodeIds, "node")}\n → 修正例: .node("${e.to}", { lane: "...", stack: 0, kind: "...", title: "..." }) を edge 宣言前に追加`,
|
|
169
169
|
);
|
|
170
170
|
}
|
|
171
|
-
if (e.from === e.to) {
|
|
172
|
-
errors.push(
|
|
173
|
-
`[cdl] self-loop: edge "${e.id}" の from と to が同じ node "${e.from}" です (self-loop は現状未対応)\n → 別 node を作って 2 段に分けるか、 後続 PR の self-loop support を待ってください`,
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
171
|
}
|
|
177
172
|
|
|
178
173
|
// ───────────────────────────────────────────────────────────
|
|
@@ -200,6 +195,19 @@ export function validate(diag: CdlDiagram): void {
|
|
|
200
195
|
);
|
|
201
196
|
}
|
|
202
197
|
}
|
|
198
|
+
// 描く速さの割合 (cdl#570)。 範囲外は描画側が 1 に落とすので図は出るが、 書いた指定は
|
|
199
|
+
// 何もしない。 黙って捨てると「書いたのに速さが変わらない」 が手掛かりなしで起きる
|
|
200
|
+
if (p.drawRatio !== undefined) {
|
|
201
|
+
if (!Number.isFinite(p.drawRatio) || p.drawRatio <= 0 || p.drawRatio > 1) {
|
|
202
|
+
warnings.push(
|
|
203
|
+
`phase "${p.id}" の drawRatio ${p.drawRatio} は範囲外 (0 より大きく 1 以下)。 1 として扱う`,
|
|
204
|
+
);
|
|
205
|
+
} else if ((p.draw ?? []).length === 0) {
|
|
206
|
+
warnings.push(
|
|
207
|
+
`phase "${p.id}" は drawRatio を持つが draw が空。 描く対象が無いので何も変わらない`,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
203
211
|
for (const t of p.tweens) {
|
|
204
212
|
if (!stateIds.has(t.stateId)) {
|
|
205
213
|
errors.push(`[cdl] unknown-ref: phase "${p.id}" tween 対象 state "${t.stateId}" 未定義`);
|