Published
Edited
Jun 23, 2020
4 stars
Insert cell
Insert cell
Insert cell
Insert cell
drawMap(() => ratio)
Insert cell
Insert cell
drawMap((polygons) => {
const extent = [Infinity, Infinity, -Infinity, -Infinity];
for (let polygon of polygons) {
for (let [x, y] of polygon) {
extent[0] = Math.min(extent[0], x);
extent[2] = Math.max(extent[2], x);
extent[1] = Math.min(extent[1], y);
extent[3] = Math.max(extent[3], y);
}
}
const dx = (extent[2] - extent[0]);
const dy = (extent[3] - extent[1]);
return dx / (dy || 1);
});
Insert cell
Insert cell
Insert cell
function drawMap(getRatio) {
const ctx = DOM.context2d(w, h);
ctx.canvas.style.maxWidth = "100%";
const path = d3.geoPath(null, ctx);
const labels = [];
for (const {properties: {name}, geometry} of geojson.features) {
if (name === 'District of Columbia') continue;
labels.push({ ...getLabelInfo(geometry, getRatio), name});
}
ctx.fillStyle = 'yellow';
ctx.beginPath();
for (const {pos, ratio} of labels) {
ctx.ellipse(pos[0], pos[1], pos.distance, pos.distance / ratio, 0, 0, Math.PI * 2, false);
ctx.closePath();
}
ctx.fill();
ctx.beginPath();
path(geojson);
ctx.lineWidth = 1;
ctx.strokeStyle = "#777";
ctx.stroke();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = '10px sans-serif';
ctx.fillStyle = 'black';
for (const {pos, name} of labels) {
ctx.fillText(name, pos[0], pos[1]);
}

return ctx.canvas;
}
Insert cell
polylabelStretched = (rings, ratio) => {
const polygon = [];
for (const ring of rings) { // stretch the input
const newRing = [];
for (const [x, y] of ring) newRing.push([x / ratio, y]);
polygon.push(newRing);
}
const result = polylabel(polygon, 0.5);
result[0] *= ratio; // stretch the result back
result.distance *= ratio;
return result;
}
Insert cell
getLabelInfo = (geometry, getRatio) => {
let pos, ratio;
if (geometry.type === 'MultiPolygon') {
let maxDist = 0; // for multipolygons, pick the polygon with most available space
for (const polygon of geometry.coordinates) {
const r = getRatio(polygon);
const p = polylabelStretched(polygon, r);
if (p.distance > maxDist) {
pos = p;
maxDist = p.distance;
ratio = r;
}
}
} else {
ratio = getRatio(geometry.coordinates);
pos = polylabelStretched(geometry.coordinates, ratio);
}
return { pos, ratio };
}
Insert cell
Insert cell
Insert cell
Insert cell
geojson = topojson.feature(states, states.objects.states)
Insert cell
states = (await fetch('https://cdn.jsdelivr.net/npm/us-atlas@3/states-albers-10m.json')).json()
Insert cell
topojson = require("topojson-client")
Insert cell
d3 = require("d3-geo")
Insert cell
polylabel = require('https://bundle.run/polylabel@1.1.0')
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