// Mapbox Globe Visualization 14: HPV Vaccine Impact on Cervical Cancer // Demonstrates multi-layer correlation visualization with 3D extrusions, // dual choropleth encoding, and gender health equity analysis // Mapbox access token mapboxgl.accessToken = 'pk.eyJ1IjoibGludXhpc2Nvb2wiLCJhIjoiY2w3ajM1MnliMDV4NDNvb2J5c3V5dzRxZyJ9.wJukH5hVSiO74GM_VSJR3Q'; // Initialize map with globe projection const map = new mapboxgl.Map({ container: 'map', style: 'mapbox://styles/mapbox/dark-v11', projection: 'globe', center: [20, 15], zoom: 1.6, pitch: 0 }); // Auto-rotation state let userInteracting = false; let rotationActive = false; // Track current styling metrics let currentSizeMetric = 'coverage'; let currentColorMetric = 'cancer-rate'; let show3DExtrusion = false; // Expression definitions for different metrics const sizeExpressions = { 'coverage': [ 'interpolate', ['linear'], ['get', 'hpv_coverage_2024'], 0, 4, // No program 20, 8, 40, 12, 60, 17, 80, 23, 95, 30 // Excellent coverage ], 'cancer-rate': [ 'interpolate', ['linear'], ['get', 'cervical_cancer_incidence'], 2, 4, // Very low incidence 5, 7, 10, 11, 15, 16, 25, 22, 35, 27, 44, 32 // Very high incidence ], 'lives-saved': [ 'interpolate', ['linear'], ['get', 'lives_saved_projected'], 0, 4, 100, 6, 500, 9, 1000, 12, 5000, 18, 10000, 24, 86000, 35 // India (largest potential) ], 'mortality': [ 'interpolate', ['linear'], ['get', 'cervical_cancer_mortality'], 1.2, 4, 3, 8, 5, 12, 10, 18, 15, 24, 30.4, 32 // Eswatini (highest) ] }; const colorExpressions = { 'coverage': [ 'interpolate', ['linear'], ['get', 'hpv_coverage_2024'], 0, '#4a0e4e', // Dark purple - no program 15, '#6b1b6b', 30, '#8e2a8e', 50, '#b366ff', // Medium purple 70, '#d499ff', 90, '#ebccff' // Light purple - excellent coverage ], 'cancer-rate': [ 'interpolate', ['linear'], ['get', 'cervical_cancer_incidence'], 2, '#66ffb3', // Green - very low incidence 5, '#99ff99', 10, '#ffff66', // Yellow - moderate 15, '#ffcc33', 20, '#ff9933', // Orange 30, '#ff6633', 40, '#ff3333', // Red - high incidence 44, '#cc0000' // Dark red - very high ], 'lives-saved': [ 'interpolate', ['linear'], ['get', 'lives_saved_projected'], 0, '#0d4d4d', // Dark teal 500, '#1a7a7a', 2000, '#26a69a', // Teal 5000, '#4db6ac', 20000, '#80cbc4', 86000, '#b2dfdb' // Light teal - highest potential ], 'mortality': [ 'interpolate', ['linear'], ['get', 'cervical_cancer_mortality'], 1.2, '#66ffb3', // Green - very low 3, '#99ff99', 6, '#ffff66', // Yellow 10, '#ffcc33', 15, '#ff6633', // Orange-red 22, '#ff3333', 30.4, '#cc0000' // Dark red - very high ] }; // 3D extrusion height expression (cancer burden) const extrusionHeightExpression = [ 'interpolate', ['linear'], ['get', 'annual_deaths'], 0, 0, 100, 50000, 500, 150000, 1000, 250000, 5000, 500000, 10000, 800000, 77000, 1500000 // India (highest deaths) ]; map.on('load', () => { // Configure globe atmosphere with purple-pink theme map.setFog({ color: 'rgba(25, 15, 35, 0.9)', 'high-color': 'rgba(80, 50, 120, 0.5)', 'horizon-blend': 0.06, 'space-color': 'rgba(8, 5, 15, 1)', 'star-intensity': 0.8 }); // Add HPV vaccine data source map.addSource('hpv-data', { type: 'geojson', data: hpvVaccineData }); // Main circle layer - vaccine coverage and cancer correlation map.addLayer({ id: 'hpv-circles', type: 'circle', source: 'hpv-data', paint: { // SIZE: Based on current size metric 'circle-radius': sizeExpressions[currentSizeMetric], // COLOR: Based on current color metric 'circle-color': colorExpressions[currentColorMetric], // OPACITY: Zoom-responsive 'circle-opacity': [ 'interpolate', ['linear'], ['zoom'], 1, 0.8, 3, 0.85, 6, 0.92 ], // STROKE: Highlight countries without programs 'circle-stroke-color': [ 'case', ['==', ['get', 'hpv_coverage_2024'], 0], '#ff6eb4', // Pink for no program '#ffffff' // White for countries with programs ], 'circle-stroke-width': [ 'case', ['==', ['get', 'hpv_coverage_2024'], 0], 2.5, // Thicker stroke for no program (attention) [ 'interpolate', ['linear'], ['zoom'], 1, 0.5, 4, 1, 8, 1.5 ] ] } }); // 3D extrusion layer for cancer burden (initially hidden) map.addLayer({ id: 'cancer-burden-3d', type: 'fill-extrusion', source: 'hpv-data', layout: { 'visibility': 'none' }, paint: { 'fill-extrusion-color': [ 'interpolate', ['linear'], ['get', 'cervical_cancer_incidence'], 2, '#4db6ac', // Teal - low incidence 10, '#ffeb3b', // Yellow 20, '#ff9800', // Orange 30, '#f44336', // Red 44, '#b71c1c' // Dark red - high incidence ], 'fill-extrusion-height': extrusionHeightExpression, 'fill-extrusion-base': 0, 'fill-extrusion-opacity': 0.85 } }); // Labels for high-burden countries map.addLayer({ id: 'country-labels', type: 'symbol', source: 'hpv-data', filter: ['>=', ['get', 'annual_deaths'], 2000], layout: { 'text-field': ['get', 'name'], 'text-size': [ 'interpolate', ['linear'], ['get', 'annual_deaths'], 2000, 10, 10000, 12, 77000, 14 ], 'text-offset': [0, 1.8], 'text-anchor': 'top', 'text-max-width': 8 }, paint: { 'text-color': '#b366ff', 'text-halo-color': '#0a0a0f', 'text-halo-width': 2, 'text-opacity': [ 'interpolate', ['linear'], ['zoom'], 1, 0, 2.5, 0.5, 5, 0.85 ] }, minzoom: 2 }); // Interaction: Popup on hover const popup = new mapboxgl.Popup({ closeButton: false, closeOnClick: false, offset: 15 }); map.on('mouseenter', 'hpv-circles', (e) => { map.getCanvas().style.cursor = 'pointer'; const props = e.features[0].properties; const coordinates = e.features[0].geometry.coordinates.slice(); // Determine coverage status let coverageStatus = 'low'; if (props.hpv_coverage_2024 > 70) coverageStatus = 'high'; else if (props.hpv_coverage_2024 > 40) coverageStatus = 'medium'; // Determine cancer rate status (inverse) let cancerStatus = 'high'; if (props.cervical_cancer_incidence < 10) cancerStatus = 'low'; else if (props.cervical_cancer_incidence < 20) cancerStatus = 'medium'; const programInfo = props.hpv_coverage_2024 > 0 ? `Started: ${props.vaccine_program_started}
Target Age: ${props.target_age}
Policy: ${props.gender_policy === 'girls-and-boys' ? 'Girls & Boys' : 'Girls Only'}` : 'No vaccination program'; const html = ` `; popup.setLngLat(coordinates).setHTML(html).addTo(map); }); map.on('mouseleave', 'hpv-circles', () => { map.getCanvas().style.cursor = ''; popup.remove(); }); // Auto-rotation logic function spinGlobe() { if (!userInteracting && rotationActive) { const center = map.getCenter(); center.lng -= 0.15; map.easeTo({ center, duration: 100, easing: (n) => n }); } } const spinInterval = setInterval(spinGlobe, 100); map.on('mousedown', () => { userInteracting = true; }); map.on('mouseup', () => { userInteracting = false; }); map.on('dragend', () => { userInteracting = false; }); map.on('touchstart', () => { userInteracting = true; }); map.on('touchend', () => { userInteracting = false; }); // Control: Toggle rotation document.getElementById('toggle-rotation').addEventListener('click', (e) => { rotationActive = !rotationActive; e.target.textContent = rotationActive ? 'Pause Rotation' : 'Resume Rotation'; }); // Control: Reset view document.getElementById('reset-view').addEventListener('click', () => { map.flyTo({ center: [20, 15], zoom: 1.6, pitch: 0, bearing: 0, duration: 2000 }); }); // Control: Toggle 3D comparison mode document.getElementById('toggle-comparison').addEventListener('click', (e) => { show3DExtrusion = !show3DExtrusion; if (show3DExtrusion) { // Show 3D extrusions, increase pitch for better view map.setLayoutProperty('cancer-burden-3d', 'visibility', 'visible'); map.easeTo({ pitch: 45, duration: 1000 }); e.target.textContent = 'Hide 3D Cancer Burden'; e.target.classList.add('active'); } else { // Hide 3D extrusions, reset pitch map.setLayoutProperty('cancer-burden-3d', 'visibility', 'none'); map.easeTo({ pitch: 0, duration: 1000 }); e.target.textContent = 'Toggle 3D Cancer Burden'; e.target.classList.remove('active'); } }); // Control: Size metric selector document.getElementById('size-metric').addEventListener('change', (e) => { currentSizeMetric = e.target.value; updateCircleSize(); updateLegend(); }); // Control: Color metric selector document.getElementById('color-metric').addEventListener('change', (e) => { currentColorMetric = e.target.value; updateCircleColor(); updateLegend(); }); // Update circle size based on metric function updateCircleSize() { map.setPaintProperty('hpv-circles', 'circle-radius', sizeExpressions[currentSizeMetric]); } // Update circle color based on metric function updateCircleColor() { map.setPaintProperty('hpv-circles', 'circle-color', colorExpressions[currentColorMetric]); } // Update legend to match current metrics function updateLegend() { const metricLabels = { 'coverage': { name: 'HPV Vaccine Coverage', min: '0% (No program)', max: '95% (High coverage)', gradient: 'coverage-gradient' }, 'cancer-rate': { name: 'Cervical Cancer Incidence', min: 'Low (2 per 100K)', max: 'High (44 per 100K)', gradient: 'cancer-gradient' }, 'lives-saved': { name: 'Lives That Could Be Saved', min: '0', max: '86,000 (India)', gradient: 'lives-saved-gradient' }, 'mortality': { name: 'Cervical Cancer Mortality', min: 'Low (1.2 per 100K)', max: 'High (30.4 per 100K)', gradient: 'cancer-gradient' } }; const colorInfo = metricLabels[currentColorMetric]; const sizeInfo = metricLabels[currentSizeMetric]; // Update color legend document.getElementById('color-metric-label').textContent = colorInfo.name; document.getElementById('color-min-label').textContent = colorInfo.min; document.getElementById('color-max-label').textContent = colorInfo.max; const colorGradient = document.getElementById('color-gradient'); colorGradient.className = 'legend-gradient ' + colorInfo.gradient; // Update size legend document.getElementById('size-metric-label').textContent = sizeInfo.name; document.getElementById('size-min-label').textContent = sizeInfo.min; document.getElementById('size-max-label').textContent = sizeInfo.max; } // Initialize legend updateLegend(); });