import { useEffect, useState, useRef, useCallback } from "react"; import ForceGraph3D from "react-force-graph-3d"; import graphData from "../data.json"; import SpriteText from "https://esm.sh/three-spritetext"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faInfoCircle, faExternalLinkAlt, faChevronLeft, faChevronRight } from "@fortawesome/free-solid-svg-icons"; export default function GraphComponent({ selectedCategory }) { const fgRef = useRef(); const [selectedNode, setSelectedNode] = useState(null); const [graphDataState, setGraphDataState] = useState({ nodes: [], links: [] }); const [imageIndex, setImageIndex] = useState(0); // State to track current image in the carousel // Set distance from camera orbit const distance = 400; const speed = 0.3; useEffect(() => { // Clone the original graph data to avoid modifying it directly const updatedGraphData = { nodes: graphData.nodes.map(node => ({ ...node, visible: !selectedCategory || node.category === selectedCategory // Hide nodes not in selected category })), links: graphData.links.map(link => ({ ...link, visible: (!selectedCategory || (graphData.nodes.find(n => n.id === link.source)?.category === selectedCategory && graphData.nodes.find(n => n.id === link.target)?.category === selectedCategory)) // Hide links if either connected node is hidden })) }; setGraphDataState(updatedGraphData); // Camera orbit logic const cameraOrbit = () => { let angle = 0; fgRef.current.cameraPosition({ z: distance }); // Set interval for camera orbit effect const intervalId = setInterval(() => { fgRef.current.cameraPosition({ x: distance * Math.sin(angle), z: distance * Math.cos(angle) }); angle += (Math.PI / 300) * speed; }, 10); // Clear up the interval when the component is unmounted return () => clearInterval(intervalId); }; // Call camera orbit after the graph is set const cleanupCameraOrbit = cameraOrbit(); return cleanupCameraOrbit; }, [selectedCategory]); const handleNodeClick = useCallback((node) => { setSelectedNode(node); setImageIndex(0); // Reset the image index when a new node is clicked }, []); const handleBackgroundClick = useCallback(() => { setSelectedNode(null); }, []); // Carousel navigation functions const prevImage = () => { if (selectedNode && selectedNode.image && Array.isArray(selectedNode.image)) { setImageIndex((prevIndex) => (prevIndex > 0 ? prevIndex - 1 : selectedNode.image.length - 1)); } }; const nextImage = () => { if (selectedNode && selectedNode.image && Array.isArray(selectedNode.image)) { setImageIndex((prevIndex) => (prevIndex < selectedNode.image.length - 1 ? prevIndex + 1 : 0)); } }; return (
{/* Information Window Wrapper */} {selectedNode && (
{/* Image and Description */}
{/* Image Section */} {selectedNode.image && (
{/* If it's an array of images, render carousel */} {Array.isArray(selectedNode.image) ? (
{`${selectedNode.name} {/* Left and Right Chevron Buttons */}
) : ( // Single image {selectedNode.name} )}
)} {/* Text Content */}
{/* Title */}

{selectedNode.name}

{/* Info Button fontawesome */} {selectedNode.link && ( )}
{selectedNode.location.city} | {selectedNode.location.state} | {selectedNode.location.country}
{/* Description */}

{selectedNode.description}

{/* Close button */}
)} {/* ForceGraph3D */} { const sprite = new SpriteText(node.name); sprite.color = node.color; sprite.textHeight = 8; return sprite; }} linkVisibility={link => link.visible} // Hide links properly nodeVisibility={node => node.visible} // Hide nodes properly onNodeClick={handleNodeClick} onBackgroundClick={handleBackgroundClick} />
); }