polly-graph 0.2.5 → 0.2.7

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