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 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(150).strength(0.4)
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(-500)
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) => this.getNodeRadius(node) + 2).strength(1)
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(0.5)
2105
- // Center around origin like force-graph
2106
- ).velocityDecay(adaptiveVelocityDecay).alphaDecay(adaptiveAlphaDecay).on("tick", config.onTick).on("end", () => this.handleSimulationEnd());
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 = 0.3) {
2172
- if (!this.simulation) return;
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(alphaTarget).restart();
2175
- if (this.config?.cooldownTime) {
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 baseDistance = this.calculateBaseDistance();
2249
- const sourceNode = this.findNodeById(typeof link.source === "string" ? link.source : link.source.id);
2250
- const targetNode = this.findNodeById(typeof link.target === "string" ? link.target : link.target.id);
2251
- const sourceRadius = this.getNodeRadius(sourceNode);
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 (!node.x || !node.y) {
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
- this.simulation.stop();
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
- this.simulation.restart();
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(0.3);
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(150).strength(0.4)
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(-500)
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) => this.getNodeRadius(node) + 2).strength(1)
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(0.5)
2042
- // Center around origin like force-graph
2043
- ).velocityDecay(adaptiveVelocityDecay).alphaDecay(adaptiveAlphaDecay).on("tick", config.onTick).on("end", () => this.handleSimulationEnd());
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 = 0.3) {
2109
- if (!this.simulation) return;
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(alphaTarget).restart();
2112
- if (this.config?.cooldownTime) {
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 baseDistance = this.calculateBaseDistance();
2186
- const sourceNode = this.findNodeById(typeof link.source === "string" ? link.source : link.source.id);
2187
- const targetNode = this.findNodeById(typeof link.target === "string" ? link.target : link.target.id);
2188
- const sourceRadius = this.getNodeRadius(sourceNode);
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 (!node.x || !node.y) {
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
- this.simulation.stop();
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
- this.simulation.restart();
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(0.3);
5198
+ this.config.physicsManager.reheat();
5145
5199
  obj.fx = obj.x;
5146
5200
  obj.fy = obj.y;
5147
5201
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polly-graph",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Reusable D3-based graph visualization SDK with configurable nodes, links, labels, interactions, and layout behaviors.",
5
5
  "license": "MIT",
6
6
  "author": "Badal",