mapjson supplies the coordinate of every national capital; Open-Meteo supplies the current temperature at each. One request fetches the current temperature for all ~190 capitals at once, and each dot is coloured from freezing (blue) to scorching (red).
fetching capital coordinates + live temperatures…
https://api.mapjson.com/v1/geo?...&properties=name,capital,capitalLat,capitalLng https://api.open-meteo.com/v1/forecast?latitude=LAT,LAT,…&longitude=LNG,LNG,…¤t=temperature_2m
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/topojson-client@3/dist/topojson-client.min.js"></script>
<style>
body { margin: 0; background: #0b1220; }
svg { width: 100%; height: auto; display: block; }
</style>
</head>
<body>
<svg id="map"></svg>
<script>
const W = 1200, H = 620;
const svg = d3.select("#map").attr("viewBox", `0 0 ${W} ${H}`);
const tt = d3.select("body").append("div").style("position","fixed").style("pointer-events","none").style("opacity",0).style("background","#0b1220").style("color","#eaf2ff").style("font","11px ui-monospace,monospace").style("padding","5px 9px").style("border","1px solid #24405f").style("border-radius","4px").style("z-index",10);
const projection = d3.geoNaturalEarth1().rotate([-11, 0]);
const path = d3.geoPath(projection);
const cT = d3.scaleSequential(t => d3.interpolateRdYlBu(1 - t)).domain([-10,40]).clamp(true);
d3.json("https://api.mapjson.com/v1/geo?layer=countries&filter=world&detail=low&properties=name,capital,capitalLat,capitalLng&format=geojson").then(world=>{
projection.fitExtent([[6,6],[W-6,H-6]], world);
svg.append("g").selectAll("path").data(world.features).join("path").attr("d", path)
.attr("fill","#17263b").attr("stroke","#26415f").attr("stroke-width",0.4);
const caps = world.features.map(f=>f.properties)
.filter(p=>p.capital && isFinite(p.capitalLat) && isFinite(p.capitalLng))
.map(p=>({name:p.capital, country:p.name, lat:+p.capitalLat, lng:+p.capitalLng}));
const lat = caps.map(c=>c.lat.toFixed(3)).join(","), lng = caps.map(c=>c.lng.toFixed(3)).join(",");
return fetch(`https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lng}¤t=temperature_2m`)
.then(r=>r.json()).then(res=>{
const arr = Array.isArray(res) ? res : [res];
caps.forEach((c,i)=> c.temp = arr[i] && arr[i].current ? arr[i].current.temperature_2m : null);
const got = caps.filter(c=>c.temp!=null);
svg.append("g").selectAll("circle").data(got).join("circle")
.attr("cx", d=>projection([d.lng,d.lat])[0]).attr("cy", d=>projection([d.lng,d.lat])[1])
.attr("r",4.2).attr("fill", d=>cT(d.temp)).attr("stroke","#0b1220").attr("stroke-width",0.7).style("cursor","pointer")
.on("mousemove",(e,d)=>{tt.style("opacity",1).style("left",(e.clientX+12)+"px").style("top",(e.clientY-8)+"px")
.html(`<b>${Math.round(d.temp)}°C</b> · ${d.name}, ${d.country}`);})
.on("mouseout",()=>tt.style("opacity",0));
const live = document.getElementById("live");
const t = arr[0] && arr[0].current ? arr[0].current.time.replace("T"," ") : "";
if (live) live.innerHTML = `<b>${got.length}</b> capitals · live from Open-Meteo (${t})`;
});
});
</script>
</body>
</html>