block by HarryStevens c1e3953cb073b7c66d73da24c2775266

Voronoi Drag + Jiggle

Full Screen

Drag the circles to change the Voronoi diagram. It also updates itself with new data.

See also:

Voronoi Jiggle

Voronoi Drag

index.html

<html>
  <head>
    <style>
    body {
      margin: 0;
    }
    .voronoi {
      stroke-width: 1px;
      stroke: #3a403d;
    }
    .dot {
      fill: #fff;
      fill-opacity: .1;
      stroke:#3a403d;
      cursor: move;
    }
    </style>
  </head>
  <body>
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.2.1/chroma.min.js"></script>
    <script>
    var width = window.innerWidth,
      height = window.innerHeight,
      alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".split(""),
      duration = 12, // milliseconds of transition duration
      n = 5, // amount of random movement
      r = [],
      data = [],
      c = chroma.scale(["#fc8d62", "#ffd92f", "#66c2a5", "#8da0cb"]).domain([0, width * height / alphabet.length * 2]); // color scale
    for (var i = n * -1; i <= n; i++){
      r.push(i / 125);
    }
    alphabet.forEach(function(d){
      data.push({name: d, x: random(0, 100), y: random(0, 100)})
    });

    // scales
    var x = d3.scaleLinear().domain([0, 100]).range([0, width]);
    var y = d3.scaleLinear().domain([0, 100]).range([height, 0]);
    var xr = d3.scaleLinear().domain([0, width]).range([0, 100]);
    var yr = d3.scaleLinear().domain([height, 0]).range([0, 100]);

    // wrapper
    var svg = d3.select("body").append("svg")
        .attr("width", width)
        .attr("height", height);

    // voronoi tesselation
    var voronoi = d3.voronoi()
      .x(function(d) { return x(d.x); })
      .y(function(d) { return y(d.y); })
      .extent([[0, 0], [width, height]]);

    function redraw(data){

      // transition
      var t = d3.transition()
          .duration(duration);

      // JOIN
      var voronoiGroup = svg.selectAll(".voronoi")
        .data(voronoi(data).polygons(), function(d){ return d.data.name; });

      var circle = svg.selectAll(".dot")
        .data(data, function(d){ return d.name; });

      // EXIT
      voronoiGroup.exit().remove();

      circle.exit().remove();

      // UPDATE
      voronoiGroup
        .transition(t)
          .attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; })
          .style("fill", function(d){ return c(area(d) * -1); });

      circle
        .transition(t)
          .attr("cx",function(d){ return x(d.x); })
          .attr("cy",function(d){ return y(d.y); });

      // ENTER
      voronoiGroup.enter().append("path")
          .attr("class", "voronoi")
          .attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; })
          .style("fill", function(d){ return c(area(d) * -1); })

      circle.enter().append("circle")
          .attr("class", "dot")
          .attr("r", 5)
          .attr("cx",function(d){ return x(d.x); })
          .attr("cy",function(d){ return y(d.y); })
        .call(d3.drag()
          .on("drag", dragged)
        );

    }

    redraw(data);
    
    d3.interval(function() {
      data.forEach(function(d,i){
        d.x = d.x + (1 * r[random(0, r.length - 1)]);
        d.y = d.y + (1 * r[random(0, r.length - 1)]);
        if (d.x < 0){
          d.x = 0;
        } else if (d.x > 100){
          d.x = 100;
        }
        if (d.y < 0){
          d.y = 0;
        } else if (d.y > 100){
          d.y = 100;
        }
        data[i] = d;
      });
      redraw(data);
    }, duration * 2);

    /*FUNCTIONS*/

    function dragged(d){
      var coordinates = [0, 0];
      coordinates = d3.mouse(this);
      d.x = xr(coordinates[0]);
      d.y = yr(coordinates[1]);
      var i = findWithAttr(data, "name", d.name);
      data[i] = d;
      redraw(data);
    }

    function findWithAttr(array, attr, value) {
      for(var i = 0; i < array.length; i += 1) {
          if(array[i][attr] === value) {
              return i;
          }
      }
      return -1;
    }

    function random(min, max) {
      return Math.floor(Math.random() * (max - min + 1)) + min;
    }

    function shuffle(array) {
      var m = array.length, t, i;

      // While there remain elements to shuffle…
      while (m) {

        // Pick a remaining element…
        i = Math.floor(Math.random() * m--);

        // And swap it with the current element.
        t = array[m];
        array[m] = array[i];
        array[i] = t;
      }

      return array;
    }

    function area(points) {
      var sum = 0.0;
      var length = points.length;
      if (length < 3) {
        return sum;
      }
      points.forEach(function(d1, i1) {
        i2 = (i1 + 1) % length;
        d2 = points[i2];
        sum += (d2[1] * d1[0]) - (d1[1] * d2[0]);
      });
      return sum / 2;
    }

    </script>
  </body>
</html>