Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active January 27, 2024 14:53
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save mbostock/310c99e53880faec2434 to your computer and use it in GitHub Desktop.
Save mbostock/310c99e53880faec2434 to your computer and use it in GitHub Desktop.
A Less-Angry Rainbow
license: gpl-3.0

Experimenting with a rainbow color scale that is cylical but has better perceptual properties. The HCL rainbow has roughly-constant luminance, but is ugly. The cubehelix rainbow, inspired by Matteo Niccoli’s perceptual rainbow but extended to 360°, varies in brightness but is prettier.

!function(){function t(t){return function(e,i){e=d3.hsl(e),i=d3.hsl(i);var r=(e.h+120)*a,h=(i.h+120)*a-r,s=e.s,l=i.s-s,o=e.l,u=i.l-o;return isNaN(l)&&(l=0,s=isNaN(s)?i.s:s),isNaN(h)&&(h=0,r=isNaN(r)?i.h:r),function(a){var e=r+h*a,i=Math.pow(o+u*a,t),c=(s+l*a)*i*(1-i);return"#"+n(i+c*(-.14861*Math.cos(e)+1.78277*Math.sin(e)))+n(i+c*(-.29227*Math.cos(e)-.90649*Math.sin(e)))+n(i+c*1.97294*Math.cos(e))}}}function n(t){var n=(t=0>=t?0:t>=1?255:0|255*t).toString(16);return 16>t?"0"+n:n}var a=Math.PI/180;d3.scale.cubehelix=function(){return d3.scale.linear().range([d3.hsl(300,.5,0),d3.hsl(-240,.5,1)]).interpolate(d3.interpolateCubehelix)},d3.interpolateCubehelix=t(1),d3.interpolateCubehelix.gamma=t}();
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.space {
position: absolute;
}
.space canvas {
float: left;
}
.space div {
position: absolute;
top: 0;
left: 20px;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="cubehelix.min.js"></script>
<script>
var spaces = [
{
name: "Rainbow (HSL)",
color: function(t) {
return d3.hsl(t * 360, 1, .5);
}
},
{
name: "Rainbow (HCL)",
color: function(t) {
return d3.hcl(t * 360, 100, 55);
}
},
{
name: "Rainbow (Cubehelix)",
color: d3.scale.cubehelix()
.domain([0, .5, 1])
.range([
d3.hsl(-100, 0.75, 0.35),
d3.hsl( 80, 1.50, 0.80),
d3.hsl( 260, 0.75, 0.35)
])
}
];
var y = d3.scale.ordinal()
.domain(spaces.map(function(d) { return d.name; }))
.rangeRoundBands([0, 500], .04);
var margin = y.range()[0],
width = 960 - margin - margin,
height = y.rangeBand();
var space = d3.select("body").selectAll(".space")
.data(spaces)
.enter().append("div")
.attr("class", "space")
.style("width", width + "px")
.style("height", height + "px")
.style("left", margin + "px")
.style("top", function(d) { return y(d.name) + "px"; });
space.append("canvas")
.attr("width", width)
.attr("height", 1)
.style("width", width + "px")
.style("height", height / 2 + "px")
.each(render(function(color) { return color; }));
space.append("canvas")
.attr("width", width)
.attr("height", 1)
.style("width", width + "px")
.style("height", height / 2 + "px")
.each(render(function(color) { color = d3.hcl(color); color.c = 0; return color; }));
space.append("div")
.style("line-height", height / 2 + "px")
.text(function(d) { return d.name; });
function render(color) {
return function(d) {
var context = this.getContext("2d"),
image = context.createImageData(width, 1);
for (var i = 0, j = -1, c; i < width; ++i) {
c = d3.rgb(color.call(this, d.color(i / width)));
image.data[++j] = c.r;
image.data[++j] = c.g;
image.data[++j] = c.b;
image.data[++j] = 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