@aiready/components 0.13.18 → 0.13.20
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/dist/charts/ForceDirectedGraph.d.ts +7 -13
- package/dist/charts/ForceDirectedGraph.js +451 -337
- package/dist/charts/ForceDirectedGraph.js.map +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +728 -586
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/charts/ForceDirectedGraph.tsx +12 -509
- package/src/charts/LinkItem.tsx +1 -1
- package/src/charts/NodeItem.tsx +1 -1
- package/src/charts/force-directed/ControlButton.tsx +39 -0
- package/src/charts/force-directed/ForceDirectedGraph.tsx +250 -0
- package/src/charts/force-directed/GraphCanvas.tsx +129 -0
- package/src/charts/{GraphControls.tsx → force-directed/GraphControls.tsx} +3 -110
- package/src/charts/force-directed/index.ts +31 -0
- package/src/charts/force-directed/types.ts +102 -0
- package/src/charts/{hooks.ts → force-directed/useGraphInteractions.ts} +64 -1
- package/src/charts/force-directed/useGraphLayout.ts +54 -0
- package/src/charts/force-directed/useImperativeHandle.ts +131 -0
- package/src/charts/layout-utils.ts +1 -1
- package/src/components/feedback/__tests__/badge.test.tsx +92 -0
- package/src/components/ui/__tests__/button.test.tsx +203 -0
- package/src/data-display/__tests__/ScoreBar.test.tsx +215 -0
- package/src/index.ts +4 -1
- package/src/utils/__tests__/score.test.ts +28 -7
- package/src/utils/score.ts +67 -29
- package/src/charts/types.ts +0 -24
|
@@ -1,10 +1,288 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as
|
|
1
|
+
import { forwardRef, useRef, useState, useEffect, useCallback, useImperativeHandle, useMemo } from 'react';
|
|
2
|
+
import * as d33 from 'd3';
|
|
3
3
|
import { clsx } from 'clsx';
|
|
4
4
|
import { twMerge } from 'tailwind-merge';
|
|
5
|
-
import {
|
|
5
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
6
|
|
|
7
|
-
// src/charts/
|
|
7
|
+
// src/charts/force-directed/useGraphInteractions.ts
|
|
8
|
+
function pinNode(node) {
|
|
9
|
+
node.fx = node.x;
|
|
10
|
+
node.fy = node.y;
|
|
11
|
+
}
|
|
12
|
+
function unpinNode(node) {
|
|
13
|
+
node.fx = null;
|
|
14
|
+
node.fy = null;
|
|
15
|
+
}
|
|
16
|
+
function unpinAllNodes(nodes) {
|
|
17
|
+
nodes.forEach(unpinNode);
|
|
18
|
+
}
|
|
19
|
+
function useGraphZoom(svgRef, gRef, enableZoom, setTransform, transformRef) {
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!enableZoom || !svgRef.current || !gRef.current) return;
|
|
22
|
+
const svg = d33.select(svgRef.current);
|
|
23
|
+
const g = d33.select(gRef.current);
|
|
24
|
+
const zoom3 = d33.zoom().scaleExtent([0.1, 10]).on("zoom", (event) => {
|
|
25
|
+
g.attr("transform", event.transform);
|
|
26
|
+
transformRef.current = event.transform;
|
|
27
|
+
setTransform(event.transform);
|
|
28
|
+
});
|
|
29
|
+
svg.call(zoom3);
|
|
30
|
+
return () => {
|
|
31
|
+
svg.on(".zoom", null);
|
|
32
|
+
};
|
|
33
|
+
}, [enableZoom, svgRef, gRef, setTransform, transformRef]);
|
|
34
|
+
}
|
|
35
|
+
function useWindowDrag(enableDrag, svgRef, transformRef, dragActiveRef, dragNodeRef, onDragEnd) {
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!enableDrag) return;
|
|
38
|
+
const handleWindowMove = (event) => {
|
|
39
|
+
if (!dragActiveRef.current || !dragNodeRef.current) return;
|
|
40
|
+
const svg = svgRef.current;
|
|
41
|
+
if (!svg) return;
|
|
42
|
+
const rect = svg.getBoundingClientRect();
|
|
43
|
+
const t = transformRef.current;
|
|
44
|
+
const x = (event.clientX - rect.left - t.x) / t.k;
|
|
45
|
+
const y = (event.clientY - rect.top - t.y) / t.k;
|
|
46
|
+
dragNodeRef.current.fx = x;
|
|
47
|
+
dragNodeRef.current.fy = y;
|
|
48
|
+
};
|
|
49
|
+
const handleWindowUp = () => {
|
|
50
|
+
if (!dragActiveRef.current) return;
|
|
51
|
+
onDragEnd();
|
|
52
|
+
dragNodeRef.current = null;
|
|
53
|
+
dragActiveRef.current = false;
|
|
54
|
+
};
|
|
55
|
+
const handleWindowLeave = (event) => {
|
|
56
|
+
if (event.relatedTarget === null) handleWindowUp();
|
|
57
|
+
};
|
|
58
|
+
window.addEventListener("mousemove", handleWindowMove);
|
|
59
|
+
window.addEventListener("mouseup", handleWindowUp);
|
|
60
|
+
window.addEventListener("mouseout", handleWindowLeave);
|
|
61
|
+
window.addEventListener("blur", handleWindowUp);
|
|
62
|
+
return () => {
|
|
63
|
+
window.removeEventListener("mousemove", handleWindowMove);
|
|
64
|
+
window.removeEventListener("mouseup", handleWindowUp);
|
|
65
|
+
window.removeEventListener("mouseout", handleWindowLeave);
|
|
66
|
+
window.removeEventListener("blur", handleWindowUp);
|
|
67
|
+
};
|
|
68
|
+
}, [enableDrag, svgRef, transformRef, dragActiveRef, dragNodeRef, onDragEnd]);
|
|
69
|
+
}
|
|
70
|
+
function useNodeInteractions(enableDrag, _nodes, _pinnedNodes, setPinnedNodes, restart, stop) {
|
|
71
|
+
const handleDragStart = useCallback(
|
|
72
|
+
(event, node) => {
|
|
73
|
+
if (!enableDrag) return;
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
event.stopPropagation();
|
|
76
|
+
pinNode(node);
|
|
77
|
+
setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
|
|
78
|
+
stop();
|
|
79
|
+
},
|
|
80
|
+
[enableDrag, stop, setPinnedNodes]
|
|
81
|
+
);
|
|
82
|
+
const handleNodeDoubleClick = useCallback(
|
|
83
|
+
(event, node) => {
|
|
84
|
+
event.stopPropagation();
|
|
85
|
+
if (!enableDrag) return;
|
|
86
|
+
if (node.fx === null || node.fx === void 0) {
|
|
87
|
+
pinNode(node);
|
|
88
|
+
setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
|
|
89
|
+
} else {
|
|
90
|
+
unpinNode(node);
|
|
91
|
+
setPinnedNodes((prev) => {
|
|
92
|
+
const next = new Set(prev);
|
|
93
|
+
next.delete(node.id);
|
|
94
|
+
return next;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
restart();
|
|
98
|
+
},
|
|
99
|
+
[enableDrag, restart, setPinnedNodes]
|
|
100
|
+
);
|
|
101
|
+
return { handleDragStart, handleNodeDoubleClick };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/charts/constants.ts
|
|
105
|
+
var DEFAULT_NODE_COLOR = "#64748b";
|
|
106
|
+
var DEFAULT_NODE_SIZE = 10;
|
|
107
|
+
var DEFAULT_LINK_COLOR = "#94a3b8";
|
|
108
|
+
var DEFAULT_LINK_WIDTH = 1;
|
|
109
|
+
var CIRCULAR_LAYOUT_RADIUS_RATIO = 0.35;
|
|
110
|
+
var FIT_VIEW_PADDING = 40;
|
|
111
|
+
var TRANSITION_DURATION_MS = 300;
|
|
112
|
+
var PACKAGE_BOUNDARY_FILL = "rgba(148,163,184,0.06)";
|
|
113
|
+
var PACKAGE_BOUNDARY_STROKE = "#475569";
|
|
114
|
+
var PACKAGE_BOUNDARY_STROKE_WIDTH = 2;
|
|
115
|
+
var PACKAGE_BOUNDARY_DASH = "6 6";
|
|
116
|
+
var PACKAGE_LABEL_FONT_SIZE = 11;
|
|
117
|
+
var PACKAGE_LABEL_COLOR = "#475569";
|
|
118
|
+
|
|
119
|
+
// src/charts/layout-utils.ts
|
|
120
|
+
function applyCircularLayout(nodes, width, height) {
|
|
121
|
+
const centerX = width / 2;
|
|
122
|
+
const centerY = height / 2;
|
|
123
|
+
const radius = Math.min(width, height) * CIRCULAR_LAYOUT_RADIUS_RATIO;
|
|
124
|
+
nodes.forEach((node, i) => {
|
|
125
|
+
const angle = 2 * Math.PI * i / nodes.length;
|
|
126
|
+
node.fx = centerX + Math.cos(angle) * radius;
|
|
127
|
+
node.fy = centerY + Math.sin(angle) * radius;
|
|
128
|
+
node.x = node.fx;
|
|
129
|
+
node.y = node.fy;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function applyHierarchicalLayout(nodes, width, height) {
|
|
133
|
+
const groups = /* @__PURE__ */ new Map();
|
|
134
|
+
nodes.forEach((n) => {
|
|
135
|
+
const key = n.packageGroup || n.group || "root";
|
|
136
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
137
|
+
groups.get(key).push(n);
|
|
138
|
+
});
|
|
139
|
+
const groupArray = Array.from(groups.entries());
|
|
140
|
+
const cols = Math.ceil(Math.sqrt(groupArray.length));
|
|
141
|
+
const groupSpacingX = width * 0.8 / cols;
|
|
142
|
+
const groupSpacingY = height * 0.8 / Math.ceil(groupArray.length / cols);
|
|
143
|
+
groupArray.forEach(([groupKey, groupNodes], gi) => {
|
|
144
|
+
const col = gi % cols;
|
|
145
|
+
const row = Math.floor(gi / cols);
|
|
146
|
+
const groupX = (col + 0.5) * groupSpacingX;
|
|
147
|
+
const groupY = (row + 0.5) * groupSpacingY;
|
|
148
|
+
if (groupKey.startsWith("pkg:") || groupKey === groupKey) {
|
|
149
|
+
groupNodes.forEach((n, ni) => {
|
|
150
|
+
const angle = 2 * Math.PI * ni / groupNodes.length;
|
|
151
|
+
const r = Math.min(80, 20 + groupNodes.length * 8);
|
|
152
|
+
n.fx = groupX + Math.cos(angle) * r;
|
|
153
|
+
n.fy = groupY + Math.sin(angle) * r;
|
|
154
|
+
n.x = n.fx;
|
|
155
|
+
n.y = n.fy;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function applyInitialForceLayout(nodes, width, height) {
|
|
161
|
+
nodes.forEach((node) => {
|
|
162
|
+
if (node.fx === void 0 || node.fx === null) {
|
|
163
|
+
node.x = Math.random() * width;
|
|
164
|
+
node.y = Math.random() * height;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/charts/force-directed/useGraphLayout.ts
|
|
170
|
+
function useGraphLayout(initialNodes, width, height, layout, restart) {
|
|
171
|
+
const nodes = useMemo(() => {
|
|
172
|
+
if (!initialNodes || !initialNodes.length) return initialNodes;
|
|
173
|
+
const copy = initialNodes.map((n) => ({ ...n }));
|
|
174
|
+
if (layout === "circular") applyCircularLayout(copy, width, height);
|
|
175
|
+
else if (layout === "hierarchical")
|
|
176
|
+
applyHierarchicalLayout(copy, width, height);
|
|
177
|
+
else applyInitialForceLayout(copy, width, height);
|
|
178
|
+
return copy;
|
|
179
|
+
}, [initialNodes, width, height, layout]);
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
if (!nodes || nodes.length === 0) return;
|
|
182
|
+
if (layout === "circular") applyCircularLayout(nodes, width, height);
|
|
183
|
+
else if (layout === "hierarchical")
|
|
184
|
+
applyHierarchicalLayout(nodes, width, height);
|
|
185
|
+
restart();
|
|
186
|
+
}, [layout, nodes, width, height, restart]);
|
|
187
|
+
return { nodes };
|
|
188
|
+
}
|
|
189
|
+
function useSimulationControls() {
|
|
190
|
+
const restart = useCallback(() => {
|
|
191
|
+
}, []);
|
|
192
|
+
const stop = useCallback(() => {
|
|
193
|
+
}, []);
|
|
194
|
+
const setForcesEnabled = useCallback((enabled) => {
|
|
195
|
+
}, []);
|
|
196
|
+
return { restart, stop, setForcesEnabled };
|
|
197
|
+
}
|
|
198
|
+
function useImperativeHandleMethods({
|
|
199
|
+
nodes,
|
|
200
|
+
pinnedNodes,
|
|
201
|
+
setPinnedNodes,
|
|
202
|
+
restart,
|
|
203
|
+
width,
|
|
204
|
+
height,
|
|
205
|
+
layout,
|
|
206
|
+
handleLayoutChange,
|
|
207
|
+
svgRef,
|
|
208
|
+
gRef,
|
|
209
|
+
setTransform,
|
|
210
|
+
internalDragEnabledRef
|
|
211
|
+
}) {
|
|
212
|
+
const pinAll = useCallback(() => {
|
|
213
|
+
const newPinned = /* @__PURE__ */ new Set();
|
|
214
|
+
nodes.forEach((node) => {
|
|
215
|
+
pinNode(node);
|
|
216
|
+
newPinned.add(node.id);
|
|
217
|
+
});
|
|
218
|
+
setPinnedNodes(newPinned);
|
|
219
|
+
restart();
|
|
220
|
+
}, [nodes, setPinnedNodes, restart]);
|
|
221
|
+
const unpinAll = useCallback(() => {
|
|
222
|
+
unpinAllNodes(nodes);
|
|
223
|
+
setPinnedNodes(/* @__PURE__ */ new Set());
|
|
224
|
+
restart();
|
|
225
|
+
}, [nodes, setPinnedNodes, restart]);
|
|
226
|
+
const resetLayout = useCallback(() => {
|
|
227
|
+
unpinAllNodes(nodes);
|
|
228
|
+
setPinnedNodes(/* @__PURE__ */ new Set());
|
|
229
|
+
restart();
|
|
230
|
+
}, [nodes, setPinnedNodes, restart]);
|
|
231
|
+
const fitView = useCallback(() => {
|
|
232
|
+
if (!svgRef.current || !nodes.length) return;
|
|
233
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
234
|
+
nodes.forEach((node) => {
|
|
235
|
+
if (node.x !== void 0 && node.y !== void 0) {
|
|
236
|
+
const size = node.size || DEFAULT_NODE_SIZE;
|
|
237
|
+
minX = Math.min(minX, node.x - size);
|
|
238
|
+
maxX = Math.max(maxX, node.x + size);
|
|
239
|
+
minY = Math.min(minY, node.y - size);
|
|
240
|
+
maxY = Math.max(maxY, node.y + size);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
if (!isFinite(minX)) return;
|
|
244
|
+
const scale = Math.min(
|
|
245
|
+
(width - FIT_VIEW_PADDING * 2) / (maxX - minX),
|
|
246
|
+
(height - FIT_VIEW_PADDING * 2) / (maxY - minY),
|
|
247
|
+
10
|
|
248
|
+
);
|
|
249
|
+
const x = width / 2 - (minX + maxX) / 2 * scale;
|
|
250
|
+
const y = height / 2 - (minY + maxY) / 2 * scale;
|
|
251
|
+
if (gRef.current && svgRef.current) {
|
|
252
|
+
const svg = d33.select(svgRef.current);
|
|
253
|
+
const newTransform = d33.zoomIdentity.translate(x, y).scale(scale);
|
|
254
|
+
svg.transition().duration(TRANSITION_DURATION_MS).call(d33.zoom().transform, newTransform);
|
|
255
|
+
setTransform(newTransform);
|
|
256
|
+
}
|
|
257
|
+
}, [nodes, width, height, svgRef, gRef, setTransform]);
|
|
258
|
+
const getPinnedNodes = useCallback(
|
|
259
|
+
() => Array.from(pinnedNodes),
|
|
260
|
+
[pinnedNodes]
|
|
261
|
+
);
|
|
262
|
+
const setDragMode = useCallback(
|
|
263
|
+
(enabled) => {
|
|
264
|
+
internalDragEnabledRef.current = enabled;
|
|
265
|
+
},
|
|
266
|
+
[internalDragEnabledRef]
|
|
267
|
+
);
|
|
268
|
+
const setLayoutMethod = useCallback(
|
|
269
|
+
(newLayout) => {
|
|
270
|
+
handleLayoutChange(newLayout);
|
|
271
|
+
},
|
|
272
|
+
[handleLayoutChange]
|
|
273
|
+
);
|
|
274
|
+
const getLayout = useCallback(() => layout, [layout]);
|
|
275
|
+
return {
|
|
276
|
+
pinAll,
|
|
277
|
+
unpinAll,
|
|
278
|
+
resetLayout,
|
|
279
|
+
fitView,
|
|
280
|
+
getPinnedNodes,
|
|
281
|
+
setDragMode,
|
|
282
|
+
setLayout: setLayoutMethod,
|
|
283
|
+
getLayout
|
|
284
|
+
};
|
|
285
|
+
}
|
|
8
286
|
function cn(...inputs) {
|
|
9
287
|
return twMerge(clsx(inputs));
|
|
10
288
|
}
|
|
@@ -137,21 +415,6 @@ var LinkItem = ({
|
|
|
137
415
|
] });
|
|
138
416
|
};
|
|
139
417
|
var LinkItem_default = LinkItem;
|
|
140
|
-
|
|
141
|
-
// src/charts/constants.ts
|
|
142
|
-
var DEFAULT_NODE_COLOR = "#64748b";
|
|
143
|
-
var DEFAULT_NODE_SIZE = 10;
|
|
144
|
-
var DEFAULT_LINK_COLOR = "#94a3b8";
|
|
145
|
-
var DEFAULT_LINK_WIDTH = 1;
|
|
146
|
-
var CIRCULAR_LAYOUT_RADIUS_RATIO = 0.35;
|
|
147
|
-
var FIT_VIEW_PADDING = 40;
|
|
148
|
-
var TRANSITION_DURATION_MS = 300;
|
|
149
|
-
var PACKAGE_BOUNDARY_FILL = "rgba(148,163,184,0.06)";
|
|
150
|
-
var PACKAGE_BOUNDARY_STROKE = "#475569";
|
|
151
|
-
var PACKAGE_BOUNDARY_STROKE_WIDTH = 2;
|
|
152
|
-
var PACKAGE_BOUNDARY_DASH = "6 6";
|
|
153
|
-
var PACKAGE_LABEL_FONT_SIZE = 11;
|
|
154
|
-
var PACKAGE_LABEL_COLOR = "#475569";
|
|
155
418
|
var PackageBoundaries = ({
|
|
156
419
|
packageBounds
|
|
157
420
|
}) => {
|
|
@@ -185,118 +448,94 @@ var PackageBoundaries = ({
|
|
|
185
448
|
] }, pid)) });
|
|
186
449
|
};
|
|
187
450
|
PackageBoundaries.displayName = "PackageBoundaries";
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
nodes
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
451
|
+
var GraphCanvas = ({
|
|
452
|
+
svgRef,
|
|
453
|
+
gRef,
|
|
454
|
+
width,
|
|
455
|
+
height,
|
|
456
|
+
className,
|
|
457
|
+
nodes,
|
|
458
|
+
links,
|
|
459
|
+
pinnedNodes,
|
|
460
|
+
selectedNodeId,
|
|
461
|
+
hoveredNodeId,
|
|
462
|
+
defaultNodeColor = DEFAULT_NODE_COLOR,
|
|
463
|
+
defaultNodeSize = DEFAULT_NODE_SIZE,
|
|
464
|
+
defaultLinkColor = DEFAULT_LINK_COLOR,
|
|
465
|
+
defaultLinkWidth = DEFAULT_LINK_WIDTH,
|
|
466
|
+
showNodeLabels = true,
|
|
467
|
+
showLinkLabels = false,
|
|
468
|
+
onNodeClick,
|
|
469
|
+
onNodeHover,
|
|
470
|
+
onLinkClick,
|
|
471
|
+
packageBounds,
|
|
472
|
+
handleNodeDoubleClick,
|
|
473
|
+
handleDragStart,
|
|
474
|
+
restart,
|
|
475
|
+
setPinnedNodes
|
|
476
|
+
}) => {
|
|
477
|
+
return /* @__PURE__ */ jsxs(
|
|
478
|
+
"svg",
|
|
479
|
+
{
|
|
480
|
+
ref: svgRef,
|
|
481
|
+
width,
|
|
482
|
+
height,
|
|
483
|
+
className: cn("bg-white dark:bg-gray-900", className),
|
|
484
|
+
onDoubleClick: () => {
|
|
485
|
+
unpinAllNodes(nodes);
|
|
486
|
+
setPinnedNodes(/* @__PURE__ */ new Set());
|
|
487
|
+
restart();
|
|
488
|
+
},
|
|
489
|
+
children: [
|
|
490
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(
|
|
491
|
+
"marker",
|
|
492
|
+
{
|
|
493
|
+
id: "arrow",
|
|
494
|
+
viewBox: "0 0 10 10",
|
|
495
|
+
refX: "20",
|
|
496
|
+
refY: "5",
|
|
497
|
+
markerWidth: "6",
|
|
498
|
+
markerHeight: "6",
|
|
499
|
+
orient: "auto",
|
|
500
|
+
children: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 10 5 L 0 10 z", fill: defaultLinkColor })
|
|
501
|
+
}
|
|
502
|
+
) }),
|
|
503
|
+
/* @__PURE__ */ jsxs("g", { ref: gRef, children: [
|
|
504
|
+
links.map((link, i) => /* @__PURE__ */ jsx(
|
|
505
|
+
LinkItem_default,
|
|
506
|
+
{
|
|
507
|
+
link,
|
|
508
|
+
onClick: onLinkClick,
|
|
509
|
+
defaultWidth: defaultLinkWidth,
|
|
510
|
+
showLabel: showLinkLabels,
|
|
511
|
+
nodes
|
|
512
|
+
},
|
|
513
|
+
`link-${i}`
|
|
514
|
+
)),
|
|
515
|
+
nodes.map((node) => /* @__PURE__ */ jsx(
|
|
516
|
+
NodeItem_default,
|
|
517
|
+
{
|
|
518
|
+
node,
|
|
519
|
+
isSelected: selectedNodeId === node.id,
|
|
520
|
+
isHovered: hoveredNodeId === node.id,
|
|
521
|
+
pinned: pinnedNodes.has(node.id),
|
|
522
|
+
defaultNodeSize,
|
|
523
|
+
defaultNodeColor,
|
|
524
|
+
showLabel: showNodeLabels,
|
|
525
|
+
onClick: onNodeClick,
|
|
526
|
+
onDoubleClick: handleNodeDoubleClick,
|
|
527
|
+
onMouseEnter: (n) => onNodeHover?.(n),
|
|
528
|
+
onMouseLeave: () => onNodeHover?.(null),
|
|
529
|
+
onMouseDown: handleDragStart
|
|
530
|
+
},
|
|
531
|
+
node.id
|
|
532
|
+
)),
|
|
533
|
+
/* @__PURE__ */ jsx(PackageBoundaries, { packageBounds: packageBounds || {} })
|
|
534
|
+
] })
|
|
535
|
+
]
|
|
235
536
|
}
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
function useGraphZoom(svgRef, gRef, enableZoom, setTransform, transformRef) {
|
|
239
|
-
useEffect(() => {
|
|
240
|
-
if (!enableZoom || !svgRef.current || !gRef.current) return;
|
|
241
|
-
const svg = d32.select(svgRef.current);
|
|
242
|
-
const g = d32.select(gRef.current);
|
|
243
|
-
const zoom3 = d32.zoom().scaleExtent([0.1, 10]).on("zoom", (event) => {
|
|
244
|
-
g.attr("transform", event.transform);
|
|
245
|
-
transformRef.current = event.transform;
|
|
246
|
-
setTransform(event.transform);
|
|
247
|
-
});
|
|
248
|
-
svg.call(zoom3);
|
|
249
|
-
return () => {
|
|
250
|
-
svg.on(".zoom", null);
|
|
251
|
-
};
|
|
252
|
-
}, [enableZoom, svgRef, gRef, setTransform, transformRef]);
|
|
253
|
-
}
|
|
254
|
-
function useWindowDrag(enableDrag, svgRef, transformRef, dragActiveRef, dragNodeRef, onDragEnd) {
|
|
255
|
-
useEffect(() => {
|
|
256
|
-
if (!enableDrag) return;
|
|
257
|
-
const handleWindowMove = (event) => {
|
|
258
|
-
if (!dragActiveRef.current || !dragNodeRef.current) return;
|
|
259
|
-
const svg = svgRef.current;
|
|
260
|
-
if (!svg) return;
|
|
261
|
-
const rect = svg.getBoundingClientRect();
|
|
262
|
-
const t = transformRef.current;
|
|
263
|
-
const x = (event.clientX - rect.left - t.x) / t.k;
|
|
264
|
-
const y = (event.clientY - rect.top - t.y) / t.k;
|
|
265
|
-
dragNodeRef.current.fx = x;
|
|
266
|
-
dragNodeRef.current.fy = y;
|
|
267
|
-
};
|
|
268
|
-
const handleWindowUp = () => {
|
|
269
|
-
if (!dragActiveRef.current) return;
|
|
270
|
-
onDragEnd();
|
|
271
|
-
dragNodeRef.current = null;
|
|
272
|
-
dragActiveRef.current = false;
|
|
273
|
-
};
|
|
274
|
-
const handleWindowLeave = (event) => {
|
|
275
|
-
if (event.relatedTarget === null) handleWindowUp();
|
|
276
|
-
};
|
|
277
|
-
window.addEventListener("mousemove", handleWindowMove);
|
|
278
|
-
window.addEventListener("mouseup", handleWindowUp);
|
|
279
|
-
window.addEventListener("mouseout", handleWindowLeave);
|
|
280
|
-
window.addEventListener("blur", handleWindowUp);
|
|
281
|
-
return () => {
|
|
282
|
-
window.removeEventListener("mousemove", handleWindowMove);
|
|
283
|
-
window.removeEventListener("mouseup", handleWindowUp);
|
|
284
|
-
window.removeEventListener("mouseout", handleWindowLeave);
|
|
285
|
-
window.removeEventListener("blur", handleWindowUp);
|
|
286
|
-
};
|
|
287
|
-
}, [enableDrag, svgRef, transformRef, dragActiveRef, dragNodeRef, onDragEnd]);
|
|
288
|
-
}
|
|
289
|
-
function pinNode(node) {
|
|
290
|
-
node.fx = node.x;
|
|
291
|
-
node.fy = node.y;
|
|
292
|
-
}
|
|
293
|
-
function unpinNode(node) {
|
|
294
|
-
node.fx = null;
|
|
295
|
-
node.fy = null;
|
|
296
|
-
}
|
|
297
|
-
function unpinAllNodes(nodes) {
|
|
298
|
-
nodes.forEach(unpinNode);
|
|
299
|
-
}
|
|
537
|
+
);
|
|
538
|
+
};
|
|
300
539
|
var ForceDirectedGraph = forwardRef(
|
|
301
540
|
({
|
|
302
541
|
nodes: initialNodes,
|
|
@@ -310,12 +549,12 @@ var ForceDirectedGraph = forwardRef(
|
|
|
310
549
|
onLinkClick,
|
|
311
550
|
selectedNodeId,
|
|
312
551
|
hoveredNodeId,
|
|
313
|
-
defaultNodeColor
|
|
314
|
-
defaultNodeSize
|
|
315
|
-
defaultLinkColor
|
|
316
|
-
defaultLinkWidth
|
|
317
|
-
showNodeLabels
|
|
318
|
-
showLinkLabels
|
|
552
|
+
defaultNodeColor,
|
|
553
|
+
defaultNodeSize,
|
|
554
|
+
defaultLinkColor,
|
|
555
|
+
defaultLinkWidth,
|
|
556
|
+
showNodeLabels,
|
|
557
|
+
showLinkLabels,
|
|
319
558
|
className,
|
|
320
559
|
manualLayout = false,
|
|
321
560
|
onManualLayoutChange,
|
|
@@ -333,9 +572,8 @@ var ForceDirectedGraph = forwardRef(
|
|
|
333
572
|
const internalDragEnabledRef = useRef(enableDrag);
|
|
334
573
|
const [layout, setLayout] = useState(externalLayout || "force");
|
|
335
574
|
useEffect(() => {
|
|
336
|
-
if (externalLayout && externalLayout !== layout)
|
|
575
|
+
if (externalLayout && externalLayout !== layout)
|
|
337
576
|
setLayout(externalLayout);
|
|
338
|
-
}
|
|
339
577
|
}, [externalLayout, layout]);
|
|
340
578
|
const handleLayoutChange = useCallback(
|
|
341
579
|
(newLayout) => {
|
|
@@ -347,87 +585,32 @@ var ForceDirectedGraph = forwardRef(
|
|
|
347
585
|
useEffect(() => {
|
|
348
586
|
internalDragEnabledRef.current = enableDrag;
|
|
349
587
|
}, [enableDrag]);
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}, [initialNodes, width, height, layout]);
|
|
359
|
-
const restart = React.useCallback(() => {
|
|
360
|
-
}, []);
|
|
361
|
-
const stop = React.useCallback(() => {
|
|
362
|
-
}, []);
|
|
363
|
-
const setForcesEnabled = React.useCallback((enabled) => {
|
|
364
|
-
}, []);
|
|
365
|
-
useEffect(() => {
|
|
366
|
-
if (!nodes || nodes.length === 0) return;
|
|
367
|
-
if (layout === "circular") applyCircularLayout(nodes, width, height);
|
|
368
|
-
else if (layout === "hierarchical")
|
|
369
|
-
applyHierarchicalLayout(nodes, width, height);
|
|
370
|
-
restart();
|
|
371
|
-
}, [layout, nodes, width, height, restart]);
|
|
588
|
+
const { restart, stop, setForcesEnabled } = useSimulationControls();
|
|
589
|
+
const { nodes } = useGraphLayout(
|
|
590
|
+
initialNodes,
|
|
591
|
+
width,
|
|
592
|
+
height,
|
|
593
|
+
layout,
|
|
594
|
+
restart
|
|
595
|
+
);
|
|
372
596
|
useEffect(() => {
|
|
373
|
-
|
|
374
|
-
else setForcesEnabled(true);
|
|
597
|
+
setForcesEnabled(!(manualLayout || pinnedNodes.size > 0));
|
|
375
598
|
}, [manualLayout, pinnedNodes, setForcesEnabled]);
|
|
376
599
|
useImperativeHandle(
|
|
377
600
|
ref,
|
|
378
|
-
() => ({
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
restart();
|
|
392
|
-
},
|
|
393
|
-
resetLayout: () => {
|
|
394
|
-
unpinAllNodes(nodes);
|
|
395
|
-
setPinnedNodes(/* @__PURE__ */ new Set());
|
|
396
|
-
restart();
|
|
397
|
-
},
|
|
398
|
-
fitView: () => {
|
|
399
|
-
if (!svgRef.current || !nodes.length) return;
|
|
400
|
-
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
401
|
-
nodes.forEach((node) => {
|
|
402
|
-
if (node.x !== void 0 && node.y !== void 0) {
|
|
403
|
-
const size = node.size || DEFAULT_NODE_SIZE;
|
|
404
|
-
minX = Math.min(minX, node.x - size);
|
|
405
|
-
maxX = Math.max(maxX, node.x + size);
|
|
406
|
-
minY = Math.min(minY, node.y - size);
|
|
407
|
-
maxY = Math.max(maxY, node.y + size);
|
|
408
|
-
}
|
|
409
|
-
});
|
|
410
|
-
if (!isFinite(minX)) return;
|
|
411
|
-
const scale = Math.min(
|
|
412
|
-
(width - FIT_VIEW_PADDING * 2) / (maxX - minX),
|
|
413
|
-
(height - FIT_VIEW_PADDING * 2) / (maxY - minY),
|
|
414
|
-
10
|
|
415
|
-
);
|
|
416
|
-
const x = width / 2 - (minX + maxX) / 2 * scale;
|
|
417
|
-
const y = height / 2 - (minY + maxY) / 2 * scale;
|
|
418
|
-
if (gRef.current && svgRef.current) {
|
|
419
|
-
const svg = d32.select(svgRef.current);
|
|
420
|
-
const newTransform = d32.zoomIdentity.translate(x, y).scale(scale);
|
|
421
|
-
svg.transition().duration(TRANSITION_DURATION_MS).call(d32.zoom().transform, newTransform);
|
|
422
|
-
setTransform(newTransform);
|
|
423
|
-
}
|
|
424
|
-
},
|
|
425
|
-
getPinnedNodes: () => Array.from(pinnedNodes),
|
|
426
|
-
setDragMode: (enabled) => {
|
|
427
|
-
internalDragEnabledRef.current = enabled;
|
|
428
|
-
},
|
|
429
|
-
setLayout: (newLayout) => handleLayoutChange(newLayout),
|
|
430
|
-
getLayout: () => layout
|
|
601
|
+
() => useImperativeHandleMethods({
|
|
602
|
+
nodes,
|
|
603
|
+
pinnedNodes,
|
|
604
|
+
setPinnedNodes,
|
|
605
|
+
restart,
|
|
606
|
+
width,
|
|
607
|
+
height,
|
|
608
|
+
layout,
|
|
609
|
+
handleLayoutChange,
|
|
610
|
+
svgRef,
|
|
611
|
+
gRef,
|
|
612
|
+
setTransform,
|
|
613
|
+
internalDragEnabledRef
|
|
431
614
|
}),
|
|
432
615
|
[
|
|
433
616
|
nodes,
|
|
@@ -441,8 +624,7 @@ var ForceDirectedGraph = forwardRef(
|
|
|
441
624
|
]
|
|
442
625
|
);
|
|
443
626
|
useEffect(() => {
|
|
444
|
-
|
|
445
|
-
onManualLayoutChange(manualLayout);
|
|
627
|
+
onManualLayoutChange?.(manualLayout);
|
|
446
628
|
}, [manualLayout, onManualLayoutChange]);
|
|
447
629
|
useGraphZoom(svgRef, gRef, enableZoom, setTransform, transformRef);
|
|
448
630
|
useWindowDrag(
|
|
@@ -458,57 +640,48 @@ var ForceDirectedGraph = forwardRef(
|
|
|
458
640
|
);
|
|
459
641
|
useEffect(() => {
|
|
460
642
|
if (!gRef.current) return;
|
|
461
|
-
const g =
|
|
643
|
+
const g = d33.select(gRef.current);
|
|
462
644
|
g.selectAll("g.node").each(function() {
|
|
463
|
-
const
|
|
464
|
-
if (
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
645
|
+
const d = d33.select(this).datum();
|
|
646
|
+
if (d)
|
|
647
|
+
d33.select(this).attr(
|
|
648
|
+
"transform",
|
|
649
|
+
`translate(${d.x || 0},${d.y || 0})`
|
|
650
|
+
);
|
|
469
651
|
});
|
|
470
652
|
g.selectAll("line").each(function() {
|
|
471
|
-
const l =
|
|
653
|
+
const l = d33.select(this).datum();
|
|
472
654
|
if (!l) return;
|
|
473
|
-
const s = typeof l.source === "object" ? l.source : nodes.find((n) => n.id === l.source)
|
|
474
|
-
const t = typeof l.target === "object" ? l.target : nodes.find((n) => n.id === l.target)
|
|
475
|
-
if (
|
|
476
|
-
|
|
655
|
+
const s = typeof l.source === "object" ? l.source : nodes.find((n) => n.id === l.source);
|
|
656
|
+
const t = typeof l.target === "object" ? l.target : nodes.find((n) => n.id === l.target);
|
|
657
|
+
if (s && t)
|
|
658
|
+
d33.select(this).attr("x1", s.x).attr("y1", s.y).attr("x2", t.x).attr("y2", t.y);
|
|
477
659
|
});
|
|
478
660
|
}, [nodes, initialLinks]);
|
|
479
|
-
const handleDragStart =
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
pinNode(node);
|
|
487
|
-
setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
|
|
488
|
-
stop();
|
|
489
|
-
},
|
|
490
|
-
[enableDrag, stop]
|
|
661
|
+
const { handleDragStart, handleNodeDoubleClick } = useNodeInteractions(
|
|
662
|
+
enableDrag,
|
|
663
|
+
nodes,
|
|
664
|
+
pinnedNodes,
|
|
665
|
+
setPinnedNodes,
|
|
666
|
+
restart,
|
|
667
|
+
stop
|
|
491
668
|
);
|
|
492
669
|
useEffect(() => {
|
|
493
670
|
if (!gRef.current || !enableDrag) return;
|
|
494
|
-
const g =
|
|
495
|
-
const dragBehavior =
|
|
496
|
-
const target = event.sourceEvent
|
|
497
|
-
const
|
|
498
|
-
const id = grp?.getAttribute("data-id");
|
|
671
|
+
const g = d33.select(gRef.current);
|
|
672
|
+
const dragBehavior = d33.drag().on("start", (event) => {
|
|
673
|
+
const target = event.sourceEvent?.target || event.target;
|
|
674
|
+
const id = target.closest?.("g.node")?.getAttribute("data-id");
|
|
499
675
|
if (!id || !internalDragEnabledRef.current) return;
|
|
500
676
|
const node = nodes.find((n) => n.id === id);
|
|
501
677
|
if (!node) return;
|
|
502
678
|
if (!event.active) restart();
|
|
503
679
|
dragActiveRef.current = true;
|
|
504
680
|
dragNodeRef.current = node;
|
|
505
|
-
pinNode(node);
|
|
506
|
-
setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
|
|
507
681
|
}).on("drag", (event) => {
|
|
508
|
-
if (!dragActiveRef.current || !dragNodeRef.current)
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const rect = svg.getBoundingClientRect();
|
|
682
|
+
if (!dragActiveRef.current || !dragNodeRef.current || !svgRef.current)
|
|
683
|
+
return;
|
|
684
|
+
const rect = svgRef.current.getBoundingClientRect();
|
|
512
685
|
dragNodeRef.current.fx = (event.sourceEvent.clientX - rect.left - transform.x) / transform.k;
|
|
513
686
|
dragNodeRef.current.fy = (event.sourceEvent.clientY - rect.top - transform.y) / transform.k;
|
|
514
687
|
}).on("end", () => {
|
|
@@ -519,99 +692,40 @@ var ForceDirectedGraph = forwardRef(
|
|
|
519
692
|
return () => {
|
|
520
693
|
g.selectAll("g.node").on(".drag", null);
|
|
521
694
|
};
|
|
522
|
-
}, [
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
nodes,
|
|
526
|
-
transform,
|
|
527
|
-
restart,
|
|
528
|
-
setForcesEnabled,
|
|
529
|
-
internalDragEnabledRef
|
|
530
|
-
]);
|
|
531
|
-
const handleNodeDoubleClick = useCallback(
|
|
532
|
-
(event, node) => {
|
|
533
|
-
event.stopPropagation();
|
|
534
|
-
if (!enableDrag) return;
|
|
535
|
-
if (node.fx === null || node.fx === void 0) {
|
|
536
|
-
pinNode(node);
|
|
537
|
-
setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
|
|
538
|
-
} else {
|
|
539
|
-
unpinNode(node);
|
|
540
|
-
setPinnedNodes((prev) => {
|
|
541
|
-
const next = new Set(prev);
|
|
542
|
-
next.delete(node.id);
|
|
543
|
-
return next;
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
restart();
|
|
547
|
-
},
|
|
548
|
-
[enableDrag, restart]
|
|
549
|
-
);
|
|
550
|
-
return /* @__PURE__ */ jsxs(
|
|
551
|
-
"svg",
|
|
695
|
+
}, [gRef, enableDrag, nodes, transform, restart, setForcesEnabled]);
|
|
696
|
+
return /* @__PURE__ */ jsx(
|
|
697
|
+
GraphCanvas,
|
|
552
698
|
{
|
|
553
|
-
|
|
699
|
+
svgRef,
|
|
700
|
+
gRef,
|
|
554
701
|
width,
|
|
555
702
|
height,
|
|
556
|
-
className
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
/* @__PURE__ */ jsxs("g", { ref: gRef, children: [
|
|
577
|
-
initialLinks.map((link, i) => /* @__PURE__ */ jsx(
|
|
578
|
-
LinkItem_default,
|
|
579
|
-
{
|
|
580
|
-
link,
|
|
581
|
-
onClick: onLinkClick,
|
|
582
|
-
defaultWidth: defaultLinkWidth,
|
|
583
|
-
showLabel: showLinkLabels,
|
|
584
|
-
nodes
|
|
585
|
-
},
|
|
586
|
-
`link-${i}`
|
|
587
|
-
)),
|
|
588
|
-
nodes.map((node) => /* @__PURE__ */ jsx(
|
|
589
|
-
NodeItem_default,
|
|
590
|
-
{
|
|
591
|
-
node,
|
|
592
|
-
isSelected: selectedNodeId === node.id,
|
|
593
|
-
isHovered: hoveredNodeId === node.id,
|
|
594
|
-
pinned: pinnedNodes.has(node.id),
|
|
595
|
-
defaultNodeSize,
|
|
596
|
-
defaultNodeColor,
|
|
597
|
-
showLabel: showNodeLabels,
|
|
598
|
-
onClick: onNodeClick,
|
|
599
|
-
onDoubleClick: handleNodeDoubleClick,
|
|
600
|
-
onMouseEnter: (n) => onNodeHover?.(n),
|
|
601
|
-
onMouseLeave: () => onNodeHover?.(null),
|
|
602
|
-
onMouseDown: handleDragStart
|
|
603
|
-
},
|
|
604
|
-
node.id
|
|
605
|
-
)),
|
|
606
|
-
/* @__PURE__ */ jsx(PackageBoundaries, { packageBounds: packageBounds || {} })
|
|
607
|
-
] })
|
|
608
|
-
]
|
|
703
|
+
className,
|
|
704
|
+
nodes,
|
|
705
|
+
links: initialLinks,
|
|
706
|
+
pinnedNodes,
|
|
707
|
+
selectedNodeId,
|
|
708
|
+
hoveredNodeId,
|
|
709
|
+
defaultNodeColor,
|
|
710
|
+
defaultNodeSize,
|
|
711
|
+
defaultLinkColor,
|
|
712
|
+
defaultLinkWidth,
|
|
713
|
+
showNodeLabels,
|
|
714
|
+
showLinkLabels,
|
|
715
|
+
onNodeClick,
|
|
716
|
+
onNodeHover,
|
|
717
|
+
onLinkClick,
|
|
718
|
+
packageBounds,
|
|
719
|
+
handleNodeDoubleClick,
|
|
720
|
+
handleDragStart,
|
|
721
|
+
restart,
|
|
722
|
+
setPinnedNodes
|
|
609
723
|
}
|
|
610
724
|
);
|
|
611
725
|
}
|
|
612
726
|
);
|
|
613
727
|
ForceDirectedGraph.displayName = "ForceDirectedGraph";
|
|
614
728
|
|
|
615
|
-
export { ForceDirectedGraph };
|
|
729
|
+
export { ForceDirectedGraph, ForceDirectedGraph as default };
|
|
616
730
|
//# sourceMappingURL=ForceDirectedGraph.js.map
|
|
617
731
|
//# sourceMappingURL=ForceDirectedGraph.js.map
|