← Examples
countries · halftone · print · art

Halftone Earth

The world as a printer's halftone screen. The land is painted to a hidden canvas, softly blurred, and then sampled on a regular grid — each cell becomes a dot whose size grows the further inland it sits. Continents read as solid ink at their hearts and dissolve into a scatter of dots along every coast.

API Call

https://api.mapjson.com/v1/geo?layer=countries&filter=world&detail=medium

Code

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/topojson-client@3/dist/topojson-client.min.js"></script>
  <style>
    body { margin: 0; background: #f7f3ea; }
    svg { width: 100%; height: auto; display: block; }
  </style>
</head>
<body>
  <svg id="map"></svg>
  <script>
  const W = 1100, H = 620;
  const svg = d3.select("#map").attr("viewBox", `0 0 ${W} ${H}`);
  const projection = d3.geoNaturalEarth1().rotate([-11, 0]);
  d3.json("https://api.mapjson.com/v1/geo?layer=countries&filter=world&detail=medium&format=geojson").then(fc=>{
    projection.fitExtent([[16,16],[W-16,H-16]], fc);
    // paint land white on black, blur it, and read the blur as "how far inland" (0 at coast → 1 deep inland)
    const a = document.createElement("canvas"); a.width = W; a.height = H;
    const ac = a.getContext("2d"); const gp = d3.geoPath(projection, ac);
    ac.fillStyle = "#000"; ac.fillRect(0,0,W,H);
    ac.fillStyle = "#fff"; fc.features.forEach(f=>{ ac.beginPath(); gp(f); ac.fill(); });
    const b = document.createElement("canvas"); b.width = W; b.height = H;
    const bc = b.getContext("2d"); bc.filter = "blur(7px)"; bc.drawImage(a, 0, 0);
    const px = bc.getImageData(0,0,W,H).data;
    // halftone dot grid — radius grows with inland-ness, so continents are solid inside, dotty at the coast
    const g = svg.append("g"); const S = 9, RMAX = S * 0.62;
    for (let y = S/2; y < H; y += S) for (let x = S/2; x < W; x += S){
      const v = px[((y|0)*W + (x|0))*4] / 255; if (v < 0.06) continue;
      g.append("circle").attr("cx", x).attr("cy", y).attr("r", Math.min(RMAX, Math.sqrt(v)*RMAX)).attr("fill", "#232733");
    }
  });
  </script>
</body>
</html>