<div id="root"></div>
html,
body {
	margin: 0;
	padding: 0;
	overflow: hidden;
	width: 100%;
	height: 100%;
}

#root {
	width: 100%;
	height: 100vh;
}
// Sample data for visualization
const globalData = [
	{ region: "North America", value: 78, lat: 40, lng: -100, color: "#FF5733" },
	{ region: "Europe", value: 82, lat: 50, lng: 10, color: "#33FF57" },
	{ region: "Asia", value: 65, lat: 30, lng: 100, color: "#3357FF" },
	{ region: "Africa", value: 45, lat: 0, lng: 20, color: "#F3FF33" },
	{ region: "South America", value: 56, lat: -20, lng: -60, color: "#FF33F3" },
	{ region: "Oceania", value: 88, lat: -25, lng: 135, color: "#33FFF3" }
];

const connectionData = [
	{ source: "North America", target: "Europe", value: 230 },
	{ source: "Europe", target: "Asia", value: 190 },
	{ source: "North America", target: "Asia", value: 270 },
	{ source: "Europe", target: "Africa", value: 120 },
	{ source: "Asia", target: "Oceania", value: 150 },
	{ source: "North America", target: "South America", value: 180 }
];

const timeSeriesData = Array(20)
	.fill()
	.map((_, i) => ({
		date: new Date(2023, i % 12, 1),
		value: 50 + Math.sin(i * 0.5) * 20 + Math.random() * 10
	}));

const Dashboard = () => {
	const { useEffect, useRef, useState } = React;
	const globeContainerRef = useRef(null);
	const chartRef = useRef(null);
	const gaugeRef = useRef(null);
	const timeSeriesRef = useRef(null);
	const [selectedRegion, setSelectedRegion] = useState(null);
	const [showCharts, setShowCharts] = useState(false);
	const [hoveredRegion, setHoveredRegion] = useState(null);
	const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });

	// Setup Three.js Globe
	useEffect(() => {
		if (!globeContainerRef.current) return;

		// Clear any existing content
		while (globeContainerRef.current.firstChild) {
			globeContainerRef.current.removeChild(globeContainerRef.current.firstChild);
		}

		// Create scene, camera, and renderer
		const scene = new THREE.Scene();
		scene.background = new THREE.Color("#111827");

		const camera = new THREE.PerspectiveCamera(
			75,
			globeContainerRef.current.clientWidth /
				globeContainerRef.current.clientHeight,
			0.1,
			1000
		);
		camera.position.z = 200;

		const renderer = new THREE.WebGLRenderer({ antialias: true });
		renderer.setSize(
			globeContainerRef.current.clientWidth,
			globeContainerRef.current.clientHeight
		);
		globeContainerRef.current.appendChild(renderer.domElement);

		// Simple orbit controls with inertia for smooth movement
		const controls = {
			rotateSpeed: 0.5,
			autoRotate: false,
			momentum: 0.92, // Momentum factor for smooth movement
			damping: 0.85, // Damping for deceleration
			minDistance: 150,
			maxDistance: 400,
			update: function () {
				// Apply momentum if not dragging
				if (!this.isDragging) {
					this.velocity.x *= this.momentum;
					this.velocity.y *= this.momentum;

					// Only rotate if velocity is significant
					if (
						Math.abs(this.velocity.x) > 0.001 ||
						Math.abs(this.velocity.y) > 0.001
					) {
						this.applyRotation(this.velocity.x, this.velocity.y);
					}
				}

				// Apply zoom changes with smooth interpolation
				if (Math.abs(this.zoomDelta) > 0.1) {
					// Calculate target distance with constraints
					const currentDistance = camera.position.length();
					let targetDistance = currentDistance - this.zoomDelta * 2;
					targetDistance = Math.max(
						this.minDistance,
						Math.min(this.maxDistance, targetDistance)
					);

					// Apply zoom with interpolation
					const newDistance =
						currentDistance + (targetDistance - currentDistance) * 0.1;
					const direction = camera.position.clone().normalize();
					camera.position.copy(direction.multiplyScalar(newDistance));

					// Reduce zoom delta
					this.zoomDelta *= 0.85;
				}

				camera.lookAt(0, 0, 0);
			},
			// Apply rotation with proper 3D math for smooth orbiting
			applyRotation: function (deltaX, deltaY) {
				// Create rotation quaternions
				const rotationY = new THREE.Quaternion().setFromAxisAngle(
					new THREE.Vector3(0, 1, 0),
					-deltaX * this.rotateSpeed
				);

				const rotationX = new THREE.Quaternion().setFromAxisAngle(
					new THREE.Vector3(1, 0, 0).applyQuaternion(
						new THREE.Quaternion().setFromRotationMatrix(camera.matrix)
					),
					-deltaY * this.rotateSpeed
				);

				// Apply rotations
				const tempPos = camera.position.clone();
				tempPos.applyQuaternion(rotationY);
				tempPos.applyQuaternion(rotationX);

				// Check if we're going too far up/down and limit if needed
				const verticalLimit = 0.9;
				const upVector = new THREE.Vector3(0, 1, 0);
				const angle = tempPos.normalize().dot(upVector);

				if (Math.abs(angle) < verticalLimit) {
					camera.position.copy(
						tempPos.normalize().multiplyScalar(camera.position.length())
					);
				} else {
					// Only apply horizontal rotation if we hit the vertical limit
					camera.position.applyQuaternion(rotationY);
				}
			},
			isDragging: false,
			velocity: { x: 0, y: 0 },
			previousMouse: { x: 0, y: 0 },
			zoomDelta: 0,
			// Focus on a specific point
			focusOnPoint: function (point) {
				// Calculate the direction from origin to point
				const direction = point.clone().normalize();

				// Set target position at optimal viewing distance
				const targetPos = direction.multiplyScalar(250);

				// Create animation for smooth transition
				const startPos = camera.position.clone();
				const startTime = Date.now();
				const duration = 1000; // ms

				const animateFocus = () => {
					const elapsed = Date.now() - startTime;
					const progress = Math.min(elapsed / duration, 1);

					// Ease in-out function for smooth acceleration/deceleration
					const easeInOut = (t) =>
						t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
					const t = easeInOut(progress);

					// Interpolate position
					camera.position.x = startPos.x + (targetPos.x - startPos.x) * t;
					camera.position.y = startPos.y + (targetPos.y - startPos.y) * t;
					camera.position.z = startPos.z + (targetPos.z - startPos.z) * t;

					camera.lookAt(0, 0, 0);

					if (progress < 1) {
						requestAnimationFrame(animateFocus);
					}
				};

				// Start animation
				animateFocus();

				// Reset any velocity to stop existing momentum
				this.velocity = { x: 0, y: 0 };
			}
		};

		// Set initial camera position
		controls.cameraStartPosition = {
			x: camera.position.x,
			y: camera.position.y,
			z: camera.position.z
		};

		// Mouse event handlers
		const onMouseDown = (event) => {
			event.preventDefault();
			controls.isDragging = true;
			controls.previousMouse = { x: event.clientX, y: event.clientY };
			// Reset velocity when starting to drag
			controls.velocity = { x: 0, y: 0 };
		};

		const onMouseMove = (event) => {
			// Calculate mouse position in normalized device coordinates
			const rect = renderer.domElement.getBoundingClientRect();
			const mouse = new THREE.Vector2();
			mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
			mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;

			// Update the raycaster
			raycaster.setFromCamera(mouse, camera);

			// Check for intersections with data points
			const intersects = raycaster.intersectObjects(dataPoints);

			if (intersects.length > 0) {
				document.body.style.cursor = "pointer";
				const point = intersects[0].object;

				// Update tooltip information
				setHoveredRegion(point.userData);

				// Get screen position for tooltip
				const vector = new THREE.Vector3();
				vector.setFromMatrixPosition(point.matrixWorld);
				vector.project(camera);

				const x = (vector.x * 0.5 + 0.5) * renderer.domElement.clientWidth;
				const y = (-vector.y * 0.5 + 0.5) * renderer.domElement.clientHeight;

				setTooltipPosition({ x, y });

				// Scale up the hovered point
				point.scale.set(1.5, 1.5, 1.5);

				// Scale down other points
				dataPoints.forEach((p) => {
					if (p !== point) {
						p.scale.set(1, 1, 1);
					}
				});
			} else {
				document.body.style.cursor = "default";
				setHoveredRegion(null);

				// Reset all points
				dataPoints.forEach((p) => {
					p.scale.set(1, 1, 1);
				});
			}

			// Update controls
			if (controls.isDragging) {
				const currentMouse = { x: event.clientX, y: event.clientY };

				// Calculate delta and update velocity
				const deltaX = (currentMouse.x - controls.previousMouse.x) * 0.01;
				const deltaY = (currentMouse.y - controls.previousMouse.y) * 0.01;

				// Apply rotation directly during drag for immediate feedback
				controls.applyRotation(deltaX, deltaY);

				// Update velocity based on movement
				controls.velocity = {
					x: deltaX,
					y: deltaY
				};

				// Save current position for next frame
				controls.previousMouse = currentMouse;
			}
		};

		const onMouseUp = () => {
			controls.isDragging = false;
			// Don't reset velocity to allow momentum to continue
		};

		const onWheel = (event) => {
			event.preventDefault();
			controls.zoomDelta += event.deltaY * 0.1;
		};

		// Then attach events explicitly to the renderer's DOM element
		renderer.domElement.addEventListener("mousedown", onMouseDown);
		document.addEventListener("mousemove", onMouseMove); // Use document for better tracking
		document.addEventListener("mouseup", onMouseUp);
		renderer.domElement.addEventListener("wheel", onWheel, { passive: false });

		// Create Earth
		const earthGeometry = new THREE.SphereGeometry(100, 64, 64);
		const earthMaterial = new THREE.MeshPhongMaterial({
			color: 0x2233ff,
			emissive: 0x112244,
			transparent: true,
			opacity: 0.9
		});
		const earth = new THREE.Mesh(earthGeometry, earthMaterial);
		scene.add(earth);

		// Add atmosphere glow
		const atmosphereGeometry = new THREE.SphereGeometry(102, 64, 64);
		const atmosphereMaterial = new THREE.MeshPhongMaterial({
			color: 0x3366ff,
			emissive: 0x3366ff,
			transparent: true,
			opacity: 0.2,
			side: THREE.BackSide
		});
		const atmosphere = new THREE.Mesh(atmosphereGeometry, atmosphereMaterial);
		scene.add(atmosphere);

		// Add wireframe overlay
		const wireframeGeometry = new THREE.SphereGeometry(101, 32, 32);
		const wireframeMaterial = new THREE.MeshBasicMaterial({
			color: 0x3399ff,
			wireframe: true,
			transparent: true,
			opacity: 0.1
		});
		const wireframe = new THREE.Mesh(wireframeGeometry, wireframeMaterial);
		scene.add(wireframe);

		// Add lighting
		const ambientLight = new THREE.AmbientLight(0x404040, 1);
		scene.add(ambientLight);

		const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
		directionalLight.position.set(1, 1, 1);
		scene.add(directionalLight);

		// Add data points
		const dataPoints = [];
		globalData.forEach((point) => {
			// Convert lat/lng to 3D coordinates
			const phi = (90 - point.lat) * (Math.PI / 180);
			const theta = (point.lng + 180) * (Math.PI / 180);
			const x = -(100 * Math.sin(phi) * Math.cos(theta));
			const z = 100 * Math.sin(phi) * Math.sin(theta);
			const y = 100 * Math.cos(phi);

			// Create point geometry
			const pointGeometry = new THREE.SphereGeometry(
				Math.sqrt(point.value) * 0.5,
				16,
				16
			);
			const pointMaterial = new THREE.MeshBasicMaterial({ color: point.color });
			const pointMesh = new THREE.Mesh(pointGeometry, pointMaterial);

			// Position point slightly above surface
			pointMesh.position.set(x, y, z);
			pointMesh.userData = { region: point.region, value: point.value };

			// Add to scene
			scene.add(pointMesh);
			dataPoints.push(pointMesh);

			// Add extruded cylinder from surface
			const direction = new THREE.Vector3(x, y, z).normalize();
			const height = Math.sqrt(point.value) * 0.8;
			const cylinderGeometry = new THREE.CylinderGeometry(0.5, 0.5, height, 8);
			const cylinderMaterial = new THREE.MeshBasicMaterial({
				color: point.color,
				transparent: true,
				opacity: 0.6
			});
			const cylinder = new THREE.Mesh(cylinderGeometry, cylinderMaterial);

			// Position and orient cylinder
			cylinder.position.set(
				x - (direction.x * height) / 2,
				y - (direction.y * height) / 2,
				z - (direction.z * height) / 2
			);
			cylinder.lookAt(0, 0, 0);
			cylinder.rotateX(Math.PI / 2);

			scene.add(cylinder);
		});

		// Add arc connections
		connectionData.forEach((connection) => {
			const source = globalData.find((d) => d.region === connection.source);
			const target = globalData.find((d) => d.region === connection.target);

			if (source && target) {
				// Convert source and target to 3D coordinates
				const sourcePhi = (90 - source.lat) * (Math.PI / 180);
				const sourceTheta = (source.lng + 180) * (Math.PI / 180);
				const sourceX = -(100 * Math.sin(sourcePhi) * Math.cos(sourceTheta));
				const sourceZ = 100 * Math.sin(sourcePhi) * Math.sin(sourceTheta);
				const sourceY = 100 * Math.cos(sourcePhi);

				const targetPhi = (90 - target.lat) * (Math.PI / 180);
				const targetTheta = (target.lng + 180) * (Math.PI / 180);
				const targetX = -(100 * Math.sin(targetPhi) * Math.cos(targetTheta));
				const targetZ = 100 * Math.sin(targetPhi) * Math.sin(targetTheta);
				const targetY = 100 * Math.cos(targetPhi);

				// Create a curved path between points
				const curvePoints = [];
				for (let i = 0; i <= 20; i++) {
					const t = i / 20;
					// Interpolate between source and target
					const x = sourceX * (1 - t) + targetX * t;
					const y = sourceY * (1 - t) + targetY * t;
					const z = sourceZ * (1 - t) + targetZ * t;

					// Add height to the curve
					const midPoint = new THREE.Vector3(x, y, z);
					const length = midPoint.length();
					midPoint.normalize();
					const heightFactor = Math.sin(Math.PI * t) * (connection.value / 10);
					midPoint.multiplyScalar(length + heightFactor);

					curvePoints.push(midPoint);
				}

				// Create curve from points
				const curve = new THREE.CatmullRomCurve3(curvePoints);
				const curveGeometry = new THREE.TubeGeometry(curve, 20, 0.5, 8, false);

				// Create gradient material
				const curveMaterial = new THREE.MeshBasicMaterial({
					color: new THREE.Color(source.color).lerp(
						new THREE.Color(target.color),
						0.5
					),
					transparent: true,
					opacity: 0.6
				});

				const curveMesh = new THREE.Mesh(curveGeometry, curveMaterial);
				scene.add(curveMesh);
			}
		});

		// Add stars
		const starGeometry = new THREE.BufferGeometry();
		const starMaterial = new THREE.PointsMaterial({
			color: 0xffffff,
			size: 0.7,
			transparent: true,
			opacity: 0.8
		});

		const starVertices = [];
		for (let i = 0; i < 3000; i++) {
			const x = (Math.random() - 0.5) * 2000;
			const y = (Math.random() - 0.5) * 2000;
			const z = (Math.random() - 0.5) * 2000;
			starVertices.push(x, y, z);
		}

		starGeometry.setAttribute(
			"position",
			new THREE.Float32BufferAttribute(starVertices, 3)
		);
		const stars = new THREE.Points(starGeometry, starMaterial);
		scene.add(stars);

		// Raycaster for interaction
		const raycaster = new THREE.Raycaster();

		// Click handler
		const onClick = (event) => {
			// Don't process clicks when dragging
			if (controls.isDragging) return;

			// Calculate mouse position in normalized device coordinates
			const rect = renderer.domElement.getBoundingClientRect();
			const mouse = new THREE.Vector2();
			mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
			mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;

			// Update the raycaster
			raycaster.setFromCamera(mouse, camera);

			// Check for intersections with data points
			const intersects = raycaster.intersectObjects(dataPoints);

			if (intersects.length > 0) {
				const point = intersects[0].object;
				setSelectedRegion(point.userData.region);
				setShowCharts(true);

				// Focus camera on the selected region
				controls.focusOnPoint(point.position);

				// Highlight the selected point
				dataPoints.forEach((p) => {
					if (p === point) {
						// Scale up the selected point
						p.scale.set(2, 2, 2);

						// Create a pulse effect
						const pulseEffect = () => {
							const scale = 1.8 + Math.sin(Date.now() * 0.005) * 0.2;
							p.scale.set(scale, scale, scale);

							if (p.userData.region === selectedRegion) {
								requestAnimationFrame(pulseEffect);
							} else {
								// Reset scale when no longer selected
								p.scale.set(1, 1, 1);
							}
						};

						pulseEffect();
					} else {
						// Dim the other points
						p.scale.set(0.8, 0.8, 0.8);
					}
				});
			}
		};

		window.addEventListener("click", onClick);

		// Animation loop
		const animate = () => {
			requestAnimationFrame(animate);

			// Make wireframe rotate slightly differently
			wireframe.rotation.y += 0.0003;
			wireframe.rotation.x += 0.0001;

			// Make atmosphere pulse
			const time = Date.now() * 0.0005;
			atmosphere.material.opacity = 0.2 + Math.sin(time) * 0.05;

			// Rotate stars slowly
			stars.rotation.y += 0.0001;
			// update the controls on each frame
			controls.update();

			renderer.render(scene, camera);
		};

		animate();

		// Resize handler
		const handleResize = () => {
			if (!globeContainerRef.current) return;

			camera.aspect =
				globeContainerRef.current.clientWidth /
				globeContainerRef.current.clientHeight;
			camera.updateProjectionMatrix();
			renderer.setSize(
				globeContainerRef.current.clientWidth,
				globeContainerRef.current.clientHeight
			);
		};

		window.addEventListener("resize", handleResize);

		// Cleanup
		return () => {
			renderer.domElement.removeEventListener("mousedown", onMouseDown);
			document.removeEventListener("mousemove", onMouseMove);
			document.removeEventListener("mouseup", onMouseUp);
			renderer.domElement.removeEventListener("wheel", onWheel);
			window.removeEventListener("click", onClick);
			window.removeEventListener("resize", handleResize);

			if (globeContainerRef.current) {
				while (globeContainerRef.current.firstChild) {
					globeContainerRef.current.removeChild(
						globeContainerRef.current.firstChild
					);
				}
			}

			// Dispose of geometries and materials
			earthGeometry.dispose();
			earthMaterial.dispose();
			atmosphereGeometry.dispose();
			atmosphereMaterial.dispose();
			wireframeGeometry.dispose();
			wireframeMaterial.dispose();
			starGeometry.dispose();
			starMaterial.dispose();
		};
	}, []);

	// Setup D3 Charts
	useEffect(() => {
		if (!showCharts) return;

		// Create radar chart
		if (chartRef.current) {
			const width = chartRef.current.clientWidth;
			const height = chartRef.current.clientHeight;
			const radius = Math.min(width, height) / 2 - 30;

			d3.select(chartRef.current).selectAll("*").remove();

			const svg = d3
				.select(chartRef.current)
				.append("svg")
				.attr("width", width)
				.attr("height", height)
				.append("g")
				.attr("transform", `translate(${width / 2}, ${height / 2})`);

			// Create circular grid
			const levels = 5;
			for (let i = 0; i < levels; i++) {
				svg
					.append("circle")
					.attr("cx", 0)
					.attr("cy", 0)
					.attr("r", (radius * (i + 1)) / levels)
					.attr("fill", "none")
					.attr("stroke", "#333")
					.attr("stroke-width", 0.5)
					.attr("opacity", 0.3);
			}

			// Create radar axes
			const features = [
				"Technology",
				"Economy",
				"Education",
				"Health",
				"Environment",
				"Infrastructure"
			];
			const angleSlice = (Math.PI * 2) / features.length;

			// Create axes
			features.forEach((feature, i) => {
				const angle = angleSlice * i - Math.PI / 2;

				// Draw axis line
				svg
					.append("line")
					.attr("x1", 0)
					.attr("y1", 0)
					.attr("x2", radius * Math.cos(angle))
					.attr("y2", radius * Math.sin(angle))
					.attr("stroke", "#333")
					.attr("stroke-width", 0.5)
					.attr("opacity", 0.3);

				// Add axis label
				svg
					.append("text")
					.attr("x", (radius + 10) * Math.cos(angle))
					.attr("y", (radius + 10) * Math.sin(angle))
					.attr("text-anchor", "middle")
					.attr("dy", "0.35em")
					.attr("fill", "#fff")
					.attr("font-size", "12px")
					.attr("opacity", 0)
					.text(feature)
					.transition()
					.delay(i * 100)
					.duration(500)
					.attr("opacity", 1);
			});

			// Generate random data for selected region
			const radarData = features.map((feature) => ({
				feature,
				value: 0.2 + Math.random() * 0.7 // Random value between 0.2 and 0.9
			}));

			// Create radar path
			const radarLine = d3
				.lineRadial()
				.radius((d) => d.value * radius)
				.angle((d, i) => i * angleSlice - Math.PI / 2)
				.curve(d3.curveLinearClosed);

			// Add radar path with animation
			const regionColor =
				globalData.find((d) => d.region === selectedRegion)?.color || "#33FFF3";

			const radarPath = svg
				.append("path")
				.datum(radarData)
				.attr("d", (d) => radarLine(d.map((p) => ({ value: 0, angle: p.feature }))))
				.attr("fill", regionColor)
				.attr("fill-opacity", 0.6)
				.attr("stroke", regionColor)
				.attr("stroke-width", 2);

			// Animate radar path
			radarPath
				.transition()
				.duration(1000)
				.attr("d", (d) =>
					radarLine(d.map((p) => ({ value: p.value, angle: p.feature })))
				);

			// Add data points
			radarData.forEach((d, i) => {
				const angle = angleSlice * i - Math.PI / 2;

				svg
					.append("circle")
					.attr("cx", 0)
					.attr("cy", 0)
					.attr("r", 4)
					.attr("fill", regionColor)
					.attr("opacity", 0)
					.transition()
					.delay(1000 + i * 100)
					.duration(300)
					.attr("cx", d.value * radius * Math.cos(angle))
					.attr("cy", d.value * radius * Math.sin(angle))
					.attr("opacity", 1);
			});
		}

		// Create gauge chart
		if (gaugeRef.current) {
			const width = gaugeRef.current.clientWidth;
			const height = gaugeRef.current.clientHeight;

			d3.select(gaugeRef.current).selectAll("*").remove();

			const svg = d3
				.select(gaugeRef.current)
				.append("svg")
				.attr("width", width)
				.attr("height", height)
				.append("g")
				.attr("transform", `translate(${width / 2}, ${height / 2})`);

			const radius = Math.min(width, height) / 2 - 10;

			// Create gauge background
			const arc = d3
				.arc()
				.innerRadius(radius * 0.7)
				.outerRadius(radius)
				.startAngle(-Math.PI / 2)
				.endAngle(Math.PI / 2);

			svg
				.append("path")
				.attr("d", arc())
				.attr("fill", "#333")
				.attr("stroke", "#555")
				.attr("stroke-width", 1);

			// Create value scale
			const scale = d3
				.scaleLinear()
				.domain([0, 100])
				.range([-Math.PI / 2, Math.PI / 2]);

			// Generate value for selected region
			const gaugeValue =
				globalData.find((d) => d.region === selectedRegion)?.value || 50;
			const regionColor =
				globalData.find((d) => d.region === selectedRegion)?.color || "#33FFF3";

			// Create value arc
			const valueArc = d3
				.arc()
				.innerRadius(radius * 0.7)
				.outerRadius(radius)
				.startAngle(-Math.PI / 2)
				.endAngle(-Math.PI / 2); // Start at zero

			const valueArcPath = svg
				.append("path")
				.attr("d", valueArc())
				.attr("fill", regionColor);

			// Animate value arc
			valueArcPath
				.transition()
				.duration(1500)
				.attrTween("d", function () {
					return function (t) {
						valueArc.endAngle(-Math.PI / 2 + scale(gaugeValue * t) + Math.PI / 2);
						return valueArc();
					};
				});

			// Add gauge needle
			const needleLine = svg
				.append("line")
				.attr("x1", 0)
				.attr("y1", 0)
				.attr("x2", 0)
				.attr("y2", -radius * 0.8)
				.attr("stroke", "#fff")
				.attr("stroke-width", 2)
				.attr("transform", "rotate(0)");

			// Animate needle
			needleLine
				.transition()
				.duration(1500)
				.attrTween("transform", function () {
					return function (t) {
						const angle = scale(gaugeValue * t) * (180 / Math.PI);
						return `rotate(${angle})`;
					};
				});

			// Add needle center
			svg
				.append("circle")
				.attr("cx", 0)
				.attr("cy", 0)
				.attr("r", radius * 0.1)
				.attr("fill", "#fff")
				.attr("stroke", "#333")
				.attr("stroke-width", 1);

			// Add value text
			const valueText = svg
				.append("text")
				.attr("x", 0)
				.attr("y", radius * 0.3)
				.attr("text-anchor", "middle")
				.attr("fill", "#fff")
				.attr("font-size", "24px")
				.attr("font-weight", "bold")
				.text("0");

			// Animate value text
			valueText
				.transition()
				.duration(1500)
				.tween("text", function () {
					const i = d3.interpolate(0, gaugeValue);
					return function (t) {
						this.textContent = Math.round(i(t));
					};
				});

			// Add label
			svg
				.append("text")
				.attr("x", 0)
				.attr("y", radius * 0.5)
				.attr("text-anchor", "middle")
				.attr("fill", "#999")
				.attr("font-size", "14px")
				.text("Development Index");
		}

		// Create time series chart
		if (timeSeriesRef.current) {
			const width = timeSeriesRef.current.clientWidth;
			const height = timeSeriesRef.current.clientHeight - 30;
			const margin = { top: 20, right: 20, bottom: 30, left: 40 };

			d3.select(timeSeriesRef.current).selectAll("*").remove();

			const svg = d3
				.select(timeSeriesRef.current)
				.append("svg")
				.attr("width", width)
				.attr("height", height + margin.top + margin.bottom)
				.append("g")
				.attr("transform", `translate(${margin.left}, ${margin.top})`);

			// Create scales
			const x = d3
				.scaleTime()
				.domain(d3.extent(timeSeriesData, (d) => d.date))
				.range([0, width - margin.left - margin.right]);

			const y = d3
				.scaleLinear()
				.domain([0, d3.max(timeSeriesData, (d) => d.value) * 1.1])
				.range([height, 0]);

			// Add X axis
			svg
				.append("g")
				.attr("transform", `translate(0, ${height})`)
				.attr("color", "#666")
				.call(d3.axisBottom(x).ticks(6).tickFormat(d3.timeFormat("%b %y")));

			// Add Y axis
			svg.append("g").attr("color", "#666").call(d3.axisLeft(y));

			// Add grid lines
			svg
				.append("g")
				.attr("class", "grid")
				.attr("opacity", 0.1)
				.call(
					d3
						.axisLeft(y)
						.tickSize(-width + margin.left + margin.right)
						.tickFormat("")
				);

			// Create line generator
			const line = d3
				.line()
				.x((d) => x(d.date))
				.y((d) => y(d.value))
				.curve(d3.curveMonotoneX);

			// Add line path
			const regionColor =
				globalData.find((d) => d.region === selectedRegion)?.color || "#33FFF3";

			const path = svg
				.append("path")
				.datum(timeSeriesData)
				.attr("fill", "none")
				.attr("stroke", regionColor)
				.attr("stroke-width", 3)
				.attr("stroke-linejoin", "round")
				.attr("stroke-linecap", "round");

			// Animate line drawing
			const pathLength = path.node().getTotalLength();

			path
				.attr("stroke-dasharray", pathLength)
				.attr("stroke-dashoffset", pathLength)
				.attr("d", line)
				.transition()
				.duration(2000)
				.attr("stroke-dashoffset", 0);

			// Add area under line
			const area = d3
				.area()
				.x((d) => x(d.date))
				.y0(height)
				.y1((d) => y(d.value))
				.curve(d3.curveMonotoneX);

			const areaPath = svg
				.append("path")
				.datum(timeSeriesData)
				.attr("fill", regionColor)
				.attr("fill-opacity", 0.2)
				.attr("d", area);

			// Animate area filling
			areaPath
				.attr("opacity", 0)
				.transition()
				.delay(1500)
				.duration(500)
				.attr("opacity", 1);

			// Add data points
			svg
				.selectAll(".data-point")
				.data(timeSeriesData)
				.enter()
				.append("circle")
				.attr("class", "data-point")
				.attr("cx", (d) => x(d.date))
				.attr("cy", (d) => y(d.value))
				.attr("r", 4)
				.attr("fill", regionColor)
				.attr("stroke", "#fff")
				.attr("stroke-width", 1)
				.attr("opacity", 0)
				.transition()
				.delay((_, i) => 2000 + i * 50)
				.duration(300)
				.attr("opacity", 1);
		}
	}, [showCharts, selectedRegion]);

	return (
		<div className="flex flex-col w-full h-screen bg-gray-900 text-white">
			{/* Header */}
			<div className="bg-gray-800 p-4 flex justify-between items-center">
				<div className="text-2xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-600">
					Global Technology Index Dashboard
				</div>
				{selectedRegion && (
					<div className="text-xl">
						Region: <span className="font-bold">{selectedRegion}</span>
					</div>
				)}
				<div className="flex items-center space-x-4">
					{selectedRegion && (
						<button
							className="px-4 py-2 bg-blue-600 rounded hover:bg-blue-700 transition-colors"
							onClick={() => {
								// Reset selected region state
								setSelectedRegion(null);
								setShowCharts(false);

								// Reset all data points to normal size
								dataPoints.forEach((p) => {
									p.scale.set(1, 1, 1);
								});

								// Animate camera back to default position
								const startPos = camera.position.clone();
								const targetPos = new THREE.Vector3(0, 0, 200);
								const startTime = Date.now();
								const duration = 1000; // ms

								const resetCamera = () => {
									const elapsed = Date.now() - startTime;
									const progress = Math.min(elapsed / duration, 1);

									// Ease in-out function
									const easeInOut = (t) =>
										t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
									const t = easeInOut(progress);

									// Interpolate position
									camera.position.x = startPos.x + (targetPos.x - startPos.x) * t;
									camera.position.y = startPos.y + (targetPos.y - startPos.y) * t;
									camera.position.z = startPos.z + (targetPos.z - startPos.z) * t;

									camera.lookAt(0, 0, 0);

									if (progress < 1) {
										requestAnimationFrame(resetCamera);
									}
								};

								resetCamera();

								// Reset controls
								controls.velocity = { x: 0, y: 0 };
							}}
						>
							Reset View
						</button>
					)}
				</div>
			</div>

			{/* Floating tooltip for hovered region */}
			{hoveredRegion && (
				<div
					className="absolute z-20 bg-gray-800 border border-blue-500 rounded-lg shadow-lg p-4 pointer-events-none"
					style={{
						left: `${tooltipPosition.x}px`,
						top: `${tooltipPosition.y + 20}px`,
						maxWidth: "250px",
						opacity: 0.9
					}}
				>
					<div className="text-lg font-bold text-blue-400 mb-1">
						{hoveredRegion.region}
					</div>
					<div className="flex items-center mb-2">
						<div
							className="w-4 h-4 rounded-full mr-2"
							style={{
								backgroundColor: globalData.find(
									(d) => d.region === hoveredRegion.region
								)?.color
							}}
						></div>
						<div>
							Index: <span className="font-semibold">{hoveredRegion.value}</span>
						</div>
					</div>
					<div className="text-xs text-gray-400">
						Click to view detailed analytics
					</div>
				</div>
			)}

			{/* Main container */}
			<div className="flex flex-1 relative overflow-hidden">
				{/* 3D Globe container */}
				<div
					ref={globeContainerRef}
					className="absolute inset-0 w-full h-full"
					style={{
						width: showCharts ? "calc(100% - 400px)" : "100%",
						transition: "width 500ms ease-in-out"
					}}
				/>

				{/* Charts panel */}
				<div
					className="absolute right-0 top-0 bottom-0 bg-gray-800 border-l border-gray-700 shadow-xl overflow-auto"
					style={{
						width: "400px",
						transform: showCharts ? "translateX(0)" : "translateX(100%)",
						transition: "transform 500ms ease-in-out"
					}}
				>
					{selectedRegion && (
						<>
							<div className="p-4 border-b border-gray-700">
								<h2 className="text-xl font-bold">{selectedRegion} Analytics</h2>
							</div>

							<div className="p-4">
								<h3 className="text-lg font-medium mb-2">Performance Metrics</h3>
								<div ref={chartRef} className="h-64 mb-6"></div>

								<h3 className="text-lg font-medium mb-2">Development Index</h3>
								<div ref={gaugeRef} className="h-48 mb-6"></div>

								<h3 className="text-lg font-medium mb-2">Growth Trend</h3>
								<div ref={timeSeriesRef} className="h-64"></div>
							</div>
						</>
					)}
				</div>
			</div>

			{/* Footer */}
			<div className="bg-gray-800 p-3 text-xs text-gray-400 flex justify-between items-center">
				<div>
					Data refreshed: April 30, 2025 • Regions: {globalData.length} •
					Connections: {connectionData.length}
				</div>
				<div className="flex space-x-4">
					<button className="hover:text-white transition-colors">Dashboard</button>
					<button className="hover:text-white transition-colors">Reports</button>
					<button className="hover:text-white transition-colors">Settings</button>
				</div>
			</div>
		</div>
	);
};

// Render the component into the DOM
ReactDOM.render(<Dashboard />, document.getElementById("root"));
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://unpkg.com/react@18.2.0/umd/react.production.min.js
  2. https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js
  3. https://cdn.jsdelivr.net/npm/d3@7.8.5/dist/d3.min.js
  4. https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js
  5. https://cdn.tailwindcss.com