@cardenelabs/cdl 0.14.0 → 0.15.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 +223 -1
- package/README.md +20 -1
- package/dist/index.cjs +1233 -91
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -5
- package/dist/index.d.ts +21 -5
- package/dist/index.js +1232 -92
- 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 +33 -3
- 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
|
@@ -1538,13 +1538,23 @@ export function quadrant(preset: QuadrantPreset): QuadrantBuilder {
|
|
|
1538
1538
|
|
|
1539
1539
|
// ─── chart (Pie / Bar / Line 統合) ─────────────────────────────
|
|
1540
1540
|
|
|
1541
|
-
export type ChartType =
|
|
1541
|
+
export type ChartType =
|
|
1542
|
+
| "pie"
|
|
1543
|
+
| "bar"
|
|
1544
|
+
| "line"
|
|
1545
|
+
| "gauge"
|
|
1546
|
+
| "radial"
|
|
1547
|
+
| "stat"
|
|
1548
|
+
| "waffle"
|
|
1549
|
+
| "stacked-bar";
|
|
1542
1550
|
|
|
1543
1551
|
export type ChartDatum = {
|
|
1544
1552
|
id: string;
|
|
1545
1553
|
label: string;
|
|
1546
1554
|
/** 棒 / 折れ線 / 扇の大きさ。 `{名前}` を書くと状態から読む (#468) */
|
|
1547
1555
|
value: number | BindingRef;
|
|
1556
|
+
/** 前の時点の値 (cdl#551)。 `type: stacked-bar` で書くと帯が 2 本になる */
|
|
1557
|
+
previous?: number | BindingRef;
|
|
1548
1558
|
/** 個別 tone (default は preset の defaultTone) */
|
|
1549
1559
|
tone?: Tone;
|
|
1550
1560
|
};
|
|
@@ -1580,7 +1590,17 @@ export function chart(preset: ChartPreset): ChartBuilder {
|
|
|
1580
1590
|
const canvasW = toGridSize(preset.itemWidth ?? 640);
|
|
1581
1591
|
// 高さも格子の倍数にする。 line / bar は 360 だったが 360 % 16 = 8 で下端が格子から外れ、
|
|
1582
1592
|
// 箱の 4 辺が載る条件を満たさなかった。
|
|
1583
|
-
|
|
1593
|
+
// 円と半円は縦を使わないぶん低い。 折れ線と棒は軸のぶん高さが要る
|
|
1594
|
+
const canvasH = toGridSize(
|
|
1595
|
+
preset.type === "pie" ||
|
|
1596
|
+
preset.type === "gauge" ||
|
|
1597
|
+
preset.type === "radial" ||
|
|
1598
|
+
preset.type === "stat" ||
|
|
1599
|
+
preset.type === "waffle" ||
|
|
1600
|
+
preset.type === "stacked-bar"
|
|
1601
|
+
? 320
|
|
1602
|
+
: 360,
|
|
1603
|
+
);
|
|
1584
1604
|
const b = diagram(preset.id, { topic: preset.topic });
|
|
1585
1605
|
const data: ChartDatum[] = [];
|
|
1586
1606
|
|
|
@@ -1588,6 +1608,11 @@ export function chart(preset: ChartPreset): ChartBuilder {
|
|
|
1588
1608
|
line: "chart-line" as const,
|
|
1589
1609
|
pie: "chart-pie" as const,
|
|
1590
1610
|
bar: "chart-bar" as const,
|
|
1611
|
+
gauge: "chart-gauge" as const,
|
|
1612
|
+
radial: "chart-radial" as const,
|
|
1613
|
+
stat: "chart-stat" as const,
|
|
1614
|
+
waffle: "chart-waffle" as const,
|
|
1615
|
+
"stacked-bar": "chart-stacked-bar" as const,
|
|
1591
1616
|
};
|
|
1592
1617
|
|
|
1593
1618
|
const api: ChartBuilder = {
|
|
@@ -1606,7 +1631,12 @@ export function chart(preset: ChartPreset): ChartBuilder {
|
|
|
1606
1631
|
eyebrow: preset.type,
|
|
1607
1632
|
w: canvasW,
|
|
1608
1633
|
h: canvasH,
|
|
1609
|
-
chartData: data.map((d) => ({
|
|
1634
|
+
chartData: data.map((d) => ({
|
|
1635
|
+
label: d.label,
|
|
1636
|
+
value: d.value,
|
|
1637
|
+
...(d.previous === undefined ? {} : { previous: d.previous }),
|
|
1638
|
+
tone: d.tone,
|
|
1639
|
+
})),
|
|
1610
1640
|
});
|
|
1611
1641
|
b.phase(
|
|
1612
1642
|
"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}" 未定義`);
|
package/src/visual-validate.ts
CHANGED
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
* test が無いと、 次に種別を足した時に同じ状態へ戻る。
|
|
78
78
|
* ───────────────────────────────────────────────────────────
|
|
79
79
|
*/
|
|
80
|
-
import type
|
|
80
|
+
import { EDGE_HEADS, type CdlDiagram, type LaidDiagram, type LaidNode, type NodeKind } from "./types";
|
|
81
81
|
import { CLEARANCE_LANE_LABEL, DIST_LABEL_PATH_MAX } from "./layout/clearance-constants";
|
|
82
82
|
import { layout } from "./layout";
|
|
83
83
|
import {
|
|
@@ -134,7 +134,9 @@ import {
|
|
|
134
134
|
computeLabelBBoxWorld,
|
|
135
135
|
hasRenderedLabel,
|
|
136
136
|
isRenderedNode,
|
|
137
|
+
genericTitleBaselineY,
|
|
137
138
|
rendersRows,
|
|
139
|
+
TITLE_ROW_MIN_GAP,
|
|
138
140
|
rendersTitleText,
|
|
139
141
|
fitsOwnTitleWidth,
|
|
140
142
|
rowBaselineY,
|
|
@@ -238,6 +240,11 @@ export const GRID_ALIGNED_KINDS: ReadonlySet<NodeKind> = new Set([
|
|
|
238
240
|
"chart-pie",
|
|
239
241
|
"chart-line",
|
|
240
242
|
"chart-bar",
|
|
243
|
+
"chart-gauge",
|
|
244
|
+
"chart-radial",
|
|
245
|
+
"chart-stat",
|
|
246
|
+
"chart-waffle",
|
|
247
|
+
"chart-stacked-bar",
|
|
241
248
|
"funnel-stages",
|
|
242
249
|
"quadrant-matrix",
|
|
243
250
|
]);
|
|
@@ -374,6 +381,7 @@ export type VisualAxis =
|
|
|
374
381
|
| "column-gap-uniform"
|
|
375
382
|
// Axis 68 (#387) = 行を描かない種別に rows が書かれている (書いた内容が画面に出ない)。
|
|
376
383
|
| "rows-not-rendered"
|
|
384
|
+
| "title-row-overlap"
|
|
377
385
|
// #393 = 検査に渡された図の形自体が壊れている (検査できなかったことを結果として出す)。
|
|
378
386
|
| "malformed-input"
|
|
379
387
|
// #393 = 検査が例外で中断したが、 原因が入力の破綻か検査側の欠陥か **決められない**。
|
|
@@ -498,6 +506,7 @@ function emptyCounts(): Record<VisualAxis, number> {
|
|
|
498
506
|
"row-gap-uniform": 0,
|
|
499
507
|
"column-gap-uniform": 0,
|
|
500
508
|
"rows-not-rendered": 0,
|
|
509
|
+
"title-row-overlap": 0,
|
|
501
510
|
"malformed-input": 0,
|
|
502
511
|
"validation-interrupted": 0,
|
|
503
512
|
};
|
|
@@ -2736,6 +2745,36 @@ function runAxes(
|
|
|
2736
2745
|
);
|
|
2737
2746
|
}
|
|
2738
2747
|
|
|
2748
|
+
// ───────────────────────────────────────────────────────────
|
|
2749
|
+
// Axis 69: title-row-overlap (題の字と 1 行目の字が重なる)
|
|
2750
|
+
//
|
|
2751
|
+
// 判定 = 題の baseline から下へ伸びる字形と、 1 行目の baseline から上へ伸びる字形が
|
|
2752
|
+
// 触れるか。 位置は `spec.ts` の 2 関数 (`genericTitleBaselineY` / `rowBaselineY`) から
|
|
2753
|
+
// 取るので、 描画側を変えれば本軸も一緒に動く。
|
|
2754
|
+
//
|
|
2755
|
+
// 題と行の縦位置は形ごとに別々の式で決まっていた時期があり、 円柱と中央寄せの 3 種で
|
|
2756
|
+
// 字が重なっていた (#558)。 軸 4 (`row-format`) と軸 16 (`row-vertical-spacing`) は
|
|
2757
|
+
// 行どうしと枠との関係しか見ないため、 題との関係は誰も見ていなかった。
|
|
2758
|
+
//
|
|
2759
|
+
// `storage` は専用の表組みで題と列の間に区切り線を引いており、 位置の出所が別なので対象外。
|
|
2760
|
+
// ───────────────────────────────────────────────────────────
|
|
2761
|
+
for (const n of diag.nodes) {
|
|
2762
|
+
if (!n.rows || n.rows.length === 0) continue;
|
|
2763
|
+
if (!rendersRows(n.kind)) continue;
|
|
2764
|
+
if (n.kind === "storage") continue;
|
|
2765
|
+
// 高さを取れない node は測らない。 0 に潰すと中央寄せの題が上端に来て誤って重なる判定になる
|
|
2766
|
+
if (typeof n.h !== "number") continue;
|
|
2767
|
+
const 題 = genericTitleBaselineY(n.kind, n.h, true);
|
|
2768
|
+
const 行 = rowBaselineY(n.kind, 0);
|
|
2769
|
+
if (行 === null) continue;
|
|
2770
|
+
const 間 = 行 - 題;
|
|
2771
|
+
if (間 >= TITLE_ROW_MIN_GAP) continue;
|
|
2772
|
+
push(
|
|
2773
|
+
"title-row-overlap",
|
|
2774
|
+
`node "${n.id}" (kind=${n.kind ?? "未指定"}) の題 baseline y=${題.toFixed(0)} と 1 行目 baseline y=${行.toFixed(0)} の間が ${間.toFixed(0)} world で、 見込む字形の高さ ${TITLE_ROW_MIN_GAP.toFixed(0)} に届かない (字が重なる)`,
|
|
2775
|
+
);
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2739
2778
|
// ───────────────────────────────────────────────────────────
|
|
2740
2779
|
// Axis 17: group-boundary-clearance (topology group の boundary padding + 外部 clearance)
|
|
2741
2780
|
//
|
|
@@ -3307,6 +3346,14 @@ function runAxes(
|
|
|
3307
3346
|
`edge "${e.id}" tone "${e.tone}" が TONE_COLORS 定義に存在しない、 marker "cdl-arrow-${e.tone}" dead ref`,
|
|
3308
3347
|
);
|
|
3309
3348
|
}
|
|
3349
|
+
// 端の形も同じ理由で dead ref になる (#560)。 marker は EDGE_HEADS の分しか定義しないので、
|
|
3350
|
+
// 定義外の値が来ると "cdl-arrow-<tone>-<未定義>" を引いて矢頭が消える。
|
|
3351
|
+
if (e.head !== undefined && !EDGE_HEADS.includes(e.head)) {
|
|
3352
|
+
push(
|
|
3353
|
+
"marker-gradient-def-integrity",
|
|
3354
|
+
`edge "${e.id}" head "${e.head}" が EDGE_HEADS 定義に存在しない、 marker "cdl-arrow-${e.tone}-${e.head}" dead ref`,
|
|
3355
|
+
);
|
|
3356
|
+
}
|
|
3310
3357
|
}
|
|
3311
3358
|
|
|
3312
3359
|
// ───────────────────────────────────────────────────────────
|