block by alexmacy 2dc8e86b3fde00c23ccad92e390a2ef5

Voronoi Sorting v2

Full Screen

An update to Voronoi Sorting, the polygons now get their fill color from a logarithmic scale similar to how they are sorted horizontally. This creates a more pleasing dispersion of color. Each iteration also randomly selects a color scheme from 18 of D3 4.0’s sequential color schemes.

The previous versions are here: Voronoi Shuffling v1, Voronoi Shuffling v2, & Voronoi Shuffling v3

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<head>

    <style>

        body { 
            margin: 0; 
        }

        path { 
            fill-opacity: .75; 
            stroke: black; 
        }

    </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)())]],
        x = d3.scaleLog().range([0,width]);

    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] = [x(samples[i].area), samples[i].point[1]];}
        }

        // 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]])
        x.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(" + (-samples[i].point[0] + x(samples[i].area)) + ",0)"
            })

        // 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>