// Mapbox Globe Visualization 5: Global Educational Institutions // Demonstrates data-driven styling with match expressions for categorical data // and interpolate expressions for continuous educational metrics // 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: [15, 25], zoom: 1.8, pitch: 0 }); // Auto-rotation state let userInteracting = false; let rotationActive = true; // Track current styling metric let currentSizeMetric = 'enrollment'; let currentColorMetric = 'quality'; map.on('load', () => { // Configure globe atmosphere map.setFog({ color: 'rgba(20, 30, 50, 0.9)', 'high-color': 'rgba(50, 80, 150, 0.5)', 'horizon-blend': 0.05, 'space-color': 'rgba(5, 5, 15, 1)', 'star-intensity': 0.7 }); // Add educational institutions data source map.addSource('education', { type: 'geojson', data: educationData }); // Main institution layer - using MATCH expression for categorical type styling // and INTERPOLATE for continuous quality metrics // This demonstrates the data-driven styling learned from Mapbox documentation map.addLayer({ id: 'institutions', type: 'circle', source: 'education', paint: { // SIZE: Interpolate based on enrollment (continuous data) 'circle-radius': [ 'interpolate', ['linear'], ['get', 'enrollment'], 1000, 4, // Small institutions 10000, 7, 30000, 11, 60000, 16, 100000, 22, 350000, 30 // Massive universities ], // COLOR: Interpolate based on quality score (continuous data) // Uses a diverging-like scale from red (low quality) to gold (high quality) 'circle-color': [ 'interpolate', ['linear'], ['get', 'quality'], 50, '#8b0000', // Dark red - very low quality 60, '#dc143c', // Crimson - low quality 70, '#ff6347', // Tomato - below average 75, '#ff8c00', // Dark orange - average 80, '#ffa500', // Orange - above average 85, '#ffd700', // Gold - good 90, '#00ced1', // Dark turquoise - very good 95, '#00bfff', // Deep sky blue - excellent 100, '#1e90ff' // Dodger blue - world class ], // OPACITY: Zoom-responsive for better visibility 'circle-opacity': [ 'interpolate', ['linear'], ['zoom'], 1, 0.75, 4, 0.85, 8, 0.95 ], // STROKE: Categorical styling using MATCH expression // This is the key technique learned from the web source 'circle-stroke-color': [ 'match', ['get', 'type'], 'University', '#ffffff', 'School', '#cccccc', '#999999' // default ], 'circle-stroke-width': [ 'interpolate', ['linear'], ['zoom'], 1, 0.5, 4, 1, 8, 2 ] } }); // Labels layer for top-tier institutions (quality >= 85) map.addLayer({ id: 'institution-labels', type: 'symbol', source: 'education', filter: ['>=', ['get', 'quality'], 85], layout: { 'text-field': ['get', 'name'], 'text-size': [ 'interpolate', ['linear'], ['get', 'quality'], 85, 10, 100, 14 ], 'text-offset': [0, 1.5], 'text-anchor': 'top', 'text-max-width': 8 }, paint: { 'text-color': '#ffffff', 'text-halo-color': '#000000', 'text-halo-width': 1.5, 'text-opacity': [ 'interpolate', ['linear'], ['zoom'], 1, 0, 3, 0.6, 6, 1 ] }, minzoom: 3 }); // Interaction: Popup on hover const popup = new mapboxgl.Popup({ closeButton: false, closeOnClick: false, offset: 15 }); map.on('mouseenter', 'institutions', (e) => { map.getCanvas().style.cursor = 'pointer'; const props = e.features[0].properties; const coordinates = e.features[0].geometry.coordinates.slice(); const html = `

${props.name}

