Voronoi Overlay

HTML

<script src="//d3js.org/d3.v4.min.js"></script>

CSS

body {
  position: relative;
  width: 960px;
  height: 500px;
}

path {
  pointer-events: all;
  fill: none;
  stroke: #666;
  stroke-opacity: 0.2;
}

.active circle {
  stroke: #000;
  stroke-width: 2px;
}

JavaScript

var svg = d3.select("body").append("svg").attr("width", 960).attr("height", 500);
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    radius = 32;
    
var filter = svg.append("defs")
  .append("filter")
    .attr("id", "blur")
  .append("feGaussianBlur")
    .attr("stdDeviation", 5);
    
var image = new Image;
image.src = "https://octodex.github.com/images/catstello.png";
image.onload = load;

var circles = d3.range(20).map(function() {
  return {
    x: Math.round(Math.random() * (width - radius * 2) + radius),
    y: Math.round(Math.random() * (height - radius * 2) + radius)
  };
});

var color = d3.scaleOrdinal()
    .range(d3.schemeCategory20);

var voronoi = d3.voronoi()
    .x(function(d) { return d.x; })
    .y(function(d) { return d.y; })
    .extent([[-1, -1], [width + 1, height + 1]]);

var circle = svg.selectAll("g")
  .data(circles)
  .enter().append("g")
    .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));

var cell = circle.append("path")
  .data(voronoi.polygons(circles))
    .attr("d", renderCell)
    .attr("id", function(d, i) { return "cell-" + i; })
    .style("fill", function(d, i) { return color(i); })
    .attr("filter", "url(#blur)");

function load() {
  /* svg.append("image")
      .attr("xlink:href", this.src)
      .attr("width", "100%")
      .attr("height", "100%") */
}

function blur() {
  filter.attr("stdDeviation", this.value / 5);
}

function dragstarted(d) {
  d3.select(this).raise().classed("active", true);
}

function dragged(d) {
  d3.select(this).select("circle").attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
  cell = cell.data(voronoi.polygons(circles)).attr("d", renderCell);
}

function dragended(d, i) {
  d3.select(this).classed("active", false);
}

function renderCell(d) {
  return d == null ? null : "M" + d.join("L") + "Z";
}