This is an example of how to split an SVG path into an arbitrary number of pieces.
It is the technique used for The winding path to 270 electoral votes
<!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="//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>