"use client"; import * as d3 from "d3"; import type { Feature, FeatureCollection, GeoJsonProperties, Geometry, } from "geojson"; import { AlertTriangle, Info, MapPin } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { feature } from "topojson-client"; import type { GeometryCollection, Objects, Topology, } from "topojson-specification"; import { HorizontalBarChart } from "./horizontal-bar-chart"; import { BarDataPoint } from "./types"; // Constants const MAP_CONFIG = { defaultWidth: 688, defaultHeight: 400, pointRadius: 6, selectedPointRadius: 8, transitionDuration: 300, } as const; const MAP_COLORS = { landFill: "var(--chart-border-emphasis)", landStroke: "var(--chart-border)", pointDefault: "#DB2B49", pointSelected: "#86DA26", pointHover: "#DB2B49", } as const; const RISK_LEVELS = { LOW_HIGH: "low-high", HIGH: "high", CRITICAL: "critical", } as const; type RiskLevel = (typeof RISK_LEVELS)[keyof typeof RISK_LEVELS]; interface LocationPoint { id: string; name: string; region: string; coordinates: [number, number]; totalFindings: number; riskLevel: RiskLevel; severityData: BarDataPoint[]; change?: number; } export interface MapChartData { locations: LocationPoint[]; regions: string[]; } export interface MapChartProps { data: MapChartData; height?: number; onLocationSelect?: (location: LocationPoint | null) => void; } // Utility functions function createProjection(width: number, height: number) { return d3 .geoNaturalEarth1() .fitExtent( [ [1, 1], [width - 1, height - 1], ], { type: "Sphere" }, ) .precision(0.2); } async function fetchWorldData(): Promise { try { const worldAtlasModule = await import("world-atlas/countries-110m.json"); const worldData = worldAtlasModule.default || worldAtlasModule; const topology = worldData as unknown as Topology; return feature( topology, topology.objects.countries as GeometryCollection, ) as FeatureCollection; } catch (error) { console.error("Error loading world map data:", error); return null; } } // Helper: Create SVG element function createSVGElement( type: string, attributes: Record, ): T { const element = document.createElementNS( "http://www.w3.org/2000/svg", type, ) as T; Object.entries(attributes).forEach(([key, value]) => { element.setAttribute(key, value); }); return element; } // Components function MapTooltip({ location, position, }: { location: LocationPoint; position: { x: number; y: number }; }) { const CHART_COLORS = { tooltipBorder: "var(--chart-border-emphasis)", tooltipBackground: "var(--chart-background)", textPrimary: "var(--chart-text-primary)", textSecondary: "var(--chart-text-secondary)", }; return (
{location.name}
{location.totalFindings.toLocaleString()} Fail Findings
{location.change !== undefined && (

{location.change > 0 ? "+" : ""} {location.change}% {" "} since last scan

)}
); } function EmptyState() { const CHART_COLORS = { tooltipBorder: "var(--chart-border-emphasis)", tooltipBackground: "var(--chart-background)", textSecondary: "var(--chart-text-secondary)", }; return (

Select a location on the map to view details

); } function LoadingState({ height }: { height: number }) { const CHART_COLORS = { textSecondary: "var(--chart-text-secondary)", }; return (
Loading map...
); } export function MapChart({ data, height = MAP_CONFIG.defaultHeight, }: MapChartProps) { const svgRef = useRef(null); const containerRef = useRef(null); const [selectedLocation, setSelectedLocation] = useState(null); const [hoveredLocation, setHoveredLocation] = useState( null, ); const [tooltipPosition, setTooltipPosition] = useState<{ x: number; y: number; } | null>(null); const [worldData, setWorldData] = useState(null); const [isLoadingMap, setIsLoadingMap] = useState(true); const [dimensions, setDimensions] = useState<{ width: number; height: number; }>({ width: MAP_CONFIG.defaultWidth, height, }); // Fetch world data once on mount useEffect(() => { let isMounted = true; fetchWorldData() .then((data) => { if (isMounted && data) setWorldData(data); }) .catch(console.error) .finally(() => { if (isMounted) setIsLoadingMap(false); }); return () => { isMounted = false; }; }, []); // Update dimensions on resize useEffect(() => { const updateDimensions = () => { if (containerRef.current) { setDimensions({ width: containerRef.current.clientWidth, height }); } }; updateDimensions(); window.addEventListener("resize", updateDimensions); return () => window.removeEventListener("resize", updateDimensions); }, [height]); // Render the map useEffect(() => { if (!svgRef.current || !worldData || isLoadingMap) return; const svg = svgRef.current; const { width, height } = dimensions; svg.innerHTML = ""; const projection = createProjection(width, height); const path = d3.geoPath().projection(projection); // Render countries const mapGroup = createSVGElement("g", { class: "map-countries", }); worldData.features?.forEach( (feature: Feature) => { const pathData = path(feature); if (pathData) { const pathElement = createSVGElement("path", { d: pathData, fill: MAP_COLORS.landFill, stroke: MAP_COLORS.landStroke, "stroke-width": "0.5", }); mapGroup.appendChild(pathElement); } }, ); svg.appendChild(mapGroup); // Helper to update tooltip position const updateTooltip = (e: MouseEvent) => { const rect = svg.getBoundingClientRect(); setTooltipPosition({ x: e.clientX - rect.left, y: e.clientY - rect.top, }); }; // Helper to create circle const createCircle = (location: LocationPoint) => { const projected = projection(location.coordinates); if (!projected) return null; const [x, y] = projected; if (x < 0 || x > width || y < 0 || y > height) return null; const isSelected = selectedLocation?.id === location.id; const isHovered = hoveredLocation?.id === location.id; const classes = ["cursor-pointer"]; if (isSelected) classes.push("drop-shadow-[0_0_8px_#86da26]"); if (isHovered && !isSelected) classes.push("opacity-70"); const circle = createSVGElement("circle", { cx: x.toString(), cy: y.toString(), r: (isSelected ? MAP_CONFIG.selectedPointRadius : MAP_CONFIG.pointRadius ).toString(), fill: isSelected ? MAP_COLORS.pointSelected : MAP_COLORS.pointDefault, class: classes.join(" "), }); circle.addEventListener("click", () => setSelectedLocation(isSelected ? null : location), ); circle.addEventListener("mouseenter", (e) => { setHoveredLocation(location); updateTooltip(e); }); circle.addEventListener("mousemove", updateTooltip); circle.addEventListener("mouseleave", () => { setHoveredLocation(null); setTooltipPosition(null); }); return circle; }; // Render points const pointsGroup = createSVGElement("g", { class: "threat-points", }); // Unselected points first data.locations.forEach((location) => { if (selectedLocation?.id !== location.id) { const circle = createCircle(location); if (circle) pointsGroup.appendChild(circle); } }); // Selected point last (on top) if (selectedLocation) { const selectedData = data.locations.find( (loc) => loc.id === selectedLocation.id, ); if (selectedData) { const circle = createCircle(selectedData); if (circle) pointsGroup.appendChild(circle); } } svg.appendChild(pointsGroup); }, [ data.locations, dimensions, selectedLocation, hoveredLocation, worldData, isLoadingMap, ]); const CHART_COLORS = { tooltipBorder: "var(--chart-border-emphasis)", tooltipBackground: "var(--chart-background)", textPrimary: "var(--chart-text-primary)", textSecondary: "var(--chart-text-secondary)", }; return (
{/* Map Section */}

Threat Map

{isLoadingMap ? ( ) : ( <>
{hoveredLocation && tooltipPosition && ( )}
{data.locations.length} Locations
)}
{/* Details Section */}
{selectedLocation ? (

{selectedLocation.name}

{selectedLocation.totalFindings.toLocaleString()} Total Findings

) : ( )}
); }