mirror of
				https://github.com/ae-utbm/sith.git
				synced 2025-10-31 00:53:08 +00:00 
			
		
		
		
	poc: 2D graph for galaxy
This commit is contained in:
		| @@ -1,138 +0,0 @@ | ||||
| import { default as ForceGraph3D } from "3d-force-graph"; | ||||
| import { forceX, forceY, forceZ } from "d3-force-3d"; | ||||
| // biome-ignore lint/style/noNamespaceImport: This is how it should be imported | ||||
| import * as Three from "three"; | ||||
| import SpriteText from "three-spritetext"; | ||||
|  | ||||
| /** | ||||
|  * @typedef GalaxyConfig | ||||
|  * @property {number} nodeId id of the current user node | ||||
|  * @property {string} dataUrl url to fetch the galaxy data from | ||||
|  **/ | ||||
|  | ||||
| /** | ||||
|  * Load the galaxy of an user | ||||
|  * @param {GalaxyConfig} config | ||||
|  **/ | ||||
| window.loadGalaxy = (config) => { | ||||
|   window.getNodeFromId = (id) => { | ||||
|     return Graph.graphData().nodes.find((n) => n.id === id); | ||||
|   }; | ||||
|  | ||||
|   window.getLinksFromNodeId = (id) => { | ||||
|     return Graph.graphData().links.filter( | ||||
|       (l) => l.source.id === id || l.target.id === id, | ||||
|     ); | ||||
|   }; | ||||
|  | ||||
|   window.focusNode = (node) => { | ||||
|     highlightNodes.clear(); | ||||
|     highlightLinks.clear(); | ||||
|  | ||||
|     hoverNode = node || null; | ||||
|     if (node) { | ||||
|       // collect neighbors and links for highlighting | ||||
|       for (const link of window.getLinksFromNodeId(node.id)) { | ||||
|         highlightLinks.add(link); | ||||
|         highlightNodes.add(link.source); | ||||
|         highlightNodes.add(link.target); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // refresh node and link display | ||||
|     Graph.nodeThreeObject(Graph.nodeThreeObject()) | ||||
|       .linkWidth(Graph.linkWidth()) | ||||
|       .linkDirectionalParticles(Graph.linkDirectionalParticles()); | ||||
|  | ||||
|     // Aim at node from outside it | ||||
|     const distance = 42; | ||||
|     const distRatio = 1 + distance / Math.hypot(node.x, node.y, node.z); | ||||
|  | ||||
|     const newPos = | ||||
|       node.x || node.y || node.z | ||||
|         ? { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio } | ||||
|         : { x: 0, y: 0, z: distance }; // special case if node is in (0,0,0) | ||||
|  | ||||
|     Graph.cameraPosition( | ||||
|       newPos, // new position | ||||
|       node, // lookAt ({ x, y, z }) | ||||
|       3000, // ms transition duration | ||||
|     ); | ||||
|   }; | ||||
|  | ||||
|   const highlightNodes = new Set(); | ||||
|   const highlightLinks = new Set(); | ||||
|   let hoverNode = null; | ||||
|  | ||||
|   const grpahDiv = document.getElementById("3d-graph"); | ||||
|   const Graph = ForceGraph3D(); | ||||
|   Graph(grpahDiv); | ||||
|   Graph.jsonUrl(config.dataUrl) | ||||
|     .width( | ||||
|       grpahDiv.parentElement.clientWidth > 1200 | ||||
|         ? 1200 | ||||
|         : grpahDiv.parentElement.clientWidth, | ||||
|     ) // Not perfect at all. JS-fu master from the future, please fix this :-) | ||||
|     .height(1000) | ||||
|     .enableNodeDrag(false) // allow easier navigation | ||||
|     .onNodeClick((node) => { | ||||
|       const camera = Graph.cameraPosition(); | ||||
|       const distance = Math.sqrt( | ||||
|         (node.x - camera.x) ** 2 + (node.y - camera.y) ** 2 + (node.z - camera.z) ** 2, | ||||
|       ); | ||||
|       if (distance < 120 || highlightNodes.has(node)) { | ||||
|         window.focusNode(node); | ||||
|       } | ||||
|     }) | ||||
|     .linkWidth((link) => (highlightLinks.has(link) ? 0.4 : 0.0)) | ||||
|     .linkColor((link) => | ||||
|       highlightLinks.has(link) ? "rgba(255,160,0,1)" : "rgba(128,255,255,0.6)", | ||||
|     ) | ||||
|     .linkVisibility((link) => highlightLinks.has(link)) | ||||
|     .nodeVisibility((node) => highlightNodes.has(node) || node.mass > 4) | ||||
|     // .linkDirectionalParticles(link => highlightLinks.has(link) ? 3 : 1) // kinda buggy for now, and slows this a bit, but would be great to help visualize lanes | ||||
|     .linkDirectionalParticleWidth(0.2) | ||||
|     .linkDirectionalParticleSpeed(-0.006) | ||||
|     .nodeThreeObject((node) => { | ||||
|       const sprite = new SpriteText(node.name); | ||||
|       sprite.material.depthWrite = false; // make sprite background transparent | ||||
|       sprite.color = highlightNodes.has(node) | ||||
|         ? node === hoverNode | ||||
|           ? "rgba(200,0,0,1)" | ||||
|           : "rgba(255,160,0,0.8)" | ||||
|         : "rgba(0,255,255,0.2)"; | ||||
|       sprite.textHeight = 2; | ||||
|       sprite.center = new Three.Vector2(1.2, 0.5); | ||||
|       return sprite; | ||||
|     }) | ||||
|     .onEngineStop(() => { | ||||
|       window.focusNode(window.getNodeFromId(config.nodeId)); | ||||
|       Graph.onEngineStop(() => { | ||||
|         /* nope */ | ||||
|       }); // don't call ourselves in a loop while moving the focus | ||||
|     }); | ||||
|  | ||||
|   // Set distance between stars | ||||
|   Graph.d3Force("link").distance((link) => link.value); | ||||
|  | ||||
|   // Set high masses nearer the center of the galaxy | ||||
|   // TODO: quick and dirty strength computation, this will need tuning. | ||||
|   Graph.d3Force( | ||||
|     "positionX", | ||||
|     forceX().strength((node) => { | ||||
|       return 1 - 1 / node.mass; | ||||
|     }), | ||||
|   ); | ||||
|   Graph.d3Force( | ||||
|     "positionY", | ||||
|     forceY().strength((node) => { | ||||
|       return 1 - 1 / node.mass; | ||||
|     }), | ||||
|   ); | ||||
|   Graph.d3Force( | ||||
|     "positionZ", | ||||
|     forceZ().strength((node) => { | ||||
|       return 1 - 1 / node.mass; | ||||
|     }), | ||||
|   ); | ||||
| }; | ||||
							
								
								
									
										108
									
								
								galaxy/static/bundled/galaxy/galaxy-index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								galaxy/static/bundled/galaxy/galaxy-index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | ||||
| import { exportToHtml } from "#core:utils/globals"; | ||||
|  | ||||
| import cytoscape from "cytoscape"; | ||||
| import d3Force, { type D3ForceLayoutOptions } from "cytoscape-d3-force"; | ||||
|  | ||||
| cytoscape.use(d3Force); | ||||
|  | ||||
| interface GalaxyConfig { | ||||
|   nodeId: number; | ||||
|   dataUrl: string; | ||||
| } | ||||
|  | ||||
| async function getGraphData(dataUrl: string) { | ||||
|   const response = await fetch(dataUrl); | ||||
|   if (!response.ok) { | ||||
|     return []; | ||||
|   } | ||||
|  | ||||
|   const content = await response.json(); | ||||
|   const nodes = content.nodes.map((node, i) => { | ||||
|     return { | ||||
|       group: "nodes", | ||||
|       data: { | ||||
|         id: node.id, | ||||
|         name: node.name, | ||||
|         mass: node.mass, | ||||
|       }, | ||||
|     }; | ||||
|   }); | ||||
|  | ||||
|   const edges = content.links.map((link) => { | ||||
|     return { | ||||
|       group: "edges", | ||||
|       data: { | ||||
|         id: `edge_${link.source}_${link.value}`, | ||||
|         source: link.source, | ||||
|         target: link.target, | ||||
|         value: link.value, | ||||
|       }, | ||||
|     }; | ||||
|   }); | ||||
|  | ||||
|   return { nodes: nodes, edges: edges }; | ||||
| } | ||||
|  | ||||
| exportToHtml("loadGalaxy", async (config: GalaxyConfig) => { | ||||
|   const graphDiv = document.getElementById("3d-graph"); | ||||
|   const elements = await getGraphData(config.dataUrl); | ||||
|   const cy = cytoscape({ | ||||
|     container: graphDiv, | ||||
|     elements: elements, | ||||
|     style: [ | ||||
|       { | ||||
|         selector: "node", | ||||
|         style: { | ||||
|           label: "data(name)", | ||||
|           "background-color": "red", | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         selector: ".focused", | ||||
|         style: { | ||||
|           "border-width": "5px", | ||||
|           "border-style": "solid", | ||||
|           "border-color": "black", | ||||
|           "target-arrow-color": "black", | ||||
|           "line-color": "black", | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         selector: "edge", | ||||
|         style: { | ||||
|           width: 0.1, | ||||
|         }, | ||||
|       }, | ||||
|     ], | ||||
|     layout: { | ||||
|       name: "d3-force", | ||||
|       animate: false, | ||||
|       fit: false, | ||||
|       ungrabifyWhileSimulating: true, | ||||
|       fixedAfterDragging: true, | ||||
|       linkId: (node) => { | ||||
|         return node.id; | ||||
|       }, | ||||
|  | ||||
|       linkDistance: (link) => { | ||||
|         return link?.value * 1000; | ||||
|       }, | ||||
|  | ||||
|       stop: () => { | ||||
|         // Disable user grabbing of nodes | ||||
|         // This has to be disabled after the simulation is done | ||||
|         // Otherwise the simulation can't move nodes | ||||
|         cy.autolock(true); | ||||
|  | ||||
|         // Center on current user node | ||||
|         for (const node of cy.nodes()) { | ||||
|           if (node.id() === `${config.nodeId}`) { | ||||
|             node.addClass("focused"); | ||||
|             cy.center(node); | ||||
|             break; | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|     } as D3ForceLayoutOptions, | ||||
|   }); | ||||
| }); | ||||
| @@ -5,14 +5,14 @@ | ||||
| {% endblock %} | ||||
|  | ||||
| {% block additional_js %} | ||||
|   <script type="module" src="{{ static('bundled/galaxy/galaxy-index.js') }}"></script> | ||||
|   <script type="module" src="{{ static('bundled/galaxy/galaxy-index.ts') }}"></script> | ||||
| {% endblock %} | ||||
|  | ||||
|  | ||||
| {% block content %} | ||||
|   {% if object.current_star %} | ||||
|     <div style="display: flex; flex-wrap: wrap;"> | ||||
|       <div id="3d-graph"></div> | ||||
|       <div style="width: 100%; height: 70vh; display: block" id="3d-graph"></div> | ||||
|  | ||||
|       <div style="margin: 1em;"> | ||||
|         <p><a onclick="window.focusNode(window.getNodeFromId({{ object.id }}))">Reset on {{ object.get_display_name() }}</a></p> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user