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.
- package/dist/force-3d-graph.js +544 -425
- package/dist/force-3d-graph.js.map +1 -1
- package/dist/force-3d-graph.umd.cjs +62 -19
- package/dist/force-3d-graph.umd.cjs.map +1 -1
- package/dist/index.d.ts +6 -0
- package/package.json +1 -1
package/dist/force-3d-graph.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
var ot = Object.defineProperty;
|
|
2
|
-
var nt = (
|
|
3
|
-
var l = (
|
|
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
|
|
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__ */ ((
|
|
35
|
-
function
|
|
36
|
-
const
|
|
37
|
-
return
|
|
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(
|
|
45
|
+
`, document.body.appendChild(h), h;
|
|
45
46
|
}
|
|
46
|
-
function
|
|
47
|
-
return
|
|
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
|
|
50
|
-
const e =
|
|
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
|
|
57
|
-
if (!
|
|
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 =
|
|
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
|
|
63
|
-
if (!
|
|
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 =
|
|
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(
|
|
69
|
-
return typeof
|
|
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(
|
|
72
|
-
if (!
|
|
73
|
-
const e =
|
|
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
|
|
77
|
-
return
|
|
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" },
|
|
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(),
|
|
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
|
|
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 &&
|
|
101
|
-
let
|
|
102
|
-
isFinite(
|
|
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 &&
|
|
105
|
+
if (t.zoomToCursor && D) {
|
|
105
106
|
let U = null;
|
|
106
107
|
if (t.object.isPerspectiveCamera) {
|
|
107
108
|
const _ = n.length();
|
|
108
|
-
U = ne(_ *
|
|
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(
|
|
113
|
-
_.unproject(t.object), t.object.zoom = Math.max(t.minZoom, Math.min(t.maxZoom, t.object.zoom /
|
|
114
|
-
const Q = new S(
|
|
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) : (
|
|
119
|
-
} else t.object.isOrthographicCamera && (t.object.zoom = Math.max(t.minZoom, Math.min(t.maxZoom, t.object.zoom /
|
|
120
|
-
return
|
|
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,
|
|
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
|
|
136
|
+
let o = i.NONE;
|
|
136
137
|
const a = 1e-6, r = new ze(), c = new ze();
|
|
137
|
-
let
|
|
138
|
-
const p = new S(), x = new
|
|
139
|
-
let
|
|
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
|
|
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
|
-
}(),
|
|
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),
|
|
174
|
-
} else t.object.isOrthographicCamera ? (
|
|
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 ?
|
|
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 ?
|
|
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
|
-
|
|
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
|
-
|
|
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),
|
|
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),
|
|
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(
|
|
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),
|
|
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),
|
|
325
|
+
ue(n), o = i.PAN;
|
|
325
326
|
} else {
|
|
326
327
|
if (t.enableRotate === !1) return;
|
|
327
|
-
ge(n),
|
|
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),
|
|
334
|
+
ge(n), o = i.ROTATE;
|
|
334
335
|
} else {
|
|
335
336
|
if (t.enablePan === !1) return;
|
|
336
|
-
ue(n),
|
|
337
|
+
ue(n), o = i.PAN;
|
|
337
338
|
}
|
|
338
339
|
break;
|
|
339
340
|
default:
|
|
340
|
-
|
|
341
|
+
o = i.NONE;
|
|
341
342
|
}
|
|
342
|
-
|
|
343
|
+
o !== i.NONE && t.dispatchEvent(ce);
|
|
343
344
|
}
|
|
344
345
|
function We(n) {
|
|
345
|
-
switch (
|
|
346
|
-
case
|
|
346
|
+
switch (o) {
|
|
347
|
+
case i.ROTATE:
|
|
347
348
|
if (t.enableRotate === !1) return;
|
|
348
349
|
$e(n);
|
|
349
350
|
break;
|
|
350
|
-
case
|
|
351
|
+
case i.DOLLY:
|
|
351
352
|
if (t.enableZoom === !1) return;
|
|
352
353
|
Ge(n);
|
|
353
354
|
break;
|
|
354
|
-
case
|
|
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 ||
|
|
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),
|
|
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),
|
|
399
|
+
me(n), o = i.TOUCH_PAN;
|
|
399
400
|
break;
|
|
400
401
|
default:
|
|
401
|
-
|
|
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),
|
|
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),
|
|
413
|
+
_e(n), o = i.TOUCH_DOLLY_ROTATE;
|
|
413
414
|
break;
|
|
414
415
|
default:
|
|
415
|
-
|
|
416
|
+
o = i.NONE;
|
|
416
417
|
}
|
|
417
418
|
break;
|
|
418
419
|
default:
|
|
419
|
-
|
|
420
|
+
o = i.NONE;
|
|
420
421
|
}
|
|
421
|
-
|
|
422
|
+
o !== i.NONE && t.dispatchEvent(ce);
|
|
422
423
|
}
|
|
423
424
|
function et(n) {
|
|
424
|
-
switch (Ne(n),
|
|
425
|
-
case
|
|
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
|
|
430
|
+
case i.TOUCH_PAN:
|
|
430
431
|
if (t.enablePan === !1) return;
|
|
431
432
|
be(n), t.update();
|
|
432
433
|
break;
|
|
433
|
-
case
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
482
|
-
this.camera = new y.PerspectiveCamera(
|
|
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,
|
|
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
|
|
501
|
-
|
|
502
|
-
const
|
|
503
|
-
|
|
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 } =
|
|
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 (!
|
|
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),
|
|
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
|
-
},
|
|
596
|
+
}, o = {
|
|
596
597
|
...e,
|
|
597
|
-
position:
|
|
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:
|
|
602
|
+
{ ...e, position: i },
|
|
602
603
|
s
|
|
603
604
|
);
|
|
604
|
-
return this.sceneManager.add(a.group), this.nodes.set(e.id,
|
|
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),
|
|
619
|
-
return !t || !
|
|
620
|
-
|
|
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),
|
|
628
|
-
t &&
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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),
|
|
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
|
-
|
|
733
|
+
i,
|
|
733
734
|
t.position,
|
|
734
|
-
|
|
735
|
+
i.position
|
|
735
736
|
);
|
|
736
|
-
return this.sceneManager.add(
|
|
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 =
|
|
744
|
+
const t = H(e, s);
|
|
744
745
|
if (!this.edgeKeySet.has(t))
|
|
745
746
|
return !1;
|
|
746
|
-
const
|
|
747
|
-
(a) =>
|
|
747
|
+
const i = this.edges.findIndex(
|
|
748
|
+
(a) => H(a.source, a.target) === t
|
|
748
749
|
);
|
|
749
|
-
if (
|
|
750
|
+
if (i === -1)
|
|
750
751
|
return !1;
|
|
751
|
-
const
|
|
752
|
-
return this.sceneManager.remove(
|
|
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 =
|
|
759
|
+
const t = H(e, s);
|
|
759
760
|
this.highlightedEdgeKey && this.highlightedEdgeKey !== t && this.unhighlightCurrentEdge();
|
|
760
|
-
const
|
|
761
|
-
(
|
|
761
|
+
const i = this.edges.findIndex(
|
|
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
|
}
|
|
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) =>
|
|
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],
|
|
807
|
-
|
|
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
|
-
|
|
810
|
-
|
|
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
|
|
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.
|
|
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
|
|
895
|
-
const
|
|
896
|
-
for (let a =
|
|
897
|
-
const r = e[a], c = r.position.x -
|
|
898
|
-
let x = c * c +
|
|
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 =
|
|
902
|
-
|
|
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,
|
|
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
|
|
928
|
-
e.velocity.x -= t / r * p / e.mass, e.velocity.y -=
|
|
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
|
|
931
|
-
|
|
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,
|
|
938
|
-
let a = t * t +
|
|
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),
|
|
942
|
-
e.velocity.x -= t / r *
|
|
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
|
|
951
|
-
if (!
|
|
952
|
-
const a =
|
|
953
|
-
if (
|
|
954
|
-
const x = (
|
|
955
|
-
|
|
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
|
|
1022
|
-
s.x = Math.min(s.x,
|
|
1023
|
-
const
|
|
1024
|
-
return s.x -=
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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 >=
|
|
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
|
|
1063
|
-
{ min: { x: s.min.x, y: s.min.y, z: s.min.z }, max: { x:
|
|
1064
|
-
{ min: { x:
|
|
1065
|
-
{ min: { x: s.min.x, y: a, z: s.min.z }, max: { x:
|
|
1066
|
-
{ min: { x:
|
|
1067
|
-
{ min: { x: s.min.x, y: s.min.y, z: r }, max: { x:
|
|
1068
|
-
{ min: { x:
|
|
1069
|
-
{ min: { x: s.min.x, y: a, z: r }, max: { x:
|
|
1070
|
-
{ min: { x:
|
|
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],
|
|
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:
|
|
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,
|
|
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 /
|
|
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
|
|
1195
|
-
const
|
|
1196
|
-
|
|
1197
|
-
const a =
|
|
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,
|
|
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
|
|
1236
|
+
for (let d = 0; d < c.data.length; d += 4) {
|
|
1208
1237
|
const p = (Math.random() - 0.5) * 5;
|
|
1209
|
-
c.data[
|
|
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(
|
|
1240
|
+
a.putImageData(c, 0, 0), s.push(o);
|
|
1212
1241
|
}
|
|
1213
|
-
this.envMap = new y.CubeTexture(s.map((
|
|
1214
|
-
const
|
|
1215
|
-
return
|
|
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
|
|
1261
|
+
const i = new y.Color(16750950), o = new y.ShaderMaterial({
|
|
1233
1262
|
uniforms: {
|
|
1234
|
-
uColor: { value:
|
|
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,
|
|
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"),
|
|
1336
|
-
|
|
1337
|
-
const a =
|
|
1338
|
-
t.width = Math.max(128, a + 24), t.height = s + 20,
|
|
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],
|
|
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 =
|
|
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
|
|
1423
|
+
const i = this.getGeometry(s), o = this.materialFactory.createGlassMaterial(
|
|
1395
1424
|
e.color ?? this.defaultNodeColor
|
|
1396
|
-
), a = new y.Mesh(
|
|
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,
|
|
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(),
|
|
1481
|
-
return
|
|
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
|
-
},
|
|
1488
|
-
line:
|
|
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
|
|
1510
|
-
|
|
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,
|
|
1539
|
-
return
|
|
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,
|
|
1546
|
-
return Math.sqrt(t * t +
|
|
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),
|
|
1603
|
-
if (this.frustum.containsPoint(t) || this.frustum.containsPoint(
|
|
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
|
|
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 =
|
|
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
|
|
1693
|
-
|
|
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
|
|
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
|
|
1705
|
-
for (;
|
|
1706
|
-
if ((
|
|
1707
|
-
return
|
|
1708
|
-
|
|
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
|
|
1722
|
-
if (
|
|
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:
|
|
1725
|
-
sourceNode:
|
|
1726
|
-
targetNode:
|
|
1727
|
-
edgeLine:
|
|
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
|
|
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
|
|
1740
|
-
if (
|
|
1741
|
-
let a =
|
|
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 ((
|
|
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
|
|
1828
|
-
|
|
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 =
|
|
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
|
-
(
|
|
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
|
|
2108
|
-
this.panelTemplate ?
|
|
2109
|
-
const
|
|
2110
|
-
|
|
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
|
|
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: ${
|
|
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: ${
|
|
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
|
|
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(),
|
|
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 >
|
|
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,
|
|
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(
|
|
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
|
|
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
|
|
2534
|
-
s.length > 0 && (
|
|
2535
|
-
const a =
|
|
2536
|
-
|
|
2537
|
-
<div class="f3d-search-result-item" data-node-id="${this.escapeHtml(
|
|
2538
|
-
<div class="f3d-result-label">${this.escapeHtml(
|
|
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 && (
|
|
2543
|
-
|
|
2544
|
-
<div class="f3d-search-result-item" data-node-id="${this.escapeHtml(
|
|
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(
|
|
2575
|
+
<div class="f3d-result-relationship">${this.escapeHtml(o.relationship || "connected")}</div>
|
|
2547
2576
|
</div>
|
|
2548
2577
|
`;
|
|
2549
|
-
}), t.length > 5 && (
|
|
2550
|
-
|
|
2551
|
-
const a =
|
|
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,
|
|
2710
|
-
this.transform.x = t - (t - this.transform.x) * r, this.transform.y =
|
|
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,
|
|
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,
|
|
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 =
|
|
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(
|
|
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(
|
|
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),
|
|
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
|
-
|
|
2732
|
-
else if (
|
|
2733
|
-
const
|
|
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
|
|
2750
|
-
if (Math.sqrt(
|
|
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
|
|
2757
|
-
const
|
|
2758
|
-
if (!
|
|
2759
|
-
const r = a.x -
|
|
2760
|
-
if (
|
|
2761
|
-
const p = Math.max(0, Math.min(1, ((e -
|
|
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
|
|
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,
|
|
2777
|
-
let
|
|
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
|
|
2781
|
-
let x = p.x -
|
|
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
|
-
|
|
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),
|
|
2791
|
-
if (!c || !
|
|
2792
|
-
let p =
|
|
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,
|
|
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 >
|
|
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
|
-
|
|
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,
|
|
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 +=
|
|
2813
|
-
for (let
|
|
2814
|
-
t.beginPath(), t.arc(c,
|
|
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),
|
|
2820
|
-
if (!t || !
|
|
2821
|
-
const
|
|
2822
|
-
|
|
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,
|
|
2829
|
-
if (t ||
|
|
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 ||
|
|
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,
|
|
2848
|
-
r.addColorStop(0, `rgba(${Math.min(255, c + 60)}, ${Math.min(255,
|
|
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
|
|
2976
|
+
const i = s.position || {
|
|
2865
2977
|
x: (Math.random() - 0.5) * 300,
|
|
2866
2978
|
y: (Math.random() - 0.5) * 300
|
|
2867
|
-
},
|
|
2979
|
+
}, o = {
|
|
2868
2980
|
id: s.id,
|
|
2869
2981
|
label: s.label,
|
|
2870
|
-
x:
|
|
2871
|
-
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,
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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
|
|
2967
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
3110
|
-
this.edgeTooltipManager.show(e.edge, e.sourceNode, e.targetNode, t,
|
|
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((
|
|
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,
|
|
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
|
-
})),
|
|
3276
|
+
})), o = {
|
|
3158
3277
|
nodes: s.nodes || [],
|
|
3159
|
-
edges:
|
|
3278
|
+
edges: i
|
|
3160
3279
|
};
|
|
3161
|
-
if (this.graphData =
|
|
3162
|
-
te.setExpectedNodeCount(
|
|
3163
|
-
for (const r of
|
|
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 (
|
|
3167
|
-
for (const r of
|
|
3285
|
+
if (o.edges && Array.isArray(o.edges))
|
|
3286
|
+
for (const r of o.edges)
|
|
3168
3287
|
this.addEdge(r);
|
|
3169
|
-
this.graphEngine = new
|
|
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(
|
|
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 (!
|
|
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 (!
|
|
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
|
|
3234
|
-
if (!
|
|
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
|
|
3238
|
-
if (
|
|
3239
|
-
for (const a of
|
|
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 (
|
|
3242
|
-
for (const a of
|
|
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,
|
|
3245
|
-
} catch (
|
|
3246
|
-
return console.error("[ForceGraph3D] Error expanding node:",
|
|
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
|
|
3294
|
-
x:
|
|
3295
|
-
y:
|
|
3296
|
-
z:
|
|
3297
|
-
},
|
|
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
|
-
|
|
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
|
|
3309
|
-
if (!
|
|
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: (
|
|
3315
|
-
y: (
|
|
3316
|
-
z: (
|
|
3317
|
-
},
|
|
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,
|
|
3322
|
-
const
|
|
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(),
|
|
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(),
|
|
3347
|
-
return t.forEach((
|
|
3348
|
-
var
|
|
3349
|
-
const a = (
|
|
3350
|
-
(a || r || c) &&
|
|
3351
|
-
}),
|
|
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
|
|
3477
|
+
var o;
|
|
3359
3478
|
if (!e || e.trim() === "")
|
|
3360
3479
|
return [];
|
|
3361
|
-
const s = e.toLowerCase().trim(), t = this.edgeManager.getAllEdges(),
|
|
3480
|
+
const s = e.toLowerCase().trim(), t = this.edgeManager.getAllEdges(), i = [];
|
|
3362
3481
|
for (const a of t)
|
|
3363
|
-
if ((
|
|
3364
|
-
const c = this.nodeManager.getNode(a.source),
|
|
3365
|
-
c &&
|
|
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
|
|
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
|
|
3410
|
-
|
|
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),
|
|
3422
|
-
t &&
|
|
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((
|
|
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
|
|
3515
|
-
this.setPhysicsParams({ repulsionStrength:
|
|
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
|
|
3518
|
-
this.setPhysicsParams({ attractionStrength:
|
|
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
|
|
3521
|
-
this.setPhysicsParams({ damping:
|
|
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
|
|
3524
|
-
|
|
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
|
|
3707
|
+
function At(h = 30) {
|
|
3589
3708
|
const e = [], s = [];
|
|
3590
|
-
for (let
|
|
3591
|
-
const
|
|
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-${
|
|
3594
|
-
label:
|
|
3595
|
-
color: Fe[
|
|
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
|
|
3604
|
-
const
|
|
3722
|
+
for (let i = 1; i < h; i++) {
|
|
3723
|
+
const o = Math.floor(Math.random() * i);
|
|
3605
3724
|
s.push({
|
|
3606
|
-
source: `node-${
|
|
3607
|
-
target: `node-${
|
|
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(
|
|
3612
|
-
for (let
|
|
3613
|
-
const
|
|
3614
|
-
let a = Math.floor(Math.random() *
|
|
3615
|
-
|
|
3616
|
-
const r = `node-${
|
|
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
|
|
3628
|
-
const e = [], s = [], t = Math.ceil(
|
|
3629
|
-
for (let
|
|
3630
|
-
|
|
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
|
|
3636
|
-
const a = o
|
|
3754
|
+
for (let o = 0; o < h; o++) {
|
|
3755
|
+
const a = i[o % t];
|
|
3637
3756
|
e.push({
|
|
3638
|
-
id: `node-${
|
|
3639
|
-
label: `N${
|
|
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
|
|
3648
|
-
const a = Math.floor(
|
|
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-${
|
|
3651
|
-
target: `node-${Math.min(r,
|
|
3769
|
+
source: `node-${o}`,
|
|
3770
|
+
target: `node-${Math.min(r, o - 1)}`,
|
|
3652
3771
|
relationship: "links to"
|
|
3653
3772
|
});
|
|
3654
3773
|
}
|
|
3655
|
-
for (let
|
|
3656
|
-
const a =
|
|
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
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
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
|