Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active July 4, 2016 13:46
Show Gist options
  • Save mbostock/048d21cf747371b11884f75ad896e5a5 to your computer and use it in GitHub Desktop.
Save mbostock/048d21cf747371b11884f75ad896e5a5 to your computer and use it in GitHub Desktop.
ColorBrewer Spline
license: gpl-3.0
height: 620

Using uniform one-dimensional b-splines to interpolate in RGB color space, we can convert a discrete color scheme to a continuous one. This shows the “PiYG” scheme from ColorBrewer which has eleven original colors. This scale, and many more, are available in d3-scale-chromatic.

<!DOCTYPE html>
<meta charset="utf-8">
<canvas width="880" height="1" style="width:880px;height:80px;margin:20px 40px;background:#ccc;"></canvas>
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 40, left: 40, bottom: 40, right: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var canvas = d3.select("canvas").node(),
context = canvas.getContext("2d"),
canvasWidth = canvas.width;
var x0 = d3.scaleQuantize()
.domain([0, 1])
.range(["#8e0152", "#c51b7d", "#de77ae", "#f1b6da", "#fde0ef", "#f7f7f7", "#e6f5d0", "#b8e186", "#7fbc41", "#4d9221", "#276419"]); // PiYG
var x1 = d3.scalePoint()
.domain(x0.range())
.range([0, width]);
var x2 = d3.scaleLinear()
.domain([0, 1])
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, 255])
.range([height, 0]);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
g.append("g")
.selectAll("circle")
.data(x1.domain())
.enter().append("circle")
.attr("cx", function(d) { return x1(d); })
.attr("r", 20)
.attr("fill", function(d) { return d; });
g.append("g")
.attr("class", "axis axis--x")
.call(d3.axisTop(x1).tickPadding(18));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y));
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x2));
var line = g.append("g")
.attr("class", "lines")
.selectAll("path")
.data(["r", "g", "b"].map(function(channel) {
var colors = x0.range().map(function(c) { return d3.rgb(c); }), n = colors.length;
return colors.map(function(c, i) {
return [i / (n - 1), c[channel]];
});
}))
.enter().append("g")
.attr("fill", "none")
.attr("stroke", function(d, i) { return ["#f00", "#0f0", "#00f"][i]; });
line.append("path")
.attr("stroke-width", 3)
.attr("d", function(values) {
var i = d3.interpolateBasis(values.map(function(v) { return v[1]; }));
return d3.line()
.x(function(t) { return x2(t); })
.y(function(t) { return y(i(t)); })
(d3.range(0, 1 + 1e-6, 0.001));
});
line.append("path")
.attr("stroke-dasharray", "2,2")
.attr("stroke", "#000")
.attr("d", d3.line()
.curve(d3.curveLinear)
.x(function(d) { return x2(d[0]); })
.y(function(d) { return y(d[1]); }));
var image = context.createImageData(canvasWidth, 1),
interpolate = d3.interpolateRgbBasis(x0.range());
for (var i = 0, k = 0; i < canvasWidth; ++i, k += 4) {
var c = d3.rgb(interpolate(i / (canvasWidth - 1)));
image.data[k] = c.r;
image.data[k + 1] = c.g;
image.data[k + 2] = c.b;
image.data[k + 3] = 255;
}
context.putImageData(image, 0, 0);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment