force-3d-graph 1.0.0
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/README.md +114 -0
- package/dist/force-3d-graph.js +2625 -0
- package/dist/force-3d-graph.js.map +1 -0
- package/dist/force-3d-graph.umd.cjs +336 -0
- package/dist/force-3d-graph.umd.cjs.map +1 -0
- package/dist/index.d.ts +320 -0
- package/package.json +55 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
(function(C,d){typeof exports=="object"&&typeof module<"u"?d(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],d):(C=typeof globalThis<"u"?globalThis:C||self,d(C.ForceGraph3D={},C.THREE))})(this,function(C,d){"use strict";var Mt=Object.defineProperty;var wt=(C,d,I)=>d in C?Mt(C,d,{enumerable:!0,configurable:!0,writable:!0,value:I}):C[d]=I;var r=(C,d,I)=>wt(C,typeof d!="symbol"?d+"":d,I);function I(c){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(c){for(const o in c)if(o!=="default"){const t=Object.getOwnPropertyDescriptor(c,o);Object.defineProperty(e,o,t.get?t:{enumerable:!0,get:()=>c[o]})}}return e.default=c,Object.freeze(e)}const g=I(d),P={backgroundColor:657930,cameraPosition:{x:0,y:0,z:80},cameraFov:75,repulsionStrength:100,attractionStrength:.01,damping:.9,useBarnesHut:!1,barnesHutTheta:.5,defaultNodeColor:4886754,nodeRadius:2,nodeSegments:32,enableLOD:!0,lodDistances:[50,100,200],lodSegments:[32,16,8],edgeColor:8947848,edgeOpacity:.4,enableEdgeCulling:!0,showPanel:!0,targetFPS:60,maxVisibleNodes:1e4};var j=(c=>(c[c.HIGH=0]="HIGH",c[c.MEDIUM=1]="MEDIUM",c[c.LOW=2]="LOW",c))(j||{});function ke(){const c=document.createElement("div");return c.id="force-graph-3d-container",c.style.cssText=`
|
|
2
|
+
width: 100%;
|
|
3
|
+
height: 100%;
|
|
4
|
+
position: absolute;
|
|
5
|
+
top: 0;
|
|
6
|
+
left: 0;
|
|
7
|
+
overflow: hidden;
|
|
8
|
+
`,document.body.appendChild(c),c}function De(c){return c&&c instanceof HTMLElement?c:(console.warn("[ForceGraph3D] No container provided, creating one automatically"),ke())}function le(c){const e=c.getBoundingClientRect();return{width:e.width||window.innerWidth,height:e.height||window.innerHeight}}function Q(c){if(!c||typeof c!="object")return console.warn("[ForceGraph3D] Invalid node: must be an object"),!1;const e=c;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&&!je(e.position)?(console.warn("[ForceGraph3D] Invalid node: position must have x, y, z numbers"),!1):!0}function J(c){if(!c||typeof c!="object")return console.warn("[ForceGraph3D] Invalid edge: must be an object"),!1;const e=c;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}function Ie(c){return typeof c!="string"||c.trim()===""?(console.warn("[ForceGraph3D] Invalid node ID: must be a non-empty string"),!1):!0}function je(c){if(!c||typeof c!="object")return!1;const e=c;return typeof e.x=="number"&&typeof e.y=="number"&&typeof e.z=="number"}function F(c,e){return c===e?`${c}-${e}`:c<e?`${c}-${e}`:`${e}-${c}`}const ce={type:"change"},ee={type:"start"},he={type:"end"},U=new d.Ray,de=new d.Plane,Ae=Math.cos(70*d.MathUtils.DEG2RAD);class He extends d.EventDispatcher{constructor(e,o){super(),this.object=e,this.domElement=o,this.domElement.style.touchAction="none",this.enabled=!0,this.target=new d.Vector3,this.cursor=new d.Vector3,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=.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:d.MOUSE.ROTATE,MIDDLE:d.MOUSE.DOLLY,RIGHT:d.MOUSE.PAN},this.touches={ONE:d.TOUCH.ROTATE,TWO:d.TOUCH.DOLLY_PAN},this.target0=this.target.clone(),this.position0=this.object.position.clone(),this.zoom0=this.object.zoom,this._domElementKeyEvents=null,this.getPolarAngle=function(){return l.phi},this.getAzimuthalAngle=function(){return l.theta},this.getDistance=function(){return this.object.position.distanceTo(this.target)},this.listenToKeyEvents=function(s){s.addEventListener("keydown",ae),this._domElementKeyEvents=s},this.stopListenToKeyEvents=function(){this._domElementKeyEvents.removeEventListener("keydown",ae),this._domElementKeyEvents=null},this.saveState=function(){t.target0.copy(t.target),t.position0.copy(t.object.position),t.zoom0=t.object.zoom},this.reset=function(){t.target.copy(t.target0),t.object.position.copy(t.position0),t.object.zoom=t.zoom0,t.object.updateProjectionMatrix(),t.dispatchEvent(ce),t.update(),i=n.NONE},this.update=function(){const s=new d.Vector3,h=new d.Quaternion().setFromUnitVectors(e.up,new d.Vector3(0,1,0)),f=h.clone().invert(),v=new d.Vector3,E=new d.Quaternion,k=new d.Vector3,z=2*Math.PI;return function(vt=null){const Te=t.object.position;s.copy(Te).sub(t.target),s.applyQuaternion(h),l.setFromVector3(s),t.autoRotate&&i===n.NONE&&G(tt(vt)),t.enableDamping?(l.theta+=p.theta*t.dampingFactor,l.phi+=p.phi*t.dampingFactor):(l.theta+=p.theta,l.phi+=p.phi);let O=t.minAzimuthAngle,L=t.maxAzimuthAngle;isFinite(O)&&isFinite(L)&&(O<-Math.PI?O+=z:O>Math.PI&&(O-=z),L<-Math.PI?L+=z:L>Math.PI&&(L-=z),O<=L?l.theta=Math.max(O,Math.min(L,l.theta)):l.theta=l.theta>(O+L)/2?Math.max(O,l.theta):Math.min(L,l.theta)),l.phi=Math.max(t.minPolarAngle,Math.min(t.maxPolarAngle,l.phi)),l.makeSafe(),t.enableDamping===!0?t.target.addScaledVector(m,t.dampingFactor):t.target.add(m),t.target.sub(t.cursor),t.target.clampLength(t.minTargetRadius,t.maxTargetRadius),t.target.add(t.cursor),t.zoomToCursor&&_||t.object.isOrthographicCamera?l.radius=ie(l.radius):l.radius=ie(l.radius*u),s.setFromSpherical(l),s.applyQuaternion(f),Te.copy(t.target).add(s),t.object.lookAt(t.target),t.enableDamping===!0?(p.theta*=1-t.dampingFactor,p.phi*=1-t.dampingFactor,m.multiplyScalar(1-t.dampingFactor)):(p.set(0,0,0),m.set(0,0,0));let re=!1;if(t.zoomToCursor&&_){let Y=null;if(t.object.isPerspectiveCamera){const V=s.length();Y=ie(V*u);const Z=V-Y;t.object.position.addScaledVector(me,Z),t.object.updateMatrixWorld()}else if(t.object.isOrthographicCamera){const V=new d.Vector3(T.x,T.y,0);V.unproject(t.object),t.object.zoom=Math.max(t.minZoom,Math.min(t.maxZoom,t.object.zoom/u)),t.object.updateProjectionMatrix(),re=!0;const Z=new d.Vector3(T.x,T.y,0);Z.unproject(t.object),t.object.position.sub(Z).add(V),t.object.updateMatrixWorld(),Y=s.length()}else console.warn("WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled."),t.zoomToCursor=!1;Y!==null&&(this.screenSpacePanning?t.target.set(0,0,-1).transformDirection(t.object.matrix).multiplyScalar(Y).add(t.object.position):(U.origin.copy(t.object.position),U.direction.set(0,0,-1).transformDirection(t.object.matrix),Math.abs(t.object.up.dot(U.direction))<Ae?e.lookAt(t.target):(de.setFromNormalAndCoplanarPoint(t.object.up,t.target),U.intersectPlane(de,t.target))))}else t.object.isOrthographicCamera&&(t.object.zoom=Math.max(t.minZoom,Math.min(t.maxZoom,t.object.zoom/u)),t.object.updateProjectionMatrix(),re=!0);return u=1,_=!1,re||v.distanceToSquared(t.object.position)>a||8*(1-E.dot(t.object.quaternion))>a||k.distanceToSquared(t.target)>0?(t.dispatchEvent(ce),v.copy(t.object.position),E.copy(t.object.quaternion),k.copy(t.target),!0):!1}}(),this.dispose=function(){t.domElement.removeEventListener("contextmenu",Le),t.domElement.removeEventListener("pointerdown",ze),t.domElement.removeEventListener("pointercancel",$),t.domElement.removeEventListener("wheel",Pe),t.domElement.removeEventListener("pointermove",se),t.domElement.removeEventListener("pointerup",$),t._domElementKeyEvents!==null&&(t._domElementKeyEvents.removeEventListener("keydown",ae),t._domElementKeyEvents=null)};const t=this,n={NONE:-1,ROTATE:0,DOLLY:1,PAN:2,TOUCH_ROTATE:3,TOUCH_PAN:4,TOUCH_DOLLY_PAN:5,TOUCH_DOLLY_ROTATE:6};let i=n.NONE;const a=1e-6,l=new d.Spherical,p=new d.Spherical;let u=1;const m=new d.Vector3,x=new d.Vector2,M=new d.Vector2,b=new d.Vector2,y=new d.Vector2,w=new d.Vector2,N=new d.Vector2,A=new d.Vector2,H=new d.Vector2,D=new d.Vector2,me=new d.Vector3,T=new d.Vector2;let _=!1;const S=[],X={};let te=!1;function tt(s){return s!==null?2*Math.PI/60*t.autoRotateSpeed*s:2*Math.PI/60/60*t.autoRotateSpeed}function q(s){const h=Math.abs(s*.01);return Math.pow(.95,t.zoomSpeed*h)}function G(s){p.theta-=s}function W(s){p.phi-=s}const fe=function(){const s=new d.Vector3;return function(f,v){s.setFromMatrixColumn(v,0),s.multiplyScalar(-f),m.add(s)}}(),ye=function(){const s=new d.Vector3;return function(f,v){t.screenSpacePanning===!0?s.setFromMatrixColumn(v,1):(s.setFromMatrixColumn(v,0),s.crossVectors(t.object.up,s)),s.multiplyScalar(f),m.add(s)}}(),R=function(){const s=new d.Vector3;return function(f,v){const E=t.domElement;if(t.object.isPerspectiveCamera){const k=t.object.position;s.copy(k).sub(t.target);let z=s.length();z*=Math.tan(t.object.fov/2*Math.PI/180),fe(2*f*z/E.clientHeight,t.object.matrix),ye(2*v*z/E.clientHeight,t.object.matrix)}else t.object.isOrthographicCamera?(fe(f*(t.object.right-t.object.left)/t.object.zoom/E.clientWidth,t.object.matrix),ye(v*(t.object.top-t.object.bottom)/t.object.zoom/E.clientHeight,t.object.matrix)):(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - pan disabled."),t.enablePan=!1)}}();function oe(s){t.object.isPerspectiveCamera||t.object.isOrthographicCamera?u/=s:(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."),t.enableZoom=!1)}function be(s){t.object.isPerspectiveCamera||t.object.isOrthographicCamera?u*=s:(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."),t.enableZoom=!1)}function ne(s,h){if(!t.zoomToCursor)return;_=!0;const f=t.domElement.getBoundingClientRect(),v=s-f.left,E=h-f.top,k=f.width,z=f.height;T.x=v/k*2-1,T.y=-(E/z)*2+1,me.set(T.x,T.y,1).unproject(t.object).sub(t.object.position).normalize()}function ie(s){return Math.max(t.minDistance,Math.min(t.maxDistance,s))}function xe(s){x.set(s.clientX,s.clientY)}function ot(s){ne(s.clientX,s.clientX),A.set(s.clientX,s.clientY)}function ve(s){y.set(s.clientX,s.clientY)}function nt(s){M.set(s.clientX,s.clientY),b.subVectors(M,x).multiplyScalar(t.rotateSpeed);const h=t.domElement;G(2*Math.PI*b.x/h.clientHeight),W(2*Math.PI*b.y/h.clientHeight),x.copy(M),t.update()}function it(s){H.set(s.clientX,s.clientY),D.subVectors(H,A),D.y>0?oe(q(D.y)):D.y<0&&be(q(D.y)),A.copy(H),t.update()}function st(s){w.set(s.clientX,s.clientY),N.subVectors(w,y).multiplyScalar(t.panSpeed),R(N.x,N.y),y.copy(w),t.update()}function at(s){ne(s.clientX,s.clientY),s.deltaY<0?be(q(s.deltaY)):s.deltaY>0&&oe(q(s.deltaY)),t.update()}function rt(s){let h=!1;switch(s.code){case t.keys.UP:s.ctrlKey||s.metaKey||s.shiftKey?W(2*Math.PI*t.rotateSpeed/t.domElement.clientHeight):R(0,t.keyPanSpeed),h=!0;break;case t.keys.BOTTOM:s.ctrlKey||s.metaKey||s.shiftKey?W(-2*Math.PI*t.rotateSpeed/t.domElement.clientHeight):R(0,-t.keyPanSpeed),h=!0;break;case t.keys.LEFT:s.ctrlKey||s.metaKey||s.shiftKey?G(2*Math.PI*t.rotateSpeed/t.domElement.clientHeight):R(t.keyPanSpeed,0),h=!0;break;case t.keys.RIGHT:s.ctrlKey||s.metaKey||s.shiftKey?G(-2*Math.PI*t.rotateSpeed/t.domElement.clientHeight):R(-t.keyPanSpeed,0),h=!0;break}h&&(s.preventDefault(),t.update())}function Me(s){if(S.length===1)x.set(s.pageX,s.pageY);else{const h=K(s),f=.5*(s.pageX+h.x),v=.5*(s.pageY+h.y);x.set(f,v)}}function we(s){if(S.length===1)y.set(s.pageX,s.pageY);else{const h=K(s),f=.5*(s.pageX+h.x),v=.5*(s.pageY+h.y);y.set(f,v)}}function Ce(s){const h=K(s),f=s.pageX-h.x,v=s.pageY-h.y,E=Math.sqrt(f*f+v*v);A.set(0,E)}function lt(s){t.enableZoom&&Ce(s),t.enablePan&&we(s)}function ct(s){t.enableZoom&&Ce(s),t.enableRotate&&Me(s)}function Ee(s){if(S.length==1)M.set(s.pageX,s.pageY);else{const f=K(s),v=.5*(s.pageX+f.x),E=.5*(s.pageY+f.y);M.set(v,E)}b.subVectors(M,x).multiplyScalar(t.rotateSpeed);const h=t.domElement;G(2*Math.PI*b.x/h.clientHeight),W(2*Math.PI*b.y/h.clientHeight),x.copy(M)}function Se(s){if(S.length===1)w.set(s.pageX,s.pageY);else{const h=K(s),f=.5*(s.pageX+h.x),v=.5*(s.pageY+h.y);w.set(f,v)}N.subVectors(w,y).multiplyScalar(t.panSpeed),R(N.x,N.y),y.copy(w)}function Ne(s){const h=K(s),f=s.pageX-h.x,v=s.pageY-h.y,E=Math.sqrt(f*f+v*v);H.set(0,E),D.set(0,Math.pow(H.y/A.y,t.zoomSpeed)),oe(D.y),A.copy(H);const k=(s.pageX+h.x)*.5,z=(s.pageY+h.y)*.5;ne(k,z)}function ht(s){t.enableZoom&&Ne(s),t.enablePan&&Se(s)}function dt(s){t.enableZoom&&Ne(s),t.enableRotate&&Ee(s)}function ze(s){t.enabled!==!1&&(S.length===0&&(t.domElement.setPointerCapture(s.pointerId),t.domElement.addEventListener("pointermove",se),t.domElement.addEventListener("pointerup",$)),bt(s),s.pointerType==="touch"?ft(s):pt(s))}function se(s){t.enabled!==!1&&(s.pointerType==="touch"?yt(s):gt(s))}function $(s){xt(s),S.length===0&&(t.domElement.releasePointerCapture(s.pointerId),t.domElement.removeEventListener("pointermove",se),t.domElement.removeEventListener("pointerup",$)),t.dispatchEvent(he),i=n.NONE}function pt(s){let h;switch(s.button){case 0:h=t.mouseButtons.LEFT;break;case 1:h=t.mouseButtons.MIDDLE;break;case 2:h=t.mouseButtons.RIGHT;break;default:h=-1}switch(h){case d.MOUSE.DOLLY:if(t.enableZoom===!1)return;ot(s),i=n.DOLLY;break;case d.MOUSE.ROTATE:if(s.ctrlKey||s.metaKey||s.shiftKey){if(t.enablePan===!1)return;ve(s),i=n.PAN}else{if(t.enableRotate===!1)return;xe(s),i=n.ROTATE}break;case d.MOUSE.PAN:if(s.ctrlKey||s.metaKey||s.shiftKey){if(t.enableRotate===!1)return;xe(s),i=n.ROTATE}else{if(t.enablePan===!1)return;ve(s),i=n.PAN}break;default:i=n.NONE}i!==n.NONE&&t.dispatchEvent(ee)}function gt(s){switch(i){case n.ROTATE:if(t.enableRotate===!1)return;nt(s);break;case n.DOLLY:if(t.enableZoom===!1)return;it(s);break;case n.PAN:if(t.enablePan===!1)return;st(s);break}}function Pe(s){t.enabled===!1||t.enableZoom===!1||i!==n.NONE||(s.preventDefault(),t.dispatchEvent(ee),at(ut(s)),t.dispatchEvent(he))}function ut(s){const h=s.deltaMode,f={clientX:s.clientX,clientY:s.clientY,deltaY:s.deltaY};switch(h){case 1:f.deltaY*=16;break;case 2:f.deltaY*=100;break}return s.ctrlKey&&!te&&(f.deltaY*=10),f}function mt(s){s.key==="Control"&&(te=!0,document.addEventListener("keyup",Oe,{passive:!0,capture:!0}))}function Oe(s){s.key==="Control"&&(te=!1,document.removeEventListener("keyup",Oe,{passive:!0,capture:!0}))}function ae(s){t.enabled===!1||t.enablePan===!1||rt(s)}function ft(s){switch(Fe(s),S.length){case 1:switch(t.touches.ONE){case d.TOUCH.ROTATE:if(t.enableRotate===!1)return;Me(s),i=n.TOUCH_ROTATE;break;case d.TOUCH.PAN:if(t.enablePan===!1)return;we(s),i=n.TOUCH_PAN;break;default:i=n.NONE}break;case 2:switch(t.touches.TWO){case d.TOUCH.DOLLY_PAN:if(t.enableZoom===!1&&t.enablePan===!1)return;lt(s),i=n.TOUCH_DOLLY_PAN;break;case d.TOUCH.DOLLY_ROTATE:if(t.enableZoom===!1&&t.enableRotate===!1)return;ct(s),i=n.TOUCH_DOLLY_ROTATE;break;default:i=n.NONE}break;default:i=n.NONE}i!==n.NONE&&t.dispatchEvent(ee)}function yt(s){switch(Fe(s),i){case n.TOUCH_ROTATE:if(t.enableRotate===!1)return;Ee(s),t.update();break;case n.TOUCH_PAN:if(t.enablePan===!1)return;Se(s),t.update();break;case n.TOUCH_DOLLY_PAN:if(t.enableZoom===!1&&t.enablePan===!1)return;ht(s),t.update();break;case n.TOUCH_DOLLY_ROTATE:if(t.enableZoom===!1&&t.enableRotate===!1)return;dt(s),t.update();break;default:i=n.NONE}}function Le(s){t.enabled!==!1&&s.preventDefault()}function bt(s){S.push(s.pointerId)}function xt(s){delete X[s.pointerId];for(let h=0;h<S.length;h++)if(S[h]==s.pointerId){S.splice(h,1);return}}function Fe(s){let h=X[s.pointerId];h===void 0&&(h=new d.Vector2,X[s.pointerId]=h),h.set(s.pageX,s.pageY)}function K(s){const h=s.pointerId===S[0]?S[1]:S[0];return X[h]}t.domElement.addEventListener("contextmenu",Le),t.domElement.addEventListener("pointerdown",ze),t.domElement.addEventListener("pointercancel",$),t.domElement.addEventListener("wheel",Pe,{passive:!1}),document.addEventListener("keydown",mt,{passive:!0,capture:!0}),this.update()}}class Re{constructor(e,o){r(this,"scene");r(this,"camera");r(this,"renderer");r(this,"controls");r(this,"container");r(this,"resizeHandler");this.container=e,this.scene=new g.Scene,this.scene.background=new g.Color(o.backgroundColor??657930);const{width:t,height:n}=le(e),i=o.cameraFov??75;this.camera=new g.PerspectiveCamera(i,t/n,.1,2e3);const a=o.cameraPosition??{x:0,y:0,z:80};this.camera.position.set(a.x,a.y,a.z),this.renderer=new g.WebGLRenderer({antialias:!0,alpha:!0,powerPreference:"high-performance"}),this.renderer.setSize(t,n),this.renderer.setPixelRatio(Math.min(window.devicePixelRatio,2)),this.renderer.toneMapping=g.ACESFilmicToneMapping,this.renderer.toneMappingExposure=1,this.renderer.shadowMap.enabled=!0,this.renderer.shadowMap.type=g.PCFSoftShadowMap,e.appendChild(this.renderer.domElement),this.controls=new He(this.camera,this.renderer.domElement),this.controls.enableDamping=!0,this.controls.dampingFactor=.05,this.controls.rotateSpeed=.8,this.controls.zoomSpeed=1.2,this.controls.minDistance=10,this.controls.maxDistance=500,this.setupLighting(),this.resizeHandler=this.onWindowResize.bind(this),window.addEventListener("resize",this.resizeHandler)}setupLighting(){const e=new g.AmbientLight(16777215,.4);this.scene.add(e);const o=new g.DirectionalLight(16777215,.9);o.position.set(50,60,40),o.castShadow=!0,o.shadow.mapSize.width=1024,o.shadow.mapSize.height=1024,this.scene.add(o);const t=new g.DirectionalLight(16773344,.4);t.position.set(-50,30,-40),this.scene.add(t);const n=new g.DirectionalLight(16777215,.3);n.position.set(0,-30,-50),this.scene.add(n);const i=new g.PointLight(16750950,.5,150);i.position.set(40,20,40),this.scene.add(i);const a=new g.PointLight(16764057,.4,150);a.position.set(-40,-20,40),this.scene.add(a);const l=new g.PointLight(6724095,.2,100);l.position.set(0,40,-40),this.scene.add(l)}onWindowResize(){const{width:e,height:o}=le(this.container);this.camera.aspect=e/o,this.camera.updateProjectionMatrix(),this.renderer.setSize(e,o)}add(e){this.scene.add(e)}remove(e){this.scene.remove(e)}render(){this.controls.update(),this.renderer.render(this.scene,this.camera)}getCameraPosition(){return{x:this.camera.position.x,y:this.camera.position.y,z:this.camera.position.z}}getCameraDirection(){const e=new g.Vector3;return this.camera.getWorldDirection(e),e}dispose(){for(window.removeEventListener("resize",this.resizeHandler),this.controls.dispose(),this.renderer.dispose(),this.renderer.domElement.parentNode&&this.renderer.domElement.parentNode.removeChild(this.renderer.domElement);this.scene.children.length>0;){const e=this.scene.children[0];this.scene.remove(e)}}}class Ke{constructor(e,o){r(this,"sceneManager");r(this,"nodeFactory");r(this,"nodes",new Map);r(this,"nodeObjects",new Map);this.sceneManager=e,this.nodeFactory=o}hasNode(e){return this.nodes.has(e)}addNode(e,o=0){if(!Q(e))return!1;if(this.nodes.has(e.id))return console.warn(`[ForceGraph3D] Node with id "${e.id}" already exists`),!1;const t=e.position??{x:(Math.random()-.5)*50,y:(Math.random()-.5)*50,z:(Math.random()-.5)*50},n={...e,position:t,velocity:{x:0,y:0,z:0},mass:1},i=this.nodeFactory.createNode({...e,position:t},o);return this.sceneManager.add(i.group),this.nodes.set(e.id,n),this.nodeObjects.set(e.id,i),!0}removeNode(e){const o=this.nodes.get(e),t=this.nodeObjects.get(e);return!o||!t?!1:(this.sceneManager.remove(t.group),this.nodeFactory.disposeNode(t),this.nodes.delete(e),this.nodeObjects.delete(e),!0)}updateNode(e,o){const t=this.nodes.get(e),n=this.nodeObjects.get(e);return!t||!n?(console.warn(`[ForceGraph3D] Node "${e}" not found`),!1):(o.label!==void 0&&(t.label=o.label,this.nodeFactory.updateNodeLabel(n,o.label)),o.color!==void 0&&(t.color=o.color,this.nodeFactory.updateNodeColor(n,o.color)),Object.keys(o).forEach(i=>{i!=="id"&&i!=="label"&&i!=="color"&&i!=="position"&&(t[i]=o[i])}),!0)}updateNodePosition(e,o){const t=this.nodes.get(e),n=this.nodeObjects.get(e);t&&n&&(t.position=o,n.group.position.set(o.x,o.y,o.z))}updateNodeLOD(e,o){const t=this.nodeObjects.get(e);t&&this.nodeFactory.updateNodeLOD(t,o)}getNode(e){return this.nodes.get(e)}getNodeObject(e){return this.nodeObjects.get(e)}getAllNodes(){return this.nodes}getAllNodeObjects(){const e=[];return this.nodeObjects.forEach(o=>{e.push(o.group)}),e}getAllSpheres(){const e=[];return this.nodeObjects.forEach(o=>{e.push(o.sphere)}),e}getNodeCount(){return this.nodes.size}clear(){this.nodes.forEach((e,o)=>{this.removeNode(o)})}dispose(){this.clear()}}class Ge{constructor(e,o,t){r(this,"sceneManager");r(this,"nodeManager");r(this,"edgeFactory");r(this,"edges",[]);r(this,"edgeObjects",[]);r(this,"edgeKeySet",new Set);r(this,"highlightedEdgeKey",null);this.sceneManager=e,this.nodeManager=o,this.edgeFactory=t}hasEdge(e,o){const t=F(e,o);return this.edgeKeySet.has(t)}addEdge(e){if(!J(e))return!1;if(!this.nodeManager.hasNode(e.source))return console.warn(`[ForceGraph3D] Source node "${e.source}" does not exist`),!1;if(!this.nodeManager.hasNode(e.target))return console.warn(`[ForceGraph3D] Target node "${e.target}" does not exist`),!1;const o=F(e.source,e.target);if(this.edgeKeySet.has(o))return console.warn(`[ForceGraph3D] Edge "${e.source}" -> "${e.target}" already exists`),!1;const t=this.nodeManager.getNode(e.source),n=this.nodeManager.getNode(e.target),i=this.edgeFactory.createEdge(e,t,n,t.position,n.position);return this.sceneManager.add(i.line),this.edges.push(e),this.edgeObjects.push(i),this.edgeKeySet.add(o),!0}removeEdge(e,o){const t=F(e,o);if(!this.edgeKeySet.has(t))return!1;const n=this.edges.findIndex(a=>F(a.source,a.target)===t);if(n===-1)return!1;const i=this.edgeObjects[n];return this.sceneManager.remove(i.line),this.edgeFactory.disposeEdge(i),this.edges.splice(n,1),this.edgeObjects.splice(n,1),this.edgeKeySet.delete(t),this.highlightedEdgeKey===t&&(this.highlightedEdgeKey=null),!0}highlightEdge(e,o){const t=F(e,o);this.highlightedEdgeKey&&this.highlightedEdgeKey!==t&&this.unhighlightCurrentEdge();const n=this.edges.findIndex(i=>F(i.source,i.target)===t);n!==-1&&(this.edgeFactory.highlightEdge(this.edgeObjects[n]),this.highlightedEdgeKey=t)}unhighlightCurrentEdge(){if(!this.highlightedEdgeKey)return;const e=this.edges.findIndex(o=>F(o.source,o.target)===this.highlightedEdgeKey);e!==-1&&this.edgeFactory.unhighlightEdge(this.edgeObjects[e]),this.highlightedEdgeKey=null}removeEdgesForNode(e){const o=[];this.edges.forEach(t=>{(t.source===e||t.target===e)&&o.push({source:t.source,target:t.target})}),o.forEach(t=>{this.removeEdge(t.source,t.target)})}getEdgesForNode(e){return this.edges.filter(o=>o.source===e||o.target===e)}getNeighborIds(e){const o=[];return this.edges.forEach(t=>{t.source===e?o.push(t.target):t.target===e&&o.push(t.source)}),[...new Set(o)]}updateEdgePositions(){this.edgeObjects.forEach((e,o)=>{const t=this.edges[o],n=this.nodeManager.getNode(t.source),i=this.nodeManager.getNode(t.target);n&&i&&this.edgeFactory.updateEdgePositions(e,n.position,i.position)})}getAllEdges(){return this.edges}getAllEdgeLines(){return this.edgeObjects.map(e=>e.line)}getEdgeCount(){return this.edges.length}clear(){this.edgeObjects.forEach(e=>{this.sceneManager.remove(e.line),this.edgeFactory.disposeEdge(e)}),this.edges=[],this.edgeObjects=[],this.edgeKeySet.clear(),this.highlightedEdgeKey=null}dispose(){this.clear()}}class pe{constructor(e,o,t={}){r(this,"nodes");r(this,"edges");r(this,"repulsionStrength");r(this,"attractionStrength");r(this,"damping");r(this,"useBarnesHut");r(this,"barnesHutTheta");r(this,"alpha",1);r(this,"alphaDecay",.0228);r(this,"alphaMin",.001);r(this,"alphaTarget",0);this.nodes=e,this.edges=o,this.repulsionStrength=t.repulsionStrength??100,this.attractionStrength=t.attractionStrength??.01,this.damping=t.damping??.9,this.useBarnesHut=t.useBarnesHut??!1,this.barnesHutTheta=t.barnesHutTheta??.5}simulate(){if(this.alpha<this.alphaMin)return;const e=this.nodes.size;(this.useBarnesHut||e>=1e3)&&e>=100?this.calculateRepulsionBarnesHut():this.calculateRepulsionBruteForce(),this.calculateAttraction(),this.applyForces(),this.alpha+=(this.alphaTarget-this.alpha)*this.alphaDecay}calculateRepulsionBruteForce(){const e=Array.from(this.nodes.values()),o=e.length;for(let t=0;t<o;t++){const n=e[t];for(let i=t+1;i<o;i++){const a=e[i],l=a.position.x-n.position.x,p=a.position.y-n.position.y,u=a.position.z-n.position.z;let m=l*l+p*p+u*u;m<.01&&(m=.01);const x=Math.sqrt(m),M=this.repulsionStrength*this.alpha/m,b=l/x*M,y=p/x*M,w=u/x*M;n.velocity.x-=b/n.mass,n.velocity.y-=y/n.mass,n.velocity.z-=w/n.mass,a.velocity.x+=b/a.mass,a.velocity.y+=y/a.mass,a.velocity.z+=w/a.mass}}}calculateRepulsionBarnesHut(){const e=Array.from(this.nodes.values()),o=new $e(e);for(const t of e)this.calculateForceFromOctree(t,o.root)}calculateForceFromOctree(e,o){if(o.isLeaf){o.node&&o.node.id!==e.id&&this.applyRepulsionBetween(e,o.node);return}if(o.mass===0)return;const t=o.centerOfMass.x-e.position.x,n=o.centerOfMass.y-e.position.y,i=o.centerOfMass.z-e.position.z,a=Math.sqrt(t*t+n*n+i*i);if(o.size/a<this.barnesHutTheta){const l=Math.max(a*a,.01),p=this.repulsionStrength*this.alpha*o.mass/l;e.velocity.x-=t/a*p/e.mass,e.velocity.y-=n/a*p/e.mass,e.velocity.z-=i/a*p/e.mass}else for(const l of o.children)l&&this.calculateForceFromOctree(e,l)}applyRepulsionBetween(e,o){const t=o.position.x-e.position.x,n=o.position.y-e.position.y,i=o.position.z-e.position.z;let a=t*t+n*n+i*i;a<.01&&(a=.01);const l=Math.sqrt(a),p=this.repulsionStrength*this.alpha/a;e.velocity.x-=t/l*p/e.mass,e.velocity.y-=n/l*p/e.mass,e.velocity.z-=i/l*p/e.mass}calculateAttraction(){for(const e of this.edges){const o=this.nodes.get(e.source),t=this.nodes.get(e.target);if(!o||!t)continue;const n=t.position.x-o.position.x,i=t.position.y-o.position.y,a=t.position.z-o.position.z,l=Math.sqrt(n*n+i*i+a*a);if(l<.01)continue;const u=(l-15)*this.attractionStrength*this.alpha,m=n/l*u,x=i/l*u,M=a/l*u;o.velocity.x+=m/o.mass,o.velocity.y+=x/o.mass,o.velocity.z+=M/o.mass,t.velocity.x-=m/t.mass,t.velocity.y-=x/t.mass,t.velocity.z-=M/t.mass}}applyForces(){for(const e of this.nodes.values())e.velocity.x*=this.damping,e.velocity.y*=this.damping,e.velocity.z*=this.damping,e.position.x+=e.velocity.x,e.position.y+=e.velocity.y,e.position.z+=e.velocity.z}setEdges(e){this.edges=e}restart(){this.alpha=1}getAlpha(){return this.alpha}setPhysicsParams(e){e.repulsionStrength!==void 0&&(this.repulsionStrength=e.repulsionStrength),e.attractionStrength!==void 0&&(this.attractionStrength=e.attractionStrength),e.damping!==void 0&&(this.damping=e.damping)}}class $e{constructor(e){r(this,"root");const o=this.calculateBounds(e);this.root=this.buildTree(e,o)}calculateBounds(e){if(e.length===0)return{min:{x:-100,y:-100,z:-100},max:{x:100,y:100,z:100}};const o={x:1/0,y:1/0,z:1/0},t={x:-1/0,y:-1/0,z:-1/0};for(const i of e)o.x=Math.min(o.x,i.position.x),o.y=Math.min(o.y,i.position.y),o.z=Math.min(o.z,i.position.z),t.x=Math.max(t.x,i.position.x),t.y=Math.max(t.y,i.position.y),t.z=Math.max(t.z,i.position.z);const n=10;return o.x-=n,o.y-=n,o.z-=n,t.x+=n,t.y+=n,t.z+=n,{min:o,max:t}}buildTree(e,o,t=0){const n=Math.max(o.max.x-o.min.x,o.max.y-o.min.y,o.max.z-o.min.z);if(e.length===0)return{bounds:o,size:n,centerOfMass:{x:0,y:0,z:0},mass:0,isLeaf:!0,node:null,children:[]};if(e.length===1||t>20){let b=0;const y={x:0,y:0,z:0};for(const w of e)b+=w.mass,y.x+=w.position.x*w.mass,y.y+=w.position.y*w.mass,y.z+=w.position.z*w.mass;return b>0&&(y.x/=b,y.y/=b,y.z/=b),{bounds:o,size:n,centerOfMass:y,mass:b,isLeaf:!0,node:e[0],children:[]}}const i=(o.min.x+o.max.x)/2,a=(o.min.y+o.max.y)/2,l=(o.min.z+o.max.z)/2,p=[[],[],[],[],[],[],[],[]];for(const b of e){const y=(b.position.x>=i?1:0)+(b.position.y>=a?2:0)+(b.position.z>=l?4:0);p[y].push(b)}const u=[{min:{x:o.min.x,y:o.min.y,z:o.min.z},max:{x:i,y:a,z:l}},{min:{x:i,y:o.min.y,z:o.min.z},max:{x:o.max.x,y:a,z:l}},{min:{x:o.min.x,y:a,z:o.min.z},max:{x:i,y:o.max.y,z:l}},{min:{x:i,y:a,z:o.min.z},max:{x:o.max.x,y:o.max.y,z:l}},{min:{x:o.min.x,y:o.min.y,z:l},max:{x:i,y:a,z:o.max.z}},{min:{x:i,y:o.min.y,z:l},max:{x:o.max.x,y:a,z:o.max.z}},{min:{x:o.min.x,y:a,z:l},max:{x:i,y:o.max.y,z:o.max.z}},{min:{x:i,y:a,z:l},max:{x:o.max.x,y:o.max.y,z:o.max.z}}],m=[];let x=0;const M={x:0,y:0,z:0};for(let b=0;b<8;b++)if(p[b].length>0){const y=this.buildTree(p[b],u[b],t+1);m.push(y),x+=y.mass,M.x+=y.centerOfMass.x*y.mass,M.y+=y.centerOfMass.y*y.mass,M.z+=y.centerOfMass.z*y.mass}else m.push(null);return x>0&&(M.x/=x,M.y/=x,M.z/=x),{bounds:o,size:n,centerOfMass:M,mass:x,isLeaf:!1,node:null,children:m}}}class Ye{constructor(e,o,t,n=60){r(this,"sceneManager");r(this,"animationId",null);r(this,"isRunning",!1);r(this,"frameInterval");r(this,"lastFrameTime",0);r(this,"onSimulate");r(this,"onRender");r(this,"frameCount",0);r(this,"fpsStartTime",0);r(this,"currentFPS",60);r(this,"animate",()=>{if(!this.isRunning)return;this.animationId=requestAnimationFrame(this.animate);const e=performance.now(),o=e-this.lastFrameTime;if(o<this.frameInterval)return;this.lastFrameTime=e-o%this.frameInterval,this.frameCount++;const t=e-this.fpsStartTime;t>=1e3&&(this.currentFPS=this.frameCount/(t/1e3),this.frameCount=0,this.fpsStartTime=e),this.onSimulate(),this.onRender(),this.sceneManager.render()});this.sceneManager=e,this.onSimulate=o,this.onRender=t,this.frameInterval=1e3/n}start(){this.isRunning||(this.isRunning=!0,this.lastFrameTime=performance.now(),this.fpsStartTime=performance.now(),this.frameCount=0,this.animate())}stop(){this.isRunning=!1,this.animationId!==null&&(cancelAnimationFrame(this.animationId),this.animationId=null)}getFPS(){return Math.round(this.currentFPS)}setTargetFPS(e){this.frameInterval=1e3/e}isAnimating(){return this.isRunning}dispose(){this.stop()}}class Ve{constructor(){r(this,"envMap",null);r(this,"materialCache",new Map);r(this,"COLORS",[16777215,16750950,16764057,16772829,16746564]);this.createEnvironmentMap()}createEnvironmentMap(){const o=[],t=[{colors:["#1a1a2e","#16213e","#0f3460"]},{colors:["#2d1f1f","#1a1a1a","#0f0f0f"]},{colors:["#1f2d2d","#1a1a2e","#101020"]},{colors:["#0a0a0a","#050505","#000000"]},{colors:["#1a1a2e","#0f1a2a","#0a0a15"]},{colors:["#2d1a1a","#1a0a0a","#0f0505"]}];for(const n of t){const i=document.createElement("canvas");i.width=256,i.height=256;const a=i.getContext("2d"),l=a.createRadialGradient(256/2,256/2,0,256/2,256/2,256*.8);l.addColorStop(0,n.colors[0]),l.addColorStop(.5,n.colors[1]),l.addColorStop(1,n.colors[2]),a.fillStyle=l,a.fillRect(0,0,256,256);const p=a.getImageData(0,0,256,256);for(let u=0;u<p.data.length;u+=4){const m=(Math.random()-.5)*5;p.data[u]=Math.min(255,Math.max(0,p.data[u]+m)),p.data[u+1]=Math.min(255,Math.max(0,p.data[u+1]+m)),p.data[u+2]=Math.min(255,Math.max(0,p.data[u+2]+m))}a.putImageData(p,0,0),o.push(i)}this.envMap=new g.CubeTexture(o.map(n=>{const i=new Image;return i.src=n.toDataURL(),i})),this.envMap.needsUpdate=!0}getRandomColor(){return this.COLORS[Math.floor(Math.random()*this.COLORS.length)]}createGlassMaterial(e){const t="glass-single";if(this.materialCache.has(t))return this.materialCache.get(t).clone();const n=new g.Color(16750950),i=new g.ShaderMaterial({uniforms:{uColor:{value:n},uEnvMap:{value:this.envMap},uGlowColor:{value:new g.Color(16777215)},uGlowIntensity:{value:.8},uReflectivity:{value:.4},uFresnelPower:{value:2.5}},vertexShader:`
|
|
9
|
+
varying vec3 vNormal;
|
|
10
|
+
varying vec3 vViewPosition;
|
|
11
|
+
varying vec3 vWorldPosition;
|
|
12
|
+
varying vec2 vUv;
|
|
13
|
+
|
|
14
|
+
void main() {
|
|
15
|
+
vNormal = normalize(normalMatrix * normal);
|
|
16
|
+
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
|
17
|
+
vViewPosition = -mvPosition.xyz;
|
|
18
|
+
vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
|
|
19
|
+
vUv = uv;
|
|
20
|
+
gl_Position = projectionMatrix * mvPosition;
|
|
21
|
+
}
|
|
22
|
+
`,fragmentShader:`
|
|
23
|
+
uniform vec3 uColor;
|
|
24
|
+
uniform vec3 uGlowColor;
|
|
25
|
+
uniform samplerCube uEnvMap;
|
|
26
|
+
uniform float uGlowIntensity;
|
|
27
|
+
uniform float uReflectivity;
|
|
28
|
+
uniform float uFresnelPower;
|
|
29
|
+
|
|
30
|
+
varying vec3 vNormal;
|
|
31
|
+
varying vec3 vViewPosition;
|
|
32
|
+
varying vec3 vWorldPosition;
|
|
33
|
+
varying vec2 vUv;
|
|
34
|
+
|
|
35
|
+
void main() {
|
|
36
|
+
vec3 viewDir = normalize(vViewPosition);
|
|
37
|
+
vec3 normal = normalize(vNormal);
|
|
38
|
+
|
|
39
|
+
// Fresnel effect - edges are more visible
|
|
40
|
+
float fresnel = pow(1.0 - abs(dot(viewDir, normal)), uFresnelPower);
|
|
41
|
+
|
|
42
|
+
// Center glow: bright white in center, fading to base color at edges
|
|
43
|
+
// Center is where normal faces camera directly (low fresnel)
|
|
44
|
+
float centerGlow = (1.0 - fresnel) * uGlowIntensity;
|
|
45
|
+
|
|
46
|
+
// Environment reflection on edges
|
|
47
|
+
vec3 reflectDir = reflect(-viewDir, normal);
|
|
48
|
+
vec3 envColor = textureCube(uEnvMap, reflectDir).rgb;
|
|
49
|
+
|
|
50
|
+
// Base color with reflection at edges
|
|
51
|
+
vec3 baseColor = uColor;
|
|
52
|
+
vec3 edgeColor = mix(baseColor, envColor, uReflectivity * fresnel);
|
|
53
|
+
|
|
54
|
+
// Blend: white glow in center, colored edges
|
|
55
|
+
vec3 finalColor = mix(edgeColor, uGlowColor, centerGlow);
|
|
56
|
+
|
|
57
|
+
// Add rim highlight for glass effect
|
|
58
|
+
float rimLight = pow(fresnel, 4.0) * 0.4;
|
|
59
|
+
finalColor += vec3(rimLight) * uColor;
|
|
60
|
+
|
|
61
|
+
// Opacity: fully opaque with glow effect visible
|
|
62
|
+
float opacity = 0.85 + fresnel * 0.15;
|
|
63
|
+
|
|
64
|
+
gl_FragColor = vec4(finalColor, opacity);
|
|
65
|
+
}
|
|
66
|
+
`,transparent:!0,side:g.FrontSide,depthWrite:!0,blending:g.NormalBlending});return this.materialCache.set(t,i),i.clone()}createEdgeMaterial(e=6710886,o=.4){return new g.LineBasicMaterial({color:e,transparent:!0,opacity:o,linewidth:1})}createHighlightedEdgeMaterial(){return new g.LineBasicMaterial({color:16750950,transparent:!1,opacity:1,linewidth:2})}createLabelMaterial(e,o=24){const t=document.createElement("canvas"),n=t.getContext("2d");n.font=`600 ${o}px Inter, -apple-system, sans-serif`;const a=n.measureText(e).width;t.width=Math.max(128,a+24),t.height=o+20,n.clearRect(0,0,t.width,t.height),n.font=`600 ${o}px Inter, -apple-system, sans-serif`,n.textAlign="center",n.textBaseline="middle",n.shadowColor="rgba(0, 0, 0, 0.8)",n.shadowBlur=4,n.shadowOffsetX=1,n.shadowOffsetY=1,n.fillStyle="rgba(255, 255, 255, 0.95)",n.fillText(e,t.width/2,t.height/2);const l=new g.CanvasTexture(t);return l.needsUpdate=!0,new g.SpriteMaterial({map:l,transparent:!0,depthTest:!1,depthWrite:!1})}getEnvMap(){return this.envMap}dispose(){this.materialCache.forEach(e=>e.dispose()),this.materialCache.clear(),this.envMap&&this.envMap.dispose()}}class Ue{constructor(e,o=2,t=[32,16,8]){r(this,"materialFactory");r(this,"geometryCache",new Map);r(this,"nodeRadius");r(this,"lodSegments");this.materialFactory=e,this.nodeRadius=o,this.lodSegments=t,this.initGeometryCache()}initGeometryCache(){this.lodSegments.forEach((e,o)=>{const t=`lod-${o}`;this.geometryCache.set(t,new g.SphereGeometry(this.nodeRadius,e,e))})}getGeometry(e){const o=`lod-${e}`;return this.geometryCache.has(o)?this.geometryCache.get(o):this.geometryCache.get("lod-0")}createNode(e,o=0){const t=new g.Group;t.name=`node-${e.id}`,t.userData={nodeId:e.id,nodeData:e};const n=this.getGeometry(o),i=this.materialFactory.createGlassMaterial(e.color??4886754),a=new g.Mesh(n,i);a.castShadow=!0,a.receiveShadow=!0,t.add(a);const l=this.materialFactory.createLabelMaterial(e.label),p=new g.Sprite(l);return p.position.y=this.nodeRadius+1.5,p.scale.set(4,1,1),t.add(p),e.position&&t.position.set(e.position.x,e.position.y,e.position.z),{group:t,sphere:a,label:p,lodLevel:o}}updateNodeLOD(e,o){if(e.lodLevel===o)return;const t=this.getGeometry(o);e.sphere.geometry=t,e.lodLevel=o}updateNodeColor(e,o){e.sphere.material instanceof g.Material&&e.sphere.material.dispose(),e.sphere.material=this.materialFactory.createGlassMaterial(o)}updateNodeLabel(e,o){e.label.material instanceof g.SpriteMaterial&&(e.label.material.map&&e.label.material.map.dispose(),e.label.material.dispose()),e.label.material=this.materialFactory.createLabelMaterial(o)}disposeNode(e){e.sphere.material instanceof g.Material&&e.sphere.material.dispose(),e.label.material instanceof g.SpriteMaterial&&(e.label.material.map&&e.label.material.map.dispose(),e.label.material.dispose())}dispose(){this.geometryCache.forEach(e=>e.dispose()),this.geometryCache.clear()}}class Be{constructor(e,o=10066329,t=.5){r(this,"materialFactory");r(this,"edgeColor");r(this,"edgeOpacity");r(this,"defaultMaterial",null);r(this,"highlightMaterial",null);this.materialFactory=e,this.edgeColor=o,this.edgeOpacity=t}getDefaultMaterial(){return this.defaultMaterial||(this.defaultMaterial=this.materialFactory.createEdgeMaterial(this.edgeColor,this.edgeOpacity)),this.defaultMaterial}getHighlightMaterial(){return this.highlightMaterial||(this.highlightMaterial=this.materialFactory.createHighlightedEdgeMaterial()),this.highlightMaterial}createEdge(e,o,t,n,i){const a=new g.BufferGeometry,l=new Float32Array([n.x,n.y,n.z,i.x,i.y,i.z]);a.setAttribute("position",new g.BufferAttribute(l,3));const p=this.getDefaultMaterial().clone(),u=new g.Line(a,p);return u.name=`edge-${e.source}-${e.target}`,u.userData={source:e.source,target:e.target,edge:e,sourceNode:o,targetNode:t},u.frustumCulled=!0,{line:u,source:e.source,target:e.target}}highlightEdge(e){e.line.material instanceof g.Material&&e.line.material.dispose(),e.line.material=this.getHighlightMaterial().clone()}unhighlightEdge(e){e.line.material instanceof g.Material&&e.line.material.dispose(),e.line.material=this.getDefaultMaterial().clone()}updateEdgePositions(e,o,t){const n=e.line.geometry.attributes.position,i=n.array;i[0]=o.x,i[1]=o.y,i[2]=o.z,i[3]=t.x,i[4]=t.y,i[5]=t.z,n.needsUpdate=!0,e.line.geometry.computeBoundingSphere()}disposeEdge(e){e.line.geometry.dispose(),e.line.material instanceof g.Material&&e.line.material.dispose()}dispose(){this.defaultMaterial&&this.defaultMaterial.dispose(),this.highlightMaterial&&this.highlightMaterial.dispose()}}class _e{constructor(e,o=[50,100,200],t=!0){r(this,"camera");r(this,"lodDistances");r(this,"enabled");this.camera=e,this.lodDistances=o,this.enabled=t}getLODLevel(e){if(!this.enabled)return j.HIGH;const o=e.x-this.camera.position.x,t=e.y-this.camera.position.y,n=e.z-this.camera.position.z,i=Math.sqrt(o*o+t*t+n*n);return i<this.lodDistances[0]?j.HIGH:i<this.lodDistances[1]?j.MEDIUM:j.LOW}shouldRenderNode(e,o=500){const t=e.x-this.camera.position.x,n=e.y-this.camera.position.y,i=e.z-this.camera.position.z;return Math.sqrt(t*t+n*n+i*i)<o}setLODDistances(e){this.lodDistances=e}setEnabled(e){this.enabled=e}}class Xe{constructor(e,o=!0){r(this,"camera");r(this,"frustum");r(this,"projScreenMatrix");r(this,"enabled");this.camera=e,this.frustum=new g.Frustum,this.projScreenMatrix=new g.Matrix4,this.enabled=o}update(){this.projScreenMatrix.multiplyMatrices(this.camera.projectionMatrix,this.camera.matrixWorldInverse),this.frustum.setFromProjectionMatrix(this.projScreenMatrix)}isPointVisible(e){if(!this.enabled)return!0;const o=new g.Vector3(e.x,e.y,e.z);return this.frustum.containsPoint(o)}isSphereVisible(e,o){if(!this.enabled)return!0;const t=new g.Sphere(new g.Vector3(e.x,e.y,e.z),o);return this.frustum.intersectsSphere(t)}isLineVisible(e,o){if(!this.enabled)return!0;const t=new g.Vector3(e.x,e.y,e.z),n=new g.Vector3(o.x,o.y,o.z);if(this.frustum.containsPoint(t)||this.frustum.containsPoint(n))return!0;const i=new g.Vector3((e.x+o.x)/2,(e.y+o.y)/2,(e.z+o.z)/2),a=i.distanceTo(t),l=new g.Sphere(i,a);return this.frustum.intersectsSphere(l)}setEnabled(e){this.enabled=e}}class qe{constructor(e,o){r(this,"sceneManager");r(this,"raycaster");r(this,"mouse");r(this,"container");r(this,"onNodeClick",null);r(this,"onNodeHover",null);r(this,"onEdgeHover",null);r(this,"hoveredNodeId",null);r(this,"hoveredEdgeKey",null);r(this,"nodeObjects",[]);r(this,"edgeObjects",[]);this.sceneManager=e,this.container=o,this.raycaster=new g.Raycaster,this.raycaster.params.Line={threshold:.5},this.mouse=new g.Vector2,this.handleClick=this.handleClick.bind(this),this.handleMouseMove=this.handleMouseMove.bind(this),o.addEventListener("click",this.handleClick),o.addEventListener("mousemove",this.handleMouseMove)}setNodeObjects(e){this.nodeObjects=e}setEdgeObjects(e){this.edgeObjects=e}setClickCallback(e){this.onNodeClick=e}setHoverCallback(e){this.onNodeHover=e}setEdgeHoverCallback(e){this.onEdgeHover=e}handleClick(e){const o=this.getIntersectedNode(e);o&&this.onNodeClick&&this.onNodeClick(o)}handleMouseMove(e){const o=this.getIntersectedNode(e),t=(o==null?void 0:o.id)??null;if(t!==this.hoveredNodeId&&(this.hoveredNodeId=t,this.onNodeHover&&this.onNodeHover(o)),o){this.hoveredEdgeKey!==null&&this.onEdgeHover&&(this.hoveredEdgeKey=null,this.onEdgeHover(null)),this.container.style.cursor="pointer";return}const n=this.getIntersectedEdge(e),i=n?`${n.edge.source}-${n.edge.target}`:null;i!==this.hoveredEdgeKey&&(this.hoveredEdgeKey=i,this.onEdgeHover&&this.onEdgeHover(n)),this.container.style.cursor=n?"pointer":"default"}getIntersectedNode(e){var n;const o=this.container.getBoundingClientRect();this.mouse.x=(e.clientX-o.left)/o.width*2-1,this.mouse.y=-((e.clientY-o.top)/o.height)*2+1,this.raycaster.setFromCamera(this.mouse,this.sceneManager.camera);const t=this.raycaster.intersectObjects(this.nodeObjects,!0);if(t.length>0){let i=t[0].object;for(;i;){if((n=i.userData)!=null&&n.nodeData)return i.userData.nodeData;i=i.parent}}return null}getIntersectedEdge(e){const o=this.container.getBoundingClientRect();this.mouse.x=(e.clientX-o.left)/o.width*2-1,this.mouse.y=-((e.clientY-o.top)/o.height)*2+1,this.raycaster.setFromCamera(this.mouse,this.sceneManager.camera);const t=this.raycaster.intersectObjects(this.edgeObjects,!1);if(t.length>0){const n=t[0].object,i=n.userData;if(i!=null&&i.edge&&(i!=null&&i.sourceNode)&&(i!=null&&i.targetNode))return{edge:i.edge,sourceNode:i.sourceNode,targetNode:i.targetNode,edgeLine:n}}return null}getIntersectedNodeId(e,o){var i;const t=this.container.getBoundingClientRect();this.mouse.x=(e-t.left)/t.width*2-1,this.mouse.y=-((o-t.top)/t.height)*2+1,this.raycaster.setFromCamera(this.mouse,this.sceneManager.camera);const n=this.raycaster.intersectObjects(this.nodeObjects,!0);if(n.length>0){let a=n[0].object;for(;a;){if((i=a.userData)!=null&&i.nodeId)return a.userData.nodeId;a=a.parent}}return null}dispose(){this.container.removeEventListener("click",this.handleClick),this.container.removeEventListener("mousemove",this.handleMouseMove)}}class We{constructor(e){r(this,"container");r(this,"panel",null);r(this,"currentNodeId",null);r(this,"visible",!1);r(this,"panelTemplate",null);r(this,"panelStyles",{});r(this,"onExpand",null);this.container=e,this.createPanel()}createPanel(){this.panel=document.createElement("div"),this.panel.className="force-graph-panel",this.panel.style.cssText=`
|
|
67
|
+
position: absolute;
|
|
68
|
+
right: 20px;
|
|
69
|
+
top: 50%;
|
|
70
|
+
transform: translateY(-50%);
|
|
71
|
+
width: 280px;
|
|
72
|
+
max-height: 80vh;
|
|
73
|
+
overflow-y: auto;
|
|
74
|
+
background: rgba(15, 15, 25, 0.85);
|
|
75
|
+
backdrop-filter: blur(20px);
|
|
76
|
+
-webkit-backdrop-filter: blur(20px);
|
|
77
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
78
|
+
border-radius: 16px;
|
|
79
|
+
padding: 24px;
|
|
80
|
+
color: white;
|
|
81
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
82
|
+
box-shadow:
|
|
83
|
+
0 8px 32px rgba(0, 0, 0, 0.4),
|
|
84
|
+
inset 0 0 0 1px rgba(255, 255, 255, 0.05);
|
|
85
|
+
z-index: 1000;
|
|
86
|
+
opacity: 0;
|
|
87
|
+
pointer-events: none;
|
|
88
|
+
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
89
|
+
`,Object.entries(this.panelStyles).forEach(([e,o])=>{this.panel.style.setProperty(e,o)}),this.container.appendChild(this.panel)}setPanelTemplate(e){this.panelTemplate=e}setPanelStyles(e){this.panelStyles=e,this.panel&&Object.entries(e).forEach(([o,t])=>{this.panel.style.setProperty(o,t)})}setExpandCallback(e){this.onExpand=e}show(e,o){if(!this.panel)return;this.currentNodeId=e.id;let t;this.panelTemplate?t=this.panelTemplate(e,o):t=this.generateDefaultContent(e,o),this.panel.innerHTML=t;const n=this.panel.querySelector('[data-action="expand"]'),i=this.panel.querySelector("[data-depth-select]");n&&this.onExpand&&n.addEventListener("click",()=>{if(this.currentNodeId){const l=i?parseInt(i.value,10):1;this.onExpand(this.currentNodeId,l)}});const a=this.panel.querySelector('[data-action="close"]');a&&a.addEventListener("click",()=>{this.hide()}),this.panel.style.opacity="1",this.panel.style.pointerEvents="auto",this.panel.style.transform="translateY(-50%) translateX(0)",this.visible=!0}generateDefaultContent(e,o){const t=e.color?`#${e.color.toString(16).padStart(6,"0")}`:"#4A90E2";return`
|
|
90
|
+
<style>
|
|
91
|
+
.force-graph-panel h2 {
|
|
92
|
+
margin: 0 0 16px 0;
|
|
93
|
+
font-size: 20px;
|
|
94
|
+
font-weight: 600;
|
|
95
|
+
letter-spacing: -0.5px;
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
gap: 10px;
|
|
99
|
+
}
|
|
100
|
+
.force-graph-panel .color-dot {
|
|
101
|
+
width: 12px;
|
|
102
|
+
height: 12px;
|
|
103
|
+
border-radius: 50%;
|
|
104
|
+
background: ${t};
|
|
105
|
+
box-shadow: 0 0 10px ${t}80;
|
|
106
|
+
}
|
|
107
|
+
.force-graph-panel .info-row {
|
|
108
|
+
display: flex;
|
|
109
|
+
justify-content: space-between;
|
|
110
|
+
padding: 8px 0;
|
|
111
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
112
|
+
font-size: 13px;
|
|
113
|
+
}
|
|
114
|
+
.force-graph-panel .info-row:last-child {
|
|
115
|
+
border-bottom: none;
|
|
116
|
+
}
|
|
117
|
+
.force-graph-panel .info-label {
|
|
118
|
+
color: rgba(255, 255, 255, 0.6);
|
|
119
|
+
}
|
|
120
|
+
.force-graph-panel .info-value {
|
|
121
|
+
color: rgba(255, 255, 255, 0.95);
|
|
122
|
+
font-weight: 500;
|
|
123
|
+
}
|
|
124
|
+
.force-graph-panel .neighbors-section {
|
|
125
|
+
margin-top: 16px;
|
|
126
|
+
}
|
|
127
|
+
.force-graph-panel .neighbors-title {
|
|
128
|
+
font-size: 12px;
|
|
129
|
+
text-transform: uppercase;
|
|
130
|
+
letter-spacing: 1px;
|
|
131
|
+
color: rgba(255, 255, 255, 0.5);
|
|
132
|
+
margin-bottom: 8px;
|
|
133
|
+
}
|
|
134
|
+
.force-graph-panel .neighbor-chip {
|
|
135
|
+
display: inline-block;
|
|
136
|
+
padding: 4px 10px;
|
|
137
|
+
margin: 2px;
|
|
138
|
+
background: rgba(255, 255, 255, 0.1);
|
|
139
|
+
border-radius: 12px;
|
|
140
|
+
font-size: 12px;
|
|
141
|
+
}
|
|
142
|
+
.force-graph-panel .depth-selector {
|
|
143
|
+
margin-top: 16px;
|
|
144
|
+
margin-bottom: 8px;
|
|
145
|
+
}
|
|
146
|
+
.force-graph-panel .depth-label {
|
|
147
|
+
font-size: 12px;
|
|
148
|
+
color: rgba(255, 255, 255, 0.6);
|
|
149
|
+
margin-bottom: 6px;
|
|
150
|
+
text-transform: uppercase;
|
|
151
|
+
letter-spacing: 0.5px;
|
|
152
|
+
}
|
|
153
|
+
.force-graph-panel select {
|
|
154
|
+
width: 100%;
|
|
155
|
+
padding: 8px 12px;
|
|
156
|
+
background: rgba(255, 255, 255, 0.1);
|
|
157
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
158
|
+
border-radius: 6px;
|
|
159
|
+
color: white;
|
|
160
|
+
font-size: 13px;
|
|
161
|
+
cursor: pointer;
|
|
162
|
+
outline: none;
|
|
163
|
+
transition: all 0.2s ease;
|
|
164
|
+
}
|
|
165
|
+
.force-graph-panel select:hover {
|
|
166
|
+
background: rgba(255, 255, 255, 0.15);
|
|
167
|
+
}
|
|
168
|
+
.force-graph-panel select:focus {
|
|
169
|
+
border-color: rgba(96, 165, 250, 0.5);
|
|
170
|
+
}
|
|
171
|
+
.force-graph-panel .btn-row {
|
|
172
|
+
display: flex;
|
|
173
|
+
gap: 8px;
|
|
174
|
+
margin-top: 20px;
|
|
175
|
+
}
|
|
176
|
+
.force-graph-panel button {
|
|
177
|
+
flex: 1;
|
|
178
|
+
padding: 10px 16px;
|
|
179
|
+
border: none;
|
|
180
|
+
border-radius: 8px;
|
|
181
|
+
font-size: 13px;
|
|
182
|
+
font-weight: 500;
|
|
183
|
+
cursor: pointer;
|
|
184
|
+
transition: all 0.2s ease;
|
|
185
|
+
}
|
|
186
|
+
.force-graph-panel .btn-expand {
|
|
187
|
+
background: linear-gradient(135deg, #60a5fa, #a78bfa);
|
|
188
|
+
color: white;
|
|
189
|
+
}
|
|
190
|
+
.force-graph-panel .btn-expand:hover {
|
|
191
|
+
transform: translateY(-1px);
|
|
192
|
+
box-shadow: 0 4px 12px rgba(96, 165, 250, 0.4);
|
|
193
|
+
}
|
|
194
|
+
.force-graph-panel .btn-close {
|
|
195
|
+
background: rgba(255, 255, 255, 0.1);
|
|
196
|
+
color: rgba(255, 255, 255, 0.8);
|
|
197
|
+
}
|
|
198
|
+
.force-graph-panel .btn-close:hover {
|
|
199
|
+
background: rgba(255, 255, 255, 0.15);
|
|
200
|
+
}
|
|
201
|
+
</style>
|
|
202
|
+
|
|
203
|
+
<h2>
|
|
204
|
+
<span class="color-dot"></span>
|
|
205
|
+
${this.escapeHtml(e.label)}
|
|
206
|
+
</h2>
|
|
207
|
+
|
|
208
|
+
<div class="info-row">
|
|
209
|
+
<span class="info-label">ID</span>
|
|
210
|
+
<span class="info-value">${this.escapeHtml(e.id)}</span>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<div class="info-row">
|
|
214
|
+
<span class="info-label">Connections</span>
|
|
215
|
+
<span class="info-value">${o.length}</span>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
${o.length>0?`
|
|
219
|
+
<div class="neighbors-section">
|
|
220
|
+
<div class="neighbors-title">Connected To</div>
|
|
221
|
+
${o.slice(0,5).map(n=>`<span class="neighbor-chip">${this.escapeHtml(n.label)}</span>`).join("")}
|
|
222
|
+
${o.length>5?`<span class="neighbor-chip">+${o.length-5} more</span>`:""}
|
|
223
|
+
</div>
|
|
224
|
+
`:""}
|
|
225
|
+
|
|
226
|
+
<div class="depth-selector">
|
|
227
|
+
<div class="depth-label">Expansion Depth</div>
|
|
228
|
+
<select data-depth-select>
|
|
229
|
+
<option value="1">1 Level</option>
|
|
230
|
+
<option value="2">2 Levels</option>
|
|
231
|
+
<option value="3" selected>3 Levels</option>
|
|
232
|
+
</select>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
<div class="btn-row">
|
|
237
|
+
<button class="btn-expand" data-action="expand">Expand</button>
|
|
238
|
+
<button class="btn-close" data-action="close">Close</button>
|
|
239
|
+
</div>
|
|
240
|
+
`}escapeHtml(e){const o=document.createElement("div");return o.textContent=e,o.innerHTML}hide(){this.panel&&(this.panel.style.opacity="0",this.panel.style.pointerEvents="none",this.panel.style.transform="translateY(-50%) translateX(20px)",this.visible=!1,this.currentNodeId=null)}isVisible(){return this.visible}getCurrentNodeId(){return this.currentNodeId}dispose(){this.panel&&this.panel.parentNode&&this.panel.parentNode.removeChild(this.panel),this.panel=null}}class Ze{constructor(){r(this,"tooltip",null);r(this,"visible",!1);this.createTooltip(),this.setupMouseTracking()}createTooltip(){this.tooltip=document.createElement("div"),this.tooltip.className="force-graph-edge-tooltip",this.tooltip.style.cssText=`
|
|
241
|
+
position: fixed;
|
|
242
|
+
padding: 10px 14px;
|
|
243
|
+
background: rgba(30, 30, 30, 0.95);
|
|
244
|
+
backdrop-filter: blur(10px);
|
|
245
|
+
-webkit-backdrop-filter: blur(10px);
|
|
246
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
247
|
+
border-radius: 8px;
|
|
248
|
+
color: white;
|
|
249
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
250
|
+
font-size: 13px;
|
|
251
|
+
pointer-events: none;
|
|
252
|
+
z-index: 10000;
|
|
253
|
+
opacity: 0;
|
|
254
|
+
transform: translateY(5px);
|
|
255
|
+
transition: opacity 0.15s ease, transform 0.15s ease;
|
|
256
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
|
257
|
+
max-width: 400px;
|
|
258
|
+
white-space: normal;
|
|
259
|
+
word-wrap: break-word;
|
|
260
|
+
overflow-wrap: break-word;
|
|
261
|
+
`,document.body.appendChild(this.tooltip)}setupMouseTracking(){document.addEventListener("mousemove",e=>{this.visible&&this.tooltip&&this.positionTooltip(e.clientX,e.clientY)})}positionTooltip(e,o){if(!this.tooltip)return;const t=this.tooltip.getBoundingClientRect(),n=window.innerWidth,i=window.innerHeight;let a=e+15,l=o+15;a+t.width>n-10&&(a=e-t.width-15),l+t.height>i-10&&(l=o-t.height-15),a<10&&(a=10),l<10&&(l=10),this.tooltip.style.left=`${a}px`,this.tooltip.style.top=`${l}px`}show(e,o,t,n,i){if(!this.tooltip)return;const a=e.relationship||"connected to";this.tooltip.innerHTML=`
|
|
262
|
+
<div style="display: flex; flex-direction: column; gap: 4px;">
|
|
263
|
+
<div style="display: flex; align-items: center; gap: 6px; flex-wrap: wrap;">
|
|
264
|
+
<span style="font-weight: 600; color: #ff9966;">${this.escapeHtml(o.label)}</span>
|
|
265
|
+
</div>
|
|
266
|
+
<div style="color: rgba(255, 255, 255, 0.6); font-style: italic; font-size: 12px; padding-left: 8px;">
|
|
267
|
+
↳ ${this.escapeHtml(a)}
|
|
268
|
+
</div>
|
|
269
|
+
<div style="display: flex; align-items: center; gap: 6px; flex-wrap: wrap;">
|
|
270
|
+
<span style="font-weight: 600; color: #ff9966;">${this.escapeHtml(t.label)}</span>
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
`,this.positionTooltip(n,i),this.tooltip.style.opacity="1",this.tooltip.style.transform="translateY(0)",this.visible=!0}updatePosition(e,o){this.visible&&this.positionTooltip(e,o)}hide(){this.tooltip&&(this.tooltip.style.opacity="0",this.tooltip.style.transform="translateY(5px)",this.visible=!1)}isVisible(){return this.visible}escapeHtml(e){const o=document.createElement("div");return o.textContent=e,o.innerHTML}dispose(){this.tooltip&&this.tooltip.parentNode&&this.tooltip.parentNode.removeChild(this.tooltip),this.tooltip=null}}class Qe{constructor(e,o={}){r(this,"options");r(this,"container");r(this,"sceneManager");r(this,"nodeManager");r(this,"edgeManager");r(this,"graphEngine");r(this,"rendererManager");r(this,"materialFactory");r(this,"nodeFactory");r(this,"edgeFactory");r(this,"lodManager");r(this,"frustumCuller");r(this,"raycasterManager");r(this,"panelManager");r(this,"edgeTooltipManager");r(this,"eventCallbacks",new Map);r(this,"initialized",!1);r(this,"devControls",null);this.options={...P,...o},this.container=De(e),this.materialFactory=new Ve,this.nodeFactory=new Ue(this.materialFactory,this.options.nodeRadius??P.nodeRadius,this.options.lodSegments??P.lodSegments),this.edgeFactory=new Be(this.materialFactory,this.options.edgeColor??P.edgeColor,this.options.edgeOpacity??P.edgeOpacity),this.sceneManager=new Re(this.container,this.options),this.lodManager=new _e(this.sceneManager.camera,this.options.lodDistances??P.lodDistances,this.options.enableLOD??P.enableLOD),this.frustumCuller=new Xe(this.sceneManager.camera,this.options.enableEdgeCulling??P.enableEdgeCulling),this.nodeManager=new Ke(this.sceneManager,this.nodeFactory),this.edgeManager=new Ge(this.sceneManager,this.nodeManager,this.edgeFactory),this.graphEngine=new pe(this.nodeManager.getAllNodes(),this.edgeManager.getAllEdges(),{repulsionStrength:this.options.repulsionStrength,attractionStrength:this.options.attractionStrength,damping:this.options.damping,useBarnesHut:this.options.useBarnesHut,barnesHutTheta:this.options.barnesHutTheta}),this.rendererManager=new Ye(this.sceneManager,()=>this.onSimulate(),()=>this.onRender(),this.options.targetFPS??P.targetFPS),this.raycasterManager=new qe(this.sceneManager,this.container),this.panelManager=new We(this.container),this.edgeTooltipManager=new Ze,this.setupCallbacks(),this.rendererManager.start(),this.initialized=!0,this.emit("ready")}setupCallbacks(){this.raycasterManager.setClickCallback(e=>{this.onNodeClick(e)}),this.options.onNodeHover&&this.raycasterManager.setHoverCallback(this.options.onNodeHover),this.panelManager.setExpandCallback((e,o)=>{this.expandNode(e,o)}),this.options.panelTemplate&&this.panelManager.setPanelTemplate(this.options.panelTemplate),this.options.panelStyles&&this.panelManager.setPanelStyles(this.options.panelStyles),this.raycasterManager.setEdgeHoverCallback(e=>{this.onEdgeHover(e)})}onEdgeHover(e){if(e){this.edgeManager.highlightEdge(e.edge.source,e.edge.target);const o=this.container.getBoundingClientRect(),t=o.left+o.width/2,n=o.top+o.height/2;this.edgeTooltipManager.show(e.edge,e.sourceNode,e.targetNode,t,n),this.options.onEdgeHover&&this.options.onEdgeHover(e.edge,e.sourceNode,e.targetNode),this.emit("edgeHover",e.edge,e.sourceNode,e.targetNode)}else this.edgeManager.unhighlightCurrentEdge(),this.edgeTooltipManager.hide(),this.options.onEdgeHover&&this.options.onEdgeHover(null,null,null),this.emit("edgeHover",null,null,null)}onNodeClick(e){const t=this.edgeManager.getNeighborIds(e.id).map(n=>this.nodeManager.getNode(n)).filter(n=>n!==void 0);this.options.showPanel!==!1&&this.panelManager.show(e,t),this.options.onNodeClick&&this.options.onNodeClick(e),this.emit("nodeClick",e)}onSimulate(){this.graphEngine.simulate();for(const[e,o]of this.nodeManager.getAllNodes())if(this.nodeManager.updateNodePosition(e,o.position),this.options.enableLOD){const t=this.lodManager.getLODLevel(o.position);this.nodeManager.updateNodeLOD(e,t)}this.edgeManager.updateEdgePositions()}onRender(){this.frustumCuller.update(),this.raycasterManager.setNodeObjects(this.nodeManager.getAllNodeObjects()),this.raycasterManager.setEdgeObjects(this.edgeManager.getAllEdgeLines())}setData(e){if(this.edgeManager.clear(),this.nodeManager.clear(),e.nodes&&Array.isArray(e.nodes))for(const o of e.nodes)this.addNode(o);if(e.edges&&Array.isArray(e.edges))for(const o of e.edges)this.addEdge(o);this.graphEngine=new pe(this.nodeManager.getAllNodes(),this.edgeManager.getAllEdges(),{repulsionStrength:this.options.repulsionStrength,attractionStrength:this.options.attractionStrength,damping:this.options.damping,useBarnesHut:this.options.useBarnesHut,barnesHutTheta:this.options.barnesHutTheta}),this.graphEngine.restart()}addNode(e){if(!Q(e))return!1;const o=this.nodeManager.addNode(e);return o&&(this.graphEngine.restart(),this.options.onNodeAdd&&this.options.onNodeAdd(e),this.emit("nodeAdd",e)),o}removeNode(e){if(!Ie(e))return!1;this.edgeManager.removeEdgesForNode(e);const o=this.nodeManager.removeNode(e);return o&&(this.graphEngine.restart(),this.options.onNodeRemove&&this.options.onNodeRemove(e),this.emit("nodeRemove",e),this.panelManager.getCurrentNodeId()===e&&this.panelManager.hide()),o}updateNode(e,o){return this.nodeManager.updateNode(e,o)}addEdge(e){if(!J(e))return!1;const o=this.edgeManager.addEdge(e);return o&&(this.graphEngine.setEdges(this.edgeManager.getAllEdges()),this.graphEngine.restart(),this.options.onEdgeAdd&&this.options.onEdgeAdd(e),this.emit("edgeAdd",e)),o}removeEdge(e,o){const t=this.edgeManager.removeEdge(e,o);return t&&(this.graphEngine.setEdges(this.edgeManager.getAllEdges()),this.options.onEdgeRemove&&this.options.onEdgeRemove({source:e,target:o}),this.emit("edgeRemove",{source:e,target:o})),t}async expandNode(e,o=1,t){const n=t??this.options.onExpand;if(!n)return console.warn("[ForceGraph3D] No expand callback provided"),!1;try{const i=await n(e,o);if(i.nodes&&Array.isArray(i.nodes))for(const a of i.nodes)this.addNode(a);if(i.edges&&Array.isArray(i.edges))for(const a of i.edges)this.addEdge(a);return this.panelManager.hide(),this.emit("expand",e,i),!0}catch(i){return console.error("[ForceGraph3D] Error expanding node:",i),!1}}getNode(e){return this.nodeManager.getNode(e)}getNeighbors(e){return this.edgeManager.getNeighborIds(e).map(t=>this.nodeManager.getNode(t)).filter(t=>t!==void 0)}getNodeCount(){return this.nodeManager.getNodeCount()}getEdgeCount(){return this.edgeManager.getEdgeCount()}setExpandCallback(e){this.options.onExpand=e}focusOnNode(e,o=30){const t=this.nodeManager.getNode(e);if(!t){console.warn(`[ForceGraph3D] Node "${e}" not found`);return}const n=t.position,i=this.sceneManager.camera,a=this.sceneManager.controls,l=i.position.clone().sub(a.target).normalize(),p={x:n.x+l.x*o,y:n.y+l.y*o,z:n.z+l.z*o},u={x:i.position.x,y:i.position.y,z:i.position.z},m={x:a.target.x,y:a.target.y,z:a.target.z},x=800,M=performance.now(),b=()=>{const y=performance.now()-M,w=Math.min(y/x,1),N=1-Math.pow(1-w,3);i.position.x=u.x+(p.x-u.x)*N,i.position.y=u.y+(p.y-u.y)*N,i.position.z=u.z+(p.z-u.z)*N,a.target.x=m.x+(n.x-m.x)*N,a.target.y=m.y+(n.y-m.y)*N,a.target.z=m.z+(n.z-m.z)*N,a.update(),w<1&&requestAnimationFrame(b)};b()}showNodePanel(e){const o=this.nodeManager.getNode(e);if(!o){console.warn(`[ForceGraph3D] Node "${e}" not found`);return}const t=this.getNeighbors(e);this.panelManager.show(o,t)}searchNodes(e){if(!e||e.trim()==="")return[];const o=e.toLowerCase().trim(),t=this.nodeManager.getAllNodes(),n=[];return t.forEach(i=>{var u,m,x;const a=(u=i.label)==null?void 0:u.toLowerCase().includes(o),l=(m=i.id)==null?void 0:m.toLowerCase().includes(o),p=(x=i.type)==null?void 0:x.toLowerCase().includes(o);(a||l||p)&&n.push(i)}),n}searchEdges(e){var i;if(!e||e.trim()==="")return[];const o=e.toLowerCase().trim(),t=this.edgeManager.getAllEdges(),n=[];for(const a of t)if((i=a.relationship)==null?void 0:i.toLowerCase().includes(o)){const p=this.nodeManager.getNode(a.source),u=this.nodeManager.getNode(a.target);p&&u&&n.push({edge:a,sourceNode:p,targetNode:u})}return n}getAllNodes(){const e=this.nodeManager.getAllNodes();return Array.from(e.values())}getAllEdges(){return this.edgeManager.getAllEdges()}isInitialized(){return this.initialized}on(e,o){this.eventCallbacks.has(e)||this.eventCallbacks.set(e,[]),this.eventCallbacks.get(e).push(o)}emit(e,...o){const t=this.eventCallbacks.get(e);t&&t.forEach(n=>n(...o))}setPhysicsParams(e){this.graphEngine.setPhysicsParams(e),this.graphEngine.restart()}createDevControls(){this.devControls=document.createElement("div"),this.devControls.className="force-graph-dev-controls",this.devControls.innerHTML=`
|
|
274
|
+
<style>
|
|
275
|
+
.force-graph-dev-controls {
|
|
276
|
+
position: absolute;
|
|
277
|
+
top: 20px;
|
|
278
|
+
right: 20px;
|
|
279
|
+
background: rgba(0, 0, 0, 0.8);
|
|
280
|
+
backdrop-filter: blur(10px);
|
|
281
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
282
|
+
border-radius: 12px;
|
|
283
|
+
padding: 16px;
|
|
284
|
+
color: white;
|
|
285
|
+
font-family: 'Inter', -apple-system, sans-serif;
|
|
286
|
+
font-size: 12px;
|
|
287
|
+
z-index: 1001;
|
|
288
|
+
min-width: 220px;
|
|
289
|
+
}
|
|
290
|
+
.force-graph-dev-controls h3 {
|
|
291
|
+
margin: 0 0 12px 0;
|
|
292
|
+
font-size: 14px;
|
|
293
|
+
color: #60a5fa;
|
|
294
|
+
}
|
|
295
|
+
.force-graph-dev-controls .control-group {
|
|
296
|
+
margin-bottom: 12px;
|
|
297
|
+
}
|
|
298
|
+
.force-graph-dev-controls label {
|
|
299
|
+
display: block;
|
|
300
|
+
margin-bottom: 4px;
|
|
301
|
+
color: rgba(255, 255, 255, 0.7);
|
|
302
|
+
}
|
|
303
|
+
.force-graph-dev-controls input[type="range"] {
|
|
304
|
+
width: 100%;
|
|
305
|
+
margin-top: 4px;
|
|
306
|
+
}
|
|
307
|
+
.force-graph-dev-controls .value {
|
|
308
|
+
color: #60a5fa;
|
|
309
|
+
font-weight: 600;
|
|
310
|
+
}
|
|
311
|
+
.force-graph-dev-controls .stats {
|
|
312
|
+
margin-top: 12px;
|
|
313
|
+
padding-top: 12px;
|
|
314
|
+
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
315
|
+
}
|
|
316
|
+
</style>
|
|
317
|
+
<h3>⚙️ Dev Controls</h3>
|
|
318
|
+
<div class="control-group">
|
|
319
|
+
<label>Repulsion: <span class="value" id="dev-repulsion-val">${this.options.repulsionStrength}</span></label>
|
|
320
|
+
<input type="range" id="dev-repulsion" min="10" max="500" value="${this.options.repulsionStrength}">
|
|
321
|
+
</div>
|
|
322
|
+
<div class="control-group">
|
|
323
|
+
<label>Attraction: <span class="value" id="dev-attraction-val">${(this.options.attractionStrength??.01).toFixed(3)}</span></label>
|
|
324
|
+
<input type="range" id="dev-attraction" min="1" max="100" value="${(this.options.attractionStrength??.01)*1e3}">
|
|
325
|
+
</div>
|
|
326
|
+
<div class="control-group">
|
|
327
|
+
<label>Damping: <span class="value" id="dev-damping-val">${this.options.damping}</span></label>
|
|
328
|
+
<input type="range" id="dev-damping" min="50" max="99" value="${(this.options.damping??.9)*100}">
|
|
329
|
+
</div>
|
|
330
|
+
<div class="stats">
|
|
331
|
+
<div>Nodes: <span class="value" id="dev-node-count">0</span></div>
|
|
332
|
+
<div>Edges: <span class="value" id="dev-edge-count">0</span></div>
|
|
333
|
+
<div>FPS: <span class="value" id="dev-fps">60</span></div>
|
|
334
|
+
</div>
|
|
335
|
+
`,this.container.appendChild(this.devControls);const e=this.devControls.querySelector("#dev-repulsion"),o=this.devControls.querySelector("#dev-attraction"),t=this.devControls.querySelector("#dev-damping");e==null||e.addEventListener("input",()=>{const n=parseFloat(e.value);this.setPhysicsParams({repulsionStrength:n}),this.devControls.querySelector("#dev-repulsion-val").textContent=n.toString()}),o==null||o.addEventListener("input",()=>{const n=parseFloat(o.value)/1e3;this.setPhysicsParams({attractionStrength:n}),this.devControls.querySelector("#dev-attraction-val").textContent=n.toFixed(3)}),t==null||t.addEventListener("input",()=>{const n=parseFloat(t.value)/100;this.setPhysicsParams({damping:n}),this.devControls.querySelector("#dev-damping-val").textContent=n.toFixed(2)}),setInterval(()=>{const n=this.devControls.querySelector("#dev-node-count"),i=this.devControls.querySelector("#dev-edge-count"),a=this.devControls.querySelector("#dev-fps");n&&(n.textContent=this.getNodeCount().toString()),i&&(i.textContent=this.getEdgeCount().toString()),a&&(a.textContent=this.rendererManager.getFPS().toString())},500)}destroy(){this.rendererManager.dispose(),this.panelManager.dispose(),this.raycasterManager.dispose(),this.edgeTooltipManager.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}}const ge=["Alpha","Beta","Gamma","Delta","Epsilon","Zeta","Eta","Theta","Iota","Kappa","Lambda","Mu","Nu","Xi","Omicron","Pi","Rho","Sigma","Tau","Upsilon","Phi","Chi","Psi","Omega","Core","Hub","Node","Link","Point","Vertex"],B=["connects to","links with","relates to","depends on","references","extends","includes","partners with","collaborates with","supports"],ue=[16777215,16750950,16764057,16772829,16746564];function Je(c=30){const e=[],o=[];for(let n=0;n<c;n++){const i=n<ge.length?ge[n]:`Node ${n+1}`;e.push({id:`node-${n}`,label:i,color:ue[n%ue.length],position:{x:(Math.random()-.5)*60,y:(Math.random()-.5)*60,z:(Math.random()-.5)*60}})}for(let n=1;n<c;n++){const i=Math.floor(Math.random()*n);o.push({source:`node-${n}`,target:`node-${i}`,relationship:B[Math.floor(Math.random()*B.length)]})}const t=Math.floor(c*.5);for(let n=0;n<t;n++){const i=Math.floor(Math.random()*c);let a=Math.floor(Math.random()*c);i===a&&(a=(a+1)%c);const l=`node-${i}`,p=`node-${a}`;o.some(m=>m.source===l&&m.target===p||m.source===p&&m.target===l)||o.push({source:l,target:p,relationship:B[Math.floor(Math.random()*B.length)]})}return{nodes:e,edges:o}}function et(c=1e3){const e=[],o=[],t=Math.ceil(c/50),n=[];for(let i=0;i<t;i++)n.push({x:(Math.random()-.5)*200,y:(Math.random()-.5)*200,z:(Math.random()-.5)*200});for(let i=0;i<c;i++){const a=n[i%t];e.push({id:`node-${i}`,label:`N${i}`,position:{x:a.x+(Math.random()-.5)*40,y:a.y+(Math.random()-.5)*40,z:a.z+(Math.random()-.5)*40}})}for(let i=1;i<c;i++){const a=Math.floor(i/50)*50,l=a===0?Math.floor(Math.random()*i):a+Math.floor(Math.random()*Math.min(i-a,50));o.push({source:`node-${i}`,target:`node-${Math.min(l,i-1)}`,relationship:"links to"})}for(let i=1;i<t;i++){const a=i*50,l=(i-1)*50+Math.floor(Math.random()*50);o.push({source:`node-${a}`,target:`node-${l}`,relationship:"bridges to"})}return{nodes:e,edges:o}}C.DEFAULT_OPTIONS=P,C.ForceGraph3D=Qe,C.LODLevel=j,C.createEdgeKey=F,C.generateLargeSampleData=et,C.generateSampleData=Je,C.validateEdgeData=J,C.validateNodeData=Q,Object.defineProperty(C,Symbol.toStringTag,{value:"Module"})});
|
|
336
|
+
//# sourceMappingURL=force-3d-graph.umd.cjs.map
|