The world reduced to Mondrian's grammar: the plane recursively split into rectangles, ruled off in thick black, and painted in three primaries on an off-white ground — with the catch that red, blue, and yellow only pool where the land is. Ocean cells stay white. Squint and the continents are still there, hiding inside a Composition in Red, Blue and Yellow. One /v1/geo call.
The mapjson geometry is rasterized once to an offscreen canvas as a land mask. The canvas is then split by recursive guillotine cuts — each rectangle divided in two, mostly across its longer side, at an off-centre line — until the cells reach Mondrian-ish proportions. For each cell the land coverage is measured against the mask: a mostly-water cell stays off-white, while land cells are flooded with a primary (redder where the land is densest). Thick black rules and a heavy frame finish it. Every recompose reseeds the cuts, so the same continents fall into a new arrangement each time.
https://api.mapjson.com/v1/geo?layer=countries&filter=world&detail=medium&format=geojson
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Composition with Continents — after Mondrian</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { margin: 0; background: #fafafa; font-family: system-ui, sans-serif; }
.wrap { max-width: 980px; margin: 2rem auto; padding: 0 1rem; }
#map { background: #f4f1e9; border-radius: 4px; overflow: hidden; }
#map svg { width: 100%; display: block; }
button { margin-top: 0.7rem; font: 12px ui-monospace, monospace; padding: 6px 12px; background: #fff; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; }
</style>
</head>
<body>
<div class="wrap">
<div id="map"></div>
<button id="repaint">↻ recompose</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
<script>
const W = 960, H = 620;
const svg = d3.select("#map").append("svg").attr("viewBox", `0 0 ${W} ${H}`);
const off = document.createElement("canvas"); off.width = W; off.height = H;
const octx = off.getContext("2d");
const WHITE = "#f6f2e8", BLACK = "#141414";
const RED = "#d1332a", BLUE = "#1f4fa3", YEL = "#f2c33d";
// recursive guillotine subdivision → Mondrian-ish rectangles
function split(x, y, w, h, depth, out) {
const MIN = 68;
const stop = depth <= 0 || (w < MIN * 1.7 && h < MIN * 1.7) || (Math.random() < 0.22 && w < MIN * 3.2 && h < MIN * 3.2);
if (stop) { out.push({ x, y, w, h }); return; }
const horiz = w > h ? Math.random() < 0.78 : Math.random() < 0.22; // cut across the longer side, mostly
const t = 0.32 + Math.random() * 0.36;
if (horiz) { const c = Math.round(w * t); split(x, y, c, h, depth - 1, out); split(x + c, y, w - c, h, depth - 1, out); }
else { const c = Math.round(h * t); split(x, y, w, c, depth - 1, out); split(x, y + c, w, h - c, depth - 1, out); }
}
let land = null;
function paint(world) {
if (world) {
const proj = d3.geoEqualEarth().rotate([-11, 0]).fitExtent([[30, 40], [W - 30, H - 40]], { type: "Sphere" });
const p = d3.geoPath(proj, octx);
octx.fillStyle = "#000"; octx.fillRect(0, 0, W, H);
octx.beginPath(); world.features.forEach((f) => p(f)); octx.fillStyle = "#fff"; octx.fill();
land = octx.getImageData(0, 0, W, H).data;
}
const isLand = (x, y) => { x |= 0; y |= 0; return x >= 0 && y >= 0 && x < W && y < H && land[(y * W + x) * 4] > 128; };
const landFrac = (c) => {
let n = 0, t = 0;
for (let sy = c.y + 5; sy < c.y + c.h - 5; sy += 7) for (let sx = c.x + 5; sx < c.x + c.w - 5; sx += 7) { t++; if (isLand(sx, sy)) n++; }
return t ? n / t : 0;
};
const cells = [];
split(0, 0, W, H, 6, cells);
for (const c of cells) {
const lf = landFrac(c);
if (lf < 0.12 ? Math.random() < 0.04 : Math.random() < 0.72) {
c.fill = lf > 0.55 ? RED : lf > 0.28 ? (Math.random() < 0.6 ? BLUE : RED) : (Math.random() < 0.6 ? YEL : BLUE);
} else c.fill = WHITE;
}
svg.selectAll("*").remove();
svg.append("rect").attr("width", W).attr("height", H).attr("fill", WHITE);
svg.append("g").selectAll("rect").data(cells).join("rect")
.attr("x", (c) => c.x).attr("y", (c) => c.y).attr("width", (c) => c.w).attr("height", (c) => c.h)
.attr("fill", (c) => c.fill).attr("stroke", BLACK).attr("stroke-width", 7);
svg.append("rect").attr("x", 3).attr("y", 3).attr("width", W - 6).attr("height", H - 6)
.attr("fill", "none").attr("stroke", BLACK).attr("stroke-width", 11);
}
d3.json("https://api.mapjson.com/v1/geo?layer=countries&filter=world&detail=medium&format=geojson").then((w) => paint(w));
document.getElementById("repaint").addEventListener("click", () => paint(null));
</script>
</body>
</html>