force-3d-graph 1.2.5 → 1.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  var ot = Object.defineProperty;
2
- var nt = (d, e, s) => e in d ? ot(d, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : d[e] = s;
3
- var l = (d, e, s) => nt(d, typeof e != "symbol" ? e + "" : e, s);
2
+ var nt = (h, e, s) => e in h ? ot(h, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : h[e] = s;
3
+ var l = (h, e, s) => nt(h, typeof e != "symbol" ? e + "" : e, s);
4
4
  import * as y from "three";
5
- import { EventDispatcher as at, Vector3 as S, MOUSE as $, TOUCH as G, Spherical as ze, Quaternion as Te, Vector2 as T, Ray as rt, Plane as lt, MathUtils as ct } from "three";
5
+ import { EventDispatcher as at, Vector3 as S, MOUSE as $, TOUCH as G, Spherical as ze, Quaternion as ke, Vector2 as k, Ray as rt, Plane as lt, MathUtils as ct } from "three";
6
6
  const P = {
7
7
  backgroundColor: 657930,
8
8
  cameraPosition: { x: 0, y: 0, z: 80 },
@@ -28,55 +28,56 @@ const P = {
28
28
  searchPlaceholder: "Search nodes or relationships...",
29
29
  initialViewMode: "3d",
30
30
  showViewToggle: !0,
31
+ showLegend: !0,
31
32
  targetFPS: 60,
32
33
  maxVisibleNodes: 1e4
33
34
  };
34
- var X = /* @__PURE__ */ ((d) => (d[d.HIGH = 0] = "HIGH", d[d.MEDIUM = 1] = "MEDIUM", d[d.LOW = 2] = "LOW", d))(X || {});
35
- function ht() {
36
- const d = document.createElement("div");
37
- return d.id = "force-graph-3d-container", d.style.cssText = `
35
+ var X = /* @__PURE__ */ ((h) => (h[h.HIGH = 0] = "HIGH", h[h.MEDIUM = 1] = "MEDIUM", h[h.LOW = 2] = "LOW", h))(X || {});
36
+ function dt() {
37
+ const h = document.createElement("div");
38
+ return h.id = "force-graph-3d-container", h.style.cssText = `
38
39
  width: 100%;
39
40
  height: 100%;
40
41
  position: absolute;
41
42
  top: 0;
42
43
  left: 0;
43
44
  overflow: hidden;
44
- `, document.body.appendChild(d), d;
45
+ `, document.body.appendChild(h), h;
45
46
  }
46
- function dt(d) {
47
- return d && d instanceof HTMLElement ? d : (console.warn("[ForceGraph3D] No container provided, creating one automatically"), ht());
47
+ function ht(h) {
48
+ return h && h instanceof HTMLElement ? h : (console.warn("[ForceGraph3D] No container provided, creating one automatically"), dt());
48
49
  }
49
- function ke(d) {
50
- const e = d.getBoundingClientRect();
50
+ function Te(h) {
51
+ const e = h.getBoundingClientRect();
51
52
  return {
52
53
  width: e.width || window.innerWidth,
53
54
  height: e.height || window.innerHeight
54
55
  };
55
56
  }
56
- function He(d) {
57
- if (!d || typeof d != "object")
57
+ function De(h) {
58
+ if (!h || typeof h != "object")
58
59
  return console.warn("[ForceGraph3D] Invalid node: must be an object"), !1;
59
- const e = d;
60
+ const e = h;
60
61
  return typeof e.id != "string" || e.id.trim() === "" ? (console.warn("[ForceGraph3D] Invalid node: id must be a non-empty string"), !1) : typeof e.label != "string" ? (console.warn("[ForceGraph3D] Invalid node: label must be a string"), !1) : e.color !== void 0 && typeof e.color != "number" ? (console.warn("[ForceGraph3D] Invalid node: color must be a number (hex)"), !1) : e.position !== void 0 && !gt(e.position) ? (console.warn("[ForceGraph3D] Invalid node: position must have x, y, z numbers"), !1) : !0;
61
62
  }
62
- function De(d) {
63
- if (!d || typeof d != "object")
63
+ function He(h) {
64
+ if (!h || typeof h != "object")
64
65
  return console.warn("[ForceGraph3D] Invalid edge: must be an object"), !1;
65
- const e = d;
66
+ const e = h;
66
67
  return typeof e.source != "string" || e.source.trim() === "" ? (console.warn("[ForceGraph3D] Invalid edge: source must be a non-empty string"), !1) : typeof e.target != "string" || e.target.trim() === "" ? (console.warn("[ForceGraph3D] Invalid edge: target must be a non-empty string"), !1) : !0;
67
68
  }
68
- function pt(d) {
69
- return typeof d != "string" || d.trim() === "" ? (console.warn("[ForceGraph3D] Invalid node ID: must be a non-empty string"), !1) : !0;
69
+ function pt(h) {
70
+ return typeof h != "string" || h.trim() === "" ? (console.warn("[ForceGraph3D] Invalid node ID: must be a non-empty string"), !1) : !0;
70
71
  }
71
- function gt(d) {
72
- if (!d || typeof d != "object") return !1;
73
- const e = d;
72
+ function gt(h) {
73
+ if (!h || typeof h != "object") return !1;
74
+ const e = h;
74
75
  return typeof e.x == "number" && typeof e.y == "number" && typeof e.z == "number";
75
76
  }
76
- function D(d, e) {
77
- return d === e ? `${d}-${e}` : d < e ? `${d}-${e}` : `${e}-${d}`;
77
+ function H(h, e) {
78
+ return h === e ? `${h}-${e}` : h < e ? `${h}-${e}` : `${e}-${h}`;
78
79
  }
79
- const Pe = { type: "change" }, ce = { type: "start" }, Re = { type: "end" }, J = new rt(), Ie = new lt(), ut = Math.cos(70 * ct.DEG2RAD);
80
+ const Pe = { type: "change" }, ce = { type: "start" }, Le = { type: "end" }, J = new rt(), Re = new lt(), ut = Math.cos(70 * ct.DEG2RAD);
80
81
  class ft extends at {
81
82
  constructor(e, s) {
82
83
  super(), this.object = e, this.domElement = s, this.domElement.style.touchAction = "none", this.enabled = !0, this.target = new S(), this.cursor = new S(), this.minDistance = 0, this.maxDistance = 1 / 0, this.minZoom = 0, this.maxZoom = 1 / 0, this.minTargetRadius = 0, this.maxTargetRadius = 1 / 0, this.minPolarAngle = 0, this.maxPolarAngle = Math.PI, this.minAzimuthAngle = -1 / 0, this.maxAzimuthAngle = 1 / 0, this.enableDamping = !1, this.dampingFactor = 0.05, this.enableZoom = !0, this.zoomSpeed = 1, this.enableRotate = !0, this.rotateSpeed = 1, this.enablePan = !0, this.panSpeed = 1, this.screenSpacePanning = !0, this.keyPanSpeed = 7, this.zoomToCursor = !1, this.autoRotate = !1, this.autoRotateSpeed = 2, this.keys = { LEFT: "ArrowLeft", UP: "ArrowUp", RIGHT: "ArrowRight", BOTTOM: "ArrowDown" }, this.mouseButtons = { LEFT: $.ROTATE, MIDDLE: $.DOLLY, RIGHT: $.PAN }, this.touches = { ONE: G.ROTATE, TWO: G.DOLLY_PAN }, this.target0 = this.target.clone(), this.position0 = this.object.position.clone(), this.zoom0 = this.object.zoom, this._domElementKeyEvents = null, this.getPolarAngle = function() {
@@ -92,37 +93,37 @@ class ft extends at {
92
93
  }, this.saveState = function() {
93
94
  t.target0.copy(t.target), t.position0.copy(t.object.position), t.zoom0 = t.object.zoom;
94
95
  }, this.reset = function() {
95
- t.target.copy(t.target0), t.object.position.copy(t.position0), t.object.zoom = t.zoom0, t.object.updateProjectionMatrix(), t.dispatchEvent(Pe), t.update(), i = o.NONE;
96
+ t.target.copy(t.target0), t.object.position.copy(t.position0), t.object.zoom = t.zoom0, t.object.updateProjectionMatrix(), t.dispatchEvent(Pe), t.update(), o = i.NONE;
96
97
  }, this.update = function() {
97
- const n = new S(), g = new Te().setFromUnitVectors(e.up, new S(0, 1, 0)), v = g.clone().invert(), w = new S(), C = new Te(), F = new S(), z = 2 * Math.PI;
98
+ const n = new S(), g = new ke().setFromUnitVectors(e.up, new S(0, 1, 0)), v = g.clone().invert(), w = new S(), C = new ke(), F = new S(), z = 2 * Math.PI;
98
99
  return function(it = null) {
99
100
  const Se = t.object.position;
100
- n.copy(Se).sub(t.target), n.applyQuaternion(g), r.setFromVector3(n), t.autoRotate && i === o.NONE && B(Ae(it)), t.enableDamping ? (r.theta += c.theta * t.dampingFactor, r.phi += c.phi * t.dampingFactor) : (r.theta += c.theta, r.phi += c.phi);
101
- let I = t.minAzimuthAngle, L = t.maxAzimuthAngle;
102
- isFinite(I) && isFinite(L) && (I < -Math.PI ? I += z : I > Math.PI && (I -= z), L < -Math.PI ? L += z : L > Math.PI && (L -= z), I <= L ? r.theta = Math.max(I, Math.min(L, r.theta)) : r.theta = r.theta > (I + L) / 2 ? Math.max(I, r.theta) : Math.min(L, r.theta)), r.phi = Math.max(t.minPolarAngle, Math.min(t.maxPolarAngle, r.phi)), r.makeSafe(), t.enableDamping === !0 ? t.target.addScaledVector(p, t.dampingFactor) : t.target.add(p), t.target.sub(t.cursor), t.target.clampLength(t.minTargetRadius, t.maxTargetRadius), t.target.add(t.cursor), t.zoomToCursor && H || t.object.isOrthographicCamera ? r.radius = ne(r.radius) : r.radius = ne(r.radius * h), n.setFromSpherical(r), n.applyQuaternion(v), Se.copy(t.target).add(n), t.object.lookAt(t.target), t.enableDamping === !0 ? (c.theta *= 1 - t.dampingFactor, c.phi *= 1 - t.dampingFactor, p.multiplyScalar(1 - t.dampingFactor)) : (c.set(0, 0, 0), p.set(0, 0, 0));
101
+ n.copy(Se).sub(t.target), n.applyQuaternion(g), r.setFromVector3(n), t.autoRotate && o === i.NONE && B(Ae(it)), t.enableDamping ? (r.theta += c.theta * t.dampingFactor, r.phi += c.phi * t.dampingFactor) : (r.theta += c.theta, r.phi += c.phi);
102
+ let R = t.minAzimuthAngle, I = t.maxAzimuthAngle;
103
+ isFinite(R) && isFinite(I) && (R < -Math.PI ? R += z : R > Math.PI && (R -= z), I < -Math.PI ? I += z : I > Math.PI && (I -= z), R <= I ? r.theta = Math.max(R, Math.min(I, r.theta)) : r.theta = r.theta > (R + I) / 2 ? Math.max(R, r.theta) : Math.min(I, r.theta)), r.phi = Math.max(t.minPolarAngle, Math.min(t.maxPolarAngle, r.phi)), r.makeSafe(), t.enableDamping === !0 ? t.target.addScaledVector(p, t.dampingFactor) : t.target.add(p), t.target.sub(t.cursor), t.target.clampLength(t.minTargetRadius, t.maxTargetRadius), t.target.add(t.cursor), t.zoomToCursor && D || t.object.isOrthographicCamera ? r.radius = ne(r.radius) : r.radius = ne(r.radius * d), n.setFromSpherical(r), n.applyQuaternion(v), Se.copy(t.target).add(n), t.object.lookAt(t.target), t.enableDamping === !0 ? (c.theta *= 1 - t.dampingFactor, c.phi *= 1 - t.dampingFactor, p.multiplyScalar(1 - t.dampingFactor)) : (c.set(0, 0, 0), p.set(0, 0, 0));
103
104
  let le = !1;
104
- if (t.zoomToCursor && H) {
105
+ if (t.zoomToCursor && D) {
105
106
  let U = null;
106
107
  if (t.object.isPerspectiveCamera) {
107
108
  const _ = n.length();
108
- U = ne(_ * h);
109
+ U = ne(_ * d);
109
110
  const Q = _ - U;
110
111
  t.object.position.addScaledVector(Y, Q), t.object.updateMatrixWorld();
111
112
  } else if (t.object.isOrthographicCamera) {
112
- const _ = new S(k.x, k.y, 0);
113
- _.unproject(t.object), t.object.zoom = Math.max(t.minZoom, Math.min(t.maxZoom, t.object.zoom / h)), t.object.updateProjectionMatrix(), le = !0;
114
- const Q = new S(k.x, k.y, 0);
113
+ const _ = new S(T.x, T.y, 0);
114
+ _.unproject(t.object), t.object.zoom = Math.max(t.minZoom, Math.min(t.maxZoom, t.object.zoom / d)), t.object.updateProjectionMatrix(), le = !0;
115
+ const Q = new S(T.x, T.y, 0);
115
116
  Q.unproject(t.object), t.object.position.sub(Q).add(_), t.object.updateMatrixWorld(), U = n.length();
116
117
  } else
117
118
  console.warn("WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled."), t.zoomToCursor = !1;
118
- U !== null && (this.screenSpacePanning ? t.target.set(0, 0, -1).transformDirection(t.object.matrix).multiplyScalar(U).add(t.object.position) : (J.origin.copy(t.object.position), J.direction.set(0, 0, -1).transformDirection(t.object.matrix), Math.abs(t.object.up.dot(J.direction)) < ut ? e.lookAt(t.target) : (Ie.setFromNormalAndCoplanarPoint(t.object.up, t.target), J.intersectPlane(Ie, t.target))));
119
- } else t.object.isOrthographicCamera && (t.object.zoom = Math.max(t.minZoom, Math.min(t.maxZoom, t.object.zoom / h)), t.object.updateProjectionMatrix(), le = !0);
120
- return h = 1, H = !1, le || w.distanceToSquared(t.object.position) > a || 8 * (1 - C.dot(t.object.quaternion)) > a || F.distanceToSquared(t.target) > 0 ? (t.dispatchEvent(Pe), w.copy(t.object.position), C.copy(t.object.quaternion), F.copy(t.target), !0) : !1;
119
+ U !== null && (this.screenSpacePanning ? t.target.set(0, 0, -1).transformDirection(t.object.matrix).multiplyScalar(U).add(t.object.position) : (J.origin.copy(t.object.position), J.direction.set(0, 0, -1).transformDirection(t.object.matrix), Math.abs(t.object.up.dot(J.direction)) < ut ? e.lookAt(t.target) : (Re.setFromNormalAndCoplanarPoint(t.object.up, t.target), J.intersectPlane(Re, t.target))));
120
+ } else t.object.isOrthographicCamera && (t.object.zoom = Math.max(t.minZoom, Math.min(t.maxZoom, t.object.zoom / d)), t.object.updateProjectionMatrix(), le = !0);
121
+ return d = 1, D = !1, le || w.distanceToSquared(t.object.position) > a || 8 * (1 - C.dot(t.object.quaternion)) > a || F.distanceToSquared(t.target) > 0 ? (t.dispatchEvent(Pe), w.copy(t.object.position), C.copy(t.object.quaternion), F.copy(t.target), !0) : !1;
121
122
  };
122
123
  }(), this.dispose = function() {
123
124
  t.domElement.removeEventListener("contextmenu", Ce), t.domElement.removeEventListener("pointerdown", Me), t.domElement.removeEventListener("pointercancel", K), t.domElement.removeEventListener("wheel", we), t.domElement.removeEventListener("pointermove", ae), t.domElement.removeEventListener("pointerup", K), t._domElementKeyEvents !== null && (t._domElementKeyEvents.removeEventListener("keydown", re), t._domElementKeyEvents = null);
124
125
  };
125
- const t = this, o = {
126
+ const t = this, i = {
126
127
  NONE: -1,
127
128
  ROTATE: 0,
128
129
  DOLLY: 1,
@@ -132,11 +133,11 @@ class ft extends at {
132
133
  TOUCH_DOLLY_PAN: 5,
133
134
  TOUCH_DOLLY_ROTATE: 6
134
135
  };
135
- let i = o.NONE;
136
+ let o = i.NONE;
136
137
  const a = 1e-6, r = new ze(), c = new ze();
137
- let h = 1;
138
- const p = new S(), x = new T(), m = new T(), u = new T(), f = new T(), b = new T(), M = new T(), N = new T(), O = new T(), R = new T(), Y = new S(), k = new T();
139
- let H = !1;
138
+ let d = 1;
139
+ const p = new S(), x = new k(), m = new k(), u = new k(), f = new k(), b = new k(), M = new k(), N = new k(), O = new k(), L = new k(), Y = new S(), T = new k();
140
+ let D = !1;
140
141
  const E = [], q = {};
141
142
  let se = !1;
142
143
  function Ae(n) {
@@ -152,12 +153,12 @@ class ft extends at {
152
153
  function Z(n) {
153
154
  c.phi -= n;
154
155
  }
155
- const he = function() {
156
+ const de = function() {
156
157
  const n = new S();
157
158
  return function(v, w) {
158
159
  n.setFromMatrixColumn(w, 0), n.multiplyScalar(-v), p.add(n);
159
160
  };
160
- }(), de = function() {
161
+ }(), he = function() {
161
162
  const n = new S();
162
163
  return function(v, w) {
163
164
  t.screenSpacePanning === !0 ? n.setFromMatrixColumn(w, 1) : (n.setFromMatrixColumn(w, 0), n.crossVectors(t.object.up, n)), n.multiplyScalar(v), p.add(n);
@@ -170,22 +171,22 @@ class ft extends at {
170
171
  const F = t.object.position;
171
172
  n.copy(F).sub(t.target);
172
173
  let z = n.length();
173
- z *= Math.tan(t.object.fov / 2 * Math.PI / 180), he(2 * v * z / C.clientHeight, t.object.matrix), de(2 * w * z / C.clientHeight, t.object.matrix);
174
- } else t.object.isOrthographicCamera ? (he(v * (t.object.right - t.object.left) / t.object.zoom / C.clientWidth, t.object.matrix), de(w * (t.object.top - t.object.bottom) / t.object.zoom / C.clientHeight, t.object.matrix)) : (console.warn("WARNING: OrbitControls.js encountered an unknown camera type - pan disabled."), t.enablePan = !1);
174
+ z *= Math.tan(t.object.fov / 2 * Math.PI / 180), de(2 * v * z / C.clientHeight, t.object.matrix), he(2 * w * z / C.clientHeight, t.object.matrix);
175
+ } else t.object.isOrthographicCamera ? (de(v * (t.object.right - t.object.left) / t.object.zoom / C.clientWidth, t.object.matrix), he(w * (t.object.top - t.object.bottom) / t.object.zoom / C.clientHeight, t.object.matrix)) : (console.warn("WARNING: OrbitControls.js encountered an unknown camera type - pan disabled."), t.enablePan = !1);
175
176
  };
176
177
  }();
177
178
  function ie(n) {
178
- t.object.isPerspectiveCamera || t.object.isOrthographicCamera ? h /= n : (console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."), t.enableZoom = !1);
179
+ t.object.isPerspectiveCamera || t.object.isOrthographicCamera ? d /= n : (console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."), t.enableZoom = !1);
179
180
  }
180
181
  function pe(n) {
181
- t.object.isPerspectiveCamera || t.object.isOrthographicCamera ? h *= n : (console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."), t.enableZoom = !1);
182
+ t.object.isPerspectiveCamera || t.object.isOrthographicCamera ? d *= n : (console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."), t.enableZoom = !1);
182
183
  }
183
184
  function oe(n, g) {
184
185
  if (!t.zoomToCursor)
185
186
  return;
186
- H = !0;
187
+ D = !0;
187
188
  const v = t.domElement.getBoundingClientRect(), w = n - v.left, C = g - v.top, F = v.width, z = v.height;
188
- k.x = w / F * 2 - 1, k.y = -(C / z) * 2 + 1, Y.set(k.x, k.y, 1).unproject(t.object).sub(t.object.position).normalize();
189
+ T.x = w / F * 2 - 1, T.y = -(C / z) * 2 + 1, Y.set(T.x, T.y, 1).unproject(t.object).sub(t.object.position).normalize();
189
190
  }
190
191
  function ne(n) {
191
192
  return Math.max(t.minDistance, Math.min(t.maxDistance, n));
@@ -205,7 +206,7 @@ class ft extends at {
205
206
  B(2 * Math.PI * u.x / g.clientHeight), Z(2 * Math.PI * u.y / g.clientHeight), x.copy(m), t.update();
206
207
  }
207
208
  function Ge(n) {
208
- O.set(n.clientX, n.clientY), R.subVectors(O, N), R.y > 0 ? ie(W(R.y)) : R.y < 0 && pe(W(R.y)), N.copy(O), t.update();
209
+ O.set(n.clientX, n.clientY), L.subVectors(O, N), L.y > 0 ? ie(W(L.y)) : L.y < 0 && pe(W(L.y)), N.copy(O), t.update();
209
210
  }
210
211
  function Ye(n) {
211
212
  b.set(n.clientX, n.clientY), M.subVectors(b, f).multiplyScalar(t.panSpeed), A(M.x, M.y), f.copy(b), t.update();
@@ -279,7 +280,7 @@ class ft extends at {
279
280
  }
280
281
  function ve(n) {
281
282
  const g = j(n), v = n.pageX - g.x, w = n.pageY - g.y, C = Math.sqrt(v * v + w * w);
282
- O.set(0, C), R.set(0, Math.pow(O.y / N.y, t.zoomSpeed)), ie(R.y), N.copy(O);
283
+ O.set(0, C), L.set(0, Math.pow(O.y / N.y, t.zoomSpeed)), ie(L.y), N.copy(O);
283
284
  const F = (n.pageX + g.x) * 0.5, z = (n.pageY + g.y) * 0.5;
284
285
  oe(F, z);
285
286
  }
@@ -296,7 +297,7 @@ class ft extends at {
296
297
  t.enabled !== !1 && (n.pointerType === "touch" ? et(n) : We(n));
297
298
  }
298
299
  function K(n) {
299
- st(n), E.length === 0 && (t.domElement.releasePointerCapture(n.pointerId), t.domElement.removeEventListener("pointermove", ae), t.domElement.removeEventListener("pointerup", K)), t.dispatchEvent(Re), i = o.NONE;
300
+ st(n), E.length === 0 && (t.domElement.releasePointerCapture(n.pointerId), t.domElement.removeEventListener("pointermove", ae), t.domElement.removeEventListener("pointerup", K)), t.dispatchEvent(Le), o = i.NONE;
300
301
  }
301
302
  function qe(n) {
302
303
  let g;
@@ -316,49 +317,49 @@ class ft extends at {
316
317
  switch (g) {
317
318
  case $.DOLLY:
318
319
  if (t.enableZoom === !1) return;
319
- je(n), i = o.DOLLY;
320
+ je(n), o = i.DOLLY;
320
321
  break;
321
322
  case $.ROTATE:
322
323
  if (n.ctrlKey || n.metaKey || n.shiftKey) {
323
324
  if (t.enablePan === !1) return;
324
- ue(n), i = o.PAN;
325
+ ue(n), o = i.PAN;
325
326
  } else {
326
327
  if (t.enableRotate === !1) return;
327
- ge(n), i = o.ROTATE;
328
+ ge(n), o = i.ROTATE;
328
329
  }
329
330
  break;
330
331
  case $.PAN:
331
332
  if (n.ctrlKey || n.metaKey || n.shiftKey) {
332
333
  if (t.enableRotate === !1) return;
333
- ge(n), i = o.ROTATE;
334
+ ge(n), o = i.ROTATE;
334
335
  } else {
335
336
  if (t.enablePan === !1) return;
336
- ue(n), i = o.PAN;
337
+ ue(n), o = i.PAN;
337
338
  }
338
339
  break;
339
340
  default:
340
- i = o.NONE;
341
+ o = i.NONE;
341
342
  }
342
- i !== o.NONE && t.dispatchEvent(ce);
343
+ o !== i.NONE && t.dispatchEvent(ce);
343
344
  }
344
345
  function We(n) {
345
- switch (i) {
346
- case o.ROTATE:
346
+ switch (o) {
347
+ case i.ROTATE:
347
348
  if (t.enableRotate === !1) return;
348
349
  $e(n);
349
350
  break;
350
- case o.DOLLY:
351
+ case i.DOLLY:
351
352
  if (t.enableZoom === !1) return;
352
353
  Ge(n);
353
354
  break;
354
- case o.PAN:
355
+ case i.PAN:
355
356
  if (t.enablePan === !1) return;
356
357
  Ye(n);
357
358
  break;
358
359
  }
359
360
  }
360
361
  function we(n) {
361
- t.enabled === !1 || t.enableZoom === !1 || i !== o.NONE || (n.preventDefault(), t.dispatchEvent(ce), Be(Ze(n)), t.dispatchEvent(Re));
362
+ t.enabled === !1 || t.enableZoom === !1 || o !== i.NONE || (n.preventDefault(), t.dispatchEvent(ce), Be(Ze(n)), t.dispatchEvent(Le));
362
363
  }
363
364
  function Ze(n) {
364
365
  const g = n.deltaMode, v = {
@@ -391,55 +392,55 @@ class ft extends at {
391
392
  switch (t.touches.ONE) {
392
393
  case G.ROTATE:
393
394
  if (t.enableRotate === !1) return;
394
- fe(n), i = o.TOUCH_ROTATE;
395
+ fe(n), o = i.TOUCH_ROTATE;
395
396
  break;
396
397
  case G.PAN:
397
398
  if (t.enablePan === !1) return;
398
- me(n), i = o.TOUCH_PAN;
399
+ me(n), o = i.TOUCH_PAN;
399
400
  break;
400
401
  default:
401
- i = o.NONE;
402
+ o = i.NONE;
402
403
  }
403
404
  break;
404
405
  case 2:
405
406
  switch (t.touches.TWO) {
406
407
  case G.DOLLY_PAN:
407
408
  if (t.enableZoom === !1 && t.enablePan === !1) return;
408
- Ue(n), i = o.TOUCH_DOLLY_PAN;
409
+ Ue(n), o = i.TOUCH_DOLLY_PAN;
409
410
  break;
410
411
  case G.DOLLY_ROTATE:
411
412
  if (t.enableZoom === !1 && t.enableRotate === !1) return;
412
- _e(n), i = o.TOUCH_DOLLY_ROTATE;
413
+ _e(n), o = i.TOUCH_DOLLY_ROTATE;
413
414
  break;
414
415
  default:
415
- i = o.NONE;
416
+ o = i.NONE;
416
417
  }
417
418
  break;
418
419
  default:
419
- i = o.NONE;
420
+ o = i.NONE;
420
421
  }
421
- i !== o.NONE && t.dispatchEvent(ce);
422
+ o !== i.NONE && t.dispatchEvent(ce);
422
423
  }
423
424
  function et(n) {
424
- switch (Ne(n), i) {
425
- case o.TOUCH_ROTATE:
425
+ switch (Ne(n), o) {
426
+ case i.TOUCH_ROTATE:
426
427
  if (t.enableRotate === !1) return;
427
428
  xe(n), t.update();
428
429
  break;
429
- case o.TOUCH_PAN:
430
+ case i.TOUCH_PAN:
430
431
  if (t.enablePan === !1) return;
431
432
  be(n), t.update();
432
433
  break;
433
- case o.TOUCH_DOLLY_PAN:
434
+ case i.TOUCH_DOLLY_PAN:
434
435
  if (t.enableZoom === !1 && t.enablePan === !1) return;
435
436
  Xe(n), t.update();
436
437
  break;
437
- case o.TOUCH_DOLLY_ROTATE:
438
+ case i.TOUCH_DOLLY_ROTATE:
438
439
  if (t.enableZoom === !1 && t.enableRotate === !1) return;
439
440
  Ve(n), t.update();
440
441
  break;
441
442
  default:
442
- i = o.NONE;
443
+ o = i.NONE;
443
444
  }
444
445
  }
445
446
  function Ce(n) {
@@ -458,7 +459,7 @@ class ft extends at {
458
459
  }
459
460
  function Ne(n) {
460
461
  let g = q[n.pointerId];
461
- g === void 0 && (g = new T(), q[n.pointerId] = g), g.set(n.pageX, n.pageY);
462
+ g === void 0 && (g = new k(), q[n.pointerId] = g), g.set(n.pageX, n.pageY);
462
463
  }
463
464
  function j(n) {
464
465
  const g = n.pointerId === E[0] ? E[1] : E[0];
@@ -478,14 +479,14 @@ class mt {
478
479
  this.container = e, this.scene = new y.Scene(), this.scene.background = new y.Color(
479
480
  s.backgroundColor ?? 657930
480
481
  );
481
- const { width: t, height: o } = ke(e), i = s.cameraFov ?? 75;
482
- this.camera = new y.PerspectiveCamera(i, t / o, 0.1, 2e3);
482
+ const { width: t, height: i } = Te(e), o = s.cameraFov ?? 75;
483
+ this.camera = new y.PerspectiveCamera(o, t / i, 0.1, 2e3);
483
484
  const a = s.cameraPosition ?? { x: 0, y: 0, z: 80 };
484
485
  this.camera.position.set(a.x, a.y, a.z), this.renderer = new y.WebGLRenderer({
485
486
  antialias: !0,
486
487
  alpha: !0,
487
488
  powerPreference: "high-performance"
488
- }), this.renderer.setSize(t, o), this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)), this.renderer.toneMapping = y.ACESFilmicToneMapping, this.renderer.toneMappingExposure = 1, this.renderer.shadowMap.enabled = !0, this.renderer.shadowMap.type = y.PCFSoftShadowMap, e.appendChild(this.renderer.domElement), this.controls = new ft(this.camera, this.renderer.domElement), this.controls.enableDamping = !0, this.controls.dampingFactor = 0.05, this.controls.rotateSpeed = 0.8, this.controls.zoomSpeed = 1.2, this.controls.minDistance = 10, this.controls.maxDistance = 2e3, this.setupLighting(), this.resizeHandler = this.onWindowResize.bind(this), window.addEventListener("resize", this.resizeHandler);
489
+ }), this.renderer.setSize(t, i), this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)), this.renderer.toneMapping = y.ACESFilmicToneMapping, this.renderer.toneMappingExposure = 1, this.renderer.shadowMap.enabled = !0, this.renderer.shadowMap.type = y.PCFSoftShadowMap, e.appendChild(this.renderer.domElement), this.controls = new ft(this.camera, this.renderer.domElement), this.controls.enableDamping = !0, this.controls.dampingFactor = 0.05, this.controls.rotateSpeed = 0.8, this.controls.zoomSpeed = 1.2, this.controls.minDistance = 10, this.controls.maxDistance = 2e3, this.setupLighting(), this.resizeHandler = this.onWindowResize.bind(this), window.addEventListener("resize", this.resizeHandler);
489
490
  }
490
491
  /**
491
492
  * Sets up scene lighting for gradient glass on dark background
@@ -497,10 +498,10 @@ class mt {
497
498
  s.position.set(50, 60, 40), s.castShadow = !0, s.shadow.mapSize.width = 1024, s.shadow.mapSize.height = 1024, this.scene.add(s);
498
499
  const t = new y.DirectionalLight(16773344, 0.4);
499
500
  t.position.set(-50, 30, -40), this.scene.add(t);
500
- const o = new y.DirectionalLight(16777215, 0.3);
501
- o.position.set(0, -30, -50), this.scene.add(o);
502
- const i = new y.PointLight(16750950, 0.5, 150);
503
- i.position.set(40, 20, 40), this.scene.add(i);
501
+ const i = new y.DirectionalLight(16777215, 0.3);
502
+ i.position.set(0, -30, -50), this.scene.add(i);
503
+ const o = new y.PointLight(16750950, 0.5, 150);
504
+ o.position.set(40, 20, 40), this.scene.add(o);
504
505
  const a = new y.PointLight(16764057, 0.4, 150);
505
506
  a.position.set(-40, -20, 40), this.scene.add(a);
506
507
  const r = new y.PointLight(6724095, 0.2, 100);
@@ -510,7 +511,7 @@ class mt {
510
511
  * Handle window resize
511
512
  */
512
513
  onWindowResize() {
513
- const { width: e, height: s } = ke(this.container);
514
+ const { width: e, height: s } = Te(this.container);
514
515
  this.camera.aspect = e / s, this.camera.updateProjectionMatrix(), this.renderer.setSize(e, s);
515
516
  }
516
517
  /**
@@ -584,24 +585,24 @@ const V = class V {
584
585
  * @returns true if added, false if node already exists or invalid
585
586
  */
586
587
  addNode(e, s = 0) {
587
- if (!He(e))
588
+ if (!De(e))
588
589
  return !1;
589
590
  if (this.nodes.has(e.id))
590
591
  return console.warn(`[ForceGraph3D] Node with id "${e.id}" already exists`), !1;
591
- const t = Math.max(50, Math.cbrt(V.expectedNodeCount) * 10), o = e.position ?? {
592
+ const t = Math.max(50, Math.cbrt(V.expectedNodeCount) * 10), i = e.position ?? {
592
593
  x: (Math.random() - 0.5) * t,
593
594
  y: (Math.random() - 0.5) * t,
594
595
  z: (Math.random() - 0.5) * t
595
- }, i = {
596
+ }, o = {
596
597
  ...e,
597
- position: o,
598
+ position: i,
598
599
  velocity: { x: 0, y: 0, z: 0 },
599
600
  mass: 1
600
601
  }, a = this.nodeFactory.createNode(
601
- { ...e, position: o },
602
+ { ...e, position: i },
602
603
  s
603
604
  );
604
- return this.sceneManager.add(a.group), this.nodes.set(e.id, i), this.nodeObjects.set(e.id, a), !0;
605
+ return this.sceneManager.add(a.group), this.nodes.set(e.id, o), this.nodeObjects.set(e.id, a), !0;
605
606
  }
606
607
  /**
607
608
  * Removes a node from the graph
@@ -615,17 +616,17 @@ const V = class V {
615
616
  * Updates a node's properties
616
617
  */
617
618
  updateNode(e, s) {
618
- const t = this.nodes.get(e), o = this.nodeObjects.get(e);
619
- return !t || !o ? (console.warn(`[ForceGraph3D] Node "${e}" not found`), !1) : (s.label !== void 0 && (t.label = s.label, this.nodeFactory.updateNodeLabel(o, s.label)), s.color !== void 0 && (t.color = s.color, this.nodeFactory.updateNodeColor(o, s.color)), Object.keys(s).forEach((i) => {
620
- i !== "id" && i !== "label" && i !== "color" && i !== "position" && (t[i] = s[i]);
619
+ const t = this.nodes.get(e), i = this.nodeObjects.get(e);
620
+ return !t || !i ? (console.warn(`[ForceGraph3D] Node "${e}" not found`), !1) : (s.label !== void 0 && (t.label = s.label, this.nodeFactory.updateNodeLabel(i, s.label)), s.color !== void 0 && (t.color = s.color, this.nodeFactory.updateNodeColor(i, s.color)), Object.keys(s).forEach((o) => {
621
+ o !== "id" && o !== "label" && o !== "color" && o !== "position" && (t[o] = s[o]);
621
622
  }), !0);
622
623
  }
623
624
  /**
624
625
  * Updates a node's position (called by physics engine)
625
626
  */
626
627
  updateNodePosition(e, s) {
627
- const t = this.nodes.get(e), o = this.nodeObjects.get(e);
628
- t && o && (t.position = s, o.group.position.set(s.x, s.y, s.z));
628
+ const t = this.nodes.get(e), i = this.nodeObjects.get(e);
629
+ t && i && (t.position = s, i.group.position.set(s.x, s.y, s.z));
629
630
  }
630
631
  /**
631
632
  * Updates a node's LOD level
@@ -709,7 +710,7 @@ class yt {
709
710
  * Checks if an edge exists
710
711
  */
711
712
  hasEdge(e, s) {
712
- const t = D(e, s);
713
+ const t = H(e, s);
713
714
  return this.edgeKeySet.has(t);
714
715
  }
715
716
  /**
@@ -717,50 +718,50 @@ class yt {
717
718
  * @returns true if added, false if edge already exists or nodes don't exist
718
719
  */
719
720
  addEdge(e) {
720
- if (!De(e))
721
+ if (!He(e))
721
722
  return !1;
722
723
  if (!this.nodeManager.hasNode(e.source))
723
724
  return console.warn(`[ForceGraph3D] Source node "${e.source}" does not exist`), !1;
724
725
  if (!this.nodeManager.hasNode(e.target))
725
726
  return console.warn(`[ForceGraph3D] Target node "${e.target}" does not exist`), !1;
726
- const s = D(e.source, e.target);
727
+ const s = H(e.source, e.target);
727
728
  if (this.edgeKeySet.has(s))
728
729
  return console.warn(`[ForceGraph3D] Edge "${e.source}" -> "${e.target}" already exists`), !1;
729
- const t = this.nodeManager.getNode(e.source), o = this.nodeManager.getNode(e.target), i = this.edgeFactory.createEdge(
730
+ const t = this.nodeManager.getNode(e.source), i = this.nodeManager.getNode(e.target), o = this.edgeFactory.createEdge(
730
731
  e,
731
732
  t,
732
- o,
733
+ i,
733
734
  t.position,
734
- o.position
735
+ i.position
735
736
  );
736
- return this.sceneManager.add(i.line), this.edges.push(e), this.edgeObjects.push(i), this.edgeKeySet.add(s), !0;
737
+ return this.sceneManager.add(o.line), this.edges.push(e), this.edgeObjects.push(o), this.edgeKeySet.add(s), !0;
737
738
  }
738
739
  /**
739
740
  * Removes an edge from the graph
740
741
  * @returns true if removed, false if not found
741
742
  */
742
743
  removeEdge(e, s) {
743
- const t = D(e, s);
744
+ const t = H(e, s);
744
745
  if (!this.edgeKeySet.has(t))
745
746
  return !1;
746
- const o = this.edges.findIndex(
747
- (a) => D(a.source, a.target) === t
747
+ const i = this.edges.findIndex(
748
+ (a) => H(a.source, a.target) === t
748
749
  );
749
- if (o === -1)
750
+ if (i === -1)
750
751
  return !1;
751
- const i = this.edgeObjects[o];
752
- return this.sceneManager.remove(i.line), this.edgeFactory.disposeEdge(i), this.edges.splice(o, 1), this.edgeObjects.splice(o, 1), this.edgeKeySet.delete(t), this.highlightedEdgeKey === t && (this.highlightedEdgeKey = null), !0;
752
+ const o = this.edgeObjects[i];
753
+ return this.sceneManager.remove(o.line), this.edgeFactory.disposeEdge(o), this.edges.splice(i, 1), this.edgeObjects.splice(i, 1), this.edgeKeySet.delete(t), this.highlightedEdgeKey === t && (this.highlightedEdgeKey = null), !0;
753
754
  }
754
755
  /**
755
756
  * Highlights an edge
756
757
  */
757
758
  highlightEdge(e, s) {
758
- const t = D(e, s);
759
+ const t = H(e, s);
759
760
  this.highlightedEdgeKey && this.highlightedEdgeKey !== t && this.unhighlightCurrentEdge();
760
- const o = this.edges.findIndex(
761
- (i) => D(i.source, i.target) === t
761
+ const i = this.edges.findIndex(
762
+ (o) => H(o.source, o.target) === t
762
763
  );
763
- o !== -1 && (this.edgeFactory.highlightEdge(this.edgeObjects[o]), this.highlightedEdgeKey = t);
764
+ i !== -1 && (this.edgeFactory.highlightEdge(this.edgeObjects[i]), this.highlightedEdgeKey = t);
764
765
  }
765
766
  /**
766
767
  * Unhighlights the currently highlighted edge
@@ -768,7 +769,7 @@ class yt {
768
769
  unhighlightCurrentEdge() {
769
770
  if (!this.highlightedEdgeKey) return;
770
771
  const e = this.edges.findIndex(
771
- (s) => D(s.source, s.target) === this.highlightedEdgeKey
772
+ (s) => H(s.source, s.target) === this.highlightedEdgeKey
772
773
  );
773
774
  e !== -1 && this.edgeFactory.unhighlightEdge(this.edgeObjects[e]), this.highlightedEdgeKey = null;
774
775
  }
@@ -803,11 +804,11 @@ class yt {
803
804
  */
804
805
  updateEdgePositions() {
805
806
  this.edgeObjects.forEach((e, s) => {
806
- const t = this.edges[s], o = this.nodeManager.getNode(t.source), i = this.nodeManager.getNode(t.target);
807
- o && i && this.edgeFactory.updateEdgePositions(
807
+ const t = this.edges[s], i = this.nodeManager.getNode(t.source), o = this.nodeManager.getNode(t.target);
808
+ i && o && this.edgeFactory.updateEdgePositions(
808
809
  e,
809
- o.position,
810
- i.position
810
+ i.position,
811
+ o.position
811
812
  );
812
813
  });
813
814
  }
@@ -844,7 +845,7 @@ class yt {
844
845
  this.clear();
845
846
  }
846
847
  }
847
- class Le {
848
+ class Ie {
848
849
  constructor(e, s, t = {}) {
849
850
  l(this, "nodes");
850
851
  l(this, "edges");
@@ -861,14 +862,38 @@ class Le {
861
862
  // Skip repulsion beyond this distance
862
863
  l(this, "REPULSION_CUTOFF_SQ");
863
864
  // Precomputed cutoff²
864
- l(this, "GRAVITY_STRENGTH", 0.02);
865
+ l(this, "GRAVITY_STRENGTH", 0.05);
865
866
  // Centering force strength
867
+ // Hub node pinning
868
+ l(this, "pinnedNodeId", null);
866
869
  // Simulation state
867
870
  l(this, "alpha", 1);
868
871
  l(this, "alphaDecay", 0.0228);
869
872
  l(this, "alphaMin", 1e-3);
870
873
  l(this, "alphaTarget", 0);
871
- this.nodes = e, this.edges = s, this.repulsionStrength = t.repulsionStrength ?? 100, this.attractionStrength = t.attractionStrength ?? 0.01, this.damping = t.damping ?? 0.9, this.useBarnesHut = t.useBarnesHut ?? !1, this.barnesHutTheta = t.barnesHutTheta ?? 0.5, this.REPULSION_CUTOFF_SQ = this.REPULSION_CUTOFF * this.REPULSION_CUTOFF;
874
+ this.nodes = e, this.edges = s, this.repulsionStrength = t.repulsionStrength ?? 100, this.attractionStrength = t.attractionStrength ?? 0.01, this.damping = t.damping ?? 0.9, this.useBarnesHut = t.useBarnesHut ?? !1, this.barnesHutTheta = t.barnesHutTheta ?? 0.5, this.REPULSION_CUTOFF_SQ = this.REPULSION_CUTOFF * this.REPULSION_CUTOFF, this.initializeNodeMassAndPin();
875
+ }
876
+ /**
877
+ * Assigns mass proportional to node degree (connection count) and
878
+ * pins the most-connected hub node at the origin.
879
+ */
880
+ initializeNodeMassAndPin() {
881
+ const e = /* @__PURE__ */ new Map();
882
+ for (const i of this.edges)
883
+ e.set(i.source, (e.get(i.source) || 0) + 1), e.set(i.target, (e.get(i.target) || 0) + 1);
884
+ console.log(`[ForceGraph3D] initializeNodeMassAndPin: ${this.edges.length} edges, ${this.nodes.size} nodes, ${e.size} unique endpoints`);
885
+ let s = 0, t = null;
886
+ for (const [i, o] of e) {
887
+ o > s && (s = o, t = i);
888
+ const a = this.nodes.get(i);
889
+ a && (a.mass = 1 + Math.log2(1 + o));
890
+ }
891
+ if (console.log(`[ForceGraph3D] Hub detected: id="${t}", degree=${s}, threshold=10`), t && s > 10) {
892
+ this.pinnedNodeId = t;
893
+ const i = this.nodes.get(t);
894
+ i ? (i.position.x = 0, i.position.y = 0, i.position.z = 0, i.velocity.x = 0, i.velocity.y = 0, i.velocity.z = 0, console.log(`[ForceGraph3D] ✅ Hub node "${t}" pinned at origin`)) : console.warn(`[ForceGraph3D] ⚠️ Hub node "${t}" found in edges but NOT in nodes map!`);
895
+ } else
896
+ console.log(`[ForceGraph3D] No hub pinned (maxDegree=${s} <= 10 or hubId=${t})`);
872
897
  }
873
898
  /**
874
899
  * Computes effective repulsion strength scaled by node count.
@@ -891,15 +916,15 @@ class Le {
891
916
  */
892
917
  calculateRepulsionBruteForce() {
893
918
  const e = Array.from(this.nodes.values()), s = e.length, t = this.getEffectiveRepulsion();
894
- for (let o = 0; o < s; o++) {
895
- const i = e[o];
896
- for (let a = o + 1; a < s; a++) {
897
- const r = e[a], c = r.position.x - i.position.x, h = r.position.y - i.position.y, p = r.position.z - i.position.z;
898
- let x = c * c + h * h + p * p;
919
+ for (let i = 0; i < s; i++) {
920
+ const o = e[i];
921
+ for (let a = i + 1; a < s; a++) {
922
+ const r = e[a], c = r.position.x - o.position.x, d = r.position.y - o.position.y, p = r.position.z - o.position.z;
923
+ let x = c * c + d * d + p * p;
899
924
  if (x > this.REPULSION_CUTOFF_SQ) continue;
900
925
  x < 0.01 && (x = 0.01);
901
- const m = Math.sqrt(x), u = t * this.alpha / x, f = c / m * u, b = h / m * u, M = p / m * u;
902
- i.velocity.x -= f / i.mass, i.velocity.y -= b / i.mass, i.velocity.z -= M / i.mass, r.velocity.x += f / r.mass, r.velocity.y += b / r.mass, r.velocity.z += M / r.mass;
926
+ const m = Math.sqrt(x), u = t * this.alpha / x, f = c / m * u, b = d / m * u, M = p / m * u;
927
+ o.velocity.x -= f / o.mass, o.velocity.y -= b / o.mass, o.velocity.z -= M / o.mass, r.velocity.x += f / r.mass, r.velocity.y += b / r.mass, r.velocity.z += M / r.mass;
903
928
  }
904
929
  }
905
930
  }
@@ -920,26 +945,26 @@ class Le {
920
945
  return;
921
946
  }
922
947
  if (s.mass === 0) return;
923
- const t = s.centerOfMass.x - e.position.x, o = s.centerOfMass.y - e.position.y, i = s.centerOfMass.z - e.position.z, a = t * t + o * o + i * i;
948
+ const t = s.centerOfMass.x - e.position.x, i = s.centerOfMass.y - e.position.y, o = s.centerOfMass.z - e.position.z, a = t * t + i * i + o * o;
924
949
  if (a > this.REPULSION_CUTOFF_SQ) return;
925
950
  const r = Math.sqrt(a), c = this.getEffectiveRepulsion();
926
951
  if (r > 0 && s.size / r < this.barnesHutTheta) {
927
- const h = Math.max(a, 0.01), p = c * this.alpha * s.mass / h;
928
- e.velocity.x -= t / r * p / e.mass, e.velocity.y -= o / r * p / e.mass, e.velocity.z -= i / r * p / e.mass;
952
+ const d = Math.max(a, 0.01), p = c * this.alpha * s.mass / d;
953
+ e.velocity.x -= t / r * p / e.mass, e.velocity.y -= i / r * p / e.mass, e.velocity.z -= o / r * p / e.mass;
929
954
  } else
930
- for (const h of s.children)
931
- h && this.calculateForceFromOctree(e, h);
955
+ for (const d of s.children)
956
+ d && this.calculateForceFromOctree(e, d);
932
957
  }
933
958
  /**
934
959
  * Apply repulsion between two nodes (with cutoff)
935
960
  */
936
961
  applyRepulsionBetween(e, s) {
937
- const t = s.position.x - e.position.x, o = s.position.y - e.position.y, i = s.position.z - e.position.z;
938
- let a = t * t + o * o + i * i;
962
+ const t = s.position.x - e.position.x, i = s.position.y - e.position.y, o = s.position.z - e.position.z;
963
+ let a = t * t + i * i + o * o;
939
964
  if (a > this.REPULSION_CUTOFF_SQ) return;
940
965
  a < 0.01 && (a = 0.01);
941
- const r = Math.sqrt(a), h = this.getEffectiveRepulsion() * this.alpha / a;
942
- e.velocity.x -= t / r * h / e.mass, e.velocity.y -= o / r * h / e.mass, e.velocity.z -= i / r * h / e.mass;
966
+ const r = Math.sqrt(a), d = this.getEffectiveRepulsion() * this.alpha / a;
967
+ e.velocity.x -= t / r * d / e.mass, e.velocity.y -= i / r * d / e.mass, e.velocity.z -= o / r * d / e.mass;
943
968
  }
944
969
  /**
945
970
  * Calculate attraction forces along edges
@@ -947,12 +972,12 @@ class Le {
947
972
  calculateAttraction() {
948
973
  const e = this.nodes.size, s = e > 500 ? 1 + Math.log10(e / 500) : 1;
949
974
  for (const t of this.edges) {
950
- const o = this.nodes.get(t.source), i = this.nodes.get(t.target);
951
- if (!o || !i) continue;
952
- const a = i.position.x - o.position.x, r = i.position.y - o.position.y, c = i.position.z - o.position.z, h = Math.sqrt(a * a + r * r + c * c);
953
- if (h < 0.01) continue;
954
- const x = (h - 15) * this.attractionStrength * s * this.alpha, m = a / h * x, u = r / h * x, f = c / h * x;
955
- o.velocity.x += m / o.mass, o.velocity.y += u / o.mass, o.velocity.z += f / o.mass, i.velocity.x -= m / i.mass, i.velocity.y -= u / i.mass, i.velocity.z -= f / i.mass;
975
+ const i = this.nodes.get(t.source), o = this.nodes.get(t.target);
976
+ if (!i || !o) continue;
977
+ const a = o.position.x - i.position.x, r = o.position.y - i.position.y, c = o.position.z - i.position.z, d = Math.sqrt(a * a + r * r + c * c);
978
+ if (d < 0.01) continue;
979
+ const x = (d - 15) * this.attractionStrength * s * this.alpha, m = a / d * x, u = r / d * x, f = c / d * x;
980
+ i.velocity.x += m / i.mass, i.velocity.y += u / i.mass, i.velocity.z += f / i.mass, o.velocity.x -= m / o.mass, o.velocity.y -= u / o.mass, o.velocity.z -= f / o.mass;
956
981
  }
957
982
  }
958
983
  /**
@@ -969,6 +994,10 @@ class Le {
969
994
  */
970
995
  applyForces() {
971
996
  for (const e of this.nodes.values()) {
997
+ if (e.id === this.pinnedNodeId) {
998
+ e.position.x = 0, e.position.y = 0, e.position.z = 0, e.velocity.x = 0, e.velocity.y = 0, e.velocity.z = 0;
999
+ continue;
1000
+ }
972
1001
  e.velocity.x *= this.damping, e.velocity.y *= this.damping, e.velocity.z *= this.damping;
973
1002
  const s = Math.sqrt(
974
1003
  e.velocity.x * e.velocity.x + e.velocity.y * e.velocity.y + e.velocity.z * e.velocity.z
@@ -1018,13 +1047,13 @@ class xt {
1018
1047
  max: { x: 100, y: 100, z: 100 }
1019
1048
  };
1020
1049
  const s = { x: 1 / 0, y: 1 / 0, z: 1 / 0 }, t = { x: -1 / 0, y: -1 / 0, z: -1 / 0 };
1021
- for (const i of e)
1022
- s.x = Math.min(s.x, i.position.x), s.y = Math.min(s.y, i.position.y), s.z = Math.min(s.z, i.position.z), t.x = Math.max(t.x, i.position.x), t.y = Math.max(t.y, i.position.y), t.z = Math.max(t.z, i.position.z);
1023
- const o = 10;
1024
- return s.x -= o, s.y -= o, s.z -= o, t.x += o, t.y += o, t.z += o, { min: s, max: t };
1050
+ for (const o of e)
1051
+ s.x = Math.min(s.x, o.position.x), s.y = Math.min(s.y, o.position.y), s.z = Math.min(s.z, o.position.z), t.x = Math.max(t.x, o.position.x), t.y = Math.max(t.y, o.position.y), t.z = Math.max(t.z, o.position.z);
1052
+ const i = 10;
1053
+ return s.x -= i, s.y -= i, s.z -= i, t.x += i, t.y += i, t.z += i, { min: s, max: t };
1025
1054
  }
1026
1055
  buildTree(e, s, t = 0) {
1027
- const o = Math.max(
1056
+ const i = Math.max(
1028
1057
  s.max.x - s.min.x,
1029
1058
  s.max.y - s.min.y,
1030
1059
  s.max.z - s.min.z
@@ -1032,7 +1061,7 @@ class xt {
1032
1061
  if (e.length === 0)
1033
1062
  return {
1034
1063
  bounds: s,
1035
- size: o,
1064
+ size: i,
1036
1065
  centerOfMass: { x: 0, y: 0, z: 0 },
1037
1066
  mass: 0,
1038
1067
  isLeaf: !0,
@@ -1046,7 +1075,7 @@ class xt {
1046
1075
  u += b.mass, f.x += b.position.x * b.mass, f.y += b.position.y * b.mass, f.z += b.position.z * b.mass;
1047
1076
  return u > 0 && (f.x /= u, f.y /= u, f.z /= u), {
1048
1077
  bounds: s,
1049
- size: o,
1078
+ size: i,
1050
1079
  centerOfMass: f,
1051
1080
  mass: u,
1052
1081
  isLeaf: !0,
@@ -1054,32 +1083,32 @@ class xt {
1054
1083
  children: []
1055
1084
  };
1056
1085
  }
1057
- const i = (s.min.x + s.max.x) / 2, a = (s.min.y + s.max.y) / 2, r = (s.min.z + s.max.z) / 2, c = [[], [], [], [], [], [], [], []];
1086
+ const o = (s.min.x + s.max.x) / 2, a = (s.min.y + s.max.y) / 2, r = (s.min.z + s.max.z) / 2, c = [[], [], [], [], [], [], [], []];
1058
1087
  for (const u of e) {
1059
- const f = (u.position.x >= i ? 1 : 0) + (u.position.y >= a ? 2 : 0) + (u.position.z >= r ? 4 : 0);
1088
+ const f = (u.position.x >= o ? 1 : 0) + (u.position.y >= a ? 2 : 0) + (u.position.z >= r ? 4 : 0);
1060
1089
  c[f].push(u);
1061
1090
  }
1062
- const h = [
1063
- { min: { x: s.min.x, y: s.min.y, z: s.min.z }, max: { x: i, y: a, z: r } },
1064
- { min: { x: i, y: s.min.y, z: s.min.z }, max: { x: s.max.x, y: a, z: r } },
1065
- { min: { x: s.min.x, y: a, z: s.min.z }, max: { x: i, y: s.max.y, z: r } },
1066
- { min: { x: i, y: a, z: s.min.z }, max: { x: s.max.x, y: s.max.y, z: r } },
1067
- { min: { x: s.min.x, y: s.min.y, z: r }, max: { x: i, y: a, z: s.max.z } },
1068
- { min: { x: i, y: s.min.y, z: r }, max: { x: s.max.x, y: a, z: s.max.z } },
1069
- { min: { x: s.min.x, y: a, z: r }, max: { x: i, y: s.max.y, z: s.max.z } },
1070
- { min: { x: i, y: a, z: r }, max: { x: s.max.x, y: s.max.y, z: s.max.z } }
1091
+ const d = [
1092
+ { min: { x: s.min.x, y: s.min.y, z: s.min.z }, max: { x: o, y: a, z: r } },
1093
+ { min: { x: o, y: s.min.y, z: s.min.z }, max: { x: s.max.x, y: a, z: r } },
1094
+ { min: { x: s.min.x, y: a, z: s.min.z }, max: { x: o, y: s.max.y, z: r } },
1095
+ { min: { x: o, y: a, z: s.min.z }, max: { x: s.max.x, y: s.max.y, z: r } },
1096
+ { min: { x: s.min.x, y: s.min.y, z: r }, max: { x: o, y: a, z: s.max.z } },
1097
+ { min: { x: o, y: s.min.y, z: r }, max: { x: s.max.x, y: a, z: s.max.z } },
1098
+ { min: { x: s.min.x, y: a, z: r }, max: { x: o, y: s.max.y, z: s.max.z } },
1099
+ { min: { x: o, y: a, z: r }, max: { x: s.max.x, y: s.max.y, z: s.max.z } }
1071
1100
  ], p = [];
1072
1101
  let x = 0;
1073
1102
  const m = { x: 0, y: 0, z: 0 };
1074
1103
  for (let u = 0; u < 8; u++)
1075
1104
  if (c[u].length > 0) {
1076
- const f = this.buildTree(c[u], h[u], t + 1);
1105
+ const f = this.buildTree(c[u], d[u], t + 1);
1077
1106
  p.push(f), x += f.mass, m.x += f.centerOfMass.x * f.mass, m.y += f.centerOfMass.y * f.mass, m.z += f.centerOfMass.z * f.mass;
1078
1107
  } else
1079
1108
  p.push(null);
1080
1109
  return x > 0 && (m.x /= x, m.y /= x, m.z /= x), {
1081
1110
  bounds: s,
1082
- size: o,
1111
+ size: i,
1083
1112
  centerOfMass: m,
1084
1113
  mass: x,
1085
1114
  isLeaf: !1,
@@ -1089,7 +1118,7 @@ class xt {
1089
1118
  }
1090
1119
  }
1091
1120
  class bt {
1092
- constructor(e, s, t, o = 60) {
1121
+ constructor(e, s, t, i = 60) {
1093
1122
  l(this, "sceneManager");
1094
1123
  l(this, "animationId", null);
1095
1124
  l(this, "isRunning", !1);
@@ -1114,7 +1143,7 @@ class bt {
1114
1143
  const t = e - this.fpsStartTime;
1115
1144
  t >= 1e3 && (this.currentFPS = this.frameCount / (t / 1e3), this.frameCount = 0, this.fpsStartTime = e), this.onSimulate(), this.onRender(), this.sceneManager.render();
1116
1145
  });
1117
- this.sceneManager = e, this.onSimulate = s, this.onRender = t, this.frameInterval = 1e3 / o;
1146
+ this.sceneManager = e, this.onSimulate = s, this.onRender = t, this.frameInterval = 1e3 / i;
1118
1147
  }
1119
1148
  /**
1120
1149
  * Starts the animation loop
@@ -1191,10 +1220,10 @@ class vt {
1191
1220
  { colors: ["#2d1a1a", "#1a0a0a", "#0f0505"] }
1192
1221
  // -z
1193
1222
  ];
1194
- for (const o of t) {
1195
- const i = document.createElement("canvas");
1196
- i.width = 256, i.height = 256;
1197
- const a = i.getContext("2d"), r = a.createRadialGradient(
1223
+ for (const i of t) {
1224
+ const o = document.createElement("canvas");
1225
+ o.width = 256, o.height = 256;
1226
+ const a = o.getContext("2d"), r = a.createRadialGradient(
1198
1227
  256 / 2,
1199
1228
  256 / 2,
1200
1229
  0,
@@ -1202,17 +1231,17 @@ class vt {
1202
1231
  256 / 2,
1203
1232
  256 * 0.8
1204
1233
  );
1205
- r.addColorStop(0, o.colors[0]), r.addColorStop(0.5, o.colors[1]), r.addColorStop(1, o.colors[2]), a.fillStyle = r, a.fillRect(0, 0, 256, 256);
1234
+ r.addColorStop(0, i.colors[0]), r.addColorStop(0.5, i.colors[1]), r.addColorStop(1, i.colors[2]), a.fillStyle = r, a.fillRect(0, 0, 256, 256);
1206
1235
  const c = a.getImageData(0, 0, 256, 256);
1207
- for (let h = 0; h < c.data.length; h += 4) {
1236
+ for (let d = 0; d < c.data.length; d += 4) {
1208
1237
  const p = (Math.random() - 0.5) * 5;
1209
- c.data[h] = Math.min(255, Math.max(0, c.data[h] + p)), c.data[h + 1] = Math.min(255, Math.max(0, c.data[h + 1] + p)), c.data[h + 2] = Math.min(255, Math.max(0, c.data[h + 2] + p));
1238
+ c.data[d] = Math.min(255, Math.max(0, c.data[d] + p)), c.data[d + 1] = Math.min(255, Math.max(0, c.data[d + 1] + p)), c.data[d + 2] = Math.min(255, Math.max(0, c.data[d + 2] + p));
1210
1239
  }
1211
- a.putImageData(c, 0, 0), s.push(i);
1240
+ a.putImageData(c, 0, 0), s.push(o);
1212
1241
  }
1213
- this.envMap = new y.CubeTexture(s.map((o) => {
1214
- const i = new Image();
1215
- return i.src = o.toDataURL(), i;
1242
+ this.envMap = new y.CubeTexture(s.map((i) => {
1243
+ const o = new Image();
1244
+ return o.src = i.toDataURL(), o;
1216
1245
  })), this.envMap.needsUpdate = !0;
1217
1246
  }
1218
1247
  /**
@@ -1229,9 +1258,9 @@ class vt {
1229
1258
  const t = "glass-single";
1230
1259
  if (this.materialCache.has(t))
1231
1260
  return this.materialCache.get(t).clone();
1232
- const o = new y.Color(16750950), i = new y.ShaderMaterial({
1261
+ const i = new y.Color(16750950), o = new y.ShaderMaterial({
1233
1262
  uniforms: {
1234
- uColor: { value: o },
1263
+ uColor: { value: i },
1235
1264
  uEnvMap: { value: this.envMap },
1236
1265
  uGlowColor: { value: new y.Color(16777215) },
1237
1266
  uGlowIntensity: { value: 0.8 },
@@ -1303,7 +1332,7 @@ class vt {
1303
1332
  depthWrite: !0,
1304
1333
  blending: y.NormalBlending
1305
1334
  });
1306
- return this.materialCache.set(t, i), i.clone();
1335
+ return this.materialCache.set(t, o), o.clone();
1307
1336
  }
1308
1337
  /**
1309
1338
  * Creates material for edges (light color for dark background)
@@ -1332,10 +1361,10 @@ class vt {
1332
1361
  * Creates a sprite material for labels (light text for dark background)
1333
1362
  */
1334
1363
  createLabelMaterial(e, s = 24) {
1335
- const t = document.createElement("canvas"), o = t.getContext("2d");
1336
- o.font = `600 ${s}px Inter, -apple-system, sans-serif`;
1337
- const a = o.measureText(e).width;
1338
- t.width = Math.max(128, a + 24), t.height = s + 20, o.clearRect(0, 0, t.width, t.height), o.font = `600 ${s}px Inter, -apple-system, sans-serif`, o.textAlign = "center", o.textBaseline = "middle", o.shadowColor = "rgba(0, 0, 0, 0.8)", o.shadowBlur = 4, o.shadowOffsetX = 1, o.shadowOffsetY = 1, o.fillStyle = "rgba(255, 255, 255, 0.95)", o.fillText(e, t.width / 2, t.height / 2);
1364
+ const t = document.createElement("canvas"), i = t.getContext("2d");
1365
+ i.font = `600 ${s}px Inter, -apple-system, sans-serif`;
1366
+ const a = i.measureText(e).width;
1367
+ t.width = Math.max(128, a + 24), t.height = s + 20, i.clearRect(0, 0, t.width, t.height), i.font = `600 ${s}px Inter, -apple-system, sans-serif`, i.textAlign = "center", i.textBaseline = "middle", i.shadowColor = "rgba(0, 0, 0, 0.8)", i.shadowBlur = 4, i.shadowOffsetX = 1, i.shadowOffsetY = 1, i.fillStyle = "rgba(255, 255, 255, 0.95)", i.fillText(e, t.width / 2, t.height / 2);
1339
1368
  const r = new y.CanvasTexture(t);
1340
1369
  return r.needsUpdate = !0, new y.SpriteMaterial({
1341
1370
  map: r,
@@ -1358,13 +1387,13 @@ class vt {
1358
1387
  }
1359
1388
  }
1360
1389
  class Mt {
1361
- constructor(e, s = 2, t = [32, 16, 8], o = 16750950) {
1390
+ constructor(e, s = 2, t = [32, 16, 8], i = 16750950) {
1362
1391
  l(this, "materialFactory");
1363
1392
  l(this, "geometryCache", /* @__PURE__ */ new Map());
1364
1393
  l(this, "nodeRadius");
1365
1394
  l(this, "lodSegments");
1366
1395
  l(this, "defaultNodeColor");
1367
- this.materialFactory = e, this.nodeRadius = s, this.lodSegments = t, this.defaultNodeColor = o, this.initGeometryCache();
1396
+ this.materialFactory = e, this.nodeRadius = s, this.lodSegments = t, this.defaultNodeColor = i, this.initGeometryCache();
1368
1397
  }
1369
1398
  /**
1370
1399
  * Pre-create geometries for each LOD level
@@ -1391,9 +1420,9 @@ class Mt {
1391
1420
  createNode(e, s = 0) {
1392
1421
  const t = new y.Group();
1393
1422
  t.name = `node-${e.id}`, t.userData = { nodeId: e.id, nodeData: e };
1394
- const o = this.getGeometry(s), i = this.materialFactory.createGlassMaterial(
1423
+ const i = this.getGeometry(s), o = this.materialFactory.createGlassMaterial(
1395
1424
  e.color ?? this.defaultNodeColor
1396
- ), a = new y.Mesh(o, i);
1425
+ ), a = new y.Mesh(i, o);
1397
1426
  a.castShadow = !0, a.receiveShadow = !0, t.add(a);
1398
1427
  const r = this.materialFactory.createLabelMaterial(e.label), c = new y.Sprite(r);
1399
1428
  return c.position.y = this.nodeRadius + 1.5, c.scale.set(4, 1, 1), t.add(c), e.position && t.position.set(
@@ -1467,25 +1496,25 @@ class wt {
1467
1496
  /**
1468
1497
  * Creates an edge line between two positions
1469
1498
  */
1470
- createEdge(e, s, t, o, i) {
1499
+ createEdge(e, s, t, i, o) {
1471
1500
  const a = new y.BufferGeometry(), r = new Float32Array([
1472
- o.x,
1473
- o.y,
1474
- o.z,
1475
1501
  i.x,
1476
1502
  i.y,
1477
- i.z
1503
+ i.z,
1504
+ o.x,
1505
+ o.y,
1506
+ o.z
1478
1507
  ]);
1479
1508
  a.setAttribute("position", new y.BufferAttribute(r, 3));
1480
- const c = this.getDefaultMaterial().clone(), h = new y.Line(a, c);
1481
- return h.name = `edge-${e.source}-${e.target}`, h.userData = {
1509
+ const c = this.getDefaultMaterial().clone(), d = new y.Line(a, c);
1510
+ return d.name = `edge-${e.source}-${e.target}`, d.userData = {
1482
1511
  source: e.source,
1483
1512
  target: e.target,
1484
1513
  edge: e,
1485
1514
  sourceNode: s,
1486
1515
  targetNode: t
1487
- }, h.frustumCulled = !0, {
1488
- line: h,
1516
+ }, d.frustumCulled = !0, {
1517
+ line: d,
1489
1518
  source: e.source,
1490
1519
  target: e.target
1491
1520
  };
@@ -1506,8 +1535,8 @@ class wt {
1506
1535
  * Updates an edge's positions
1507
1536
  */
1508
1537
  updateEdgePositions(e, s, t) {
1509
- const o = e.line.geometry.attributes.position, i = o.array;
1510
- i[0] = s.x, i[1] = s.y, i[2] = s.z, i[3] = t.x, i[4] = t.y, i[5] = t.z, o.needsUpdate = !0, e.line.geometry.computeBoundingSphere();
1538
+ const i = e.line.geometry.attributes.position, o = i.array;
1539
+ o[0] = s.x, o[1] = s.y, o[2] = s.z, o[3] = t.x, o[4] = t.y, o[5] = t.z, i.needsUpdate = !0, e.line.geometry.computeBoundingSphere();
1511
1540
  }
1512
1541
  /**
1513
1542
  * Disposes an edge's resources
@@ -1535,15 +1564,15 @@ class Et {
1535
1564
  getLODLevel(e) {
1536
1565
  if (!this.enabled)
1537
1566
  return X.HIGH;
1538
- const s = e.x - this.camera.position.x, t = e.y - this.camera.position.y, o = e.z - this.camera.position.z, i = Math.sqrt(s * s + t * t + o * o);
1539
- return i < this.lodDistances[0] ? X.HIGH : i < this.lodDistances[1] ? X.MEDIUM : X.LOW;
1567
+ const s = e.x - this.camera.position.x, t = e.y - this.camera.position.y, i = e.z - this.camera.position.z, o = Math.sqrt(s * s + t * t + i * i);
1568
+ return o < this.lodDistances[0] ? X.HIGH : o < this.lodDistances[1] ? X.MEDIUM : X.LOW;
1540
1569
  }
1541
1570
  /**
1542
1571
  * Checks if a node should be visible based on distance
1543
1572
  */
1544
1573
  shouldRenderNode(e, s = 500) {
1545
- const t = e.x - this.camera.position.x, o = e.y - this.camera.position.y, i = e.z - this.camera.position.z;
1546
- return Math.sqrt(t * t + o * o + i * i) < s;
1574
+ const t = e.x - this.camera.position.x, i = e.y - this.camera.position.y, o = e.z - this.camera.position.z;
1575
+ return Math.sqrt(t * t + i * i + o * o) < s;
1547
1576
  }
1548
1577
  /**
1549
1578
  * Sets the LOD distances
@@ -1599,14 +1628,14 @@ class Ct {
1599
1628
  */
1600
1629
  isLineVisible(e, s) {
1601
1630
  if (!this.enabled) return !0;
1602
- const t = new y.Vector3(e.x, e.y, e.z), o = new y.Vector3(s.x, s.y, s.z);
1603
- if (this.frustum.containsPoint(t) || this.frustum.containsPoint(o))
1631
+ const t = new y.Vector3(e.x, e.y, e.z), i = new y.Vector3(s.x, s.y, s.z);
1632
+ if (this.frustum.containsPoint(t) || this.frustum.containsPoint(i))
1604
1633
  return !0;
1605
- const i = new y.Vector3(
1634
+ const o = new y.Vector3(
1606
1635
  (e.x + s.x) / 2,
1607
1636
  (e.y + s.y) / 2,
1608
1637
  (e.z + s.z) / 2
1609
- ), a = i.distanceTo(t), r = new y.Sphere(i, a);
1638
+ ), a = o.distanceTo(t), r = new y.Sphere(o, a);
1610
1639
  return this.frustum.intersectsSphere(r);
1611
1640
  }
1612
1641
  /**
@@ -1689,23 +1718,23 @@ class Nt {
1689
1718
  this.hoveredEdgeKey !== null && this.onEdgeHover && (this.hoveredEdgeKey = null, this.onEdgeHover(null)), this.container.style.cursor = "pointer";
1690
1719
  return;
1691
1720
  }
1692
- const o = this.getIntersectedEdge(e), i = o ? `${o.edge.source}-${o.edge.target}` : null;
1693
- i !== this.hoveredEdgeKey && (this.hoveredEdgeKey = i, this.onEdgeHover && this.onEdgeHover(o)), this.container.style.cursor = o ? "pointer" : "default";
1721
+ const i = this.getIntersectedEdge(e), o = i ? `${i.edge.source}-${i.edge.target}` : null;
1722
+ o !== this.hoveredEdgeKey && (this.hoveredEdgeKey = o, this.onEdgeHover && this.onEdgeHover(i)), this.container.style.cursor = i ? "pointer" : "default";
1694
1723
  }
1695
1724
  /**
1696
1725
  * Gets the intersected node from a mouse event
1697
1726
  */
1698
1727
  getIntersectedNode(e) {
1699
- var o;
1728
+ var i;
1700
1729
  const s = this.container.getBoundingClientRect();
1701
1730
  this.mouse.x = (e.clientX - s.left) / s.width * 2 - 1, this.mouse.y = -((e.clientY - s.top) / s.height) * 2 + 1, this.raycaster.setFromCamera(this.mouse, this.sceneManager.camera);
1702
1731
  const t = this.raycaster.intersectObjects(this.nodeObjects, !0);
1703
1732
  if (t.length > 0) {
1704
- let i = t[0].object;
1705
- for (; i; ) {
1706
- if ((o = i.userData) != null && o.nodeData)
1707
- return i.userData.nodeData;
1708
- i = i.parent;
1733
+ let o = t[0].object;
1734
+ for (; o; ) {
1735
+ if ((i = o.userData) != null && i.nodeData)
1736
+ return o.userData.nodeData;
1737
+ o = o.parent;
1709
1738
  }
1710
1739
  }
1711
1740
  return null;
@@ -1718,13 +1747,13 @@ class Nt {
1718
1747
  this.mouse.x = (e.clientX - s.left) / s.width * 2 - 1, this.mouse.y = -((e.clientY - s.top) / s.height) * 2 + 1, this.raycaster.setFromCamera(this.mouse, this.sceneManager.camera);
1719
1748
  const t = this.raycaster.intersectObjects(this.edgeObjects, !1);
1720
1749
  if (t.length > 0) {
1721
- const o = t[0].object, i = o.userData;
1722
- if (i != null && i.edge && (i != null && i.sourceNode) && (i != null && i.targetNode))
1750
+ const i = t[0].object, o = i.userData;
1751
+ if (o != null && o.edge && (o != null && o.sourceNode) && (o != null && o.targetNode))
1723
1752
  return {
1724
- edge: i.edge,
1725
- sourceNode: i.sourceNode,
1726
- targetNode: i.targetNode,
1727
- edgeLine: o
1753
+ edge: o.edge,
1754
+ sourceNode: o.sourceNode,
1755
+ targetNode: o.targetNode,
1756
+ edgeLine: i
1728
1757
  };
1729
1758
  }
1730
1759
  return null;
@@ -1733,14 +1762,14 @@ class Nt {
1733
1762
  * Performs a raycast and returns the intersected node ID
1734
1763
  */
1735
1764
  getIntersectedNodeId(e, s) {
1736
- var i;
1765
+ var o;
1737
1766
  const t = this.container.getBoundingClientRect();
1738
1767
  this.mouse.x = (e - t.left) / t.width * 2 - 1, this.mouse.y = -((s - t.top) / t.height) * 2 + 1, this.raycaster.setFromCamera(this.mouse, this.sceneManager.camera);
1739
- const o = this.raycaster.intersectObjects(this.nodeObjects, !0);
1740
- if (o.length > 0) {
1741
- let a = o[0].object;
1768
+ const i = this.raycaster.intersectObjects(this.nodeObjects, !0);
1769
+ if (i.length > 0) {
1770
+ let a = i[0].object;
1742
1771
  for (; a; ) {
1743
- if ((i = a.userData) != null && i.nodeId)
1772
+ if ((o = a.userData) != null && o.nodeId)
1744
1773
  return a.userData.nodeId;
1745
1774
  a = a.parent;
1746
1775
  }
@@ -1824,10 +1853,10 @@ class St {
1824
1853
  this.currentNodeId = e.id;
1825
1854
  let t;
1826
1855
  this.panelTemplate ? t = this.panelTemplate(e, s) : t = this.generateDefaultContent(e, s), this.panel.innerHTML = t;
1827
- const o = this.panel.querySelector('[data-action="expand"]'), i = this.panel.querySelector("[data-depth-select]");
1828
- o && this.onExpand && o.addEventListener("click", () => {
1856
+ const i = this.panel.querySelector('[data-action="expand"]'), o = this.panel.querySelector("[data-depth-select]");
1857
+ i && this.onExpand && i.addEventListener("click", () => {
1829
1858
  if (this.currentNodeId) {
1830
- const r = i ? parseInt(i.value, 10) : 1;
1859
+ const r = o ? parseInt(o.value, 10) : 1;
1831
1860
  this.onExpand(this.currentNodeId, r);
1832
1861
  }
1833
1862
  });
@@ -1986,7 +2015,7 @@ class St {
1986
2015
  <div class="neighbors-section">
1987
2016
  <div class="neighbors-title">Connected To</div>
1988
2017
  ${s.slice(0, 5).map(
1989
- (o) => `<span class="neighbor-chip">${this.escapeHtml(o.label)}</span>`
2018
+ (i) => `<span class="neighbor-chip">${this.escapeHtml(i.label)}</span>`
1990
2019
  ).join("")}
1991
2020
  ${s.length > 5 ? `<span class="neighbor-chip">+${s.length - 5} more</span>` : ""}
1992
2021
  </div>
@@ -2104,10 +2133,10 @@ class zt {
2104
2133
  show(e, s, t) {
2105
2134
  if (!this.panel) return;
2106
2135
  this.currentEdgeKey = `${e.source}-${e.target}`;
2107
- let o;
2108
- this.panelTemplate ? o = this.panelTemplate(e, s, t) : o = this.generateDefaultContent(e, s, t), this.panel.innerHTML = o;
2109
- const i = this.panel.querySelector('[data-action="close"]');
2110
- i && i.addEventListener("click", () => {
2136
+ let i;
2137
+ this.panelTemplate ? i = this.panelTemplate(e, s, t) : i = this.generateDefaultContent(e, s, t), this.panel.innerHTML = i;
2138
+ const o = this.panel.querySelector('[data-action="close"]');
2139
+ o && o.addEventListener("click", () => {
2111
2140
  this.hide(), this.onClose && this.onClose();
2112
2141
  });
2113
2142
  const a = this.panel.querySelector('[data-action="goto-source"]');
@@ -2123,7 +2152,7 @@ class zt {
2123
2152
  * Generates default panel content
2124
2153
  */
2125
2154
  generateDefaultContent(e, s, t) {
2126
- const o = s.color ? `#${s.color.toString(16).padStart(6, "0")}` : "#ff9966", i = t.color ? `#${t.color.toString(16).padStart(6, "0")}` : "#ff9966", a = e.relationship || "connected to";
2155
+ const i = s.color ? `#${s.color.toString(16).padStart(6, "0")}` : "#ff9966", o = t.color ? `#${t.color.toString(16).padStart(6, "0")}` : "#ff9966", a = e.relationship || "connected to";
2127
2156
  return `
2128
2157
  <style>
2129
2158
  .force-graph-edge-panel .panel-header {
@@ -2233,7 +2262,7 @@ class zt {
2233
2262
  <div class="node-card" data-action="goto-source" title="Click to focus on ${this.escapeHtml(s.label)}">
2234
2263
  <div class="node-type">Source</div>
2235
2264
  <div class="node-card-header">
2236
- <span class="color-dot" style="background: ${o}; box-shadow: 0 0 8px ${o}80;"></span>
2265
+ <span class="color-dot" style="background: ${i}; box-shadow: 0 0 8px ${i}80;"></span>
2237
2266
  <span class="node-label">${this.escapeHtml(s.label)}</span>
2238
2267
  </div>
2239
2268
  </div>
@@ -2243,7 +2272,7 @@ class zt {
2243
2272
  <div class="node-card" data-action="goto-target" title="Click to focus on ${this.escapeHtml(t.label)}">
2244
2273
  <div class="node-type">Target</div>
2245
2274
  <div class="node-card-header">
2246
- <span class="color-dot" style="background: ${i}; box-shadow: 0 0 8px ${i}80;"></span>
2275
+ <span class="color-dot" style="background: ${o}; box-shadow: 0 0 8px ${o}80;"></span>
2247
2276
  <span class="node-label">${this.escapeHtml(t.label)}</span>
2248
2277
  </div>
2249
2278
  </div>
@@ -2285,7 +2314,7 @@ class zt {
2285
2314
  this.panel && this.panel.parentNode && this.panel.parentNode.removeChild(this.panel), this.panel = null;
2286
2315
  }
2287
2316
  }
2288
- class Tt {
2317
+ class kt {
2289
2318
  constructor() {
2290
2319
  l(this, "tooltip", null);
2291
2320
  l(this, "visible", !1);
@@ -2331,14 +2360,14 @@ class Tt {
2331
2360
  */
2332
2361
  positionTooltip(e, s) {
2333
2362
  if (!this.tooltip) return;
2334
- const t = this.tooltip.getBoundingClientRect(), o = window.innerWidth, i = window.innerHeight;
2363
+ const t = this.tooltip.getBoundingClientRect(), i = window.innerWidth, o = window.innerHeight;
2335
2364
  let a = e + 15, r = s + 15;
2336
- a + t.width > o - 10 && (a = e - t.width - 15), r + t.height > i - 10 && (r = s - t.height - 15), a < 10 && (a = 10), r < 10 && (r = 10), this.tooltip.style.left = `${a}px`, this.tooltip.style.top = `${r}px`;
2365
+ a + t.width > i - 10 && (a = e - t.width - 15), r + t.height > o - 10 && (r = s - t.height - 15), a < 10 && (a = 10), r < 10 && (r = 10), this.tooltip.style.left = `${a}px`, this.tooltip.style.top = `${r}px`;
2337
2366
  }
2338
2367
  /**
2339
2368
  * Shows the tooltip with edge info
2340
2369
  */
2341
- show(e, s, t, o, i) {
2370
+ show(e, s, t, i, o) {
2342
2371
  if (!this.tooltip) return;
2343
2372
  const a = e.relationship || "connected to";
2344
2373
  this.tooltip.innerHTML = `
@@ -2353,7 +2382,7 @@ class Tt {
2353
2382
  <span style="font-weight: 600; color: #ff9966;">${this.escapeHtml(t.label)}</span>
2354
2383
  </div>
2355
2384
  </div>
2356
- `, this.positionTooltip(o, i), this.tooltip.style.opacity = "1", this.tooltip.style.transform = "translateY(0)", this.visible = !0;
2385
+ `, this.positionTooltip(i, o), this.tooltip.style.opacity = "1", this.tooltip.style.transform = "translateY(0)", this.visible = !0;
2357
2386
  }
2358
2387
  /**
2359
2388
  * Updates tooltip position (called externally on mouse move)
@@ -2387,7 +2416,7 @@ class Tt {
2387
2416
  this.tooltip && this.tooltip.parentNode && this.tooltip.parentNode.removeChild(this.tooltip), this.tooltip = null;
2388
2417
  }
2389
2418
  }
2390
- class kt {
2419
+ class Tt {
2391
2420
  constructor(e, s) {
2392
2421
  l(this, "container");
2393
2422
  l(this, "searchContainer", null);
@@ -2530,25 +2559,25 @@ class kt {
2530
2559
  this.searchResults.innerHTML = '<div class="f3d-no-results">No results found</div>', this.searchResults.style.display = "block";
2531
2560
  return;
2532
2561
  }
2533
- let o = "";
2534
- s.length > 0 && (o += '<div class="f3d-search-section-header">Nodes</div>', s.slice(0, 10).forEach((i) => {
2535
- const a = i.type || "Node";
2536
- o += `
2537
- <div class="f3d-search-result-item" data-node-id="${this.escapeHtml(i.id)}">
2538
- <div class="f3d-result-label">${this.escapeHtml(i.label)}</div>
2562
+ let i = "";
2563
+ s.length > 0 && (i += '<div class="f3d-search-section-header">Nodes</div>', s.slice(0, 10).forEach((o) => {
2564
+ const a = o.type || "Node";
2565
+ i += `
2566
+ <div class="f3d-search-result-item" data-node-id="${this.escapeHtml(o.id)}">
2567
+ <div class="f3d-result-label">${this.escapeHtml(o.label)}</div>
2539
2568
  <div class="f3d-result-type">${this.escapeHtml(a)}</div>
2540
2569
  </div>
2541
2570
  `;
2542
- }), s.length > 10 && (o += `<div class="f3d-no-results">+ ${s.length - 10} more nodes</div>`)), t.length > 0 && (o += '<div class="f3d-search-section-header">Relationships</div>', t.slice(0, 5).forEach(({ edge: i, sourceNode: a, targetNode: r }) => {
2543
- o += `
2544
- <div class="f3d-search-result-item" data-node-id="${this.escapeHtml(i.source)}">
2571
+ }), s.length > 10 && (i += `<div class="f3d-no-results">+ ${s.length - 10} more nodes</div>`)), t.length > 0 && (i += '<div class="f3d-search-section-header">Relationships</div>', t.slice(0, 5).forEach(({ edge: o, sourceNode: a, targetNode: r }) => {
2572
+ i += `
2573
+ <div class="f3d-search-result-item" data-node-id="${this.escapeHtml(o.source)}">
2545
2574
  <div class="f3d-result-label">${this.escapeHtml(a.label)} → ${this.escapeHtml(r.label)}</div>
2546
- <div class="f3d-result-relationship">${this.escapeHtml(i.relationship || "connected")}</div>
2575
+ <div class="f3d-result-relationship">${this.escapeHtml(o.relationship || "connected")}</div>
2547
2576
  </div>
2548
2577
  `;
2549
- }), t.length > 5 && (o += `<div class="f3d-no-results">+ ${t.length - 5} more relationships</div>`)), this.searchResults.innerHTML = o, this.searchResults.style.display = "block", this.searchResults.querySelectorAll(".f3d-search-result-item").forEach((i) => {
2550
- i.addEventListener("click", () => {
2551
- const a = i.dataset.nodeId;
2578
+ }), t.length > 5 && (i += `<div class="f3d-no-results">+ ${t.length - 5} more relationships</div>`)), this.searchResults.innerHTML = i, this.searchResults.style.display = "block", this.searchResults.querySelectorAll(".f3d-search-result-item").forEach((o) => {
2579
+ o.addEventListener("click", () => {
2580
+ const a = o.dataset.nodeId;
2552
2581
  a && (this.onResultClick(a), this.searchResults && (this.searchResults.style.display = "none"), this.searchInput && this.searchInput.blur());
2553
2582
  });
2554
2583
  });
@@ -2653,6 +2682,89 @@ class Pt {
2653
2682
  this.toggleContainer && this.toggleContainer.parentNode && this.toggleContainer.parentNode.removeChild(this.toggleContainer);
2654
2683
  }
2655
2684
  }
2685
+ class Lt {
2686
+ constructor(e) {
2687
+ l(this, "container");
2688
+ l(this, "legendContainer", null);
2689
+ l(this, "nodeCountElement", null);
2690
+ l(this, "linkCountElement", null);
2691
+ this.container = e, this.init();
2692
+ }
2693
+ init() {
2694
+ this.createLegendUI(), this.injectStyles();
2695
+ }
2696
+ createLegendUI() {
2697
+ this.legendContainer = document.createElement("div"), this.legendContainer.className = "f3d-legend-container";
2698
+ const e = document.createElement("div");
2699
+ e.className = "f3d-legend-title", e.textContent = "Graph";
2700
+ const s = this.createStatRow("Nodes"), t = this.createStatRow("Links");
2701
+ this.nodeCountElement = s.valueElement, this.linkCountElement = t.valueElement, this.legendContainer.appendChild(e), this.legendContainer.appendChild(s.rowElement), this.legendContainer.appendChild(t.rowElement), this.container.appendChild(this.legendContainer);
2702
+ }
2703
+ createStatRow(e) {
2704
+ const s = document.createElement("div");
2705
+ s.className = "f3d-legend-row";
2706
+ const t = document.createElement("span");
2707
+ t.className = "f3d-legend-label", t.textContent = e;
2708
+ const i = document.createElement("span");
2709
+ return i.className = "f3d-legend-value", i.textContent = "0", s.appendChild(t), s.appendChild(i), { rowElement: s, valueElement: i };
2710
+ }
2711
+ injectStyles() {
2712
+ const e = "f3d-legend-styles";
2713
+ if (document.getElementById(e))
2714
+ return;
2715
+ const s = document.createElement("style");
2716
+ s.id = e, s.textContent = `
2717
+ .f3d-legend-container {
2718
+ position: absolute;
2719
+ right: 20px;
2720
+ bottom: 20px;
2721
+ z-index: 100;
2722
+ min-width: 140px;
2723
+ padding: 10px 12px;
2724
+ display: flex;
2725
+ flex-direction: column;
2726
+ gap: 8px;
2727
+ background: rgba(255, 255, 255, 0.08);
2728
+ backdrop-filter: blur(20px);
2729
+ -webkit-backdrop-filter: blur(20px);
2730
+ border: 1px solid rgba(255, 255, 255, 0.12);
2731
+ border-radius: 12px;
2732
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
2733
+ color: white;
2734
+ font-family: inherit;
2735
+ pointer-events: none;
2736
+ }
2737
+ .f3d-legend-title {
2738
+ font-size: 10px;
2739
+ letter-spacing: 1px;
2740
+ text-transform: uppercase;
2741
+ font-weight: 700;
2742
+ color: rgba(255, 255, 255, 0.55);
2743
+ }
2744
+ .f3d-legend-row {
2745
+ display: flex;
2746
+ align-items: center;
2747
+ justify-content: space-between;
2748
+ gap: 12px;
2749
+ }
2750
+ .f3d-legend-label {
2751
+ font-size: 12px;
2752
+ color: rgba(255, 255, 255, 0.75);
2753
+ }
2754
+ .f3d-legend-value {
2755
+ font-size: 13px;
2756
+ font-weight: 700;
2757
+ color: rgba(255, 153, 102, 0.95);
2758
+ }
2759
+ `, document.head.appendChild(s);
2760
+ }
2761
+ updateCounts(e, s) {
2762
+ this.nodeCountElement && (this.nodeCountElement.textContent = e.toString()), this.linkCountElement && (this.linkCountElement.textContent = s.toString());
2763
+ }
2764
+ dispose() {
2765
+ this.legendContainer && this.legendContainer.parentNode && this.legendContainer.parentNode.removeChild(this.legendContainer);
2766
+ }
2767
+ }
2656
2768
  const Rt = {
2657
2769
  backgroundColor: "#0a0a0a",
2658
2770
  gridColor: "rgba(255, 255, 255, 0.03)",
@@ -2706,31 +2818,31 @@ class It {
2706
2818
  setupInteractions() {
2707
2819
  this.canvas.addEventListener("wheel", (e) => {
2708
2820
  e.preventDefault();
2709
- const s = this.canvas.getBoundingClientRect(), t = e.clientX - s.left, o = e.clientY - s.top, i = e.deltaY > 0 ? 0.9 : 1.1, a = Math.max(0.1, Math.min(5, this.transform.scale * i)), r = a / this.transform.scale;
2710
- this.transform.x = t - (t - this.transform.x) * r, this.transform.y = o - (o - this.transform.y) * r, this.transform.scale = a;
2821
+ const s = this.canvas.getBoundingClientRect(), t = e.clientX - s.left, i = e.clientY - s.top, o = e.deltaY > 0 ? 0.9 : 1.1, a = Math.max(0.1, Math.min(5, this.transform.scale * o)), r = a / this.transform.scale;
2822
+ this.transform.x = t - (t - this.transform.x) * r, this.transform.y = i - (i - this.transform.y) * r, this.transform.scale = a;
2711
2823
  }), this.canvas.addEventListener("mousedown", (e) => {
2712
- const s = this.canvas.getBoundingClientRect(), t = e.clientX - s.left, o = e.clientY - s.top, i = this.screenToWorld(t, o), a = this.findNodeAt(i.x, i.y);
2824
+ const s = this.canvas.getBoundingClientRect(), t = e.clientX - s.left, i = e.clientY - s.top, o = this.screenToWorld(t, i), a = this.findNodeAt(o.x, o.y);
2713
2825
  this.dragStartPos = { x: e.clientX, y: e.clientY }, a ? (this.isDragging = !0, this.draggedNode = a, this.canvas.style.cursor = "grabbing", this.isSimulating = !0) : (this.isPanning = !0, this.canvas.style.cursor = "grabbing"), this.lastMousePos = { x: e.clientX, y: e.clientY };
2714
2826
  }), this.canvas.addEventListener("mousemove", (e) => {
2715
- const s = this.canvas.getBoundingClientRect(), t = e.clientX - s.left, o = e.clientY - s.top, i = this.screenToWorld(t, o);
2827
+ const s = this.canvas.getBoundingClientRect(), t = e.clientX - s.left, i = e.clientY - s.top, o = this.screenToWorld(t, i);
2716
2828
  if (this.isDragging && this.draggedNode)
2717
- this.draggedNode.x = i.x, this.draggedNode.y = i.y, this.draggedNode.vx = 0, this.draggedNode.vy = 0;
2829
+ this.draggedNode.x = o.x, this.draggedNode.y = o.y, this.draggedNode.vx = 0, this.draggedNode.vy = 0;
2718
2830
  else if (this.isPanning) {
2719
2831
  const a = e.clientX - this.lastMousePos.x, r = e.clientY - this.lastMousePos.y;
2720
2832
  this.transform.x += a, this.transform.y += r, this.lastMousePos = { x: e.clientX, y: e.clientY };
2721
2833
  } else {
2722
- const a = this.findNodeAt(i.x, i.y);
2834
+ const a = this.findNodeAt(o.x, o.y);
2723
2835
  if (a !== this.hoveredNode && (this.hoveredNode = a, a ? (this.hoveredEdge && (this.hoveredEdge = null, this.options.onEdgeHover && this.options.onEdgeHover(null)), this.canvas.style.cursor = "pointer", this.options.onNodeHover && this.options.onNodeHover(a.data)) : this.options.onNodeHover && this.options.onNodeHover(null)), !a) {
2724
- const r = this.findEdgeAt(i.x, i.y);
2836
+ const r = this.findEdgeAt(o.x, o.y);
2725
2837
  r !== this.hoveredEdge && (this.hoveredEdge = r, this.canvas.style.cursor = r ? "pointer" : "grab", this.options.onEdgeHover && this.options.onEdgeHover(r ? r.data : null, e));
2726
2838
  }
2727
2839
  }
2728
2840
  }), this.canvas.addEventListener("mouseup", (e) => {
2729
- const s = Math.abs(e.clientX - this.dragStartPos.x), t = Math.abs(e.clientY - this.dragStartPos.y), o = s < 5 && t < 5;
2841
+ const s = Math.abs(e.clientX - this.dragStartPos.x), t = Math.abs(e.clientY - this.dragStartPos.y), i = s < 5 && t < 5;
2730
2842
  if (this.isDragging && this.draggedNode)
2731
- o && this.options.onNodeClick && (this.selectedNode = this.draggedNode, this.options.onNodeClick(this.draggedNode.data));
2732
- else if (o) {
2733
- const i = this.canvas.getBoundingClientRect(), a = this.screenToWorld(e.clientX - i.left, e.clientY - i.top), r = this.findEdgeAt(a.x, a.y);
2843
+ i && this.options.onNodeClick && (this.selectedNode = this.draggedNode, this.options.onNodeClick(this.draggedNode.data));
2844
+ else if (i) {
2845
+ const o = this.canvas.getBoundingClientRect(), a = this.screenToWorld(e.clientX - o.left, e.clientY - o.top), r = this.findEdgeAt(a.x, a.y);
2734
2846
  r && this.options.onEdgeClick && this.options.onEdgeClick(r.data);
2735
2847
  }
2736
2848
  this.isDragging = !1, this.isPanning = !1, this.draggedNode = null, this.canvas.style.cursor = this.hoveredNode ? "pointer" : "grab";
@@ -2746,21 +2858,21 @@ class It {
2746
2858
  }
2747
2859
  findNodeAt(e, s) {
2748
2860
  for (const t of this.nodes.values()) {
2749
- const o = t.x - e, i = t.y - s;
2750
- if (Math.sqrt(o * o + i * i) < t.radius)
2861
+ const i = t.x - e, o = t.y - s;
2862
+ if (Math.sqrt(i * i + o * o) < t.radius)
2751
2863
  return t;
2752
2864
  }
2753
2865
  return null;
2754
2866
  }
2755
2867
  findEdgeAt(e, s) {
2756
- for (const o of this.edges) {
2757
- const i = this.nodes.get(o.source), a = this.nodes.get(o.target);
2758
- if (!i || !a) continue;
2759
- const r = a.x - i.x, c = a.y - i.y, h = r * r + c * c;
2760
- if (h === 0) continue;
2761
- const p = Math.max(0, Math.min(1, ((e - i.x) * r + (s - i.y) * c) / h)), x = i.x + p * r, m = i.y + p * c, u = e - x, f = s - m;
2868
+ for (const i of this.edges) {
2869
+ const o = this.nodes.get(i.source), a = this.nodes.get(i.target);
2870
+ if (!o || !a) continue;
2871
+ const r = a.x - o.x, c = a.y - o.y, d = r * r + c * c;
2872
+ if (d === 0) continue;
2873
+ const p = Math.max(0, Math.min(1, ((e - o.x) * r + (s - o.y) * c) / d)), x = o.x + p * r, m = o.y + p * c, u = e - x, f = s - m;
2762
2874
  if (Math.sqrt(u * u + f * f) < 12)
2763
- return o;
2875
+ return i;
2764
2876
  }
2765
2877
  return null;
2766
2878
  }
@@ -2773,60 +2885,60 @@ class It {
2773
2885
  simulate() {
2774
2886
  const e = Array.from(this.nodes.values()), s = e.length;
2775
2887
  if (s === 0) return;
2776
- const t = 60, o = 5;
2777
- let i = 0;
2888
+ const t = 60, i = 5;
2889
+ let o = 0;
2778
2890
  for (let r = 0; r < s; r++)
2779
2891
  for (let c = r + 1; c < s; c++) {
2780
- const h = e[r], p = e[c];
2781
- let x = p.x - h.x, m = p.y - h.y, u = Math.sqrt(x * x + m * m);
2892
+ const d = e[r], p = e[c];
2893
+ let x = p.x - d.x, m = p.y - d.y, u = Math.sqrt(x * x + m * m);
2782
2894
  if (u < t * 3) {
2783
2895
  u < 1 && (u = 1);
2784
2896
  const f = this.options.repulsionStrength / (u * u), b = x / u * f, M = m / u * f;
2785
- h.vx -= b, h.vy -= M, p.vx += b, p.vy += M;
2897
+ d.vx -= b, d.vy -= M, p.vx += b, p.vy += M;
2786
2898
  }
2787
2899
  }
2788
2900
  const a = 80;
2789
2901
  for (const r of this.edges) {
2790
- const c = this.nodes.get(r.source), h = this.nodes.get(r.target);
2791
- if (!c || !h) continue;
2792
- let p = h.x - c.x, x = h.y - c.y, m = Math.sqrt(p * p + x * x);
2902
+ const c = this.nodes.get(r.source), d = this.nodes.get(r.target);
2903
+ if (!c || !d) continue;
2904
+ let p = d.x - c.x, x = d.y - c.y, m = Math.sqrt(p * p + x * x);
2793
2905
  m < 1 && (m = 1);
2794
2906
  const f = (m - a) * this.options.attractionStrength, b = p / m * f, M = x / m * f;
2795
- c.vx += b, c.vy += M, h.vx -= b, h.vy -= M;
2907
+ c.vx += b, c.vy += M, d.vx -= b, d.vy -= M;
2796
2908
  }
2797
2909
  for (const r of e) {
2798
2910
  if (this.draggedNode === r) continue;
2799
2911
  r.vx *= this.options.damping, r.vy *= this.options.damping;
2800
2912
  const c = Math.sqrt(r.vx * r.vx + r.vy * r.vy);
2801
- c > o && (r.vx = r.vx / c * o, r.vy = r.vy / c * o), r.x += r.vx, r.y += r.vy, i += r.vx * r.vx + r.vy * r.vy;
2913
+ c > i && (r.vx = r.vx / c * i, r.vy = r.vy / c * i), r.x += r.vx, r.y += r.vy, o += r.vx * r.vx + r.vy * r.vy;
2802
2914
  }
2803
- i < 0.01 && !this.draggedNode && (this.isSimulating = !1);
2915
+ o < 0.01 && !this.draggedNode && (this.isSimulating = !1);
2804
2916
  }
2805
2917
  render() {
2806
2918
  const e = this.ctx, s = this.canvas.width / (window.devicePixelRatio || 1), t = this.canvas.height / (window.devicePixelRatio || 1);
2807
2919
  e.fillStyle = this.options.backgroundColor, e.fillRect(0, 0, s, t), this.renderGrid(s, t), e.save(), e.translate(this.transform.x, this.transform.y), e.scale(this.transform.scale, this.transform.scale), this.renderEdges(), this.renderNodes(), e.restore();
2808
2920
  }
2809
2921
  renderGrid(e, s) {
2810
- const t = this.ctx, o = 40 * this.transform.scale, i = 1.5, a = this.transform.x % o, r = this.transform.y % o;
2922
+ const t = this.ctx, i = 40 * this.transform.scale, o = 1.5, a = this.transform.x % i, r = this.transform.y % i;
2811
2923
  t.fillStyle = this.options.gridColor;
2812
- for (let c = a; c < e; c += o)
2813
- for (let h = r; h < s; h += o)
2814
- t.beginPath(), t.arc(c, h, i, 0, Math.PI * 2), t.fill();
2924
+ for (let c = a; c < e; c += i)
2925
+ for (let d = r; d < s; d += i)
2926
+ t.beginPath(), t.arc(c, d, o, 0, Math.PI * 2), t.fill();
2815
2927
  }
2816
2928
  renderEdges() {
2817
2929
  const e = this.ctx;
2818
2930
  for (const s of this.edges) {
2819
- const t = this.nodes.get(s.source), o = this.nodes.get(s.target);
2820
- if (!t || !o) continue;
2821
- const i = s === this.hoveredEdge, a = e.createLinearGradient(t.x, t.y, o.x, o.y);
2822
- i ? (a.addColorStop(0, "rgba(255, 153, 102, 0.8)"), a.addColorStop(0.5, "rgba(255, 255, 255, 0.5)"), a.addColorStop(1, "rgba(102, 153, 255, 0.8)"), e.lineWidth = 3) : (a.addColorStop(0, "rgba(255, 153, 102, 0.3)"), a.addColorStop(0.5, "rgba(255, 255, 255, 0.15)"), a.addColorStop(1, "rgba(102, 153, 255, 0.3)"), e.lineWidth = 1.5), e.beginPath(), e.moveTo(t.x, t.y), e.lineTo(o.x, o.y), e.strokeStyle = a, e.stroke();
2931
+ const t = this.nodes.get(s.source), i = this.nodes.get(s.target);
2932
+ if (!t || !i) continue;
2933
+ const o = s === this.hoveredEdge, a = e.createLinearGradient(t.x, t.y, i.x, i.y);
2934
+ o ? (a.addColorStop(0, "rgba(255, 153, 102, 0.8)"), a.addColorStop(0.5, "rgba(255, 255, 255, 0.5)"), a.addColorStop(1, "rgba(102, 153, 255, 0.8)"), e.lineWidth = 3) : (a.addColorStop(0, "rgba(255, 153, 102, 0.3)"), a.addColorStop(0.5, "rgba(255, 255, 255, 0.15)"), a.addColorStop(1, "rgba(102, 153, 255, 0.3)"), e.lineWidth = 1.5), e.beginPath(), e.moveTo(t.x, t.y), e.lineTo(i.x, i.y), e.strokeStyle = a, e.stroke();
2823
2935
  }
2824
2936
  }
2825
2937
  renderNodes() {
2826
2938
  const e = this.ctx;
2827
2939
  for (const s of this.nodes.values()) {
2828
- const t = s === this.hoveredNode, o = s === this.selectedNode, i = this.hoveredEdge && (s.data.id === this.hoveredEdge.source || s.data.id === this.hoveredEdge.target), a = s.radius * (t ? 1.1 : 1);
2829
- if (t || o || i) {
2940
+ const t = s === this.hoveredNode, i = s === this.selectedNode, o = this.hoveredEdge && (s.data.id === this.hoveredEdge.source || s.data.id === this.hoveredEdge.target), a = s.radius * (t ? 1.1 : 1);
2941
+ if (t || i || o) {
2830
2942
  const f = e.createRadialGradient(
2831
2943
  s.x,
2832
2944
  s.y,
@@ -2834,7 +2946,7 @@ class It {
2834
2946
  s.x,
2835
2947
  s.y,
2836
2948
  a * 2
2837
- ), b = t || o ? 0.4 : 0.25;
2949
+ ), b = t || i ? 0.4 : 0.25;
2838
2950
  f.addColorStop(0, `rgba(255, 153, 102, ${b})`), f.addColorStop(1, "rgba(255, 153, 102, 0)"), e.fillStyle = f, e.beginPath(), e.arc(s.x, s.y, a * 2, 0, Math.PI * 2), e.fill();
2839
2951
  }
2840
2952
  const r = e.createRadialGradient(
@@ -2844,8 +2956,8 @@ class It {
2844
2956
  s.x,
2845
2957
  s.y,
2846
2958
  a
2847
- ), c = s.color >> 16 & 255, h = s.color >> 8 & 255, p = s.color & 255;
2848
- r.addColorStop(0, `rgba(${Math.min(255, c + 60)}, ${Math.min(255, h + 60)}, ${Math.min(255, p + 60)}, 0.95)`), r.addColorStop(0.7, `rgba(${c}, ${h}, ${p}, 0.9)`), r.addColorStop(1, `rgba(${Math.max(0, c - 40)}, ${Math.max(0, h - 40)}, ${Math.max(0, p - 40)}, 0.85)`), e.beginPath(), e.arc(s.x, s.y, a, 0, Math.PI * 2), e.fillStyle = r, e.fill(), e.strokeStyle = "rgba(255, 255, 255, 0.2)", e.lineWidth = 1, e.stroke(), e.beginPath(), e.arc(s.x - a * 0.25, s.y - a * 0.25, a * 0.3, 0, Math.PI * 2), e.fillStyle = "rgba(255, 255, 255, 0.15)", e.fill(), e.fillStyle = "white", e.font = "600 11px Inter, -apple-system, BlinkMacSystemFont, sans-serif", e.textAlign = "center", e.textBaseline = "middle";
2959
+ ), c = s.color >> 16 & 255, d = s.color >> 8 & 255, p = s.color & 255;
2960
+ r.addColorStop(0, `rgba(${Math.min(255, c + 60)}, ${Math.min(255, d + 60)}, ${Math.min(255, p + 60)}, 0.95)`), r.addColorStop(0.7, `rgba(${c}, ${d}, ${p}, 0.9)`), r.addColorStop(1, `rgba(${Math.max(0, c - 40)}, ${Math.max(0, d - 40)}, ${Math.max(0, p - 40)}, 0.85)`), e.beginPath(), e.arc(s.x, s.y, a, 0, Math.PI * 2), e.fillStyle = r, e.fill(), e.strokeStyle = "rgba(255, 255, 255, 0.2)", e.lineWidth = 1, e.stroke(), e.beginPath(), e.arc(s.x - a * 0.25, s.y - a * 0.25, a * 0.3, 0, Math.PI * 2), e.fillStyle = "rgba(255, 255, 255, 0.15)", e.fill(), e.fillStyle = "white", e.font = "600 11px Inter, -apple-system, BlinkMacSystemFont, sans-serif", e.textAlign = "center", e.textBaseline = "middle";
2849
2961
  const x = a * 1.6;
2850
2962
  let m = s.label, u = e.measureText(m).width;
2851
2963
  if (u > x) {
@@ -2861,14 +2973,14 @@ class It {
2861
2973
  */
2862
2974
  setData(e) {
2863
2975
  this.nodes.clear(), this.edges = [], this.nodeIdToIndex.clear(), e.nodes.forEach((s, t) => {
2864
- const o = s.position || {
2976
+ const i = s.position || {
2865
2977
  x: (Math.random() - 0.5) * 300,
2866
2978
  y: (Math.random() - 0.5) * 300
2867
- }, i = {
2979
+ }, o = {
2868
2980
  id: s.id,
2869
2981
  label: s.label,
2870
- x: o.x,
2871
- y: o.y,
2982
+ x: i.x,
2983
+ y: i.y,
2872
2984
  vx: 0,
2873
2985
  vy: 0,
2874
2986
  color: s.color || 16750950,
@@ -2876,7 +2988,7 @@ class It {
2876
2988
  radius: this.options.nodeRadius,
2877
2989
  data: s
2878
2990
  };
2879
- this.nodes.set(s.id, i), this.nodeIdToIndex.set(s.id, t);
2991
+ this.nodes.set(s.id, o), this.nodeIdToIndex.set(s.id, t);
2880
2992
  }), this.edges = e.edges.map((s) => ({
2881
2993
  source: s.source,
2882
2994
  target: s.target,
@@ -2952,19 +3064,19 @@ class It {
2952
3064
  focusOnNode(e) {
2953
3065
  const s = this.nodes.get(e);
2954
3066
  if (!s) return;
2955
- const t = this.canvas.width / 2 / (window.devicePixelRatio || 1) - s.x * this.transform.scale, o = this.canvas.height / 2 / (window.devicePixelRatio || 1) - s.y * this.transform.scale, i = this.transform.x, a = this.transform.y, r = 500, c = performance.now(), h = () => {
3067
+ const t = this.canvas.width / 2 / (window.devicePixelRatio || 1) - s.x * this.transform.scale, i = this.canvas.height / 2 / (window.devicePixelRatio || 1) - s.y * this.transform.scale, o = this.transform.x, a = this.transform.y, r = 500, c = performance.now(), d = () => {
2956
3068
  const p = performance.now() - c, x = Math.min(p / r, 1), m = 1 - Math.pow(1 - x, 3);
2957
- this.transform.x = i + (t - i) * m, this.transform.y = a + (o - a) * m, x < 1 ? requestAnimationFrame(h) : this.selectedNode = s;
3069
+ this.transform.x = o + (t - o) * m, this.transform.y = a + (i - a) * m, x < 1 ? requestAnimationFrame(d) : this.selectedNode = s;
2958
3070
  };
2959
- h();
3071
+ d();
2960
3072
  }
2961
3073
  /**
2962
3074
  * Updates node positions from 3D data
2963
3075
  */
2964
3076
  syncFrom3D(e) {
2965
3077
  e.forEach((s, t) => {
2966
- const o = this.nodes.get(t);
2967
- o && (o.x = s.position.x * 3, o.y = s.position.y * 3, o.vx = 0, o.vy = 0);
3078
+ const i = this.nodes.get(t);
3079
+ i && (i.x = s.position.x * 3, i.y = s.position.y * 3, i.vx = 0, i.vy = 0);
2968
3080
  }), this.isSimulating = !1;
2969
3081
  }
2970
3082
  /**
@@ -3024,6 +3136,7 @@ class Ht {
3024
3136
  l(this, "edgeTooltipManager");
3025
3137
  l(this, "searchManager", null);
3026
3138
  l(this, "viewToggleManager", null);
3139
+ l(this, "legendManager", null);
3027
3140
  // 2D Renderer
3028
3141
  l(this, "forceGraph2D", null);
3029
3142
  // Event system
@@ -3033,7 +3146,7 @@ class Ht {
3033
3146
  l(this, "devControls", null);
3034
3147
  l(this, "viewMode", "3d");
3035
3148
  l(this, "graphData", null);
3036
- this.options = { ...P, ...s }, this.container = dt(e), this.materialFactory = new vt(), this.nodeFactory = new Mt(
3149
+ this.options = { ...P, ...s }, this.container = ht(e), this.materialFactory = new vt(), this.nodeFactory = new Mt(
3037
3150
  this.materialFactory,
3038
3151
  this.options.nodeRadius ?? P.nodeRadius,
3039
3152
  this.options.lodSegments ?? P.lodSegments,
@@ -3049,7 +3162,7 @@ class Ht {
3049
3162
  ), this.frustumCuller = new Ct(
3050
3163
  this.sceneManager.camera,
3051
3164
  this.options.enableEdgeCulling ?? P.enableEdgeCulling
3052
- ), this.nodeManager = new te(this.sceneManager, this.nodeFactory), this.edgeManager = new yt(this.sceneManager, this.nodeManager, this.edgeFactory), this.graphEngine = new Le(
3165
+ ), this.nodeManager = new te(this.sceneManager, this.nodeFactory), this.edgeManager = new yt(this.sceneManager, this.nodeManager, this.edgeFactory), this.graphEngine = new Ie(
3053
3166
  this.nodeManager.getAllNodes(),
3054
3167
  this.edgeManager.getAllEdges(),
3055
3168
  {
@@ -3064,11 +3177,11 @@ class Ht {
3064
3177
  () => this.onSimulate(),
3065
3178
  () => this.onRender(),
3066
3179
  this.options.targetFPS ?? P.targetFPS
3067
- ), this.raycasterManager = new Nt(this.sceneManager, this.container), this.panelManager = new St(this.container), this.edgePanelManager = new zt(this.container), this.edgeTooltipManager = new Tt(), this.edgePanelManager.setNodeClickCallback((t) => {
3180
+ ), this.raycasterManager = new Nt(this.sceneManager, this.container), this.panelManager = new St(this.container), this.edgePanelManager = new zt(this.container), this.edgeTooltipManager = new kt(), this.edgePanelManager.setNodeClickCallback((t) => {
3068
3181
  this.edgePanelManager.hide(), this.focusOnNode(t), setTimeout(() => {
3069
3182
  this.showNodePanel(t);
3070
3183
  }, 400);
3071
- }), this.options.showSearch !== !1 && (this.searchManager = new kt(this.container, {
3184
+ }), this.options.showSearch !== !1 && (this.searchManager = new Tt(this.container, {
3072
3185
  placeholder: this.options.searchPlaceholder,
3073
3186
  onSearch: (t) => ({
3074
3187
  nodeResults: this.searchNodes(t),
@@ -3084,7 +3197,7 @@ class Ht {
3084
3197
  onViewChange: (t) => {
3085
3198
  this.switchView(t);
3086
3199
  }
3087
- })), this.setupCallbacks(), this.rendererManager.start(), this.initialized = !0, this.emit("ready");
3200
+ })), this.options.showLegend !== !1 && (this.legendManager = new Lt(this.container), this.updateLegendCounts()), this.setupCallbacks(), this.rendererManager.start(), this.initialized = !0, this.emit("ready");
3088
3201
  }
3089
3202
  /**
3090
3203
  * Sets up internal callbacks
@@ -3106,8 +3219,8 @@ class Ht {
3106
3219
  onEdgeHover(e) {
3107
3220
  if (e) {
3108
3221
  this.edgeManager.highlightEdge(e.edge.source, e.edge.target);
3109
- const s = this.container.getBoundingClientRect(), t = s.left + s.width / 2, o = s.top + s.height / 2;
3110
- this.edgeTooltipManager.show(e.edge, e.sourceNode, e.targetNode, t, o), this.options.onEdgeHover && this.options.onEdgeHover(e.edge, e.sourceNode, e.targetNode), this.emit("edgeHover", e.edge, e.sourceNode, e.targetNode);
3222
+ const s = this.container.getBoundingClientRect(), t = s.left + s.width / 2, i = s.top + s.height / 2;
3223
+ this.edgeTooltipManager.show(e.edge, e.sourceNode, e.targetNode, t, i), this.options.onEdgeHover && this.options.onEdgeHover(e.edge, e.sourceNode, e.targetNode), this.emit("edgeHover", e.edge, e.sourceNode, e.targetNode);
3111
3224
  } else
3112
3225
  this.edgeManager.unhighlightCurrentEdge(), this.edgeTooltipManager.hide(), this.options.onEdgeHover && this.options.onEdgeHover(null, null, null), this.emit("edgeHover", null, null, null);
3113
3226
  }
@@ -3116,7 +3229,7 @@ class Ht {
3116
3229
  */
3117
3230
  onNodeClick(e) {
3118
3231
  this.edgePanelManager.hide();
3119
- const t = this.edgeManager.getNeighborIds(e.id).map((o) => this.nodeManager.getNode(o)).filter((o) => o !== void 0);
3232
+ const t = this.edgeManager.getNeighborIds(e.id).map((i) => this.nodeManager.getNode(i)).filter((i) => i !== void 0);
3120
3233
  this.options.showPanel !== !1 && this.panelManager.show(e, t), this.options.onNodeClick && this.options.onNodeClick(e), this.emit("nodeClick", e);
3121
3234
  }
3122
3235
  /**
@@ -3143,6 +3256,12 @@ class Ht {
3143
3256
  onRender() {
3144
3257
  this.frustumCuller.update(), this.raycasterManager.setNodeObjects(this.nodeManager.getAllNodeObjects()), this.raycasterManager.setEdgeObjects(this.edgeManager.getAllEdgeLines());
3145
3258
  }
3259
+ /**
3260
+ * Updates bottom-right legend counts
3261
+ */
3262
+ updateLegendCounts() {
3263
+ this.legendManager && this.legendManager.updateCounts(this.getNodeCount(), this.getEdgeCount());
3264
+ }
3146
3265
  // ==========================================================================
3147
3266
  // Public API
3148
3267
  // ==========================================================================
@@ -3151,22 +3270,22 @@ class Ht {
3151
3270
  */
3152
3271
  setData(e) {
3153
3272
  var a;
3154
- const s = (a = e.data) != null && a.nodes ? e.data : e, o = (s.edges || s.links || []).map((r) => ({
3273
+ const s = (a = e.data) != null && a.nodes ? e.data : e, i = (s.edges || s.links || []).map((r) => ({
3155
3274
  ...r,
3156
3275
  relationship: r.relationship || r.label || "related_to"
3157
- })), i = {
3276
+ })), o = {
3158
3277
  nodes: s.nodes || [],
3159
- edges: o
3278
+ edges: i
3160
3279
  };
3161
- if (this.graphData = i, this.edgeManager.clear(), this.nodeManager.clear(), i.nodes && Array.isArray(i.nodes)) {
3162
- te.setExpectedNodeCount(i.nodes.length);
3163
- for (const r of i.nodes)
3280
+ if (this.graphData = o, this.edgeManager.clear(), this.nodeManager.clear(), o.nodes && Array.isArray(o.nodes)) {
3281
+ te.setExpectedNodeCount(o.nodes.length);
3282
+ for (const r of o.nodes)
3164
3283
  this.addNode(r);
3165
3284
  }
3166
- if (i.edges && Array.isArray(i.edges))
3167
- for (const r of i.edges)
3285
+ if (o.edges && Array.isArray(o.edges))
3286
+ for (const r of o.edges)
3168
3287
  this.addEdge(r);
3169
- this.graphEngine = new Le(
3288
+ this.graphEngine = new Ie(
3170
3289
  this.nodeManager.getAllNodes(),
3171
3290
  this.edgeManager.getAllEdges(),
3172
3291
  {
@@ -3176,17 +3295,17 @@ class Ht {
3176
3295
  useBarnesHut: this.options.useBarnesHut,
3177
3296
  barnesHutTheta: this.options.barnesHutTheta
3178
3297
  }
3179
- ), this.graphEngine.restart(), this.forceGraph2D && this.forceGraph2D.setData(i);
3298
+ ), this.graphEngine.restart(), this.forceGraph2D && this.forceGraph2D.setData(o), this.updateLegendCounts();
3180
3299
  }
3181
3300
  /**
3182
3301
  * Adds a node to the graph
3183
3302
  * @returns true if added, false if node already exists or invalid
3184
3303
  */
3185
3304
  addNode(e) {
3186
- if (!He(e))
3305
+ if (!De(e))
3187
3306
  return !1;
3188
3307
  const s = this.nodeManager.addNode(e);
3189
- return s && (this.graphEngine.restart(), this.forceGraph2D && this.forceGraph2D.addNode(e), this.options.onNodeAdd && this.options.onNodeAdd(e), this.emit("nodeAdd", e)), s;
3308
+ return s && (this.graphEngine.restart(), this.forceGraph2D && this.forceGraph2D.addNode(e), this.options.onNodeAdd && this.options.onNodeAdd(e), this.emit("nodeAdd", e), this.updateLegendCounts()), s;
3190
3309
  }
3191
3310
  /**
3192
3311
  * Removes a node from the graph
@@ -3197,7 +3316,7 @@ class Ht {
3197
3316
  return !1;
3198
3317
  this.edgeManager.removeEdgesForNode(e);
3199
3318
  const s = this.nodeManager.removeNode(e);
3200
- return s && (this.graphEngine.restart(), this.options.onNodeRemove && this.options.onNodeRemove(e), this.emit("nodeRemove", e), this.panelManager.getCurrentNodeId() === e && this.panelManager.hide()), s;
3319
+ return s && (this.graphEngine.restart(), this.options.onNodeRemove && this.options.onNodeRemove(e), this.emit("nodeRemove", e), this.panelManager.getCurrentNodeId() === e && this.panelManager.hide(), this.updateLegendCounts()), s;
3201
3320
  }
3202
3321
  /**
3203
3322
  * Updates a node's properties
@@ -3210,10 +3329,10 @@ class Ht {
3210
3329
  * @returns true if added, false if edge already exists or nodes don't exist
3211
3330
  */
3212
3331
  addEdge(e) {
3213
- if (!De(e))
3332
+ if (!He(e))
3214
3333
  return !1;
3215
3334
  const s = this.edgeManager.addEdge(e);
3216
- return s && (this.graphEngine.setEdges(this.edgeManager.getAllEdges()), this.graphEngine.restart(), this.forceGraph2D && this.forceGraph2D.addEdge(e), this.options.onEdgeAdd && this.options.onEdgeAdd(e), this.emit("edgeAdd", e)), s;
3335
+ return s && (this.graphEngine.setEdges(this.edgeManager.getAllEdges()), this.graphEngine.restart(), this.forceGraph2D && this.forceGraph2D.addEdge(e), this.options.onEdgeAdd && this.options.onEdgeAdd(e), this.emit("edgeAdd", e), this.updateLegendCounts()), s;
3217
3336
  }
3218
3337
  /**
3219
3338
  * Removes an edge from the graph
@@ -3221,7 +3340,7 @@ class Ht {
3221
3340
  */
3222
3341
  removeEdge(e, s) {
3223
3342
  const t = this.edgeManager.removeEdge(e, s);
3224
- return t && (this.graphEngine.setEdges(this.edgeManager.getAllEdges()), this.options.onEdgeRemove && this.options.onEdgeRemove({ source: e, target: s }), this.emit("edgeRemove", { source: e, target: s })), t;
3343
+ return t && (this.graphEngine.setEdges(this.edgeManager.getAllEdges()), this.options.onEdgeRemove && this.options.onEdgeRemove({ source: e, target: s }), this.emit("edgeRemove", { source: e, target: s }), this.updateLegendCounts()), t;
3225
3344
  }
3226
3345
  /**
3227
3346
  * Expands a node by fetching more data
@@ -3230,20 +3349,20 @@ class Ht {
3230
3349
  * @param fetchFn - Optional fetch function to override the default
3231
3350
  */
3232
3351
  async expandNode(e, s = 1, t) {
3233
- const o = t ?? this.options.onExpand;
3234
- if (!o)
3352
+ const i = t ?? this.options.onExpand;
3353
+ if (!i)
3235
3354
  return console.warn("[ForceGraph3D] No expand callback provided"), !1;
3236
3355
  try {
3237
- const i = await o(e, s);
3238
- if (i.nodes && Array.isArray(i.nodes))
3239
- for (const a of i.nodes)
3356
+ const o = await i(e, s);
3357
+ if (o.nodes && Array.isArray(o.nodes))
3358
+ for (const a of o.nodes)
3240
3359
  this.addNode(a);
3241
- if (i.edges && Array.isArray(i.edges))
3242
- for (const a of i.edges)
3360
+ if (o.edges && Array.isArray(o.edges))
3361
+ for (const a of o.edges)
3243
3362
  this.addEdge(a);
3244
- return this.panelManager.hide(), this.emit("expand", e, i), !0;
3245
- } catch (i) {
3246
- return console.error("[ForceGraph3D] Error expanding node:", i), !1;
3363
+ return this.panelManager.hide(), this.emit("expand", e, o), !0;
3364
+ } catch (o) {
3365
+ return console.error("[ForceGraph3D] Error expanding node:", o), !1;
3247
3366
  }
3248
3367
  }
3249
3368
  /**
@@ -3290,13 +3409,13 @@ class Ht {
3290
3409
  console.warn(`[ForceGraph3D] Node "${e}" not found`);
3291
3410
  return;
3292
3411
  }
3293
- const o = t.position, i = this.sceneManager.camera, a = this.sceneManager.controls, r = i.position.clone().sub(a.target).normalize(), c = {
3294
- x: o.x + r.x * s,
3295
- y: o.y + r.y * s,
3296
- z: o.z + r.z * s
3297
- }, h = { x: i.position.x, y: i.position.y, z: i.position.z }, p = { x: a.target.x, y: a.target.y, z: a.target.z }, x = 800, m = performance.now(), u = () => {
3412
+ const i = t.position, o = this.sceneManager.camera, a = this.sceneManager.controls, r = o.position.clone().sub(a.target).normalize(), c = {
3413
+ x: i.x + r.x * s,
3414
+ y: i.y + r.y * s,
3415
+ z: i.z + r.z * s
3416
+ }, d = { x: o.position.x, y: o.position.y, z: o.position.z }, p = { x: a.target.x, y: a.target.y, z: a.target.z }, x = 800, m = performance.now(), u = () => {
3298
3417
  const f = performance.now() - m, b = Math.min(f / x, 1), M = 1 - Math.pow(1 - b, 3);
3299
- i.position.x = h.x + (c.x - h.x) * M, i.position.y = h.y + (c.y - h.y) * M, i.position.z = h.z + (c.z - h.z) * M, a.target.x = p.x + (o.x - p.x) * M, a.target.y = p.y + (o.y - p.y) * M, a.target.z = p.z + (o.z - p.z) * M, a.update(), b < 1 && requestAnimationFrame(u);
3418
+ o.position.x = d.x + (c.x - d.x) * M, o.position.y = d.y + (c.y - d.y) * M, o.position.z = d.z + (c.z - d.z) * M, a.target.x = p.x + (i.x - p.x) * M, a.target.y = p.y + (i.y - p.y) * M, a.target.z = p.z + (i.z - p.z) * M, a.update(), b < 1 && requestAnimationFrame(u);
3300
3419
  };
3301
3420
  u();
3302
3421
  }
@@ -3305,22 +3424,22 @@ class Ht {
3305
3424
  * Camera targets the midpoint and zooms out enough to see both nodes
3306
3425
  */
3307
3426
  focusOnEdge(e, s, t = 1.5) {
3308
- const o = this.nodeManager.getNode(e), i = this.nodeManager.getNode(s);
3309
- if (!o || !i) {
3427
+ const i = this.nodeManager.getNode(e), o = this.nodeManager.getNode(s);
3428
+ if (!i || !o) {
3310
3429
  console.warn(`[ForceGraph3D] Could not find nodes for edge "${e}" -> "${s}"`);
3311
3430
  return;
3312
3431
  }
3313
3432
  const a = this.sceneManager.camera, r = this.sceneManager.controls, c = {
3314
- x: (o.position.x + i.position.x) / 2,
3315
- y: (o.position.y + i.position.y) / 2,
3316
- z: (o.position.z + i.position.z) / 2
3317
- }, h = i.position.x - o.position.x, p = i.position.y - o.position.y, x = i.position.z - o.position.z, m = Math.sqrt(h * h + p * p + x * x), u = Math.max(m * t, 40), f = a.position.clone().sub(r.target).normalize(), b = {
3433
+ x: (i.position.x + o.position.x) / 2,
3434
+ y: (i.position.y + o.position.y) / 2,
3435
+ z: (i.position.z + o.position.z) / 2
3436
+ }, d = o.position.x - i.position.x, p = o.position.y - i.position.y, x = o.position.z - i.position.z, m = Math.sqrt(d * d + p * p + x * x), u = Math.max(m * t, 40), f = a.position.clone().sub(r.target).normalize(), b = {
3318
3437
  x: c.x + f.x * u,
3319
3438
  y: c.y + f.y * u,
3320
3439
  z: c.z + f.z * u
3321
- }, M = { x: a.position.x, y: a.position.y, z: a.position.z }, N = { x: r.target.x, y: r.target.y, z: r.target.z }, O = 800, R = performance.now(), Y = () => {
3322
- const k = performance.now() - R, H = Math.min(k / O, 1), E = 1 - Math.pow(1 - H, 3);
3323
- a.position.x = M.x + (b.x - M.x) * E, a.position.y = M.y + (b.y - M.y) * E, a.position.z = M.z + (b.z - M.z) * E, r.target.x = N.x + (c.x - N.x) * E, r.target.y = N.y + (c.y - N.y) * E, r.target.z = N.z + (c.z - N.z) * E, r.update(), H < 1 && requestAnimationFrame(Y);
3440
+ }, M = { x: a.position.x, y: a.position.y, z: a.position.z }, N = { x: r.target.x, y: r.target.y, z: r.target.z }, O = 800, L = performance.now(), Y = () => {
3441
+ const T = performance.now() - L, D = Math.min(T / O, 1), E = 1 - Math.pow(1 - D, 3);
3442
+ a.position.x = M.x + (b.x - M.x) * E, a.position.y = M.y + (b.y - M.y) * E, a.position.z = M.z + (b.z - M.z) * E, r.target.x = N.x + (c.x - N.x) * E, r.target.y = N.y + (c.y - N.y) * E, r.target.z = N.z + (c.z - N.z) * E, r.update(), D < 1 && requestAnimationFrame(Y);
3324
3443
  };
3325
3444
  Y();
3326
3445
  }
@@ -3343,28 +3462,28 @@ class Ht {
3343
3462
  searchNodes(e) {
3344
3463
  if (!e || e.trim() === "")
3345
3464
  return [];
3346
- const s = e.toLowerCase().trim(), t = this.nodeManager.getAllNodes(), o = [];
3347
- return t.forEach((i) => {
3348
- var h, p, x;
3349
- const a = (h = i.label) == null ? void 0 : h.toLowerCase().includes(s), r = (p = i.id) == null ? void 0 : p.toLowerCase().includes(s), c = (x = i.type) == null ? void 0 : x.toLowerCase().includes(s);
3350
- (a || r || c) && o.push(i);
3351
- }), o;
3465
+ const s = e.toLowerCase().trim(), t = this.nodeManager.getAllNodes(), i = [];
3466
+ return t.forEach((o) => {
3467
+ var d, p, x;
3468
+ const a = (d = o.label) == null ? void 0 : d.toLowerCase().includes(s), r = (p = o.id) == null ? void 0 : p.toLowerCase().includes(s), c = (x = o.type) == null ? void 0 : x.toLowerCase().includes(s);
3469
+ (a || r || c) && i.push(o);
3470
+ }), i;
3352
3471
  }
3353
3472
  /**
3354
3473
  * Searches edges by relationship (case-insensitive)
3355
3474
  * @returns Array of matching edges with source/target node info
3356
3475
  */
3357
3476
  searchEdges(e) {
3358
- var i;
3477
+ var o;
3359
3478
  if (!e || e.trim() === "")
3360
3479
  return [];
3361
- const s = e.toLowerCase().trim(), t = this.edgeManager.getAllEdges(), o = [];
3480
+ const s = e.toLowerCase().trim(), t = this.edgeManager.getAllEdges(), i = [];
3362
3481
  for (const a of t)
3363
- if ((i = a.relationship) == null ? void 0 : i.toLowerCase().includes(s)) {
3364
- const c = this.nodeManager.getNode(a.source), h = this.nodeManager.getNode(a.target);
3365
- c && h && o.push({ edge: a, sourceNode: c, targetNode: h });
3482
+ if ((o = a.relationship) == null ? void 0 : o.toLowerCase().includes(s)) {
3483
+ const c = this.nodeManager.getNode(a.source), d = this.nodeManager.getNode(a.target);
3484
+ c && d && i.push({ edge: a, sourceNode: c, targetNode: d });
3366
3485
  }
3367
- return o;
3486
+ return i;
3368
3487
  }
3369
3488
  /**
3370
3489
  * Gets all nodes as an array
@@ -3406,11 +3525,11 @@ class Ht {
3406
3525
  },
3407
3526
  onEdgeHover: (s, t) => {
3408
3527
  if (s && t) {
3409
- const o = this.nodeManager.getNode(s.source), i = this.nodeManager.getNode(s.target);
3410
- o && i && this.edgeTooltipManager.show(
3528
+ const i = this.nodeManager.getNode(s.source), o = this.nodeManager.getNode(s.target);
3529
+ i && o && this.edgeTooltipManager.show(
3411
3530
  s,
3412
- o,
3413
3531
  i,
3532
+ o,
3414
3533
  t.clientX,
3415
3534
  t.clientY
3416
3535
  );
@@ -3418,8 +3537,8 @@ class Ht {
3418
3537
  this.edgeTooltipManager.hide();
3419
3538
  },
3420
3539
  onEdgeClick: (s) => {
3421
- const t = this.nodeManager.getNode(s.source), o = this.nodeManager.getNode(s.target);
3422
- t && o && this.edgePanelManager.show(s, t, o);
3540
+ const t = this.nodeManager.getNode(s.source), i = this.nodeManager.getNode(s.target);
3541
+ t && i && this.edgePanelManager.show(s, t, i);
3423
3542
  }
3424
3543
  }), this.graphData && (this.forceGraph2D.setData(this.graphData), this.forceGraph2D.syncFrom3D(this.nodeManager.getAllNodes())))) : (this.forceGraph2D && this.forceGraph2D.hide(), this.sceneManager.renderer.domElement.style.display = "block", this.rendererManager.start()), this.emit("viewChange", e));
3425
3544
  }
@@ -3434,7 +3553,7 @@ class Ht {
3434
3553
  */
3435
3554
  emit(e, ...s) {
3436
3555
  const t = this.eventCallbacks.get(e);
3437
- t && t.forEach((o) => o(...s));
3556
+ t && t.forEach((i) => i(...s));
3438
3557
  }
3439
3558
  /**
3440
3559
  * Sets physics parameters for both 3D and 2D views
@@ -3511,24 +3630,24 @@ class Ht {
3511
3630
  `, this.container.appendChild(this.devControls);
3512
3631
  const e = this.devControls.querySelector("#dev-repulsion"), s = this.devControls.querySelector("#dev-attraction"), t = this.devControls.querySelector("#dev-damping");
3513
3632
  e == null || e.addEventListener("input", () => {
3514
- const o = parseFloat(e.value);
3515
- this.setPhysicsParams({ repulsionStrength: o }), this.devControls.querySelector("#dev-repulsion-val").textContent = o.toString();
3633
+ const i = parseFloat(e.value);
3634
+ this.setPhysicsParams({ repulsionStrength: i }), this.devControls.querySelector("#dev-repulsion-val").textContent = i.toString();
3516
3635
  }), s == null || s.addEventListener("input", () => {
3517
- const o = parseFloat(s.value) / 1e3;
3518
- this.setPhysicsParams({ attractionStrength: o }), this.devControls.querySelector("#dev-attraction-val").textContent = o.toFixed(3);
3636
+ const i = parseFloat(s.value) / 1e3;
3637
+ this.setPhysicsParams({ attractionStrength: i }), this.devControls.querySelector("#dev-attraction-val").textContent = i.toFixed(3);
3519
3638
  }), t == null || t.addEventListener("input", () => {
3520
- const o = parseFloat(t.value) / 100;
3521
- this.setPhysicsParams({ damping: o }), this.devControls.querySelector("#dev-damping-val").textContent = o.toFixed(2);
3639
+ const i = parseFloat(t.value) / 100;
3640
+ this.setPhysicsParams({ damping: i }), this.devControls.querySelector("#dev-damping-val").textContent = i.toFixed(2);
3522
3641
  }), setInterval(() => {
3523
- const o = this.devControls.querySelector("#dev-node-count"), i = this.devControls.querySelector("#dev-edge-count"), a = this.devControls.querySelector("#dev-fps");
3524
- o && (o.textContent = this.getNodeCount().toString()), i && (i.textContent = this.getEdgeCount().toString()), a && (a.textContent = this.rendererManager.getFPS().toString());
3642
+ const i = this.devControls.querySelector("#dev-node-count"), o = this.devControls.querySelector("#dev-edge-count"), a = this.devControls.querySelector("#dev-fps");
3643
+ i && (i.textContent = this.getNodeCount().toString()), o && (o.textContent = this.getEdgeCount().toString()), a && (a.textContent = this.rendererManager.getFPS().toString());
3525
3644
  }, 500);
3526
3645
  }
3527
3646
  /**
3528
3647
  * Destroys the graph and releases all resources
3529
3648
  */
3530
3649
  destroy() {
3531
- this.rendererManager.dispose(), this.panelManager.dispose(), this.raycasterManager.dispose(), this.edgeTooltipManager.dispose(), this.searchManager && this.searchManager.dispose(), this.viewToggleManager && this.viewToggleManager.dispose(), this.forceGraph2D && this.forceGraph2D.dispose(), this.edgeManager.dispose(), this.nodeManager.dispose(), this.nodeFactory.dispose(), this.materialFactory.dispose(), this.sceneManager.dispose(), this.devControls && this.devControls.parentNode && this.devControls.parentNode.removeChild(this.devControls), this.eventCallbacks.clear(), this.initialized = !1;
3650
+ this.rendererManager.dispose(), this.panelManager.dispose(), this.raycasterManager.dispose(), this.edgeTooltipManager.dispose(), this.searchManager && this.searchManager.dispose(), this.viewToggleManager && this.viewToggleManager.dispose(), this.legendManager && this.legendManager.dispose(), this.forceGraph2D && this.forceGraph2D.dispose(), this.edgeManager.dispose(), this.nodeManager.dispose(), this.nodeFactory.dispose(), this.materialFactory.dispose(), this.sceneManager.dispose(), this.devControls && this.devControls.parentNode && this.devControls.parentNode.removeChild(this.devControls), this.eventCallbacks.clear(), this.initialized = !1;
3532
3651
  }
3533
3652
  }
3534
3653
  const Oe = [
@@ -3585,14 +3704,14 @@ const Oe = [
3585
3704
  16746564
3586
3705
  // Darker tangerine
3587
3706
  ];
3588
- function Dt(d = 30) {
3707
+ function At(h = 30) {
3589
3708
  const e = [], s = [];
3590
- for (let o = 0; o < d; o++) {
3591
- const i = o < Oe.length ? Oe[o] : `Node ${o + 1}`;
3709
+ for (let i = 0; i < h; i++) {
3710
+ const o = i < Oe.length ? Oe[i] : `Node ${i + 1}`;
3592
3711
  e.push({
3593
- id: `node-${o}`,
3594
- label: i,
3595
- color: Fe[o % Fe.length],
3712
+ id: `node-${i}`,
3713
+ label: o,
3714
+ color: Fe[i % Fe.length],
3596
3715
  position: {
3597
3716
  x: (Math.random() - 0.5) * 60,
3598
3717
  y: (Math.random() - 0.5) * 60,
@@ -3600,20 +3719,20 @@ function Dt(d = 30) {
3600
3719
  }
3601
3720
  });
3602
3721
  }
3603
- for (let o = 1; o < d; o++) {
3604
- const i = Math.floor(Math.random() * o);
3722
+ for (let i = 1; i < h; i++) {
3723
+ const o = Math.floor(Math.random() * i);
3605
3724
  s.push({
3606
- source: `node-${o}`,
3607
- target: `node-${i}`,
3725
+ source: `node-${i}`,
3726
+ target: `node-${o}`,
3608
3727
  relationship: ee[Math.floor(Math.random() * ee.length)]
3609
3728
  });
3610
3729
  }
3611
- const t = Math.floor(d * 0.5);
3612
- for (let o = 0; o < t; o++) {
3613
- const i = Math.floor(Math.random() * d);
3614
- let a = Math.floor(Math.random() * d);
3615
- i === a && (a = (a + 1) % d);
3616
- const r = `node-${i}`, c = `node-${a}`;
3730
+ const t = Math.floor(h * 0.5);
3731
+ for (let i = 0; i < t; i++) {
3732
+ const o = Math.floor(Math.random() * h);
3733
+ let a = Math.floor(Math.random() * h);
3734
+ o === a && (a = (a + 1) % h);
3735
+ const r = `node-${o}`, c = `node-${a}`;
3617
3736
  s.some(
3618
3737
  (p) => p.source === r && p.target === c || p.source === c && p.target === r
3619
3738
  ) || s.push({
@@ -3624,19 +3743,19 @@ function Dt(d = 30) {
3624
3743
  }
3625
3744
  return { nodes: e, edges: s };
3626
3745
  }
3627
- function At(d = 1e3) {
3628
- const e = [], s = [], t = Math.ceil(d / 50), o = [];
3629
- for (let i = 0; i < t; i++)
3630
- o.push({
3746
+ function jt(h = 1e3) {
3747
+ const e = [], s = [], t = Math.ceil(h / 50), i = [];
3748
+ for (let o = 0; o < t; o++)
3749
+ i.push({
3631
3750
  x: (Math.random() - 0.5) * 200,
3632
3751
  y: (Math.random() - 0.5) * 200,
3633
3752
  z: (Math.random() - 0.5) * 200
3634
3753
  });
3635
- for (let i = 0; i < d; i++) {
3636
- const a = o[i % t];
3754
+ for (let o = 0; o < h; o++) {
3755
+ const a = i[o % t];
3637
3756
  e.push({
3638
- id: `node-${i}`,
3639
- label: `N${i}`,
3757
+ id: `node-${o}`,
3758
+ label: `N${o}`,
3640
3759
  position: {
3641
3760
  x: a.x + (Math.random() - 0.5) * 40,
3642
3761
  y: a.y + (Math.random() - 0.5) * 40,
@@ -3644,16 +3763,16 @@ function At(d = 1e3) {
3644
3763
  }
3645
3764
  });
3646
3765
  }
3647
- for (let i = 1; i < d; i++) {
3648
- const a = Math.floor(i / 50) * 50, r = a === 0 ? Math.floor(Math.random() * i) : a + Math.floor(Math.random() * Math.min(i - a, 50));
3766
+ for (let o = 1; o < h; o++) {
3767
+ const a = Math.floor(o / 50) * 50, r = a === 0 ? Math.floor(Math.random() * o) : a + Math.floor(Math.random() * Math.min(o - a, 50));
3649
3768
  s.push({
3650
- source: `node-${i}`,
3651
- target: `node-${Math.min(r, i - 1)}`,
3769
+ source: `node-${o}`,
3770
+ target: `node-${Math.min(r, o - 1)}`,
3652
3771
  relationship: "links to"
3653
3772
  });
3654
3773
  }
3655
- for (let i = 1; i < t; i++) {
3656
- const a = i * 50, r = (i - 1) * 50 + Math.floor(Math.random() * 50);
3774
+ for (let o = 1; o < t; o++) {
3775
+ const a = o * 50, r = (o - 1) * 50 + Math.floor(Math.random() * 50);
3657
3776
  s.push({
3658
3777
  source: `node-${a}`,
3659
3778
  target: `node-${r}`,
@@ -3666,10 +3785,10 @@ export {
3666
3785
  P as DEFAULT_OPTIONS,
3667
3786
  Ht as ForceGraph3D,
3668
3787
  X as LODLevel,
3669
- D as createEdgeKey,
3670
- At as generateLargeSampleData,
3671
- Dt as generateSampleData,
3672
- De as validateEdgeData,
3673
- He as validateNodeData
3788
+ H as createEdgeKey,
3789
+ jt as generateLargeSampleData,
3790
+ At as generateSampleData,
3791
+ He as validateEdgeData,
3792
+ De as validateNodeData
3674
3793
  };
3675
3794
  //# sourceMappingURL=force-3d-graph.js.map