@aiready/components 0.14.0 → 0.14.2
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/README.md +6 -6
- package/dist/components/button.d.ts +1 -1
- package/dist/hooks/useForceSimulation.js +41 -14
- package/dist/hooks/useForceSimulation.js.map +1 -1
- package/dist/index.js +41 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/hooks/useForceSimulation.ts +96 -54
package/README.md
CHANGED
|
@@ -11,11 +11,11 @@ Unified shared components library (UI, charts, hooks, utilities) for AIReady.
|
|
|
11
11
|
│
|
|
12
12
|
▼
|
|
13
13
|
🎛️ @aiready/cli (orchestrator)
|
|
14
|
-
│ │ │ │ │ │ │ │ │
|
|
15
|
-
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
|
|
16
|
-
[PAT] [CTX] [CON] [AMP] [DEP] [DOC] [SIG] [AGT] [TST]
|
|
17
|
-
│ │ │ │ │ │ │ │ │
|
|
18
|
-
|
|
14
|
+
│ │ │ │ │ │ │ │ │ │
|
|
15
|
+
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
|
|
16
|
+
[PAT] [CTX] [CON] [AMP] [DEP] [DOC] [SIG] [AGT] [TST] [CTR]
|
|
17
|
+
│ │ │ │ │ │ │ │ │ │
|
|
18
|
+
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
|
|
19
19
|
│
|
|
20
20
|
▼
|
|
21
21
|
🏢 @aiready/core
|
|
@@ -25,7 +25,7 @@ Legend:
|
|
|
25
25
|
CON = consistency AMP = change-amplification
|
|
26
26
|
DEP = deps-health DOC = doc-drift
|
|
27
27
|
SIG = ai-signal-clarity AGT = agent-grounding
|
|
28
|
-
TST = testability
|
|
28
|
+
TST = testability CTR = contract-enforcement
|
|
29
29
|
CMP = @aiready/components ★ (support package — shared UI library, not a scorer)
|
|
30
30
|
★ = YOU ARE HERE
|
|
31
31
|
```
|
|
@@ -3,7 +3,7 @@ import * as React from 'react';
|
|
|
3
3
|
import { VariantProps } from 'class-variance-authority';
|
|
4
4
|
|
|
5
5
|
declare const buttonVariants: (props?: ({
|
|
6
|
-
variant?: "
|
|
6
|
+
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | "glow" | "glass" | "accent" | null | undefined;
|
|
7
7
|
size?: "default" | "sm" | "lg" | "icon" | "xs" | null | undefined;
|
|
8
8
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
9
9
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
|
@@ -48,9 +48,10 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
48
48
|
const stopTimeoutRef = useRef(null);
|
|
49
49
|
const nodesKey = initialNodes.map((n) => n.id).join("|");
|
|
50
50
|
const linksKey = (initialLinks || []).map((l) => {
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
|
|
51
|
+
const sourceId = typeof l.source === "string" ? l.source : l.source?.id;
|
|
52
|
+
const targetId = typeof l.target === "string" ? l.target : l.target?.id;
|
|
53
|
+
const linkType = l.type || "";
|
|
54
|
+
return `${sourceId}->${targetId}:${linkType}`;
|
|
54
55
|
}).join("|");
|
|
55
56
|
useEffect(() => {
|
|
56
57
|
const nodesCopy = initialNodes.map((node) => ({ ...node }));
|
|
@@ -65,6 +66,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
65
66
|
n.vy = (Math.random() - 0.5) * 2;
|
|
66
67
|
});
|
|
67
68
|
} catch (e) {
|
|
69
|
+
console.warn("Failed to seed node positions, falling back to random:", e);
|
|
68
70
|
seedRandomPositions(nodesCopy, width, height);
|
|
69
71
|
}
|
|
70
72
|
const simulation = d3.forceSimulation(
|
|
@@ -74,14 +76,25 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
74
76
|
const linkForce = d3.forceLink(
|
|
75
77
|
linksCopy
|
|
76
78
|
);
|
|
77
|
-
linkForce.id((d) => d.id).distance(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
linkForce.id((d) => d.id).distance((d) => {
|
|
80
|
+
const link = d;
|
|
81
|
+
return link && link.distance != null ? link.distance : linkDistance;
|
|
82
|
+
}).strength(linkStrength);
|
|
83
|
+
simulation.force(
|
|
84
|
+
"link",
|
|
85
|
+
linkForce
|
|
86
|
+
);
|
|
81
87
|
} catch (e) {
|
|
88
|
+
console.warn("Failed to configure link force, using fallback:", e);
|
|
82
89
|
try {
|
|
83
|
-
simulation.force(
|
|
84
|
-
|
|
90
|
+
simulation.force(
|
|
91
|
+
"link",
|
|
92
|
+
d3.forceLink(
|
|
93
|
+
linksCopy
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
} catch (fallbackError) {
|
|
97
|
+
console.warn("Fallback link force also failed:", fallbackError);
|
|
85
98
|
}
|
|
86
99
|
}
|
|
87
100
|
try {
|
|
@@ -94,7 +107,8 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
94
107
|
d3.forceCenter(width / 2, height / 2).strength(centerStrength)
|
|
95
108
|
);
|
|
96
109
|
const collide = d3.forceCollide().radius((d) => {
|
|
97
|
-
const
|
|
110
|
+
const node = d;
|
|
111
|
+
const nodeSize = node && node.size ? node.size : 10;
|
|
98
112
|
return nodeSize + collisionRadius;
|
|
99
113
|
}).strength(collisionStrength);
|
|
100
114
|
simulation.force("collision", collide);
|
|
@@ -112,20 +126,22 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
112
126
|
try {
|
|
113
127
|
simulation.alphaTarget(alphaTarget);
|
|
114
128
|
} catch (e) {
|
|
115
|
-
|
|
129
|
+
console.warn("Failed to set alpha target:", e);
|
|
116
130
|
}
|
|
117
131
|
try {
|
|
118
132
|
simulation.alpha(warmAlpha);
|
|
119
133
|
} catch (e) {
|
|
120
|
-
|
|
134
|
+
console.warn("Failed to set initial alpha:", e);
|
|
121
135
|
}
|
|
122
136
|
} catch (e) {
|
|
137
|
+
console.warn("Failed to configure simulation forces:", e);
|
|
123
138
|
}
|
|
124
139
|
simulationRef.current = simulation;
|
|
125
140
|
if (stopTimeoutRef.current != null) {
|
|
126
141
|
try {
|
|
127
142
|
globalThis.clearTimeout(stopTimeoutRef.current);
|
|
128
143
|
} catch (e) {
|
|
144
|
+
console.warn("Failed to clear simulation timeout:", e);
|
|
129
145
|
}
|
|
130
146
|
stopTimeoutRef.current = null;
|
|
131
147
|
}
|
|
@@ -138,6 +154,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
138
154
|
simulation.alpha(0);
|
|
139
155
|
simulation.stop();
|
|
140
156
|
} catch (e) {
|
|
157
|
+
console.warn("Failed to stop simulation:", e);
|
|
141
158
|
}
|
|
142
159
|
setIsRunning(false);
|
|
143
160
|
setNodes([...nodesCopy]);
|
|
@@ -151,6 +168,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
151
168
|
if (typeof onTick === "function")
|
|
152
169
|
onTick(nodesCopy, linksCopy, simulation);
|
|
153
170
|
} catch (e) {
|
|
171
|
+
console.warn("Tick callback error:", e);
|
|
154
172
|
}
|
|
155
173
|
try {
|
|
156
174
|
if (simulation.alpha() <= alphaMin) {
|
|
@@ -160,7 +178,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
160
178
|
}
|
|
161
179
|
simulation.stop();
|
|
162
180
|
} catch (e) {
|
|
163
|
-
|
|
181
|
+
console.warn("Failed to stop simulation:", e);
|
|
164
182
|
}
|
|
165
183
|
setAlpha(simulation.alpha());
|
|
166
184
|
setIsRunning(false);
|
|
@@ -169,6 +187,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
169
187
|
return;
|
|
170
188
|
}
|
|
171
189
|
} catch (e) {
|
|
190
|
+
console.warn("Error checking simulation alpha:", e);
|
|
172
191
|
}
|
|
173
192
|
const now = Date.now();
|
|
174
193
|
const shouldUpdate = now - lastUpdate >= tickThrottleMs;
|
|
@@ -191,11 +210,13 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
191
210
|
try {
|
|
192
211
|
simulation.on("tick", null);
|
|
193
212
|
} catch (e) {
|
|
213
|
+
console.warn("Failed to clear simulation tick handler:", e);
|
|
194
214
|
}
|
|
195
215
|
if (stopTimeoutRef.current != null) {
|
|
196
216
|
try {
|
|
197
217
|
globalThis.clearTimeout(stopTimeoutRef.current);
|
|
198
218
|
} catch (e) {
|
|
219
|
+
console.warn("Failed to clear timeout on cleanup:", e);
|
|
199
220
|
}
|
|
200
221
|
stopTimeoutRef.current = null;
|
|
201
222
|
}
|
|
@@ -203,6 +224,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
203
224
|
try {
|
|
204
225
|
(globalThis.cancelAnimationFrame || ((id) => clearTimeout(id)))(rafId);
|
|
205
226
|
} catch (e) {
|
|
227
|
+
console.warn("Failed to cancel animation frame:", e);
|
|
206
228
|
}
|
|
207
229
|
rafId = null;
|
|
208
230
|
}
|
|
@@ -239,6 +261,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
239
261
|
try {
|
|
240
262
|
globalThis.clearTimeout(stopTimeoutRef.current);
|
|
241
263
|
} catch (e) {
|
|
264
|
+
console.warn("Failed to clear simulation timeout:", e);
|
|
242
265
|
}
|
|
243
266
|
stopTimeoutRef.current = null;
|
|
244
267
|
}
|
|
@@ -248,6 +271,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
248
271
|
simulationRef.current?.alpha(0);
|
|
249
272
|
simulationRef.current?.stop();
|
|
250
273
|
} catch (e) {
|
|
274
|
+
console.warn("Failed to stop simulation:", e);
|
|
251
275
|
}
|
|
252
276
|
setIsRunning(false);
|
|
253
277
|
}, maxSimulationTimeMs);
|
|
@@ -272,7 +296,9 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
272
296
|
if (forcesEnabledRef.current === enabled) return;
|
|
273
297
|
forcesEnabledRef.current = enabled;
|
|
274
298
|
try {
|
|
275
|
-
const charge = sim.force(
|
|
299
|
+
const charge = sim.force(
|
|
300
|
+
"charge"
|
|
301
|
+
);
|
|
276
302
|
if (charge && typeof charge.strength === "function") {
|
|
277
303
|
charge.strength(enabled ? originalForcesRef.current.charge : 0);
|
|
278
304
|
}
|
|
@@ -281,6 +307,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
281
307
|
link.strength(enabled ? originalForcesRef.current.link : 0);
|
|
282
308
|
}
|
|
283
309
|
} catch (e) {
|
|
310
|
+
console.warn("Failed to toggle simulation forces:", e);
|
|
284
311
|
}
|
|
285
312
|
};
|
|
286
313
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hooks/simulation-helpers.ts","../../src/hooks/useForceSimulation.ts"],"names":["e"],"mappings":"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","file":"useForceSimulation.js","sourcesContent":["import { SimulationNode } from './simulation-types';\n\n/**\n * Stabilizes nodes by zeroing velocities and rounding positions.\n * Extracted to reduce duplicate patterns in useForceSimulation.\n */\nexport function stabilizeNodes(nodes: SimulationNode[]): void {\n nodes.forEach((n) => {\n (n as any).vx = 0;\n (n as any).vy = 0;\n if (typeof n.x === 'number') n.x = Number(n.x.toFixed(3));\n if (typeof n.y === 'number') n.y = Number(n.y.toFixed(3));\n });\n}\n\n/**\n * Seeds nodes with random positions within bounds.\n * Used as fallback when initial positioning fails.\n */\nexport function seedRandomPositions(\n nodes: SimulationNode[],\n width: number,\n height: number\n): void {\n nodes.forEach((n) => {\n n.x = Math.random() * width;\n n.y = Math.random() * height;\n (n as any).vx = (Math.random() - 0.5) * 10;\n (n as any).vy = (Math.random() - 0.5) * 10;\n });\n}\n","// Import helpers from separate module\nimport { stabilizeNodes, seedRandomPositions } from './simulation-helpers';\nimport type {\n SimulationNode,\n SimulationLink,\n ForceSimulationOptions,\n UseForceSimulationReturn,\n} from './simulation-types';\n\nimport { useEffect, useRef, useState } from 'react';\nimport * as d3 from 'd3';\n\n/**\n * Hook for managing d3-force simulations\n * Automatically handles simulation lifecycle, tick updates, and cleanup\n *\n * @param initialNodes - Initial nodes for the simulation\n * @param initialLinks - Initial links for the simulation\n * @param options - Configuration options for the force simulation\n * @returns Simulation state and control functions\n *\n * @example\n * ```tsx\n * function NetworkGraph() {\n * const nodes = [\n * { id: 'node1', name: 'Node 1' },\n * { id: 'node2', name: 'Node 2' },\n * { id: 'node3', name: 'Node 3' },\n * ];\n *\n * const links = [\n * { source: 'node1', target: 'node2' },\n * { source: 'node2', target: 'node3' },\n * ];\n *\n * const { nodes: simulatedNodes, links: simulatedLinks, restart } = useForceSimulation(\n * nodes,\n * links,\n * {\n * width: 800,\n * height: 600,\n * chargeStrength: -500,\n * linkDistance: 150,\n * }\n * );\n *\n * return (\n * <svg width={800} height={600}>\n * {simulatedLinks.map((link, i) => (\n * <line\n * key={i}\n * x1={(link.source as SimulationNode).x}\n * y1={(link.source as SimulationNode).y}\n * x2={(link.target as SimulationNode).x}\n * y2={(link.target as SimulationNode).y}\n * stroke=\"#999\"\n * />\n * ))}\n * {simulatedNodes.map((node) => (\n * <circle\n * key={node.id}\n * cx={node.x}\n * cy={node.y}\n * r={10}\n * fill=\"#69b3a2\"\n * />\n * ))}\n * </svg>\n * );\n * }\n * ```\n */\nexport function useForceSimulation(\n initialNodes: SimulationNode[],\n initialLinks: SimulationLink[],\n options: ForceSimulationOptions\n): UseForceSimulationReturn & { setForcesEnabled: (enabled: boolean) => void } {\n /**\n * Enable or disable the simulation forces (charge and link forces).\n * When disabled, nodes can still be dragged but won't be affected by forces.\n * @param enabled - When true, simulation forces are active; when false, forces are disabled\n */\n const {\n chargeStrength = -300,\n linkDistance = 100,\n linkStrength = 1,\n collisionStrength = 1,\n collisionRadius = 10,\n centerStrength = 0.1,\n width,\n height,\n alphaDecay = 0.0228,\n velocityDecay = 0.4,\n alphaTarget = 0,\n warmAlpha = 0.3,\n alphaMin = 0.01,\n onTick,\n // Optional throttle in milliseconds for tick updates (reduce React re-renders)\n // Lower values = smoother but more CPU; default ~30ms (~33fps)\n stabilizeOnStop = true,\n tickThrottleMs = 33,\n maxSimulationTimeMs = 3000,\n } = options;\n\n const [nodes, setNodes] = useState<SimulationNode[]>(initialNodes);\n const [links, setLinks] = useState<SimulationLink[]>(initialLinks);\n const [isRunning, setIsRunning] = useState(false);\n const [alpha, setAlpha] = useState(1);\n\n const simulationRef = useRef<d3.Simulation<\n SimulationNode,\n SimulationLink\n > | null>(null);\n const stopTimeoutRef = useRef<number | null>(null);\n\n // Create lightweight keys for nodes/links so we only recreate the simulation\n // when the actual identity/content of inputs change (not when parent passes\n // new array references on each render).\n const nodesKey = initialNodes.map((n) => n.id).join('|');\n const linksKey = (initialLinks || [])\n .map((l) => {\n const s = typeof l.source === 'string' ? l.source : (l.source as any)?.id;\n const t = typeof l.target === 'string' ? l.target : (l.target as any)?.id;\n return `${s}->${t}:${(l as any).type || ''}`;\n })\n .join('|');\n\n useEffect(() => {\n // Create a copy of nodes and links to avoid mutating the original data\n const nodesCopy = initialNodes.map((node) => ({ ...node }));\n const linksCopy = initialLinks.map((link) => ({ ...link }));\n\n // ALWAYS seed initial positions to ensure nodes don't stack at origin\n // This is critical for force-directed graphs to work properly\n try {\n // Always seed positions for all nodes when simulation is created\n // This ensures nodes start spread out even if they have coordinates\n nodesCopy.forEach((n, i) => {\n // Use deterministic but more widely spread positions based on index\n const angle = (i * 2 * Math.PI) / nodesCopy.length;\n // Larger seed radius to encourage an initial spread\n const radius = Math.min(width, height) * 0.45;\n n.x = width / 2 + radius * Math.cos(angle);\n n.y = height / 2 + radius * Math.sin(angle);\n // Add very small random velocity to avoid large initial motion\n (n as any).vx = (Math.random() - 0.5) * 2;\n (n as any).vy = (Math.random() - 0.5) * 2;\n });\n } catch (e) {\n void e;\n // If error, fall back to random positions\n seedRandomPositions(nodesCopy, width, height);\n }\n\n // Create the simulation\n const simulation = d3.forceSimulation(\n nodesCopy as any\n ) as unknown as d3.Simulation<SimulationNode, SimulationLink>;\n\n // Configure link force separately to avoid using generic type args on d3 helpers\n try {\n const linkForce = d3.forceLink(\n linksCopy as any\n ) as unknown as d3.ForceLink<SimulationNode, SimulationLink>;\n linkForce\n .id((d: any) => d.id)\n .distance((d: any) =>\n d && d.distance != null ? d.distance : linkDistance\n )\n .strength(linkStrength);\n simulation.force('link', linkForce as any);\n } catch (e) {\n void e;\n // fallback: attach a plain link force\n try {\n simulation.force('link', d3.forceLink(linksCopy as any) as any);\n } catch (e) {\n void e;\n }\n }\n try {\n simulation.force(\n 'charge',\n d3.forceManyBody().strength(chargeStrength) as any\n );\n simulation.force(\n 'center',\n d3.forceCenter(width / 2, height / 2).strength(centerStrength) as any\n );\n const collide = d3\n .forceCollide()\n .radius((d: any) => {\n const nodeSize = d && d.size ? d.size : 10;\n return nodeSize + collisionRadius;\n })\n .strength(collisionStrength as any) as any;\n simulation.force('collision', collide);\n simulation.force(\n 'x',\n d3\n .forceX(width / 2)\n .strength(Math.max(0.02, centerStrength * 0.5)) as any\n );\n simulation.force(\n 'y',\n d3\n .forceY(height / 2)\n .strength(Math.max(0.02, centerStrength * 0.5)) as any\n );\n simulation.alphaDecay(alphaDecay);\n simulation.velocityDecay(velocityDecay);\n simulation.alphaMin(alphaMin);\n try {\n simulation.alphaTarget(alphaTarget);\n } catch (e) {\n void e;\n }\n try {\n simulation.alpha(warmAlpha);\n } catch (e) {\n void e;\n }\n } catch (e) {\n void e;\n // ignore force configuration errors\n }\n\n simulationRef.current = simulation;\n\n // Force-stop timeout to ensure simulation doesn't run forever.\n if (stopTimeoutRef.current != null) {\n try {\n (globalThis.clearTimeout as any)(stopTimeoutRef.current);\n } catch (e) {\n void e;\n }\n stopTimeoutRef.current = null;\n }\n if (maxSimulationTimeMs && maxSimulationTimeMs > 0) {\n stopTimeoutRef.current = (globalThis.setTimeout as any)(() => {\n try {\n if (stabilizeOnStop) {\n stabilizeNodes(nodesCopy);\n }\n simulation.alpha(0);\n simulation.stop();\n } catch (e) {\n void e;\n }\n setIsRunning(false);\n setNodes([...nodesCopy]);\n setLinks([...linksCopy]);\n }, maxSimulationTimeMs) as unknown as number;\n }\n\n // Update state on each tick. Batch updates via requestAnimationFrame to avoid\n // excessive React re-renders which can cause visual flicker.\n let rafId: number | null = null;\n let lastUpdate = 0;\n const tickHandler = () => {\n try {\n if (typeof onTick === 'function')\n onTick(nodesCopy, linksCopy, simulation);\n } catch (e) {\n void e;\n }\n\n // If simulation alpha has cooled below the configured minimum, stop it to\n // ensure nodes don't drift indefinitely (acts as a hard-stop safeguard).\n try {\n if (simulation.alpha() <= (alphaMin as number)) {\n try {\n if (stabilizeOnStop) {\n stabilizeNodes(nodesCopy);\n }\n simulation.stop();\n } catch (e) {\n void e;\n }\n setAlpha(simulation.alpha());\n setIsRunning(false);\n setNodes([...nodesCopy]);\n setLinks([...linksCopy]);\n return;\n }\n } catch (e) {\n void e;\n }\n\n const now = Date.now();\n const shouldUpdate = now - lastUpdate >= (tickThrottleMs as number);\n if (rafId == null && shouldUpdate) {\n rafId = (\n globalThis.requestAnimationFrame ||\n ((cb: FrameRequestCallback) => setTimeout(cb, 16))\n )(() => {\n rafId = null;\n lastUpdate = Date.now();\n setNodes([...nodesCopy]);\n setLinks([...linksCopy]);\n setAlpha(simulation.alpha());\n setIsRunning(simulation.alpha() > simulation.alphaMin());\n }) as unknown as number;\n }\n };\n\n simulation.on('tick', tickHandler);\n\n simulation.on('end', () => {\n setIsRunning(false);\n });\n\n // Cleanup on unmount\n return () => {\n try {\n simulation.on('tick', null as any);\n } catch (e) {\n void e;\n }\n if (stopTimeoutRef.current != null) {\n try {\n (globalThis.clearTimeout as any)(stopTimeoutRef.current);\n } catch (e) {\n void e;\n }\n stopTimeoutRef.current = null;\n }\n if (rafId != null) {\n try {\n (\n globalThis.cancelAnimationFrame ||\n ((id: number) => clearTimeout(id))\n )(rafId);\n } catch (e) {\n void e;\n }\n rafId = null;\n }\n simulation.stop();\n };\n }, [\n nodesKey,\n linksKey,\n chargeStrength,\n linkDistance,\n linkStrength,\n collisionStrength,\n collisionRadius,\n centerStrength,\n width,\n height,\n alphaDecay,\n velocityDecay,\n alphaTarget,\n alphaMin,\n stabilizeOnStop,\n tickThrottleMs,\n maxSimulationTimeMs,\n ]);\n\n const restart = () => {\n if (simulationRef.current) {\n // Reheat the simulation to a modest alpha target rather than forcing\n // full heat; this matches the Observable pattern and helps stability.\n try {\n simulationRef.current.alphaTarget(warmAlpha).restart();\n } catch {\n simulationRef.current.restart();\n }\n setIsRunning(true);\n // Reset safety timeout when simulation is manually restarted\n if (stopTimeoutRef.current != null) {\n try {\n (globalThis.clearTimeout as any)(stopTimeoutRef.current);\n } catch (e) {\n void e;\n }\n stopTimeoutRef.current = null;\n }\n if (maxSimulationTimeMs && maxSimulationTimeMs > 0) {\n stopTimeoutRef.current = (globalThis.setTimeout as any)(() => {\n try {\n simulationRef.current?.alpha(0);\n simulationRef.current?.stop();\n } catch (e) {\n void e;\n }\n setIsRunning(false);\n }, maxSimulationTimeMs) as unknown as number;\n }\n }\n };\n\n const stop = () => {\n if (simulationRef.current) {\n simulationRef.current.stop();\n setIsRunning(false);\n }\n };\n\n const originalForcesRef = useRef({\n charge: chargeStrength,\n link: linkStrength,\n collision: collisionStrength,\n });\n const forcesEnabledRef = useRef(true);\n\n const setForcesEnabled = (enabled: boolean) => {\n const sim = simulationRef.current;\n if (!sim) return;\n // avoid repeated updates\n if (forcesEnabledRef.current === enabled) return;\n forcesEnabledRef.current = enabled;\n\n try {\n // Only toggle charge and link forces to avoid collapse; keep collision/centering\n const charge: any = sim.force('charge');\n if (charge && typeof charge.strength === 'function') {\n charge.strength(enabled ? originalForcesRef.current.charge : 0);\n }\n\n const link: any = sim.force('link');\n if (link && typeof link.strength === 'function') {\n link.strength(enabled ? originalForcesRef.current.link : 0);\n }\n } catch (e) {\n void e;\n }\n };\n\n return {\n nodes,\n links,\n restart,\n stop,\n isRunning,\n alpha,\n setForcesEnabled,\n };\n}\n\n/**\n * Hook for creating a draggable force simulation\n * Provides drag handlers that can be attached to node elements\n *\n * @param simulation - The d3 force simulation instance\n * @returns Drag behavior that can be applied to nodes\n *\n * @example\n * ```tsx\n * function DraggableNetworkGraph() {\n * const simulation = useRef<d3.Simulation<SimulationNode, SimulationLink>>();\n * const drag = useDrag(simulation.current);\n *\n * return (\n * <svg>\n * {nodes.map((node) => (\n * <circle\n * key={node.id}\n * {...drag}\n * cx={node.x}\n * cy={node.y}\n * r={10}\n * />\n * ))}\n * </svg>\n * );\n * }\n * ```\n */\nexport function useDrag(\n simulation: d3.Simulation<SimulationNode, any> | null | undefined\n) {\n const dragStarted = (event: any, node: SimulationNode) => {\n if (!simulation) return;\n if (!event.active) simulation.alphaTarget(0.3).restart();\n node.fx = node.x;\n node.fy = node.y;\n };\n\n const dragged = (event: any, node: SimulationNode) => {\n node.fx = event.x;\n node.fy = event.y;\n };\n\n const dragEnded = (event: any, node: SimulationNode) => {\n if (!simulation) return;\n if (!event.active) simulation.alphaTarget(0);\n node.fx = null;\n node.fy = null;\n };\n\n return {\n onDragStart: dragStarted,\n onDrag: dragged,\n onDragEnd: dragEnded,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/simulation-helpers.ts","../../src/hooks/useForceSimulation.ts"],"names":[],"mappings":"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","file":"useForceSimulation.js","sourcesContent":["import { SimulationNode } from './simulation-types';\n\n/**\n * Stabilizes nodes by zeroing velocities and rounding positions.\n * Extracted to reduce duplicate patterns in useForceSimulation.\n */\nexport function stabilizeNodes(nodes: SimulationNode[]): void {\n nodes.forEach((n) => {\n (n as any).vx = 0;\n (n as any).vy = 0;\n if (typeof n.x === 'number') n.x = Number(n.x.toFixed(3));\n if (typeof n.y === 'number') n.y = Number(n.y.toFixed(3));\n });\n}\n\n/**\n * Seeds nodes with random positions within bounds.\n * Used as fallback when initial positioning fails.\n */\nexport function seedRandomPositions(\n nodes: SimulationNode[],\n width: number,\n height: number\n): void {\n nodes.forEach((n) => {\n n.x = Math.random() * width;\n n.y = Math.random() * height;\n (n as any).vx = (Math.random() - 0.5) * 10;\n (n as any).vy = (Math.random() - 0.5) * 10;\n });\n}\n","// Import helpers from separate module\nimport { stabilizeNodes, seedRandomPositions } from './simulation-helpers';\nimport type {\n SimulationNode,\n SimulationLink,\n ForceSimulationOptions,\n UseForceSimulationReturn,\n} from './simulation-types';\n\nimport { useEffect, useRef, useState } from 'react';\nimport * as d3 from 'd3';\n\n/**\n * Hook for managing d3-force simulations\n * Automatically handles simulation lifecycle, tick updates, and cleanup\n *\n * @param initialNodes - Initial nodes for the simulation\n * @param initialLinks - Initial links for the simulation\n * @param options - Configuration options for the force simulation\n * @returns Simulation state and control functions\n *\n * @example\n * ```tsx\n * function NetworkGraph() {\n * const nodes = [\n * { id: 'node1', name: 'Node 1' },\n * { id: 'node2', name: 'Node 2' },\n * { id: 'node3', name: 'Node 3' },\n * ];\n *\n * const links = [\n * { source: 'node1', target: 'node2' },\n * { source: 'node2', target: 'node3' },\n * ];\n *\n * const { nodes: simulatedNodes, links: simulatedLinks, restart } = useForceSimulation(\n * nodes,\n * links,\n * {\n * width: 800,\n * height: 600,\n * chargeStrength: -500,\n * linkDistance: 150,\n * }\n * );\n *\n * return (\n * <svg width={800} height={600}>\n * {simulatedLinks.map((link, i) => (\n * <line\n * key={i}\n * x1={(link.source as SimulationNode).x}\n * y1={(link.source as SimulationNode).y}\n * x2={(link.target as SimulationNode).x}\n * y2={(link.target as SimulationNode).y}\n * stroke=\"#999\"\n * />\n * ))}\n * {simulatedNodes.map((node) => (\n * <circle\n * key={node.id}\n * cx={node.x}\n * cy={node.y}\n * r={10}\n * fill=\"#69b3a2\"\n * />\n * ))}\n * </svg>\n * );\n * }\n * ```\n */\nexport function useForceSimulation(\n initialNodes: SimulationNode[],\n initialLinks: SimulationLink[],\n options: ForceSimulationOptions\n): UseForceSimulationReturn & { setForcesEnabled: (enabled: boolean) => void } {\n /**\n * Enable or disable the simulation forces (charge and link forces).\n * When disabled, nodes can still be dragged but won't be affected by forces.\n * @param enabled - When true, simulation forces are active; when false, forces are disabled\n */\n const {\n chargeStrength = -300,\n linkDistance = 100,\n linkStrength = 1,\n collisionStrength = 1,\n collisionRadius = 10,\n centerStrength = 0.1,\n width,\n height,\n alphaDecay = 0.0228,\n velocityDecay = 0.4,\n alphaTarget = 0,\n warmAlpha = 0.3,\n alphaMin = 0.01,\n onTick,\n // Optional throttle in milliseconds for tick updates (reduce React re-renders)\n // Lower values = smoother but more CPU; default ~30ms (~33fps)\n stabilizeOnStop = true,\n tickThrottleMs = 33,\n maxSimulationTimeMs = 3000,\n } = options;\n\n const [nodes, setNodes] = useState<SimulationNode[]>(initialNodes);\n const [links, setLinks] = useState<SimulationLink[]>(initialLinks);\n const [isRunning, setIsRunning] = useState(false);\n const [alpha, setAlpha] = useState(1);\n\n const simulationRef = useRef<d3.Simulation<\n SimulationNode,\n SimulationLink\n > | null>(null);\n const stopTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Create lightweight keys for nodes/links so we only recreate the simulation\n // when the actual identity/content of inputs change (not when parent passes\n // new array references on each render).\n const nodesKey = initialNodes.map((n) => n.id).join('|');\n const linksKey = (initialLinks || [])\n .map((l) => {\n const sourceId =\n typeof l.source === 'string'\n ? l.source\n : (l.source as SimulationNode)?.id;\n const targetId =\n typeof l.target === 'string'\n ? l.target\n : (l.target as SimulationNode)?.id;\n const linkType = (l as SimulationLink & { type?: string }).type || '';\n return `${sourceId}->${targetId}:${linkType}`;\n })\n .join('|');\n\n useEffect(() => {\n // Create a copy of nodes and links to avoid mutating the original data\n const nodesCopy = initialNodes.map((node) => ({ ...node }));\n const linksCopy = initialLinks.map((link) => ({ ...link }));\n\n // ALWAYS seed initial positions to ensure nodes don't stack at origin\n // This is critical for force-directed graphs to work properly\n try {\n // Always seed positions for all nodes when simulation is created\n // This ensures nodes start spread out even if they have coordinates\n nodesCopy.forEach((n, i) => {\n // Use deterministic but more widely spread positions based on index\n const angle = (i * 2 * Math.PI) / nodesCopy.length;\n // Larger seed radius to encourage an initial spread\n const radius = Math.min(width, height) * 0.45;\n n.x = width / 2 + radius * Math.cos(angle);\n n.y = height / 2 + radius * Math.sin(angle);\n // Add very small random velocity to avoid large initial motion\n n.vx = (Math.random() - 0.5) * 2;\n n.vy = (Math.random() - 0.5) * 2;\n });\n } catch (e) {\n console.warn('Failed to seed node positions, falling back to random:', e);\n // If error, fall back to random positions\n seedRandomPositions(nodesCopy, width, height);\n }\n\n // Create the simulation\n const simulation = d3.forceSimulation(\n nodesCopy as SimulationNode[]\n ) as d3.Simulation<SimulationNode, SimulationLink>;\n\n // Configure link force separately to avoid using generic type args on d3 helpers\n try {\n const linkForce = d3.forceLink(\n linksCopy as d3.SimulationLinkDatum<SimulationNode>[]\n ) as d3.ForceLink<SimulationNode, d3.SimulationLinkDatum<SimulationNode>>;\n linkForce\n .id((d: SimulationNode) => d.id)\n .distance((d: d3.SimulationLinkDatum<SimulationNode>) => {\n const link = d as SimulationLink & { distance?: number };\n return link && link.distance != null ? link.distance : linkDistance;\n })\n .strength(linkStrength);\n simulation.force(\n 'link',\n linkForce as d3.Force<\n SimulationNode,\n d3.SimulationLinkDatum<SimulationNode>\n >\n );\n } catch (e) {\n console.warn('Failed to configure link force, using fallback:', e);\n // fallback: attach a plain link force\n try {\n simulation.force(\n 'link',\n d3.forceLink(\n linksCopy as d3.SimulationLinkDatum<SimulationNode>[]\n ) as d3.Force<SimulationNode, d3.SimulationLinkDatum<SimulationNode>>\n );\n } catch (fallbackError) {\n console.warn('Fallback link force also failed:', fallbackError);\n }\n }\n try {\n simulation.force(\n 'charge',\n d3.forceManyBody().strength(chargeStrength) as d3.Force<\n SimulationNode,\n d3.SimulationLinkDatum<SimulationNode>\n >\n );\n simulation.force(\n 'center',\n d3\n .forceCenter(width / 2, height / 2)\n .strength(centerStrength) as d3.Force<\n SimulationNode,\n d3.SimulationLinkDatum<SimulationNode>\n >\n );\n const collide = d3\n .forceCollide()\n .radius((d: d3.SimulationNodeDatum) => {\n const node = d as SimulationNode;\n const nodeSize = node && node.size ? (node.size as number) : 10;\n return nodeSize + collisionRadius;\n })\n .strength(collisionStrength) as d3.Force<\n SimulationNode,\n d3.SimulationLinkDatum<SimulationNode>\n >;\n simulation.force('collision', collide);\n simulation.force(\n 'x',\n d3\n .forceX(width / 2)\n .strength(Math.max(0.02, centerStrength * 0.5)) as d3.Force<\n SimulationNode,\n d3.SimulationLinkDatum<SimulationNode>\n >\n );\n simulation.force(\n 'y',\n d3\n .forceY(height / 2)\n .strength(Math.max(0.02, centerStrength * 0.5)) as d3.Force<\n SimulationNode,\n d3.SimulationLinkDatum<SimulationNode>\n >\n );\n simulation.alphaDecay(alphaDecay);\n simulation.velocityDecay(velocityDecay);\n simulation.alphaMin(alphaMin);\n try {\n simulation.alphaTarget(alphaTarget);\n } catch (e) {\n console.warn('Failed to set alpha target:', e);\n }\n try {\n simulation.alpha(warmAlpha);\n } catch (e) {\n console.warn('Failed to set initial alpha:', e);\n }\n } catch (e) {\n console.warn('Failed to configure simulation forces:', e);\n // ignore force configuration errors\n }\n\n simulationRef.current = simulation;\n\n // Force-stop timeout to ensure simulation doesn't run forever.\n if (stopTimeoutRef.current != null) {\n try {\n globalThis.clearTimeout(stopTimeoutRef.current);\n } catch (e) {\n console.warn('Failed to clear simulation timeout:', e);\n }\n stopTimeoutRef.current = null;\n }\n if (maxSimulationTimeMs && maxSimulationTimeMs > 0) {\n stopTimeoutRef.current = globalThis.setTimeout(() => {\n try {\n if (stabilizeOnStop) {\n stabilizeNodes(nodesCopy);\n }\n simulation.alpha(0);\n simulation.stop();\n } catch (e) {\n console.warn('Failed to stop simulation:', e);\n }\n setIsRunning(false);\n setNodes([...nodesCopy]);\n setLinks([...linksCopy]);\n }, maxSimulationTimeMs);\n }\n\n // Update state on each tick. Batch updates via requestAnimationFrame to avoid\n // excessive React re-renders which can cause visual flicker.\n let rafId: number | null = null;\n let lastUpdate = 0;\n const tickHandler = () => {\n try {\n if (typeof onTick === 'function')\n onTick(nodesCopy, linksCopy, simulation);\n } catch (e) {\n console.warn('Tick callback error:', e);\n }\n\n // If simulation alpha has cooled below the configured minimum, stop it to\n // ensure nodes don't drift indefinitely (acts as a hard-stop safeguard).\n try {\n if (simulation.alpha() <= alphaMin) {\n try {\n if (stabilizeOnStop) {\n stabilizeNodes(nodesCopy);\n }\n simulation.stop();\n } catch (e) {\n console.warn('Failed to stop simulation:', e);\n }\n setAlpha(simulation.alpha());\n setIsRunning(false);\n setNodes([...nodesCopy]);\n setLinks([...linksCopy]);\n return;\n }\n } catch (e) {\n console.warn('Error checking simulation alpha:', e);\n }\n\n const now = Date.now();\n const shouldUpdate = now - lastUpdate >= tickThrottleMs;\n if (rafId == null && shouldUpdate) {\n rafId = (\n globalThis.requestAnimationFrame ||\n ((cb: FrameRequestCallback) => setTimeout(cb, 16))\n )(() => {\n rafId = null;\n lastUpdate = Date.now();\n setNodes([...nodesCopy]);\n setLinks([...linksCopy]);\n setAlpha(simulation.alpha());\n setIsRunning(simulation.alpha() > simulation.alphaMin());\n });\n }\n };\n\n simulation.on('tick', tickHandler);\n\n simulation.on('end', () => {\n setIsRunning(false);\n });\n\n // Cleanup on unmount\n return () => {\n try {\n simulation.on('tick', null);\n } catch (e) {\n console.warn('Failed to clear simulation tick handler:', e);\n }\n if (stopTimeoutRef.current != null) {\n try {\n globalThis.clearTimeout(stopTimeoutRef.current);\n } catch (e) {\n console.warn('Failed to clear timeout on cleanup:', e);\n }\n stopTimeoutRef.current = null;\n }\n if (rafId != null) {\n try {\n (\n globalThis.cancelAnimationFrame ||\n ((id: number) => clearTimeout(id))\n )(rafId);\n } catch (e) {\n console.warn('Failed to cancel animation frame:', e);\n }\n rafId = null;\n }\n simulation.stop();\n };\n }, [\n nodesKey,\n linksKey,\n chargeStrength,\n linkDistance,\n linkStrength,\n collisionStrength,\n collisionRadius,\n centerStrength,\n width,\n height,\n alphaDecay,\n velocityDecay,\n alphaTarget,\n alphaMin,\n stabilizeOnStop,\n tickThrottleMs,\n maxSimulationTimeMs,\n ]);\n\n const restart = () => {\n if (simulationRef.current) {\n // Reheat the simulation to a modest alpha target rather than forcing\n // full heat; this matches the Observable pattern and helps stability.\n try {\n simulationRef.current.alphaTarget(warmAlpha).restart();\n } catch {\n simulationRef.current.restart();\n }\n setIsRunning(true);\n // Reset safety timeout when simulation is manually restarted\n if (stopTimeoutRef.current != null) {\n try {\n globalThis.clearTimeout(stopTimeoutRef.current);\n } catch (e) {\n console.warn('Failed to clear simulation timeout:', e);\n }\n stopTimeoutRef.current = null;\n }\n if (maxSimulationTimeMs && maxSimulationTimeMs > 0) {\n stopTimeoutRef.current = globalThis.setTimeout(() => {\n try {\n simulationRef.current?.alpha(0);\n simulationRef.current?.stop();\n } catch (e) {\n console.warn('Failed to stop simulation:', e);\n }\n setIsRunning(false);\n }, maxSimulationTimeMs);\n }\n }\n };\n\n const stop = () => {\n if (simulationRef.current) {\n simulationRef.current.stop();\n setIsRunning(false);\n }\n };\n\n const originalForcesRef = useRef({\n charge: chargeStrength,\n link: linkStrength,\n collision: collisionStrength,\n });\n const forcesEnabledRef = useRef(true);\n\n const setForcesEnabled = (enabled: boolean) => {\n const sim = simulationRef.current;\n if (!sim) return;\n // avoid repeated updates\n if (forcesEnabledRef.current === enabled) return;\n forcesEnabledRef.current = enabled;\n\n try {\n // Only toggle charge and link forces to avoid collapse; keep collision/centering\n const charge = sim.force(\n 'charge'\n ) as d3.ForceManyBody<SimulationNode> | null;\n if (charge && typeof charge.strength === 'function') {\n charge.strength(enabled ? originalForcesRef.current.charge : 0);\n }\n\n const link = sim.force('link') as d3.ForceLink<\n SimulationNode,\n d3.SimulationLinkDatum<SimulationNode>\n > | null;\n if (link && typeof link.strength === 'function') {\n link.strength(enabled ? originalForcesRef.current.link : 0);\n }\n } catch (e) {\n console.warn('Failed to toggle simulation forces:', e);\n }\n };\n\n return {\n nodes,\n links,\n restart,\n stop,\n isRunning,\n alpha,\n setForcesEnabled,\n };\n}\n\n/**\n * Hook for creating a draggable force simulation\n * Provides drag handlers that can be attached to node elements\n *\n * @param simulation - The d3 force simulation instance\n * @returns Drag behavior that can be applied to nodes\n *\n * @example\n * ```tsx\n * function DraggableNetworkGraph() {\n * const simulation = useRef<d3.Simulation<SimulationNode, SimulationLink>>();\n * const drag = useDrag(simulation.current);\n *\n * return (\n * <svg>\n * {nodes.map((node) => (\n * <circle\n * key={node.id}\n * {...drag}\n * cx={node.x}\n * cy={node.y}\n * r={10}\n * />\n * ))}\n * </svg>\n * );\n * }\n * ```\n */\nexport function useDrag(\n simulation: d3.Simulation<SimulationNode, any> | null | undefined\n) {\n const dragStarted = (event: any, node: SimulationNode) => {\n if (!simulation) return;\n if (!event.active) simulation.alphaTarget(0.3).restart();\n node.fx = node.x;\n node.fy = node.y;\n };\n\n const dragged = (event: any, node: SimulationNode) => {\n node.fx = event.x;\n node.fy = event.y;\n };\n\n const dragEnded = (event: any, node: SimulationNode) => {\n if (!simulation) return;\n if (!event.active) simulation.alphaTarget(0);\n node.fx = null;\n node.fy = null;\n };\n\n return {\n onDragStart: dragStarted,\n onDrag: dragged,\n onDragEnd: dragEnded,\n };\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -2098,9 +2098,10 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
2098
2098
|
const stopTimeoutRef = useRef(null);
|
|
2099
2099
|
const nodesKey = initialNodes.map((n) => n.id).join("|");
|
|
2100
2100
|
const linksKey = (initialLinks || []).map((l) => {
|
|
2101
|
-
const
|
|
2102
|
-
const
|
|
2103
|
-
|
|
2101
|
+
const sourceId = typeof l.source === "string" ? l.source : l.source?.id;
|
|
2102
|
+
const targetId = typeof l.target === "string" ? l.target : l.target?.id;
|
|
2103
|
+
const linkType = l.type || "";
|
|
2104
|
+
return `${sourceId}->${targetId}:${linkType}`;
|
|
2104
2105
|
}).join("|");
|
|
2105
2106
|
useEffect(() => {
|
|
2106
2107
|
const nodesCopy = initialNodes.map((node) => ({ ...node }));
|
|
@@ -2115,6 +2116,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
2115
2116
|
n.vy = (Math.random() - 0.5) * 2;
|
|
2116
2117
|
});
|
|
2117
2118
|
} catch (e) {
|
|
2119
|
+
console.warn("Failed to seed node positions, falling back to random:", e);
|
|
2118
2120
|
seedRandomPositions(nodesCopy, width, height);
|
|
2119
2121
|
}
|
|
2120
2122
|
const simulation = d32.forceSimulation(
|
|
@@ -2124,14 +2126,25 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
2124
2126
|
const linkForce = d32.forceLink(
|
|
2125
2127
|
linksCopy
|
|
2126
2128
|
);
|
|
2127
|
-
linkForce.id((d) => d.id).distance(
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2129
|
+
linkForce.id((d) => d.id).distance((d) => {
|
|
2130
|
+
const link = d;
|
|
2131
|
+
return link && link.distance != null ? link.distance : linkDistance;
|
|
2132
|
+
}).strength(linkStrength);
|
|
2133
|
+
simulation.force(
|
|
2134
|
+
"link",
|
|
2135
|
+
linkForce
|
|
2136
|
+
);
|
|
2131
2137
|
} catch (e) {
|
|
2138
|
+
console.warn("Failed to configure link force, using fallback:", e);
|
|
2132
2139
|
try {
|
|
2133
|
-
simulation.force(
|
|
2134
|
-
|
|
2140
|
+
simulation.force(
|
|
2141
|
+
"link",
|
|
2142
|
+
d32.forceLink(
|
|
2143
|
+
linksCopy
|
|
2144
|
+
)
|
|
2145
|
+
);
|
|
2146
|
+
} catch (fallbackError) {
|
|
2147
|
+
console.warn("Fallback link force also failed:", fallbackError);
|
|
2135
2148
|
}
|
|
2136
2149
|
}
|
|
2137
2150
|
try {
|
|
@@ -2144,7 +2157,8 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
2144
2157
|
d32.forceCenter(width / 2, height / 2).strength(centerStrength)
|
|
2145
2158
|
);
|
|
2146
2159
|
const collide = d32.forceCollide().radius((d) => {
|
|
2147
|
-
const
|
|
2160
|
+
const node = d;
|
|
2161
|
+
const nodeSize = node && node.size ? node.size : 10;
|
|
2148
2162
|
return nodeSize + collisionRadius;
|
|
2149
2163
|
}).strength(collisionStrength);
|
|
2150
2164
|
simulation.force("collision", collide);
|
|
@@ -2162,20 +2176,22 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
2162
2176
|
try {
|
|
2163
2177
|
simulation.alphaTarget(alphaTarget);
|
|
2164
2178
|
} catch (e) {
|
|
2165
|
-
|
|
2179
|
+
console.warn("Failed to set alpha target:", e);
|
|
2166
2180
|
}
|
|
2167
2181
|
try {
|
|
2168
2182
|
simulation.alpha(warmAlpha);
|
|
2169
2183
|
} catch (e) {
|
|
2170
|
-
|
|
2184
|
+
console.warn("Failed to set initial alpha:", e);
|
|
2171
2185
|
}
|
|
2172
2186
|
} catch (e) {
|
|
2187
|
+
console.warn("Failed to configure simulation forces:", e);
|
|
2173
2188
|
}
|
|
2174
2189
|
simulationRef.current = simulation;
|
|
2175
2190
|
if (stopTimeoutRef.current != null) {
|
|
2176
2191
|
try {
|
|
2177
2192
|
globalThis.clearTimeout(stopTimeoutRef.current);
|
|
2178
2193
|
} catch (e) {
|
|
2194
|
+
console.warn("Failed to clear simulation timeout:", e);
|
|
2179
2195
|
}
|
|
2180
2196
|
stopTimeoutRef.current = null;
|
|
2181
2197
|
}
|
|
@@ -2188,6 +2204,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
2188
2204
|
simulation.alpha(0);
|
|
2189
2205
|
simulation.stop();
|
|
2190
2206
|
} catch (e) {
|
|
2207
|
+
console.warn("Failed to stop simulation:", e);
|
|
2191
2208
|
}
|
|
2192
2209
|
setIsRunning(false);
|
|
2193
2210
|
setNodes([...nodesCopy]);
|
|
@@ -2201,6 +2218,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
2201
2218
|
if (typeof onTick === "function")
|
|
2202
2219
|
onTick(nodesCopy, linksCopy, simulation);
|
|
2203
2220
|
} catch (e) {
|
|
2221
|
+
console.warn("Tick callback error:", e);
|
|
2204
2222
|
}
|
|
2205
2223
|
try {
|
|
2206
2224
|
if (simulation.alpha() <= alphaMin) {
|
|
@@ -2210,7 +2228,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
2210
2228
|
}
|
|
2211
2229
|
simulation.stop();
|
|
2212
2230
|
} catch (e) {
|
|
2213
|
-
|
|
2231
|
+
console.warn("Failed to stop simulation:", e);
|
|
2214
2232
|
}
|
|
2215
2233
|
setAlpha(simulation.alpha());
|
|
2216
2234
|
setIsRunning(false);
|
|
@@ -2219,6 +2237,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
2219
2237
|
return;
|
|
2220
2238
|
}
|
|
2221
2239
|
} catch (e) {
|
|
2240
|
+
console.warn("Error checking simulation alpha:", e);
|
|
2222
2241
|
}
|
|
2223
2242
|
const now = Date.now();
|
|
2224
2243
|
const shouldUpdate = now - lastUpdate >= tickThrottleMs;
|
|
@@ -2241,11 +2260,13 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
2241
2260
|
try {
|
|
2242
2261
|
simulation.on("tick", null);
|
|
2243
2262
|
} catch (e) {
|
|
2263
|
+
console.warn("Failed to clear simulation tick handler:", e);
|
|
2244
2264
|
}
|
|
2245
2265
|
if (stopTimeoutRef.current != null) {
|
|
2246
2266
|
try {
|
|
2247
2267
|
globalThis.clearTimeout(stopTimeoutRef.current);
|
|
2248
2268
|
} catch (e) {
|
|
2269
|
+
console.warn("Failed to clear timeout on cleanup:", e);
|
|
2249
2270
|
}
|
|
2250
2271
|
stopTimeoutRef.current = null;
|
|
2251
2272
|
}
|
|
@@ -2253,6 +2274,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
2253
2274
|
try {
|
|
2254
2275
|
(globalThis.cancelAnimationFrame || ((id) => clearTimeout(id)))(rafId);
|
|
2255
2276
|
} catch (e) {
|
|
2277
|
+
console.warn("Failed to cancel animation frame:", e);
|
|
2256
2278
|
}
|
|
2257
2279
|
rafId = null;
|
|
2258
2280
|
}
|
|
@@ -2289,6 +2311,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
2289
2311
|
try {
|
|
2290
2312
|
globalThis.clearTimeout(stopTimeoutRef.current);
|
|
2291
2313
|
} catch (e) {
|
|
2314
|
+
console.warn("Failed to clear simulation timeout:", e);
|
|
2292
2315
|
}
|
|
2293
2316
|
stopTimeoutRef.current = null;
|
|
2294
2317
|
}
|
|
@@ -2298,6 +2321,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
2298
2321
|
simulationRef.current?.alpha(0);
|
|
2299
2322
|
simulationRef.current?.stop();
|
|
2300
2323
|
} catch (e) {
|
|
2324
|
+
console.warn("Failed to stop simulation:", e);
|
|
2301
2325
|
}
|
|
2302
2326
|
setIsRunning(false);
|
|
2303
2327
|
}, maxSimulationTimeMs);
|
|
@@ -2322,7 +2346,9 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
2322
2346
|
if (forcesEnabledRef.current === enabled) return;
|
|
2323
2347
|
forcesEnabledRef.current = enabled;
|
|
2324
2348
|
try {
|
|
2325
|
-
const charge = sim.force(
|
|
2349
|
+
const charge = sim.force(
|
|
2350
|
+
"charge"
|
|
2351
|
+
);
|
|
2326
2352
|
if (charge && typeof charge.strength === "function") {
|
|
2327
2353
|
charge.strength(enabled ? originalForcesRef.current.charge : 0);
|
|
2328
2354
|
}
|
|
@@ -2331,6 +2357,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
|
|
|
2331
2357
|
link.strength(enabled ? originalForcesRef.current.link : 0);
|
|
2332
2358
|
}
|
|
2333
2359
|
} catch (e) {
|
|
2360
|
+
console.warn("Failed to toggle simulation forces:", e);
|
|
2334
2361
|
}
|
|
2335
2362
|
};
|
|
2336
2363
|
return {
|