@cardenelabs/cdl 0.10.0 → 0.12.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.
@@ -2,6 +2,7 @@ import type { JSX } from "react";
2
2
  import type { LaidNode, Tone } from "../types";
3
3
  import { TONE } from "../render/tone";
4
4
  import { resolveGanttData } from "../render/payload-binding";
5
+ import { 進みを畳む } from "./draw-progress";
5
6
 
6
7
  /**
7
8
  * gantt-timeline kind ... 1 node に task 配列 (ganttData) を保持し、 帯状 timeline を SVG 描画する。
@@ -13,10 +14,16 @@ export function GanttNode({
13
14
  node,
14
15
  active,
15
16
  stateValues = {},
17
+ drawing = false,
18
+ progress = 1,
16
19
  }: {
17
20
  node: LaidNode;
18
21
  active: boolean;
19
22
  stateValues?: Record<string, string>;
23
+ /** この phase の `draw` に載っているか (cdl#521) */
24
+ drawing?: boolean;
25
+ /** phase の進み (0→1)。 `drawing` が true の時だけ読む */
26
+ progress?: number;
20
27
  }): JSX.Element {
21
28
  const x0 = node.cx - node.w / 2;
22
29
  const y0 = node.cy - node.h / 2;
@@ -166,6 +173,14 @@ export function GanttNode({
166
173
  const 帯の左 = (idx: number): number => xAt(idx) + 帯の余白 / 2;
167
174
  const 帯の右 = (idx: number): number => xAt(idx) + cellW - 帯の余白 / 2;
168
175
 
176
+ /**
177
+ * 帯がどこまで伸びたか (0..1、 cdl#521)。 描く指定が無ければ常に 1 = 全長。
178
+ *
179
+ * **帯ごとに始端が違う**。 棒グラフは全部が同じ横軸から伸びるが、工程表は工程ごとに
180
+ * 始まる位置が違うため、包みも帯ごとに作って不動点をその帯の左端に置く。
181
+ */
182
+ const 伸び = 進みを畳む(drawing, progress);
183
+
169
184
  const taskById = new Map(整えた.map((t) => [t.id, t]));
170
185
  const rowOf = new Map(整えた.map((t, i) => [t.id, i]));
171
186
 
@@ -243,7 +258,12 @@ export function GanttNode({
243
258
  // 余白の決め方を変えた時に負の幅を出さないために残す
244
259
  const bw = Math.max(0, 帯の右(t.endIdx) - bx);
245
260
  return (
246
- <g key={`bar-${t.id}`}>
261
+ <g
262
+ key={`bar-${t.id}`}
263
+ {...(drawing
264
+ ? { transform: `translate(${bx} 0) scale(${伸び} 1) translate(${-bx} 0)` }
265
+ : {})}
266
+ >
247
267
  <rect
248
268
  data-cdl-role="gantt-bar"
249
269
  data-cdl-unresolved={t.unresolved ? "true" : undefined}
@@ -304,6 +324,9 @@ export function GanttNode({
304
324
  ` L ${enterX} ${(parentMidY + tipY) / 2} L ${enterX} ${tipY} L ${tipX} ${tipY}`;
305
325
  const arrowHeadPath = `M ${tipX + arrowHeadSize} ${tipY} L ${tipX} ${tipY - arrowHeadSize / 2} L ${tipX} ${tipY + arrowHeadSize / 2} Z`;
306
326
  return (
327
+ // 矢印は両端の帯が出てから描く。 帯が無い場所へ矢印だけが伸びると、
328
+ // 指す先の無い線になる (cdl#521)
329
+ (drawing && 伸び < 1) ? null : (
307
330
  <g key={`dep-${parent.id}-${t.id}`} data-cdl-role="gantt-arrow">
308
331
  <path
309
332
  d={path}
@@ -321,6 +344,7 @@ export function GanttNode({
321
344
  strokeLinejoin="round"
322
345
  />
323
346
  </g>
347
+ )
324
348
  );
325
349
  })}
326
350
  </g>
@@ -3,6 +3,7 @@ import { 箱に積む } from "./box-lines";
3
3
  import type { LaidNode, Tone, MindBranchNode } from "../types";
4
4
  import { TONE } from "../render/tone";
5
5
  import { resolveMindData } from "../render/payload-binding";
6
+ import { 進みを畳む, 伸ばすdash, 段の伸び } from "./draw-progress";
6
7
 
7
8
  const MIND_TONES: Tone[] = ["accent", "teal", "success", "warning", "info", "error"];
8
9
 
@@ -31,10 +32,16 @@ export function MindMapNode({
31
32
  node,
32
33
  active,
33
34
  stateValues = {},
35
+ drawing = false,
36
+ progress = 1,
34
37
  }: {
35
38
  node: LaidNode;
36
39
  active: boolean;
37
40
  stateValues?: Record<string, string>;
41
+ /** この phase の `draw` に載っているか (cdl#520) */
42
+ drawing?: boolean;
43
+ /** phase の進み (0→1)。 `drawing` が true の時だけ読む */
44
+ progress?: number;
38
45
  }): JSX.Element {
39
46
  const x0 = node.cx - node.w / 2;
40
47
  const y0 = node.cy - node.h / 2;
@@ -50,6 +57,16 @@ export function MindMapNode({
50
57
 
51
58
  const layout = computeMindMapLayout(mindData, canvasCx, canvasCy, canvasW, canvasH);
52
59
 
60
+ /**
61
+ * 枝がどこまで伸びたか (0..1、 cdl#520)。 描く指定が無ければ常に 1 = 全部出ている。
62
+ *
63
+ * 中心に近い段から順に伸ばす。 全部を同時に伸ばすと、離れた枝が宙に浮いたまま伸びる。
64
+ */
65
+ const 伸び = 進みを畳む(drawing, progress);
66
+ const 最大深さ = layout.nodes.reduce((m, n) => Math.max(m, n.depth), 0);
67
+ /** その箱が出るか。 中心 (深さ 0) は起点なので進み 0 でも出す */
68
+ const 箱が出る = (深さ: number): boolean => !drawing || 段の伸び(伸び, 深さ, 最大深さ) >= 1;
69
+
53
70
  return (
54
71
  <g transform={`translate(${x0} ${y0})`}>
55
72
  <rect
@@ -64,20 +81,29 @@ export function MindMapNode({
64
81
  strokeWidth={active ? 3 : 1.25}
65
82
  />
66
83
 
67
- {layout.edges.map((e, i) => (
68
- <path
69
- key={`edge-${i}`}
70
- data-cdl-role="mind-edge"
71
- d={e.d}
72
- fill="none"
73
- stroke={toneColor(e.tone)}
74
- strokeWidth={2.5}
75
- strokeOpacity={0.55}
76
- strokeLinecap="round"
77
- />
78
- ))}
84
+ {layout.edges.map((e, i) => {
85
+ // 循環を閉じる枝は根から外へ向かう枝ではないため、全 node が出てから表示する
86
+ const 枝の伸び = e.deferUntilComplete
87
+ ? (伸び >= 1 ? 1 : 0)
88
+ : 段の伸び(伸び, e.depth, 最大深さ);
89
+ return (
90
+ <path
91
+ key={`edge-${i}`}
92
+ data-cdl-role="mind-edge"
93
+ d={e.d}
94
+ fill="none"
95
+ stroke={toneColor(e.tone)}
96
+ strokeWidth={2.5}
97
+ strokeOpacity={0.55}
98
+ strokeLinecap="round"
99
+ {...伸ばすdash(drawing, 枝の伸び)}
100
+ />
101
+ );
102
+ })}
79
103
 
80
104
  {layout.nodes.map((n) => {
105
+ // 枝が届いてから箱を出す。 届く前に出すと、繋がっていない箱が宙に浮く
106
+ if (!箱が出る(n.depth)) return null;
81
107
  // 補足は箱の中の 2 行目に出す (#471)。 箱の高さは変えないため、 入らない時は落ちる
82
108
  const 行 = 箱に積む(
83
109
  [
@@ -150,6 +176,8 @@ type LayoutNode = {
150
176
  h: number;
151
177
  tone?: Tone;
152
178
  isRoot: boolean;
179
+ /** 中心からの段数 (中心が 0、 cdl#520)。 段ごとに順に伸ばすのに使う */
180
+ depth: number;
153
181
  /** 名前を状態から解決できなかった項目に付く印 (#467) */
154
182
  unresolved?: true;
155
183
  };
@@ -157,6 +185,10 @@ type LayoutNode = {
157
185
  type LayoutEdge = {
158
186
  d: string;
159
187
  tone?: Tone;
188
+ /** 枝の先 (子) の段数 (cdl#520)。 段ごとに順に伸ばすのに使う */
189
+ depth: number;
190
+ /** 循環により、元の親が layout 上で子と同じ深さか深くなった枝 */
191
+ deferUntilComplete: boolean;
160
192
  };
161
193
 
162
194
  export function computeMindMapLayout(
@@ -179,6 +211,8 @@ export function computeMindMapLayout(
179
211
  w: rootW,
180
212
  h: rootH,
181
213
  isRoot: true,
214
+ // 中心は起点なので段数 0 (cdl#520)
215
+ depth: 0,
182
216
  ...((mindData as { unresolved?: true }).unresolved ? { unresolved: true as const } : {}),
183
217
  });
184
218
 
@@ -339,6 +373,8 @@ export function computeMindMapLayout(
339
373
  y,
340
374
  w,
341
375
  h,
376
+ // 第 1 階層が段数 1 (`subDepth` は 0 始まり、 cdl#520)
377
+ depth: subDepth + 1,
342
378
  tone: myTone,
343
379
  isRoot: false,
344
380
  ...((node as { unresolved?: true }).unresolved ? { unresolved: true as const } : {}),
@@ -388,6 +424,9 @@ export function computeMindMapLayout(
388
424
  }
389
425
  }
390
426
 
427
+ // 枝の段数は「その枝の先 (子)」 の段数にする (cdl#520)。 箱を置き終えた後に引く
428
+ const 段数 = new Map(nodes.map((n) => [n.id, n.depth]));
429
+
391
430
  // edge = 親の辺 → 子の辺。 root → 第 1 branch は root の辺から。
392
431
  for (const br of mindData.branches) {
393
432
  const childPos = nodePos.get(br.id);
@@ -395,17 +434,29 @@ export function computeMindMapLayout(
395
434
  const isLeft = nodeSide.get(br.id) === "left";
396
435
  const childHalfW = nodeHalfW.get(br.id) ?? subBranchW / 2;
397
436
  const tone = nodeTone.get(br.id);
437
+ const childDepth = 段数.get(br.id) ?? 1;
438
+ const parentDepth = 段数.get(br.parent) ?? 0;
398
439
  if (br.parent === mindData.rootId) {
399
440
  const startX = rootEdgeXFor(isLeft);
400
441
  const endX = childPos.x + (isLeft ? childHalfW : -childHalfW);
401
- edges.push({ d: makeCurve(startX, cy, endX, childPos.y, isLeft), tone });
442
+ edges.push({
443
+ d: makeCurve(startX, cy, endX, childPos.y, isLeft),
444
+ tone,
445
+ depth: childDepth,
446
+ deferUntilComplete: false,
447
+ });
402
448
  } else {
403
449
  const parentPos = nodePos.get(br.parent);
404
450
  if (!parentPos) continue;
405
451
  const parentHalfW = nodeHalfW.get(br.parent) ?? branchW / 2;
406
452
  const startX = parentPos.x + (isLeft ? -parentHalfW : parentHalfW);
407
453
  const endX = childPos.x + (isLeft ? childHalfW : -childHalfW);
408
- edges.push({ d: makeCurve(startX, parentPos.y, endX, childPos.y, isLeft), tone });
454
+ edges.push({
455
+ d: makeCurve(startX, parentPos.y, endX, childPos.y, isLeft),
456
+ tone,
457
+ depth: Math.max(parentDepth, childDepth),
458
+ deferUntilComplete: parentDepth >= childDepth,
459
+ });
409
460
  }
410
461
  }
411
462
 
@@ -3,6 +3,7 @@ import type { LaidNode, TreeNodePayload } from "../types";
3
3
  import { TONE } from "../render/tone";
4
4
  import { resolveTreeData } from "../render/payload-binding";
5
5
  import { 箱に積む, type 積んだ行 } from "../kinds/box-lines";
6
+ import { 進みを畳む, 伸ばすdash, 段の伸び } from "./draw-progress";
6
7
 
7
8
  /**
8
9
  * tree-hierarchy kind ... 1 node に tree node 配列 (treeData) を保持し、
@@ -53,10 +54,16 @@ export function TreeNode({
53
54
  node,
54
55
  active,
55
56
  stateValues = {},
57
+ drawing = false,
58
+ progress = 1,
56
59
  }: {
57
60
  node: LaidNode;
58
61
  active: boolean;
59
62
  stateValues?: Record<string, string>;
63
+ /** この phase の `draw` に載っているか (cdl#520) */
64
+ drawing?: boolean;
65
+ /** phase の進み (0→1)。 `drawing` が true の時だけ読む */
66
+ progress?: number;
60
67
  }): JSX.Element {
61
68
  const x0 = node.cx - node.w / 2;
62
69
  const y0 = node.cy - node.h / 2;
@@ -72,6 +79,16 @@ export function TreeNode({
72
79
  const canvasH = node.h - PAD_TOP - PAD_BOTTOM;
73
80
 
74
81
  const layout = computeTreeLayout(treeNodes, canvasW, canvasH);
82
+
83
+ /**
84
+ * 枝がどこまで伸びたか (0..1、 cdl#520)。 描く指定が無ければ常に 1 = 全部出ている。
85
+ *
86
+ * 根に近い段から順に伸ばす。 全部を同時に伸ばすと、離れた枝が宙に浮いたまま伸びる。
87
+ */
88
+ const 伸び = 進みを畳む(drawing, progress);
89
+ const 最大深さ = layout.nodes.reduce((m, n) => Math.max(m, n.depth), 0);
90
+ /** その箱が出るか。 根 (深さ 0) は起点なので進み 0 でも出す */
91
+ const 箱が出る = (深さ: number): boolean => !drawing || 段の伸び(伸び, 深さ, 最大深さ) >= 1;
75
92
  const depthColors = [TONE.accent, TONE.teal, TONE.success, TONE.warning, TONE.info];
76
93
 
77
94
  return (
@@ -106,6 +123,10 @@ export function TreeNode({
106
123
  const y2 = PAD_TOP + e.y2;
107
124
  const midY = (y1 + y2) / 2;
108
125
  const path = `M ${x1} ${y1} L ${x1} ${midY} L ${x2} ${midY} L ${x2} ${y2}`;
126
+ // 循環を閉じる枝は根から外へ向かう枝ではないため、全 node が出てから表示する
127
+ const 枝の伸び = e.deferUntilComplete
128
+ ? (伸び >= 1 ? 1 : 0)
129
+ : 段の伸び(伸び, e.depth, 最大深さ);
109
130
  return (
110
131
  <g key={`edge-${i}`}>
111
132
  <path
@@ -116,13 +137,19 @@ export function TreeNode({
116
137
  strokeOpacity={0.55}
117
138
  strokeWidth={1.5}
118
139
  strokeLinejoin="round"
140
+ {...伸ばすdash(drawing, 枝の伸び)}
119
141
  />
120
- <circle cx={x1} cy={midY} r={2.5} fill={TONE.accent} fillOpacity={0.75} />
142
+ {/* 曲がり角の点は枝が届いてから出す。 先に出ると線の無い場所に点が浮く */}
143
+ {(!drawing || 枝の伸び >= 1) && (
144
+ <circle cx={x1} cy={midY} r={2.5} fill={TONE.accent} fillOpacity={0.75} />
145
+ )}
121
146
  </g>
122
147
  );
123
148
  })}
124
149
 
125
150
  {layout.nodes.map((n) => {
151
+ // 枝が届いてから箱を出す。 届く前に出すと、繋がっていない箱が宙に浮く
152
+ if (!箱が出る(n.depth)) return null;
126
153
  const accent = depthColors[Math.min(n.depth, depthColors.length - 1)] ?? depthColors[0]!;
127
154
  const isRoot = n.depth === 0;
128
155
  // 補足は箱の中の 2 行目に出す (#471)。 箱の高さは変えないため、 入らない時は落ちる
@@ -232,13 +259,23 @@ type LayoutNode = {
232
259
  unresolved?: true;
233
260
  };
234
261
 
262
+ type LayoutEdge = {
263
+ x1: number;
264
+ y1: number;
265
+ x2: number;
266
+ y2: number;
267
+ depth: number;
268
+ /** 循環により、元の親が layout 上で子と同じ深さか深くなった枝 */
269
+ deferUntilComplete: boolean;
270
+ };
271
+
235
272
  export function computeTreeLayout(
236
273
  treeNodes: TreeNodePayload[],
237
274
  canvasW: number,
238
275
  canvasH: number,
239
276
  ): {
240
277
  nodes: LayoutNode[];
241
- edges: Array<{ x1: number; y1: number; x2: number; y2: number }>;
278
+ edges: LayoutEdge[];
242
279
  } {
243
280
  // parent が実在しない node は root 扱いにする。 そうしないと root からの再帰で辿れず、
244
281
  // node が silent に消える (旧実装は全 node を列挙していたので消えなかった)。
@@ -357,7 +394,9 @@ export function computeTreeLayout(
357
394
  }
358
395
  const hById = new Map(nodes.map((n) => [n.id, n.h]));
359
396
 
360
- const edges: Array<{ x1: number; y1: number; x2: number; y2: number }> = [];
397
+ // 枝の段数は「その枝の先 (子)」 の段数 (cdl#520)。 段ごとに順に伸ばすのに使う
398
+ const 段数 = new Map(nodes.map((n) => [n.id, n.depth]));
399
+ const edges: LayoutEdge[] = [];
361
400
  for (const n of treeNodes) {
362
401
  if (!n.parent) continue;
363
402
  const parent = posOf.get(n.parent);
@@ -365,7 +404,16 @@ export function computeTreeLayout(
365
404
  if (!parent || !child) continue;
366
405
  const parentH = hById.get(n.parent) ?? nodeH;
367
406
  const childH = hById.get(n.id) ?? nodeH;
368
- edges.push({ x1: parent.x, y1: parent.y + parentH / 2, x2: child.x, y2: child.y - childH / 2 });
407
+ const parentDepth = 段数.get(n.parent) ?? 0;
408
+ const childDepth = 段数.get(n.id) ?? 1;
409
+ edges.push({
410
+ x1: parent.x,
411
+ y1: parent.y + parentH / 2,
412
+ x2: child.x,
413
+ y2: child.y - childH / 2,
414
+ depth: Math.max(parentDepth, childDepth),
415
+ deferUntilComplete: parentDepth >= childDepth,
416
+ });
369
417
  }
370
418
 
371
419
  return { nodes, edges };
@@ -2,6 +2,7 @@ import type { JSX } from "react";
2
2
  import type { LaidNode, JourneyEmotion } from "../types";
3
3
  import { TONE } from "../render/tone";
4
4
  import { resolveJourneyData } from "../render/payload-binding";
5
+ import { 進みを畳む, 伸ばすdash, ベジエの累積割合 } from "./draw-progress";
5
6
 
6
7
  /**
7
8
  * journey-map kind ... 1 node に step 配列 (journeyData) を保持し、 UX journey map を SVG 描画する。
@@ -18,10 +19,16 @@ export function UserJourneyNode({
18
19
  node,
19
20
  active,
20
21
  stateValues = {},
22
+ drawing = false,
23
+ progress = 1,
21
24
  }: {
22
25
  node: LaidNode;
23
26
  active: boolean;
24
27
  stateValues?: Record<string, string>;
28
+ /** この phase の `draw` に載っているか (cdl#520) */
29
+ drawing?: boolean;
30
+ /** phase の進み (0→1)。 `drawing` が true の時だけ読む */
31
+ progress?: number;
25
32
  }): JSX.Element {
26
33
  const x0 = node.cx - node.w / 2;
27
34
  const y0 = node.cy - node.h / 2;
@@ -70,6 +77,21 @@ export function UserJourneyNode({
70
77
 
71
78
  // smooth curve for emotion path
72
79
  const pts = steps.map((s, i) => ({ x: xAt(i), y: yAt(s.emotion) }));
80
+ /**
81
+ * 線がどこまで伸びたか (0..1、 cdl#520)。 描く指定が無ければ常に 1 = 全長。
82
+ */
83
+ const 伸び = 進みを畳む(drawing, progress);
84
+ /**
85
+ * 各段までの累積割合。 **点番号の比率ではなく実際の経路長で測る** (#513 の指摘)。
86
+ * 気持ちの上下で区間の長さが変わるため、比率で判定すると先端と丸がずれる。
87
+ */
88
+ const 段までの割合 = ベジエの累積割合(pts, (前, 後) => {
89
+ const midX = (前.x + 後.x) / 2;
90
+ return [{ x: midX, y: 前.y }, { x: midX, y: 後.y }];
91
+ });
92
+ /** その段まで線の先が届いたか。 左端は進み 0 でも出す (そこが線の起点) */
93
+ const 線の先が届いた = (idx: number): boolean => !drawing || 伸び >= (段までの割合[idx] ?? 0);
94
+
73
95
  const smoothPath = pts.length > 1
74
96
  ? pts.reduce((acc, p, i) => {
75
97
  if (i === 0) return `M ${p.x} ${p.y}`;
@@ -144,6 +166,10 @@ export function UserJourneyNode({
144
166
 
145
167
  {steps.length > 1 && (
146
168
  <>
169
+ {/*
170
+ 影と本体は同じ進みで伸ばす。 片方だけ先行すると、線の先に影だけが伸びた
171
+ 形になる (cdl#520)
172
+ */}
147
173
  <path
148
174
  data-cdl-role="journey-line-glow"
149
175
  d={smoothPath}
@@ -152,6 +178,7 @@ export function UserJourneyNode({
152
178
  strokeOpacity={0.18}
153
179
  strokeWidth={10}
154
180
  strokeLinecap="round"
181
+ {...伸ばすdash(drawing, 伸び)}
155
182
  />
156
183
  <path
157
184
  data-cdl-role="journey-line"
@@ -161,11 +188,12 @@ export function UserJourneyNode({
161
188
  strokeWidth={2.75}
162
189
  strokeLinejoin="round"
163
190
  strokeLinecap="round"
191
+ {...伸ばすdash(drawing, 伸び)}
164
192
  />
165
193
  </>
166
194
  )}
167
195
 
168
- {steps.map((s, idx) => (
196
+ {steps.map((s, idx) => !線の先が届いた(idx) ? null : (
169
197
  <g
170
198
  key={`step-${s.id}`}
171
199
  data-cdl-role="journey-step"
@@ -170,21 +170,77 @@ export function CdlNodeView({
170
170
  />
171
171
  );
172
172
  case "chart-pie":
173
- return <ChartPieNode node={resolvedNode} active={active} stateValues={stateValues} />;
173
+ return (
174
+ <ChartPieNode
175
+ node={resolvedNode}
176
+ active={active}
177
+ stateValues={stateValues}
178
+ drawing={drawing}
179
+ progress={progress}
180
+ />
181
+ );
174
182
  case "chart-bar":
175
- return <ChartBarNode node={resolvedNode} active={active} stateValues={stateValues} />;
183
+ return (
184
+ <ChartBarNode
185
+ node={resolvedNode}
186
+ active={active}
187
+ stateValues={stateValues}
188
+ drawing={drawing}
189
+ progress={progress}
190
+ />
191
+ );
176
192
  case "gantt-timeline":
177
- return <GanttNode node={resolvedNode} active={active} stateValues={stateValues} />;
193
+ return (
194
+ <GanttNode
195
+ node={resolvedNode}
196
+ active={active}
197
+ stateValues={stateValues}
198
+ drawing={drawing}
199
+ progress={progress}
200
+ />
201
+ );
178
202
  case "mind-map":
179
- return <MindMapNode node={resolvedNode} active={active} stateValues={stateValues} />;
203
+ return (
204
+ <MindMapNode
205
+ node={resolvedNode}
206
+ active={active}
207
+ stateValues={stateValues}
208
+ drawing={drawing}
209
+ progress={progress}
210
+ />
211
+ );
180
212
  case "funnel-stages":
181
- return <FunnelNode node={resolvedNode} active={active} stateValues={stateValues} />;
213
+ return (
214
+ <FunnelNode
215
+ node={resolvedNode}
216
+ active={active}
217
+ stateValues={stateValues}
218
+ drawing={drawing}
219
+ progress={progress}
220
+ />
221
+ );
182
222
  case "quadrant-matrix":
183
223
  return <QuadrantNode node={resolvedNode} active={active} stateValues={stateValues} />;
184
224
  case "tree-hierarchy":
185
- return <TreeHierarchyNode node={resolvedNode} active={active} stateValues={stateValues} />;
225
+ return (
226
+ <TreeHierarchyNode
227
+ node={resolvedNode}
228
+ active={active}
229
+ stateValues={stateValues}
230
+ drawing={drawing}
231
+ progress={progress}
232
+ />
233
+ );
186
234
  case "journey-map":
187
- return <UserJourneyNode node={resolvedNode} active={active} stateValues={stateValues} />;
235
+ return (
236
+ <UserJourneyNode
237
+ node={resolvedNode}
238
+ active={active}
239
+ stateValues={stateValues}
240
+ drawing={drawing}
241
+ progress={progress}
242
+ />
243
+ );
188
244
  case "shape-file":
189
245
  return <ShapeFileNode node={resolvedNode} active={active} />;
190
246
  case "shape-folder":
package/src/types.ts CHANGED
@@ -2128,8 +2128,20 @@ export type CdlPhase = {
2128
2128
  * この phase で「左の起点から描き始める」 node の id (cdl#512)。
2129
2129
  *
2130
2130
  * `activate` と同じ形で node id を並べる。 対象の node は phase の進み (0→1) に合わせて
2131
- * 描かれ、 進みが 1 に達した時点で全体が出る。 現在の対象は `chart-line` だけで、
2132
- * 折れ線が左端から右へ伸び、 点と値の数字は線の先が届いた時に現れる。
2131
+ * 描かれ、 進みが 1 に達した時点で全体が出る。 起点は種別ごとに違う。
2132
+ *
2133
+ * | kind | 起点 | 動き | 添える値 |
2134
+ * |---|---|---|---|
2135
+ * | `chart-line` | 左端 | 線が右へ伸びる | 点と数字は線の先が届いた時 |
2136
+ * | `chart-bar` | 横軸 | 棒が上へ伸びる | 数字は棒の頭に付いて動く |
2137
+ * | `chart-pie` | 12 時 | 扇が時計回りに開く | 凡例はその扇が開き始めた時 |
2138
+ * | `journey-map` | 左端 | 気持ちの線が右へ伸びる | 段はその点に線の先が届いた時 |
2139
+ * | `mind-map` | 中心 | 枝が段ごとに外へ伸びる | 箱はその枝が届いた時 |
2140
+ * | `tree-hierarchy` | 根 | 枝が段ごとに下へ伸びる | 箱はその枝が届いた時 |
2141
+ * | `gantt-timeline` | 各帯の始端 | 帯が右へ伸びる | 依存の矢印は帯が出揃った時 |
2142
+ * | `funnel-stages` | 上端 | 段が上から順に積まれる | 件数と離脱率はその段が出切った時 |
2143
+ *
2144
+ * 対象種別の一覧は `validate.ts` の `DRAW_KINDS` が持つ。
2133
2145
  *
2134
2146
  * **`activate` を兼ねさせない**。 1 箱で図全体を描く種別 (`chart-*` 等) では焦点が
2135
2147
  * その箱に解決されるため、 連続する phase で光り続ける図が実在する。 「光っていたら
package/src/validate.ts CHANGED
@@ -5,6 +5,34 @@ import { nodeTemplateTexts } from "./render/template-fields";
5
5
  import { derivedRefIds } from "./render/derived-values";
6
6
 
7
7
  const TONES = new Set<string>(TONE_NAMES);
8
+
9
+ /**
10
+ * phase の `draw` が効く kind (cdl#512 / cdl#516)。
11
+ *
12
+ * **一覧を 1 箇所で持つ**。 知らせの文面もここから組み立てる = 対応種別が増えた時に
13
+ * 片方だけ古くなる形を作らない。
14
+ *
15
+ * | kind | 起点 | 動き |
16
+ * |---|---|---|
17
+ * | `chart-line` | 左端 | 線が右へ伸びる |
18
+ * | `chart-bar` | 横軸 | 棒が上へ伸びる |
19
+ * | `chart-pie` | 12 時 | 扇が時計回りに開く |
20
+ * | `journey-map` | 左端 | 気持ちの線が右へ伸びる |
21
+ * | `mind-map` | 中心 | 枝が中心から外へ伸びる |
22
+ * | `tree-hierarchy` | 根 | 枝が上の段から下の段へ伸びる |
23
+ * | `gantt-timeline` | 各帯の始端 | 帯が右へ伸びる |
24
+ * | `funnel-stages` | 上端 | 段が上から順に積まれる |
25
+ */
26
+ export const DRAW_KINDS: ReadonlySet<string> = new Set([
27
+ "chart-line",
28
+ "chart-bar",
29
+ "chart-pie",
30
+ "journey-map",
31
+ "mind-map",
32
+ "tree-hierarchy",
33
+ "gantt-timeline",
34
+ "funnel-stages",
35
+ ]);
8
36
  const KINDS = new Set<string>(NODE_KINDS);
9
37
  const ENG_BANNED = /\b(FUNCTION|STORAGE|EVENT LOG|BALANCES MAPPING|READ|WRITE|EMIT|CALL)\b/;
10
38
 
@@ -166,8 +194,10 @@ export function validate(diag: CdlDiagram): void {
166
194
  // 描き方を持たない種別を指しても図は出るが、 書いた指定は何もしない。
167
195
  // 黙って捨てると「書いたのに伸びない」 が手掛かりなしで起きる
168
196
  const kind = diag.nodes.find((n) => n.id === id)?.kind;
169
- if (kind !== "chart-line") {
170
- warnings.push(`phase "${p.id}" の draw "${id}" は kind "${kind}" で、 左から描く動きを持たない (対象は chart-line)`);
197
+ if (!DRAW_KINDS.has(String(kind))) {
198
+ warnings.push(
199
+ `phase "${p.id}" の draw "${id}" は kind "${kind}" で、 起点から描く動きを持たない (対象は ${[...DRAW_KINDS].join(" / ")})`,
200
+ );
171
201
  }
172
202
  }
173
203
  for (const t of p.tweens) {