|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<svg width="960" height="500"> |
|
<path fill="none" stroke-width="10" d=" |
|
M86,388 |
|
L203,330 |
|
C320,272,554,156,673.8333333333334,165.83333333333334 |
|
C793.6666666666666,175.66666666666666,799.3333333333334,311.3333333333333,683.5,316.6666666666667 |
|
C567.6666666666666,322,330.3333333333333,197,211.66666666666666,134.5 |
|
L93,72"></path> |
|
</svg> |
|
<script src="//d3js.org/d3.v4.min.js"></script> |
|
<script> |
|
|
|
var color = d3.interpolateRainbow; |
|
|
|
var path = d3.select("path").remove(); |
|
|
|
d3.select("svg").selectAll("path") |
|
.data(quads(samples(path.node(), 8))) |
|
.enter().append("path") |
|
.style("fill", function(d) { return color(d.t); }) |
|
.style("stroke", function(d) { return color(d.t); }) |
|
.attr("d", function(d) { return lineJoin(d[0], d[1], d[2], d[3], 32); }); |
|
|
|
// Sample the SVG path uniformly with the specified precision. |
|
function samples(path, precision) { |
|
var n = path.getTotalLength(), t = [0], i = 0, dt = precision; |
|
while ((i += dt) < n) t.push(i); |
|
t.push(n); |
|
return t.map(function(t) { |
|
var p = path.getPointAtLength(t), a = [p.x, p.y]; |
|
a.t = t / n; |
|
return a; |
|
}); |
|
} |
|
|
|
// Compute quads of adjacent points [p0, p1, p2, p3]. |
|
function quads(points) { |
|
return d3.range(points.length - 1).map(function(i) { |
|
var a = [points[i - 1], points[i], points[i + 1], points[i + 2]]; |
|
a.t = (points[i].t + points[i + 1].t) / 2; |
|
return a; |
|
}); |
|
} |
|
|
|
// Compute stroke outline for segment p12. |
|
function lineJoin(p0, p1, p2, p3, width) { |
|
var u12 = perp(p1, p2), |
|
r = width / 2, |
|
a = [p1[0] + u12[0] * r, p1[1] + u12[1] * r], |
|
b = [p2[0] + u12[0] * r, p2[1] + u12[1] * r], |
|
c = [p2[0] - u12[0] * r, p2[1] - u12[1] * r], |
|
d = [p1[0] - u12[0] * r, p1[1] - u12[1] * r]; |
|
|
|
if (p0) { // clip ad and dc using average of u01 and u12 |
|
var u01 = perp(p0, p1), e = [p1[0] + u01[0] + u12[0], p1[1] + u01[1] + u12[1]]; |
|
a = lineIntersect(p1, e, a, b); |
|
d = lineIntersect(p1, e, d, c); |
|
} |
|
|
|
if (p3) { // clip ab and dc using average of u12 and u23 |
|
var u23 = perp(p2, p3), e = [p2[0] + u23[0] + u12[0], p2[1] + u23[1] + u12[1]]; |
|
b = lineIntersect(p2, e, a, b); |
|
c = lineIntersect(p2, e, d, c); |
|
} |
|
|
|
return "M" + a + "L" + b + " " + c + " " + d + "Z"; |
|
} |
|
|
|
// Compute intersection of two infinite lines ab and cd. |
|
function lineIntersect(a, b, c, d) { |
|
var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, |
|
y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, |
|
ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21); |
|
return [x1 + ua * x21, y1 + ua * y21]; |
|
} |
|
|
|
// Compute unit vector perpendicular to p01. |
|
function perp(p0, p1) { |
|
var u01x = p0[1] - p1[1], u01y = p1[0] - p0[0], |
|
u01d = Math.sqrt(u01x * u01x + u01y * u01y); |
|
return [u01x / u01d, u01y / u01d]; |
|
} |
|
|
|
</script> |
If I change it to the following with only a single move command, it works fine, although the thickness is still too big:
EDIT: Ah, the width is the final parameter to
lineJoin
. I suppose it would be possible to make this detect the width instead of having us passing it in (which is redundant since we chose the width in the original path already).