infinite-agents-public/mapbox_test/mapbox_globe_3/src/index.js

371 lines
9.8 KiB
JavaScript

import { economicData } from './data/economic-data.js';
// 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, 20],
zoom: 1.5,
attributionControl: false
});
// Add attribution and navigation controls
map.addControl(new mapboxgl.AttributionControl({ compact: true }), 'bottom-right');
map.addControl(new mapboxgl.NavigationControl(), 'top-right');
// Configuration for atmosphere
map.on('style.load', () => {
map.setFog({
color: 'rgb(186, 210, 235)',
'high-color': 'rgb(36, 92, 223)',
'horizon-blend': 0.02,
'space-color': 'rgb(11, 11, 25)',
'star-intensity': 0.6
});
});
// Active metric for visualization
let activeMetric = 'gdpPerCapita';
let activeColorMetric = 'growthRate';
// Color scales and data ranges
const colorScales = {
growthRate: {
// Diverging scale: negative (red) to zero (white) to positive (green)
stops: [
[-25, '#b2182b'], // Deep red for severe contraction
[-10, '#ef8a62'], // Light red for contraction
[-5, '#fddbc7'], // Very light red
[0, '#f7f7f7'], // White for zero growth
[2, '#d1e5f0'], // Very light blue
[4, '#67a9cf'], // Light blue for moderate growth
[8, '#2166ac'] // Deep blue for strong growth
]
},
developmentIndex: {
// Sequential scale for development (low to high)
stops: [
[0.35, '#fff7ec'],
[0.45, '#fee8c8'],
[0.55, '#fdd49e'],
[0.65, '#fdbb84'],
[0.75, '#fc8d59'],
[0.85, '#ef6548'],
[0.95, '#d7301f']
]
},
gdpPerCapita: {
// Sequential scale for GDP (low to high)
stops: [
[0, '#f7fcf5'],
[5000, '#e5f5e0'],
[10000, '#c7e9c0'],
[20000, '#a1d99b'],
[40000, '#74c476'],
[60000, '#41ab5d'],
[100000, '#238b45']
]
},
tradeVolume: {
// Sequential scale for trade
stops: [
[0, '#fff5f0'],
[50, '#fee0d2'],
[200, '#fcbba1'],
[500, '#fc9272'],
[1000, '#fb6a4a'],
[3000, '#ef3b2c'],
[6000, '#a50f15']
]
}
};
// Size scales
const sizeScales = {
gdpPerCapita: {
min: 3,
max: 25,
stops: [
[0, 3],
[10000, 8],
[30000, 15],
[70000, 25]
]
},
tradeVolume: {
min: 3,
max: 30,
stops: [
[0, 3],
[100, 8],
[500, 15],
[2000, 22],
[6000, 30]
]
},
developmentIndex: {
min: 4,
max: 20,
stops: [
[0.35, 4],
[0.55, 8],
[0.75, 14],
[0.95, 20]
]
},
growthRate: {
min: 5,
max: 20,
stops: [
[-25, 5],
[-5, 8],
[0, 10],
[5, 15],
[12, 20]
]
}
};
// Add economic data layer
map.on('load', () => {
// Add source
map.addSource('economic-indicators', {
type: 'geojson',
data: economicData
});
// Add circle layer with data-driven styling
map.addLayer({
id: 'economic-circles',
type: 'circle',
source: 'economic-indicators',
paint: {
// Circle radius based on active size metric using interpolate expression
'circle-radius': [
'interpolate',
['linear'],
['get', activeMetric],
...sizeScales[activeMetric].stops.flat()
],
// Circle color based on growth rate using interpolate expression
'circle-color': [
'interpolate',
['linear'],
['get', activeColorMetric],
...colorScales[activeColorMetric].stops.flat()
],
// Opacity with zoom-based adjustment
'circle-opacity': [
'interpolate',
['linear'],
['zoom'],
1, 0.7,
4, 0.8,
8, 0.9
],
// Stroke for better visibility
'circle-stroke-width': [
'interpolate',
['linear'],
['zoom'],
1, 0.5,
4, 1,
8, 2
],
'circle-stroke-color': '#ffffff',
'circle-stroke-opacity': 0.5
}
});
// Add country labels layer
map.addLayer({
id: 'country-labels',
type: 'symbol',
source: 'economic-indicators',
layout: {
'text-field': ['get', 'code'],
'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
'text-size': [
'interpolate',
['linear'],
['zoom'],
1, 8,
4, 12,
8, 16
],
'text-offset': [0, 0],
'text-anchor': 'center'
},
paint: {
'text-color': '#ffffff',
'text-halo-color': '#000000',
'text-halo-width': 1,
'text-opacity': [
'interpolate',
['linear'],
['zoom'],
1, 0,
2, 0.6,
4, 1
]
},
minzoom: 1.5
});
// Create popup
const popup = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false,
offset: 10
});
// Show popup on hover
map.on('mouseenter', 'economic-circles', (e) => {
map.getCanvas().style.cursor = 'pointer';
const coordinates = e.features[0].geometry.coordinates.slice();
const props = e.features[0].properties;
const html = `
<div style="font-family: Arial, sans-serif; min-width: 220px;">
<h3 style="margin: 0 0 10px 0; font-size: 16px; font-weight: bold; color: #333;">
${props.country}
</h3>
<div style="font-size: 13px; line-height: 1.6; color: #555;">
<div style="margin-bottom: 6px;">
<strong>GDP per Capita:</strong> $${props.gdpPerCapita.toLocaleString()}
</div>
<div style="margin-bottom: 6px; color: ${props.growthRate >= 0 ? '#2166ac' : '#b2182b'};">
<strong>Growth Rate:</strong> ${props.growthRate >= 0 ? '+' : ''}${props.growthRate}%
</div>
<div style="margin-bottom: 6px;">
<strong>Development Index:</strong> ${(props.developmentIndex || props.developmentRate || 0).toFixed(3)}
</div>
<div>
<strong>Trade Volume:</strong> $${props.tradeVolume}B
</div>
</div>
</div>
`;
popup.setLngLat(coordinates).setHTML(html).addTo(map);
});
map.on('mouseleave', 'economic-circles', () => {
map.getCanvas().style.cursor = '';
popup.remove();
});
// Update visualization function
window.updateVisualization = (sizeMetric, colorMetric) => {
activeMetric = sizeMetric;
activeColorMetric = colorMetric;
// Update circle radius
map.setPaintProperty('economic-circles', 'circle-radius', [
'interpolate',
['linear'],
['get', activeMetric],
...sizeScales[activeMetric].stops.flat()
]);
// Update circle color
map.setPaintProperty('economic-circles', 'circle-color', [
'interpolate',
['linear'],
['get', activeColorMetric],
...colorScales[activeColorMetric].stops.flat()
]);
// Update legend
updateLegend();
};
// Initialize legend
updateLegend();
});
// Update legend based on active metrics
function updateLegend() {
const sizeLegend = document.getElementById('size-legend');
const colorLegend = document.getElementById('color-legend');
// Size legend
const sizeScale = sizeScales[activeMetric];
const sizeLabels = {
gdpPerCapita: 'GDP per Capita ($)',
tradeVolume: 'Trade Volume ($B)',
developmentIndex: 'Development Index',
growthRate: 'Growth Rate (%)'
};
sizeLegend.innerHTML = `
<div style="font-weight: bold; margin-bottom: 8px; font-size: 13px;">
Size: ${sizeLabels[activeMetric]}
</div>
<div style="display: flex; align-items: flex-end; gap: 8px;">
<div style="text-align: center;">
<div style="width: ${sizeScale.min * 2}px; height: ${sizeScale.min * 2}px; border-radius: 50%; background: rgba(100,149,237,0.6); margin: 0 auto 4px;"></div>
<div style="font-size: 11px;">${formatValue(sizeScale.stops[0][0], activeMetric)}</div>
</div>
<div style="text-align: center;">
<div style="width: ${sizeScale.max * 2}px; height: ${sizeScale.max * 2}px; border-radius: 50%; background: rgba(100,149,237,0.6); margin: 0 auto 4px;"></div>
<div style="font-size: 11px;">${formatValue(sizeScale.stops[sizeScale.stops.length - 1][0], activeMetric)}</div>
</div>
</div>
`;
// Color legend
const colorScale = colorScales[activeColorMetric];
const colorLabels = {
gdpPerCapita: 'GDP per Capita ($)',
tradeVolume: 'Trade Volume ($B)',
developmentIndex: 'Development Index',
growthRate: 'Growth Rate (%)'
};
const gradientStops = colorScale.stops.map((stop, i) => {
const percent = (i / (colorScale.stops.length - 1)) * 100;
return `${stop[1]} ${percent}%`;
}).join(', ');
colorLegend.innerHTML = `
<div style="font-weight: bold; margin-bottom: 8px; font-size: 13px;">
Color: ${colorLabels[activeColorMetric]}
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<div style="font-size: 11px;">${formatValue(colorScale.stops[0][0], activeColorMetric)}</div>
<div style="flex: 1; height: 20px; background: linear-gradient(to right, ${gradientStops}); border-radius: 3px;"></div>
<div style="font-size: 11px;">${formatValue(colorScale.stops[colorScale.stops.length - 1][0], activeColorMetric)}</div>
</div>
`;
}
// Format values based on metric type
function formatValue(value, metric) {
if (metric === 'gdpPerCapita') {
return `$${(value / 1000).toFixed(0)}k`;
} else if (metric === 'tradeVolume') {
return `$${value}B`;
} else if (metric === 'developmentIndex') {
return value.toFixed(2);
} else if (metric === 'growthRate') {
return value >= 0 ? `+${value}%` : `${value}%`;
}
return value;
}
// Enable rotation
map.on('idle', () => {
if (map.getLayer('economic-circles')) {
map.rotateTo((map.getBearing() + 0.3) % 360, { duration: 120000 });
}
});