Skip to content

Instantly share code, notes, and snippets.

@grrowl
Forked from christophermanning/README.md
Last active November 20, 2016 08:27
Show Gist options
  • Save grrowl/cbcd33ef6ad855827b8248eada2f2ed4 to your computer and use it in GitHub Desktop.
Save grrowl/cbcd33ef6ad855827b8248eada2f2ed4 to your computer and use it in GitHub Desktop.
Voronoi Diagram with Force Directed Nodes and Delaunay Links

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

  • Drag the cells to interact with the diagram.
  • Use the mousewheel to add/remove nodes.
  • Hold shift while using the mousewheel to change the initialization spiral
  • Press the letter s to toggle the simulation

References

Run this gist at bl.ocks.org

<!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="http://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(-70)
.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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment