Public
Edited
Apr 24
Insert cell
Insert cell
topojson = await import("https://cdn.jsdelivr.net/npm/topojson-client@3/+esm")

Insert cell
fullD3 = await import("https://cdn.jsdelivr.net/npm/d3@7/+esm")

Insert cell
projection = d3.geoStereographic()
.scale(width / 4)
.translate([width / 2, height / 2])
.rotate([-27, 0])
.clipAngle(180 - 1e-4)
.clipExtent([[0, 0], [width, height]])
.precision(0.2)

Insert cell
info = html`<div style="font-family: sans-serif; padding: 1rem;"></div>`

Insert cell
world = FileAttachment("countries-110m.json").json()
.then(data => topojson.feature(data, data.objects.countries))

Insert cell
interactiveMap = {
const rawSvg = DOM.svg(width, height);
const svg = fullD3.select(rawSvg)
.attr("width", width)
.attr("height", height);

const path = fullD3.geoPath(projection);

// 🧭 Add graticule (latitude/longitude lines)
const graticule = fullD3.geoGraticule();
svg.append("path")
.datum(graticule)
.attr("fill", "none")
.attr("stroke", "#ccc")
.attr("stroke-width", 0.5)
.attr("stroke-opacity", 0.8)
.attr("d", path);

// 🎯 Focus countries
const focusCountries = ["CHN", "TUR", "RUS", "VEN", "SLV"];

const customColors = {
CHN: "#e63946",
TUR: "#457b9d",
RUS: "#1d3557",
VEN: "#ffb703",
SLV: "#2a9d8f"
};

svg.append("g")
.selectAll("path")
.data(world.features)
.join("path")
.attr("fill", d => {
const iso = d.properties.iso_a3;
return focusCountries.includes(iso) ? customColors[iso] : "#000";
})
.attr("stroke", "none")
.attr("d", path)
.on("click", (event, d) => {
if (focusCountries.includes(d.properties.iso_a3)) {
zoomToFeature(d);
showInfo(d.properties.iso_a3);
}
});


function zoomToFeature(feature) {
const [[x0, y0], [x1, y1]] = path.bounds(feature);
const dx = x1 - x0, dy = y1 - y0;
const scale = Math.min(width / dx, height / dy) * 0.9;

projection
.scale(scale * width / 4)
.translate([width / 2, height / 2])
.rotate([-fullD3.geoCentroid(feature)[0], -fullD3.geoCentroid(feature)[1]]);

svg.selectAll("path").attr("d", path);
}

function showInfo(iso) {
const headlines = {
CHN: `🇨🇳 China:
- 2015: [State Dept. Human Rights Report](https://2009-2017.state.gov/j/drl/rls/hrrpt/2015/eap/252755.htm)
- 2018: [Axios: Asylum](https://www.axios.com/2018/04/19/china-political-asylum-immigration-one-child-policy)
- 2021: [Economist](https://www.economist.com/graphic-detail/2021/07/28/under-xi-jinping-the-number-of-chinese-asylum-seekers-has-shot-up)`,
TUR: `🇹🇷 Turkey:
- [NPR: Military Officers](https://www.npr.org/2019/05/29/727796635/growing-number-of-turkish-military-officers-seek-asylum-in-the-u-s)
- [Guardian](http://theguardian.com/us-news/2025/mar/06/turkey-us-immigration-asylum-denial)`,
RUS: `🇷🇺 Russia:
- [RadioFreeEurope](https://www.rferl.org/a/russia-asylum-seekers-us-increase/29032910.html)
- [AZPM](https://www.azpm.org/p/news-topical-national/2018/2/13/124252-russian-asylum-seekers-increase/)`,
VEN: `🇻🇪 Venezuela:
- [Pew Research](https://www.pewresearch.org/short-reads/2017/08/03/venezuelan-asylum-applications-to-u-s-soar-in-2016/)
- [AP, Guardian, CFR, Wikipedia links]`,
SLV: `🇸🇻 El Salvador:
- [Guardian 2014](https://www.theguardian.com/world/2014/jun/20/us-immigration-central-america-texas-detention)
- [Guardian 2015](https://www.theguardian.com/us-news/2015/oct/12/obama-immigration-deportations-central-america)
- [HRW 2020](https://www.hrw.org/report/2020/02/05/deported-danger/united-states-deportation-policies-expose-salvadorans-death-and)`
};

info.innerHTML = headlines[iso] || "No data available";
}

return rawSvg;
}

Insert cell
height = Math.max(640, width)
Insert cell
import {d3, map} with {projection, height} from "@d3/world-map"
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more