Skip to content

Instantly share code, notes, and snippets.

@bycoffe
Last active March 19, 2023 09:45
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bycoffe/18441cddeb8fe147b719fab5e30b5d45 to your computer and use it in GitHub Desktop.
Save bycoffe/18441cddeb8fe147b719fab5e30b5d45 to your computer and use it in GitHub Desktop.
Split an SVG path into pieces
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: Helvetica;
font-size: 16px;
}
ol li {
margin-bottom: 10px;
}
li.highlight {
background: yellow;
}
pre {
display: inline;
background: lightgray;
}
#chart {
width: 960px;
height: 350px;
}
path {
fill: none;
stroke: #999;
}
path.hidden {
display: none;
}
path.init {
stroke-width: 2;
}
path.piece {
stroke-width: 12;
stroke: none;
}
line.sep {
stroke-width: 4;
stroke: none;
}
</style>
<body>
<div id="chart"></div>
<ol id="instructions">
<li data-id="1" class="highlight">Draw an initial path.</li>
<li data-id="2" >Determine how many pieces you want and what percentage of the full path each piece should account for. (Here we're using 20 pieces of random sizes.)</li>
<li data-id="3">Get the location of each section's points along the overall path.</li>
<li data-id="4">Use those points to draw a new path for each section.</li>
</ol>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script>
function splitPath() {
var numPieces = 20,
pieceSizes = [],
pieces = [];
for (var i=0; i<numPieces; i++) {
pieceSizes.push({i: i, size: Math.floor(Math.random() * 20) + 5});
}
var size = pieceSizes.reduce(function(a, b) {
return a + b.size;
}, 0);
var pieceSize = pLength / size;
pieceSizes.forEach(function(x, j) {
var segs = [];
for (var i=0; i<=x.size+sampleInterval; i+=sampleInterval) {
pt = p.getPointAtLength((i*pieceSize)+(cumu*pieceSize));
segs.push([pt.x, pt.y]);
}
angle = Math.atan2(segs[1][1] - segs[0][1], segs[1][0] - segs[0][0]) * 180 / Math.PI;
pieces.push({id: j, segs: segs, angle: angle});
cumu += x.size;
});
return pieces;
}
var margin = {top: 0, right: 20, bottom: 0, left: 20},
width = 960 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom,
colors = d3.schemeCategory20b,
pts = [],
numPts = 7;
colors.sort(function(a, b) {
return Math.random() > .5 ? 1 : -1;
});
for (var i=0; i<numPts; i++) {
pts.push([i*(width/numPts), 50]);
pts.push([i*(width/numPts), height-50]);
pts.push([i*(width/numPts)+50, height-50]);
pts.push([i*(width/numPts)+50, 50]);
}
var path = d3.line()
.curve(d3.curveCardinal),
svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom),
g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"),
line = g.append("path")
.attr("class", "hidden init")
.attr("d", path(pts)),
p = line.node(),
pLength = p.getTotalLength(),
cumu = 0,
sampleInterval = .25;
function showLine(callback) {
line.classed("hidden", false)
.attr("stroke-dasharray", pLength + " " + pLength)
.attr("stroke-dashoffset", pLength)
.transition()
.duration(1500)
.ease(d3.easeLinear)
.attr("stroke-dashoffset", 0)
.on("end", function() {
callback();
});
}
function drawSegments(pieces) {
d3.selectAll("#instructions li").classed("highlight", function() {
return this.getAttribute("data-id") === "4";
});
var lines = g.selectAll("path.piece")
.data(pieces)
.enter().append("path")
.attr("class", "piece")
.attr("d", function(d, i) {
return path(d.segs);
});
var seps = g.selectAll("line.sep")
.data(pieces)
.enter().append("line")
.attr("class", "sep")
.attr("transform", function(d, i) {
return "translate(" + d.segs[0][0] + "," + d.segs[0][1] + ")rotate(" + (d.angle-90) + " 0 0)";
})
.attr("x1", -12)
.attr("y1", 0)
.attr("x2", 12)
.attr("y2", 0);
lines.transition()
.duration(0)
.delay(function(d, i) {
return i * 250;
})
.style("stroke", function(d, i) {
return colors[i];
})
.on("end", function(d, i) {
if (i === pieces.length-1) {
d3.selectAll("#instructions li").classed("highlight", false);
}
})
seps.transition()
.duration(0)
.delay(function(d, i) {
return i * 250;
})
.style("stroke", "#fff");
}
showLine(function() {
d3.selectAll("#instructions li").classed("highlight", function() {
return this.getAttribute("data-id") === "3";
});
var pieces = splitPath();
var segments = g.selectAll("g.segment")
.data(pieces)
.enter().append("g"),
pts = [];
pieces.forEach(function(x) {
x.segs.forEach(function(seg, i) {
if (i > 0 && i % 2 === 0) {
pts.push({id: x.id, seg: seg});
}
});
});
var dots = g.selectAll("circle")
.data(pts)
.enter().append("circle")
.attr("cx", function(d, i) {
return d.seg[0];
})
.attr("cy", function(d, i) {
return d.seg[1];
})
.style("fill", function(d, i, j) {
return colors[d.id];
})
.attr("r", 0);
dots.transition()
.duration(0)
.delay(function(d, i) {
return i * 10;
})
.attr("r", 3)
.on("end", function(d, i, j) {
if (i === pts.length-1) {
drawSegments(pieces);
}
});
});
</script>
</body>
@dalisoft
Copy link

It is possible to do in curves without getPointsAtLength because its slow and this is needed me for morph split closed path to more for match another path to get best result morph looking. Has triangulation and i am not undetstand good. Look at flubberjs that uses triangulation or my minimorph on npm or github to know what i mean. Thanks for work. I will credit you if you help me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment