Published
Edited
Feb 15, 2019
Insert cell
Insert cell
Insert cell
{
const offscreenContext = DOM.context2d(width, height, 1);
const offscreenPath = d3.geoPath(projection, offscreenContext);
const context = DOM.context2d(width, height);
const path = d3.geoPath(projection, context);

// Initialize the context’s path with the desired boundary.
for (const c of countries){
offscreenContext.beginPath();
offscreenPath(c);
// hacky random color string
context.fillStyle = '#' + (Math.random().toString(16) + "000000").substring(2, 8);
// Iterate over the grid and test whether points are inside.
for (let j = 0, y = j * dy; y < height + r; y += dy, ++j) {
for (let x = (j & 1) * dx / 2; x < width + dx / 2; x += dx) {
if (offscreenContext.isPointInPath(x, y)) {
context.beginPath();
hexagonPath(context, x, y, r);
context.fill();
}
}
yield context.canvas;
}
}
}
Insert cell
function hexagonPath(context, x, y, r) {
context.moveTo(x, y - r);
for (let a = 1; a < 6; ++a) {
const angle = a * Math.PI / 3;
context.lineTo(x + Math.sin(angle) * r, y - Math.cos(angle) * r);
}
context.closePath();
}
Insert cell
Insert cell
function cells(j) {
const offscreenContext = DOM.context2d(width, height, 1);
const offscreenPath = d3.geoPath(projection, offscreenContext);

const rings = [];

// Initialize the context’s path with the desired boundary.
offscreenContext.beginPath();
offscreenPath(countries[j]);

// Iterate over the grid and test whether points are inside.

for (let j = 0, y = j * dy; y < height + r; y += dy, ++j) {
for (let x = (j & 1) * dx / 2; x < width + dx / 2; x += dx) {
if (offscreenContext.isPointInPath(x, y)) {
rings.push(hexagonPoints(x, y, r));
}
}
}

return {
type: "MultiPolygon",
coordinates: rings.map(ring => [ring])
};
}
Insert cell
function hexagonPoints(x, y, r) {
const points = [[x, y - r]];
for (let a = 1; a < 6; ++a) {
const angle = a * Math.PI / 3;
points.push([x + Math.sin(angle) * r, y - Math.cos(angle) * r]);
}
points.push([x, y - r]);
return points;
}
Insert cell
Insert cell
// The following works but is slow and useless

// {
// const context = DOM.context2d(width, height);
// const path = d3.geoPath(null, context);
// context.beginPath();
// for (let j = 0; j < countries.length; j++) {
// path(cells(j));
// context.fillStyle = "#eee";
// context.fill();
// context.stroke();
// }
// return context.canvas;
// }
Insert cell
Insert cell
topology = (j) => topojson.topology({cells:cells(j)}, 1e6)
Insert cell
Insert cell
merge = j => {
const top = topology(j);
return topojson.merge(top, [top.objects.cells]);
}
Insert cell
{
const context = DOM.context2d(width, height);
const path = d3.geoPath(null, context);
context.beginPath();
for (let j = 0; j<countries.length; j++) {
path(merge(j));
context.fillStyle = "#eee";
context.fill();
context.stroke();
}
return context.canvas;
}
Insert cell
Insert cell
Insert cell
Insert cell
height = {
const [[minX, minY], [maxX, maxY]] = d3.geoPath(projection).bounds({type: "Sphere"});
return Math.round(maxY - minY + dy);
}
Insert cell
projection = d3.geoEqualEarth().fitWidth(width, {type: "Sphere"})
Insert cell
countries = topojson.feature(world, world.objects.countries).features
Insert cell
land = topojson.feature(world, world.objects.land)
Insert cell
world = d3.json("https://cdn.jsdelivr.net/npm/world-atlas@1/world/110m.json")
Insert cell
topojson = require("topojson-server@3", "topojson-client@3")
Insert cell
d3 = require("d3@5")
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