/** * Polio Eradication Progress Visualization (1980-2020) * Using shared architecture for reliability and best practices */ import { MAPBOX_CONFIG } from '../../shared/mapbox-config.js'; import { generateVaccineData } from '../../shared/data-generator.js'; import { LayerFactory, COLOR_SCALES } from '../../shared/layer-factory.js'; // Generate polio data (Point geometries with realistic metrics) const polioData = generateVaccineData('polio'); // Initialize map with validated configuration const map = new mapboxgl.Map({ container: 'map', ...MAPBOX_CONFIG.getMapOptions({ style: 'mapbox://styles/mapbox/dark-v11', center: [20, 20], zoom: 1.5, pitch: 0 }) }); // Timeline state let currentYear = 1980; let isPlaying = false; let animationInterval = null; let userInteracting = false; // Historical milestones const milestones = { 1980: "Pre-GPEI: Estimated 400,000 polio cases annually worldwide", 1988: "Global Polio Eradication Initiative (GPEI) launched - 22% global coverage", 1991: "Last polio case in the Americas (Peru) - Western Hemisphere progress", 1994: "Americas certified polio-free - First WHO region to achieve eradication", 2000: "Western Pacific region certified polio-free - Including China and Australia", 2002: "Europe certified polio-free - 51 countries achieve eradication", 2012: "India achieves 3 years without polio - Major milestone for South Asia", 2014: "Southeast Asia certified polio-free - 11 countries including India", 2020: "Africa certified polio-free - Wild poliovirus eradicated from continent", }; // Map load event map.on('load', () => { const factory = new LayerFactory(map); // Apply medical-themed atmosphere factory.applyGlobeAtmosphere({ theme: 'medical' }); // Add data source map.addSource('polio-data', { type: 'geojson', data: polioData }); // Create main circle layer using coverage_1980 as default (will be updated dynamically) const layer = factory.createCircleLayer({ id: 'polio-circles', source: 'polio-data', sizeProperty: 'population', colorProperty: 'coverage_1980', colorScale: 'coverage', sizeRange: [4, 25], opacityRange: [0.75, 0.9] }); map.addLayer(layer); // Setup hover effects with custom popup let hoveredFeatureId = null; map.on('mouseenter', 'polio-circles', (e) => { map.getCanvas().style.cursor = 'pointer'; if (e.features.length > 0) { const feature = e.features[0]; const props = feature.properties; const coverage = props[`coverage_${currentYear}`] || 0; let coverageClass = 'coverage-low'; if (coverage >= 80) coverageClass = 'coverage-high'; else if (coverage >= 40) coverageClass = 'coverage-medium'; let popupContent = ` `; if (props.polio_free_year && currentYear >= props.polio_free_year) { popupContent += ` `; } if (props.endemic && currentYear === 2020) { popupContent += ` `; } new mapboxgl.Popup({ offset: 15 }) .setLngLat(feature.geometry.coordinates) .setHTML(popupContent) .addTo(map); } }); map.on('mouseleave', 'polio-circles', () => { map.getCanvas().style.cursor = ''; popup.remove(); }); // Add legend (will update title dynamically) const legendDiv = factory.addLegend({ title: `Polio Coverage ${currentYear}`, colorScale: 'coverage', position: 'bottom-right' }); // Store reference for updates window.polioLegend = legendDiv; // Initialize visualization for 1980 updateVisualization(); // Globe auto-rotation const spinGlobe = () => { if (!userInteracting && map.isStyleLoaded()) { map.easeTo({ center: [map.getCenter().lng + 0.05, map.getCenter().lat], duration: 100, easing: (n) => n }); } requestAnimationFrame(spinGlobe); }; // spinGlobe(); // Auto-rotation disabled map.on('mousedown', () => { userInteracting = true; }); map.on('mouseup', () => { userInteracting = false; }); map.on('dragend', () => { userInteracting = false; }); map.on('pitchend', () => { userInteracting = false; }); map.on('rotateend', () => { userInteracting = false; }); }); // Update visualization for current year function updateVisualization() { if (!map.isStyleLoaded()) return; // Update year display document.getElementById('year-display').textContent = currentYear; document.getElementById('year-slider').value = currentYear; // Update milestone text const milestoneText = milestones[currentYear] || 'Progress continues...'; document.getElementById('milestone-text').textContent = milestoneText; // Update layer color to use current year's coverage data const colorExpression = [ 'interpolate', ['linear'], ['get', `coverage_${currentYear}`], ...COLOR_SCALES.coverage.stops.flatMap((stop, i) => [ stop, COLOR_SCALES.coverage.colors[i] ]) ]; map.setPaintProperty('polio-circles', 'circle-color', colorExpression); // Update legend title if (window.polioLegend) { const legendTitle = window.polioLegend.querySelector('h3'); if (legendTitle) { legendTitle.textContent = `Polio Coverage ${currentYear}`; } } // Update statistics updateStatistics(); } // Calculate and update statistics function updateStatistics() { const features = polioData.features; let totalCoverage = 0; let certifiedCount = 0; let endemicCount = 0; let validCountries = 0; features.forEach(feature => { const props = feature.properties; const coverage = props[`coverage_${currentYear}`]; if (coverage !== undefined && coverage > 0) { totalCoverage += coverage; validCountries++; } if (props.polio_free_year && currentYear >= props.polio_free_year) { certifiedCount++; } if (props.endemic && currentYear === 2020) { endemicCount++; } }); const avgCoverage = validCountries > 0 ? (totalCoverage / validCountries).toFixed(1) : 0; // Update stat displays document.getElementById('global-coverage').textContent = `${avgCoverage}%`; document.getElementById('certified-countries').textContent = certifiedCount; document.getElementById('endemic-countries').textContent = endemicCount; // Calculate cases prevented (estimated based on coverage improvement) const baselineCoverage = 22; // 1980 global coverage const coverageImprovement = avgCoverage - baselineCoverage; const casesPrevented = Math.round((coverageImprovement / 100) * 20000000); // 20M total prevented by 2020 document.getElementById('cases-prevented').textContent = casesPrevented > 0 ? `${(casesPrevented / 1000000).toFixed(1)}M` : '0'; } // Play/pause animation function playAnimation() { if (isPlaying) { stopAnimation(); return; } isPlaying = true; document.getElementById('play-btn').textContent = 'Pause'; document.getElementById('play-btn').classList.add('active'); animationInterval = setInterval(() => { if (currentYear >= 2020) { currentYear = 1980; } else { currentYear++; } updateVisualization(); }, 800); } function stopAnimation() { isPlaying = false; document.getElementById('play-btn').textContent = 'Play'; document.getElementById('play-btn').classList.remove('active'); if (animationInterval) { clearInterval(animationInterval); animationInterval = null; } } function resetAnimation() { stopAnimation(); currentYear = 1980; updateVisualization(); } // Timeline controls document.getElementById('year-slider').addEventListener('input', (e) => { stopAnimation(); currentYear = parseInt(e.target.value); updateVisualization(); }); document.getElementById('play-btn').addEventListener('click', playAnimation); document.getElementById('reset-btn').addEventListener('click', resetAnimation); // Handle window resize window.addEventListener('resize', () => { map.resize(); });