Country: ${props.country}
Type: ${props.type}
Quality Score: ${props.quality}/100
Enrollment: ${props.enrollment.toLocaleString()}
Literacy Rate: ${props.literacy}%
Funding: $${props.funding}M USD
`; popup.setLngLat(coordinates).setHTML(html).addTo(map); }); map.on('mouseleave', 'institutions', () => { map.getCanvas().style.cursor = ''; popup.remove(); }); // Start auto-rotation startRotation(); // Update statistics panel updateStatistics(); }); // Auto-rotation function function startRotation() { if (!rotationActive || userInteracting) return; const center = map.getCenter(); center.lng -= 0.08; // Slower rotation for educational theme map.easeTo({ center: center, duration: 1000, easing: (t) => t }); } // Rotation loop map.on('moveend', () => { if (rotationActive) { startRotation(); } }); // Pause rotation on user interaction map.on('mousedown', () => { userInteracting = true; }); map.on('mouseup', () => { userInteracting = false; if (rotationActive) startRotation(); }); map.on('dragstart', () => { userInteracting = true; }); map.on('dragend', () => { userInteracting = false; if (rotationActive) startRotation(); }); map.on('pitchstart', () => { userInteracting = true; }); map.on('pitchend', () => { userInteracting = false; if (rotationActive) startRotation(); }); // Toggle rotation document.getElementById('toggle-rotation').addEventListener('click', () => { rotationActive = !rotationActive; document.getElementById('toggle-rotation').textContent = rotationActive ? 'Pause Rotation' : 'Resume Rotation'; if (rotationActive) startRotation(); }); // Reset view document.getElementById('reset-view').addEventListener('click', () => { map.flyTo({ center: [15, 25], zoom: 1.8, pitch: 0, bearing: 0, duration: 2000 }); }); // Metric switching - demonstrates dynamic expression updates document.getElementById('size-metric').addEventListener('change', (e) => { currentSizeMetric = e.target.value; updateCircleSize(); updateLegend(); }); document.getElementById('color-metric').addEventListener('change', (e) => { currentColorMetric = e.target.value; updateCircleColor(); updateLegend(); }); // Update circle size based on selected metric function updateCircleSize() { const sizeExpressions = { enrollment: [ 'interpolate', ['linear'], ['get', 'enrollment'], 1000, 4, 10000, 7, 30000, 11, 60000, 16, 100000, 22, 350000, 30 ], quality: [ 'interpolate', ['linear'], ['get', 'quality'], 50, 4, 60, 6, 70, 9, 80, 13, 90, 18, 100, 26 ], literacy: [ 'interpolate', ['linear'], ['get', 'literacy'], 40, 4, 60, 8, 75, 12, 90, 17, 100, 24 ], funding: [ 'interpolate', ['linear'], ['get', 'funding'], 200, 4, 500, 7, 1000, 10, 2000, 14, 3500, 19, 5500, 28 ] }; map.setPaintProperty('institutions', 'circle-radius', sizeExpressions[currentSizeMetric]); } // Update circle color based on selected metric function updateCircleColor() { const colorExpressions = { quality: [ 'interpolate', ['linear'], ['get', 'quality'], 50, '#8b0000', 60, '#dc143c', 70, '#ff6347', 75, '#ff8c00', 80, '#ffa500', 85, '#ffd700', 90, '#00ced1', 95, '#00bfff', 100, '#1e90ff' ], literacy: [ 'interpolate', ['linear'], ['get', 'literacy'], 40, '#8b0000', 50, '#dc143c', 65, '#ff6347', 75, '#ffa500', 85, '#ffd700', 92, '#00ced1', 97, '#00bfff', 100, '#1e90ff' ], enrollment: [ 'interpolate', ['linear'], ['get', 'enrollment'], 1000, '#4a148c', // Deep purple - small 10000, '#7b1fa2', // Purple 30000, '#9c27b0', // Medium purple 60000, '#ba68c8', // Light purple 100000, '#ce93d8', // Very light purple 350000, '#e1bee7' // Pale purple - massive ], funding: [ 'interpolate', ['linear'], ['get', 'funding'], 200, '#1a5490', // Dark blue - low funding 500, '#2874a6', 1000, '#3498db', // Medium blue 2000, '#5dade2', 3500, '#85c1e9', 5500, '#aed6f1' // Light blue - high funding ] }; map.setPaintProperty('institutions', 'circle-color', colorExpressions[currentColorMetric]); } // Update statistics panel function updateStatistics() { document.getElementById('total-institutions').textContent = globalStats.totalInstitutions; document.getElementById('avg-quality').textContent = globalStats.avgQuality; document.getElementById('avg-literacy').textContent = globalStats.avgLiteracy + '%'; document.getElementById('total-enrollment').textContent = (globalStats.totalEnrollment / 1000000).toFixed(1) + 'M'; } // Update legend based on current metrics function updateLegend() { const sizeLabels = { enrollment: { unit: ' students', min: '1K', max: '350K' }, quality: { unit: '/100', min: '50', max: '100' }, literacy: { unit: '%', min: '40', max: '100' }, funding: { unit: 'M USD', min: '200', max: '5500' } }; const colorLabels = { quality: { min: 'Low Quality (50)', max: 'World Class (100)' }, literacy: { min: 'Low Literacy (40%)', max: 'Universal (100%)' }, enrollment: { min: 'Small (1K)', max: 'Massive (350K)' }, funding: { min: 'Low Funded ($200M)', max: 'High Funded ($5.5B)' } }; const sizeLabel = sizeLabels[currentSizeMetric]; const colorLabel = colorLabels[currentColorMetric]; document.getElementById('size-min-label').textContent = sizeLabel.min; document.getElementById('size-max-label').textContent = sizeLabel.max; document.getElementById('color-min-label').textContent = colorLabel.min; document.getElementById('color-max-label').textContent = colorLabel.max; } // Add navigation controls map.addControl(new mapboxgl.NavigationControl(), 'bottom-right'); map.addControl(new mapboxgl.FullscreenControl(), 'bottom-right'); // Initialize legend updateLegend();