Our World in Data publishes the renewable share of each country's electricity, keyed by plain country names. Most match mapjson straight away — but some are spelled differently: Turkey vs Türkiye, East Timor vs Timor-Leste, Cote d'Ivoire vs Ivory Coast. Passing the names through mapjson's /v1/resolve maps every one to the right country (and would drop any non-country aggregates), then the values colour the map.
fetching Our World in Data + resolving names…
// live data — Our World in Data (keyed by country names)
https://ourworldindata.org/grapher/share-electricity-renewables.csv?csvType=filtered
// resolve those names to countries — mapjson ontology (up to 1000 keys per call)
POST https://api.mapjson.com/v1/resolve { "keys": ["Turkey", "East Timor", "Czechia", …] }
// geometry — mapjson
https://api.mapjson.com/v1/geo?layer=countries&filter=world&detail=medium&properties=iso2,name
<!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: #eef2ec; }
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","#1c2833").style("color","#fff").style("font","11px ui-monospace,monospace").style("padding","5px 9px").style("border-radius","4px").style("z-index",10);
const projection = d3.geoNaturalEarth1().rotate([-11, 0]);
const path = d3.geoPath(projection);
const color = d3.scaleSequential(d3.interpolateGreens).domain([0, 100]).clamp(true);
const COL = "renewable_share_of_electricity__pct";
// One call — /v1/resolve accepts up to 1000 keys. Track any names the ontology had to fix.
async function resolveByName(names){
const res = await fetch("https://api.mapjson.com/v1/resolve", {
method:"POST", headers:{"Content-Type":"application/json"}, body: JSON.stringify({ keys: names })
}).then(r=>r.json());
const iso = {}, fixes = [];
res.results.forEach(r=>{
if (r.crosswalk && r.crosswalk.iso2 && r.status !== "miss"){
iso[r.key] = r.crosswalk.iso2;
if (r.name && r.name !== r.key) fixes.push(`${r.key} → ${r.name}`);
}
});
return { iso, fixes };
}
(async function(){
const [rows, world] = await Promise.all([
d3.csv("https://ourworldindata.org/grapher/share-electricity-renewables.csv?csvType=filtered&useColumnShortNames=true"),
d3.json("https://api.mapjson.com/v1/geo?layer=countries&filter=world&detail=medium&properties=iso2,name&format=geojson"),
]);
const valByName = {}; rows.forEach(r => { if (r[COL] !== "" && r[COL] != null) valByName[r.entity] = +r[COL]; });
const { iso: iso2ByName, fixes } = await resolveByName(Object.keys(valByName));
const valByIso = {}; for (const [n,v] of Object.entries(valByName)){ const iso = iso2ByName[n]; if (iso) valByIso[iso] = v; }
projection.fitExtent([[6,6],[W-6,H-6]], world);
svg.append("g").selectAll("path").data(world.features).join("path").attr("d", path)
.attr("fill", d => valByIso[d.properties.iso2] != null ? color(valByIso[d.properties.iso2]) : "#dfe3da")
.attr("stroke", "#ffffff").attr("stroke-width", 0.4).style("cursor","default")
.on("mousemove",(e,d)=>{ const v = valByIso[d.properties.iso2];
tt.style("opacity",1).style("left",(e.clientX+12)+"px").style("top",(e.clientY-8)+"px")
.html(`${d.properties.name||""}${v!=null?" · <b>"+Math.round(v)+"%</b> renewable":""}`);})
.on("mouseout",()=>tt.style("opacity",0));
const live = document.getElementById("live");
if (live) live.innerHTML = `<b>${Object.keys(valByIso).length}</b> countries joined by name in one <code>/v1/resolve</code> call. `
+ (fixes.length ? `${fixes.length} needed the ontology — e.g. ${fixes.slice(0,3).join(", ")} — spellings a plain text match would silently drop.` : `every name matched outright.`);
})();
</script>
</body>
</html>