/**
* 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();
});