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
xxxxxxxxxx
<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>
https://d3js.org/d3.v4.min.js