Created
October 24, 2024 11:03
-
-
Save davidglassborow/004e331ac464c2af1d459ce55a938d36 to your computer and use it in GitHub Desktop.
Globe with some data
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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