This example shows the results (orange) of performing four different boolean operations (union, difference, xor and intersection) on two 2D shapes (blue). Thanks to the powerful clipper.js library, the computation is performed in client-side Javascript.
The example is almost entirely taken from this clipper.js demo.
(function() {
var clip_paths, clip_type, cpr, height, path, paths2string, solution_paths, solutions, subj_paths, succeeded, svg, width, _i, _j, _len, _len2, _ref, _ref2;
width = 960;
height = 500;
/* create the SVG
*/
svg = d3.select('body').append('svg').attr('width', width).attr('height', height);
/* define subject and clip paths
*/
subj_paths = [
[
{
X: 10,
Y: 10
}, {
X: 110,
Y: 10
}, {
X: 110,
Y: 110
}, {
X: 10,
Y: 110
}
], [
{
X: 20,
Y: 20
}, {
X: 20,
Y: 100
}, {
X: 100,
Y: 100
}, {
X: 100,
Y: 20
}
]
];
clip_paths = [
[
{
X: 50,
Y: 50
}, {
X: 150,
Y: 50
}, {
X: 150,
Y: 150
}, {
X: 50,
Y: 150
}
], [
{
X: 60,
Y: 60
}, {
X: 60,
Y: 140
}, {
X: 140,
Y: 140
}, {
X: 140,
Y: 60
}
]
];
/* create and instruct Clipper to work with the provided paths
*/
cpr = new ClipperLib.Clipper();
/* true for closed paths
*/
cpr.AddPaths(subj_paths, ClipperLib.PolyType.ptSubject, true);
cpr.AddPaths(clip_paths, ClipperLib.PolyType.ptClip, true);
/* perform a UNION, a DIFFERENCE, a XOR and an INTERSECTION
*/
solutions = [];
_ref = [ClipperLib.ClipType.ctUnion, ClipperLib.ClipType.ctDifference, ClipperLib.ClipType.ctXor, ClipperLib.ClipType.ctIntersection];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
clip_type = _ref[_i];
solution_paths = new ClipperLib.Paths();
succeeded = cpr.Execute(clip_type, solution_paths, ClipperLib.PolyFillType.pftNonZero, ClipperLib.PolyFillType.pftNonZero);
if (!succeeded) throw new Error('Clipper operation failed!');
solutions.push(solution_paths);
}
/* Converts Paths to SVG path string
*/
/* and scales down the coordinates
*/
/* from http://jsclipper.sourceforge.net/6.1.3.1/index.html?p=starter_boolean.html
*/
paths2string = function(paths, scale) {
var i, p, path, svgpath, _j, _len2, _len3;
svgpath = '';
if (!(scale != null)) scale = 1;
for (_j = 0, _len2 = paths.length; _j < _len2; _j++) {
path = paths[_j];
for (i = 0, _len3 = path.length; i < _len3; i++) {
p = path[i];
if (i === 0) {
svgpath += 'M';
} else {
svgpath += 'L';
}
svgpath += p.X / scale + ", " + p.Y / scale;
}
svgpath += 'Z';
}
if (svgpath === '') svgpath = 'M0,0';
return svgpath;
};
/* display all the solutions in SVG
*/
svg.selectAll('path').data(solutions).enter().append('path').attr('d', function(d) {
return paths2string(d);
}).attr('transform', function(d, i) {
return "translate(" + (width / 2 + (i - 2) * 200 + 50) + "," + (height / 2 + 25) + ")";
});
/* display the original paths as reference
*/
_ref2 = [subj_paths, clip_paths];
for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
path = _ref2[_j];
svg.append('path').attr('class', 'original').attr('d', paths2string(path)).attr('transform', "translate(" + (width / 2 - 75) + ",50)");
}
}).call(this);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Boolean operations on 2D shapes</title>
<link type="text/css" href="index.css" rel="stylesheet"/>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//jsclipper.sourceforge.net/6.1.3.1/clipper.js"></script>
</head>
<body>
</body>
<script src="index.js"></script>
</html>
width = 960
height = 500
### create the SVG ###
svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
### define subject and clip paths ###
subj_paths = [
[{X:10,Y:10},{X:110,Y:10},{X:110,Y:110},{X:10,Y:110}],
[{X:20,Y:20},{X:20,Y:100},{X:100,Y:100},{X:100,Y:20}]
]
clip_paths = [
[{X:50,Y:50},{X:150,Y:50},{X:150,Y:150},{X:50,Y:150}],
[{X:60,Y:60},{X:60,Y:140},{X:140,Y:140},{X:140,Y:60}]
]
### create and instruct Clipper to work with the provided paths ###
cpr = new ClipperLib.Clipper()
### true for closed paths ###
cpr.AddPaths(subj_paths, ClipperLib.PolyType.ptSubject, true)
cpr.AddPaths(clip_paths, ClipperLib.PolyType.ptClip, true)
### perform a UNION, a DIFFERENCE, a XOR and an INTERSECTION ###
solutions = []
for clip_type in [ClipperLib.ClipType.ctUnion, ClipperLib.ClipType.ctDifference, ClipperLib.ClipType.ctXor, ClipperLib.ClipType.ctIntersection]
solution_paths = new ClipperLib.Paths()
succeeded = cpr.Execute(clip_type, solution_paths, ClipperLib.PolyFillType.pftNonZero, ClipperLib.PolyFillType.pftNonZero)
if not succeeded
throw new Error('Clipper operation failed!')
solutions.push solution_paths
### Converts Paths to SVG path string ###
### and scales down the coordinates ###
### from http://jsclipper.sourceforge.net/6.1.3.1/index.html?p=starter_boolean.html ###
paths2string = (paths, scale) ->
svgpath = ''
if not scale?
scale = 1
for path in paths
for p, i in path
if i is 0
svgpath += 'M'
else
svgpath += 'L'
svgpath += p.X/scale + ", " + p.Y/scale
svgpath += 'Z'
if svgpath is ''
svgpath = 'M0,0'
return svgpath
### display all the solutions in SVG ###
svg.selectAll('path')
.data(solutions)
.enter().append('path')
.attr('d', (d) -> paths2string(d))
.attr('transform', (d,i) -> "translate(#{width/2+(i-2)*200+50},#{height/2+25})")
### display the original paths as reference ###
for path in [subj_paths, clip_paths]
svg.append('path')
.attr('class', 'original')
.attr('d', paths2string(path))
.attr('transform', "translate(#{width/2-75},50)")
path {
fill: orange;
stroke: black;
shape-rendering: crispEdges;
}
.original {
fill: teal;
fill-opacity: 0.5;
}
path
fill: orange
stroke: black
shape-rendering: crispEdges
.original
fill: teal
fill-opacity: 0.5