block by gka 8fbc26b06d2075611e72

8fbc26b06d2075611e72

Full Screen

Created by Christopher Manning

Summary

Nodes are linked to nodes in neighboring cells. The cell’s color is a function of its area.

The white lines are the Delaunay triangulation and the purple cells are the Voronoi diagram.

Controls

References

Run this gist at bl.ocks.org

index.html

<!DOCTYPE html>
<html>
<head>
    <title>Voronoi Diagram with Force Directed Nodes and Delaunay Links</title>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
    <script src="//d3js.org/d3.v3.min.js"></script>
    <style type="text/css">
        circle {
            stroke: #EFEDF5;
            fill: #EFEDF5;
            pointer-events: none;
        }
        line {
            pointer-events: none;
            stroke: #EFEDF5;
            stroke-width: 2px;
            opacity: .05;
        }
        path{
            stroke: #EFEDF5;
            stroke-width: 4px;
        }
    </style>
</head>
<body>
<div id="chart">
</div>
<script type="text/javascript">
    var w = window.innerWidth > 960 ? 960 : (window.innerWidth || 960),
        h = window.innerHeight > 500 ? 500 : (window.innerHeight || 500),
        radius = 5.25,
        links = [],
        simulate = true,
        zoomToAdd = true,
        // https://github.com/mbostock/d3/blob/master/lib/colorbrewer/colorbrewer.js#L105
        color = d3.scale.quantize().domain([10000, 7250]).range(["#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#54278f","#3f007d"])

    var numVertices = (w*h) / 3000;
    var vertices = d3.range(numVertices).map(function(i) {
        angle = radius * (i+10);
        return {x: angle*Math.cos(angle)+(w/2), y: angle*Math.sin(angle)+(h/2)};
    });
    var d3_geom_voronoi = d3.geom.voronoi().x(function(d) { return d.x; }).y(function(d) { return d.y; })
    var prevEventScale = 1;
    var zoom = d3.behavior.zoom().on("zoom", function(d,i) {
        if (zoomToAdd){
          if (d3.event.scale > prevEventScale) {
              angle = radius * vertices.length;
              vertices.push({x: angle*Math.cos(angle)+(w/2), y: angle*Math.sin(angle)+(h/2)})
          } else if (vertices.length > 2 && d3.event.scale != prevEventScale) {
              vertices.pop();
          }
          force.nodes(vertices).start()
        } else {
          if (d3.event.scale > prevEventScale) {
            radius+= .01
          } else {
            radius -= .01
          }
          vertices.forEach(function(d, i) {
            angle = radius * (i+10);
            vertices[i] = {x: angle*Math.cos(angle)+(w/2), y: angle*Math.sin(angle)+(h/2)};
          });
          force.nodes(vertices).start()
        }
        prevEventScale = d3.event.scale;
    });

    d3.select(window)
      .on("keydown", function() {
        // shift
        if(d3.event.keyCode == 16) {
          zoomToAdd = false
        }

        // s
        if(d3.event.keyCode == 83) {
          simulate = !simulate
          if(simulate) {
            force.start()
          } else {
            force.stop()
          }
        }
      })
      .on("keyup", function() {
        zoomToAdd = true
      })

    var svg = d3.select("#chart")
            .append("svg")
            .attr("width", w)
            .attr("height", h)
            .call(zoom)

    var force = d3.layout.force()
            .charge(function(d,i) { return i > 0 ? -30: -500 ; } )
            .size([w, h])
            .on("tick", update);

    force.nodes(vertices).start();

    var circle = svg.selectAll("circle");
    var path = svg.selectAll("path");
    var link = svg.selectAll("line");

    function update(e) {
        path = path.data(d3_geom_voronoi(vertices))
        path.enter().append("path")
            // drag node by dragging cell
            .call(d3.behavior.drag()
              .on("drag", function(d, i) {
                  vertices[i] = {x: vertices[i].x + d3.event.dx, y: vertices[i].y + d3.event.dy}
              })
            )
            .style("fill", function(d, i) { return color(0) })
        path.attr("d", function(d) { return "M" + d.join("L") + "Z"; })
            .transition().duration(150).style("fill", function(d, i) { return color(d3.geom.polygon(d).area()) })
        path.exit().remove();

        circle = circle.data(vertices)
        circle.enter().append("circle")
              .attr("r", 0)
              .transition().duration(1000).attr("r", 5);
        circle.attr("cx", function(d) { return d.x; })
              .attr("cy", function(d) { return d.y; });
        circle.exit().transition().attr("r", 0).remove();

        link = link.data(d3_geom_voronoi.links(vertices))
        link.enter().append("line")
        link.attr("x1", function(d) { return d.source.x; })
            .attr("y1", function(d) { return d.source.y; })
            .attr("x2", function(d) { return d.target.x; })
            .attr("y2", function(d) { return d.target.y; })

        link.exit().remove()

        if(!simulate) force.stop()
    }
</script>
</body>
</html>