I'm working with a D3 example file for a force directed voronoi graph... however, I mainly just needed a simplified version with only three vertices... so I simplified the file and have included a JSFiddle example of where my file stands currently. My issue is how the edge condition is handled. Right now, the voronoi edges extend out to the edge of the container div. However, I'd like to clip each voronoi cell by a circle boundary. I've included two images to help explain my problem. The first image shows the script as it exists now, whereas the second is made with photoshop - showing the circular clipping boundary. It seems like D3's polygon.clip method would be the best option, but I don't really know how to implement the clipping method into my script. Any suggestions would be greatly appreciated.
<!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">
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,
cc = ["#FFA94A","#F58A3A","#F85A19"]
var numVertices = (w*h) / 200000;
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;
}
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
}
})
.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(-300)
.size([w, h])
.on("tick", update);
force.nodes(vertices).start();
var path = svg.selectAll("path");
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 cc[0] })
path.attr("d", function(d) { return "M" + d.join("L") + "Z"; })
.transition().duration(150)
.style("fill", function(d, i) { return cc[i] })
path.exit().remove();
if(!simulate) force.stop()
}