polly-graph 0.2.5 → 0.2.6

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.js CHANGED
@@ -9120,28 +9120,32 @@ function shouldRenderControl(config, key) {
9120
9120
  return value;
9121
9121
  }
9122
9122
 
9123
- // src/shared/icons/fit.svg?raw
9124
- var fit_default = '<svg\n xmlns="http://www.w3.org/2000/svg"\n viewBox="0 0 24 24"\n fill="none"\n stroke="currentColor"\n stroke-width="2"\n stroke-linecap="round"\n stroke-linejoin="round"\n>\n <path d="M5 9V5H9" />\n <path d="M19 9V5H15" />\n <path d="M5 15V19H9" />\n <path d="M19 15V19H15" />\n</svg>';
9125
-
9126
- // src/shared/icons/reset.svg?raw
9127
- var reset_default = '<svg\n xmlns="http://www.w3.org/2000/svg"\n viewBox="0 0 24 24"\n fill="none"\n stroke="currentColor"\n stroke-width="2"\n stroke-linecap="round"\n stroke-linejoin="round"\n>\n <path d="M20 12a8 8 0 1 1-2.3-5.7" />\n <path d="M20 4.5v4h-4" />\n</svg>';
9128
-
9129
- // src/shared/icons/plus.svg?raw
9130
- var plus_default = '<svg\n xmlns="http://www.w3.org/2000/svg"\n viewBox="0 0 24 24"\n fill="none"\n stroke="currentColor"\n stroke-width="2"\n stroke-linecap="round"\n stroke-linejoin="round"\n>\n <path d="M5 12h14m-7-7v14" />\n</svg>';
9131
-
9132
- // src/shared/icons/minus.svg?raw
9133
- var minus_default = '<svg\n xmlns="http://www.w3.org/2000/svg"\n viewBox="0 0 24 24"\n fill="none"\n stroke="currentColor"\n stroke-width="2"\n stroke-linecap="round"\n stroke-linejoin="round"\n>\n <path d="M19 12H5" />\n</svg>';
9134
-
9135
- // src/shared/icons/caret.svg?raw
9136
- var caret_default = '<svg\n xmlns="http://www.w3.org/2000/svg"\n viewBox="0 0 24 24"\n fill="none"\n stroke="currentColor"\n stroke-width="2"\n stroke-linecap="round"\n stroke-linejoin="round"\n>\n <path d="M9 20L16.5 12L9 4" />\n</svg>';
9137
-
9138
9123
  // src/shared/icons/index.ts
