Skip to content

Instantly share code, notes, and snippets.

@davidglassborow
Created October 24, 2024 11:03
Show Gist options
  • Save davidglassborow/004e331ac464c2af1d459ce55a938d36 to your computer and use it in GitHub Desktop.
Save davidglassborow/004e331ac464c2af1d459ce55a938d36 to your computer and use it in GitHub Desktop.
Globe with some data
<!DOCTYPE html>
<html>
<head>
<title>Interactive 3D Globe</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.min.js"></script>
<style>
body {
margin: 0;
background: #000;
overflow: hidden;
}
canvas {
display: block;
cursor: pointer;
}
.controls {
position: fixed;
top: 20px;
left: 20px;
color: white;
font-family: Arial, sans-serif;
}
#tooltip {
position: fixed;
padding: 12px 16px;
background: rgba(0, 0, 0, 0.8);
color: white;
font-family: Arial, sans-serif;
font-size: 14px;
pointer-events: none;
border-radius: 6px;
display: none;
max-width: 280px;
}
#tooltip h3 {
margin: 0 0 8px 0;
color: #ff9966;
}
#tooltip p {
margin: 4px 0;
}
</style>
</head>
<body>
<div class="controls">
<h2>Interactive Globe</h2>
<p>Drag to rotate • Scroll to zoom • Hover over countries</p>
</div>
<div id="tooltip"></div>
<script>
// https://claude.ai/chat/d533f102-65e6-420b-a9dd-7319916c1dfe
// Country data mapping
const countryData = {
"4": {"name": "Afghanistan", "capital": "Kabul", "region": "Asia", "population": "38.9M"},
"8": {"name": "Albania", "capital": "Tirana", "region": "Europe", "population": "2.8M"},
"12": {"name": "Algeria", "capital": "Algiers", "region": "Africa", "population": "44.6M"},
"20": {"name": "Andorra", "capital": "Andorra la Vella", "region": "Europe", "population": "77K"},
"24": {"name": "Angola", "capital": "Luanda", "region": "Africa", "population": "32.9M"},
"28": {"name": "Antigua and Barbuda", "capital": "Saint John's", "region": "Caribbean", "population": "98K"},
"32": {"name": "Argentina", "capital": "Buenos Aires", "region": "South America", "population": "45.2M"},
"36": {"name": "Australia", "capital": "Canberra", "region": "Oceania", "population": "25.7M"},
"40": {"name": "Austria", "capital": "Vienna", "region": "Europe", "population": "9.0M"},
"44": {"name": "Bahamas", "capital": "Nassau", "region": "Caribbean", "population": "393K"},
"48": {"name": "Bahrain", "capital": "Manama", "region": "Middle East", "population": "1.7M"},
"50": {"name": "Bangladesh", "capital": "Dhaka", "region": "Asia", "population": "164.7M"},
"51": {"name": "Armenia", "capital": "Yerevan", "region": "Asia", "population": "3.0M"},
"52": {"name": "Barbados", "capital": "Bridgetown", "region": "Caribbean", "population": "287K"},
"56": {"name": "Belgium", "capital": "Brussels", "region": "Europe", "population": "11.6M"},
"64": {"name": "Bhutan", "capital": "Thimphu", "region": "Asia", "population": "772K"},
"68": {"name": "Bolivia", "capital": "La Paz", "region": "South America", "population": "11.8M"},
"70": {"name": "Bosnia and Herzegovina", "capital": "Sarajevo", "region": "Europe", "population": "3.3M"},
"72": {"name": "Botswana", "capital": "Gaborone", "region": "Africa", "population": "2.4M"},
"76": {"name": "Brazil", "capital": "Brasília", "region": "South America", "population": "212.6M"},
"96": {"name": "Brunei", "capital": "Bandar Seri Begawan", "region": "Asia", "population": "437K"},
"100": {"name": "Bulgaria", "capital": "Sofia", "region": "Europe", "population": "6.9M"},
"104": {"name": "Myanmar", "capital": "Naypyidaw", "region": "Asia", "population": "54.4M"},
"108": {"name": "Burundi", "capital": "Gitega", "region": "Africa", "population": "11.9M"},
"116": {"name": "Cambodia", "capital": "Phnom Penh", "region": "Asia", "population": "16.7M"},
"120": {"name": "Cameroon", "capital": "Yaoundé", "region": "Africa", "population": "26.5M"},
"124": {"name": "Canada", "capital": "Ottawa", "region": "North America", "population": "38.0M"},
"144": {"name": "Sri Lanka", "capital": "Colombo", "region": "Asia", "population": "22.0M"},
"152": {"name": "Chile", "capital": "Santiago", "region": "South America", "population": "19.5M"},
"156": {"name": "China", "capital": "Beijing", "region": "Asia", "population": "1.4B"},
"170": {"name": "Colombia", "capital": "Bogotá", "region": "South America", "population": "51.0M"},
"188": {"name": "Costa Rica", "capital": "San José", "region": "Central America", "population": "5.1M"},
"191": {"name": "Croatia", "capital": "Zagreb", "region": "Europe", "population": "4.0M"},
"192": {"name": "Cuba", "capital": "Havana", "region": "Caribbean", "population": "11.3M"},
"203": {"name": "Czech Republic", "capital": "Prague", "region": "Europe", "population": "10.7M"},
"208": {"name": "Denmark", "capital": "Copenhagen", "region": "Europe", "population": "5.8M"},
"214": {"name": "Dominican Republic", "capital": "Santo Domingo", "region": "Caribbean", "population": "10.8M"},
"218": {"name": "Ecuador", "capital": "Quito", "region": "South America", "population": "17.6M"},
"222": {"name": "El Salvador", "capital": "San Salvador", "region": "Central America", "population": "6.5M"},
"226": {"name": "Equatorial Guinea", "capital": "Malabo", "region": "Africa", "population": "1.4M"},
"231": {"name": "Ethiopia", "capital": "Addis Ababa", "region": "Africa", "population": "117.9M"},
"233": {"name": "Estonia", "capital": "Tallinn", "region": "Europe", "population": "1.3M"},
"246": {"name": "Finland", "capital": "Helsinki", "region": "Europe", "population": "5.5M"},
"250": {"name": "France", "capital": "Paris", "region": "Europe", "population": "67.4M"},
"268": {"name": "Georgia", "capital": "Tbilisi", "region": "Asia", "population": "3.7M"},
"276": {"name": "Germany", "capital": "Berlin", "region": "Europe", "population": "83.2M"},
"288": {"name": "Ghana", "capital": "Accra", "region": "Africa", "population": "31.0M"},
"300": {"name": "Greece", "capital": "Athens", "region": "Europe", "population": "10.7M"},
"320": {"name": "Guatemala", "capital": "Guatemala City", "region": "Central America", "population": "17.9M"},
"324": {"name": "Guinea", "capital": "Conakry", "region": "Africa", "population": "13.1M"},
"328": {"name": "Guyana", "capital": "Georgetown", "region": "South America", "population": "787K"},
"332": {"name": "Haiti", "capital": "Port-au-Prince", "region": "Caribbean", "population": "11.4M"},
"340": {"name": "Honduras", "capital": "Tegucigalpa", "region": "Central America", "population": "9.9M"},
"348": {"name": "Hungary", "capital": "Budapest", "region": "Europe", "population": "9.7M"},
"352": {"name": "Iceland", "capital": "Reykjavík", "region": "Europe", "population": "364K"},
"356": {"name": "India", "capital": "New Delhi", "region": "Asia", "population": "1.38B"},
"360": {"name": "Indonesia", "capital": "Jakarta", "region": "Asia", "population": "273.5M"},
"364": {"name": "Iran", "capital": "Tehran", "region": "Middle East", "population": "85.0M"},
"368": {"name": "Iraq", "capital": "Baghdad", "region": "Middle East", "population": "40.5M"},
"372": {"name": "Ireland", "capital": "Dublin", "region": "Europe", "population": "5.0M"},
"376": {"name": "Israel", "capital": "Jerusalem", "region": "Middle East", "population": "9.3M"},
"380": {"name": "Italy", "capital": "Rome", "region": "Europe", "population": "60.3M"},
"388": {"name": "Jamaica", "capital": "Kingston", "region": "Caribbean", "population": "2.9M"},
"392": {"name": "Japan", "capital": "Tokyo", "region": "Asia", "population": "125.4M"},
"398": {"name": "Kazakhstan", "capital": "Nur-Sultan", "region": "Asia", "population": "18.8M"},
"400": {"name": "Jordan", "capital": "Amman", "region": "Middle East", "population": "10.2M"},
"404": {"name": "Kenya", "capital": "Nairobi", "region": "Africa", "population": "54.4M"},
"408": {"name": "North Korea", "capital": "Pyongyang", "region": "Asia", "population": "25.7M"},
"410": {"name": "South Korea", "capital": "Seoul", "region": "Asia", "population": "51.7M"},
"414": {"name": "Kuwait", "capital": "Kuwait City", "region": "Middle East", "population": "4.3M"},
"417": {"name": "Kyrgyzstan", "capital": "Bishkek", "region": "Asia", "population": "6.5M"},
"418": {"name": "Laos", "capital": "Vientiane", "region": "Asia", "population": "7.3M"},
"422": {"name": "Lebanon", "capital": "Beirut", "region": "Middle East", "population": "6.8M"},
"428": {"name": "Latvia", "capital": "Riga", "region": "Europe", "population": "1.9M"},
"430": {"name": "Liberia", "capital": "Monrovia", "region": "Africa", "population": "5.1M"},
"434": {"name": "Libya", "capital": "Tripoli", "region": "Africa", "population": "6.9M"},
"440": {"name": "Lithuania", "capital": "Vilnius", "region": "Europe", "population": "2.8M"},
"442": {"name": "Luxembourg", "capital": "Luxembourg City", "region": "Europe", "population": "632K"},
"450": {"name": "Madagascar", "capital": "Antananarivo", "region": "Africa", "population": "27.7M"},
"458": {"name": "Malaysia", "capital": "Kuala Lumpur", "region": "Asia", "population": "32.7M"},
"466": {"name": "Mali", "capital": "Bamako", "region": "Africa", "population": "20.3M"},
"470": {"name": "Malta", "capital": "Valletta", "region": "Europe", "population": "514K"},
"484": {"name": "Mexico", "capital": "Mexico City", "region": "North America", "population": "128.9M"},
"498": {"name": "Moldova", "capital": "Chișinău", "region": "Europe", "population": "2.6M"},
"499": {"name": "Montenegro", "capital": "Podgorica", "region": "Europe", "population": "621K"},
"504": {"name": "Morocco", "capital": "Rabat", "region": "Africa", "population": "37.1M"},
"508": {"name": "Mozambique", "capital": "Maputo", "region": "Africa", "population": "31.3M"},
"512": {"name": "Oman", "capital": "Muscat", "region": "Middle East", "population": "5.1M"},
"516": {"name": "Namibia", "capital": "Windhoek", "region": "Africa", "population": "2.5M"},
"524": {"name": "Nepal", "capital": "Kathmandu", "region": "Asia", "population": "29.1M"},
"528": {"name": "Netherlands", "capital": "Amsterdam", "region": "Europe", "population": "17.4M"},
"554": {"name": "New Zealand", "capital": "Wellington", "region": "Oceania", "population": "5.1M"},
"558": {"name": "Nicaragua", "capital": "Managua", "region": "Central America", "population": "6.6M"},
"562": {"name": "Niger", "capital": "Niamey", "region": "Africa", "population": "24.2M"},
"566": {"name": "Nigeria", "capital": "Abuja", "region": "Africa", "population": "206.1M"},
"578": {"name": "Norway", "capital": "Oslo", "region": "Europe", "population": "5.4M"},
"586": {"name": "Pakistan", "capital": "Islamabad", "region": "Asia", "population": "220.9M"},
"591": {"name": "Panama", "capital": "Panama City", "region": "Central America", "population": "4.3M"},
"598": {"name": "Papua New Guinea", "capital": "Port Moresby", "region": "Oceania", "population": "8.9M"},
"600": {"name": "Paraguay", "capital": "Asunción", "region": "South America", "population": "7.1M"},
"604": {"name": "Peru", "capital": "Lima", "region": "South America", "population": "32.9M"},
"608": {"name": "Philippines", "capital": "Manila", "region": "Asia", "population": "109.6M"},
"616": {"name": "Poland", "capital": "Warsaw", "region": "Europe", "population": "37.8M"},
"620": {"name": "Portugal", "capital": "Lisbon", "region": "Europe", "population": "10.3M"},
"634": {"name": "Qatar", "capital": "Doha", "region": "Middle East", "population": "2.9M"},
"642": {"name": "Romania", "capital": "Bucharest", "region": "Europe", "population": "19.2M"},
"643": {"name": "Russia", "capital": "Moscow", "region": "Europe/Asia", "population": "144.1M"},
"646": {"name": "Rwanda", "capital": "Kigali", "region": "Africa", "population": "13.0M"},
"682": {"name": "Saudi Arabia", "capital": "Riyadh", "region": "Middle East", "population": "35.0M"},
"686": {"name": "Senegal", "capital": "Dakar", "region": "Africa", "population": "16.7M"},
"688": {"name": "Serbia", "capital": "Belgrade", "region": "Europe", "population": "6.8M"},
"702": {"name": "Singapore", "capital": "Singapore", "region": "Asia", "population": "5.7M"},
"703": {"name": "Slovakia", "capital": "Bratislava", "region": "Europe", "population": "5.5M"},
"704": {"name": "Vietnam", "capital": "Hanoi", "region": "Asia", "population": "97.3M"},
"705": {"name": "Slovenia", "capital": "Ljubljana", "region": "Europe", "population": "2.1M"},
"826": {"name": "United Kingdom", "capital": "London", "region": "Europe", "population": "67.2M"},
"840": {"name": "United States", "capital": "Washington, D.C.", "region": "North America", "population": "331.0M"}
};
// Set up the scene
const width = window.innerWidth;
const height = window.innerHeight;
let hoveredCountry = null;
// Create the projection
const projection = d3.geoOrthographic()
.scale(250)
.center([0, 0])
.rotate([0, -30])
.translate([width / 2, height / 2]);
// Create the canvas
const canvas = d3.select('body')
.append('canvas')
.attr('width', width)
.attr('height', height);
const context = canvas.node().getContext('2d');
const tooltip = d3.select('#tooltip');
// Create the path generator
const path = d3.geoPath()
.projection(projection)
.context(context);
// Calculate country area and center
function getCountryInfo(feature) {
const bounds = d3.geoBounds(feature);
const center = [(bounds[0][0] + bounds[1][0])/2, (bounds[0][1] + bounds[1][1])/2];
const area = d3.geoArea(feature);
return {
center: center,
area: Math.round(area * 6371 * 6371) // Approximate area in km²
};
}
// Setup drag behavior
const drag = d3.drag()
.on('start', dragstarted)
.on('drag', dragged);
canvas.call(drag);
let v0, r0;
function dragstarted(event) {
v0 = [event.x, event.y];
r0 = projection.rotate();
}
function dragged(event) {
if (!v0) return;
const v1 = [event.x, event.y];
const r1 = [
r0[0] + (v1[0] - v0[0]) / 4,
r0[1] - (v1[1] - v0[1]) / 4
];
projection.rotate(r1);
render();
}
// Add zoom behavior
const zoom = d3.zoom()
.scaleExtent([0.95, 20])
.on('zoom', (event) => {
projection.scale(250 * event.transform.k);
render();
});
canvas.call(zoom);
// Handle mouse move for highlighting
canvas.on('mousemove', (event) => {
const pos = d3.pointer(event);
const coordinates = projection.invert(pos);
if (coordinates) {
hoveredCountry = null;
if (window.countries) {
window.countries.features.forEach(feature => {
if (d3.geoContains(feature, coordinates)) {
hoveredCountry = feature;
}
});
}
if (hoveredCountry) {
const country = countryData[hoveredCountry.id] || {
name: "Unknown Country",
capital: "Unknown",
region: "Unknown",
population: "Unknown"
};
const geoInfo = getCountryInfo(hoveredCountry);
tooltip
.style('display', 'block')
.style('left', (event.pageX + 10) + 'px')
.style('top', (event.pageY + 10) + 'px')
.html(`
<h3>${country.name}</h3>
<p><strong>Capital:</strong> ${country.capital}</p>
<p><strong>Region:</strong> ${country.region}</p>
<p><strong>Population:</strong> ${country.population}</p>
<p><strong>Area:</strong> ${geoInfo.area.toLocaleString()} km²</p>
<p><strong>Coordinates:</strong> ${geoInfo.center.map(d => d.toFixed(2))}</p>
`);
} else {
tooltip.style('display', 'none');
}
render();
}
});
canvas.on('mouseout', () => {
hoveredCountry = null;
tooltip.style('display', 'none');
render();
});
// Render function
function render() {
context.clearRect(0, 0, width, height);
// Draw globe background
context.beginPath();
context.arc(width / 2, height / 2, projection.scale(), 0, 2 * Math.PI);
context.fillStyle = '#4444ff';
context.fill();
// Draw countries
if (window.countries) {
window.countries.features.forEach(feature => {
context.beginPath();
path(feature);
if (hoveredCountry && feature.id === hoveredCountry.id) {
context.fillStyle = '#ff9966'; // Highlighted color
} else {
context.fillStyle = '#66ff66'; // Default color
}
context.fill();
context.strokeStyle = '#333';
context.lineWidth = 0.5;
context.stroke();
});
}
// Draw graticules
const graticule = d3.geoGraticule();
context.beginPath();
path(graticule());
context.strokeStyle = 'rgba(255,255,255,0.2)';
context.lineWidth = 0.5;
context.stroke();
}
// Load world data
d3.json('https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json')
.then(worldData => {
window.countries = topojson.feature(worldData, worldData.objects.countries);
render();
});
// Handle window resize
window.addEventListener('resize', () => {
const width = window.innerWidth;
const height = window.innerHeight;
canvas
.attr('width', width)
.attr('height', height);
projection.translate([width / 2, height / 2]);
render();
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment