@aiready/components 0.1.7 → 0.1.9

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,277 +1,10 @@
1
- import React, { forwardRef, useRef, useState, useEffect, useImperativeHandle, useCallback } from 'react';
2
- import * as d32 from 'd3';
1
+ import React, { forwardRef, useRef, useState, useEffect, useCallback, useImperativeHandle } from 'react';
2
+ import * as d3 from 'd3';
3
3
  import { clsx } from 'clsx';
4
4
  import { twMerge } from 'tailwind-merge';
5
5
  import { jsxs, jsx } from 'react/jsx-runtime';
6
6
 
7
7
  // src/charts/ForceDirectedGraph.tsx
8
- function useForceSimulation(initialNodes, initialLinks, options) {
9
- const {
10
- chargeStrength = -300,
11
- linkDistance = 100,
12
- linkStrength = 1,
13
- collisionStrength = 1,
14
- collisionRadius = 10,
15
- centerStrength = 0.1,
16
- width,
17
- height,
18
- alphaDecay = 0.0228,
19
- velocityDecay = 0.4,
20
- alphaTarget = 0,
21
- warmAlpha = 0.3,
22
- alphaMin = 0.01,
23
- // @ts-ignore allow extra option
24
- stabilizeOnStop = true,
25
- onTick,
26
- // Optional throttle in milliseconds for tick updates (reduce React re-renders)
27
- // Lower values = smoother but more CPU; default ~30ms (~33fps)
28
- // @ts-ignore allow extra option
29
- tickThrottleMs = 33,
30
- // @ts-ignore allow extra option
31
- maxSimulationTimeMs = 3e3
32
- } = options;
33
- const [nodes, setNodes] = useState(initialNodes);
34
- const [links, setLinks] = useState(initialLinks);
35
- const [isRunning, setIsRunning] = useState(false);
36
- const [alpha, setAlpha] = useState(1);
37
- const simulationRef = useRef(null);
38
- const stopTimeoutRef = useRef(null);
39
- const nodesKey = initialNodes.map((n) => n.id).join("|");
40
- const linksKey = (initialLinks || []).map((l) => {
41
- const s = typeof l.source === "string" ? l.source : l.source?.id;
42
- const t = typeof l.target === "string" ? l.target : l.target?.id;
43
- return `${s}->${t}:${l.type || ""}`;
44
- }).join("|");
45
- useEffect(() => {
46
- const nodesCopy = initialNodes.map((node) => ({ ...node }));
47
- const linksCopy = initialLinks.map((link) => ({ ...link }));
48
- try {
49
- nodesCopy.forEach((n, i) => {
50
- const angle = i * 2 * Math.PI / nodesCopy.length;
51
- const radius = Math.min(width, height) * 0.45;
52
- n.x = width / 2 + radius * Math.cos(angle);
53
- n.y = height / 2 + radius * Math.sin(angle);
54
- n.vx = (Math.random() - 0.5) * 2;
55
- n.vy = (Math.random() - 0.5) * 2;
56
- });
57
- } catch (e) {
58
- nodesCopy.forEach((n) => {
59
- n.x = Math.random() * width;
60
- n.y = Math.random() * height;
61
- n.vx = (Math.random() - 0.5) * 10;
62
- n.vy = (Math.random() - 0.5) * 10;
63
- });
64
- }
65
- const simulation = d32.forceSimulation(nodesCopy);
66
- try {
67
- const linkForce = d32.forceLink(linksCopy);
68
- linkForce.id((d) => d.id).distance((d) => d && d.distance != null ? d.distance : linkDistance).strength(linkStrength);
69
- simulation.force("link", linkForce);
70
- } catch (e) {
71
- try {
72
- simulation.force("link", d32.forceLink(linksCopy));
73
- } catch (e2) {
74
- }
75
- }
76
- try {
77
- simulation.force("charge", d32.forceManyBody().strength(chargeStrength));
78
- simulation.force("center", d32.forceCenter(width / 2, height / 2).strength(centerStrength));
79
- const collide = d32.forceCollide().radius((d) => {
80
- const nodeSize = d && d.size ? d.size : 10;
81
- return nodeSize + collisionRadius;
82
- }).strength(collisionStrength);
83
- simulation.force("collision", collide);
84
- simulation.force("x", d32.forceX(width / 2).strength(Math.max(0.02, centerStrength * 0.5)));
85
- simulation.force("y", d32.forceY(height / 2).strength(Math.max(0.02, centerStrength * 0.5)));
86
- simulation.alphaDecay(alphaDecay);
87
- simulation.velocityDecay(velocityDecay);
88
- simulation.alphaMin(alphaMin);
89
- try {
90
- simulation.alphaTarget(alphaTarget);
91
- } catch (e) {
92
- }
93
- try {
94
- simulation.alpha(warmAlpha);
95
- } catch (e) {
96
- }
97
- } catch (e) {
98
- }
99
- simulationRef.current = simulation;
100
- if (stopTimeoutRef.current != null) {
101
- try {
102
- globalThis.clearTimeout(stopTimeoutRef.current);
103
- } catch (e) {
104
- }
105
- stopTimeoutRef.current = null;
106
- }
107
- if (maxSimulationTimeMs && maxSimulationTimeMs > 0) {
108
- stopTimeoutRef.current = globalThis.setTimeout(() => {
109
- try {
110
- if (stabilizeOnStop) {
111
- nodesCopy.forEach((n) => {
112
- n.vx = 0;
113
- n.vy = 0;
114
- if (typeof n.x === "number") n.x = Number(n.x.toFixed(3));
115
- if (typeof n.y === "number") n.y = Number(n.y.toFixed(3));
116
- });
117
- }
118
- simulation.alpha(0);
119
- simulation.stop();
120
- } catch (e) {
121
- }
122
- setIsRunning(false);
123
- setNodes([...nodesCopy]);
124
- setLinks([...linksCopy]);
125
- }, maxSimulationTimeMs);
126
- }
127
- let rafId = null;
128
- let lastUpdate = 0;
129
- const tickHandler = () => {
130
- try {
131
- if (typeof onTick === "function") onTick(nodesCopy, linksCopy, simulation);
132
- } catch (e) {
133
- }
134
- try {
135
- if (simulation.alpha() <= alphaMin) {
136
- try {
137
- if (stabilizeOnStop) {
138
- nodesCopy.forEach((n) => {
139
- n.vx = 0;
140
- n.vy = 0;
141
- if (typeof n.x === "number") n.x = Number(n.x.toFixed(3));
142
- if (typeof n.y === "number") n.y = Number(n.y.toFixed(3));
143
- });
144
- }
145
- simulation.stop();
146
- } catch (e) {
147
- }
148
- setAlpha(simulation.alpha());
149
- setIsRunning(false);
150
- setNodes([...nodesCopy]);
151
- setLinks([...linksCopy]);
152
- return;
153
- }
154
- } catch (e) {
155
- }
156
- const now = Date.now();
157
- const shouldUpdate = now - lastUpdate >= tickThrottleMs;
158
- if (rafId == null && shouldUpdate) {
159
- rafId = (globalThis.requestAnimationFrame || ((cb) => setTimeout(cb, 16)))(() => {
160
- rafId = null;
161
- lastUpdate = Date.now();
162
- setNodes([...nodesCopy]);
163
- setLinks([...linksCopy]);
164
- setAlpha(simulation.alpha());
165
- setIsRunning(simulation.alpha() > simulation.alphaMin());
166
- });
167
- }
168
- };
169
- simulation.on("tick", tickHandler);
170
- simulation.on("end", () => {
171
- setIsRunning(false);
172
- });
173
- return () => {
174
- try {
175
- simulation.on("tick", null);
176
- } catch (e) {
177
- }
178
- if (stopTimeoutRef.current != null) {
179
- try {
180
- globalThis.clearTimeout(stopTimeoutRef.current);
181
- } catch (e) {
182
- }
183
- stopTimeoutRef.current = null;
184
- }
185
- if (rafId != null) {
186
- try {
187
- (globalThis.cancelAnimationFrame || ((id) => clearTimeout(id)))(rafId);
188
- } catch (e) {
189
- }
190
- rafId = null;
191
- }
192
- simulation.stop();
193
- };
194
- }, [
195
- nodesKey,
196
- linksKey,
197
- chargeStrength,
198
- linkDistance,
199
- linkStrength,
200
- collisionStrength,
201
- collisionRadius,
202
- centerStrength,
203
- width,
204
- height,
205
- alphaDecay,
206
- velocityDecay,
207
- alphaTarget,
208
- alphaMin,
209
- stabilizeOnStop,
210
- tickThrottleMs,
211
- maxSimulationTimeMs
212
- ]);
213
- const restart = () => {
214
- if (simulationRef.current) {
215
- try {
216
- simulationRef.current.alphaTarget(warmAlpha).restart();
217
- } catch (e) {
218
- simulationRef.current.restart();
219
- }
220
- setIsRunning(true);
221
- if (stopTimeoutRef.current != null) {
222
- try {
223
- globalThis.clearTimeout(stopTimeoutRef.current);
224
- } catch (e) {
225
- }
226
- stopTimeoutRef.current = null;
227
- }
228
- if (maxSimulationTimeMs && maxSimulationTimeMs > 0) {
229
- stopTimeoutRef.current = globalThis.setTimeout(() => {
230
- try {
231
- simulationRef.current?.alpha(0);
232
- simulationRef.current?.stop();
233
- } catch (e) {
234
- }
235
- setIsRunning(false);
236
- }, maxSimulationTimeMs);
237
- }
238
- }
239
- };
240
- const stop = () => {
241
- if (simulationRef.current) {
242
- simulationRef.current.stop();
243
- setIsRunning(false);
244
- }
245
- };
246
- const originalForcesRef = useRef({ charge: chargeStrength, link: linkStrength, collision: collisionStrength });
247
- const forcesEnabledRef = useRef(true);
248
- const setForcesEnabled = (enabled) => {
249
- const sim = simulationRef.current;
250
- if (!sim) return;
251
- if (forcesEnabledRef.current === enabled) return;
252
- forcesEnabledRef.current = enabled;
253
- try {
254
- const charge = sim.force("charge");
255
- if (charge && typeof charge.strength === "function") {
256
- charge.strength(enabled ? originalForcesRef.current.charge : 0);
257
- }
258
- const link = sim.force("link");
259
- if (link && typeof link.strength === "function") {
260
- link.strength(enabled ? originalForcesRef.current.link : 0);
261
- }
262
- } catch (e) {
263
- }
264
- };
265
- return {
266
- nodes,
267
- links,
268
- restart,
269
- stop,
270
- isRunning,
271
- alpha,
272
- setForcesEnabled
273
- };
274
- }
275
8
  function cn(...inputs) {
276
9
  return twMerge(clsx(inputs));
277
10
  }
