Public
Edited
Feb 15, 2023
6 stars
Contour Labels (canvas)Coordinated mapsgeoformatSmall circle testKruskal MazeAll the geoshapesReprojecting Vector TilesCloud ContoursHello h3-jsClipping AlbersTranslucent EarthThe truth about the Mercator projectionUsing d3-inertia with observableMercator projection of a Mercator globeD3 Vector Tiles (WIP)Finding intersections between the graticule and the clip sphere of the stereographic projection, method 2Plate tectonicsAnother hex mapEquateur & tropiquesTissot's indicatrixDistance to shoreMultiPolygon clippingSouth Africa’s medial axisSpherical intersectionVolcano Semis (points circulaires)Pencil Airocean45° mapNetCDFBlue noise sphereRubber DymaxionSpherical quasi-random (R2) distributionAutomated label placement (countries)Automated label placement (France)Automated label placement (cities)d3.geoIntersectArcDelaunay.findTriangled3-geo-voronoi and gridded dataElevation vtk.jsMapfillKrigingSpherical HeatmapReproject elevation tiles — detailReproject elevation tiles — worldFisheye Conformal MapSpherical KDE InterpolationSpherical kernel interpolation with nearest neighborsShepard’s methodModified Shepard’s methodSpherical contoursGeo Voronoi interpolationBlurry contoursHow much warmer? (BBC)H3 hexagons & geoContoursHillshading & supersamplingH3 odditiesManhattan VoronoiManhattan Voronoi IIGeoJSON feature editorColorized Manhattan Spanning Treelegra mapslegra country mapsThe complex logarithm projectionCountries small multipleThe 2D approximate Newton-Raphson methodOceanAttitudeCount visible objectsThe Gray-Fuller spatial gridGray-Fuller grid metricsGray-Fuller grid odditiesSpherical smallest-circle problemBounding CirclesCountries Enclosing CirclesFullscreen Seamless Zoomable Map TilesMap Pan & ZoomSpherical EllipsesSynchronized projectionsThe closest countryTriangular tiling of icosahedronHello, polygon-clippingCorées / KoreasHello, procedural-glHello, placekeyZoom World ChoroplethClipping spherical polygonsSpherical phyllotaxisFour-color world map with ClingoHello, jsgeoda!The Sun’s analemmaWorld of squares
World of squares (spherical)
A map of AfricaTagged bordersClipped geoVoronoiBlue noise sphere IISpherical Perlin NoiseSpherical Delaunay triangulationDynamic simplificationRewindPlot: Voronoi labelsAoC 12: shortest path under constraintsHello, pixi.jsFlight PathsRay out of a convex hullDistance to a segmentHello, A5
Also listed in…
Algorithms
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
squares = {
// this file was computed with a target quality of 1%
// ((the whole process took the browser 4 hours))
if (high)
return yield new Map(await FileAttachment("squares-001.json").json());

const squares = new Map();
for (const f of d3.sort(world.features, d3.geoArea).reverse()) {
squares.set(f.id, await qualityToeplitz(f));
yield squares;
await Promises.delay(30);
}
}
Insert cell
quality = 0.25 // try 0.05 to create high-quality squares
Insert cell
mutable current = null
Insert cell
async function qualityToeplitz(f) {
let scale = 60,
t,
e = Infinity;
while (e > quality) {
mutable current = { id: f.id, name: names.get(f.id), scale };
t = await toeplitz(points(f, scale));
e = errorDiagonal(t) + 2 * errorEdges(t);
scale *= 1.3;
}
return t;
}
Insert cell
toeplitz = async function (points) {
const test = testSquare(points);
let min = 0;
let solution;

for (let i = 0; i < points.length; i++) {
for (let j = 0; j < i; j++) {
const a = points[i];
const b = points[j];
const dist2 = d3.geoDistance(a, b);
if (dist2 <= min) continue;
const m = d3.geoInterpolate(a, b)(0.5); // center
const r = attitude({ axis: m, angle: -90 });
const c = r(a);
const d = r(b);
const t = test(a, c, b, d);
if (t) {
min = dist2;
solution = t;
}
}
if (i % 100 === 0) await Promises.delay(10);
}
return solution;
}
Insert cell
points = (feature, scale) => {
const c = d3.geoCentroid(feature);
const projection = d3
.geoAzimuthalEquidistant()
.rotate([-c[0], -c[1]])
.fitSize([scale, scale], feature);
const geopath = d3.geoPath().projection(projection);

const path = svg`<path d="${geopath(feature)}" stroke=black fill=none>`;
const l = path.getTotalLength();

const pts = d3.range(0, l).map((i) => {
const p = path.getPointAtLength(i);
return projection.invert([p.x, p.y]);
});
const d0 = d3.geoDistance(pts[0], pts[1]) * (15500 / Math.PI);

// shuffling makes the algorithm a bit faster
return Object.assign(d3.shuffle(pts), { d0 });
}
Insert cell
// todo: for better performance, we could probably use a quadtree on the locally projected shape
function testSquare(points) {
const q = new kdbush(points);
const find = (c) => {
return geokdbush.around(q, ...c, 1, /* in kilometers! */ points.d0)[0];
};
return (a, c, b, d) => (c = find(c)) && (d = find(d)) && [a, c, b, d];
}
Insert cell
Insert cell
update_map = {
const g = d3.select(map).select("g");
g.html("")
.selectAll()
.data(squares)
.join("path")
.style("stroke", ([id, a]) => (a ? "red" : "blue"))
.datum(([id, a]) =>
a
? { type: "Polygon", coordinates: [[...a, a[0]]], id }
: {
type: "Point",
coordinates: d3.geoCentroid(
world.features.find((p) => p.id === id)
),
id
}
)
.append("title")
.text(({ id }) => names.get(id));
map.render();
}
Insert cell
Insert cell
Insert cell
errorDiagonal = ([a, b, c, d]) =>
Math.abs(Math.log(d3.geoDistance(a, c) / d3.geoDistance(b, d)))
Insert cell
errorEdges = ([a, b, c, d]) => {
const [lo, hi] = d3.extent(
d3.pairs([a, b, c, d, a]).map(([a, b]) => d3.geoDistance(a, b))
);
return Math.log(hi / lo);
}
Insert cell
median_errors = [
d3.median(squares, ([, d]) => errorDiagonal(d)),
d3.median(squares, ([, d]) => errorEdges(d))
]
Insert cell
Insert cell
topo = d3.json(
"https://cdn.jsdelivr.net/npm/visionscarto-world-atlas@0.1.0/world/110m.json"
)
Insert cell
world = topojson.feature(topo, topo.objects.countries)
Insert cell
land = topojson.feature(topo, topo.objects.land)
Insert cell
names = d3
.tsv(
"https://cdn.jsdelivr.net/npm/visionscarto-world-atlas@0.0.6/world/110m.tsv"
)
.then((d) => new Map(d.map(({ id, name_long }) => [id, name_long])))
Insert cell
height = width
Insert cell
d3 = require("d3@7")
Insert cell
attitude = require("attitude")
Insert cell
Insert cell
kdbush = import("https://cdn.skypack.dev/kdbush@3").then((d) => d.default)
Insert cell
geokdbush = import("https://cdn.skypack.dev/geokdbush@1").then((d) => d.default)
Insert cell
import { zoom } from "@d3/versor-zooming"
Insert cell
import { degrees } from "@fil/math"
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