Published
Edited
Jan 9, 2021
1 fork
Importers
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
countries.features.map(d => d.properties.NAME).sort()
Insert cell
Insert cell
france = countries.features.find(d => d.properties.NAME === "France")
Insert cell
countries = topojson.feature(topology, topology.objects.ne_10m_admin_0_countries)
Insert cell
topojson = require("topojson-client@3") // converting TopoJSON to GeoJSON (required by D3)
Insert cell
topology = FileAttachment("10m-countries_topo.json").json()
Insert cell
Insert cell
bounds = ({
type: "Polygon",
coordinates: [
[
[-9.86, 51.56], [10.38, 51.56], [10.38, 41.15], [-9.86, 41.15], [-9.86, 51.56]
]
]
})
Insert cell
Insert cell
projection = d3.geoConicConformal() // Lambert's conic conformal having two standard parallels
.parallels([49, 44])
.rotate([-3, 0]) // see: https://stackoverflow.com/a/42267008/14273692
.center([0, 46.5])
.fitSize([width, height], bounds)
.clipExtent([[0, 0], [width, height]]) // for label placement (clipping observed by path.centroid)
Insert cell
d3 = require("d3@6")
Insert cell
Insert cell
createMap = () =>
createCanvas()
.call(addCountries)
.call(highlightFrance)
.call(addGraticule)
.call(addLabels);
Insert cell
createCanvas = () => {
const svg = d3.create("svg")
.attr("id", "map")
.attr("width", width)
.attr("height", height);
svg.append("path")
.attr("id", "sea")
.attr("d", path(outline));
return svg;
}
Insert cell
height = width / 4 * 3 // 4:3 ratio
Insert cell
path = d3.geoPath(projection)
Insert cell
outline = ({type: "Sphere"})
Insert cell
addCountries = (container) =>
container.append("path")
.attr("id", "land")
.attr("d", path(countries));
Insert cell
highlightFrance = (container) =>
container.append("path")
.attr("id", "france")
.attr("d", path(france));
Insert cell
addGraticule = (container) =>
container.append("path")
.attr("id", "graticule")
.attr("d", path(graticule));
Insert cell
graticule = d3.geoGraticule().step([5, 5])()
Insert cell
addLabels = (container) => {
const labels = container.append("g")
.attr("id", "labels");
const neighbours = countries.features.filter(d => d.properties.NAME !== "France");
for (const feature of neighbours) {
addLabel(labels, feature);
}
}
Insert cell
addLabel = (container, feature) => {
const centroid = path.centroid(feature);
const isVisible = centroid.every(coordinate => !Number.isNaN(coordinate));
if (isVisible) {
const label = container.append("g")
.attr("transform", `translate(${centroid.join(",")})`);

addText(label, feature.properties.NAME);
}
}
Insert cell
addText = (container, text) => {
container.append("text")
.attr("class", "background") // improves readability (when overlapping country borders and graticule lines)
.text(text);
container.append("text")
.text(text);
}
Insert cell
html`<style>
abbr[title] {
text-underline-position: under; /* 'auto' appears broken (Chrome) */
}

#map {
border: 1px solid #000;
}

#sea {
fill: #d3ebfa;
}

#land,
#france {
stroke: #7a7977;
}

#land {
fill: #f4e2ba;
}

#france {
fill: #fff3e3;
}

#graticule {
fill: none;
stroke: #ccc;
}

#labels {
font-family: sans-serif;
font-size: 10px;
text-anchor: middle;
}

.background {
stroke: #fff;
}
</style>`
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