Group data points around the largest points.
The recipe is as follows:
1) sort data points according to their sizes (here size = d[3]
)
2) use top 10% points as Voronoi sites
3) bin all data points according to their Voronoi cell, using voronoi.find(). The binning is rendered by using the Voronoi sites’s color.
See also the static version — much less code to parse!
Original work by Philippe Rivière for d3-voronoi (issue 17).
<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
d3.interval(algo, 9000);
algo();
function algo() {
var color = d3.scaleOrdinal().range(d3.schemeCategory20);
var n = 4000;
var data = d3.range(n)
.map(function(d,i) { return [Math.random() * width, Math.random() * height, color(i), Math.random()]; });
var polygon = svg.append("g")
.attr("class", "polygons");
var voronoi = d3.voronoi()
.size([width, height]);
var sites = data.sort(function(a,b) {
return d3.descending(a[3], b[3]);
})
.slice(0,n/10);
sites.forEach(function(d) {
d[4] = true;
})
var dots = d3.select('svg')
.append('g')
.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('r', function(d) {return 10 * d[3] * d[3];})
.attr('transform', function(d) { return 'translate('+ [ d[0], d[1] ] +')'; })
.attr('fill', 'white')
.attr('stroke', '#444')
.attr('stroke-width', 1)
.attr('opacity', 1)
;
var diagram = voronoi(sites);
diagram.find = function (x, y, radius){
// optimization: start from most recent result
var i, next = diagram.find.found || Math.floor(Math.random() * diagram.cells.length);
var cell = diagram.cells[next] || diagram.cells[next=0];
var dx = x - cell.site[0],
dy = y - cell.site[1],
dist = dx*dx + dy*dy;
do {
cell = diagram.cells[i=next];
next = null;
cell.halfedges.forEach(function(e) {
var edge = diagram.edges[e];
var ea = edge.left;
if (ea === cell.site || !ea) {
ea = edge.right;
}
if (ea){
var dx = x - ea[0],
dy = y - ea[1],
ndist = dx*dx + dy*dy;
if (ndist < dist){
dist = ndist;
next = ea.index;
return;
}
}
});
} while (next !== null);
diagram.find.found = i;
if (!radius || dist < radius * radius) return cell.site;
};
polygon.selectAll("path")
.data(diagram.polygons())
.enter().append("path")
.attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; })
.attr("fill", function(d,i) { return sites[i][2] })
.attr("fill-opacity", 0.001)
.attr('stroke', 'white')
.transition()
.delay(2000)
.duration(2000)
.attr("stroke", '#777')
.attr("fill-opacity", 0.2)
dots.transition()
.duration(2000)
.attr('fill', function(d) { return d[4] ? d[2] : 'white'; })
.attr('stroke-width', function(d) { return d[4] ? 4 : 0.5; })
;
dots
.transition()
.delay(1000)
.attr('opacity', function(d) { return d[4] ? 1 : 0.4; });
dots
.transition()
.delay(4000)
.duration(2000)
.attr('fill', function(d) {
var found = diagram.find(d[0],d[1]);
return sites[found.index][2];
})
.attr('opacity', 1);
polygon
.attr('opacity', 1)
.transition().delay(6000)
.duration(2000)
.attr('opacity', 0.1)
.remove();
svg.selectAll('g')
.attr('opacity', 1)
.transition()
.delay(8000)
.duration(500)
.attr('opacity', 0)
.remove();
}
</script>