block by fil c1b10942f61483d739dd601d09c30deb

Toric Voronoi

Full Screen

Using d3-voronoi on a torus.

The solution is to have height copies of each site, on N, NW, W, etc., using the cylinder technique twice.

The n first cells and n first sites are the originals.

We add a shadow property to copies of sites, and filter out links that have only shadow copies.

Made by Philippe Rivière from mbostock‘s block: Canvas Voronoi

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<canvas width="960" height="500"></canvas>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>

var canvas = d3.select("canvas").on("touchmove mousemove", moved).node(),
    context = canvas.getContext("2d"),
    width = canvas.width,
    height = canvas.height;
 

  var w = width * 0.6, h = height * 0.6;
  
  
var sites = d3.range(300)
    .map(function(d) { return [
      width / 2 + w * (0.5 - Math.random())
      ,height / 2 + h * (0.5 - Math.random())]; });

var voronoi = d3.voronoi()
    .extent([[-1, -1], [width + 1, height + 1]]);

  
redraw();

function moved() {
  var d = d3.mouse(this);
  if (d[0] < width/2 - w/2) d[0] += w;
  if (d[0] > width/2 + w/2) d[0] -= w;
  if (d[1] < height/2 - h/2) d[1] += h;
  if (d[1] > height/2 + h/2) d[1] -= h;
  sites[0] = d;
  redraw();
}


function redraw() {
  var sites2 = d3.merge([sites,
                  sites.slice().map(function(d){
                    d = [d[0]-w, d[1]];
                    d.shadow = true;
                    return d;
                  }),
                  sites.slice().map(function(d){
                    d = [d[0]+w, d[1]];
                    d.shadow = true;
                    return d;
                  }),
                 ]);
  var sites3 = d3.merge([sites2,
                  sites2.slice().map(function(d){
                    d = [d[0], d[1]-h];
                    d.shadow = true;
                    return d;
                  }),
                  sites2.slice().map(function(d){
                    d = [d[0], d[1]+h];
                    d.shadow = true;
                    return d;
                  }),
                 ]);
  
  var diagram = voronoi(sites3),
      links = diagram.links().filter(function(l){
        return !l.source.shadow || !l.target.shadow;
      });
      // remove spurious cells
      diagram.cells = diagram.cells.slice(0,sites.length);
      var polygons = diagram.polygons();

  context.clearRect(0, 0, width, height);
  context.strokeStyle = "#00b703";
  context.lineWidth = 3;
  context.beginPath();
  context.moveTo(width/2-w/2, 0);
  context.lineTo(width/2-w/2, height);
  context.stroke();
  context.beginPath();
  context.moveTo(width/2+w/2, 0);
  context.lineTo(width/2+w/2, height);
  context.stroke();
  context.beginPath();
  context.moveTo(0, height/2-h/2);
  context.lineTo(width, height/2-h/2);
  context.stroke();
  context.beginPath();
  context.moveTo(0, height/2+h/2);
  context.lineTo(width, height/2+h/2);
  context.stroke();
  context.lineWidth = 1;

  
  
  context.beginPath();
  drawCell(polygons[0]);
  context.fillStyle = "#f00";
  context.fill();

  context.beginPath();
  for (var i = 0, n = polygons.length; i < n; ++i) drawCell(polygons[i]);
  context.strokeStyle = "#000";
  context.stroke();

  context.beginPath();
  for (var i = 0, n = links.length; i < n; ++i) drawLink(links[i]);
  context.strokeStyle = "rgba(0,0,0,0.2)";
  context.stroke();

  context.beginPath();
  drawSite(sites[0]);
  context.fillStyle = "#fff";
  context.fill();

  context.beginPath();
  for (var i = 1, n = sites.length; i < n; ++i) drawSite(sites[i]);
  context.fillStyle = "#000";
  context.fill();
  context.strokeStyle = "#fff";
  context.stroke();
  context.beginPath();
  for (var i = sites.length, n = sites3.length; i < n; ++i) drawSite(sites3[i]);
  context.fillStyle = "#ccc";
  context.fill();
  context.strokeStyle = "#fff";
  context.stroke();

}

function drawSite(site) {
  context.moveTo(site[0] + 2.5, site[1]);
  context.arc(site[0], site[1], 2.5, 0, 2 * Math.PI, false);
}

function drawLink(link) {
  context.moveTo(link.source[0], link.source[1]);
  context.lineTo(link.target[0], link.target[1]);
}

function drawCell(cell) {
  if (!cell) return false;
  context.moveTo(cell[0][0], cell[0][1]);
  for (var j = 1, m = cell.length; j < m; ++j) {
    context.lineTo(cell[j][0], cell[j][1]);
  }
  context.closePath();
  return true;
}

</script>