polly-graph 0.2.3 → 0.2.4
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/index.cjs +96 -42
- package/dist/index.css +2 -0
- package/dist/index.d.cts +18 -19
- package/dist/index.d.ts +18 -19
- package/dist/index.js +96 -42
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2060,9 +2060,10 @@ var PhysicsManager = class {
|
|
|
2060
2060
|
config;
|
|
2061
2061
|
simulationStartTime;
|
|
2062
2062
|
simulationEndTime;
|
|
2063
|
-
cooldownTimer;
|
|
2064
2063
|
hasInitialAutoFitCompleted = false;
|
|
2065
2064
|
timerManager;
|
|
2065
|
+
isVisibilityListenerAttached = false;
|
|
2066
|
+
nodeMap = /* @__PURE__ */ new Map();
|
|
2066
2067
|
constructor(timerManager) {
|
|
2067
2068
|
this.timerManager = timerManager;
|
|
2068
2069
|
}
|
|
@@ -2076,34 +2077,49 @@ var PhysicsManager = class {
|
|
|
2076
2077
|
if (typeof config.onTick !== "function") {
|
|
2077
2078
|
throw new ValidationError("onTick callback is required and must be a function");
|
|
2078
2079
|
}
|
|
2080
|
+
if (!this.isVisibilityListenerAttached) {
|
|
2081
|
+
document.addEventListener("visibilitychange", this.handleVisibilityChange);
|
|
2082
|
+
this.isVisibilityListenerAttached = true;
|
|
2083
|
+
}
|
|
2079
2084
|
this.config = config;
|
|
2080
2085
|
this.simulationStartTime = performance.now();
|
|
2081
2086
|
const nodeCount = config.nodes.length;
|
|
2082
|
-
const graphArea = config.width * config.height;
|
|
2087
|
+
const graphArea = Math.max(config.width * config.height, 1);
|
|
2083
2088
|
const nodeDensity = nodeCount / (graphArea / 1e5);
|
|
2084
2089
|
const densityFactor = Math.min(nodeDensity, 2);
|
|
2085
2090
|
const baseVelocityDecay = 0.4;
|
|
2086
2091
|
const adaptiveVelocityDecay = Math.min(baseVelocityDecay + densityFactor * 0.2, 0.8);
|
|
2087
2092
|
const baseAlphaDecay = 0.02;
|
|
2088
2093
|
const adaptiveAlphaDecay = Math.min(baseAlphaDecay + densityFactor * 0.01, 0.05);
|
|
2094
|
+
if (this.simulation) {
|
|
2095
|
+
this.simulation.stop();
|
|
2096
|
+
}
|
|
2097
|
+
this.buildNodeIndex();
|
|
2098
|
+
const linkDistance = nodeCount > 1e4 ? 220 : nodeCount > 5e3 ? 190 : nodeCount > 2e3 ? 170 : 150;
|
|
2099
|
+
const chargeStrength = nodeCount > 1e4 ? -350 : nodeCount > 5e3 ? -400 : nodeCount > 2e3 ? -450 : -500;
|
|
2100
|
+
const collisionRadius = nodeCount > 1e4 ? 1 : nodeCount > 5e3 ? 2 : 2;
|
|
2101
|
+
const collisionIterations = nodeCount > 1e4 ? 1 : nodeCount > 5e3 ? 1 : 2;
|
|
2102
|
+
const centerStrength = nodeCount > 5e3 ? 0.15 : 0.5;
|
|
2103
|
+
const linkStrength = nodeCount > 1e4 ? 0.15 : nodeCount > 5e3 ? 0.25 : 0.4;
|
|
2089
2104
|
this.simulation = simulation_default(config.nodes).force(
|
|
2090
2105
|
"link",
|
|
2091
|
-
link_default(config.links).id((d) => d.id).distance(
|
|
2092
|
-
// Much weaker link strength to allow repulsion to work
|
|
2106
|
+
link_default(config.links).id((d) => d.id).distance(linkDistance).strength(linkStrength).iterations(1)
|
|
2093
2107
|
).force(
|
|
2094
2108
|
"charge",
|
|
2095
|
-
manyBody_default().strength(
|
|
2096
|
-
// Adaptive repulsion strength
|
|
2097
|
-
// .distanceMin(1) // Minimum distance for repulsion
|
|
2098
|
-
// .distanceMax(Math.max(300, 600 - densityFactor * 100)) // Reduce max distance for dense graphs
|
|
2109
|
+
manyBody_default().strength(chargeStrength).theta(nodeCount > 5e3 ? 1.2 : 0.9).distanceMax(nodeCount > 5e3 ? 500 : 1e3)
|
|
2099
2110
|
).force(
|
|
2100
2111
|
"collision",
|
|
2101
|
-
collide_default().radius((node) =>
|
|
2112
|
+
collide_default().radius((node) => (node.style?.radius ?? 20) + collisionRadius).strength(1).iterations(collisionIterations)
|
|
2102
2113
|
).force(
|
|
2103
2114
|
"center",
|
|
2104
|
-
center_default(0, 0).strength(
|
|
2105
|
-
|
|
2106
|
-
|
|
2115
|
+
center_default(0, 0).strength(centerStrength)
|
|
2116
|
+
).velocityDecay(
|
|
2117
|
+
nodeCount > 5e3 ? 0.65 : adaptiveVelocityDecay
|
|
2118
|
+
).alphaDecay(
|
|
2119
|
+
nodeCount > 5e3 ? 0.05 : adaptiveAlphaDecay
|
|
2120
|
+
).alphaMin(
|
|
2121
|
+
nodeCount > 5e3 ? 0.05 : 1e-3
|
|
2122
|
+
).on("tick", config.onTick).on("end", () => this.handleSimulationEnd());
|
|
2107
2123
|
if (config.cooldownTime) {
|
|
2108
2124
|
this.setupCooldownTimer(config.cooldownTime);
|
|
2109
2125
|
}
|
|
@@ -2156,6 +2172,34 @@ var PhysicsManager = class {
|
|
|
2156
2172
|
ErrorHandler.logError(error, { cooldownTime });
|
|
2157
2173
|
}
|
|
2158
2174
|
}
|
|
2175
|
+
/**
|
|
2176
|
+
* Build node index for O(1) lookups (Step 3 optimization)
|
|
2177
|
+
*/
|
|
2178
|
+
buildNodeIndex() {
|
|
2179
|
+
if (!this.config) return;
|
|
2180
|
+
try {
|
|
2181
|
+
this.nodeMap.clear();
|
|
2182
|
+
for (const node of this.config.nodes) {
|
|
2183
|
+
this.nodeMap.set(node.id, node);
|
|
2184
|
+
}
|
|
2185
|
+
for (const link of this.config.links) {
|
|
2186
|
+
if (typeof link.source === "string") {
|
|
2187
|
+
const sourceNode = this.nodeMap.get(link.source);
|
|
2188
|
+
if (sourceNode) {
|
|
2189
|
+
link.source = sourceNode;
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
if (typeof link.target === "string") {
|
|
2193
|
+
const targetNode = this.nodeMap.get(link.target);
|
|
2194
|
+
if (targetNode) {
|
|
2195
|
+
link.target = targetNode;
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
} catch (error) {
|
|
2200
|
+
ErrorHandler.logError(error);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2159
2203
|
/**
|
|
2160
2204
|
* Get simulation instance
|
|
2161
2205
|
*/
|
|
@@ -2168,11 +2212,15 @@ var PhysicsManager = class {
|
|
|
2168
2212
|
/**
|
|
2169
2213
|
* Reheat simulation for drag interactions
|
|
2170
2214
|
*/
|
|
2171
|
-
reheat(alphaTarget
|
|
2172
|
-
if (!this.simulation)
|
|
2215
|
+
reheat(alphaTarget) {
|
|
2216
|
+
if (!this.simulation || !this.config) {
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
2219
|
+
const nodeCount = this.config.nodes.length;
|
|
2220
|
+
const effectiveAlpha = alphaTarget ?? (nodeCount > 1e4 ? 5e-3 : nodeCount > 5e3 ? 0.01 : nodeCount > 2e3 ? 0.02 : 0.1);
|
|
2173
2221
|
try {
|
|
2174
|
-
this.simulation.alphaTarget(
|
|
2175
|
-
if (this.config
|
|
2222
|
+
this.simulation.alphaTarget(effectiveAlpha).restart();
|
|
2223
|
+
if (this.config.cooldownTime) {
|
|
2176
2224
|
this.setupCooldownTimer(this.config.cooldownTime);
|
|
2177
2225
|
}
|
|
2178
2226
|
} catch (error) {
|
|
@@ -2242,14 +2290,14 @@ var PhysicsManager = class {
|
|
|
2242
2290
|
adjustLinkDistancesForVisualShortening() {
|
|
2243
2291
|
if (!this.simulation || !this.config) return;
|
|
2244
2292
|
try {
|
|
2293
|
+
const baseDistance = this.calculateBaseDistance();
|
|
2245
2294
|
const linkForce = this.simulation.force("link");
|
|
2246
2295
|
if (linkForce) {
|
|
2247
2296
|
linkForce.distance((link) => {
|
|
2248
|
-
const
|
|
2249
|
-
const
|
|
2250
|
-
const
|
|
2251
|
-
const
|
|
2252
|
-
const targetRadius = this.getNodeRadius(targetNode);
|
|
2297
|
+
const sourceNode = typeof link.source === "string" ? this.nodeMap.get(link.source) : link.source;
|
|
2298
|
+
const targetNode = typeof link.target === "string" ? this.nodeMap.get(link.target) : link.target;
|
|
2299
|
+
const sourceRadius = sourceNode?.style?.radius ?? 20;
|
|
2300
|
+
const targetRadius = targetNode?.style?.radius ?? 20;
|
|
2253
2301
|
const arrowLength = this.getLinkArrowLength(link);
|
|
2254
2302
|
const visualCompensation = sourceRadius + targetRadius + arrowLength;
|
|
2255
2303
|
const spacingBuffer = Math.max(20, (sourceRadius + targetRadius) * 0.5);
|
|
@@ -2267,23 +2315,10 @@ var PhysicsManager = class {
|
|
|
2267
2315
|
calculateBaseDistance() {
|
|
2268
2316
|
if (!this.config) return 120;
|
|
2269
2317
|
const nodeCount = this.config.nodes.length;
|
|
2270
|
-
const graphArea = this.config.width * this.config.height;
|
|
2318
|
+
const graphArea = Math.max(this.config.width * this.config.height, 1);
|
|
2271
2319
|
const nodeAreaRatio = nodeCount / (graphArea / 1e4);
|
|
2272
2320
|
return Math.max(80, Math.min(200, 120 + nodeAreaRatio * 20));
|
|
2273
2321
|
}
|
|
2274
|
-
/**
|
|
2275
|
-
* Find node by ID
|
|
2276
|
-
*/
|
|
2277
|
-
findNodeById(id2) {
|
|
2278
|
-
return this.config?.nodes.find((node) => node.id === id2);
|
|
2279
|
-
}
|
|
2280
|
-
/**
|
|
2281
|
-
* Get node radius from style or default
|
|
2282
|
-
*/
|
|
2283
|
-
getNodeRadius(node) {
|
|
2284
|
-
if (!node) return 20;
|
|
2285
|
-
return node.style?.radius ?? 20;
|
|
2286
|
-
}
|
|
2287
2322
|
/**
|
|
2288
2323
|
* Get arrow length from link style or default
|
|
2289
2324
|
*/
|
|
@@ -2300,7 +2335,7 @@ var PhysicsManager = class {
|
|
|
2300
2335
|
if (!this.config) return;
|
|
2301
2336
|
try {
|
|
2302
2337
|
for (const node of this.config.nodes) {
|
|
2303
|
-
if (
|
|
2338
|
+
if (node.x == null || node.y == null) {
|
|
2304
2339
|
node.x = Math.random() * this.config.width;
|
|
2305
2340
|
node.y = Math.random() * this.config.height;
|
|
2306
2341
|
}
|
|
@@ -2348,30 +2383,49 @@ var PhysicsManager = class {
|
|
|
2348
2383
|
* Pause the simulation
|
|
2349
2384
|
*/
|
|
2350
2385
|
pause() {
|
|
2351
|
-
if (this.simulation) {
|
|
2352
|
-
|
|
2386
|
+
if (!this.simulation) {
|
|
2387
|
+
return;
|
|
2353
2388
|
}
|
|
2389
|
+
this.timerManager.clearTimer("simulationCooldown");
|
|
2390
|
+
this.simulation.stop();
|
|
2354
2391
|
}
|
|
2355
2392
|
/**
|
|
2356
2393
|
* Resume the simulation
|
|
2357
2394
|
*/
|
|
2358
2395
|
resume() {
|
|
2359
|
-
if (this.simulation) {
|
|
2360
|
-
|
|
2396
|
+
if (!this.simulation) {
|
|
2397
|
+
return;
|
|
2398
|
+
}
|
|
2399
|
+
const nodeCount = this.config?.nodes.length ?? 0;
|
|
2400
|
+
const alpha = nodeCount > 1e4 ? 0.01 : nodeCount > 5e3 ? 0.02 : nodeCount > 2e3 ? 0.05 : 0.3;
|
|
2401
|
+
this.simulation.alpha(alpha).alphaTarget(0).restart();
|
|
2402
|
+
if (this.config?.cooldownTime) {
|
|
2403
|
+
this.setupCooldownTimer(this.config.cooldownTime);
|
|
2361
2404
|
}
|
|
2362
2405
|
}
|
|
2406
|
+
handleVisibilityChange = () => {
|
|
2407
|
+
if (document.visibilityState !== "visible") {
|
|
2408
|
+
this.pause();
|
|
2409
|
+
} else {
|
|
2410
|
+
this.resume();
|
|
2411
|
+
}
|
|
2412
|
+
};
|
|
2363
2413
|
/**
|
|
2364
2414
|
* Destroy physics simulation
|
|
2365
2415
|
*/
|
|
2366
2416
|
destroy() {
|
|
2367
2417
|
try {
|
|
2418
|
+
if (this.isVisibilityListenerAttached) {
|
|
2419
|
+
document.removeEventListener("visibilitychange", this.handleVisibilityChange);
|
|
2420
|
+
this.isVisibilityListenerAttached = false;
|
|
2421
|
+
}
|
|
2368
2422
|
this.timerManager.clearTimer("simulationCooldown");
|
|
2369
|
-
this.cooldownTimer = void 0;
|
|
2370
2423
|
if (this.simulation) {
|
|
2371
2424
|
this.simulation.stop();
|
|
2372
2425
|
this.simulation = void 0;
|
|
2373
2426
|
}
|
|
2374
2427
|
this.config = void 0;
|
|
2428
|
+
this.nodeMap.clear();
|
|
2375
2429
|
this.simulationStartTime = void 0;
|
|
2376
2430
|
this.simulationEndTime = void 0;
|
|
2377
2431
|
this.hasInitialAutoFitCompleted = false;
|
|
@@ -5204,7 +5258,7 @@ var DragManager = class {
|
|
|
5204
5258
|
fy: obj.fy
|
|
5205
5259
|
};
|
|
5206
5260
|
if (!event.active) {
|
|
5207
|
-
this.config.physicsManager.reheat(
|
|
5261
|
+
this.config.physicsManager.reheat();
|
|
5208
5262
|
obj.fx = obj.x;
|
|
5209
5263
|
obj.fy = obj.y;
|
|
5210
5264
|
}
|
package/dist/index.css
CHANGED
|
@@ -49,9 +49,11 @@
|
|
|
49
49
|
justify-content: center;
|
|
50
50
|
cursor: pointer;
|
|
51
51
|
transition: background 120ms ease;
|
|
52
|
+
overflow: hidden;
|
|
52
53
|
}
|
|
53
54
|
.pg-control-btn:hover {
|
|
54
55
|
background: #f9fafb;
|
|
56
|
+
border-radius: 12px;
|
|
55
57
|
}
|
|
56
58
|
.pg-orient-vertical .pg-control-btn:not(:last-child) {
|
|
57
59
|
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
package/dist/index.d.cts
CHANGED
|
@@ -428,6 +428,17 @@ interface V2Instance {
|
|
|
428
428
|
resetPerformanceMetrics(): void;
|
|
429
429
|
}
|
|
430
430
|
|
|
431
|
+
interface PhysicsConfig {
|
|
432
|
+
nodes: V2Node[];
|
|
433
|
+
links: V2Link[];
|
|
434
|
+
width: number;
|
|
435
|
+
height: number;
|
|
436
|
+
onTick: () => void;
|
|
437
|
+
onEnd?: () => void;
|
|
438
|
+
autoFitView?: boolean;
|
|
439
|
+
cooldownTime?: number;
|
|
440
|
+
}
|
|
441
|
+
|
|
431
442
|
/**
|
|
432
443
|
* V2 Canvas Graph - Error Handling
|
|
433
444
|
*/
|
|
@@ -604,24 +615,15 @@ declare class CanvasManager {
|
|
|
604
615
|
* Manages D3 force simulation lifecycle and behavior
|
|
605
616
|
*/
|
|
606
617
|
|
|
607
|
-
interface PhysicsConfig {
|
|
608
|
-
nodes: V2Node[];
|
|
609
|
-
links: V2Link[];
|
|
610
|
-
width: number;
|
|
611
|
-
height: number;
|
|
612
|
-
onTick: () => void;
|
|
613
|
-
onEnd?: () => void;
|
|
614
|
-
autoFitView?: boolean;
|
|
615
|
-
cooldownTime?: number;
|
|
616
|
-
}
|
|
617
618
|
declare class PhysicsManager {
|
|
618
619
|
private simulation?;
|
|
619
620
|
private config?;
|
|
620
621
|
private simulationStartTime?;
|
|
621
622
|
private simulationEndTime?;
|
|
622
|
-
private cooldownTimer?;
|
|
623
623
|
private hasInitialAutoFitCompleted;
|
|
624
624
|
private timerManager;
|
|
625
|
+
private isVisibilityListenerAttached;
|
|
626
|
+
private nodeMap;
|
|
625
627
|
constructor(timerManager: TimerManager);
|
|
626
628
|
/**
|
|
627
629
|
* Initialize physics simulation
|
|
@@ -639,6 +641,10 @@ declare class PhysicsManager {
|
|
|
639
641
|
* Setup cooldown timer (force-graph pattern)
|
|
640
642
|
*/
|
|
641
643
|
private setupCooldownTimer;
|
|
644
|
+
/**
|
|
645
|
+
* Build node index for O(1) lookups (Step 3 optimization)
|
|
646
|
+
*/
|
|
647
|
+
private buildNodeIndex;
|
|
642
648
|
/**
|
|
643
649
|
* Get simulation instance
|
|
644
650
|
*/
|
|
@@ -673,14 +679,6 @@ declare class PhysicsManager {
|
|
|
673
679
|
* Calculate base distance based on graph size and node count
|
|
674
680
|
*/
|
|
675
681
|
private calculateBaseDistance;
|
|
676
|
-
/**
|
|
677
|
-
* Find node by ID
|
|
678
|
-
*/
|
|
679
|
-
private findNodeById;
|
|
680
|
-
/**
|
|
681
|
-
* Get node radius from style or default
|
|
682
|
-
*/
|
|
683
|
-
private getNodeRadius;
|
|
684
682
|
/**
|
|
685
683
|
* Get arrow length from link style or default
|
|
686
684
|
*/
|
|
@@ -715,6 +713,7 @@ declare class PhysicsManager {
|
|
|
715
713
|
* Resume the simulation
|
|
716
714
|
*/
|
|
717
715
|
resume(): void;
|
|
716
|
+
private handleVisibilityChange;
|
|
718
717
|
/**
|
|
719
718
|
* Destroy physics simulation
|
|
720
719
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -428,6 +428,17 @@ interface V2Instance {
|
|
|
428
428
|
resetPerformanceMetrics(): void;
|
|
429
429
|
}
|
|
430
430
|
|
|
431
|
+
interface PhysicsConfig {
|
|
432
|
+
nodes: V2Node[];
|
|
433
|
+
links: V2Link[];
|
|
434
|
+
width: number;
|
|
435
|
+
height: number;
|
|
436
|
+
onTick: () => void;
|
|
437
|
+
onEnd?: () => void;
|
|
438
|
+
autoFitView?: boolean;
|
|
439
|
+
cooldownTime?: number;
|
|
440
|
+
}
|
|
441
|
+
|
|
431
442
|
/**
|
|
432
443
|
* V2 Canvas Graph - Error Handling
|
|
433
444
|
*/
|
|
@@ -604,24 +615,15 @@ declare class CanvasManager {
|
|
|
604
615
|
* Manages D3 force simulation lifecycle and behavior
|
|
605
616
|
*/
|
|
606
617
|
|
|
607
|
-
interface PhysicsConfig {
|
|
608
|
-
nodes: V2Node[];
|
|
609
|
-
links: V2Link[];
|
|
610
|
-
width: number;
|
|
611
|
-
height: number;
|
|
612
|
-
onTick: () => void;
|
|
613
|
-
onEnd?: () => void;
|
|
614
|
-
autoFitView?: boolean;
|
|
615
|
-
cooldownTime?: number;
|
|
616
|
-
}
|
|
617
618
|
declare class PhysicsManager {
|
|
618
619
|
private simulation?;
|
|
619
620
|
private config?;
|
|
620
621
|
private simulationStartTime?;
|
|
621
622
|
private simulationEndTime?;
|
|
622
|
-
private cooldownTimer?;
|
|
623
623
|
private hasInitialAutoFitCompleted;
|
|
624
624
|
private timerManager;
|
|
625
|
+
private isVisibilityListenerAttached;
|
|
626
|
+
private nodeMap;
|
|
625
627
|
constructor(timerManager: TimerManager);
|
|
626
628
|
/**
|
|
627
629
|
* Initialize physics simulation
|
|
@@ -639,6 +641,10 @@ declare class PhysicsManager {
|
|
|
639
641
|
* Setup cooldown timer (force-graph pattern)
|
|
640
642
|
*/
|
|
641
643
|
private setupCooldownTimer;
|
|
644
|
+
/**
|
|
645
|
+
* Build node index for O(1) lookups (Step 3 optimization)
|
|
646
|
+
*/
|
|
647
|
+
private buildNodeIndex;
|
|
642
648
|
/**
|
|
643
649
|
* Get simulation instance
|
|
644
650
|
*/
|
|
@@ -673,14 +679,6 @@ declare class PhysicsManager {
|
|
|
673
679
|
* Calculate base distance based on graph size and node count
|
|
674
680
|
*/
|
|
675
681
|
private calculateBaseDistance;
|
|
676
|
-
/**
|
|
677
|
-
* Find node by ID
|
|
678
|
-
*/
|
|
679
|
-
private findNodeById;
|
|
680
|
-
/**
|
|
681
|
-
* Get node radius from style or default
|
|
682
|
-
*/
|
|
683
|
-
private getNodeRadius;
|
|
684
682
|
/**
|
|
685
683
|
* Get arrow length from link style or default
|
|
686
684
|
*/
|
|
@@ -715,6 +713,7 @@ declare class PhysicsManager {
|
|
|
715
713
|
* Resume the simulation
|
|
716
714
|
*/
|
|
717
715
|
resume(): void;
|
|
716
|
+
private handleVisibilityChange;
|
|
718
717
|
/**
|
|
719
718
|
* Destroy physics simulation
|
|
720
719
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1997,9 +1997,10 @@ var PhysicsManager = class {
|
|
|
1997
1997
|
config;
|
|
1998
1998
|
simulationStartTime;
|
|
1999
1999
|
simulationEndTime;
|
|
2000
|
-
cooldownTimer;
|
|
2001
2000
|
hasInitialAutoFitCompleted = false;
|
|
2002
2001
|
timerManager;
|
|
2002
|
+
isVisibilityListenerAttached = false;
|
|
2003
|
+
nodeMap = /* @__PURE__ */ new Map();
|
|
2003
2004
|
constructor(timerManager) {
|
|
2004
2005
|
this.timerManager = timerManager;
|
|
2005
2006
|
}
|
|
@@ -2013,34 +2014,49 @@ var PhysicsManager = class {
|
|
|
2013
2014
|
if (typeof config.onTick !== "function") {
|
|
2014
2015
|
throw new ValidationError("onTick callback is required and must be a function");
|
|
2015
2016
|
}
|
|
2017
|
+
if (!this.isVisibilityListenerAttached) {
|
|
2018
|
+
document.addEventListener("visibilitychange", this.handleVisibilityChange);
|
|
2019
|
+
this.isVisibilityListenerAttached = true;
|
|
2020
|
+
}
|
|
2016
2021
|
this.config = config;
|
|
2017
2022
|
this.simulationStartTime = performance.now();
|
|
2018
2023
|
const nodeCount = config.nodes.length;
|
|
2019
|
-
const graphArea = config.width * config.height;
|
|
2024
|
+
const graphArea = Math.max(config.width * config.height, 1);
|
|
2020
2025
|
const nodeDensity = nodeCount / (graphArea / 1e5);
|
|
2021
2026
|
const densityFactor = Math.min(nodeDensity, 2);
|
|
2022
2027
|
const baseVelocityDecay = 0.4;
|
|
2023
2028
|
const adaptiveVelocityDecay = Math.min(baseVelocityDecay + densityFactor * 0.2, 0.8);
|
|
2024
2029
|
const baseAlphaDecay = 0.02;
|
|
2025
2030
|
const adaptiveAlphaDecay = Math.min(baseAlphaDecay + densityFactor * 0.01, 0.05);
|
|
2031
|
+
if (this.simulation) {
|
|
2032
|
+
this.simulation.stop();
|
|
2033
|
+
}
|
|
2034
|
+
this.buildNodeIndex();
|
|
2035
|
+
const linkDistance = nodeCount > 1e4 ? 220 : nodeCount > 5e3 ? 190 : nodeCount > 2e3 ? 170 : 150;
|
|
2036
|
+
const chargeStrength = nodeCount > 1e4 ? -350 : nodeCount > 5e3 ? -400 : nodeCount > 2e3 ? -450 : -500;
|
|
2037
|
+
const collisionRadius = nodeCount > 1e4 ? 1 : nodeCount > 5e3 ? 2 : 2;
|
|
2038
|
+
const collisionIterations = nodeCount > 1e4 ? 1 : nodeCount > 5e3 ? 1 : 2;
|
|
2039
|
+
const centerStrength = nodeCount > 5e3 ? 0.15 : 0.5;
|
|
2040
|
+
const linkStrength = nodeCount > 1e4 ? 0.15 : nodeCount > 5e3 ? 0.25 : 0.4;
|
|
2026
2041
|
this.simulation = simulation_default(config.nodes).force(
|
|
2027
2042
|
"link",
|
|
2028
|
-
link_default(config.links).id((d) => d.id).distance(
|
|
2029
|
-
// Much weaker link strength to allow repulsion to work
|
|
2043
|
+
link_default(config.links).id((d) => d.id).distance(linkDistance).strength(linkStrength).iterations(1)
|
|
2030
2044
|
).force(
|
|
2031
2045
|
"charge",
|
|
2032
|
-
manyBody_default().strength(
|
|
2033
|
-
// Adaptive repulsion strength
|
|
2034
|
-
// .distanceMin(1) // Minimum distance for repulsion
|
|
2035
|
-
// .distanceMax(Math.max(300, 600 - densityFactor * 100)) // Reduce max distance for dense graphs
|
|
2046
|
+
manyBody_default().strength(chargeStrength).theta(nodeCount > 5e3 ? 1.2 : 0.9).distanceMax(nodeCount > 5e3 ? 500 : 1e3)
|
|
2036
2047
|
).force(
|
|
2037
2048
|
"collision",
|
|
2038
|
-
collide_default().radius((node) =>
|
|
2049
|
+
collide_default().radius((node) => (node.style?.radius ?? 20) + collisionRadius).strength(1).iterations(collisionIterations)
|
|
2039
2050
|
).force(
|
|
2040
2051
|
"center",
|
|
2041
|
-
center_default(0, 0).strength(
|
|
2042
|
-
|
|
2043
|
-
|
|
2052
|
+
center_default(0, 0).strength(centerStrength)
|
|
2053
|
+
).velocityDecay(
|
|
2054
|
+
nodeCount > 5e3 ? 0.65 : adaptiveVelocityDecay
|
|
2055
|
+
).alphaDecay(
|
|
2056
|
+
nodeCount > 5e3 ? 0.05 : adaptiveAlphaDecay
|
|
2057
|
+
).alphaMin(
|
|
2058
|
+
nodeCount > 5e3 ? 0.05 : 1e-3
|
|
2059
|
+
).on("tick", config.onTick).on("end", () => this.handleSimulationEnd());
|
|
2044
2060
|
if (config.cooldownTime) {
|
|
2045
2061
|
this.setupCooldownTimer(config.cooldownTime);
|
|
2046
2062
|
}
|
|
@@ -2093,6 +2109,34 @@ var PhysicsManager = class {
|
|
|
2093
2109
|
ErrorHandler.logError(error, { cooldownTime });
|
|
2094
2110
|
}
|
|
2095
2111
|
}
|
|
2112
|
+
/**
|
|
2113
|
+
* Build node index for O(1) lookups (Step 3 optimization)
|
|
2114
|
+
*/
|
|
2115
|
+
buildNodeIndex() {
|
|
2116
|
+
if (!this.config) return;
|
|
2117
|
+
try {
|
|
2118
|
+
this.nodeMap.clear();
|
|
2119
|
+
for (const node of this.config.nodes) {
|
|
2120
|
+
this.nodeMap.set(node.id, node);
|
|
2121
|
+
}
|
|
2122
|
+
for (const link of this.config.links) {
|
|
2123
|
+
if (typeof link.source === "string") {
|
|
2124
|
+
const sourceNode = this.nodeMap.get(link.source);
|
|
2125
|
+
if (sourceNode) {
|
|
2126
|
+
link.source = sourceNode;
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
if (typeof link.target === "string") {
|
|
2130
|
+
const targetNode = this.nodeMap.get(link.target);
|
|
2131
|
+
if (targetNode) {
|
|
2132
|
+
link.target = targetNode;
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
} catch (error) {
|
|
2137
|
+
ErrorHandler.logError(error);
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2096
2140
|
/**
|
|
2097
2141
|
* Get simulation instance
|
|
2098
2142
|
*/
|
|
@@ -2105,11 +2149,15 @@ var PhysicsManager = class {
|
|
|
2105
2149
|
/**
|
|
2106
2150
|
* Reheat simulation for drag interactions
|
|
2107
2151
|
*/
|
|
2108
|
-
reheat(alphaTarget
|
|
2109
|
-
if (!this.simulation)
|
|
2152
|
+
reheat(alphaTarget) {
|
|
2153
|
+
if (!this.simulation || !this.config) {
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
const nodeCount = this.config.nodes.length;
|
|
2157
|
+
const effectiveAlpha = alphaTarget ?? (nodeCount > 1e4 ? 5e-3 : nodeCount > 5e3 ? 0.01 : nodeCount > 2e3 ? 0.02 : 0.1);
|
|
2110
2158
|
try {
|
|
2111
|
-
this.simulation.alphaTarget(
|
|
2112
|
-
if (this.config
|
|
2159
|
+
this.simulation.alphaTarget(effectiveAlpha).restart();
|
|
2160
|
+
if (this.config.cooldownTime) {
|
|
2113
2161
|
this.setupCooldownTimer(this.config.cooldownTime);
|
|
2114
2162
|
}
|
|
2115
2163
|
} catch (error) {
|
|
@@ -2179,14 +2227,14 @@ var PhysicsManager = class {
|
|
|
2179
2227
|
adjustLinkDistancesForVisualShortening() {
|
|
2180
2228
|
if (!this.simulation || !this.config) return;
|
|
2181
2229
|
try {
|
|
2230
|
+
const baseDistance = this.calculateBaseDistance();
|
|
2182
2231
|
const linkForce = this.simulation.force("link");
|
|
2183
2232
|
if (linkForce) {
|
|
2184
2233
|
linkForce.distance((link) => {
|
|
2185
|
-
const
|
|
2186
|
-
const
|
|
2187
|
-
const
|
|
2188
|
-
const
|
|
2189
|
-
const targetRadius = this.getNodeRadius(targetNode);
|
|
2234
|
+
const sourceNode = typeof link.source === "string" ? this.nodeMap.get(link.source) : link.source;
|
|
2235
|
+
const targetNode = typeof link.target === "string" ? this.nodeMap.get(link.target) : link.target;
|
|
2236
|
+
const sourceRadius = sourceNode?.style?.radius ?? 20;
|
|
2237
|
+
const targetRadius = targetNode?.style?.radius ?? 20;
|
|
2190
2238
|
const arrowLength = this.getLinkArrowLength(link);
|
|
2191
2239
|
const visualCompensation = sourceRadius + targetRadius + arrowLength;
|
|
2192
2240
|
const spacingBuffer = Math.max(20, (sourceRadius + targetRadius) * 0.5);
|
|
@@ -2204,23 +2252,10 @@ var PhysicsManager = class {
|
|
|
2204
2252
|
calculateBaseDistance() {
|
|
2205
2253
|
if (!this.config) return 120;
|
|
2206
2254
|
const nodeCount = this.config.nodes.length;
|
|
2207
|
-
const graphArea = this.config.width * this.config.height;
|
|
2255
|
+
const graphArea = Math.max(this.config.width * this.config.height, 1);
|
|
2208
2256
|
const nodeAreaRatio = nodeCount / (graphArea / 1e4);
|
|
2209
2257
|
return Math.max(80, Math.min(200, 120 + nodeAreaRatio * 20));
|
|
2210
2258
|
}
|
|
2211
|
-
/**
|
|
2212
|
-
* Find node by ID
|
|
2213
|
-
*/
|
|
2214
|
-
findNodeById(id2) {
|
|
2215
|
-
return this.config?.nodes.find((node) => node.id === id2);
|
|
2216
|
-
}
|
|
2217
|
-
/**
|
|
2218
|
-
* Get node radius from style or default
|
|
2219
|
-
*/
|
|
2220
|
-
getNodeRadius(node) {
|
|
2221
|
-
if (!node) return 20;
|
|
2222
|
-
return node.style?.radius ?? 20;
|
|
2223
|
-
}
|
|
2224
2259
|
/**
|
|
2225
2260
|
* Get arrow length from link style or default
|
|
2226
2261
|
*/
|
|
@@ -2237,7 +2272,7 @@ var PhysicsManager = class {
|
|
|
2237
2272
|
if (!this.config) return;
|
|
2238
2273
|
try {
|
|
2239
2274
|
for (const node of this.config.nodes) {
|
|
2240
|
-
if (
|
|
2275
|
+
if (node.x == null || node.y == null) {
|
|
2241
2276
|
node.x = Math.random() * this.config.width;
|
|
2242
2277
|
node.y = Math.random() * this.config.height;
|
|
2243
2278
|
}
|
|
@@ -2285,30 +2320,49 @@ var PhysicsManager = class {
|
|
|
2285
2320
|
* Pause the simulation
|
|
2286
2321
|
*/
|
|
2287
2322
|
pause() {
|
|
2288
|
-
if (this.simulation) {
|
|
2289
|
-
|
|
2323
|
+
if (!this.simulation) {
|
|
2324
|
+
return;
|
|
2290
2325
|
}
|
|
2326
|
+
this.timerManager.clearTimer("simulationCooldown");
|
|
2327
|
+
this.simulation.stop();
|
|
2291
2328
|
}
|
|
2292
2329
|
/**
|
|
2293
2330
|
* Resume the simulation
|
|
2294
2331
|
*/
|
|
2295
2332
|
resume() {
|
|
2296
|
-
if (this.simulation) {
|
|
2297
|
-
|
|
2333
|
+
if (!this.simulation) {
|
|
2334
|
+
return;
|
|
2335
|
+
}
|
|
2336
|
+
const nodeCount = this.config?.nodes.length ?? 0;
|
|
2337
|
+
const alpha = nodeCount > 1e4 ? 0.01 : nodeCount > 5e3 ? 0.02 : nodeCount > 2e3 ? 0.05 : 0.3;
|
|
2338
|
+
this.simulation.alpha(alpha).alphaTarget(0).restart();
|
|
2339
|
+
if (this.config?.cooldownTime) {
|
|
2340
|
+
this.setupCooldownTimer(this.config.cooldownTime);
|
|
2298
2341
|
}
|
|
2299
2342
|
}
|
|
2343
|
+
handleVisibilityChange = () => {
|
|
2344
|
+
if (document.visibilityState !== "visible") {
|
|
2345
|
+
this.pause();
|
|
2346
|
+
} else {
|
|
2347
|
+
this.resume();
|
|
2348
|
+
}
|
|
2349
|
+
};
|
|
2300
2350
|
/**
|
|
2301
2351
|
* Destroy physics simulation
|
|
2302
2352
|
*/
|
|
2303
2353
|
destroy() {
|
|
2304
2354
|
try {
|
|
2355
|
+
if (this.isVisibilityListenerAttached) {
|
|
2356
|
+
document.removeEventListener("visibilitychange", this.handleVisibilityChange);
|
|
2357
|
+
this.isVisibilityListenerAttached = false;
|
|
2358
|
+
}
|
|
2305
2359
|
this.timerManager.clearTimer("simulationCooldown");
|
|
2306
|
-
this.cooldownTimer = void 0;
|
|
2307
2360
|
if (this.simulation) {
|
|
2308
2361
|
this.simulation.stop();
|
|
2309
2362
|
this.simulation = void 0;
|
|
2310
2363
|
}
|
|
2311
2364
|
this.config = void 0;
|
|
2365
|
+
this.nodeMap.clear();
|
|
2312
2366
|
this.simulationStartTime = void 0;
|
|
2313
2367
|
this.simulationEndTime = void 0;
|
|
2314
2368
|
this.hasInitialAutoFitCompleted = false;
|
|
@@ -5141,7 +5195,7 @@ var DragManager = class {
|
|
|
5141
5195
|
fy: obj.fy
|
|
5142
5196
|
};
|
|
5143
5197
|
if (!event.active) {
|
|
5144
|
-
this.config.physicsManager.reheat(
|
|
5198
|
+
this.config.physicsManager.reheat();
|
|
5145
5199
|
obj.fx = obj.x;
|
|
5146
5200
|
obj.fy = obj.y;
|
|
5147
5201
|
}
|
package/package.json
CHANGED