diff --git a/src/components/Map.jsx b/src/components/Map.jsx index f18d2d7..fb0e369 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -181,7 +181,7 @@ const MapComponent = () => { const [isDarkTheme, setIsDarkTheme] = useState(true); // Satellite mode state - const [isSatellite, setIsSatellite] = useState(false); + const [isSatellite, setIsSatellite] = useState(true); // Panel states const [activeTab, setActiveTab] = useState('analysis'); // 'analysis' | 'charts' | 'selection' @@ -206,7 +206,7 @@ const MapComponent = () => { }); // Load map data including PDS grids - const { boundaries, pdsGridsData, loading, error, totalValue } = useMapData(); + const { boundaries, pdsGridsData, timeSeriesData, loading, error, totalValue } = useMapData(); // Add sidebar state const [isSidebarOpen, setIsSidebarOpen] = useState(!isMobile); @@ -820,6 +820,7 @@ const MapComponent = () => { boundaries={boundaries} selectedMetric={selectedMetric} selectedRegion={selectedRegion} + timeSeriesData={timeSeriesData} onClose={() => setSelectedRegion(null)} style={{ position: 'absolute', diff --git a/src/components/map/DistributionHistogram.jsx b/src/components/map/DistributionHistogram.jsx index 31b4329..e3d35c7 100644 --- a/src/components/map/DistributionHistogram.jsx +++ b/src/components/map/DistributionHistogram.jsx @@ -1,95 +1,127 @@ import React, { useMemo } from 'react'; -import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ReferenceLine } from 'recharts'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts'; import { X } from 'lucide-react'; import { SHARED_STYLES } from '../../utils/gridLayerConfig'; import { getMetricInfo, formatRegionName } from '../../utils/formatters'; +import { getTimeSeriesForRegion } from '../../services/dataService'; const DistributionHistogram = ({ isDarkTheme, boundaries, selectedMetric, selectedRegion, + timeSeriesData, onClose, style }) => { - // Calculate density plot data + // Calculate density plot data for time series const densityData = useMemo(() => { - if (!boundaries || !boundaries.features) return null; + if (!boundaries || !boundaries.features || !timeSeriesData || !selectedRegion) return null; - // Extract all values for the selected metric - const allValues = boundaries.features - .map(f => f.properties[selectedMetric]) - .filter(v => v !== null && v !== undefined && !isNaN(v)); + const selectedCountry = selectedRegion.properties.country; + const selectedRegionName = selectedRegion.properties.region; + + // Get time series for selected region + const selectedTimeSeries = getTimeSeriesForRegion(timeSeriesData, selectedCountry, selectedRegionName); + if (!selectedTimeSeries || !selectedTimeSeries.data) return null; - if (allValues.length === 0) return null; + // Collect all time series data for other regions + const otherRegionsData = []; + boundaries.features.forEach(feature => { + if (feature.properties.country === selectedCountry && + feature.properties.region === selectedRegionName) { + // Skip selected region + return; + } + + const regionTimeSeries = getTimeSeriesForRegion( + timeSeriesData, + feature.properties.country, + feature.properties.region + ); + + if (regionTimeSeries && regionTimeSeries.data) { + regionTimeSeries.data.forEach(entry => { + if (entry[selectedMetric] != null && !isNaN(entry[selectedMetric])) { + otherRegionsData.push(entry[selectedMetric]); + } + }); + } + }); - // Get selected region's value - const selectedValue = selectedRegion?.properties[selectedMetric]; + // Extract values for selected region + const selectedValues = selectedTimeSeries.data + .map(entry => entry[selectedMetric]) + .filter(v => v != null && !isNaN(v)); - // Calculate statistics + if (selectedValues.length === 0 || otherRegionsData.length === 0) return null; + + // Calculate range from all data + const allValues = [...selectedValues, ...otherRegionsData]; const min = Math.min(...allValues); const max = Math.max(...allValues); const range = max - min; - // Create more points for smooth curve + // Create density points const numPoints = 50; - const bandwidth = range * 0.1; // Bandwidth for kernel density estimation + const bandwidth = range * 0.15; // Bandwidth for kernel density estimation - // Calculate density using Gaussian kernel const points = []; for (let i = 0; i <= numPoints; i++) { const x = min + (i / numPoints) * range; - let density = 0; - // Gaussian kernel density estimation - allValues.forEach(value => { + // Calculate density for selected region + let selectedDensity = 0; + selectedValues.forEach(value => { const u = (x - value) / bandwidth; - density += Math.exp(-0.5 * u * u) / Math.sqrt(2 * Math.PI); + selectedDensity += Math.exp(-0.5 * u * u) / Math.sqrt(2 * Math.PI); }); + selectedDensity = selectedDensity / (selectedValues.length * bandwidth); - density = density / (allValues.length * bandwidth); + // Calculate density for other regions + let otherDensity = 0; + otherRegionsData.forEach(value => { + const u = (x - value) / bandwidth; + otherDensity += Math.exp(-0.5 * u * u) / Math.sqrt(2 * Math.PI); + }); + otherDensity = otherDensity / (otherRegionsData.length * bandwidth); points.push({ value: x, - density: density, + selectedDensity: selectedDensity * 100, // Scale for visibility + otherDensity: otherDensity * 100, label: x.toFixed(1) }); } - // Find max density for scaling - const maxDensity = Math.max(...points.map(p => p.density)); + // Normalize densities + const maxSelectedDensity = Math.max(...points.map(p => p.selectedDensity)); + const maxOtherDensity = Math.max(...points.map(p => p.otherDensity)); + const maxDensity = Math.max(maxSelectedDensity, maxOtherDensity); - // Normalize densities to percentage points.forEach(p => { - p.density = (p.density / maxDensity) * 100; + p.selectedDensity = (p.selectedDensity / maxDensity) * 100; + p.otherDensity = (p.otherDensity / maxDensity) * 100; }); - // Add selected value density - if (selectedValue != null) { - let selectedDensity = 0; - allValues.forEach(value => { - const u = (selectedValue - value) / bandwidth; - selectedDensity += Math.exp(-0.5 * u * u) / Math.sqrt(2 * Math.PI); - }); - selectedDensity = (selectedDensity / (allValues.length * bandwidth) / maxDensity) * 100; - - // Find closest point to insert selected value info - const closestIndex = points.findIndex(p => p.value >= selectedValue); - if (closestIndex >= 0) { - points[closestIndex].isSelected = true; - points[closestIndex].selectedDensity = selectedDensity; - } - } - return { points, - selectedValue, - totalRegions: allValues.length, - metricName: selectedMetric, + selectedStats: { + mean: selectedValues.reduce((a, b) => a + b, 0) / selectedValues.length, + min: Math.min(...selectedValues), + max: Math.max(...selectedValues), + count: selectedValues.length + }, + otherStats: { + mean: otherRegionsData.reduce((a, b) => a + b, 0) / otherRegionsData.length, + min: Math.min(...otherRegionsData), + max: Math.max(...otherRegionsData), + count: otherRegionsData.length + }, min, max }; - }, [boundaries, selectedMetric, selectedRegion]); + }, [boundaries, selectedMetric, selectedRegion, timeSeriesData]); if (!densityData) return null; @@ -106,9 +138,14 @@ const DistributionHistogram = ({
- {formatRegionName(selectedRegion?.properties)} compared to all regions + {formatRegionName(selectedRegion?.properties)} vs all other districts