@cardenelabs/cdl 0.11.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.
- package/CHANGELOG.md +46 -1
- package/SPEC.md +12 -2
- package/dist/index.cjs +319 -147
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +319 -147
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +319 -147
- 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 +319 -147
- package/dist/react.js.map +1 -1
- package/dist/{render-DwH8v5Mb.d.cts → render-C7FKvMqq.d.cts} +5 -0
- package/dist/{render-DwH8v5Mb.d.ts → render-C7FKvMqq.d.ts} +5 -0
- package/package.json +1 -1
- package/src/kinds/chart-bar.tsx +3 -7
- package/src/kinds/chart-line.tsx +4 -10
- package/src/kinds/chart-pie.tsx +3 -7
- package/src/kinds/draw-progress.ts +100 -0
- package/src/kinds/funnel.tsx +101 -58
- package/src/kinds/gantt.tsx +25 -1
- package/src/kinds/mind-map.tsx +65 -14
- package/src/kinds/tree.tsx +52 -4
- package/src/kinds/user-journey.tsx +29 -1
- package/src/render/nodes.tsx +45 -5
- package/src/types.ts +5 -0
- package/src/validate.ts +15 -1
package/src/kinds/tree.tsx
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
package/src/render/nodes.tsx
CHANGED
|
@@ -190,17 +190,57 @@ export function CdlNodeView({
|
|
|
190
190
|
/>
|
|
191
191
|
);
|
|
192
192
|
case "gantt-timeline":
|
|
193
|
-
return
|
|
193
|
+
return (
|
|
194
|
+
<GanttNode
|
|
195
|
+
node={resolvedNode}
|
|
196
|
+
active={active}
|
|
197
|
+
stateValues={stateValues}
|
|
198
|
+
drawing={drawing}
|
|
199
|
+
progress={progress}
|
|
200
|
+
/>
|
|
201
|
+
);
|
|
194
202
|
case "mind-map":
|
|
195
|
-
return
|
|
203
|
+
return (
|
|
204
|
+
<MindMapNode
|
|
205
|
+
node={resolvedNode}
|
|
206
|
+
active={active}
|
|
207
|
+
stateValues={stateValues}
|
|
208
|
+
drawing={drawing}
|
|
209
|
+
progress={progress}
|
|
210
|
+
/>
|
|
211
|
+
);
|
|
196
212
|
case "funnel-stages":
|
|
197
|
-
return
|
|
213
|
+
return (
|
|
214
|
+
<FunnelNode
|
|
215
|
+
node={resolvedNode}
|
|
216
|
+
active={active}
|
|
217
|
+
stateValues={stateValues}
|
|
218
|
+
drawing={drawing}
|
|
219
|
+
progress={progress}
|
|
220
|
+
/>
|
|
221
|
+
);
|
|
198
222
|
case "quadrant-matrix":
|
|
199
223
|
return <QuadrantNode node={resolvedNode} active={active} stateValues={stateValues} />;
|
|
200
224
|
case "tree-hierarchy":
|
|
201
|
-
return
|
|
225
|
+
return (
|
|
226
|
+
<TreeHierarchyNode
|
|
227
|
+
node={resolvedNode}
|
|
228
|
+
active={active}
|
|
229
|
+
stateValues={stateValues}
|
|
230
|
+
drawing={drawing}
|
|
231
|
+
progress={progress}
|
|
232
|
+
/>
|
|
233
|
+
);
|
|
202
234
|
case "journey-map":
|
|
203
|
-
return
|
|
235
|
+
return (
|
|
236
|
+
<UserJourneyNode
|
|
237
|
+
node={resolvedNode}
|
|
238
|
+
active={active}
|
|
239
|
+
stateValues={stateValues}
|
|
240
|
+
drawing={drawing}
|
|
241
|
+
progress={progress}
|
|
242
|
+
/>
|
|
243
|
+
);
|
|
204
244
|
case "shape-file":
|
|
205
245
|
return <ShapeFileNode node={resolvedNode} active={active} />;
|
|
206
246
|
case "shape-folder":
|
package/src/types.ts
CHANGED
|
@@ -2135,6 +2135,11 @@ export type CdlPhase = {
|
|
|
2135
2135
|
* | `chart-line` | 左端 | 線が右へ伸びる | 点と数字は線の先が届いた時 |
|
|
2136
2136
|
* | `chart-bar` | 横軸 | 棒が上へ伸びる | 数字は棒の頭に付いて動く |
|
|
2137
2137
|
* | `chart-pie` | 12 時 | 扇が時計回りに開く | 凡例はその扇が開き始めた時 |
|
|
2138
|
+
* | `journey-map` | 左端 | 気持ちの線が右へ伸びる | 段はその点に線の先が届いた時 |
|
|
2139
|
+
* | `mind-map` | 中心 | 枝が段ごとに外へ伸びる | 箱はその枝が届いた時 |
|
|
2140
|
+
* | `tree-hierarchy` | 根 | 枝が段ごとに下へ伸びる | 箱はその枝が届いた時 |
|
|
2141
|
+
* | `gantt-timeline` | 各帯の始端 | 帯が右へ伸びる | 依存の矢印は帯が出揃った時 |
|
|
2142
|
+
* | `funnel-stages` | 上端 | 段が上から順に積まれる | 件数と離脱率はその段が出切った時 |
|
|
2138
2143
|
*
|
|
2139
2144
|
* 対象種別の一覧は `validate.ts` の `DRAW_KINDS` が持つ。
|
|
2140
2145
|
*
|
package/src/validate.ts
CHANGED
|
@@ -17,8 +17,22 @@ const TONES = new Set<string>(TONE_NAMES);
|
|
|
17
17
|
* | `chart-line` | 左端 | 線が右へ伸びる |
|
|
18
18
|
* | `chart-bar` | 横軸 | 棒が上へ伸びる |
|
|
19
19
|
* | `chart-pie` | 12 時 | 扇が時計回りに開く |
|
|
20
|
+
* | `journey-map` | 左端 | 気持ちの線が右へ伸びる |
|
|
21
|
+
* | `mind-map` | 中心 | 枝が中心から外へ伸びる |
|
|
22
|
+
* | `tree-hierarchy` | 根 | 枝が上の段から下の段へ伸びる |
|
|
23
|
+
* | `gantt-timeline` | 各帯の始端 | 帯が右へ伸びる |
|
|
24
|
+
* | `funnel-stages` | 上端 | 段が上から順に積まれる |
|
|
20
25
|
*/
|
|
21
|
-
export const DRAW_KINDS: ReadonlySet<string> = new Set([
|
|
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
|
+
]);
|
|
22
36
|
const KINDS = new Set<string>(NODE_KINDS);
|
|
23
37
|
const ENG_BANNED = /\b(FUNCTION|STORAGE|EVENT LOG|BALANCES MAPPING|READ|WRITE|EMIT|CALL)\b/;
|
|
24
38
|
|