@@ -382,7 +115,6 @@ var ForceDirectedGraph = forwardRef(
382
115
  links: initialLinks,
383
116
  width,
384
117
  height,
385
- simulationOptions,
386
118
  enableZoom = true,
387
119
  enableDrag = true,
388
120
  onNodeClick,
@@ -399,7 +131,9 @@ var ForceDirectedGraph = forwardRef(
399
131
  className,
400
132
  manualLayout = false,
401
133
  onManualLayoutChange,
402
- packageBounds
134
+ packageBounds,
135
+ layout: externalLayout,
136
+ onLayoutChange
403
137
  }, ref) => {
404
138
  const svgRef = useRef(null);
405
139
  const gRef = useRef(null);
@@ -409,180 +143,102 @@ var ForceDirectedGraph = forwardRef(
409
143
  const dragActiveRef = useRef(false);
410
144
  const [pinnedNodes, setPinnedNodes] = useState(/* @__PURE__ */ new Set());
411
145
  const internalDragEnabledRef = useRef(enableDrag);
146
+ const [layout, setLayout] = useState(externalLayout || "force");
147
+ useEffect(() => {
148
+ if (externalLayout && externalLayout !== layout) {
149
+ setLayout(externalLayout);
150
+ }
151
+ }, [externalLayout]);
152
+ const handleLayoutChange = useCallback((newLayout) => {
153
+ setLayout(newLayout);
154
+ onLayoutChange?.(newLayout);
155
+ }, [onLayoutChange]);
412
156
  useEffect(() => {
413
157
  internalDragEnabledRef.current = enableDrag;
414
158
  }, [enableDrag]);
415
- const onTick = (_nodesCopy, _linksCopy, _sim) => {
416
- try {
417
- const boundsToUse = clusterBounds?.bounds ?? packageBounds;
418
- const nodeClusterMap = clusterBounds?.nodeToCluster ?? {};
419
- if (boundsToUse) {
420
- Object.values(nodesById).forEach((n) => {
421
- if (!n) return;
422
- const group = n.group ?? n.packageGroup;
423
- const clusterKey = nodeClusterMap[n.id];
424
- const key = clusterKey ?? (group ? `pkg:${group}` : void 0);
425
- if (!key) return;
426
- const center = boundsToUse[key];
427
- if (!center) return;
428
- const dx = center.x - (n.x ?? 0);
429
- const dy = center.y - (n.y ?? 0);
430
- const dist = Math.sqrt(dx * dx + dy * dy);
431
- const pullStrength = Math.min(0.5, 0.15 * (dist / (center.r || 200)) + 0.06);
432
- if (!isNaN(pullStrength) && isFinite(pullStrength)) {
433
- n.vx = (n.vx ?? 0) + dx / (dist || 1) * pullStrength;
434
- n.vy = (n.vy ?? 0) + dy / (dist || 1) * pullStrength;
435
- }
436
- if (center.r && dist > center.r) {
437
- const excess = (dist - center.r) / (dist || 1);
438
- n.vx = (n.vx ?? 0) - dx * 0.02 * excess;
439
- n.vy = (n.vy ?? 0) - dy * 0.02 * excess;
440
- }
441
- });
442
- }
443
- } catch (e) {
159
+ const nodes = React.useMemo(() => {
160
+ if (!initialNodes || !initialNodes.length) return initialNodes;
161
+ const cx = width / 2;
162
+ const cy = height / 2;
163
+ if (layout === "force") {
164
+ return initialNodes.map((n) => ({
165
+ ...n,
166
+ x: Math.random() * width,
167
+ y: Math.random() * height
168
+ }));
444
169
  }
445
- };
446
- const { packageAreas, localPositions } = React.useMemo(() => {
447
- try {
448
- if (!initialNodes || !initialNodes.length) return { packageAreas: {}, localPositions: {} };
449
- const groups = /* @__PURE__ */ new Map();
450
- initialNodes.forEach((n) => {
451
- const key = n.packageGroup || n.group || "root";
452
- if (!groups.has(key)) groups.set(key, []);
453
- groups.get(key).push(n);
454
- });
455
- const groupKeys = Array.from(groups.keys());
456
- const children = groupKeys.map((k) => ({ name: k, value: Math.max(1, groups.get(k).length) }));
457
- const root = d32.hierarchy({ children });
458
- root.sum((d) => d.value);
459
- const pack2 = d32.pack().size([width, height]).padding(Math.max(20, Math.min(width, height) * 0.03));
460
- const packed = pack2(root);
461
- const packageAreas2 = {};
462
- if (packed.children) {
463
- packed.children.forEach((c) => {
464
- const name = c.data.name;
465
- packageAreas2[name] = { x: c.x, y: c.y, r: Math.max(40, c.r) };
170
+ if (layout === "circular") {
171
+ const radius = Math.min(width, height) * 0.35;
172
+ return initialNodes.map((n, i) => ({
173
+ ...n,
174
+ x: cx + Math.cos(2 * Math.PI * i / initialNodes.length) * radius,
175
+ y: cy + Math.sin(2 * Math.PI * i / initialNodes.length) * radius
176
+ }));
177
+ }
178
+ if (layout === "hierarchical") {
179
+ const cols = Math.ceil(Math.sqrt(initialNodes.length));
180
+ const spacingX = width / (cols + 1);
181
+ const spacingY = height / (Math.ceil(initialNodes.length / cols) + 1);
182
+ return initialNodes.map((n, i) => ({
183
+ ...n,
184
+ x: spacingX * (i % cols + 1),
185
+ y: spacingY * (Math.floor(i / cols) + 1)
186
+ }));
187
+ }
188
+ return initialNodes;
189
+ }, [initialNodes, width, height, layout]);
190
+ const links = initialLinks;
191
+ const restart = React.useCallback(() => {
192
+ }, []);
193
+ const stop = React.useCallback(() => {
194
+ }, []);
195
+ const setForcesEnabled = React.useCallback((_enabled) => {
196
+ }, []);
197
+ useEffect(() => {
198
+ if (!nodes || nodes.length === 0) return;
199
+ const applyLayout = () => {
200
+ const cx = width / 2;
201
+ const cy = height / 2;
202
+ if (layout === "circular") {
203
+ const radius = Math.min(width, height) * 0.35;
204
+ nodes.forEach((node, i) => {
205
+ const angle = 2 * Math.PI * i / nodes.length;
206
+ node.fx = cx + Math.cos(angle) * radius;
207
+ node.fy = cy + Math.sin(angle) * radius;
466
208
  });
467
- }
468
- const localPositions2 = {};
469
- groups.forEach((nodesInGroup, _key) => {
470
- if (!nodesInGroup || nodesInGroup.length === 0) return;
471
- const localNodes = nodesInGroup.map((n) => ({ id: n.id, x: Math.random() * 10 - 5, y: Math.random() * 10 - 5, size: n.size || 10 }));
472
- const localLinks = (initialLinks || []).filter((l) => {
473
- const s = typeof l.source === "string" ? l.source : l.source && l.source.id;
474
- const t = typeof l.target === "string" ? l.target : l.target && l.target.id;
475
- return localNodes.some((ln) => ln.id === s) && localNodes.some((ln) => ln.id === t);
476
- }).map((l) => ({ source: typeof l.source === "string" ? l.source : l.source.id, target: typeof l.target === "string" ? l.target : l.target.id }));
477
- if (localNodes.length === 1) {
478
- localPositions2[localNodes[0].id] = { x: 0, y: 0 };
479
- return;
480
- }
481
- const sim = d32.forceSimulation(localNodes).force("link", d32.forceLink(localLinks).id((d) => d.id).distance(30).strength(0.8)).force("charge", d32.forceManyBody().strength(-15)).force("collide", d32.forceCollide((d) => (d.size || 10) + 6).iterations(2)).stop();
482
- const ticks = 300;
483
- for (let i = 0; i < ticks; i++) sim.tick();
484
- localNodes.forEach((ln) => {
485
- localPositions2[ln.id] = { x: ln.x ?? 0, y: ln.y ?? 0 };
209
+ } else if (layout === "hierarchical") {
210
+ const groups = /* @__PURE__ */ new Map();
211
+ nodes.forEach((n) => {
212
+ const key = n.packageGroup || n.group || "root";
213
+ if (!groups.has(key)) groups.set(key, []);
214
+ groups.get(key).push(n);
486
215
  });
487
- });
488
- return { packageAreas: packageAreas2, localPositions: localPositions2 };
489
- } catch (e) {
490
- return { packageAreas: {}, localPositions: {} };
491
- }
492
- }, [initialNodes, initialLinks, width, height]);
493
- const seededNodes = React.useMemo(() => {
494
- if (!initialNodes || !Object.keys(packageAreas || {}).length) return initialNodes;
495
- return initialNodes.map((n) => {
496
- const key = n.packageGroup || n.group || "root";
497
- const area = packageAreas[key];
498
- const lp = localPositions[n.id];
499
- if (!area || !lp) return n;
500
- const scale = Math.max(0.5, area.r * 0.6 / (Math.max(1, Math.sqrt(lp.x * lp.x + lp.y * lp.y)) || 1));
501
- return { ...n, x: area.x + lp.x * scale, y: area.y + lp.y * scale };
502
- });
503
- }, [initialNodes, packageAreas, localPositions]);
504
- const { nodes, links, restart, stop, setForcesEnabled } = useForceSimulation(seededNodes || initialNodes, initialLinks, {
505
- width,
506
- height,
507
- chargeStrength: manualLayout ? 0 : void 0,
508
- onTick,
509
- ...simulationOptions
510
- });
511
- const nodesById = React.useMemo(() => {
512
- const m = {};
513
- (nodes || []).forEach((n) => {
514
- if (n && n.id) m[n.id] = n;
515
- });
516
- return m;
517
- }, [nodes]);
518
- const clusterBounds = React.useMemo(() => {
519
- try {
520
- if (!links || !nodes) return null;
521
- const nodeIds = new Set(nodes.map((n) => n.id));
522
- const adj = /* @__PURE__ */ new Map();
523
- nodes.forEach((n) => adj.set(n.id, /* @__PURE__ */ new Set()));
524
- links.forEach((l) => {
525
- const type = l.type || "reference";
526
- if (type !== "dependency") return;
527
- const s = typeof l.source === "string" ? l.source : l.source && l.source.id || null;
528
- const t = typeof l.target === "string" ? l.target : l.target && l.target.id || null;
529
- if (!s || !t) return;
530
- if (!nodeIds.has(s) || !nodeIds.has(t)) return;
531
- adj.get(s)?.add(t);
532
- adj.get(t)?.add(s);
533
- });
534
- const visited = /* @__PURE__ */ new Set();
535
- const comps = [];
536
- for (const nid of nodeIds) {
537
- if (visited.has(nid)) continue;
538
- const stack = [nid];
539
- const comp = [];
540
- visited.add(nid);
541
- while (stack.length) {
542
- const cur = stack.pop();
543
- comp.push(cur);
544
- const neigh = adj.get(cur);
545
- if (!neigh) continue;
546
- for (const nb of neigh) {
547
- if (!visited.has(nb)) {
548
- visited.add(nb);
549
- stack.push(nb);
550
- }
216
+ const groupArray = Array.from(groups.entries());
217
+ const cols = Math.ceil(Math.sqrt(groupArray.length));
218
+ const groupSpacingX = width * 0.8 / cols;
219
+ const groupSpacingY = height * 0.8 / Math.ceil(groupArray.length / cols);
220
+ groupArray.forEach(([groupKey, groupNodes], gi) => {
221
+ const col = gi % cols;
222
+ const row = Math.floor(gi / cols);
223
+ const groupX = (col + 0.5) * groupSpacingX;
224
+ const groupY = (row + 0.5) * groupSpacingY;
225
+ if (groupKey.startsWith("pkg:") || groupKey === groupKey) {
226
+ groupNodes.forEach((n, ni) => {
227
+ const angle = 2 * Math.PI * ni / groupNodes.length;
228
+ const r = Math.min(80, 20 + groupNodes.length * 8);
229
+ n.fx = groupX + Math.cos(angle) * r;
230
+ n.fy = groupY + Math.sin(angle) * r;
231
+ });
551
232
  }
552
- }
553
- comps.push(comp);
233
+ });
554
234
  }
555
- if (comps.length <= 1) return null;
556
- const children = comps.map((c, i) => ({ name: String(i), value: Math.max(1, c.length) }));
557
- d32.hierarchy({ children }).sum((d) => d.value).sort((a, b) => b.value - a.value);
558
- const num = comps.length;
559
- const cx = width / 2;
560
- const cy = height / 2;
561
- const base = Math.max(width, height);
562
- const circleRadius = base * Math.max(30, num * 20, Math.sqrt(num) * 12);
563
- const map = {};
564
- comps.forEach((c, i) => {
565
- const angle = 2 * Math.PI * i / num;
566
- const x = cx + Math.cos(angle) * circleRadius;
567
- const y = cy + Math.sin(angle) * circleRadius;
568
- const sizeBias = Math.sqrt(Math.max(1, c.length));
569
- const r = Math.max(200, 100 * sizeBias);
570
- map[`cluster:${i}`] = { x, y, r };
571
- });
572
- const nodeToCluster = {};
573
- comps.forEach((c, i) => c.forEach((nid) => nodeToCluster[nid] = `cluster:${i}`));
574
- return { bounds: map, nodeToCluster };
575
- } catch (e) {
576
- return null;
577
- }
578
- }, [nodes, links, width, height]);
579
- useEffect(() => {
580
- if (!packageBounds && !clusterBounds && (!packageAreas || Object.keys(packageAreas).length === 0)) return;
581
- try {
582
- restart();
583
- } catch (e) {
584
- }
585
- }, [packageBounds, clusterBounds, packageAreas, restart]);
235
+ try {
236
+ restart();
237
+ } catch (e) {
238
+ }
239
+ };
240
+ applyLayout();
241
+ }, [layout, nodes, width, height, restart]);
586
242
  useEffect(() => {
587
243
  try {
588
244
  if (manualLayout || pinnedNodes.size > 0) setForcesEnabled(false);
@@ -645,18 +301,22 @@ var ForceDirectedGraph = forwardRef(
645
301
  const x = width / 2 - centerX * scale;
646
302
  const y = height / 2 - centerY * scale;
647
303
  if (gRef.current && svgRef.current) {
648
- const svg = d32.select(svgRef.current);
649
- const newTransform = d32.zoomIdentity.translate(x, y).scale(scale);
650
- svg.transition().duration(300).call(d32.zoom().transform, newTransform);
304
+ const svg = d3.select(svgRef.current);
305
+ const newTransform = d3.zoomIdentity.translate(x, y).scale(scale);
306
+ svg.transition().duration(300).call(d3.zoom().transform, newTransform);
651
307
  setTransform(newTransform);
652
308
  }
653
309
  },
654
310
  getPinnedNodes: () => Array.from(pinnedNodes),
655
311
  setDragMode: (enabled) => {
656
312
  internalDragEnabledRef.current = enabled;
657
- }
313
+ },
314
+ setLayout: (newLayout) => {
315
+ handleLayoutChange(newLayout);
316
+ },
317
+ getLayout: () => layout
658
318
  }),
659
- [nodes, pinnedNodes, restart, width, height]
319
+ [nodes, pinnedNodes, restart, width, height, layout, handleLayoutChange]
660
320
  );
661
321
  useEffect(() => {
662
322
  try {
@@ -666,9 +326,9 @@ var ForceDirectedGraph = forwardRef(
666
326
  }, [manualLayout, onManualLayoutChange]);
667
327
  useEffect(() => {
668
328
  if (!enableZoom || !svgRef.current || !gRef.current) return;
669
- const svg = d32.select(svgRef.current);
670
- const g = d32.select(gRef.current);
671
- const zoom2 = d32.zoom().scaleExtent([0.1, 10]).on("zoom", (event) => {
329
+ const svg = d3.select(svgRef.current);
330
+ const g = d3.select(gRef.current);
331
+ const zoom2 = d3.zoom().scaleExtent([0.1, 10]).on("zoom", (event) => {
672
332
  g.attr("transform", event.transform);
673
333
  transformRef.current = event.transform;
674
334
  setTransform(event.transform);
@@ -681,19 +341,19 @@ var ForceDirectedGraph = forwardRef(
681
341
  useEffect(() => {
682
342
  if (!gRef.current) return;
683
343
  try {
684
- const g = d32.select(gRef.current);
344
+ const g = d3.select(gRef.current);
685
345
  g.selectAll("g.node").each(function() {
686
- const datum = d32.select(this).datum();
346
+ const datum = d3.select(this).datum();
687
347
  if (!datum) return;
688
- d32.select(this).attr("transform", `translate(${datum.x || 0},${datum.y || 0})`);
348
+ d3.select(this).attr("transform", `translate(${datum.x || 0},${datum.y || 0})`);
689
349
  });
690
350
  g.selectAll("line").each(function() {
691
- const l = d32.select(this).datum();
351
+ const l = d3.select(this).datum();
692
352
  if (!l) return;
693
353
  const s = typeof l.source === "object" ? l.source : nodes.find((n) => n.id === l.source) || l.source;
694
354
  const t = typeof l.target === "object" ? l.target : nodes.find((n) => n.id === l.target) || l.target;
695
355
  if (!s || !t) return;
696
- d32.select(this).attr("x1", s.x).attr("y1", s.y).attr("x2", t.x).attr("y2", t.y);
356
+ d3.select(this).attr("x1", s.x).attr("y1", s.y).attr("x2", t.x).attr("y2", t.y);
697
357
  });
698
358
  } catch (e) {
699
359
  }
@@ -754,8 +414,8 @@ var ForceDirectedGraph = forwardRef(
754
414
  }, [enableDrag]);
755
415
  useEffect(() => {
756
416
  if (!gRef.current || !enableDrag) return;
757
- const g = d32.select(gRef.current);
758
- const dragBehavior = d32.drag().on("start", function(event) {
417
+ const g = d3.select(gRef.current);
418
+ const dragBehavior = d3.drag().on("start", function(event) {
759
419
  try {
760
420
  const target = event.sourceEvent && event.sourceEvent.target || event.target;
761
421
  const grp = target.closest?.("g.node");