force-3d-graph 1.2.6 → 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() {
@@ -94,30 +95,30 @@ class ft extends at {
94
95
  }, this.reset = function() {
95
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
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);
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));
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);
@@ -134,9 +135,9 @@ class ft extends at {
134
135
  };
135
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), o = i.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;
@@ -358,7 +359,7 @@ class ft extends at {
358
359
  }
359
360
  }
360
361
  function we(n) {
361
- t.enabled === !1 || t.enableZoom === !1 || o !== i.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 = {
@@ -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,7 +479,7 @@ 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: i } = ke(e), o = s.cameraFov ?? 75;
482
+ const { width: t, height: i } = Te(e), o = s.cameraFov ?? 75;
482
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({
@@ -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,7 +585,7 @@ 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;
@@ -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,13 +718,13 @@ 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
730
  const t = this.nodeManager.getNode(e.source), i = this.nodeManager.getNode(e.target), o = this.edgeFactory.createEdge(
@@ -740,11 +741,11 @@ class yt {
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
747
  const i = this.edges.findIndex(
747
- (a) => D(a.source, a.target) === t
748
+ (a) => H(a.source, a.target) === t
748
749
  );
749
750
  if (i === -1)
750
751
  return !1;
@@ -755,10 +756,10 @@ class yt {
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
761
  const i = this.edges.findIndex(
761
- (o) => D(o.source, o.target) === t
762
+ (o) => H(o.source, o.target) === t
762
763
  );
763
764
  i !== -1 && (this.edgeFactory.highlightEdge(this.edgeObjects[i]), this.highlightedEdgeKey = t);
764
765
  }
@@ -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
  }
@@ -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");
@@ -880,17 +881,19 @@ class Le {
880
881
  const e = /* @__PURE__ */ new Map();
881
882
  for (const i of this.edges)
882
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`);
883
885
  let s = 0, t = null;
884
886
  for (const [i, o] of e) {
885
887
  o > s && (s = o, t = i);
886
888
  const a = this.nodes.get(i);
887
889
  a && (a.mass = 1 + Math.log2(1 + o));
888
890
  }
889
- if (t && s > 10) {
891
+ if (console.log(`[ForceGraph3D] Hub detected: id="${t}", degree=${s}, threshold=10`), t && s > 10) {
890
892
  this.pinnedNodeId = t;
891
893
  const i = this.nodes.get(t);
892
- 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);
893
- }
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})`);
894
897
  }
895
898
  /**
896
899
  * Computes effective repulsion strength scaled by node count.
@@ -916,11 +919,11 @@ class Le {
916
919
  for (let i = 0; i < s; i++) {
917
920
  const o = e[i];
918
921
  for (let a = i + 1; a < s; a++) {
919
- const r = e[a], c = r.position.x - o.position.x, h = r.position.y - o.position.y, p = r.position.z - o.position.z;
920
- let x = c * c + h * h + p * p;
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;
921
924
  if (x > this.REPULSION_CUTOFF_SQ) continue;
922
925
  x < 0.01 && (x = 0.01);
923
- const m = Math.sqrt(x), u = t * this.alpha / x, f = c / m * u, b = h / m * u, M = p / m * u;
926
+ const m = Math.sqrt(x), u = t * this.alpha / x, f = c / m * u, b = d / m * u, M = p / m * u;
924
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;
925
928
  }
926
929
  }
@@ -946,11 +949,11 @@ class Le {
946
949
  if (a > this.REPULSION_CUTOFF_SQ) return;
947
950
  const r = Math.sqrt(a), c = this.getEffectiveRepulsion();
948
951
  if (r > 0 && s.size / r < this.barnesHutTheta) {
949
- const h = Math.max(a, 0.01), p = c * this.alpha * s.mass / h;
952
+ const d = Math.max(a, 0.01), p = c * this.alpha * s.mass / d;
950
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;
951
954
  } else
952
- for (const h of s.children)
953
- h && this.calculateForceFromOctree(e, h);
955
+ for (const d of s.children)
956
+ d && this.calculateForceFromOctree(e, d);
954
957
  }
955
958
  /**
956
959
  * Apply repulsion between two nodes (with cutoff)
@@ -960,8 +963,8 @@ class Le {
960
963
  let a = t * t + i * i + o * o;
961
964
  if (a > this.REPULSION_CUTOFF_SQ) return;
962
965
  a < 0.01 && (a = 0.01);
963
- const r = Math.sqrt(a), h = this.getEffectiveRepulsion() * this.alpha / a;
964
- e.velocity.x -= t / r * h / e.mass, e.velocity.y -= i / r * h / e.mass, e.velocity.z -= o / 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;
965
968
  }
966
969
  /**
967
970
  * Calculate attraction forces along edges
@@ -971,9 +974,9 @@ class Le {
971
974
  for (const t of this.edges) {
972
975
  const i = this.nodes.get(t.source), o = this.nodes.get(t.target);
973
976
  if (!i || !o) continue;
974
- const a = o.position.x - i.position.x, r = o.position.y - i.position.y, c = o.position.z - i.position.z, h = Math.sqrt(a * a + r * r + c * c);
975
- if (h < 0.01) continue;
976
- const x = (h - 15) * this.attractionStrength * s * this.alpha, m = a / h * x, u = r / h * x, f = c / h * x;
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;
977
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;
978
981
  }
979
982
  }
@@ -1085,7 +1088,7 @@ class xt {
1085
1088
  const f = (u.position.x >= o ? 1 : 0) + (u.position.y >= a ? 2 : 0) + (u.position.z >= r ? 4 : 0);
1086
1089
  c[f].push(u);
1087
1090
  }
1088
- const h = [
1091
+ const d = [
1089
1092
  { min: { x: s.min.x, y: s.min.y, z: s.min.z }, max: { x: o, y: a, z: r } },
1090
1093
  { min: { x: o, y: s.min.y, z: s.min.z }, max: { x: s.max.x, y: a, z: r } },
1091
1094
  { min: { x: s.min.x, y: a, z: s.min.z }, max: { x: o, y: s.max.y, z: r } },
@@ -1099,7 +1102,7 @@ class xt {
1099
1102
  const m = { x: 0, y: 0, z: 0 };
1100
1103
  for (let u = 0; u < 8; u++)
1101
1104
  if (c[u].length > 0) {
1102
- const f = this.buildTree(c[u], h[u], t + 1);
1105
+ const f = this.buildTree(c[u], d[u], t + 1);
1103
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;
1104
1107
  } else
1105
1108
  p.push(null);
@@ -1230,9 +1233,9 @@ class vt {
1230
1233
  );
1231
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);
1232
1235
  const c = a.getImageData(0, 0, 256, 256);
1233
- for (let h = 0; h < c.data.length; h += 4) {
1236
+ for (let d = 0; d < c.data.length; d += 4) {
1234
1237
  const p = (Math.random() - 0.5) * 5;
1235
- 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));
1236
1239
  }
1237
1240
  a.putImageData(c, 0, 0), s.push(o);
1238
1241
  }
@@ -1503,15 +1506,15 @@ class wt {
1503
1506
  o.z
1504
1507
  ]);
1505
1508
  a.setAttribute("position", new y.BufferAttribute(r, 3));
1506
- const c = this.getDefaultMaterial().clone(), h = new y.Line(a, c);
1507
- 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 = {
1508
1511
  source: e.source,
1509
1512
  target: e.target,
1510
1513
  edge: e,
1511
1514
  sourceNode: s,
1512
1515
  targetNode: t
1513
- }, h.frustumCulled = !0, {
1514
- line: h,
1516
+ }, d.frustumCulled = !0, {
1517
+ line: d,
1515
1518
  source: e.source,
1516
1519
  target: e.target
1517
1520
  };
@@ -2311,7 +2314,7 @@ class zt {
2311
2314
  this.panel && this.panel.parentNode && this.panel.parentNode.removeChild(this.panel), this.panel = null;
2312
2315
  }
2313
2316
  }
2314
- class Tt {
2317
+ class kt {
2315
2318
  constructor() {
2316
2319
  l(this, "tooltip", null);
2317
2320
  l(this, "visible", !1);
@@ -2413,7 +2416,7 @@ class Tt {
2413
2416
  this.tooltip && this.tooltip.parentNode && this.tooltip.parentNode.removeChild(this.tooltip), this.tooltip = null;
2414
2417
  }
2415
2418
  }
2416
- class kt {
2419
+ class Tt {
2417
2420
  constructor(e, s) {
2418
2421
  l(this, "container");
2419
2422
  l(this, "searchContainer", null);
@@ -2679,6 +2682,89 @@ class Pt {
2679
2682
  this.toggleContainer && this.toggleContainer.parentNode && this.toggleContainer.parentNode.removeChild(this.toggleContainer);
2680
2683
  }
2681
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
+ }
2682
2768
  const Rt = {
2683
2769
  backgroundColor: "#0a0a0a",
2684
2770
  gridColor: "rgba(255, 255, 255, 0.03)",
@@ -2782,9 +2868,9 @@ class It {
2782
2868
  for (const i of this.edges) {
2783
2869
  const o = this.nodes.get(i.source), a = this.nodes.get(i.target);
2784
2870
  if (!o || !a) continue;
2785
- const r = a.x - o.x, c = a.y - o.y, h = r * r + c * c;
2786
- if (h === 0) continue;
2787
- const p = Math.max(0, Math.min(1, ((e - o.x) * r + (s - o.y) * c) / h)), x = o.x + p * r, m = o.y + p * c, u = e - x, f = s - m;
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;
2788
2874
  if (Math.sqrt(u * u + f * f) < 12)
2789
2875
  return i;
2790
2876
  }
@@ -2803,22 +2889,22 @@ class It {
2803
2889
  let o = 0;
2804
2890
  for (let r = 0; r < s; r++)
2805
2891
  for (let c = r + 1; c < s; c++) {
2806
- const h = e[r], p = e[c];
2807
- 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);
2808
2894
  if (u < t * 3) {
2809
2895
  u < 1 && (u = 1);
2810
2896
  const f = this.options.repulsionStrength / (u * u), b = x / u * f, M = m / u * f;
2811
- 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;
2812
2898
  }
2813
2899
  }
2814
2900
  const a = 80;
2815
2901
  for (const r of this.edges) {
2816
- const c = this.nodes.get(r.source), h = this.nodes.get(r.target);
2817
- if (!c || !h) continue;
2818
- 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);
2819
2905
  m < 1 && (m = 1);
2820
2906
  const f = (m - a) * this.options.attractionStrength, b = p / m * f, M = x / m * f;
2821
- 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;
2822
2908
  }
2823
2909
  for (const r of e) {
2824
2910
  if (this.draggedNode === r) continue;
@@ -2836,8 +2922,8 @@ class It {
2836
2922
  const t = this.ctx, i = 40 * this.transform.scale, o = 1.5, a = this.transform.x % i, r = this.transform.y % i;
2837
2923
  t.fillStyle = this.options.gridColor;
2838
2924
  for (let c = a; c < e; c += i)
2839
- for (let h = r; h < s; h += i)
2840
- t.beginPath(), t.arc(c, h, o, 0, Math.PI * 2), t.fill();
2925
+ for (let d = r; d < s; d += i)
2926
+ t.beginPath(), t.arc(c, d, o, 0, Math.PI * 2), t.fill();
2841
2927
  }
2842
2928
  renderEdges() {
2843
2929
  const e = this.ctx;
@@ -2870,8 +2956,8 @@ class It {
2870
2956
  s.x,
2871
2957
  s.y,
2872
2958
  a
2873
- ), c = s.color >> 16 & 255, h = s.color >> 8 & 255, p = s.color & 255;
2874
- 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";
2875
2961
  const x = a * 1.6;
2876
2962
  let m = s.label, u = e.measureText(m).width;
2877
2963
  if (u > x) {
@@ -2978,11 +3064,11 @@ class It {
2978
3064
  focusOnNode(e) {
2979
3065
  const s = this.nodes.get(e);
2980
3066
  if (!s) return;
2981
- 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(), 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 = () => {
2982
3068
  const p = performance.now() - c, x = Math.min(p / r, 1), m = 1 - Math.pow(1 - x, 3);
2983
- this.transform.x = o + (t - o) * m, this.transform.y = a + (i - 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;
2984
3070
  };
2985
- h();
3071
+ d();
2986
3072
  }
2987
3073
  /**
2988
3074
  * Updates node positions from 3D data
@@ -3050,6 +3136,7 @@ class Ht {
3050
3136
  l(this, "edgeTooltipManager");
3051
3137
  l(this, "searchManager", null);
3052
3138
  l(this, "viewToggleManager", null);
3139
+ l(this, "legendManager", null);
3053
3140
  // 2D Renderer
3054
3141
  l(this, "forceGraph2D", null);
3055
3142
  // Event system
@@ -3059,7 +3146,7 @@ class Ht {
3059
3146
  l(this, "devControls", null);
3060
3147
  l(this, "viewMode", "3d");
3061
3148
  l(this, "graphData", null);
3062
- 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(
3063
3150
  this.materialFactory,
3064
3151
  this.options.nodeRadius ?? P.nodeRadius,
3065
3152
  this.options.lodSegments ?? P.lodSegments,
@@ -3075,7 +3162,7 @@ class Ht {
3075
3162
  ), this.frustumCuller = new Ct(
3076
3163
  this.sceneManager.camera,
3077
3164
  this.options.enableEdgeCulling ?? P.enableEdgeCulling
3078
- ), 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(
3079
3166
  this.nodeManager.getAllNodes(),
3080
3167
  this.edgeManager.getAllEdges(),
3081
3168
  {
@@ -3090,11 +3177,11 @@ class Ht {
3090
3177
  () => this.onSimulate(),
3091
3178
  () => this.onRender(),
3092
3179
  this.options.targetFPS ?? P.targetFPS
3093
- ), 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) => {
3094
3181
  this.edgePanelManager.hide(), this.focusOnNode(t), setTimeout(() => {
3095
3182
  this.showNodePanel(t);
3096
3183
  }, 400);
3097
- }), this.options.showSearch !== !1 && (this.searchManager = new kt(this.container, {
3184
+ }), this.options.showSearch !== !1 && (this.searchManager = new Tt(this.container, {
3098
3185
  placeholder: this.options.searchPlaceholder,
3099
3186
  onSearch: (t) => ({
3100
3187
  nodeResults: this.searchNodes(t),
@@ -3110,7 +3197,7 @@ class Ht {
3110
3197
  onViewChange: (t) => {
3111
3198
  this.switchView(t);
3112
3199
  }
3113
- })), 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");
3114
3201
  }
3115
3202
  /**
3116
3203
  * Sets up internal callbacks
@@ -3169,6 +3256,12 @@ class Ht {
3169
3256
  onRender() {
3170
3257
  this.frustumCuller.update(), this.raycasterManager.setNodeObjects(this.nodeManager.getAllNodeObjects()), this.raycasterManager.setEdgeObjects(this.edgeManager.getAllEdgeLines());
3171
3258
  }
3259
+ /**
3260
+ * Updates bottom-right legend counts
3261
+ */
3262
+ updateLegendCounts() {
3263
+ this.legendManager && this.legendManager.updateCounts(this.getNodeCount(), this.getEdgeCount());
3264
+ }
3172
3265
  // ==========================================================================
3173
3266
  // Public API
3174
3267
  // ==========================================================================
@@ -3192,7 +3285,7 @@ class Ht {
3192
3285
  if (o.edges && Array.isArray(o.edges))
3193
3286
  for (const r of o.edges)
3194
3287
  this.addEdge(r);
3195
- this.graphEngine = new Le(
3288
+ this.graphEngine = new Ie(
3196
3289
  this.nodeManager.getAllNodes(),
3197
3290
  this.edgeManager.getAllEdges(),
3198
3291
  {
@@ -3202,17 +3295,17 @@ class Ht {
3202
3295
  useBarnesHut: this.options.useBarnesHut,
3203
3296
  barnesHutTheta: this.options.barnesHutTheta
3204
3297
  }
3205
- ), this.graphEngine.restart(), this.forceGraph2D && this.forceGraph2D.setData(o);
3298
+ ), this.graphEngine.restart(), this.forceGraph2D && this.forceGraph2D.setData(o), this.updateLegendCounts();
3206
3299
  }
3207
3300
  /**
3208
3301
  * Adds a node to the graph
3209
3302
  * @returns true if added, false if node already exists or invalid
3210
3303
  */
3211
3304
  addNode(e) {
3212
- if (!He(e))
3305
+ if (!De(e))
3213
3306
  return !1;
3214
3307
  const s = this.nodeManager.addNode(e);
3215
- 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;
3216
3309
  }
3217
3310
  /**
3218
3311
  * Removes a node from the graph
@@ -3223,7 +3316,7 @@ class Ht {
3223
3316
  return !1;
3224
3317
  this.edgeManager.removeEdgesForNode(e);
3225
3318
  const s = this.nodeManager.removeNode(e);
3226
- 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;
3227
3320
  }
3228
3321
  /**
3229
3322
  * Updates a node's properties
@@ -3236,10 +3329,10 @@ class Ht {
3236
3329
  * @returns true if added, false if edge already exists or nodes don't exist
3237
3330
  */
3238
3331
  addEdge(e) {
3239
- if (!De(e))
3332
+ if (!He(e))
3240
3333
  return !1;
3241
3334
  const s = this.edgeManager.addEdge(e);
3242
- 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;
3243
3336
  }
3244
3337
  /**
3245
3338
  * Removes an edge from the graph
@@ -3247,7 +3340,7 @@ class Ht {
3247
3340
  */
3248
3341
  removeEdge(e, s) {
3249
3342
  const t = this.edgeManager.removeEdge(e, s);
3250
- 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;
3251
3344
  }
3252
3345
  /**
3253
3346
  * Expands a node by fetching more data
@@ -3320,9 +3413,9 @@ class Ht {
3320
3413
  x: i.x + r.x * s,
3321
3414
  y: i.y + r.y * s,
3322
3415
  z: i.z + r.z * s
3323
- }, h = { 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 = () => {
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 = () => {
3324
3417
  const f = performance.now() - m, b = Math.min(f / x, 1), M = 1 - Math.pow(1 - b, 3);
3325
- o.position.x = h.x + (c.x - h.x) * M, o.position.y = h.y + (c.y - h.y) * M, o.position.z = h.z + (c.z - h.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);
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);
3326
3419
  };
3327
3420
  u();
3328
3421
  }
@@ -3340,13 +3433,13 @@ class Ht {
3340
3433
  x: (i.position.x + o.position.x) / 2,
3341
3434
  y: (i.position.y + o.position.y) / 2,
3342
3435
  z: (i.position.z + o.position.z) / 2
3343
- }, h = o.position.x - i.position.x, p = o.position.y - i.position.y, x = o.position.z - i.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 = {
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 = {
3344
3437
  x: c.x + f.x * u,
3345
3438
  y: c.y + f.y * u,
3346
3439
  z: c.z + f.z * u
3347
- }, 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 = () => {
3348
- const k = performance.now() - R, H = Math.min(k / O, 1), E = 1 - Math.pow(1 - H, 3);
3349
- 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);
3350
3443
  };
3351
3444
  Y();
3352
3445
  }
@@ -3371,8 +3464,8 @@ class Ht {
3371
3464
  return [];
3372
3465
  const s = e.toLowerCase().trim(), t = this.nodeManager.getAllNodes(), i = [];
3373
3466
  return t.forEach((o) => {
3374
- var h, p, x;
3375
- const a = (h = o.label) == null ? void 0 : h.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);
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);
3376
3469
  (a || r || c) && i.push(o);
3377
3470
  }), i;
3378
3471
  }
@@ -3387,8 +3480,8 @@ class Ht {
3387
3480
  const s = e.toLowerCase().trim(), t = this.edgeManager.getAllEdges(), i = [];
3388
3481
  for (const a of t)
3389
3482
  if ((o = a.relationship) == null ? void 0 : o.toLowerCase().includes(s)) {
3390
- const c = this.nodeManager.getNode(a.source), h = this.nodeManager.getNode(a.target);
3391
- c && h && i.push({ edge: a, sourceNode: c, targetNode: h });
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 });
3392
3485
  }
3393
3486
  return i;
3394
3487
  }
@@ -3554,7 +3647,7 @@ class Ht {
3554
3647
  * Destroys the graph and releases all resources
3555
3648
  */
3556
3649
  destroy() {
3557
- 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;
3558
3651
  }
3559
3652
  }
3560
3653
  const Oe = [
@@ -3611,9 +3704,9 @@ const Oe = [
3611
3704
  16746564
3612
3705
  // Darker tangerine
3613
3706
  ];
3614
- function Dt(d = 30) {
3707
+ function At(h = 30) {
3615
3708
  const e = [], s = [];
3616
- for (let i = 0; i < d; i++) {
3709
+ for (let i = 0; i < h; i++) {
3617
3710
  const o = i < Oe.length ? Oe[i] : `Node ${i + 1}`;
3618
3711
  e.push({
3619
3712
  id: `node-${i}`,
@@ -3626,7 +3719,7 @@ function Dt(d = 30) {
3626
3719
  }
3627
3720
  });
3628
3721
  }
3629
- for (let i = 1; i < d; i++) {
3722
+ for (let i = 1; i < h; i++) {
3630
3723
  const o = Math.floor(Math.random() * i);
3631
3724
  s.push({
3632
3725
  source: `node-${i}`,
@@ -3634,11 +3727,11 @@ function Dt(d = 30) {
3634
3727
  relationship: ee[Math.floor(Math.random() * ee.length)]
3635
3728
  });
3636
3729
  }
3637
- const t = Math.floor(d * 0.5);
3730
+ const t = Math.floor(h * 0.5);
3638
3731
  for (let i = 0; i < t; i++) {
3639
- const o = Math.floor(Math.random() * d);
3640
- let a = Math.floor(Math.random() * d);
3641
- o === a && (a = (a + 1) % d);
3732
+ const o = Math.floor(Math.random() * h);
3733
+ let a = Math.floor(Math.random() * h);
3734
+ o === a && (a = (a + 1) % h);
3642
3735
  const r = `node-${o}`, c = `node-${a}`;
3643
3736
  s.some(
3644
3737
  (p) => p.source === r && p.target === c || p.source === c && p.target === r
@@ -3650,15 +3743,15 @@ function Dt(d = 30) {
3650
3743
  }
3651
3744
  return { nodes: e, edges: s };
3652
3745
  }
3653
- function At(d = 1e3) {
3654
- const e = [], s = [], t = Math.ceil(d / 50), i = [];
3746
+ function jt(h = 1e3) {
3747
+ const e = [], s = [], t = Math.ceil(h / 50), i = [];
3655
3748
  for (let o = 0; o < t; o++)
3656
3749
  i.push({
3657
3750
  x: (Math.random() - 0.5) * 200,
3658
3751
  y: (Math.random() - 0.5) * 200,
3659
3752
  z: (Math.random() - 0.5) * 200
3660
3753
  });
3661
- for (let o = 0; o < d; o++) {
3754
+ for (let o = 0; o < h; o++) {
3662
3755
  const a = i[o % t];
3663
3756
  e.push({
3664
3757
  id: `node-${o}`,
@@ -3670,7 +3763,7 @@ function At(d = 1e3) {
3670
3763
  }
3671
3764
  });
3672
3765
  }
3673
- for (let o = 1; o < d; o++) {
3766
+ for (let o = 1; o < h; o++) {
3674
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));
3675
3768
  s.push({
3676
3769
  source: `node-${o}`,
@@ -3692,10 +3785,10 @@ export {
3692
3785
  P as DEFAULT_OPTIONS,
3693
3786
  Ht as ForceGraph3D,
3694
3787
  X as LODLevel,
3695
- D as createEdgeKey,
3696
- At as generateLargeSampleData,
3697
- Dt as generateSampleData,
3698
- De as validateEdgeData,
3699
- He as validateNodeData
3788
+ H as createEdgeKey,
3789
+ jt as generateLargeSampleData,
3790
+ At as generateSampleData,
3791
+ He as validateEdgeData,
3792
+ De as validateNodeData
3700
3793
  };
3701
3794
  //# sourceMappingURL=force-3d-graph.js.map