9124
+ var fitIconSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
9125
+ <path d="M5 9V5H9" />
9126
+ <path d="M19 9V5H15" />
9127
+ <path d="M5 15V19H9" />
9128
+ <path d="M19 15V19H15" />
9129
+ </svg>`;
9130
+ var resetIconSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
9131
+ <path d="M20 12a8 8 0 1 1-2.3-5.7" />
9132
+ <path d="M20 4.5v4h-4" />
9133
+ </svg>`;
9134
+ var plusIconSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
9135
+ <path d="M5 12h14m-7-7v14" />
9136
+ </svg>`;
9137
+ var minusIconSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
9138
+ <path d="M5 12h14" />
9139
+ </svg>`;
9140
+ var caretIconSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
9141
+ <path d="M6 9l6 6 6-6" />
9142
+ </svg>`;
9139
9143
  var icons = {
9140
- fit: fit_default,
9141
- reset: reset_default,
9142
- plus: plus_default,
9143
- minus: minus_default,
9144
- caret: caret_default
9144
+ fit: fitIconSvg,
9145
+ reset: resetIconSvg,
9146
+ plus: plusIconSvg,
9147
+ minus: minusIconSvg,
9148
+ caret: caretIconSvg
9145
9149
  };
9146
9150
  var iconSvg = {
9147
9151
  fit: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@@ -10069,16 +10073,3559 @@ function createV2Graph(config) {
10069
10073
  graph.initialize(config);
10070
10074
  return graph;
10071
10075
  }
10076
+
10077
+ // src/force-graph-wrapper/core/force-graph-wrapper.ts
10078
+ import ForceGraph from "force-graph";
10079
+
10080
+ // src/force-graph-wrapper/ui/graph-controls.ts
10081
+ function createGraphControls(container, actions, config) {
10082
+ let root2 = null;
10083
+ function mount() {
10084
+ if (!config.enabled) return;
10085
+ root2 = document.createElement("div");
10086
+ root2.className = "fg-controls";
10087
+ const position = config.position || "bottom-left";
10088
+ root2.classList.add(`fg-pos-${position}`);
10089
+ const orientation = config.orientation || "vertical";
10090
+ root2.classList.add(`fg-orient-${orientation}`);
10091
+ if (config.offset) {
10092
+ root2.style.setProperty("--fg-controls-offset-x", `${config.offset.x}px`);
10093
+ root2.style.setProperty("--fg-controls-offset-y", `${config.offset.y}px`);
10094
+ }
10095
+ appendControls2(root2, config, actions);
10096
+ container.appendChild(root2);
10097
+ }
10098
+ function destroy() {
10099
+ if (root2 && root2.parentNode) {
10100
+ root2.parentNode.removeChild(root2);
10101
+ root2 = null;
10102
+ }
10103
+ }
10104
+ return { mount, destroy };
10105
+ }
10106
+ function appendControls2(root2, config, actions) {
10107
+ const controls = [
10108
+ {
10109
+ key: "zoomIn",
10110
+ icon: getControlIcon("zoom-in"),
10111
+ label: "Zoom In",
10112
+ action: () => actions.zoomIn()
10113
+ },
10114
+ {
10115
+ key: "zoomOut",
10116
+ icon: getControlIcon("zoom-out"),
10117
+ label: "Zoom Out",
10118
+ action: () => actions.zoomOut()
10119
+ },
10120
+ {
10121
+ key: "fit",
10122
+ icon: getControlIcon("fit"),
10123
+ label: "Fit View",
10124
+ action: () => actions.fitView()
10125
+ },
10126
+ {
10127
+ key: "reset",
10128
+ icon: getControlIcon("reset"),
10129
+ label: "Reset View",
10130
+ action: () => actions.resetView()
10131
+ }
10132
+ ];
10133
+ controls.forEach((control) => {
10134
+ if (shouldShowControl(config, control.key)) {
10135
+ const button = createControlButton2(control.icon, control.label, control.action);
10136
+ root2.appendChild(button);
10137
+ }
10138
+ });
10139
+ }
10140
+ function shouldShowControl(config, key) {
10141
+ return config.show?.[key] !== false;
10142
+ }
10143
+ function createControlButton2(icon, label, onClick) {
10144
+ const button = document.createElement("button");
10145
+ button.className = "fg-control-btn";
10146
+ button.type = "button";
10147
+ button.innerHTML = icon;
10148
+ button.setAttribute("aria-label", label);
10149
+ button.setAttribute("title", label);
10150
+ button.addEventListener("click", (e) => {
10151
+ e.preventDefault();
10152
+ e.stopPropagation();
10153
+ onClick();
10154
+ });
10155
+ return button;
10156
+ }
10157
+
10158
+ // src/force-graph-wrapper/ui/graph-legends.ts
10159
+ function createGraphLegends(container, config) {
10160
+ let root2 = null;
10161
+ function mount() {
10162
+ if (!config.enabled) return;
10163
+ root2 = document.createElement("div");
10164
+ root2.className = "fg-legends";
10165
+ const position = config.position || "top-right";
10166
+ root2.classList.add(`fg-pos-${position}`);
10167
+ if (config.offset) {
10168
+ root2.style.setProperty("--fg-legends-offset-x", `${config.offset.x}px`);
10169
+ root2.style.setProperty("--fg-legends-offset-y", `${config.offset.y}px`);
10170
+ }
10171
+ container.appendChild(root2);
10172
+ }
10173
+ function destroy() {
10174
+ if (root2 && root2.parentNode) {
10175
+ root2.parentNode.removeChild(root2);
10176
+ root2 = null;
10177
+ }
10178
+ }
10179
+ function update(nodeTypes, colorMap) {
10180
+ if (!root2 || !config.enabled) return;
10181
+ root2.innerHTML = "";
10182
+ const validTypes = nodeTypes.filter((type) => type && type.trim()).sort((a2, b) => a2.localeCompare(b));
10183
+ if (validTypes.length === 0) return;
10184
+ const displayTypes = config.maxItems ? validTypes.slice(0, config.maxItems) : validTypes;
10185
+ if (config.showTitle) {
10186
+ const titleElement = document.createElement("div");
10187
+ titleElement.className = "fg-legend-title";
10188
+ titleElement.textContent = config.title || "Legend";
10189
+ root2.appendChild(titleElement);
10190
+ }
10191
+ displayTypes.forEach((type) => {
10192
+ const itemElement = createLegendItem(type, colorMap[type] || "#ccc");
10193
+ root2.appendChild(itemElement);
10194
+ });
10195
+ if (config.maxItems && validTypes.length > config.maxItems) {
10196
+ const moreElement = document.createElement("div");
10197
+ moreElement.className = "fg-legend-item fg-legend-more";
10198
+ moreElement.innerHTML = `
10199
+ <div class="fg-legend-dot" style="background-color: #999;"></div>
10200
+ <span class="fg-legend-label">and ${validTypes.length - config.maxItems} more...</span>
10201
+ `;
10202
+ root2.appendChild(moreElement);
10203
+ }
10204
+ }
10205
+ function createLegendItem(type, color2) {
10206
+ const itemElement = document.createElement("div");
10207
+ itemElement.className = "fg-legend-item";
10208
+ const dotElement = document.createElement("div");
10209
+ dotElement.className = "fg-legend-dot";
10210
+ dotElement.style.backgroundColor = color2;
10211
+ const labelElement = document.createElement("span");
10212
+ labelElement.className = "fg-legend-label";
10213
+ labelElement.textContent = type;
10214
+ itemElement.appendChild(dotElement);
10215
+ itemElement.appendChild(labelElement);
10216
+ return itemElement;
10217
+ }
10218
+ return { mount, destroy, update };
10219
+ }
10220
+
10221
+ // src/force-graph-wrapper/workers/physics-worker-manager.ts
10222
+ var PhysicsWorkerManagerImpl = class {
10223
+ worker = null;
10224
+ workerUrl = null;
10225
+ messageId = 0;
10226
+ pendingPromises = /* @__PURE__ */ new Map();
10227
+ promiseTimeouts = /* @__PURE__ */ new Map();
10228
+ constructor() {
10229
+ this.initializeWorker();
10230
+ }
10231
+ /**
10232
+ * Initialize the web worker
10233
+ */
10234
+ initializeWorker() {
10235
+ try {
10236
+ const workerScript = this.getWorkerScript();
10237
+ const blob = new Blob([workerScript], { type: "application/javascript" });
10238
+ this.workerUrl = URL.createObjectURL(blob);
10239
+ this.worker = new Worker(this.workerUrl);
10240
+ this.setupWorkerEventListeners();
10241
+ } catch (error) {
10242
+ console.warn("Physics Worker initialization failed:", error);
10243
+ this.worker = null;
10244
+ }
10245
+ }
10246
+ /**
10247
+ * Get the worker script content
10248
+ */
10249
+ getWorkerScript() {
10250
+ return `
10251
+ /**
10252
+ * Physics Web Worker for Force Graph
10253
+ * Runs D3 force simulation in a dedicated worker thread for consistent timing
10254
+ */
10255
+
10256
+ // Import D3 dependencies for force simulation
10257
+ importScripts('https://cdn.jsdelivr.net/npm/d3-dispatch@3/dist/d3-dispatch.min.js');
10258
+ importScripts('https://cdn.jsdelivr.net/npm/d3-quadtree@3/dist/d3-quadtree.min.js');
10259
+ importScripts('https://cdn.jsdelivr.net/npm/d3-timer@3/dist/d3-timer.min.js');
10260
+ importScripts('https://cdn.jsdelivr.net/npm/d3-force@3/dist/d3-force.min.js');
10261
+
10262
+ class PhysicsWorker {
10263
+ constructor() {
10264
+ this.simulation = null;
10265
+ this.nodes = [];
10266
+ this.links = [];
10267
+ this.isRunning = false;
10268
+ this.tickCount = 0;
10269
+ this.maxTicks = 300;
10270
+ this.intervalId = null; // Track interval for cleanup
10271
+ }
10272
+
10273
+ initializeSimulation(config) {
10274
+ const {
10275
+ nodes,
10276
+ links,
10277
+ width = 400,
10278
+ height = 300,
10279
+ forces = {},
10280
+ maxTicks = 300,
10281
+ alphaDecay = 0.0228,
10282
+ velocityDecay = 0.4
10283
+ } = config;
10284
+
10285
+ this.nodes = nodes.map(node => ({ ...node }));
10286
+ this.links = links.map(link => ({ ...link }));
10287
+ this.maxTicks = maxTicks;
10288
+ this.tickCount = 0;
10289
+
10290
+ this.simulation = d3.forceSimulation(this.nodes)
10291
+ .alphaDecay(alphaDecay)
10292
+ .velocityDecay(velocityDecay);
10293
+
10294
+ if (this.links.length > 0) {
10295
+ this.simulation.force('link',
10296
+ d3.forceLink(this.links)
10297
+ .id(d => d.id)
10298
+ .distance(forces.linkDistance || 30)
10299
+ .strength(forces.linkStrength || 1)
10300
+ );
10301
+ }
10302
+
10303
+ this.simulation.force('charge',
10304
+ d3.forceManyBody()
10305
+ .strength(forces.chargeStrength || -300)
10306
+ );
10307
+
10308
+ this.simulation.force('center',
10309
+ d3.forceCenter(width / 2, height / 2)
10310
+ .strength(forces.centerStrength || 1)
10311
+ );
10312
+
10313
+ if (forces.collisionRadius) {
10314
+ this.simulation.force('collision',
10315
+ d3.forceCollide(forces.collisionRadius)
10316
+ .strength(forces.collisionStrength || 0.7)
10317
+ );
10318
+ }
10319
+
10320
+ return {
10321
+ success: true,
10322
+ message: \`Simulation initialized with \${this.nodes.length} nodes and \${this.links.length} links\`
10323
+ };
10324
+ }
10325
+
10326
+ runSimulation() {
10327
+ if (!this.simulation || this.isRunning) {
10328
+ return { success: false, message: 'Simulation not initialized or already running' };
10329
+ }
10330
+
10331
+ // Clear any existing interval
10332
+ this.clearInterval();
10333
+
10334
+ this.isRunning = true;
10335
+ this.tickCount = 0;
10336
+
10337
+ this.intervalId = setInterval(() => {
10338
+ try {
10339
+ if (this.tickCount >= this.maxTicks || this.simulation.alpha() < 0.01) {
10340
+ this.completeSimulation();
10341
+ return;
10342
+ }
10343
+
10344
+ this.simulation.tick();
10345
+ this.tickCount++;
10346
+
10347
+ if (this.tickCount % 20 === 0) {
10348
+ postMessage({
10349
+ type: 'simulation_progress',
10350
+ data: {
10351
+ tickCount: this.tickCount,
10352
+ alpha: this.simulation.alpha(),
10353
+ progress: this.tickCount / this.maxTicks
10354
+ }
10355
+ });
10356
+ }
10357
+ } catch (error) {
10358
+ this.handleSimulationError(error);
10359
+ }
10360
+ }, 16);
10361
+
10362
+ return { success: true, message: 'Simulation started' };
10363
+ }
10364
+
10365
+ completeSimulation() {
10366
+ this.clearInterval();
10367
+ this.isRunning = false;
10368
+
10369
+ // Create final node data with memory-efficient copying
10370
+ const finalNodes = this.nodes.map(node => ({
10371
+ id: node.id,
10372
+ x: node.x || 0,
10373
+ y: node.y || 0,
10374
+ vx: node.vx || 0,
10375
+ vy: node.vy || 0
10376
+ }));
10377
+
10378
+ postMessage({
10379
+ type: 'simulation_complete',
10380
+ data: {
10381
+ nodes: finalNodes,
10382
+ tickCount: this.tickCount,
10383
+ finalAlpha: this.simulation ? this.simulation.alpha() : 0
10384
+ }
10385
+ });
10386
+
10387
+ // Clean up simulation resources
10388
+ this.cleanupSimulation();
10389
+ }
10390
+
10391
+ handleSimulationError(error) {
10392
+ this.clearInterval();
10393
+ this.isRunning = false;
10394
+ this.cleanupSimulation();
10395
+
10396
+ postMessage({
10397
+ type: 'error',
10398
+ data: {
10399
+ success: false,
10400
+ message: 'Simulation error: ' + error.message,
10401
+ stack: error.stack
10402
+ }
10403
+ });
10404
+ }
10405
+
10406
+ stopSimulation() {
10407
+ this.clearInterval();
10408
+
10409
+ if (this.simulation) {
10410
+ this.simulation.stop();
10411
+ this.isRunning = false;
10412
+ this.cleanupSimulation();
10413
+ return { success: true, message: 'Simulation stopped' };
10414
+ }
10415
+ return { success: false, message: 'No active simulation' };
10416
+ }
10417
+
10418
+ clearInterval() {
10419
+ if (this.intervalId) {
10420
+ clearInterval(this.intervalId);
10421
+ this.intervalId = null;
10422
+ }
10423
+ }
10424
+
10425
+ cleanupSimulation() {
10426
+ // Clear D3 simulation references
10427
+ if (this.simulation) {
10428
+ this.simulation.stop();
10429
+ this.simulation = null;
10430
+ }
10431
+
10432
+ // Clear node and link arrays to free memory
10433
+ this.nodes.length = 0;
10434
+ this.links.length = 0;
10435
+
10436
+ // Reset state
10437
+ this.tickCount = 0;
10438
+ this.isRunning = false;
10439
+ }
10440
+ }
10441
+
10442
+ const physicsWorker = new PhysicsWorker();
10443
+
10444
+ self.onmessage = function(event) {
10445
+ const { type, data } = event.data;
10446
+
10447
+ try {
10448
+ let result;
10449
+
10450
+ switch (type) {
10451
+ case 'initialize':
10452
+ result = physicsWorker.initializeSimulation(data);
10453
+ break;
10454
+ case 'run':
10455
+ result = physicsWorker.runSimulation();
10456
+ break;
10457
+ case 'stop':
10458
+ result = physicsWorker.stopSimulation();
10459
+ break;
10460
+ default:
10461
+ result = { success: false, message: \`Unknown command: \${type}\` };
10462
+ }
10463
+
10464
+ postMessage({
10465
+ type: 'response',
10466
+ data: result
10467
+ });
10468
+
10469
+ } catch (error) {
10470
+ postMessage({
10471
+ type: 'error',
10472
+ data: {
10473
+ success: false,
10474
+ message: error.message,
10475
+ stack: error.stack
10476
+ }
10477
+ });
10478
+ }
10479
+ };
10480
+ `;
10481
+ }
10482
+ /**
10483
+ * Set up worker event listeners
10484
+ */
10485
+ setupWorkerEventListeners() {
10486
+ if (!this.worker) return;
10487
+ this.worker.onmessage = (event) => {
10488
+ const { type, data } = event.data;
10489
+ switch (type) {
10490
+ case "response":
10491
+ case "simulation_complete":
10492
+ this.resolvePromise(data);
10493
+ break;
10494
+ case "simulation_progress":
10495
+ break;
10496
+ case "error":
10497
+ this.rejectPromise(new Error(data.message));
10498
+ break;
10499
+ }
10500
+ };
10501
+ this.worker.onerror = (error) => {
10502
+ console.error("Physics Worker error:", error);
10503
+ this.rejectPromise(new Error(`Worker error: ${error.message}`));
10504
+ };
10505
+ }
10506
+ /**
10507
+ * Send message to worker with promise handling
10508
+ */
10509
+ sendMessage(message) {
10510
+ return new Promise((resolve, reject) => {
10511
+ if (!this.worker) {
10512
+ reject(new Error("Physics Worker not available"));
10513
+ return;
10514
+ }
10515
+ const id2 = ++this.messageId;
10516
+ this.pendingPromises.set(id2, {
10517
+ resolve,
10518
+ reject
10519
+ });
10520
+ const timeoutId = window.setTimeout(() => {
10521
+ if (this.pendingPromises.has(id2)) {
10522
+ this.pendingPromises.delete(id2);
10523
+ this.promiseTimeouts.delete(id2);
10524
+ reject(new Error("Physics Worker timeout"));
10525
+ }
10526
+ }, 3e4);
10527
+ this.promiseTimeouts.set(id2, timeoutId);
10528
+ this.worker.postMessage({ ...message, id: id2 });
10529
+ });
10530
+ }
10531
+ /**
10532
+ * Resolve pending promise
10533
+ */
10534
+ resolvePromise(data) {
10535
+ const promises = Array.from(this.pendingPromises.values());
10536
+ this.clearAllTimeouts();
10537
+ this.pendingPromises.clear();
10538
+ promises.forEach(({ resolve }) => resolve(data));
10539
+ }
10540
+ /**
10541
+ * Reject pending promise
10542
+ */
10543
+ rejectPromise(error) {
10544
+ const promises = Array.from(this.pendingPromises.values());
10545
+ this.clearAllTimeouts();
10546
+ this.pendingPromises.clear();
10547
+ promises.forEach(({ reject }) => reject(error));
10548
+ }
10549
+ /**
10550
+ * Clear all promise timeouts
10551
+ */
10552
+ clearAllTimeouts() {
10553
+ for (const timeoutId of this.promiseTimeouts.values()) {
10554
+ window.clearTimeout(timeoutId);
10555
+ }
10556
+ this.promiseTimeouts.clear();
10557
+ }
10558
+ /**
10559
+ * Initialize physics simulation with given configuration
10560
+ */
10561
+ async initialize(config) {
10562
+ if (!this.worker) {
10563
+ console.warn("Physics Worker not available, falling back to main thread");
10564
+ return false;
10565
+ }
10566
+ try {
10567
+ const response = await this.sendMessage({
10568
+ type: "initialize",
10569
+ data: config
10570
+ });
10571
+ return response.success;
10572
+ } catch (error) {
10573
+ console.error("Failed to initialize physics simulation:", error);
10574
+ return false;
10575
+ }
10576
+ }
10577
+ /**
10578
+ * Run the physics simulation and return final node positions
10579
+ */
10580
+ async runSimulation() {
10581
+ if (!this.worker) {
10582
+ throw new Error("Physics Worker not available");
10583
+ }
10584
+ try {
10585
+ await this.sendMessage({
10586
+ type: "run"
10587
+ });
10588
+ return new Promise((resolve, reject) => {
10589
+ if (!this.worker) {
10590
+ reject(new Error("Physics Worker not available"));
10591
+ return;
10592
+ }
10593
+ const handleMessage = (event) => {
10594
+ const { type, data } = event.data;
10595
+ if (type === "simulation_complete") {
10596
+ this.worker?.removeEventListener("message", handleMessage);
10597
+ resolve(data.nodes);
10598
+ } else if (type === "error") {
10599
+ this.worker?.removeEventListener("message", handleMessage);
10600
+ reject(new Error(data.message));
10601
+ }
10602
+ };
10603
+ this.worker.addEventListener("message", handleMessage);
10604
+ });
10605
+ } catch (error) {
10606
+ console.error("Physics simulation failed:", error);
10607
+ throw error;
10608
+ }
10609
+ }
10610
+ /**
10611
+ * Stop the current simulation
10612
+ */
10613
+ stopSimulation() {
10614
+ if (this.worker) {
10615
+ this.worker.postMessage({ type: "stop" });
10616
+ }
10617
+ }
10618
+ /**
10619
+ * Terminate the worker and cleanup resources
10620
+ */
10621
+ terminate() {
10622
+ this.clearAllTimeouts();
10623
+ if (this.worker) {
10624
+ this.worker.terminate();
10625
+ this.worker = null;
10626
+ }
10627
+ if (this.workerUrl) {
10628
+ URL.revokeObjectURL(this.workerUrl);
10629
+ this.workerUrl = null;
10630
+ }
10631
+ this.pendingPromises.clear();
10632
+ this.messageId = 0;
10633
+ }
10634
+ /**
10635
+ * Check if web worker is available
10636
+ */
10637
+ isAvailable() {
10638
+ return this.worker !== null && typeof Worker !== "undefined";
10639
+ }
10640
+ };
10641
+ var physicsWorkerManager = new PhysicsWorkerManagerImpl();
10642
+
10643
+ // src/force-graph-wrapper/utils/performance-monitor.ts
10644
+ var PerformanceMonitor = class {
10645
+ metrics = {
10646
+ creation: 0,
10647
+ dataLoad: 0,
10648
+ firstRender: 0,
10649
+ render: 0,
10650
+ methodCalls: 0,
10651
+ memoryUsage: 0,
10652
+ nodeCount: 0,
10653
+ linkCount: 0,
10654
+ optimizedForNodeCount: 0,
10655
+ timestamp: Date.now()
10656
+ };
10657
+ // Internal detailed tracking
10658
+ methodCallsDetailed = /* @__PURE__ */ new Map();
10659
+ /**
10660
+ * Track execution time of any operation
10661
+ */
10662
+ track(operation, fn) {
10663
+ const start2 = performance.now();
10664
+ const result = fn();
10665
+ const duration = performance.now() - start2;
10666
+ if (!this.methodCallsDetailed.has(operation)) {
10667
+ this.methodCallsDetailed.set(operation, []);
10668
+ }
10669
+ this.methodCallsDetailed.get(operation).push(duration);
10670
+ this.metrics["methodCalls"]++;
10671
+ return result;
10672
+ }
10673
+ /**
10674
+ * Set specific metric values
10675
+ */
10676
+ setMetric(key, value) {
10677
+ if (key !== "timestamp") {
10678
+ this.metrics[key] = value;
10679
+ }
10680
+ }
10681
+ /**
10682
+ * Get current metrics snapshot
10683
+ */
10684
+ getMetrics() {
10685
+ if (typeof performance.memory !== "undefined") {
10686
+ const perfMemory = performance.memory;
10687
+ this.metrics["memoryUsage"] = perfMemory.usedJSHeapSize / 1024 / 1024;
10688
+ }
10689
+ return {
10690
+ ...this.metrics,
10691
+ timestamp: Date.now()
10692
+ };
10693
+ }
10694
+ /**
10695
+ * Get performance summary with statistics
10696
+ */
10697
+ getSummary() {
10698
+ const averages = {};
10699
+ const peaks = {};
10700
+ const warnings = [];
10701
+ this.methodCallsDetailed.forEach((times, operation) => {
10702
+ const avg = times.reduce((sum2, time) => sum2 + time, 0) / times.length;
10703
+ const peak = Math.max(...times);
10704
+ averages[operation] = Math.round(avg * 1e3) / 1e3;
10705
+ peaks[operation] = Math.round(peak * 1e3) / 1e3;
10706
+ if (avg > 100) {
10707
+ warnings.push(`${operation} averaging ${avg.toFixed(1)}ms - consider optimization`);
10708
+ }
10709
+ if (peak > 500) {
10710
+ warnings.push(`${operation} peaked at ${peak.toFixed(1)}ms - investigate bottleneck`);
10711
+ }
10712
+ });
10713
+ averages["creation"] = this.metrics["creation"];
10714
+ averages["dataLoad"] = this.metrics["dataLoad"];
10715
+ averages["firstRender"] = this.metrics["firstRender"];
10716
+ averages["render"] = this.metrics["render"];
10717
+ let nodeScaling = "Linear";
10718
+ if (this.metrics["nodeCount"] > 1e3 && this.metrics["dataLoad"] > 100) {
10719
+ nodeScaling = "Sub-optimal - may be O(n\xB2)";
10720
+ warnings.push("Data loading appears to scale poorly with node count");
10721
+ }
10722
+ return {
10723
+ averages,
10724
+ peaks,
10725
+ warnings,
10726
+ nodeScaling
10727
+ };
10728
+ }
10729
+ /**
10730
+ * Reset all metrics
10731
+ */
10732
+ reset() {
10733
+ this.metrics = {
10734
+ creation: 0,
10735
+ dataLoad: 0,
10736
+ firstRender: 0,
10737
+ render: 0,
10738
+ methodCalls: 0,
10739
+ memoryUsage: 0,
10740
+ nodeCount: 0,
10741
+ linkCount: 0,
10742
+ optimizedForNodeCount: 0,
10743
+ timestamp: Date.now()
10744
+ };
10745
+ this.methodCallsDetailed.clear();
10746
+ }
10747
+ /**
10748
+ * Log performance summary to console
10749
+ */
10750
+ logSummary() {
10751
+ const summary = this.getSummary();
10752
+ console.group("\u{1F680} Force-Graph Wrapper Performance Summary");
10753
+ console.log("\u{1F4CA} Averages:", summary.averages);
10754
+ console.log("\u26A1 Peaks:", summary.peaks);
10755
+ console.log("\u{1F4C8} Node Scaling:", summary.nodeScaling);
10756
+ console.log("\u{1F4BE} Memory Usage:", `${this.metrics["memoryUsage"].toFixed(2)} MB`);
10757
+ console.log("\u{1F4CF} Graph Size:", `${this.metrics["nodeCount"]} nodes, ${this.metrics["linkCount"]} links`);
10758
+ if (summary.warnings.length > 0) {
10759
+ console.warn("\u26A0\uFE0F Performance Warnings:");
10760
+ summary.warnings.forEach((warning) => console.warn(` \u2022 ${warning}`));
10761
+ } else {
10762
+ console.log("\u2705 No performance warnings");
10763
+ }
10764
+ console.groupEnd();
10765
+ }
10766
+ };
10767
+ var globalPerformanceMonitor = new PerformanceMonitor();
10768
+
10769
+ // src/force-graph-wrapper/core/rendering-performance-monitor.ts
10770
+ var RenderingPerformanceMonitor = class {
10771
+ frames = [];
10772
+ frameTimes = [];
10773
+ lastFrameTime = 0;
10774
+ renderCallCount = 0;
10775
+ canvasOpCount = 0;
10776
+ droppedFrameCount = 0;
10777
+ isMonitoring = false;
10778
+ animationFrameId = null;
10779
+ maxSamples = 120;
10780
+ // 2 seconds at 60fps
10781
+ targets = {
10782
+ targetFps: 60,
10783
+ maxFrameTime: 16.67,
10784
+ // 1000ms / 60fps
10785
+ maxDroppedFrames: 5,
10786
+ efficiencyThreshold: 0.8
10787
+ };
10788
+ nodeCount = 0;
10789
+ linkCount = 0;
10790
+ constructor(targets) {
10791
+ if (targets) {
10792
+ this.targets = { ...this.targets, ...targets };
10793
+ }
10794
+ }
10795
+ /**
10796
+ * Start monitoring rendering performance
10797
+ */
10798
+ startMonitoring() {
10799
+ if (this.isMonitoring) return;
10800
+ this.isMonitoring = true;
10801
+ this.frames = [];
10802
+ this.frameTimes = [];
10803
+ this.renderCallCount = 0;
10804
+ this.canvasOpCount = 0;
10805
+ this.droppedFrameCount = 0;
10806
+ this.lastFrameTime = performance.now();
10807
+ this.monitorFrame();
10808
+ }
10809
+ /**
10810
+ * Stop monitoring rendering performance
10811
+ */
10812
+ stopMonitoring() {
10813
+ this.isMonitoring = false;
10814
+ if (this.animationFrameId !== null) {
10815
+ cancelAnimationFrame(this.animationFrameId);
10816
+ this.animationFrameId = null;
10817
+ }
10818
+ }
10819
+ /**
10820
+ * Record a render call (called by wrapper during rendering)
10821
+ */
10822
+ recordRenderCall() {
10823
+ this.renderCallCount++;
10824
+ }
10825
+ /**
10826
+ * Record canvas operations (drawing calls, transforms, etc.)
10827
+ */
10828
+ recordCanvasOperation() {
10829
+ this.canvasOpCount++;
10830
+ }
10831
+ /**
10832
+ * Update node and link counts for efficiency calculations
10833
+ */
10834
+ updateCounts(nodeCount, linkCount) {
10835
+ this.nodeCount = nodeCount;
10836
+ this.linkCount = linkCount;
10837
+ }
10838
+ /**
10839
+ * Get current rendering metrics
10840
+ */
10841
+ getMetrics() {
10842
+ const now2 = performance.now();
10843
+ const currentFps = this.calculateCurrentFps();
10844
+ const averageFps = this.calculateAverageFps();
10845
+ const currentFrameTime = this.calculateCurrentFrameTime();
10846
+ const averageFrameTime = this.calculateAverageFrameTime();
10847
+ return {
10848
+ fps: currentFps,
10849
+ averageFps,
10850
+ frameTime: currentFrameTime,
10851
+ averageFrameTime,
10852
+ droppedFrames: this.droppedFrameCount,
10853
+ renderCalls: this.renderCallCount,
10854
+ canvasOperations: this.canvasOpCount,
10855
+ nodeCount: this.nodeCount,
10856
+ linkCount: this.linkCount,
10857
+ lastMeasurement: now2,
10858
+ renderingEfficiency: this.calculateEfficiency(averageFps, averageFrameTime),
10859
+ memoryUsage: this.getMemoryUsage()
10860
+ };
10861
+ }
10862
+ /**
10863
+ * Check if performance targets are being met
10864
+ */
10865
+ isPerformanceTargetMet() {
10866
+ const metrics = this.getMetrics();
10867
+ return metrics.averageFps >= this.targets.targetFps * 0.9 && // Allow 10% tolerance
10868
+ metrics.averageFrameTime <= this.targets.maxFrameTime * 1.1 && metrics.droppedFrames <= this.targets.maxDroppedFrames && metrics.renderingEfficiency >= this.targets.efficiencyThreshold;
10869
+ }
10870
+ /**
10871
+ * Get performance validation results
10872
+ */
10873
+ validatePerformance() {
10874
+ const metrics = this.getMetrics();
10875
+ const fpsTest = {
10876
+ expected: this.targets.targetFps,
10877
+ actual: metrics.averageFps,
10878
+ passed: metrics.averageFps >= this.targets.targetFps * 0.9
10879
+ };
10880
+ const frameTimeTest = {
10881
+ expected: this.targets.maxFrameTime,
10882
+ actual: metrics.averageFrameTime,
10883
+ passed: metrics.averageFrameTime <= this.targets.maxFrameTime * 1.1
10884
+ };
10885
+ const droppedFramesTest = {
10886
+ expected: this.targets.maxDroppedFrames,
10887
+ actual: metrics.droppedFrames,
10888
+ passed: metrics.droppedFrames <= this.targets.maxDroppedFrames
10889
+ };
10890
+ const efficiencyTest = {
10891
+ expected: this.targets.efficiencyThreshold,
10892
+ actual: metrics.renderingEfficiency,
10893
+ passed: metrics.renderingEfficiency >= this.targets.efficiencyThreshold
10894
+ };
10895
+ const results = {
10896
+ fpsTarget: fpsTest,
10897
+ frameTimeTarget: frameTimeTest,
10898
+ droppedFramesTarget: droppedFramesTest,
10899
+ efficiencyTarget: efficiencyTest
10900
+ };
10901
+ const passedTests = Object.values(results).filter((test) => test.passed).length;
10902
+ const overallScore = passedTests / Object.keys(results).length;
10903
+ const passed = overallScore >= 0.75;
10904
+ return {
10905
+ passed,
10906
+ results,
10907
+ overallScore
10908
+ };
10909
+ }
10910
+ /**
10911
+ * Reset performance counters
10912
+ */
10913
+ reset() {
10914
+ this.frames = [];
10915
+ this.frameTimes = [];
10916
+ this.renderCallCount = 0;
10917
+ this.canvasOpCount = 0;
10918
+ this.droppedFrameCount = 0;
10919
+ this.lastFrameTime = performance.now();
10920
+ }
10921
+ /**
10922
+ * Get rendering optimization recommendations
10923
+ */
10924
+ getOptimizationRecommendations() {
10925
+ const metrics = this.getMetrics();
10926
+ const recommendations = [];
10927
+ if (metrics.averageFps < this.targets.targetFps * 0.8) {
10928
+ recommendations.push("FPS is significantly below target - consider reducing node count or simplifying rendering");
10929
+ }
10930
+ if (metrics.averageFrameTime > this.targets.maxFrameTime * 1.5) {
10931
+ recommendations.push("Frame time is too high - optimize canvas operations or reduce complexity");
10932
+ }
10933
+ if (metrics.droppedFrames > this.targets.maxDroppedFrames * 2) {
10934
+ recommendations.push("Too many dropped frames - consider implementing frame skipping or LOD");
10935
+ }
10936
+ if (metrics.renderingEfficiency < 0.5) {
10937
+ recommendations.push("Low rendering efficiency - review canvas drawing operations");
10938
+ }
10939
+ if (metrics.canvasOperations / metrics.renderCalls > 1e3) {
10940
+ recommendations.push("High canvas operations per render - consider batching or caching");
10941
+ }
10942
+ if (recommendations.length === 0) {
10943
+ recommendations.push("Performance targets are being met - no optimizations needed");
10944
+ }
10945
+ return recommendations;
10946
+ }
10947
+ monitorFrame = () => {
10948
+ if (!this.isMonitoring) return;
10949
+ const currentTime = performance.now();
10950
+ const frameTime = currentTime - this.lastFrameTime;
10951
+ this.frameTimes.push(frameTime);
10952
+ if (this.frameTimes.length > this.maxSamples) {
10953
+ this.frameTimes.shift();
10954
+ }
10955
+ const fps = frameTime > 0 ? 1e3 / frameTime : 0;
10956
+ this.frames.push(fps);
10957
+ if (this.frames.length > this.maxSamples) {
10958
+ this.frames.shift();
10959
+ }
10960
+ if (frameTime > this.targets.maxFrameTime * 2) {
10961
+ this.droppedFrameCount++;
10962
+ }
10963
+ this.lastFrameTime = currentTime;
10964
+ this.animationFrameId = requestAnimationFrame(this.monitorFrame);
10965
+ };
10966
+ calculateCurrentFps() {
10967
+ if (this.frames.length === 0) return 0;
10968
+ return this.frames[this.frames.length - 1] || 0;
10969
+ }
10970
+ calculateAverageFps() {
10971
+ if (this.frames.length === 0) return 0;
10972
+ const sum2 = this.frames.reduce((a2, b) => a2 + b, 0);
10973
+ return sum2 / this.frames.length;
10974
+ }
10975
+ calculateCurrentFrameTime() {
10976
+ if (this.frameTimes.length === 0) return 0;
10977
+ return this.frameTimes[this.frameTimes.length - 1] || 0;
10978
+ }
10979
+ calculateAverageFrameTime() {
10980
+ if (this.frameTimes.length === 0) return 0;
10981
+ const sum2 = this.frameTimes.reduce((a2, b) => a2 + b, 0);
10982
+ return sum2 / this.frameTimes.length;
10983
+ }
10984
+ calculateEfficiency(fps, frameTime) {
10985
+ const fpsEfficiency = Math.min(fps / this.targets.targetFps, 1);
10986
+ const frameTimeEfficiency = Math.min(this.targets.maxFrameTime / frameTime, 1);
10987
+ return fpsEfficiency * 0.7 + frameTimeEfficiency * 0.3;
10988
+ }
10989
+ getMemoryUsage() {
10990
+ if (typeof performance.memory !== "undefined") {
10991
+ return performance.memory.usedJSHeapSize / 1024 / 1024;
10992
+ }
10993
+ return 0;
10994
+ }
10995
+ };
10996
+ var rendering_performance_monitor_default = RenderingPerformanceMonitor;
10997
+
10998
+ // src/force-graph-wrapper/core/canvas-optimizer.ts
10999
+ var CanvasOptimizer = class {
11000
+ canvas = null;
11001
+ context = null;
11002
+ imageDataCache = /* @__PURE__ */ new Map();
11003
+ pathCache = /* @__PURE__ */ new Map();
11004
+ renderQueue = [];
11005
+ isOptimizing = false;
11006
+ settings = {
11007
+ enableBatching: true,
11008
+ enableCaching: true,
11009
+ enableLOD: true,
11010
+ lodThreshold: 500,
11011
+ // Start LOD optimizations at 500+ nodes
11012
+ cacheSize: 100,
11013
+ batchSize: 50,
11014
+ cullingEnabled: true,
11015
+ cullingMargin: 100
11016
+ // pixels outside viewport to still render
11017
+ };
11018
+ viewport = {
11019
+ x: 0,
11020
+ y: 0,
11021
+ width: 800,
11022
+ height: 600,
11023
+ scale: 1
11024
+ };
11025
+ constructor(settings) {
11026
+ if (settings) {
11027
+ this.settings = { ...this.settings, ...settings };
11028
+ }
11029
+ }
11030
+ /**
11031
+ * Initialize optimizer with canvas element
11032
+ */
11033
+ initialize(canvas) {
11034
+ this.canvas = canvas;
11035
+ this.context = canvas.getContext("2d");
11036
+ this.updateViewport();
11037
+ }
11038
+ /**
11039
+ * Clean up all cached data and references
11040
+ */
11041
+ destroy() {
11042
+ console.log("\u{1F9F9} Cleaning up CanvasOptimizer...");
11043
+ this.imageDataCache.clear();
11044
+ this.pathCache.clear();
11045
+ this.renderQueue.length = 0;
11046
+ this.canvas = null;
11047
+ this.context = null;
11048
+ this.isOptimizing = false;
11049
+ console.log("\u2705 CanvasOptimizer cleanup completed");
11050
+ }
11051
+ /**
11052
+ * Update viewport information for culling calculations
11053
+ */
11054
+ updateViewport(x3, y3, scale) {
11055
+ if (!this.canvas) return;
11056
+ this.viewport = {
11057
+ x: x3 ?? this.viewport.x,
11058
+ y: y3 ?? this.viewport.y,
11059
+ width: this.canvas.width,
11060
+ height: this.canvas.height,
11061
+ scale: scale ?? this.viewport.scale
11062
+ };
11063
+ }
11064
+ /**
11065
+ * Optimize node rendering based on current settings and viewport
11066
+ */
11067
+ optimizeNodeRendering(nodes, renderFunction) {
11068
+ if (!this.context || !this.settings.enableLOD) {
11069
+ nodes.forEach((node) => renderFunction(node, 0));
11070
+ return;
11071
+ }
11072
+ const nodeCount = nodes.length;
11073
+ const shouldUseLOD = nodeCount > this.settings.lodThreshold;
11074
+ const lodLevel = shouldUseLOD ? this.calculateLODLevel(nodeCount, this.viewport.scale) : 0;
11075
+ if (this.settings.enableBatching) {
11076
+ this.batchRender(nodes, (node) => renderFunction(node, lodLevel));
11077
+ } else {
11078
+ const visibleNodes = this.settings.cullingEnabled ? this.cullNodes(nodes) : nodes;
11079
+ visibleNodes.forEach((node) => renderFunction(node, lodLevel));
11080
+ }
11081
+ }
11082
+ /**
11083
+ * Optimize link rendering with batching and culling
11084
+ */
11085
+ optimizeLinkRendering(links, renderFunction) {
11086
+ if (!this.context) {
11087
+ links.forEach((link) => renderFunction(link, 0));
11088
+ return;
11089
+ }
11090
+ const linkCount = links.length;
11091
+ const lodLevel = linkCount > this.settings.lodThreshold ? this.calculateLODLevel(linkCount, this.viewport.scale) : 0;
11092
+ if (this.settings.enableBatching) {
11093
+ this.batchRender(links, (link) => renderFunction(link, lodLevel));
11094
+ } else {
11095
+ const visibleLinks = this.settings.cullingEnabled ? this.cullLinks(links) : links;
11096
+ visibleLinks.forEach((link) => renderFunction(link, lodLevel));
11097
+ }
11098
+ }
11099
+ /**
11100
+ * Cache commonly used drawing operations
11101
+ */
11102
+ getCachedPath(key, createPath) {
11103
+ if (!this.settings.enableCaching) {
11104
+ return createPath();
11105
+ }
11106
+ if (this.pathCache.has(key)) {
11107
+ return this.pathCache.get(key);
11108
+ }
11109
+ const path = createPath();
11110
+ if (this.pathCache.size >= this.settings.cacheSize) {
11111
+ const firstKey = this.pathCache.keys().next().value;
11112
+ if (firstKey !== void 0) {
11113
+ this.pathCache.delete(firstKey);
11114
+ }
11115
+ }
11116
+ this.pathCache.set(key, path);
11117
+ return path;
11118
+ }
11119
+ /**
11120
+ * Cache image data for complex shapes or textures
11121
+ */
11122
+ getCachedImageData(key, createImageData) {
11123
+ if (!this.settings.enableCaching) {
11124
+ return createImageData();
11125
+ }
11126
+ if (this.imageDataCache.has(key)) {
11127
+ return this.imageDataCache.get(key);
11128
+ }
11129
+ const imageData = createImageData();
11130
+ if (this.imageDataCache.size >= this.settings.cacheSize) {
11131
+ const firstKey = this.imageDataCache.keys().next().value;
11132
+ if (firstKey !== void 0) {
11133
+ this.imageDataCache.delete(firstKey);
11134
+ }
11135
+ }
11136
+ this.imageDataCache.set(key, imageData);
11137
+ return imageData;
11138
+ }
11139
+ /**
11140
+ * Batch render operations for better performance
11141
+ */
11142
+ batchRender(items, renderFunction) {
11143
+ if (!this.context) return;
11144
+ for (let i = 0; i < items.length; i += this.settings.batchSize) {
11145
+ const batch = items.slice(i, i + this.settings.batchSize);
11146
+ const visibleItems = this.settings.cullingEnabled ? batch.filter((item) => this.isItemVisible(item)) : batch;
11147
+ this.context.save();
11148
+ visibleItems.forEach(renderFunction);
11149
+ this.context.restore();
11150
+ }
11151
+ }
11152
+ /**
11153
+ * Calculate appropriate LOD level based on node count and zoom
11154
+ */
11155
+ calculateLODLevel(itemCount, scale) {
11156
+ if (itemCount < this.settings.lodThreshold) return 0;
11157
+ if (scale > 2) return 0;
11158
+ if (scale > 1) return 1;
11159
+ if (itemCount < 1e3) return 1;
11160
+ if (itemCount < 2e3) return 2;
11161
+ return 3;
11162
+ }
11163
+ /**
11164
+ * Cull nodes outside the visible viewport
11165
+ */
11166
+ cullNodes(nodes) {
11167
+ return nodes.filter((node) => this.isNodeVisible(node));
11168
+ }
11169
+ /**
11170
+ * Cull links outside the visible viewport
11171
+ */
11172
+ cullLinks(links) {
11173
+ return links.filter((link) => this.isLinkVisible(link));
11174
+ }
11175
+ /**
11176
+ * Check if a node is visible in the current viewport
11177
+ */
11178
+ isNodeVisible(node) {
11179
+ if (!node.x || !node.y) return true;
11180
+ const margin = this.settings.cullingMargin;
11181
+ const { x: x3, y: y3, width, height, scale } = this.viewport;
11182
+ const worldX = (node.x - x3) * scale;
11183
+ const worldY = (node.y - y3) * scale;
11184
+ return worldX >= -margin && worldX <= width + margin && worldY >= -margin && worldY <= height + margin;
11185
+ }
11186
+ /**
11187
+ * Check if a link is visible in the current viewport
11188
+ */
11189
+ isLinkVisible(link) {
11190
+ const source = link.source;
11191
+ const target = link.target;
11192
+ if (!source || !target) return true;
11193
+ return this.isNodeVisible(source) || this.isNodeVisible(target) || this.lineIntersectsViewport(source, target);
11194
+ }
11195
+ /**
11196
+ * Generic item visibility check
11197
+ */
11198
+ isItemVisible(item) {
11199
+ const itemWithXY = item;
11200
+ const itemWithSourceTarget = item;
11201
+ if (itemWithXY.x !== void 0 && itemWithXY.y !== void 0) {
11202
+ return this.isNodeVisible(itemWithXY);
11203
+ }
11204
+ if (itemWithSourceTarget.source && itemWithSourceTarget.target) {
11205
+ return this.isLinkVisible(itemWithSourceTarget);
11206
+ }
11207
+ return true;
11208
+ }
11209
+ /**
11210
+ * Check if a line intersects with the viewport
11211
+ */
11212
+ lineIntersectsViewport(source, target) {
11213
+ const { x: x3, y: y3, width, height } = this.viewport;
11214
+ const margin = this.settings.cullingMargin;
11215
+ if (source.x === void 0 || source.y === void 0 || target.x === void 0 || target.y === void 0) {
11216
+ return true;
11217
+ }
11218
+ const minX = Math.min(source.x, target.x);
11219
+ const maxX = Math.max(source.x, target.x);
11220
+ const minY = Math.min(source.y, target.y);
11221
+ const maxY = Math.max(source.y, target.y);
11222
+ return !(maxX < x3 - margin || minX > x3 + width + margin || maxY < y3 - margin || minY > y3 + height + margin);
11223
+ }
11224
+ /**
11225
+ * Clear all caches
11226
+ */
11227
+ clearCaches() {
11228
+ this.imageDataCache.clear();
11229
+ this.pathCache.clear();
11230
+ }
11231
+ /**
11232
+ * Get cache statistics
11233
+ */
11234
+ getCacheStats() {
11235
+ return {
11236
+ pathCacheSize: this.pathCache.size,
11237
+ imageCacheSize: this.imageDataCache.size,
11238
+ pathCacheHitRate: 0,
11239
+ // Would need hit/miss tracking
11240
+ imageCacheHitRate: 0
11241
+ // Would need hit/miss tracking
11242
+ };
11243
+ }
11244
+ /**
11245
+ * Update optimization settings
11246
+ */
11247
+ updateSettings(newSettings) {
11248
+ this.settings = { ...this.settings, ...newSettings };
11249
+ }
11250
+ /**
11251
+ * Get current optimization settings
11252
+ */
11253
+ getSettings() {
11254
+ return { ...this.settings };
11255
+ }
11256
+ };
11257
+ var canvas_optimizer_default = CanvasOptimizer;
11258
+
11259
+ // src/force-graph-wrapper/core/force-graph-wrapper.ts
11260
+ var ForceGraphWrapper = class {
11261
+ // Core force-graph instance (strategic use of 'unknown' for library integration)
11262
+ forceGraph = null;
11263
+ // Container element
11264
+ container;
11265
+ // Performance monitor
11266
+ performanceMonitor;
11267
+ // Step 4: Advanced rendering performance monitoring
11268
+ renderingMonitor = null;
11269
+ canvasOptimizer = null;
11270
+ // Configuration
11271
+ config;
11272
+ // State tracking
11273
+ isInitialized = false;
11274
+ isDestroyed = false;
11275
+ isRenderingOptimized = false;
11276
+ enableVerboseLogging = false;
11277
+ // Disable by default for performance
11278
+ // Page visibility handling for deterministic layouts
11279
+ wasInitializedHidden = false;
11280
+ visibilityChangeHandler = null;
11281
+ // Element viewport visibility handling for deterministic physics
11282
+ wasInitializedOutOfView = false;
11283
+ intersectionObserver = null;
11284
+ // Web Worker physics for all graphs (deterministic layouts)
11285
+ webWorkerPhysicsCompleted = false;
11286
+ physicsTimeoutId = void 0;
11287
+ // Cached container dimensions when visible
11288
+ lastKnownDimensions = null;
11289
+ // Deferred fitView parameters for library-level handling
11290
+ deferredFitViewParams = null;
11291
+ pendingFitView = false;
11292
+ // Performance testing interval tracking for cleanup
11293
+ performanceTestInterval = null;
11294
+ // Canvas event listener for cleanup
11295
+ canvasClickHandler = null;
11296
+ // Export animation timeout for cleanup
11297
+ exportTimeoutId = null;
11298
+ // Graph controls UI
11299
+ controlsInstance = null;
11300
+ // Graph legends UI
11301
+ legendsInstance = null;
11302
+ /**
11303
+ * Helper method to safely cast and call methods on the force graph instance
11304
+ */
11305
+ getGraphInstance() {
11306
+ return this.forceGraph;
11307
+ }
11308
+ /**
11309
+ * Constructor - Step 1 Performance Test
11310
+ * Target: < 1ms creation time
11311
+ */
11312
+ constructor(container, config) {
11313
+ const creationStart = performance.now();
11314
+ if (!container || !(container instanceof HTMLElement)) {
11315
+ throw new Error("Invalid container: must be an HTMLElement");
11316
+ }
11317
+ this.container = container;
11318
+ this.performanceMonitor = new PerformanceMonitor();
11319
+ if (config?.enablePerformanceMonitoring) {
11320
+ this.renderingMonitor = new rendering_performance_monitor_default({
11321
+ targetFps: 60,
11322
+ maxFrameTime: 16.67,
11323
+ maxDroppedFrames: 5,
11324
+ efficiencyThreshold: 0.8
11325
+ });
11326
+ this.canvasOptimizer = new canvas_optimizer_default({
11327
+ enableBatching: false,
11328
+ // Keep it simple like react-force-graph
11329
+ enableCaching: false,
11330
+ enableLOD: false,
11331
+ lodThreshold: 500,
11332
+ cullingEnabled: false
11333
+ });
11334
+ }
11335
+ this.config = {
11336
+ container,
11337
+ width: container.clientWidth || 400,
11338
+ height: container.clientHeight || 300,
11339
+ backgroundColor: "#ffffff",
11340
+ enablePerformanceMonitoring: false,
11341
+ // Disable by default for better performance
11342
+ controls: {
11343
+ enabled: true,
11344
+ position: "bottom-left",
11345
+ orientation: "vertical"
11346
+ },
11347
+ legends: {
11348
+ enabled: true,
11349
+ position: "top-right",
11350
+ maxItems: 10
11351
+ },
11352
+ ...config
11353
+ };
11354
+ this.enableVerboseLogging = config?.enablePerformanceMonitoring === true && config?.performanceTargets?.methodCall !== void 0;
11355
+ this.setupPageVisibilityHandling();
11356
+ this.setupDeterministicPhysics();
11357
+ const creationTime = performance.now() - creationStart;
11358
+ this.performanceMonitor.setMetric("creation", creationTime);
11359
+ if (this.enableVerboseLogging) {
11360
+ this.validateCreationPerformance(creationTime);
11361
+ }
11362
+ }
11363
+ /**
11364
+ * Step 1 Performance Validation
11365
+ */
11366
+ validateCreationPerformance(_creationTime) {
11367
+ }
11368
+ /**
11369
+ * Setup automatic page visibility handling for deterministic layouts
11370
+ * Ensures consistent graph layouts regardless of tab visibility during initialization
11371
+ */
11372
+ setupPageVisibilityHandling() {
11373
+ if (this.config.handlePageVisibility === false) {
11374
+ return;
11375
+ }
11376
+ this.wasInitializedHidden = document.hidden;
11377
+ this.visibilityChangeHandler = () => {
11378
+ if (!document.hidden && this.isInitialized) {
11379
+ if (this.wasInitializedHidden) {
11380
+ if (this.enableVerboseLogging) {
11381
+ console.log("\u{1F504} Restarting simulation due to visibility change for layout consistency");
11382
+ }
11383
+ this.getGraphInstance().d3ReheatSimulation();
11384
+ this.wasInitializedHidden = false;
11385
+ }
11386
+ if (this.pendingFitView) {
11387
+ setTimeout(() => {
11388
+ if (this.enableVerboseLogging) {
11389
+ console.log("\u{1F3AF} Executing deferred fitView after visibility change");
11390
+ }
11391
+ this.zoomToFit(40, 300);
11392
+ }, 100);
11393
+ }
11394
+ }
11395
+ };
11396
+ document.addEventListener("visibilitychange", this.visibilityChangeHandler);
11397
+ }
11398
+ /**
11399
+ * Cleanup page visibility handling
11400
+ */
11401
+ cleanupPageVisibilityHandling() {
11402
+ if (this.visibilityChangeHandler) {
11403
+ document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
11404
+ this.visibilityChangeHandler = null;
11405
+ }
11406
+ }
11407
+ /**
11408
+ * Setup deterministic physics handling for viewport visibility
11409
+ * Ensures consistent physics timing regardless of element visibility
11410
+ */
11411
+ setupDeterministicPhysics() {
11412
+ if (this.config.deterministicLayout === false) {
11413
+ return;
11414
+ }
11415
+ const isInViewport = this.isElementInViewport(this.container);
11416
+ this.wasInitializedOutOfView = !isInViewport;
11417
+ if (this.enableVerboseLogging) {
11418
+ const rect = this.container.getBoundingClientRect();
11419
+ console.log(`\u{1F4CD} Graph initialized:`, {
11420
+ wasInitializedOutOfView: this.wasInitializedOutOfView,
11421
+ isInViewport,
11422
+ containerDimensions: `${this.container.clientWidth}x${this.container.clientHeight}`,
11423
+ boundingRect: {
11424
+ top: rect.top,
11425
+ bottom: rect.bottom,
11426
+ left: rect.left,
11427
+ right: rect.right
11428
+ },
11429
+ windowSize: `${window.innerWidth}x${window.innerHeight}`
11430
+ });
11431
+ }
11432
+ if (typeof IntersectionObserver !== "undefined") {
11433
+ const scrollableParent = this.findScrollableParent(this.container);
11434
+ this.intersectionObserver = new IntersectionObserver((entries) => {
11435
+ entries.forEach((entry) => {
11436
+ if (entry.isIntersecting && this.isInitialized) {
11437
+ if (this.wasInitializedOutOfView) {
11438
+ if (this.enableVerboseLogging) {
11439
+ console.log("\u{1F504} Element entered viewport - ensuring consistent physics");
11440
+ }
11441
+ if (this.physicsTimeoutId) {
11442
+ window.clearTimeout(this.physicsTimeoutId);
11443
+ this.physicsTimeoutId = void 0;
11444
+ }
11445
+ if (!this.webWorkerPhysicsCompleted) {
11446
+ this.runWebWorkerPhysics();
11447
+ }
11448
+ this.wasInitializedOutOfView = false;
11449
+ }
11450
+ if (this.pendingFitView) {
11451
+ setTimeout(() => {
11452
+ if (this.pendingFitView) {
11453
+ if (this.enableVerboseLogging) {
11454
+ console.log("\u{1F3AF} Executing deferred fitView after entering viewport");
11455
+ }
11456
+ this.zoomToFit(40, 300);
11457
+ }
11458
+ }, 100);
11459
+ }
11460
+ }
11461
+ });
11462
+ }, {
11463
+ root: scrollableParent,
11464
+ // Use found scrollable parent as root
11465
+ threshold: 0.1,
11466
+ // Trigger when 10% visible
11467
+ rootMargin: "100px"
11468
+ // Larger margin like Angular component
11469
+ });
11470
+ this.intersectionObserver.observe(this.container);
11471
+ }
11472
+ }
11473
+ /**
11474
+ * Find the scrollable parent container for intersection observer
11475
+ * Similar to Angular component approach but more generic
11476
+ */
11477
+ findScrollableParent(element) {
11478
+ let current = element.parentElement;
11479
+ while (current && current !== document.body) {
11480
+ const style = window.getComputedStyle(current);
11481
+ const overflow = style.overflow + style.overflowY + style.overflowX;
11482
+ if (/(auto|scroll)/.test(overflow) && (current.scrollHeight > current.clientHeight || current.scrollWidth > current.clientWidth)) {
11483
+ if (this.enableVerboseLogging) {
11484
+ console.log("\u{1F4CD} Found scrollable parent:", current.className || current.tagName);
11485
+ }
11486
+ return current;
11487
+ }
11488
+ current = current.parentElement;
11489
+ }
11490
+ if (this.enableVerboseLogging) {
11491
+ console.log("\u{1F4CD} No scrollable parent found, using viewport as root");
11492
+ }
11493
+ return null;
11494
+ }
11495
+ /**
11496
+ * Setup deferred fitView mechanism using IntersectionObserver
11497
+ * This handles library-level viewport-aware fitView
11498
+ */
11499
+ setupDeferredFitView() {
11500
+ if (this.intersectionObserver || !this.container || !this.deferredFitViewParams) {
11501
+ return;
11502
+ }
11503
+ if (this.enableVerboseLogging) {
11504
+ console.log("\u{1F3AF} Setting up library-level deferred fitView");
11505
+ }
11506
+ this.intersectionObserver = new IntersectionObserver((entries) => {
11507
+ entries.forEach((entry) => {
11508
+ if (entry.isIntersecting && this.pendingFitView && this.deferredFitViewParams) {
11509
+ if (this.enableVerboseLogging) {
11510
+ console.log("\u{1F3AF} Library executing deferred fitView (now visible)");
11511
+ }
11512
+ const { padding, duration } = this.deferredFitViewParams;
11513
+ this.pendingFitView = false;
11514
+ this.deferredFitViewParams = null;
11515
+ if (this.intersectionObserver) {
11516
+ this.intersectionObserver.disconnect();
11517
+ this.intersectionObserver = null;
11518
+ }
11519
+ requestAnimationFrame(() => {
11520
+ this.executeFitViewNow(padding, duration);
11521
+ });
11522
+ }
11523
+ });
11524
+ }, {
11525
+ threshold: 0.1,
11526
+ rootMargin: "50px"
11527
+ // Start checking slightly before fully visible
11528
+ });
11529
+ this.intersectionObserver.observe(this.container);
11530
+ }
11531
+ /**
11532
+ * Execute fitView immediately (bypass visibility checks)
11533
+ */
11534
+ executeFitViewNow(padding, duration) {
11535
+ if (!this.forceGraph || !this.container) return;
11536
+ const containerWidth = this.container.clientWidth;
11537
+ const containerHeight = this.container.clientHeight;
11538
+ if (containerWidth === 0 || containerHeight === 0) {
11539
+ if (this.enableVerboseLogging) {
11540
+ console.warn("\u26A0\uFE0F Still no valid dimensions for deferred fitView");
11541
+ }
11542
+ return;
11543
+ }
11544
+ this.cacheContainerDimensions();
11545
+ const bbox = this.getGraphInstance().getGraphBbox();
11546
+ const width = bbox.x[1] - bbox.x[0];
11547
+ const height = bbox.y[1] - bbox.y[0];
11548
+ const centerX = (bbox.x[0] + bbox.x[1]) / 2;
11549
+ const centerY = (bbox.y[0] + bbox.y[1]) / 2;
11550
+ const viewportWidth = containerWidth - padding * 2;
11551
+ const viewportHeight = containerHeight - padding * 2;
11552
+ if (width > 0 && height > 0) {
11553
+ const scale = Math.min(viewportWidth / width, viewportHeight / height);
11554
+ this.getGraphInstance().centerAt(centerX, centerY, duration);
11555
+ this.getGraphInstance().zoom(scale, duration);
11556
+ if (this.enableVerboseLogging) {
11557
+ console.log("\u2705 Library deferred fitView executed successfully");
11558
+ }
11559
+ }
11560
+ }
11561
+ /**
11562
+ * Cleanup deferred fitView intersection observer
11563
+ */
11564
+ cleanupDeferredFitView() {
11565
+ if (this.intersectionObserver) {
11566
+ this.intersectionObserver.disconnect();
11567
+ this.intersectionObserver = null;
11568
+ }
11569
+ this.deferredFitViewParams = null;
11570
+ this.pendingFitView = false;
11571
+ }
11572
+ /**
11573
+ * Cleanup performance test interval
11574
+ */
11575
+ cleanupPerformanceTest() {
11576
+ if (this.performanceTestInterval) {
11577
+ clearInterval(this.performanceTestInterval);
11578
+ this.performanceTestInterval = null;
11579
+ }
11580
+ }
11581
+ /**
11582
+ * Cleanup export animation timeout
11583
+ */
11584
+ cleanupExportTimeout() {
11585
+ if (this.exportTimeoutId) {
11586
+ clearTimeout(this.exportTimeoutId);
11587
+ this.exportTimeoutId = null;
11588
+ }
11589
+ }
11590
+ /**
11591
+ * Cleanup canvas event listeners
11592
+ */
11593
+ cleanupCanvasEventListeners() {
11594
+ if (this.canvasClickHandler && this.container) {
11595
+ const canvas = this.container.querySelector("canvas");
11596
+ if (canvas) {
11597
+ canvas.removeEventListener("click", this.canvasClickHandler);
11598
+ }
11599
+ this.canvasClickHandler = null;
11600
+ }
11601
+ }
11602
+ /**
11603
+ * Check if element is currently visible (even partially) in viewport
11604
+ */
11605
+ isElementInViewport(element) {
11606
+ const rect = element.getBoundingClientRect();
11607
+ const windowHeight = window.innerHeight || document.documentElement.clientHeight;
11608
+ const windowWidth = window.innerWidth || document.documentElement.clientWidth;
11609
+ return rect.bottom > 0 && // Not above viewport
11610
+ rect.right > 0 && // Not to the left of viewport
11611
+ rect.top < windowHeight && // Not below viewport
11612
+ rect.left < windowWidth;
11613
+ }
11614
+ /**
11615
+ * Cache container dimensions when they're valid (non-zero)
11616
+ */
11617
+ cacheContainerDimensions() {
11618
+ const width = this.container.clientWidth;
11619
+ const height = this.container.clientHeight;
11620
+ if (width > 0 && height > 0) {
11621
+ this.lastKnownDimensions = { width, height };
11622
+ if (this.enableVerboseLogging) {
11623
+ console.log("\u{1F4D0} Cached container dimensions:", this.lastKnownDimensions);
11624
+ }
11625
+ }
11626
+ }
11627
+ /**
11628
+ * Check if container has valid dimensions and cache them
11629
+ */
11630
+ hasValidDimensions() {
11631
+ const width = this.container.clientWidth;
11632
+ const height = this.container.clientHeight;
11633
+ if (width > 0 && height > 0) {
11634
+ this.cacheContainerDimensions();
11635
+ return true;
11636
+ }
11637
+ return false;
11638
+ }
11639
+ /**
11640
+ * Run deterministic physics using Web Worker for off-screen graphs only
11641
+ */
11642
+ async runWebWorkerPhysics() {
11643
+ if (!this.config.graphData || !this.container || this.webWorkerPhysicsCompleted) return;
11644
+ const { nodes, links } = this.config.graphData;
11645
+ if (!nodes || nodes.length === 0) return;
11646
+ if (this.physicsTimeoutId) {
11647
+ window.clearTimeout(this.physicsTimeoutId);
11648
+ this.physicsTimeoutId = void 0;
11649
+ }
11650
+ try {
11651
+ if (!physicsWorkerManager.isAvailable()) {
11652
+ if (this.enableVerboseLogging) {
11653
+ console.warn("\u26A0\uFE0F Web Worker not available, using main thread physics");
11654
+ }
11655
+ return;
11656
+ }
11657
+ const containerWidth = this.container.clientWidth || this.config.width || 800;
11658
+ const containerHeight = this.container.clientHeight || this.config.height || 600;
11659
+ const config = {
11660
+ nodes: nodes.map((node) => ({ ...node })),
11661
+ links: links ? links.map((link) => ({ ...link })) : [],
11662
+ width: containerWidth,
11663
+ height: containerHeight,
11664
+ maxTicks: 300,
11665
+ // Deterministic tick count for off-screen graphs
11666
+ alphaDecay: this.config.d3AlphaDecay || 0.0228,
11667
+ // Use wrapper config or default
11668
+ velocityDecay: this.config.d3VelocityDecay || 0.4,
11669
+ // Use wrapper config or default
11670
+ forces: {}
11671
+ // Let worker use force-graph defaults
11672
+ };
11673
+ if (this.enableVerboseLogging) {
11674
+ console.log("\u{1F3AF} Web Worker physics config:", {
11675
+ containerDimensions: `${containerWidth}x${containerHeight}`,
11676
+ configDimensions: `${this.config.width || "auto"}x${this.config.height || "auto"}`,
11677
+ nodeCount: nodes.length
11678
+ });
11679
+ }
11680
+ if (this.enableVerboseLogging) {
11681
+ console.log("\u{1F504} Running Web Worker physics for ALL graphs (visible & hidden)...");
11682
+ }
11683
+ const initialized = await physicsWorkerManager.initialize(config);
11684
+ if (!initialized) {
11685
+ throw new Error("Failed to initialize Web Worker physics");
11686
+ }
11687
+ const finalNodes = await physicsWorkerManager.runSimulation();
11688
+ this.applyWebWorkerResults(finalNodes);
11689
+ this.webWorkerPhysicsCompleted = true;
11690
+ if (this.enableVerboseLogging) {
11691
+ console.log(`\u2705 Web Worker physics completed for ALL graphs with ${finalNodes.length} nodes`);
11692
+ }
11693
+ } catch (error) {
11694
+ if (this.enableVerboseLogging) {
11695
+ console.warn("\u26A0\uFE0F Web Worker physics failed:", error);
11696
+ }
11697
+ }
11698
+ }
11699
+ /**
11700
+ * Apply Web Worker results to force-graph instance WITHOUT disrupting interactions
11701
+ */
11702
+ applyWebWorkerResults(physicsNodes) {
11703
+ if (!this.forceGraph || !this.config.graphData?.nodes) return;
11704
+ const nodeMap = new Map(physicsNodes.map((node) => [node.id, node]));
11705
+ this.config.graphData.nodes.forEach((node) => {
11706
+ const physicsNode = nodeMap.get(node.id);
11707
+ if (physicsNode) {
11708
+ if (typeof node.fx === "undefined") {
11709
+ node.x = physicsNode.x;
11710
+ node.y = physicsNode.y;
11711
+ }
11712
+ }
11713
+ });
11714
+ if (this.forceGraph) {
11715
+ const graphInstance = this.getGraphInstance();
11716
+ graphInstance.graphData(this.config.graphData);
11717
+ if (!document.hidden && this.isElementInViewport(this.container)) {
11718
+ graphInstance.d3ReheatSimulation();
11719
+ }
11720
+ }
11721
+ this.triggerKapsuleUpdate();
11722
+ if (this.enableVerboseLogging) {
11723
+ console.log("\u2705 Applied Web Worker results without disrupting interactions");
11724
+ }
11725
+ }
11726
+ /**
11727
+ * Cleanup deterministic physics handling
11728
+ */
11729
+ cleanupDeterministicPhysics() {
11730
+ if (this.physicsTimeoutId) {
11731
+ window.clearTimeout(this.physicsTimeoutId);
11732
+ this.physicsTimeoutId = void 0;
11733
+ }
11734
+ if (this.intersectionObserver) {
11735
+ this.intersectionObserver.disconnect();
11736
+ this.intersectionObserver = null;
11737
+ }
11738
+ }
11739
+ /**
11740
+ * Initialize the underlying force-graph instance
11741
+ * Step 2: Data loading with performance validation
11742
+ * Target: Linear scaling, < 10ms per 1000 nodes
11743
+ */
11744
+ initializeForceGraph() {
11745
+ if (this.isInitialized || this.isDestroyed) {
11746
+ return;
11747
+ }
11748
+ const initStart = performance.now();
11749
+ try {
11750
+ this.forceGraph = ForceGraph()(this.container);
11751
+ if (!this.forceGraph) {
11752
+ throw new Error("Failed to create force-graph instance");
11753
+ }
11754
+ this.applyConfiguration();
11755
+ this.isInitialized = true;
11756
+ if (this.config.enablePerformanceMonitoring) {
11757
+ this.setupRenderingMonitoring();
11758
+ }
11759
+ const initTime = performance.now() - initStart;
11760
+ this.performanceMonitor.setMetric("firstRender", initTime);
11761
+ if (this.config.enablePerformanceMonitoring) {
11762
+ this.validateInitializationPerformance(initTime);
11763
+ }
11764
+ } catch (error) {
11765
+ this.isInitialized = false;
11766
+ throw new Error(`Failed to initialize force-graph: ${error}`);
11767
+ }
11768
+ }
11769
+ /**
11770
+ * Apply configuration to force-graph instance
11771
+ * Step 2: Performance-optimized configuration application
11772
+ */
11773
+ applyConfiguration() {
11774
+ if (!this.forceGraph || !this.config) return;
11775
+ const graph = this.getGraphInstance();
11776
+ graph.width(this.config.width || 400).height(this.config.height || 300).backgroundColor(this.config.backgroundColor || "#ffffff").autoPauseRedraw(this.config.autoPauseRedraw ?? true);
11777
+ if (this.config.d3AlphaDecay !== void 0) {
11778
+ graph.d3AlphaDecay(this.config.d3AlphaDecay);
11779
+ }
11780
+ if (this.config.d3VelocityDecay !== void 0) {
11781
+ graph.d3VelocityDecay(this.config.d3VelocityDecay);
11782
+ }
11783
+ if (this.config.d3AlphaMin !== void 0) {
11784
+ graph.d3AlphaMin(this.config.d3AlphaMin);
11785
+ }
11786
+ if (this.config.cooldownTime !== void 0) {
11787
+ graph.cooldownTime(this.config.cooldownTime);
11788
+ }
11789
+ if (this.config.cooldownTicks !== void 0) {
11790
+ graph.cooldownTicks(this.config.cooldownTicks);
11791
+ }
11792
+ if (this.config.nodeColor) {
11793
+ graph.nodeColor(this.config.nodeColor);
11794
+ }
11795
+ if (this.config.nodeVal) {
11796
+ graph.nodeVal(this.config.nodeVal);
11797
+ }
11798
+ if (this.config.nodeRelSize !== void 0) {
11799
+ graph.nodeRelSize(this.config.nodeRelSize);
11800
+ }
11801
+ if (this.config.nodeLabel) {
11802
+ graph.nodeLabel(this.config.nodeLabel);
11803
+ }
11804
+ if (this.config.nodeVisibility) {
11805
+ graph.nodeVisibility(this.config.nodeVisibility);
11806
+ }
11807
+ if (this.config.nodeCanvasObjectMode) {
11808
+ graph.nodeCanvasObjectMode(this.config.nodeCanvasObjectMode);
11809
+ }
11810
+ if (this.config.linkColor) {
11811
+ graph.linkColor(this.config.linkColor);
11812
+ }
11813
+ if (this.config.linkWidth) {
11814
+ graph.linkWidth(this.config.linkWidth);
11815
+ }
11816
+ if (this.config.linkLabel) {
11817
+ graph.linkLabel(this.config.linkLabel);
11818
+ }
11819
+ if (this.config.linkVisibility) {
11820
+ graph.linkVisibility(this.config.linkVisibility);
11821
+ }
11822
+ if (this.config.linkDirectionalArrowLength !== void 0) {
11823
+ graph.linkDirectionalArrowLength(this.config.linkDirectionalArrowLength);
11824
+ }
11825
+ if (this.config.linkDirectionalArrowColor) {
11826
+ graph.linkDirectionalArrowColor(this.config.linkDirectionalArrowColor);
11827
+ }
11828
+ if (this.config.linkDirectionalArrowRelPos !== void 0) {
11829
+ graph.linkDirectionalArrowRelPos(this.config.linkDirectionalArrowRelPos);
11830
+ }
11831
+ if (this.config.linkDirectionalParticles !== void 0) {
11832
+ graph.linkDirectionalParticles(this.config.linkDirectionalParticles);
11833
+ }
11834
+ if (this.config.linkDirectionalParticleSpeed !== void 0) {
11835
+ graph.linkDirectionalParticleSpeed(this.config.linkDirectionalParticleSpeed);
11836
+ }
11837
+ if (this.config.linkDirectionalParticleWidth !== void 0) {
11838
+ graph.linkDirectionalParticleWidth(this.config.linkDirectionalParticleWidth);
11839
+ }
11840
+ if (this.config.linkDirectionalParticleColor) {
11841
+ graph.linkDirectionalParticleColor(this.config.linkDirectionalParticleColor);
11842
+ }
11843
+ if (this.config.linkCurvature !== void 0) {
11844
+ graph.linkCurvature(this.config.linkCurvature);
11845
+ }
11846
+ if (this.config.linkCanvasObjectMode) {
11847
+ graph.linkCanvasObjectMode(this.config.linkCanvasObjectMode);
11848
+ }
11849
+ if (this.config.enableNodeDrag !== void 0) {
11850
+ graph.enableNodeDrag(this.config.enableNodeDrag);
11851
+ }
11852
+ if (this.config.enableZoomInteraction !== void 0) {
11853
+ graph.enableZoomInteraction(this.config.enableZoomInteraction);
11854
+ }
11855
+ if (this.config.enablePanInteraction !== void 0) {
11856
+ graph.enablePanInteraction(this.config.enablePanInteraction);
11857
+ }
11858
+ if (this.config.enablePerformanceMonitoring) {
11859
+ this.setupRenderingMonitoring();
11860
+ }
11861
+ if (this.config.onNodeClick) {
11862
+ graph.onNodeClick(this.config.onNodeClick);
11863
+ }
11864
+ if (this.config.onNodeHover) {
11865
+ graph.onNodeHover(this.config.onNodeHover);
11866
+ }
11867
+ if (this.config.onLinkClick) {
11868
+ graph.onLinkClick(this.config.onLinkClick);
11869
+ }
11870
+ if (this.config.onLinkHover) {
11871
+ graph.onLinkHover(this.config.onLinkHover);
11872
+ }
11873
+ if (this.config.onRenderFramePre) {
11874
+ graph.onRenderFramePre(this.config.onRenderFramePre);
11875
+ }
11876
+ if (this.config.onRenderFramePost) {
11877
+ graph.onRenderFramePost(this.config.onRenderFramePost);
11878
+ }
11879
+ if (this.config.onEngineTick) {
11880
+ graph.onEngineTick(this.config.onEngineTick);
11881
+ }
11882
+ if (this.config.onEngineStop) {
11883
+ graph.onEngineStop(this.config.onEngineStop);
11884
+ }
11885
+ }
11886
+ /**
11887
+ * Step 2 Performance Validation for Initialization
11888
+ */
11889
+ validateInitializationPerformance(_initTime) {
11890
+ }
11891
+ /**
11892
+ * Validate data loading performance based on node/link count
11893
+ * Step 2: Linear scaling validation
11894
+ */
11895
+ validateDataLoadingPerformance(_loadTime, nodeCount, linkCount) {
11896
+ this.performanceMonitor.setMetric("nodeCount", nodeCount);
11897
+ this.performanceMonitor.setMetric("linkCount", linkCount);
11898
+ }
11899
+ // =============================================================================
11900
+ // PUBLIC API - ForceGraphMethods
11901
+ // =============================================================================
11902
+ d3ReheatSimulation() {
11903
+ if (!this.isInitialized) this.initializeForceGraph();
11904
+ if (this.forceGraph) {
11905
+ this.getGraphInstance().d3ReheatSimulation();
11906
+ }
11907
+ if (this.enableVerboseLogging) {
11908
+ console.log("\u2705 d3ReheatSimulation called");
11909
+ }
11910
+ }
11911
+ stopAnimation() {
11912
+ if (!this.isInitialized) return;
11913
+ const start2 = performance.now();
11914
+ if (this.forceGraph) {
11915
+ this.getGraphInstance().pauseAnimation();
11916
+ }
11917
+ const duration = performance.now() - start2;
11918
+ if (this.config.enablePerformanceMonitoring) {
11919
+ console.log(`\u2705 stopAnimation completed in ${duration.toFixed(3)}ms`);
11920
+ }
11921
+ }
11922
+ pauseAnimation() {
11923
+ if (!this.isInitialized) return;
11924
+ const start2 = performance.now();
11925
+ if (this.forceGraph) {
11926
+ this.getGraphInstance().pauseAnimation();
11927
+ }
11928
+ const duration = performance.now() - start2;
11929
+ if (this.config.enablePerformanceMonitoring) {
11930
+ console.log(`\u2705 pauseAnimation completed in ${duration.toFixed(3)}ms`);
11931
+ }
11932
+ }
11933
+ resumeAnimation() {
11934
+ if (!this.isInitialized) return;
11935
+ const start2 = performance.now();
11936
+ if (this.forceGraph) {
11937
+ this.getGraphInstance().resumeAnimation();
11938
+ }
11939
+ const duration = performance.now() - start2;
11940
+ if (this.config.enablePerformanceMonitoring) {
11941
+ console.log(`\u2705 resumeAnimation completed in ${duration.toFixed(3)}ms`);
11942
+ }
11943
+ }
11944
+ centerAt(x3, y3, duration) {
11945
+ if (!this.isInitialized) this.initializeForceGraph();
11946
+ const start2 = performance.now();
11947
+ if (this.forceGraph) {
11948
+ this.getGraphInstance().centerAt(x3, y3, duration);
11949
+ }
11950
+ const callDuration = performance.now() - start2;
11951
+ if (this.config.enablePerformanceMonitoring) {
11952
+ console.log(`\u2705 centerAt(${x3}, ${y3}) completed in ${callDuration.toFixed(3)}ms`);
11953
+ }
11954
+ }
11955
+ zoom(scale, duration) {
11956
+ if (!this.isInitialized) this.initializeForceGraph();
11957
+ const start2 = performance.now();
11958
+ let result = void 0;
11959
+ if (this.forceGraph) {
11960
+ if (scale === void 0) {
11961
+ result = this.getGraphInstance().zoom();
11962
+ } else {
11963
+ this.getGraphInstance().zoom(scale, duration);
11964
+ result = void 0;
11965
+ }
11966
+ } else if (scale === void 0) {
11967
+ result = 1;
11968
+ }
11969
+ const callDuration = performance.now() - start2;
11970
+ if (this.config.enablePerformanceMonitoring) {
11971
+ if (scale === void 0) {
11972
+ console.log(`\u2705 zoom() getter completed in ${callDuration.toFixed(3)}ms`);
11973
+ } else {
11974
+ console.log(`\u2705 zoom(${scale}) setter completed in ${callDuration.toFixed(3)}ms`);
11975
+ }
11976
+ }
11977
+ return result;
11978
+ }
11979
+ zoomToFit(padding, duration) {
11980
+ if (!this.isInitialized) this.initializeForceGraph();
11981
+ const start2 = performance.now();
11982
+ const containerWidth = this.container.clientWidth;
11983
+ const containerHeight = this.container.clientHeight;
11984
+ const isInViewport = this.container ? this.isElementInViewport(this.container) : false;
11985
+ const isVisible = !document.hidden && isInViewport;
11986
+ const hasValidDimensions = containerWidth > 0 && containerHeight > 0;
11987
+ let bboxValid = false;
11988
+ if (this.forceGraph) {
11989
+ const bbox = this.getGraphInstance().getGraphBbox();
11990
+ bboxValid = bbox && bbox.x[1] - bbox.x[0] > 0 && bbox.y[1] - bbox.y[0] > 0;
11991
+ }
11992
+ if (this.enableVerboseLogging) {
11993
+ console.log("\u{1F3AF} Library zoomToFit called:", {
11994
+ containerDimensions: `${containerWidth}x${containerHeight}`,
11995
+ isInViewport,
11996
+ isVisible,
11997
+ hasValidDimensions,
11998
+ bboxValid,
11999
+ documentHidden: document.hidden,
12000
+ padding: padding || 40,
12001
+ duration: duration || 300
12002
+ });
12003
+ }
12004
+ if (!isVisible || !hasValidDimensions || !bboxValid) {
12005
+ this.deferredFitViewParams = { padding: padding || 40, duration: duration || 300 };
12006
+ this.pendingFitView = true;
12007
+ if (this.enableVerboseLogging) {
12008
+ console.log("\u{1F3AF} Library DEFERRING fitView:", {
12009
+ reason: !isVisible ? "not visible" : !hasValidDimensions ? "invalid dimensions" : "invalid bbox"
12010
+ });
12011
+ }
12012
+ this.setupDeferredFitView();
12013
+ return;
12014
+ }
12015
+ if (this.forceGraph && this.container) {
12016
+ this.cacheContainerDimensions();
12017
+ const bbox = this.getGraphInstance().getGraphBbox();
12018
+ const width = bbox.x[1] - bbox.x[0];
12019
+ const height = bbox.y[1] - bbox.y[0];
12020
+ const centerX = (bbox.x[0] + bbox.x[1]) / 2;
12021
+ const centerY = (bbox.y[0] + bbox.y[1]) / 2;
12022
+ const paddingValue = padding || 40;
12023
+ const viewportWidth = containerWidth - paddingValue;
12024
+ const viewportHeight = containerHeight - paddingValue;
12025
+ const scale = Math.min(viewportWidth / width, viewportHeight / height);
12026
+ if (this.enableVerboseLogging) {
12027
+ console.log("\u{1F3AF} zoomToFit calculations:", {
12028
+ bbox: { width, height, centerX, centerY },
12029
+ container: {
12030
+ clientWidth: containerWidth,
12031
+ clientHeight: containerHeight,
12032
+ viewportWidth,
12033
+ viewportHeight
12034
+ },
12035
+ scale,
12036
+ willApply: width > 0 && height > 0
12037
+ });
12038
+ }
12039
+ if (width > 0 && height > 0) {
12040
+ this.getGraphInstance().centerAt(centerX, centerY);
12041
+ this.getGraphInstance().zoom(scale, duration || 300);
12042
+ this.pendingFitView = false;
12043
+ if (this.enableVerboseLogging) {
12044
+ console.log("\u2705 zoomToFit applied successfully");
12045
+ }
12046
+ } else if (this.enableVerboseLogging) {
12047
+ console.warn("\u26A0\uFE0F zoomToFit skipped - invalid bbox:", {
12048
+ bboxValid: width > 0 && height > 0
12049
+ });
12050
+ }
12051
+ }
12052
+ const callDuration = performance.now() - start2;
12053
+ if (this.config.enablePerformanceMonitoring) {
12054
+ console.log(`\u2705 zoomToFit() completed in ${callDuration.toFixed(3)}ms`);
12055
+ }
12056
+ }
12057
+ screen2GraphCoords(screenX, screenY) {
12058
+ if (!this.isInitialized) this.initializeForceGraph();
12059
+ const start2 = performance.now();
12060
+ let result = { x: 0, y: 0 };
12061
+ if (this.forceGraph) {
12062
+ result = this.getGraphInstance().screen2GraphCoords(screenX, screenY) || { x: 0, y: 0 };
12063
+ }
12064
+ const callDuration = performance.now() - start2;
12065
+ if (this.config.enablePerformanceMonitoring) {
12066
+ console.log(`\u2705 screen2GraphCoords(${screenX}, ${screenY}) completed in ${callDuration.toFixed(3)}ms`);
12067
+ }
12068
+ return result;
12069
+ }
12070
+ graph2ScreenCoords(graphX, graphY) {
12071
+ if (!this.isInitialized) this.initializeForceGraph();
12072
+ const start2 = performance.now();
12073
+ let result = { x: 0, y: 0 };
12074
+ if (this.forceGraph) {
12075
+ result = this.getGraphInstance().graph2ScreenCoords(graphX, graphY) || { x: 0, y: 0 };
12076
+ }
12077
+ const callDuration = performance.now() - start2;
12078
+ if (this.config.enablePerformanceMonitoring) {
12079
+ console.log(`\u2705 graph2ScreenCoords(${graphX}, ${graphY}) completed in ${callDuration.toFixed(3)}ms`);
12080
+ }
12081
+ return result;
12082
+ }
12083
+ getGraphBbox(nodes) {
12084
+ if (!this.isInitialized) this.initializeForceGraph();
12085
+ const start2 = performance.now();
12086
+ let result = { x: [0, 0], y: [0, 0] };
12087
+ if (this.forceGraph) {
12088
+ result = this.getGraphInstance().getGraphBbox(nodes) || { x: [0, 0], y: [0, 0] };
12089
+ }
12090
+ const callDuration = performance.now() - start2;
12091
+ if (this.config.enablePerformanceMonitoring) {
12092
+ console.log(`\u2705 getGraphBbox() completed in ${callDuration.toFixed(3)}ms`);
12093
+ }
12094
+ return result;
12095
+ }
12096
+ emitParticle(link) {
12097
+ if (!this.isInitialized) this.initializeForceGraph();
12098
+ const start2 = performance.now();
12099
+ if (this.forceGraph) {
12100
+ this.getGraphInstance().emitParticle(link);
12101
+ }
12102
+ const callDuration = performance.now() - start2;
12103
+ if (this.config.enablePerformanceMonitoring) {
12104
+ console.log(`\u2705 emitParticle() completed in ${callDuration.toFixed(3)}ms`);
12105
+ }
12106
+ }
12107
+ d3Force(forceName, forceImpl) {
12108
+ if (!this.isInitialized) this.initializeForceGraph();
12109
+ const start2 = performance.now();
12110
+ if (this.forceGraph) {
12111
+ if (forceImpl !== void 0) {
12112
+ this.getGraphInstance().d3Force(forceName, forceImpl);
12113
+ const callDuration2 = performance.now() - start2;
12114
+ if (this.config.enablePerformanceMonitoring) {
12115
+ console.log(`\u2705 d3Force(set ${forceName}) completed in ${callDuration2.toFixed(3)}ms`);
12116
+ }
12117
+ return this;
12118
+ } else {
12119
+ const result = this.getGraphInstance().d3Force(forceName);
12120
+ const callDuration2 = performance.now() - start2;
12121
+ if (this.config.enablePerformanceMonitoring) {
12122
+ console.log(`\u2705 d3Force(get ${forceName}) completed in ${callDuration2.toFixed(3)}ms`);
12123
+ }
12124
+ return result;
12125
+ }
12126
+ }
12127
+ const callDuration = performance.now() - start2;
12128
+ if (this.config.enablePerformanceMonitoring) {
12129
+ const action = forceImpl !== void 0 ? "set" : "get";
12130
+ console.log(`\u2705 d3Force(${action} ${forceName}) completed in ${callDuration.toFixed(3)}ms`);
12131
+ }
12132
+ return forceImpl !== void 0 ? this : null;
12133
+ }
12134
+ graphData(data) {
12135
+ if (data === void 0) {
12136
+ return this.config.graphData ?? { nodes: [], links: [] };
12137
+ }
12138
+ this.config.graphData = data;
12139
+ const nodeCount = data.nodes.length;
12140
+ const linkCount = data.links.length;
12141
+ this.pendingFitView = false;
12142
+ this.performanceMonitor.setMetric("nodeCount", nodeCount);
12143
+ this.performanceMonitor.setMetric("linkCount", linkCount);
12144
+ this.webWorkerPhysicsCompleted = false;
12145
+ if (!this.isInitialized) {
12146
+ this.initializeForceGraph();
12147
+ }
12148
+ if (this.forceGraph) {
12149
+ const loadStart = performance.now();
12150
+ this.getGraphInstance().graphData(data);
12151
+ const loadTime = performance.now() - loadStart;
12152
+ this.performanceMonitor.setMetric("dataLoad", loadTime);
12153
+ if (this.config.enablePerformanceMonitoring) {
12154
+ this.validateDataLoadingPerformance(loadTime, nodeCount, linkCount);
12155
+ }
12156
+ this.updateLegends();
12157
+ }
12158
+ if (this.enableVerboseLogging) {
12159
+ console.log(`\u{1F50D} Web Worker trigger check:`, {
12160
+ deterministicLayout: this.config.deterministicLayout !== false,
12161
+ wasInitializedOutOfView: this.wasInitializedOutOfView,
12162
+ hasNodes: data?.nodes?.length > 0,
12163
+ webWorkerCompleted: this.webWorkerPhysicsCompleted,
12164
+ nodeCount: data?.nodes?.length || 0
12165
+ });
12166
+ }
12167
+ if (this.config.deterministicLayout !== false && this.wasInitializedOutOfView && data?.nodes?.length > 0 && !this.webWorkerPhysicsCompleted) {
12168
+ if (this.enableVerboseLogging) {
12169
+ console.log(`\u{1F3AF} Triggering Web Worker physics for off-screen graph with ${data.nodes.length} nodes`);
12170
+ }
12171
+ if (this.physicsTimeoutId) {
12172
+ window.clearTimeout(this.physicsTimeoutId);
12173
+ }
12174
+ this.physicsTimeoutId = window.setTimeout(() => {
12175
+ this.runWebWorkerPhysics().catch((error) => {
12176
+ if (this.enableVerboseLogging) {
12177
+ console.warn("\u26A0\uFE0F Web Worker physics failed:", error);
12178
+ }
12179
+ });
12180
+ }, 200);
12181
+ }
12182
+ return this;
12183
+ }
12184
+ width(width) {
12185
+ if (width === void 0) {
12186
+ return this.config.width ?? 400;
12187
+ }
12188
+ const start2 = performance.now();
12189
+ this.config.width = width;
12190
+ if (this.isInitialized && this.forceGraph) {
12191
+ this.getGraphInstance().width(width);
12192
+ }
12193
+ const callDuration = performance.now() - start2;
12194
+ if (this.config.enablePerformanceMonitoring) {
12195
+ console.log(`\u2705 width(${width}) completed in ${callDuration.toFixed(3)}ms`);
12196
+ }
12197
+ return this;
12198
+ }
12199
+ height(height) {
12200
+ if (height === void 0) {
12201
+ return this.config.height ?? 300;
12202
+ }
12203
+ const start2 = performance.now();
12204
+ this.config.height = height;
12205
+ if (this.isInitialized && this.forceGraph) {
12206
+ this.getGraphInstance().height(height);
12207
+ }
12208
+ const callDuration = performance.now() - start2;
12209
+ if (this.config.enablePerformanceMonitoring) {
12210
+ console.log(`\u2705 height(${height}) completed in ${callDuration.toFixed(3)}ms`);
12211
+ }
12212
+ return this;
12213
+ }
12214
+ backgroundColor(color2) {
12215
+ if (color2 === void 0) {
12216
+ return this.config.backgroundColor ?? "#ffffff";
12217
+ }
12218
+ const start2 = performance.now();
12219
+ this.config.backgroundColor = color2;
12220
+ if (this.isInitialized && this.forceGraph) {
12221
+ this.getGraphInstance().backgroundColor(color2);
12222
+ }
12223
+ const callDuration = performance.now() - start2;
12224
+ if (this.config.enablePerformanceMonitoring) {
12225
+ console.log(`\u2705 backgroundColor(${color2}) completed in ${callDuration.toFixed(3)}ms`);
12226
+ }
12227
+ return this;
12228
+ }
12229
+ nodeColor(color2) {
12230
+ if (color2 === void 0) return this.config.nodeColor ?? "#999999";
12231
+ const start2 = performance.now();
12232
+ this.config.nodeColor = color2;
12233
+ if (this.isInitialized && this.forceGraph) {
12234
+ this.getGraphInstance().nodeColor(color2);
12235
+ }
12236
+ const callDuration = performance.now() - start2;
12237
+ if (this.config.enablePerformanceMonitoring) {
12238
+ console.log(`\u2705 nodeColor() completed in ${callDuration.toFixed(3)}ms`);
12239
+ }
12240
+ return this;
12241
+ }
12242
+ nodeVal(val) {
12243
+ if (val === void 0) return this.config.nodeVal ?? 1;
12244
+ const start2 = performance.now();
12245
+ this.config.nodeVal = val;
12246
+ if (this.isInitialized && this.forceGraph) {
12247
+ this.getGraphInstance().nodeVal(val);
12248
+ }
12249
+ const callDuration = performance.now() - start2;
12250
+ if (this.config.enablePerformanceMonitoring) {
12251
+ console.log(`\u2705 nodeVal() completed in ${callDuration.toFixed(3)}ms`);
12252
+ }
12253
+ return this;
12254
+ }
12255
+ nodeRelSize(size) {
12256
+ if (size === void 0) return this.config.nodeRelSize ?? 4;
12257
+ const start2 = performance.now();
12258
+ this.config.nodeRelSize = size;
12259
+ if (this.isInitialized && this.forceGraph) {
12260
+ this.getGraphInstance().nodeRelSize(size);
12261
+ }
12262
+ const callDuration = performance.now() - start2;
12263
+ if (this.config.enablePerformanceMonitoring) {
12264
+ console.log(`\u2705 nodeRelSize(${size}) completed in ${callDuration.toFixed(3)}ms`);
12265
+ }
12266
+ return this;
12267
+ }
12268
+ nodeLabel(label) {
12269
+ if (label === void 0) return this.config.nodeLabel ?? "";
12270
+ const start2 = performance.now();
12271
+ this.config.nodeLabel = label;
12272
+ if (this.isInitialized && this.forceGraph) {
12273
+ this.getGraphInstance().nodeLabel(label);
12274
+ }
12275
+ const callDuration = performance.now() - start2;
12276
+ if (this.config.enablePerformanceMonitoring) {
12277
+ console.log(`\u2705 nodeLabel() completed in ${callDuration.toFixed(3)}ms`);
12278
+ }
12279
+ return this;
12280
+ }
12281
+ nodeVisibility(visibility) {
12282
+ if (visibility === void 0) return this.config.nodeVisibility ?? true;
12283
+ const start2 = performance.now();
12284
+ this.config.nodeVisibility = visibility;
12285
+ if (this.isInitialized && this.forceGraph) {
12286
+ this.getGraphInstance().nodeVisibility(visibility);
12287
+ }
12288
+ const callDuration = performance.now() - start2;
12289
+ if (this.config.enablePerformanceMonitoring) {
12290
+ console.log(`\u2705 nodeVisibility() completed in ${callDuration.toFixed(3)}ms`);
12291
+ }
12292
+ return this;
12293
+ }
12294
+ nodeCanvasObjectMode(mode) {
12295
+ if (mode === void 0) return this.config.nodeCanvasObjectMode ?? "replace";
12296
+ const start2 = performance.now();
12297
+ this.config.nodeCanvasObjectMode = mode;
12298
+ if (this.isInitialized && this.forceGraph) {
12299
+ this.getGraphInstance().nodeCanvasObjectMode(mode);
12300
+ }
12301
+ const callDuration = performance.now() - start2;
12302
+ if (this.config.enablePerformanceMonitoring) {
12303
+ console.log(`\u2705 nodeCanvasObjectMode() completed in ${callDuration.toFixed(3)}ms`);
12304
+ }
12305
+ return this;
12306
+ }
12307
+ linkColor(color2) {
12308
+ if (color2 === void 0) return this.config.linkColor ?? "#999999";
12309
+ const start2 = performance.now();
12310
+ this.config.linkColor = color2;
12311
+ if (this.isInitialized && this.forceGraph) {
12312
+ this.getGraphInstance().linkColor(color2);
12313
+ }
12314
+ const callDuration = performance.now() - start2;
12315
+ if (this.config.enablePerformanceMonitoring) {
12316
+ console.log(`\u2705 linkColor() completed in ${callDuration.toFixed(3)}ms`);
12317
+ }
12318
+ return this;
12319
+ }
12320
+ linkWidth(width) {
12321
+ if (width === void 0) return this.config.linkWidth ?? 1;
12322
+ const start2 = performance.now();
12323
+ this.config.linkWidth = width;
12324
+ if (this.isInitialized && this.forceGraph) {
12325
+ this.getGraphInstance().linkWidth(width);
12326
+ }
12327
+ const callDuration = performance.now() - start2;
12328
+ if (this.config.enablePerformanceMonitoring) {
12329
+ console.log(`\u2705 linkWidth() completed in ${callDuration.toFixed(3)}ms`);
12330
+ }
12331
+ return this;
12332
+ }
12333
+ linkLabel(label) {
12334
+ if (label === void 0) return this.config.linkLabel ?? "";
12335
+ const start2 = performance.now();
12336
+ this.config.linkLabel = label;
12337
+ if (this.isInitialized && this.forceGraph) {
12338
+ this.getGraphInstance().linkLabel(label);
12339
+ }
12340
+ const callDuration = performance.now() - start2;
12341
+ if (this.config.enablePerformanceMonitoring) {
12342
+ console.log(`\u2705 linkLabel() completed in ${callDuration.toFixed(3)}ms`);
12343
+ }
12344
+ return this;
12345
+ }
12346
+ linkVisibility(visibility) {
12347
+ if (visibility === void 0) return this.config.linkVisibility ?? true;
12348
+ const start2 = performance.now();
12349
+ this.config.linkVisibility = visibility;
12350
+ if (this.isInitialized && this.forceGraph) {
12351
+ this.getGraphInstance().linkVisibility(visibility);
12352
+ }
12353
+ const callDuration = performance.now() - start2;
12354
+ if (this.config.enablePerformanceMonitoring) {
12355
+ console.log(`\u2705 linkVisibility() completed in ${callDuration.toFixed(3)}ms`);
12356
+ }
12357
+ return this;
12358
+ }
12359
+ linkDirectionalArrowLength(length) {
12360
+ if (length === void 0) return this.config.linkDirectionalArrowLength ?? 0;
12361
+ const start2 = performance.now();
12362
+ this.config.linkDirectionalArrowLength = length;
12363
+ if (this.isInitialized && this.forceGraph) {
12364
+ this.getGraphInstance().linkDirectionalArrowLength(length);
12365
+ }
12366
+ const callDuration = performance.now() - start2;
12367
+ if (this.config.enablePerformanceMonitoring) {
12368
+ console.log(`\u2705 linkDirectionalArrowLength() completed in ${callDuration.toFixed(3)}ms`);
12369
+ }
12370
+ return this;
12371
+ }
12372
+ linkDirectionalParticles(particles) {
12373
+ if (particles === void 0) return this.config.linkDirectionalParticles ?? 0;
12374
+ const start2 = performance.now();
12375
+ this.config.linkDirectionalParticles = particles;
12376
+ if (this.isInitialized && this.forceGraph) {
12377
+ this.getGraphInstance().linkDirectionalParticles(particles);
12378
+ }
12379
+ const callDuration = performance.now() - start2;
12380
+ if (this.config.enablePerformanceMonitoring) {
12381
+ console.log(`\u2705 linkDirectionalParticles() completed in ${callDuration.toFixed(3)}ms`);
12382
+ }
12383
+ return this;
12384
+ }
12385
+ linkDirectionalArrowColor(color2) {
12386
+ if (color2 === void 0) return this.config.linkDirectionalArrowColor ?? "#999999";
12387
+ const start2 = performance.now();
12388
+ this.config.linkDirectionalArrowColor = color2;
12389
+ if (this.isInitialized && this.forceGraph) {
12390
+ this.getGraphInstance().linkDirectionalArrowColor(color2);
12391
+ }
12392
+ const callDuration = performance.now() - start2;
12393
+ if (this.config.enablePerformanceMonitoring) {
12394
+ console.log(`\u2705 linkDirectionalArrowColor() completed in ${callDuration.toFixed(3)}ms`);
12395
+ }
12396
+ return this;
12397
+ }
12398
+ linkDirectionalArrowRelPos(position) {
12399
+ if (position === void 0) return this.config.linkDirectionalArrowRelPos ?? 0.5;
12400
+ const start2 = performance.now();
12401
+ this.config.linkDirectionalArrowRelPos = position;
12402
+ if (this.isInitialized && this.forceGraph) {
12403
+ this.getGraphInstance().linkDirectionalArrowRelPos(position);
12404
+ }
12405
+ const callDuration = performance.now() - start2;
12406
+ if (this.config.enablePerformanceMonitoring) {
12407
+ console.log(`\u2705 linkDirectionalArrowRelPos(${position}) completed in ${callDuration.toFixed(3)}ms`);
12408
+ }
12409
+ return this;
12410
+ }
12411
+ linkDirectionalParticleSpeed(speed) {
12412
+ if (speed === void 0) return this.config.linkDirectionalParticleSpeed ?? 1;
12413
+ const start2 = performance.now();
12414
+ this.config.linkDirectionalParticleSpeed = speed;
12415
+ if (this.isInitialized && this.forceGraph) {
12416
+ this.getGraphInstance().linkDirectionalParticleSpeed(speed);
12417
+ }
12418
+ const callDuration = performance.now() - start2;
12419
+ if (this.config.enablePerformanceMonitoring) {
12420
+ console.log(`\u2705 linkDirectionalParticleSpeed(${speed}) completed in ${callDuration.toFixed(3)}ms`);
12421
+ }
12422
+ return this;
12423
+ }
12424
+ linkDirectionalParticleWidth(width) {
12425
+ if (width === void 0) return this.config.linkDirectionalParticleWidth ?? 4;
12426
+ const start2 = performance.now();
12427
+ this.config.linkDirectionalParticleWidth = width;
12428
+ if (this.isInitialized && this.forceGraph) {
12429
+ this.getGraphInstance().linkDirectionalParticleWidth(width);
12430
+ }
12431
+ const callDuration = performance.now() - start2;
12432
+ if (this.config.enablePerformanceMonitoring) {
12433
+ console.log(`\u2705 linkDirectionalParticleWidth(${width}) completed in ${callDuration.toFixed(3)}ms`);
12434
+ }
12435
+ return this;
12436
+ }
12437
+ linkDirectionalParticleColor(color2) {
12438
+ if (color2 === void 0) return this.config.linkDirectionalParticleColor ?? "#999999";
12439
+ const start2 = performance.now();
12440
+ this.config.linkDirectionalParticleColor = color2;
12441
+ if (this.isInitialized && this.forceGraph) {
12442
+ this.getGraphInstance().linkDirectionalParticleColor(color2);
12443
+ }
12444
+ const callDuration = performance.now() - start2;
12445
+ if (this.config.enablePerformanceMonitoring) {
12446
+ console.log(`\u2705 linkDirectionalParticleColor() completed in ${callDuration.toFixed(3)}ms`);
12447
+ }
12448
+ return this;
12449
+ }
12450
+ linkCurvature(curvature) {
12451
+ if (curvature === void 0) return this.config.linkCurvature ?? 0;
12452
+ const start2 = performance.now();
12453
+ this.config.linkCurvature = curvature;
12454
+ if (this.isInitialized && this.forceGraph) {
12455
+ this.getGraphInstance().linkCurvature(curvature);
12456
+ }
12457
+ const callDuration = performance.now() - start2;
12458
+ if (this.config.enablePerformanceMonitoring) {
12459
+ console.log(`\u2705 linkCurvature(${curvature}) completed in ${callDuration.toFixed(3)}ms`);
12460
+ }
12461
+ return this;
12462
+ }
12463
+ linkCanvasObjectMode(mode) {
12464
+ if (mode === void 0) return this.config.linkCanvasObjectMode ?? "replace";
12465
+ const start2 = performance.now();
12466
+ this.config.linkCanvasObjectMode = mode;
12467
+ if (this.isInitialized && this.forceGraph) {
12468
+ this.getGraphInstance().linkCanvasObjectMode(mode);
12469
+ }
12470
+ const callDuration = performance.now() - start2;
12471
+ if (this.config.enablePerformanceMonitoring) {
12472
+ console.log(`\u2705 linkCanvasObjectMode() completed in ${callDuration.toFixed(3)}ms`);
12473
+ }
12474
+ return this;
12475
+ }
12476
+ onNodeClick(handler) {
12477
+ if (handler === void 0) return this.config.onNodeClick ?? null;
12478
+ const start2 = performance.now();
12479
+ this.config.onNodeClick = handler || void 0;
12480
+ if (this.isInitialized && this.forceGraph) {
12481
+ this.getGraphInstance().onNodeClick(handler);
12482
+ }
12483
+ const callDuration = performance.now() - start2;
12484
+ if (this.config.enablePerformanceMonitoring) {
12485
+ console.log(`\u2705 onNodeClick() completed in ${callDuration.toFixed(3)}ms`);
12486
+ }
12487
+ return this;
12488
+ }
12489
+ onNodeDoubleClick(handler) {
12490
+ if (handler === void 0) return this.config.onNodeDoubleClick ?? null;
12491
+ const start2 = performance.now();
12492
+ this.config.onNodeDoubleClick = handler || void 0;
12493
+ const callDuration = performance.now() - start2;
12494
+ if (this.config.enablePerformanceMonitoring) {
12495
+ console.log(`\u2705 onNodeDoubleClick() completed in ${callDuration.toFixed(3)}ms`);
12496
+ }
12497
+ return this;
12498
+ }
12499
+ onNodeHover(handler) {
12500
+ if (handler === void 0) return this.config.onNodeHover ?? null;
12501
+ const start2 = performance.now();
12502
+ this.config.onNodeHover = handler || void 0;
12503
+ if (this.isInitialized && this.forceGraph) {
12504
+ this.getGraphInstance().onNodeHover(handler);
12505
+ }
12506
+ const callDuration = performance.now() - start2;
12507
+ if (this.config.enablePerformanceMonitoring) {
12508
+ console.log(`\u2705 onNodeHover() completed in ${callDuration.toFixed(3)}ms`);
12509
+ }
12510
+ return this;
12511
+ }
12512
+ onLinkClick(handler) {
12513
+ if (handler === void 0) return this.config.onLinkClick ?? null;
12514
+ const start2 = performance.now();
12515
+ this.config.onLinkClick = handler || void 0;
12516
+ if (this.isInitialized && this.forceGraph) {
12517
+ this.getGraphInstance().onLinkClick(handler);
12518
+ }
12519
+ const callDuration = performance.now() - start2;
12520
+ if (this.config.enablePerformanceMonitoring) {
12521
+ console.log(`\u2705 onLinkClick() completed in ${callDuration.toFixed(3)}ms`);
12522
+ }
12523
+ return this;
12524
+ }
12525
+ onLinkHover(handler) {
12526
+ if (handler === void 0) return this.config.onLinkHover ?? null;
12527
+ const start2 = performance.now();
12528
+ this.config.onLinkHover = handler || void 0;
12529
+ if (this.isInitialized && this.forceGraph) {
12530
+ this.getGraphInstance().onLinkHover(handler);
12531
+ }
12532
+ const callDuration = performance.now() - start2;
12533
+ if (this.config.enablePerformanceMonitoring) {
12534
+ console.log(`\u2705 onLinkHover() completed in ${callDuration.toFixed(3)}ms`);
12535
+ }
12536
+ return this;
12537
+ }
12538
+ onRenderFramePre(handler) {
12539
+ if (handler === void 0) return this.config.onRenderFramePre ?? null;
12540
+ const start2 = performance.now();
12541
+ this.config.onRenderFramePre = handler || void 0;
12542
+ if (this.isInitialized && this.forceGraph) {
12543
+ this.getGraphInstance().onRenderFramePre(handler);
12544
+ }
12545
+ const callDuration = performance.now() - start2;
12546
+ if (this.config.enablePerformanceMonitoring) {
12547
+ console.log(`\u2705 onRenderFramePre() completed in ${callDuration.toFixed(3)}ms`);
12548
+ }
12549
+ return this;
12550
+ }
12551
+ onRenderFramePost(handler) {
12552
+ if (handler === void 0) return this.config.onRenderFramePost ?? null;
12553
+ const start2 = performance.now();
12554
+ this.config.onRenderFramePost = handler || void 0;
12555
+ if (this.isInitialized && this.forceGraph) {
12556
+ this.getGraphInstance().onRenderFramePost(handler);
12557
+ }
12558
+ const callDuration = performance.now() - start2;
12559
+ if (this.config.enablePerformanceMonitoring) {
12560
+ console.log(`\u2705 onRenderFramePost() completed in ${callDuration.toFixed(3)}ms`);
12561
+ }
12562
+ return this;
12563
+ }
12564
+ cooldownTime(time) {
12565
+ if (time === void 0) return this.config.cooldownTime ?? 15e3;
12566
+ const start2 = performance.now();
12567
+ this.config.cooldownTime = time;
12568
+ if (this.isInitialized && this.forceGraph) {
12569
+ this.getGraphInstance().cooldownTime(time);
12570
+ }
12571
+ const callDuration = performance.now() - start2;
12572
+ if (this.config.enablePerformanceMonitoring) {
12573
+ console.log(`\u2705 cooldownTime(${time}) completed in ${callDuration.toFixed(3)}ms`);
12574
+ }
12575
+ return this;
12576
+ }
12577
+ d3AlphaDecay(decay) {
12578
+ if (decay === void 0) return this.config.d3AlphaDecay ?? 0.0228;
12579
+ const start2 = performance.now();
12580
+ this.config.d3AlphaDecay = decay;
12581
+ if (this.isInitialized && this.forceGraph) {
12582
+ this.getGraphInstance().d3AlphaDecay(decay);
12583
+ }
12584
+ const callDuration = performance.now() - start2;
12585
+ if (this.config.enablePerformanceMonitoring) {
12586
+ console.log(`\u2705 d3AlphaDecay(${decay}) completed in ${callDuration.toFixed(3)}ms`);
12587
+ }
12588
+ return this;
12589
+ }
12590
+ d3VelocityDecay(decay) {
12591
+ if (decay === void 0) return this.config.d3VelocityDecay ?? 0.4;
12592
+ const start2 = performance.now();
12593
+ this.config.d3VelocityDecay = decay;
12594
+ if (this.isInitialized && this.forceGraph) {
12595
+ this.getGraphInstance().d3VelocityDecay(decay);
12596
+ }
12597
+ const callDuration = performance.now() - start2;
12598
+ if (this.config.enablePerformanceMonitoring) {
12599
+ console.log(`\u2705 d3VelocityDecay(${decay}) completed in ${callDuration.toFixed(3)}ms`);
12600
+ }
12601
+ return this;
12602
+ }
12603
+ onEngineStop(handler) {
12604
+ if (handler === void 0) return this.config.onEngineStop;
12605
+ const start2 = performance.now();
12606
+ this.config.onEngineStop = handler;
12607
+ if (this.isInitialized && this.forceGraph) {
12608
+ this.getGraphInstance().onEngineStop(handler);
12609
+ }
12610
+ const callDuration = performance.now() - start2;
12611
+ if (this.config.enablePerformanceMonitoring) {
12612
+ console.log(`\u2705 onEngineStop() completed in ${callDuration.toFixed(3)}ms`);
12613
+ }
12614
+ return this;
12615
+ }
12616
+ onEngineTick(handler) {
12617
+ if (handler === void 0) return this.config.onEngineTick;
12618
+ const start2 = performance.now();
12619
+ this.config.onEngineTick = handler;
12620
+ if (this.isInitialized && this.forceGraph) {
12621
+ this.getGraphInstance().onEngineTick(handler);
12622
+ }
12623
+ const callDuration = performance.now() - start2;
12624
+ if (this.config.enablePerformanceMonitoring) {
12625
+ console.log(`\u2705 onEngineTick() completed in ${callDuration.toFixed(3)}ms`);
12626
+ }
12627
+ return this;
12628
+ }
12629
+ // =============================================================================
12630
+ // PUBLIC API - ForceGraphPerformanceMethods
12631
+ // =============================================================================
12632
+ getPerformanceMetrics() {
12633
+ return this.performanceMonitor.getMetrics();
12634
+ }
12635
+ resetPerformanceMetrics() {
12636
+ this.performanceMonitor.reset();
12637
+ }
12638
+ logPerformanceSummary() {
12639
+ this.performanceMonitor.logSummary();
12640
+ }
12641
+ validatePerformance() {
12642
+ const metrics = this.performanceMonitor.getMetrics();
12643
+ const targets = this.config.performanceTargets ?? {};
12644
+ const warnings = [];
12645
+ const recommendations = [];
12646
+ if (targets.creation && metrics.creation > targets.creation) {
12647
+ warnings.push(`Creation time ${metrics.creation.toFixed(2)}ms exceeds target ${targets.creation}ms`);
12648
+ recommendations.push("Consider reducing initialization complexity");
12649
+ }
12650
+ if (targets.dataLoad && metrics.dataLoad > targets.dataLoad) {
12651
+ warnings.push(`Data load time ${metrics.dataLoad.toFixed(2)}ms exceeds target ${targets.dataLoad}ms`);
12652
+ recommendations.push("Consider data preprocessing or pagination for large datasets");
12653
+ }
12654
+ return {
12655
+ passed: warnings.length === 0,
12656
+ warnings,
12657
+ recommendations
12658
+ };
12659
+ }
12660
+ // =============================================================================
12661
+ // STEP 4: RENDERING PERFORMANCE OPTIMIZATION
12662
+ // =============================================================================
12663
+ /**
12664
+ * Setup advanced rendering performance monitoring
12665
+ * Target: 60fps @ 1000 nodes with optimizations
12666
+ */
12667
+ setupRenderingMonitoring() {
12668
+ if (!this.forceGraph || !this.renderingMonitor || !this.canvasOptimizer) return;
12669
+ const canvas = this.container.querySelector("canvas");
12670
+ if (canvas) {
12671
+ this.canvasOptimizer.initialize(canvas);
12672
+ }
12673
+ if (this.config.enablePerformanceMonitoring && this.config.performanceTargets?.render) {
12674
+ let frameCount = 0;
12675
+ this.getGraphInstance().onRenderFramePost(() => {
12676
+ frameCount++;
12677
+ if (frameCount % 10 === 0) {
12678
+ this.renderingMonitor?.recordRenderCall();
12679
+ }
12680
+ });
12681
+ this.startRenderingMonitoring();
12682
+ }
12683
+ }
12684
+ /**
12685
+ * Start advanced rendering performance monitoring
12686
+ */
12687
+ startRenderingMonitoring() {
12688
+ if (!this.renderingMonitor) return;
12689
+ this.renderingMonitor.startMonitoring();
12690
+ this.isRenderingOptimized = true;
12691
+ if (this.enableVerboseLogging) {
12692
+ console.log("\u{1F3AC} Started rendering performance monitoring");
12693
+ }
12694
+ }
12695
+ /**
12696
+ * Stop rendering performance monitoring
12697
+ */
12698
+ stopRenderingMonitoring() {
12699
+ if (!this.renderingMonitor) return;
12700
+ this.renderingMonitor.stopMonitoring();
12701
+ this.isRenderingOptimized = false;
12702
+ if (this.enableVerboseLogging) {
12703
+ const metrics = this.renderingMonitor.getMetrics();
12704
+ console.log(`\u{1F3AC} Rendering performance: ${metrics.averageFps.toFixed(1)}fps, efficiency: ${(metrics.renderingEfficiency * 100).toFixed(1)}%`);
12705
+ }
12706
+ }
12707
+ /**
12708
+ * Get current rendering performance metrics
12709
+ */
12710
+ getRenderingMetrics() {
12711
+ if (!this.isRenderingOptimized || !this.renderingMonitor) {
12712
+ return null;
12713
+ }
12714
+ const currentData = this.forceGraph?.graphData?.();
12715
+ if (currentData) {
12716
+ this.renderingMonitor.updateCounts(
12717
+ currentData.nodes?.length ?? 0,
12718
+ currentData.links?.length ?? 0
12719
+ );
12720
+ }
12721
+ return this.renderingMonitor.getMetrics();
12722
+ }
12723
+ /**
12724
+ * Validate rendering performance against Step 4 targets
12725
+ */
12726
+ validateRenderingPerformance() {
12727
+ const metrics = this.getRenderingMetrics();
12728
+ if (!metrics) {
12729
+ return {
12730
+ passed: false,
12731
+ metrics: {},
12732
+ validation: {
12733
+ passed: false,
12734
+ results: {
12735
+ fpsTarget: { expected: 60, actual: 0, passed: false },
12736
+ frameTimeTarget: { expected: 16.67, actual: 0, passed: false },
12737
+ droppedFramesTarget: { expected: 0, actual: 0, passed: false },
12738
+ efficiencyTarget: { expected: 0.9, actual: 0, passed: false }
12739
+ },
12740
+ overallScore: 0
12741
+ },
12742
+ recommendations: ["Rendering monitoring not started"]
12743
+ };
12744
+ }
12745
+ const validation = this.renderingMonitor?.validatePerformance() ?? {
12746
+ passed: false,
12747
+ results: {
12748
+ fpsTarget: { expected: 60, actual: 0, passed: false },
12749
+ frameTimeTarget: { expected: 16.67, actual: 0, passed: false },
12750
+ droppedFramesTarget: { expected: 0, actual: 0, passed: false },
12751
+ efficiencyTarget: { expected: 0.9, actual: 0, passed: false }
12752
+ },
12753
+ overallScore: 0
12754
+ };
12755
+ const recommendations = this.renderingMonitor?.getOptimizationRecommendations() ?? ["Rendering monitoring not available"];
12756
+ return {
12757
+ passed: validation.passed,
12758
+ metrics,
12759
+ validation,
12760
+ recommendations
12761
+ };
12762
+ }
12763
+ /**
12764
+ * Optimize rendering for large datasets
12765
+ * Implements performance optimizations based on node count
12766
+ */
12767
+ optimizeForDataset(nodeCount) {
12768
+ if (!this.forceGraph) {
12769
+ console.warn("Cannot optimize: graph not initialized");
12770
+ return;
12771
+ }
12772
+ if (nodeCount > 5e3) {
12773
+ this.forceGraph.nodeCanvasObject(null).linkCanvasObject(null).nodeLabel("").linkLabel("").cooldownTicks(50).d3AlphaDecay(0.05);
12774
+ } else if (nodeCount > 1e3) {
12775
+ this.forceGraph.nodeCanvasObject(null).cooldownTicks(100).d3AlphaDecay(0.04);
12776
+ } else if (nodeCount > 500) {
12777
+ this.getGraphInstance().cooldownTicks(200);
12778
+ }
12779
+ this.performanceMonitor.setMetric("optimizedForNodeCount", nodeCount);
12780
+ }
12781
+ /**
12782
+ * Force a rendering performance test
12783
+ * Useful for validation during data loading
12784
+ */
12785
+ async testRenderingPerformance(durationMs = 5e3) {
12786
+ return new Promise((resolve) => {
12787
+ const fpsReadings = [];
12788
+ const target = this.config.performanceTargets?.render ?? 60;
12789
+ const tempTracker = {
12790
+ startTime: performance.now(),
12791
+ frames: 0,
12792
+ lastTime: performance.now()
12793
+ };
12794
+ if (this.performanceTestInterval) {
12795
+ clearInterval(this.performanceTestInterval);
12796
+ }
12797
+ this.performanceTestInterval = setInterval(() => {
12798
+ const now2 = performance.now();
12799
+ tempTracker.frames++;
12800
+ if (now2 - tempTracker.lastTime >= 1e3) {
12801
+ const fps = tempTracker.frames * 1e3 / (now2 - tempTracker.lastTime);
12802
+ fpsReadings.push(fps);
12803
+ tempTracker.frames = 0;
12804
+ tempTracker.lastTime = now2;
12805
+ }
12806
+ if (now2 - tempTracker.startTime >= durationMs) {
12807
+ if (this.performanceTestInterval) {
12808
+ clearInterval(this.performanceTestInterval);
12809
+ this.performanceTestInterval = null;
12810
+ }
12811
+ const averageFPS = fpsReadings.reduce((a2, b) => a2 + b, 0) / fpsReadings.length;
12812
+ const minFPS = Math.min(...fpsReadings);
12813
+ const maxFPS = Math.max(...fpsReadings);
12814
+ const passed = averageFPS >= target * 0.8;
12815
+ resolve({
12816
+ averageFPS,
12817
+ minFPS,
12818
+ maxFPS,
12819
+ passed,
12820
+ targetFPS: target
12821
+ });
12822
+ }
12823
+ }, 16);
12824
+ });
12825
+ }
12826
+ // =============================================================================
12827
+ // PUBLIC API - Core Methods
12828
+ // =============================================================================
12829
+ render() {
12830
+ if (!this.isInitialized) {
12831
+ this.initializeForceGraph();
12832
+ }
12833
+ const renderStart = performance.now();
12834
+ if (this.forceGraph) {
12835
+ const graphInstance = this.getGraphInstance();
12836
+ if (graphInstance.refresh) {
12837
+ graphInstance.refresh();
12838
+ }
12839
+ }
12840
+ const renderTime = performance.now() - renderStart;
12841
+ this.performanceMonitor.setMetric("firstRender", renderTime);
12842
+ this.initializeControls();
12843
+ this.initializeLegends();
12844
+ this.setupDoubleClickHandling();
12845
+ if (this.config.enablePerformanceMonitoring) {
12846
+ console.log(`\u2705 First render completed in ${renderTime.toFixed(2)}ms`);
12847
+ }
12848
+ }
12849
+ cooldownTicks(ticks) {
12850
+ if (ticks === void 0) return this.config.cooldownTicks ?? 100;
12851
+ const start2 = performance.now();
12852
+ this.config.cooldownTicks = ticks;
12853
+ if (this.isInitialized && this.forceGraph) {
12854
+ this.getGraphInstance().cooldownTicks(ticks);
12855
+ }
12856
+ const callDuration = performance.now() - start2;
12857
+ if (this.config.enablePerformanceMonitoring) {
12858
+ console.log(`\u2705 cooldownTicks(${ticks}) completed in ${callDuration.toFixed(3)}ms`);
12859
+ }
12860
+ return this;
12861
+ }
12862
+ d3AlphaMin(min) {
12863
+ if (min === void 0) return this.config.d3AlphaMin ?? 1e-3;
12864
+ const start2 = performance.now();
12865
+ this.config.d3AlphaMin = min;
12866
+ if (this.isInitialized && this.forceGraph) {
12867
+ this.getGraphInstance().d3AlphaMin(min);
12868
+ }
12869
+ const callDuration = performance.now() - start2;
12870
+ if (this.config.enablePerformanceMonitoring) {
12871
+ console.log(`\u2705 d3AlphaMin(${min}) completed in ${callDuration.toFixed(3)}ms`);
12872
+ }
12873
+ return this;
12874
+ }
12875
+ enableNodeDrag(enable) {
12876
+ if (enable === void 0) return this.config.enableNodeDrag ?? true;
12877
+ const start2 = performance.now();
12878
+ this.config.enableNodeDrag = enable;
12879
+ if (this.isInitialized && this.forceGraph) {
12880
+ this.getGraphInstance().enableNodeDrag(enable);
12881
+ }
12882
+ const callDuration = performance.now() - start2;
12883
+ if (this.config.enablePerformanceMonitoring) {
12884
+ console.log(`\u2705 enableNodeDrag(${enable}) completed in ${callDuration.toFixed(3)}ms`);
12885
+ }
12886
+ return this;
12887
+ }
12888
+ nodeCanvasObject(paintFunction) {
12889
+ if (paintFunction === void 0) return this.config.nodeCanvasObject ?? null;
12890
+ const start2 = performance.now();
12891
+ this.config.nodeCanvasObject = paintFunction || void 0;
12892
+ if (this.isInitialized && this.forceGraph) {
12893
+ this.getGraphInstance().nodeCanvasObject(paintFunction);
12894
+ }
12895
+ const callDuration = performance.now() - start2;
12896
+ if (this.config.enablePerformanceMonitoring) {
12897
+ console.log(`\u2705 nodeCanvasObject() completed in ${callDuration.toFixed(3)}ms`);
12898
+ }
12899
+ return this;
12900
+ }
12901
+ linkCanvasObject(paintFunction) {
12902
+ if (paintFunction === void 0) return this.config.linkCanvasObject ?? null;
12903
+ const start2 = performance.now();
12904
+ this.config.linkCanvasObject = paintFunction || void 0;
12905
+ if (this.isInitialized && this.forceGraph) {
12906
+ this.getGraphInstance().linkCanvasObject(paintFunction);
12907
+ }
12908
+ const callDuration = performance.now() - start2;
12909
+ if (this.config.enablePerformanceMonitoring) {
12910
+ console.log(`\u2705 linkCanvasObject() completed in ${callDuration.toFixed(3)}ms`);
12911
+ }
12912
+ return this;
12913
+ }
12914
+ autoPauseRedraw(enable) {
12915
+ if (enable === void 0) return this.config.autoPauseRedraw ?? true;
12916
+ const start2 = performance.now();
12917
+ this.config.autoPauseRedraw = enable;
12918
+ if (this.isInitialized && this.forceGraph) {
12919
+ this.getGraphInstance().autoPauseRedraw(enable);
12920
+ }
12921
+ const callDuration = performance.now() - start2;
12922
+ if (this.config.enablePerformanceMonitoring) {
12923
+ console.log(`\u2705 autoPauseRedraw(${enable}) completed in ${callDuration.toFixed(3)}ms`);
12924
+ }
12925
+ return this;
12926
+ }
12927
+ enableZoomInteraction(enable) {
12928
+ if (enable === void 0) {
12929
+ const configValue = this.config.enableZoomInteraction ?? true;
12930
+ return typeof configValue === "function" ? true : configValue;
12931
+ }
12932
+ const start2 = performance.now();
12933
+ this.config.enableZoomInteraction = enable;
12934
+ if (this.isInitialized && this.forceGraph) {
12935
+ this.getGraphInstance().enableZoomInteraction(enable);
12936
+ }
12937
+ const callDuration = performance.now() - start2;
12938
+ if (this.config.enablePerformanceMonitoring) {
12939
+ console.log(`\u2705 enableZoomInteraction(${enable}) completed in ${callDuration.toFixed(3)}ms`);
12940
+ }
12941
+ return this;
12942
+ }
12943
+ enablePanInteraction(enable) {
12944
+ if (enable === void 0) {
12945
+ const configValue = this.config.enablePanInteraction ?? true;
12946
+ return typeof configValue === "function" ? true : configValue;
12947
+ }
12948
+ const start2 = performance.now();
12949
+ this.config.enablePanInteraction = enable;
12950
+ if (this.isInitialized && this.forceGraph) {
12951
+ this.getGraphInstance().enablePanInteraction(enable);
12952
+ }
12953
+ const callDuration = performance.now() - start2;
12954
+ if (this.config.enablePerformanceMonitoring) {
12955
+ console.log(`\u2705 enablePanInteraction(${enable}) completed in ${callDuration.toFixed(3)}ms`);
12956
+ }
12957
+ return this;
12958
+ }
12959
+ // =============================================================================
12960
+ // GRAPH CONTROLS METHODS
12961
+ // =============================================================================
12962
+ /**
12963
+ * Initialize graph controls if enabled
12964
+ */
12965
+ initializeControls() {
12966
+ if (!this.config.controls?.enabled) return;
12967
+ const controlsConfig = {
12968
+ ...this.config.controls,
12969
+ enabled: true
12970
+ };
12971
+ if (!controlsConfig.position) {
12972
+ controlsConfig.position = "bottom-left";
12973
+ }
12974
+ const controlActions = {
12975
+ zoomIn: () => this.zoomIn(),
12976
+ zoomOut: () => this.zoomOut(),
12977
+ fitView: () => this.fitView(),
12978
+ resetView: () => this.resetView()
12979
+ };
12980
+ try {
12981
+ this.controlsInstance = createGraphControls(this.container, controlActions, controlsConfig);
12982
+ this.controlsInstance.mount();
12983
+ if (this.config.enablePerformanceMonitoring) {
12984
+ console.log("\u2705 Graph controls initialized");
12985
+ }
12986
+ } catch {
12987
+ }
12988
+ }
12989
+ /**
12990
+ * Zoom in by a factor of 1.5
12991
+ */
12992
+ zoomIn() {
12993
+ if (!this.isInitialized) return;
12994
+ const currentZoom = this.zoom();
12995
+ this.zoom(currentZoom * 1.5, 200);
12996
+ }
12997
+ /**
12998
+ * Zoom out by a factor of 0.67
12999
+ */
13000
+ zoomOut() {
13001
+ if (!this.isInitialized) return;
13002
+ const currentZoom = this.zoom();
13003
+ this.zoom(currentZoom * 0.67, 200);
13004
+ }
13005
+ /**
13006
+ * Fit the graph to the viewport
13007
+ */
13008
+ fitView() {
13009
+ this.zoomToFit(20, 300);
13010
+ }
13011
+ /**
13012
+ * Reset the view to initial state
13013
+ */
13014
+ resetView() {
13015
+ this.centerAt(0, 0, 300);
13016
+ this.zoom(1, 300);
13017
+ }
13018
+ /**
13019
+ * Cleanup graph controls
13020
+ */
13021
+ cleanupControls() {
13022
+ if (this.controlsInstance) {
13023
+ this.controlsInstance.destroy();
13024
+ this.controlsInstance = null;
13025
+ }
13026
+ }
13027
+ /**
13028
+ * Initialize graph legends if enabled
13029
+ */
13030
+ initializeLegends() {
13031
+ if (!this.config.legends?.enabled) return;
13032
+ const legendsConfig = {
13033
+ ...this.config.legends,
13034
+ enabled: true
13035
+ };
13036
+ if (!legendsConfig.position) {
13037
+ legendsConfig.position = "top-right";
13038
+ }
13039
+ try {
13040
+ this.legendsInstance = createGraphLegends(this.container, legendsConfig);
13041
+ this.legendsInstance.mount();
13042
+ this.updateLegends();
13043
+ if (this.config.enablePerformanceMonitoring) {
13044
+ console.log("\u2705 Graph legends initialized");
13045
+ }
13046
+ } catch {
13047
+ }
13048
+ }
13049
+ /**
13050
+ * Update legends with current graph data
13051
+ */
13052
+ updateLegends() {
13053
+ if (!this.legendsInstance || !this.config.legends?.enabled) return;
13054
+ const graphData = this.config.graphData;
13055
+ if (!graphData || !graphData.nodes || graphData.nodes.length === 0) return;
13056
+ const nodeTypes = Array.from(
13057
+ new Set(
13058
+ graphData.nodes.map((node) => node.data?.type).filter((type) => Boolean(type && typeof type === "string"))
13059
+ )
13060
+ );
13061
+ const colorMap = {};
13062
+ const nodeColorAccessor = this.config.nodeColor;
13063
+ if (nodeColorAccessor && typeof nodeColorAccessor === "function") {
13064
+ graphData.nodes.forEach((node) => {
13065
+ const type = node.data?.type;
13066
+ if (type) {
13067
+ const color2 = nodeColorAccessor(node);
13068
+ if (color2) {
13069
+ colorMap[type] = color2;
13070
+ }
13071
+ }
13072
+ });
13073
+ } else if (nodeColorAccessor && typeof nodeColorAccessor === "string") {
13074
+ nodeTypes.forEach((type) => {
13075
+ if (type) {
13076
+ colorMap[type] = nodeColorAccessor;
13077
+ }
13078
+ });
13079
+ } else {
13080
+ const defaultColors = [
13081
+ "#3b82f6",
13082
+ "#ef4444",
13083
+ "#10b981",
13084
+ "#f59e0b",
13085
+ "#8b5cf6",
13086
+ "#06b6d4",
13087
+ "#84cc16",
13088
+ "#f97316",
13089
+ "#ec4899",
13090
+ "#6b7280"
13091
+ ];
13092
+ nodeTypes.forEach((type, index2) => {
13093
+ const color2 = defaultColors[index2 % defaultColors.length];
13094
+ if (type && color2) {
13095
+ colorMap[type] = color2;
13096
+ }
13097
+ });
13098
+ }
13099
+ this.legendsInstance.update(nodeTypes, colorMap);
13100
+ }
13101
+ /**
13102
+ * Cleanup graph legends
13103
+ */
13104
+ cleanupLegends() {
13105
+ if (this.legendsInstance) {
13106
+ this.legendsInstance.destroy();
13107
+ this.legendsInstance = null;
13108
+ }
13109
+ }
13110
+ /**
13111
+ * Draw legends on the export canvas (similar to React implementation)
13112
+ */
13113
+ drawLegendsOnExportCanvas(ctx, canvasWidth, canvasHeight) {
13114
+ if (!this.config.legends?.enabled || !this.legendsInstance) return;
13115
+ const graphData = this.config.graphData;
13116
+ if (!graphData || !graphData.nodes || graphData.nodes.length === 0) return;
13117
+ const nodeTypes = Array.from(
13118
+ new Set(
13119
+ graphData.nodes.map((node) => node.data?.type).filter((type) => Boolean(type && typeof type === "string"))
13120
+ )
13121
+ ).sort();
13122
+ if (nodeTypes.length === 0) return;
13123
+ const colorMap = {};
13124
+ const nodeColorAccessor = this.config.nodeColor;
13125
+ if (nodeColorAccessor && typeof nodeColorAccessor === "function") {
13126
+ graphData.nodes.forEach((node) => {
13127
+ const type = node.data?.type;
13128
+ if (type) {
13129
+ const color2 = nodeColorAccessor(node);
13130
+ if (color2) {
13131
+ colorMap[type] = color2;
13132
+ }
13133
+ }
13134
+ });
13135
+ } else {
13136
+ const defaultColors = [
13137
+ "#3b82f6",
13138
+ "#ef4444",
13139
+ "#10b981",
13140
+ "#f59e0b",
13141
+ "#8b5cf6",
13142
+ "#06b6d4",
13143
+ "#84cc16",
13144
+ "#f97316",
13145
+ "#ec4899",
13146
+ "#6b7280"
13147
+ ];
13148
+ nodeTypes.forEach((type, index2) => {
13149
+ const color2 = defaultColors[index2 % defaultColors.length];
13150
+ if (type && color2) {
13151
+ colorMap[type] = color2;
13152
+ }
13153
+ });
13154
+ }
13155
+ const LEGEND_CONFIG = {
13156
+ padding: 20,
13157
+ circleRadius: 10,
13158
+ lineHeight: 40,
13159
+ circleTextGap: 12,
13160
+ containerPadding: 16,
13161
+ borderRadius: 16
13162
+ };
13163
+ ctx.save();
13164
+ ctx.font = "20px sans-serif";
13165
+ ctx.textBaseline = "middle";
13166
+ let maxTextWidth = 0;
13167
+ nodeTypes.forEach((type) => {
13168
+ if (type) {
13169
+ const textWidth = ctx.measureText(type).width;
13170
+ maxTextWidth = Math.max(maxTextWidth, textWidth);
13171
+ }
13172
+ });
13173
+ const legendContentWidth = LEGEND_CONFIG.circleRadius * 2 + LEGEND_CONFIG.circleTextGap + maxTextWidth;
13174
+ const legendContentHeight = nodeTypes.length * LEGEND_CONFIG.lineHeight - (LEGEND_CONFIG.lineHeight - LEGEND_CONFIG.circleRadius * 2);
13175
+ const containerWidth = legendContentWidth + LEGEND_CONFIG.containerPadding * 2;
13176
+ const containerHeight = legendContentHeight + LEGEND_CONFIG.containerPadding * 2;
13177
+ const position = this.config.legends.position || "top-right";
13178
+ let containerX, containerY;
13179
+ switch (position) {
13180
+ case "top-left":
13181
+ containerX = LEGEND_CONFIG.padding;
13182
+ containerY = LEGEND_CONFIG.padding;
13183
+ break;
13184
+ case "top-right":
13185
+ default:
13186
+ containerX = canvasWidth - containerWidth - LEGEND_CONFIG.padding;
13187
+ containerY = LEGEND_CONFIG.padding;
13188
+ break;
13189
+ case "bottom-left":
13190
+ containerX = LEGEND_CONFIG.padding;
13191
+ containerY = canvasHeight - containerHeight - LEGEND_CONFIG.padding;
13192
+ break;
13193
+ case "bottom-right":
13194
+ containerX = canvasWidth - containerWidth - LEGEND_CONFIG.padding;
13195
+ containerY = canvasHeight - containerHeight - LEGEND_CONFIG.padding;
13196
+ break;
13197
+ }
13198
+ ctx.shadowColor = "rgba(0, 0, 0, 0.1)";
13199
+ ctx.shadowBlur = 10;
13200
+ ctx.shadowOffsetX = 0;
13201
+ ctx.shadowOffsetY = 2;
13202
+ ctx.fillStyle = "#ffffff";
13203
+ this.drawRoundedRect(ctx, containerX, containerY, containerWidth, containerHeight, LEGEND_CONFIG.borderRadius);
13204
+ ctx.fill();
13205
+ ctx.restore();
13206
+ const startX = containerX + LEGEND_CONFIG.containerPadding;
13207
+ const startY = containerY + LEGEND_CONFIG.containerPadding;
13208
+ nodeTypes.forEach((type, index2) => {
13209
+ if (!type) return;
13210
+ const y3 = startY + index2 * LEGEND_CONFIG.lineHeight;
13211
+ const color2 = colorMap[type] || "#ccc";
13212
+ ctx.beginPath();
13213
+ ctx.arc(startX + LEGEND_CONFIG.circleRadius, y3 + LEGEND_CONFIG.circleRadius, LEGEND_CONFIG.circleRadius, 0, 2 * Math.PI);
13214
+ ctx.fillStyle = color2;
13215
+ ctx.fill();
13216
+ ctx.fillStyle = "#374151";
13217
+ ctx.fillText(type, startX + LEGEND_CONFIG.circleRadius * 2 + LEGEND_CONFIG.circleTextGap, y3 + LEGEND_CONFIG.circleRadius);
13218
+ });
13219
+ }
13220
+ /**
13221
+ * Draw rounded rectangle helper method
13222
+ */
13223
+ drawRoundedRect(ctx, x3, y3, width, height, radius) {
13224
+ ctx.beginPath();
13225
+ ctx.moveTo(x3 + radius, y3);
13226
+ ctx.lineTo(x3 + width - radius, y3);
13227
+ ctx.quadraticCurveTo(x3 + width, y3, x3 + width, y3 + radius);
13228
+ ctx.lineTo(x3 + width, y3 + height - radius);
13229
+ ctx.quadraticCurveTo(x3 + width, y3 + height, x3 + width - radius, y3 + height);
13230
+ ctx.lineTo(x3 + radius, y3 + height);
13231
+ ctx.quadraticCurveTo(x3, y3 + height, x3, y3 + height - radius);
13232
+ ctx.lineTo(x3, y3 + radius);
13233
+ ctx.quadraticCurveTo(x3, y3, x3 + radius, y3);
13234
+ ctx.closePath();
13235
+ }
13236
+ /**
13237
+ * Export graph as PNG image
13238
+ */
13239
+ async exportGraph(fileName) {
13240
+ if (!this.isInitialized) {
13241
+ throw new Error("Graph not initialized");
13242
+ }
13243
+ const start2 = performance.now();
13244
+ try {
13245
+ const defaultFileName = `polly-graph-export-${Date.now()}.png`;
13246
+ const finalFileName = fileName || defaultFileName;
13247
+ this.zoomToFit(40, 300);
13248
+ await new Promise((resolve) => {
13249
+ this.exportTimeoutId = setTimeout(() => {
13250
+ this.exportTimeoutId = null;
13251
+ resolve(void 0);
13252
+ }, 350);
13253
+ });
13254
+ const canvas = this.container.querySelector("canvas");
13255
+ if (!canvas) {
13256
+ throw new Error("Canvas not found");
13257
+ }
13258
+ const elementsToHide = [];
13259
+ const controls = this.container.querySelectorAll(".fg-controls");
13260
+ controls.forEach((control) => {
13261
+ const element = control;
13262
+ elementsToHide.push({ element, originalDisplay: element.style.display });
13263
+ element.style.display = "none";
13264
+ });
13265
+ const legends = this.container.querySelectorAll(".fg-legends");
13266
+ legends.forEach((legend) => {
13267
+ const element = legend;
13268
+ elementsToHide.push({ element, originalDisplay: element.style.display });
13269
+ element.style.display = "none";
13270
+ });
13271
+ await new Promise((resolve) => requestAnimationFrame(resolve));
13272
+ const exportCanvas = document.createElement("canvas");
13273
+ const ctx = exportCanvas.getContext("2d");
13274
+ if (!ctx) {
13275
+ throw new Error("Could not get canvas context");
13276
+ }
13277
+ exportCanvas.width = canvas.width;
13278
+ exportCanvas.height = canvas.height;
13279
+ ctx.fillStyle = this.config.backgroundColor || "#ffffff";
13280
+ ctx.fillRect(0, 0, exportCanvas.width, exportCanvas.height);
13281
+ ctx.drawImage(canvas, 0, 0);
13282
+ this.drawLegendsOnExportCanvas(ctx, exportCanvas.width, exportCanvas.height);
13283
+ exportCanvas.toBlob((blob) => {
13284
+ if (!blob) {
13285
+ throw new Error("Failed to create image blob");
13286
+ }
13287
+ const url = URL.createObjectURL(blob);
13288
+ const link = document.createElement("a");
13289
+ link.href = url;
13290
+ link.download = finalFileName;
13291
+ document.body.appendChild(link);
13292
+ link.click();
13293
+ document.body.removeChild(link);
13294
+ URL.revokeObjectURL(url);
13295
+ }, "image/png");
13296
+ elementsToHide.forEach(({ element, originalDisplay }) => {
13297
+ element.style.display = originalDisplay;
13298
+ });
13299
+ const duration = performance.now() - start2;
13300
+ if (this.config.enablePerformanceMonitoring) {
13301
+ console.log(`\u2705 exportGraph completed in ${duration.toFixed(3)}ms`);
13302
+ }
13303
+ } catch (error) {
13304
+ const controls = this.container.querySelectorAll(".fg-controls");
13305
+ controls.forEach((control) => {
13306
+ const element = control;
13307
+ element.style.display = "";
13308
+ });
13309
+ const legends = this.container.querySelectorAll(".fg-legends");
13310
+ legends.forEach((legend) => {
13311
+ const element = legend;
13312
+ element.style.display = "";
13313
+ });
13314
+ const duration = performance.now() - start2;
13315
+ if (this.config.enablePerformanceMonitoring) {
13316
+ console.log(`\u274C exportGraph failed in ${duration.toFixed(3)}ms`);
13317
+ }
13318
+ throw error;
13319
+ }
13320
+ }
13321
+ /**
13322
+ * Setup double-click functionality
13323
+ * - Double-click on node: center and zoom to node
13324
+ * - Double-click on canvas: fit view
13325
+ */
13326
+ setupDoubleClickHandling() {
13327
+ if (!this.isInitialized) return;
13328
+ const canvas = this.container.querySelector("canvas");
13329
+ if (!canvas) return;
13330
+ let clickTimeout = null;
13331
+ let clickCount = 0;
13332
+ let lastClickTime = 0;
13333
+ const DOUBLE_CLICK_DELAY = 300;
13334
+ const originalOnNodeClick = this.config.onNodeClick;
13335
+ if (this.canvasClickHandler) {
13336
+ canvas.removeEventListener("click", this.canvasClickHandler);
13337
+ }
13338
+ this.canvasClickHandler = (event) => {
13339
+ const currentTime = Date.now();
13340
+ clickCount++;
13341
+ if (clickCount === 1) {
13342
+ clickTimeout = window.setTimeout(() => {
13343
+ if (clickCount === 1) {
13344
+ const rect = canvas.getBoundingClientRect();
13345
+ const x3 = event.clientX - rect.left;
13346
+ const y3 = event.clientY - rect.top;
13347
+ const graphCoords = this.getGraphInstance().screen2GraphCoords(x3, y3);
13348
+ const clickedNode = this.findNodeAtPosition(graphCoords.x, graphCoords.y);
13349
+ if (clickedNode && originalOnNodeClick) {
13350
+ originalOnNodeClick(clickedNode, event);
13351
+ }
13352
+ }
13353
+ clickCount = 0;
13354
+ }, DOUBLE_CLICK_DELAY);
13355
+ } else if (clickCount === 2 && currentTime - lastClickTime < DOUBLE_CLICK_DELAY) {
13356
+ if (clickTimeout) {
13357
+ window.clearTimeout(clickTimeout);
13358
+ clickTimeout = null;
13359
+ }
13360
+ const rect = canvas.getBoundingClientRect();
13361
+ const x3 = event.clientX - rect.left;
13362
+ const y3 = event.clientY - rect.top;
13363
+ const graphCoords = this.getGraphInstance().screen2GraphCoords(x3, y3);
13364
+ const clickedNode = this.findNodeAtPosition(graphCoords.x, graphCoords.y);
13365
+ if (clickedNode) {
13366
+ if (this.config.onNodeDoubleClick) {
13367
+ this.config.onNodeDoubleClick(clickedNode, event);
13368
+ } else {
13369
+ this.centerAt(clickedNode.x || 0, clickedNode.y || 0, 400);
13370
+ this.zoom(8, 400);
13371
+ }
13372
+ } else {
13373
+ this.smoothZoomToFit(500);
13374
+ }
13375
+ clickCount = 0;
13376
+ } else {
13377
+ clickCount = 1;
13378
+ if (clickTimeout) {
13379
+ window.clearTimeout(clickTimeout);
13380
+ }
13381
+ clickTimeout = window.setTimeout(() => {
13382
+ clickCount = 0;
13383
+ }, DOUBLE_CLICK_DELAY);
13384
+ }
13385
+ lastClickTime = currentTime;
13386
+ };
13387
+ canvas.addEventListener("click", this.canvasClickHandler);
13388
+ if (this.forceGraph && originalOnNodeClick) {
13389
+ this.getGraphInstance().onNodeClick(null);
13390
+ }
13391
+ }
13392
+ /**
13393
+ * Smooth zoom to fit that animates from current position
13394
+ */
13395
+ smoothZoomToFit(duration = 500) {
13396
+ if (!this.isInitialized) this.initializeForceGraph();
13397
+ if (this.forceGraph && this.container) {
13398
+ const bbox = this.getGraphInstance().getGraphBbox();
13399
+ const width = bbox.x[1] - bbox.x[0];
13400
+ const height = bbox.y[1] - bbox.y[0];
13401
+ const centerX = (bbox.x[0] + bbox.x[1]) / 2;
13402
+ const centerY = (bbox.y[0] + bbox.y[1]) / 2;
13403
+ const paddingValue = 50;
13404
+ const viewportWidth = this.container.clientWidth - paddingValue;
13405
+ const viewportHeight = this.container.clientHeight - paddingValue;
13406
+ const targetZoom = Math.min(viewportWidth / width, viewportHeight / height);
13407
+ this.getGraphInstance().centerAt(centerX, centerY, duration / 2);
13408
+ setTimeout(() => {
13409
+ this.zoom(targetZoom, duration / 2);
13410
+ }, duration / 2);
13411
+ }
13412
+ }
13413
+ /**
13414
+ * Trigger Kapsule update to ensure render callbacks continue working
13415
+ * This is essential for onRenderFramePost callbacks after physics simulation stops
13416
+ */
13417
+ triggerKapsuleUpdate() {
13418
+ if (!this.isInitialized || !this.forceGraph) return;
13419
+ try {
13420
+ const kapsuleInstance = this._getForceGraphInstance();
13421
+ if (kapsuleInstance) {
13422
+ const state = kapsuleInstance["__state"];
13423
+ if (state && typeof state === "object") {
13424
+ state["needsRedraw"] = true;
13425
+ return;
13426
+ }
13427
+ const zoomFn = kapsuleInstance["zoom"];
13428
+ if (typeof zoomFn === "function") {
13429
+ const currentZoom = zoomFn();
13430
+ if (typeof currentZoom === "number") {
13431
+ zoomFn(currentZoom);
13432
+ }
13433
+ }
13434
+ }
13435
+ } catch (error) {
13436
+ if (this.config.enablePerformanceMonitoring) {
13437
+ console.log("Kapsule update fallback used:", error);
13438
+ }
13439
+ }
13440
+ }
13441
+ /**
13442
+ * Find node at given graph coordinates
13443
+ */
13444
+ findNodeAtPosition(graphX, graphY) {
13445
+ if (!this.config.graphData?.nodes) return null;
13446
+ const nodeRadius = 4;
13447
+ const tolerance = nodeRadius + 2;
13448
+ for (const node of this.config.graphData.nodes) {
13449
+ if (typeof node.x === "number" && typeof node.y === "number") {
13450
+ const distance = Math.sqrt(
13451
+ Math.pow(node.x - graphX, 2) + Math.pow(node.y - graphY, 2)
13452
+ );
13453
+ if (distance <= tolerance) {
13454
+ return node;
13455
+ }
13456
+ }
13457
+ }
13458
+ return null;
13459
+ }
13460
+ destroy() {
13461
+ if (this.isDestroyed) return;
13462
+ this.cleanupControls();
13463
+ this.cleanupLegends();
13464
+ this.cleanupPageVisibilityHandling();
13465
+ this.cleanupDeterministicPhysics();
13466
+ this.cleanupDeferredFitView();
13467
+ this.cleanupPerformanceTest();
13468
+ this.cleanupExportTimeout();
13469
+ this.cleanupCanvasEventListeners();
13470
+ physicsWorkerManager.terminate();
13471
+ this.stopRenderingMonitoring();
13472
+ if (this.canvasOptimizer) {
13473
+ this.canvasOptimizer.destroy?.();
13474
+ }
13475
+ if (this.forceGraph) {
13476
+ this.getGraphInstance().onRenderFramePre(void 0);
13477
+ this.getGraphInstance().onRenderFramePost(void 0);
13478
+ this.getGraphInstance().onNodeClick(void 0);
13479
+ this.getGraphInstance().onNodeHover(void 0);
13480
+ this.getGraphInstance().onLinkClick(void 0);
13481
+ this.getGraphInstance().onLinkHover(void 0);
13482
+ this.getGraphInstance().onEngineTick(void 0);
13483
+ this.getGraphInstance().onEngineStop(void 0);
13484
+ const graphInstance = this.getGraphInstance();
13485
+ if (typeof graphInstance.destroy === "function") {
13486
+ graphInstance.destroy();
13487
+ }
13488
+ }
13489
+ if (this.container) {
13490
+ this.container.innerHTML = "";
13491
+ }
13492
+ this.performanceMonitor.logSummary();
13493
+ this.forceGraph = null;
13494
+ this.renderingMonitor = null;
13495
+ this.canvasOptimizer = null;
13496
+ this.isDestroyed = true;
13497
+ this.isInitialized = false;
13498
+ }
13499
+ _getForceGraphInstance() {
13500
+ return this.forceGraph;
13501
+ }
13502
+ };
13503
+ function createForceGraph(container, config) {
13504
+ return new ForceGraphWrapper(container, config);
13505
+ }
13506
+
13507
+ // src/force-graph-wrapper/types/config.types.ts
13508
+ var ConfigPresets = {
13509
+ /**
13510
+ * High-performance preset for large graphs (5000+ nodes)
13511
+ */
13512
+ highPerformance: () => ({
13513
+ autoPauseRedraw: true,
13514
+ warmupTicks: 100,
13515
+ cooldownTime: 1e3,
13516
+ d3AlphaDecay: 0.05,
13517
+ d3VelocityDecay: 0.4,
13518
+ enablePerformanceMonitoring: true,
13519
+ performanceTargets: {
13520
+ creation: 5,
13521
+ // 5ms
13522
+ dataLoad: 100,
13523
+ // 100ms
13524
+ firstRender: 500,
13525
+ // 500ms
13526
+ methodCall: 1,
13527
+ // 1ms
13528
+ memoryGrowth: 10
13529
+ // 10MB per 1000 nodes
13530
+ }
13531
+ }),
13532
+ /**
13533
+ * Interactive preset for medium graphs (50-1000 nodes)
13534
+ */
13535
+ interactive: () => ({
13536
+ enablePointerInteraction: true,
13537
+ enableNodeDrag: true,
13538
+ enableZoomInteraction: true,
13539
+ enablePanInteraction: true,
13540
+ linkHoverPrecision: 2,
13541
+ cooldownTime: 2e3,
13542
+ controls: {
13543
+ enabled: true,
13544
+ position: "bottom-left",
13545
+ orientation: "vertical"
13546
+ },
13547
+ legends: {
13548
+ enabled: true,
13549
+ position: "top-right",
13550
+ maxItems: 10
13551
+ },
13552
+ performanceTargets: {
13553
+ creation: 1,
13554
+ dataLoad: 20,
13555
+ firstRender: 50,
13556
+ methodCall: 0.5,
13557
+ memoryGrowth: 5
13558
+ }
13559
+ }),
13560
+ /**
13561
+ * Minimal preset for small graphs (5-50 nodes)
13562
+ */
13563
+ minimal: () => ({
13564
+ width: 400,
13565
+ height: 300,
13566
+ backgroundColor: "#ffffff",
13567
+ cooldownTime: 1e3,
13568
+ enablePerformanceMonitoring: false
13569
+ })
13570
+ };
13571
+ var ForceGraphConfigBuilder = class {
13572
+ config = {};
13573
+ constructor(container) {
13574
+ this.config.container = container;
13575
+ }
13576
+ data(graphData) {
13577
+ this.config.graphData = graphData;
13578
+ return this;
13579
+ }
13580
+ size(width, height) {
13581
+ this.config.width = width;
13582
+ this.config.height = height;
13583
+ return this;
13584
+ }
13585
+ performance(preset) {
13586
+ Object.assign(this.config, ConfigPresets[preset]());
13587
+ return this;
13588
+ }
13589
+ nodeColor(accessor) {
13590
+ this.config.nodeColor = accessor;
13591
+ return this;
13592
+ }
13593
+ onNodeClick(handler) {
13594
+ this.config.onNodeClick = handler;
13595
+ return this;
13596
+ }
13597
+ build() {
13598
+ if (!this.config.container || !this.config.graphData) {
13599
+ throw new Error("Container and graphData are required");
13600
+ }
13601
+ return this.config;
13602
+ }
13603
+ };
13604
+
13605
+ // src/force-graph-wrapper/index.ts
13606
+ var VERSION = "1.0.0-alpha.1";
13607
+ var LIBRARY_INFO = {
13608
+ name: "force-graph-wrapper",
13609
+ version: VERSION,
13610
+ description: "Framework-independent TypeScript wrapper for force-graph library",
13611
+ performance: "Built-in performance monitoring and optimization",
13612
+ compatibility: "Vanilla JS, React, Angular, Vue.js"
13613
+ };
10072
13614
  export {
10073
13615
  CanvasManager,
13616
+ ConfigPresets,
10074
13617
  DEFAULT_COLORS,
10075
13618
  DEFAULT_HOVER_STYLES,
10076
13619
  DEFAULT_LINK_LABEL_STYLE,
10077
13620
  DEFAULT_LINK_STYLE,
10078
13621
  DragManager,
10079
13622
  ErrorHandler,
13623
+ ForceGraphConfigBuilder,
13624
+ ForceGraphWrapper,
10080
13625
  HoverManager,
13626
+ LIBRARY_INFO,
10081
13627
  NeutralColor,
13628
+ PerformanceMonitor,
10082
13629
  PhysicsManager,
10083
13630
  PointerManager,
10084
13631
  PrimaryColor,
@@ -10087,8 +13634,10 @@ export {
10087
13634
  SelectionManager,
10088
13635
  StandardColor,
10089
13636
  V2Graph,
13637
+ VERSION,
10090
13638
  ValidationError,
10091
13639
  ZoomManager,
13640
+ createForceGraph,
10092
13641
  createV2Graph,
10093
13642
  getIcon,
10094
13643
  getIconSvg,