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