Skip to content

Instantly share code, notes, and snippets.

@FrissAnalytics
Last active May 11, 2020 08:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FrissAnalytics/ff1426264821a8fd83ba04eca5f40b18 to your computer and use it in GitHub Desktop.
Save FrissAnalytics/ff1426264821a8fd83ba04eca5f40b18 to your computer and use it in GitHub Desktop.
gradient path
license: mit
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
</style>
</head>
<body>
<svg width="500" height="500">
<path fill="none" stroke-width="10"></path>
</svg>
<script>
var c = [250, 250]; // center
var r = 200; // radius
var complete = 0.8; // percent
var circlePath = `
M ${c[0]} ${c[1]-r}
a ${r},${r} 0 1,0 0, ${(r * 2)}
a ${r},${r} 0 1,0 0, -${(r * 2)}
Z
`;
var colorInterpolator = d3.interpolateRgbBasis(["rgb(225, 65, 118)", "rgb(108, 125, 255)", "rgb(225, 65, 118)"]);
var colorHandler = (d, i, nodes) => {
let color = d3.color(colorInterpolator(d.t));
color.opacity = i/nodes.length > 1-complete ? 1 : 0;
return color;
};
var path = d3.select("path").attr("d", circlePath).remove();
console.time("[gradient stroke performance]");
d3.select("svg").selectAll("path")
.data(quads(samples(path.node(), 8)))
.enter().append("path")
.style("fill", colorHandler)
.style("stroke", colorHandler)
.style("stroke-width", 30)
.style("stroke-linecap", (d, i, all) => i === 0 || i === all.length ? "round" : "round" )
.attr("d", d => lineJoin(d[0], d[1], d[2], d[3], 32) );
console.timeEnd("[gradient stroke performance]");
// 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>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment