Research for an equidecomposability (scissors congruence) implementation.
xxxxxxxxxx
<style>
circle {
cursor: move;
fill: #fff;
stroke: #000;
stroke-width: 1px;
}
circle.active {
stroke: #000;
stroke-width: 2px;
}
path.clip {
fill: rgba(0, 0, 0, 0.3);
stroke: #000;
stroke-width: 1px;
}
path.subject {
fill: rgba(0, 0, 0, 0);
stroke: #000;
stroke-width: 1px;
}
path.clipped {
fill: rgba(240, 0, 0, 1);
stroke: #000;
stroke-width: 3px;
}
</style>
<svg width="960" height="960"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="d3-polygon-clip.js"></script>
<script>
var svg = d3.select("svg"),
width = svg.attr("width"),
height = svg.attr("height");
var randomX = d3.randomNormal(width / 2, width * 0.1),
randomY = d3.randomNormal(height / 2, height * 0.1);
var sites = d3.range(20).map(function(d) {
return [randomX(), randomY()];
});
// Counter-clockwise orientation.
var clip = [[width / 2, height / 5],
[width / 5, 3 * height / 5],
[3 * width / 5, 3 * height / 5]];
var voronoi = d3.voronoi()
.extent([[width / 4, height / 4], [3 * width / 4, 3* height / 4]]);
var subject = svg.append("g")
.selectAll("path")
.data(voronoi.polygons(sites))
.enter().append("path")
.attr("class", "subject")
.attr("d", function(d) { return "M" + d.join("L") + "Z"; });
var clipPolygon = svg.append("path")
.datum(clip)
.attr("class", "clip");
// Control points for the clip polygon.
var circle = svg.selectAll("circle")
.data(clip);
var origin = [0, 0];
circle.enter()
.append("circle")
.attr("r", 9)
.attr("cx", function(d) { return d[0]; })
.attr("cy", function(d) { return d[1]; })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
update();
// Redraw polygons in response to control point movement.
function update() {
// Ensure clip polygon has counter-clockwise orientation.
if (!ccw(clip)) clip.reverse();
clipPolygon.attr("d", function(d) { return "M" + d.join("L") + "Z"; });
clipped = svg.selectAll(".clipped")
.data(
voronoi.polygons(sites)
.map(function(d) {
d3.polygonClip(clip, d);
return d;
})
.filter(function(d) {
return d.length > 2;
})
, function(d) { return d; }); // Key on Object reference.
clipped.enter().insert("path")
.attr("class", "clipped")
.attr("d", function(d) { return "M" + d.join("L") + "Z"; });
clipped.exit().remove();
// Circles drawn on top of all other SVG elements.
d3.selectAll("circle").raise();
}
function dragstarted(d) {
d3.select(this).classed("active", true);
}
function dragged(d) {
d3.select(this)
.attr("cx", d[0] = d3.event.x)
.attr("cy", d[1] = d3.event.y)
update();
}
function dragended(d, i) {
d3.select(this).classed("active", false);
}
function polygonClone(polygon) {
var cloned = [],
i,
n;
for (i = 0, n = polygon.length; i < n; i++) {
cloned.push([polygon[i][0], polygon[i][1]]);
}
return cloned;
}
// Returns true of the given polygon has counter-clockwise orientation.
function ccw(polygon) {
function polygonInside(p, a, b) {
return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]);
}
return polygonInside(d3.polygonCentroid(polygon), polygon[0], polygon[1]);
}
</script>
https://d3js.org/d3.v4.min.js