Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active April 18, 2016 12:43
Show Gist options
  • Save mbostock/6059532 to your computer and use it in GitHub Desktop.
Save mbostock/6059532 to your computer and use it in GitHub Desktop.
Gradient Bump
license: gpl-3.0

A work-in-progress prototype of a bump chart using stroke gradients.

weight a b c
158 12.58 8.15 20.13
147 6.25 -5.08 4.31
129 5.71 6.68 6.28
126 25.38 23.75 18.83
97 14.29 12.8 11.13
95 0.39 -5.85 -1.57
95 10.34 7.32 0.1
90 10.3 8.9 13.96
73 23.77 25.43 24.73
73 8.26 6.52 8.91
68 11.84 8.97 14.2
54 5.99 5.42 11.37
52 30.81 30.22 30.41
49 21.36 24.25 24.86
47 17.21 14.87 13.9
38 26.77 23.35 30.25
31 23 25.08 16.4
31 5.79 13.94 9.67
29 3.96 -10.45 0.09
27 27.18 31.83 15.14
26 19.1 16.53 15.94
23 18.66 22.12 24.1
20 4.03 7.69 8.05
16 21.87 22.08 17.92
15 27.49 36.58 25.15
14 21.55 18.41 20.25
12 23.27 23.55 20.4
12 0.47 -2.25 0.42
11 23.39 21.7 19.01
10 9.92 4.51 11.14
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.bump path {
fill: none;
stroke: #000;
stroke-linejoin: round;
}
.bump .halo {
stroke: #fff;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 10, right: 10, bottom: 10, left: 10},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
pairId = 0;
var x = d3.scale.ordinal()
.domain(["a", "b", "c"])
.rangeBands([0, width], .5, 0);
var xTangent = 40, // Length of Bézier tangents to control curve.
yPadding = .3; // Fraction of height for vertical spacing between lines.
var y = d3.scale.linear()
.range([0, height]);
var z = {
a: d3.scale.linear().range(["#EEE8B8", "#973333"]).interpolate(d3.interpolateHcl),
b: d3.scale.linear().range(["#88A391", "#D0D6AF"]).interpolate(d3.interpolateHcl),
c: d3.scale.linear().range(["#245B7B", "#6A8D94"]).interpolate(d3.interpolateHcl)
};
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.tsv("data.tsv", type, function(error, data) {
y.domain([0, d3.sum(data, function(d) { return d.weight; }) / (1 - yPadding)]);
x.domain().forEach(function(k) {
var n = data.length, y0 = 0, dy = y.domain()[1] * yPadding / (n - 1);
data.sort(function(a, b) { return a[k] - b[k]; }).forEach(function(d, i) {
d[k + "-offset"] = y0 + d.weight / 2;
y0 += d.weight + dy;
});
z[k].domain([data[0][k], data[n - 1][k]]);
});
data.sort(function(a, b) { return b.weight - a.weight; });
var bump = svg.append("g")
.attr("class", "bump")
.selectAll("g")
.data(data)
.enter().append("g");
bump.append("path")
.attr("class", "halo")
.style("stroke-width", function(d) { return y(d.weight) + 2; })
.attr("d", line);
x.domain().slice(1).forEach(function(b, i) {
var a = x.domain()[i];
var path = bump.append("path")
.style("stroke-width", function(d) { return y(d.weight); });
var gradient = bump.append("linearGradient")
.attr("id", function(d) { return "gradient-" + a + "-" + d.index; })
.attr("x1", function(d) { return x(a) + x.rangeBand(); })
.attr("y1", function(d) { return y(d[a + "-offset"]); })
.attr("x2", function(d) { return x(b); })
.attr("y2", function(d) { return y(d[b + "-offset"]); })
.attr("gradientUnits", "userSpaceOnUse")
.attr("spreadMethod", "pad");
gradient.append("svg:stop")
.attr("offset", "0%")
.attr("stop-color", function(d) { return z[a](d[a]); })
.attr("stop-opacity", 1);
gradient.append("svg:stop")
.attr("offset", "100%")
.attr("stop-color", function(d) { return z[b](d[b]); })
.attr("stop-opacity", 1);
path
.style("stroke", function(d) { return "url(#gradient-" + a + "-" + d.index + ")"; })
.attr("d", function(d) {
return "M" + x(a) + "," + y(d[a + "-offset"])
+ "h" + x.rangeBand()
+ curve(a, b, d)
+ "h1";
});
});
bump.append("path")
.style("stroke-width", function(d) { return y(d.weight); })
.style("stroke", function(d) { return z.c(d.c); })
.attr("d", function(d) { return "M" + x("c") + "," + y(d["c-offset"]) + "h" + x.rangeBand(); });
});
function type(d, i) {
for (var k in d) d[k] = +d[k];
d.index = i;
return d;
}
function line(d) {
var path = [];
x.domain().slice(1).forEach(function(b, i) {
var a = x.domain()[i];
path.push("L", x(a), ",", y(d[a + "-offset"]), "h", x.rangeBand(), curve(a, b, d));
});
path[0] = "M";
path.push("h", x.rangeBand());
return path.join("");
}
function curve(a, b, d) {
return "C" + (x(a) + xTangent + x.rangeBand()) + "," + y(d[a + "-offset"]) + " "
+ (x(b) - xTangent) + "," + y(d[b + "-offset"]) + " "
+ x(b) + "," + y(d[b + "-offset"]);
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment