@cardenelabs/cdl 0.30.3 → 0.31.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.
@@ -1,4 +1,8 @@
1
1
  import { useMemo, type JSX } from "react";
2
+ import { 折れ線の目盛, type ChartLineScale } from "../kinds/chart-line";
3
+ import { 描く進みに直す } from "../kinds/draw-ratio";
4
+ import { lifelineStartYs, uniformLifelineEndY } from "../layout/lifeline";
5
+ import { buildDiagramAltText } from "../layout/spec";
2
6
  import {
3
7
  EDGE_HEADS,
4
8
  EDGE_HEAD_FILLS,
@@ -7,13 +11,18 @@ import {
7
11
  type CdlPhase,
8
12
  type LaidDiagram,
9
13
  } from "../types";
14
+ import {
15
+ EDGE_HEAD_MUTED_MARKER_ID,
16
+ EDGE_HEAD_SHAPE,
17
+ edgeHeadMarkerId,
18
+ edgeHeadMarkerName,
19
+ edgeHeadPaint,
20
+ } from "./edge-head";
10
21
  import { CdlEdgeView, CdlEdgeGlowView, CdlEdgeLabelView } from "./edges";
11
22
  import { CdlNodeView } from "./nodes";
23
+ import { resolveChartData } from "./payload-binding";
12
24
  import { TONE, TONE_HEX } from "./tone";
13
- import { EDGE_HEAD_SHAPE, edgeHeadMarkerId, edgeHeadPaint } from "./edge-head";
14
- import { buildDiagramAltText } from "../layout/spec";
15
- import { lifelineStartYs, uniformLifelineEndY } from "../layout/lifeline";
16
- import { 描く進みに直す } from "../kinds/draw-ratio";
25
+ import { computeStateValues } from "./utils";
17
26
 
18
27
  export function CdlStage({
19
28
  laid,
@@ -81,6 +90,28 @@ export function CdlStage({
81
90
  // この phase で左の起点から描き始める node (cdl#512)。 `activate` とは別集合で持つ =
82
91
  // 焦点が当たり続ける図で毎 phase 引き直すことになるため、 光っているかを引き金にしない
83
92
  const drawSet = new Set(currentPhase?.draw ?? []);
93
+ // 目盛は全 phase を見渡せる stage で一度だけ決める。 node 内で現在値から測ると段ごとに軸が動く。
94
+ const chartLineScales = useMemo(() => {
95
+ const nodes = laid.nodes.filter((node) => node.kind === "chart-line");
96
+ if (nodes.length === 0) return new Map<string, ChartLineScale>();
97
+
98
+ const frames = [
99
+ // phase index -1 はどの段にも入る前の初期値。
100
+ computeStateValues(laid, -1, 0),
101
+ ...laid.phases.flatMap((_, phaseIndex) => [
102
+ computeStateValues(laid, phaseIndex, 0),
103
+ computeStateValues(laid, phaseIndex, 1),
104
+ ]),
105
+ ];
106
+ const scales = new Map<string, ChartLineScale>();
107
+ for (const node of nodes) {
108
+ const values = frames.flatMap((stateValues) =>
109
+ resolveChartData(node.chartData ?? [], stateValues).map((datum) => datum.value),
110
+ );
111
+ scales.set(node.id, 折れ線の目盛(values));
112
+ }
113
+ return scales;
114
+ }, [laid]);
84
115
  // 描く対象から見た進み (cdl#570)。 `drawRatio` を書かない phase では `progress` と同値で、
85
116
  // 描く速さを phase の長さから切り離すためだけに使う
86
117
  const 描く進み = 描く進みに直す(progress, currentPhase?.drawRatio);
@@ -100,6 +131,67 @@ export function CdlStage({
100
131
  uniformFooterTop: uniformLifelineEndY(laid.lanes, laid.nodes, starts),
101
132
  };
102
133
  }, [laid]);
134
+ const edgeHeadMarkers = useMemo(() => {
135
+ const referenced = new Set<string>();
136
+ const add = (
137
+ tone: string,
138
+ head: (typeof laid.edges)[number]["head"],
139
+ fill: (typeof laid.edges)[number]["headFill"],
140
+ ): void => {
141
+ for (const small of [false, true]) {
142
+ const id = edgeHeadMarkerName(tone, head, small, fill);
143
+ if (id !== undefined) referenced.add(id);
144
+ }
145
+ };
146
+ for (const edge of laid.edges) {
147
+ add(edge.tone, edge.head, edge.headFill);
148
+ if (edge.tailHead !== undefined) add(edge.tone, edge.tailHead, edge.tailHeadFill);
149
+ }
150
+
151
+ return Object.entries(TONE).flatMap(([tone, color]) =>
152
+ (
153
+ [
154
+ [false, 14],
155
+ [true, 10],
156
+ ] as const
157
+ ).flatMap(([small, size]) =>
158
+ EDGE_HEADS.flatMap((head) =>
159
+ EDGE_HEAD_FILLS.map((fill) => {
160
+ const 形 = EDGE_HEAD_SHAPE[head];
161
+ const id = edgeHeadMarkerId(tone, head, small, fill);
162
+ if (!referenced.has(id) || 形.d === "") return null;
163
+ if (fill !== EDGE_HEAD_FILL_DEFAULT && !形.filled) return null;
164
+ const 塗 = edgeHeadPaint(head, fill, color);
165
+ return (
166
+ <marker
167
+ key={id}
168
+ id={id}
169
+ viewBox={`0 0 ${形.w} ${形.h}`}
170
+ refX={形.refX}
171
+ refY={形.h / 2}
172
+ markerWidth={(size * (形.scale ?? 1) * 形.w) / 形.h}
173
+ markerHeight={size * (形.scale ?? 1)}
174
+ markerUnits="userSpaceOnUse"
175
+ orient="auto-start-reverse"
176
+ >
177
+ <path
178
+ data-cdl-role="edge-arrowhead"
179
+ data-cdl-edge-head={head}
180
+ data-cdl-edge-head-fill={fill}
181
+ d={形.d}
182
+ fill={塗.fill}
183
+ stroke={塗.stroke}
184
+ strokeWidth={塗.strokeWidth}
185
+ strokeLinecap={塗.strokeWidth === undefined ? undefined : "round"}
186
+ strokeLinejoin={塗.strokeWidth === undefined ? undefined : "round"}
187
+ />
188
+ </marker>
189
+ );
190
+ }),
191
+ ),
192
+ ),
193
+ );
194
+ }, [laid]);
103
195
  const diagramScale = laid.diagramScale ?? 1;
104
196
  return (
105
197
  <div className={compact ? "px-3 py-3" : "px-6 py-6"} style={compact ? undefined : { minHeight: 460 }}>
@@ -157,55 +249,12 @@ export function CdlStage({
157
249
  {/* CAR-643 = cdl は geometry only。 shadow / border effect の filter は cdl SVG 内に
158
250
  持たず、 consumer app (dragon 等) が独自 filter を CSS 経由で data-cdl-role selector
159
251
  に適用する。 marker (cdl-arrow-*) は edge routing の SSOT なので cdl 内に維持。 */}
160
- {/* 光っている edge (14) と光っていない edge 用 (10) を tone ごとに用意する。
161
- 光っていない edge も tone の色で描くが、 矢頭の大きさは従来通り小さいまま保つ
162
- (Issue の制約 = 色だけを足す)。 */}
163
- {Object.entries(TONE).flatMap(([tone, color]) =>
164
- ([["", 14], ["-sm", 10]] as const).flatMap(([suffix, size]) =>
165
- EDGE_HEADS.flatMap((head) =>
166
- // 塗り方は形と別の軸 (#578)。 塗らない形では 2 つが同じ名前に畳まれるので、
167
- // `edgeHeadMarkerId` が返す名前で重複を外す
168
- EDGE_HEAD_FILLS.map((fill) => {
169
- const 形 = EDGE_HEAD_SHAPE[head];
170
- const id = edgeHeadMarkerId(tone, head, suffix === "-sm", fill);
171
- // 何も描かない端は marker を定義しない (#578)。 線側も参照しない
172
- if (形.d === "") return null;
173
- if (fill !== EDGE_HEAD_FILL_DEFAULT && !形.filled) return null;
174
- const 塗 = edgeHeadPaint(head, fill, color);
175
- return (
176
- <marker
177
- key={id}
178
- id={id}
179
- viewBox={`0 0 ${形.w} ${形.h}`}
180
- refX={形.refX}
181
- refY={形.h / 2}
182
- // 枠の縦を 10 に揃えてあるので、横だけが形の比で伸びる
183
- markerWidth={(size * (形.scale ?? 1) * 形.w) / 形.h}
184
- markerHeight={size * (形.scale ?? 1)}
185
- markerUnits="userSpaceOnUse"
186
- orient="auto-start-reverse"
187
- >
188
- <path
189
- data-cdl-role="edge-arrowhead"
190
- data-cdl-edge-head={head}
191
- data-cdl-edge-head-fill={fill}
192
- d={形.d}
193
- fill={塗.fill}
194
- stroke={塗.stroke}
195
- strokeWidth={塗.strokeWidth}
196
- strokeLinecap={塗.strokeWidth === undefined ? undefined : "round"}
197
- strokeLinejoin={塗.strokeWidth === undefined ? undefined : "round"}
198
- />
199
- </marker>
200
- );
201
- }),
202
- ),
203
- ),
204
- )}
252
+ {/* 大小は phase ごとに入れ替わるため、配置が参照する両方を保持する。 */}
253
+ {edgeHeadMarkers}
205
254
  {/* tone が TONE 定義に無い edge の受け皿。 visual-validate の tone-marker-ref 軸が
206
255
  dead ref を検知するが、 検知するまでの間に矢頭が消えないようにする。 */}
207
256
  <marker
208
- id="cdl-arrow-muted"
257
+ id={EDGE_HEAD_MUTED_MARKER_ID}
209
258
  viewBox="0 0 10 10"
210
259
  refX="9"
211
260
  refY="5"
@@ -315,6 +364,7 @@ export function CdlStage({
315
364
  progress={描く ? 描く進み : progress}
316
365
  stateValues={stateValues}
317
366
  currentPhaseId={currentPhase?.id}
367
+ chartLineScale={chartLineScales.get(node.id)}
318
368
  />
319
369
  );
320
370
  return <g key={node.id}>{view}</g>;
@@ -34,6 +34,7 @@ const NUMERIC_FIELDS = ["opacity", "renderOffsetX", "renderOffsetY"] as const;
34
34
  * |---|---|
35
35
  * | `id` / `lane` / `kind` / `role` / `tone` | 名前や区分で、 描画側が置換に掛けない |
36
36
  * | `stack` / `w` / `h` / `pos*` / `ganttAxisMax` | 数のみ。 型が `number` で文字を受けない |
37
+ * | `chartFillUnder` / `chartValueRise` / `chartTrace` | 動きを選ぶ真偽値で、文字を受けない |
37
38
  * | `shape` | 入れ子の構造。 下の `shapeTexts` が中の文字を全部拾う |
38
39
  * | `*Data` | 図表 payload。 入れ子の構造で、 下の `payloadTexts` が中の文字を拾う |
39
40
  */
@@ -52,6 +53,9 @@ type 読まない欄 =
52
53
  | "tone"
53
54
  | "shape"
54
55
  | "chartData"
56
+ | "chartFillUnder"
57
+ | "chartValueRise"
58
+ | "chartTrace"
55
59
  | "ganttData"
56
60
  | "ganttAxisMax"
57
61
  | "mindData"
package/src/types.ts CHANGED
@@ -463,6 +463,12 @@ export type CdlNode = {
463
463
  * 数値 chart の全 datum を 1 node に格納し、 kinds/chart-*.tsx が自己完結で SVG 描画する。
464
464
  */
465
465
  chartData?: ChartDatumPayload[];
466
+ /** chart-line の線から下を、上端 0.22 から下端 0 へ薄く塗る */
467
+ chartFillUnder?: boolean;
468
+ /** chart-line の値を、点への到着直後だけ下からせり上げる */
469
+ chartValueRise?: boolean;
470
+ /** chart-line を描き切った後、粒で経路をなぞって点を順に出す */
471
+ chartTrace?: boolean;
466
472
  /**
467
473
  * gantt-timeline 専用の task payload。 task 全件を 1 node に格納し、
468
474
  * kinds/gantt.tsx が縦軸 task lane + 横軸日付 rect bar で SVG 描画する。