Made some changes for using this as a badge for d3.unconf. The background color is now black and the stroke color is now white for a cleaner look with the badge’s color scheme. It also sorts vertically to fit with the badge’s orientation.
The previous versions are here: Voronoi Shuffling v1, Voronoi Shuffling v2, Voronoi Shuffling v3, & Voronoi Sorting v2
forked from alexmacy‘s block: Voronoi Sorting v2 update for d3.unconf badge
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<style>
body {
background-color: black;
margin: 0;
}
path {
fill-opacity: .75;
stroke: white;
}
</style>
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="//d3js.org/d3-scale-chromatic.v1.min.js"></script>
</head>
<body></body>
<script>
var width = window.innerWidth,
height = window.innerHeight,
samples = [],
minMax;
var voronoi = d3.voronoi()
.size([width, height])
var schemes = ["interpolateBlues", "interpolateGreens", "interpolateGreys",
"interpolateOranges", "interpolatePurples", "interpolateReds",
"interpolateBuGn", "interpolateBuPu", "interpolateGnBu",
"interpolateOrRd", "interpolatePuBuGn","interpolatePuBu",
"interpolatePuRd", "interpolateRdPu", "interpolateYlGnBu",
"interpolateYlGn", "interpolateYlOrBr", "interpolateYlOrRd"]
//the size of the polygon will determine it's fill color - and later, it's placement along the x axis
var logForColor = d3.scaleLog().range([1,0]),
color = d3[schemes[Math.floor(d3.randomUniform(0,schemes.length)())]],
y = d3.scaleLog().range([height,0]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
getData();
var paths = svg.selectAll("path")
.data(samples)
.enter().append("path")
paths.attr("transform", "translate(0,0)")
.attr("d", function(d, i) {return getPath(samples[i].sample); })
.style("fill", function(d, i) {return color(logForColor(samples[i].area));})
function getData() {
//if loading data for the first time, generate an array of random coordinates
if (!samples.length) {
for (i=0; i<500; i++) {samples[i] = [Math.random() * width, Math.random() * height];}
//otherwise, update the x coordinate based on the polygon's size
} else {
for (i in samples) {samples[i] = [samples[i].point[0], y(samples[i].area)];}
}
//create new polygons and bind them and their calculated area to the main data array
var voronoiData = voronoi(samples).polygons();
for (i in voronoiData) {
samples[i] = {
point: samples[i],
sample: voronoiData[i],
area: d3.polygonArea(voronoiData[i]),
}
}
//set the domain of the scales by finding the min and max area values
minMax = d3.extent(samples, function(d) {return d.area;});
logForColor.domain([minMax[1],minMax[0]])
y.domain(minMax);
}
//create the paths for the polygons
function getPath(points) {
//redrawing the polygons was causing some unwanted effects when transitioning a polygon to a new shape that has more sides. this was because the new points would be drawn off screen before transitioning into place.
//my solution was to default each polygon to having more points than (hopefully) necessary, with the extra points placed on top of each other. this is potentially not performant as it creates multiple points in the same location.
var thisPath = "M" + points[0];
for (n=1; n<15; n++) {
thisPath += "L" + (n < points.length ? points[n] : points[0])
}
return thisPath;
}
var sortShapes = function() {
color = d3[schemes[Math.floor(d3.randomUniform(0,schemes.length)())]]
//slide the polygons into order of size along the x-axis according to the log scale
paths.transition().duration(2000)
.attr("transform", function(d, i) { return "translate(0," + (-samples[i].point[1] + y(samples[i].area)) + ")"})
//re-calculate the voronoi polygons and their sizes based on the new positions
getData();
//transition to the new sizes and shapes
paths.transition().delay(2000).duration(2000)
.attr("transform", "translate(0,0)")
.attr("d", function(d, i) {return getPath(samples[i].sample);})
.style("fill", function(d, i) {return color(logForColor(samples[i].area));})
//loop the sorting process
d3.timeout(sortShapes, 6000);
}
d3.timeout(sortShapes, 2000);
</script>