Published
Edited
Jan 11, 2021
Insert cell
Insert cell
Insert cell
Insert cell
viewof context = {
const context = DOM.context2d(width, height);
context.canvas.style.display = "block";
context.canvas.style.maxWidth = "100%";
context.canvas.value = context;
return context.canvas;
}
Insert cell
function render(projection) {
const path = d3.geoPath(projection, context);
context.clearRect(0, 0, width, height);
context.save();
context.beginPath(), path(outline), context.clip(), context.fillStyle = "#fff", context.fillRect(0, 0, width, height);
context.beginPath(), path(graticule), context.strokeStyle = "#ccc", context.stroke();
context.beginPath(), path(land), context.fillStyle = "#000", context.fill();
context.restore();
context.beginPath(), path(outline), context.strokeStyle = "#000", context.stroke();
}
Insert cell
update = {
const r0 = mutable previousProjection;
const r1 = projection;
if (r0 === r1) return;
mutable previousProjection = r1;
const interpolate = interpolateProjection(r0, r1);
for (let j = 1, m = 45; true; ++j) {
const t = Math.min(1, ease(j / m));
render(interpolate(t).rotate([performance.now() / 100, 0]));
yield;
}
}
Insert cell
mutable previousProjection = d3.geoEquirectangularRaw
Insert cell
function interpolateProjection(raw0, raw1) {
const {scale: scale0, translate: translate0} = fit(raw0);
const {scale: scale1, translate: translate1} = fit(raw1);
return t => d3.geoProjection((x, y) => lerp2(raw0(x, y), raw1(x, y), t))
.scale(lerp1(scale0, scale1, t))
.translate(lerp2(translate0, translate1, t))
.precision(0.1);
}
Insert cell
function lerp1(x0, x1, t) {
return (1 - t) * x0 + t * x1;
}
Insert cell
function lerp2([x0, y0], [x1, y1], t) {
return [(1 - t) * x0 + t * x1, (1 - t) * y0 + t * y1];
}
Insert cell
function fit(raw) {
const p = d3.geoProjection(raw).fitExtent([[0.5, 0.5], [width - 0.5, height - 0.5]], outline);
return {scale: p.scale(), translate: p.translate()};
}
Insert cell
ease = d3.easeCubicInOut
Insert cell
width = 954
Insert cell
height = 600
Insert cell
outline = ({type: "Sphere"})
Insert cell
graticule = d3.geoGraticule10()
Insert cell
land = topojson.feature(world, world.objects.land)
Insert cell
world = FileAttachment("land-110m.json").json()
Insert cell
topojson = require("topojson-client@3")
Insert cell
d3 = require("d3-geo@2", "d3-geo-projection@3", "d3-ease@2")
Insert cell
projections = [
{name: "Aitoff", value: d3.geoAitoffRaw, Q: 66.6 },
{name: "American polyconic", value: d3.geoPolyconicRaw, Q: 47.9},
{name: "August", value: d3.geoAugustRaw, Q: 20.7},
{name: "Boggs’ eumorphic", value: d3.geoBoggsRaw,Q: 59.8},
{name: "Bonne", value: d3.geoBonneRaw(Math.PI / 4), Q: 53.3},
{name: "Collignon", value: d3.geoCollignonRaw, Q: 32.4},
{name: "Craster parabolic", value: d3.geoCrasterRaw, Q: 61.5},
{name: "cylindrical equal-area", value: d3.geoCylindricalEqualAreaRaw(38.58 / 180 * Math.PI), Q: 75.4},
{name: "Eckert I", value: d3.geoEckert1Raw, Q: 65.8},
{name: "Eckert II", value: d3.geoEckert2Raw, Q: 57.3},
{name: "Eckert III", value: d3.geoEckert3Raw, Q: 79.8},
{name: "Eckert IV", value: d3.geoEckert4Raw, Q: 82.5},
{name: "Eckert V", value: d3.geoEckert5Raw, Q: 76.1},
{name: "Eckert VI", value: d3.geoEckert6Raw, Q: 68.7},
{name: "Foucaut’s stereographic equivalent", value: d3.geoFoucautRaw, Q: 44.3},
{name: "Ginzburg V", value: d3.geoGinzburg5Raw, Q: 84.7},
{name: "Ginzburg VI", value: d3.geoGinzburg6Raw, Q: 73.2},
{name: "Ginzburg VIII", value: d3.geoGinzburg8Raw, Q: 63.4},
{name: "Hammer", value: d3.geoHammerRaw(2), Q: 64.3},
{name: "Hill eucyclic", value: d3.geoHillRaw(1), Q: 78.2},
{name: "Hufnagel pseudocylindrical", value: d3.geoHufnagelRaw(1, 0, Math.PI / 4, 2), Q: 82.1},
{name: "Kavrayskiy VII", value: d3.geoKavrayskiy7Raw, Q: 82},
{name: "Lagrange conformal", value: d3.geoLagrangeRaw(0.5), Q: 27},
{name: "Mollweide", value: d3.geoMollweideRaw, Q: 70.4},
{name: "Nell–Hammer", value: d3.geoNellHammerRaw, Q: 69.5},
{name: "Robinson", value: d3.geoRobinsonRaw, Q: 82.6},
{name: "sinusoidal", value: d3.geoSinusoidalRaw, Q: 72.4},
{name: "sinu-Mollweide", value: d3.geoSinuMollweideRaw, Q: 70.4},
{name: "Tobler hyperelliptical", value: d3.geoHyperellipticalRaw(0, 2.5, 1.183136), Q: 75.9},
{name: "Van der Grinten", value: d3.geoVanDerGrintenRaw, Q: 56.5},
{name: "Van der Grinten III", value: d3.geoVanDerGrinten3Raw, Q: 64},
{name: "Wagner VI", value: d3.geoWagner6Raw, Q: 79.5},
{name: "Wagner VII", value: d3.geoWagnerRaw(65 / 180 * Math.PI, 60 / 180 * Math.PI, 0, 200), Q: 74},
{name: "Wagner VIII", value: d3.geoWagnerRaw(65 / 180 * Math.PI, 60 / 180 * Math.PI, 20, 200), Q: 80},
{name: "Werner", value: d3.geoBonneRaw(Math.PI / 2), Q: 49.5},
{name: "Winkel tripel", value: d3.geoWinkel3Raw, Q: 81.7}
]
Insert cell
function projectionInput({name = "", value} = {}) {
projections.sort( (a,b) => b.Q - a.Q );
const form = html`<form><select name=i>${projections.map(p => {
return Object.assign(html`<option>`, {
textContent: p.name,
selected: p.name === value
});
})}</select> <i style="font-size:smaller;">${name}</i>`;
form.onchange = () => form.dispatchEvent(new CustomEvent("input")); // Safari
form.oninput = (event) => {
if (event && event.isTrusted) form.onchange = null;
form.value = projections[form.i.selectedIndex].value;
};
form.oninput();
return form;
}
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