block by mbostock 11415064

Cubehelix II

Full Screen

More examples of the d3.interpolateCubehelix plugin, replicating four Python cubehelix examples. In the last two examples, a polylinear scale creates a diverging cubehelix color ramp, and an approximate recreation of Matteo Niccoli’s perceptual rainbow.

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>

body {
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}

.ramp {
  position: absolute;
}

.ramp div {
  position: absolute;
  top: 0;
  right: 20px;
}

.ramp[name='Reversed'] div {
  color: #fff;
}

</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="cubehelix.js"></script>
<script>

var ramps = [
  {
    name: "Default",
    color: d3.scale.cubehelix()
  },
  {
    name: "Reversed",
    color: d3.scale.cubehelix()
        .domain([1, 0])
  },
  {
    name: "Hue [276°, 96°]",
    color: d3.scale.cubehelix()
        .range([d3.hsl(276, .6, 0), d3.hsl(96, .6, 1)])
  },
  {
    name: "Hue [-120°, 60°]",
    color: d3.scale.cubehelix()
        .range([d3.hsl(-120, .6, 0), d3.hsl(60, .6, 1)])
  },
  {
    name: "Hue [-40°, 60°, 160]",
    color: d3.scale.cubehelix()
        .domain([0, .5, 1])
        .range([d3.hsl(-40, .6, .3), d3.hsl(60, .6, 1), d3.hsl(160, .6, .3)])
  },
  {
    name: "Rainbow",
    color: d3.scale.cubehelix()
        .range([d3.hsl(270, .75, .35), d3.hsl(70, 1.5, .8)])
  }
];

var y = d3.scale.ordinal()
    .domain(ramps.map(function(d) { return d.name; }))
    .rangeRoundBands([0, 500], .1);

var margin = y.range()[0],
    width = 960 - margin - margin,
    height = y.rangeBand();

var ramp = d3.select("body").selectAll(".ramp")
    .data(ramps)
  .enter().append("div")
    .attr("class", "ramp")
    .attr("name", function(d) { return d.name; })
    .style("width", width + "px")
    .style("height", height + "px")
    .style("left", margin + "px")
    .style("top", function(d) { return y(d.name) + "px"; });

var canvas = ramp.append("canvas")
    .attr("width", width)
    .attr("height", 1)
    .style("width", width + "px")
    .style("height", height + "px")
    .each(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(d.color(i / (width - 1)));
        image.data[++j] = c.r;
        image.data[++j] = c.g;
        image.data[++j] = c.b;
        image.data[++j] = 255;
      }
      context.putImageData(image, 0, 0);
    });

ramp.append("div")
    .style("line-height", height + "px")
    .text(function(d) { return d.name; });

</script>

cubehelix.js

(function() {
  var radians = 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 = d3_interpolateCubehelix(1);
  d3.interpolateCubehelix.gamma = d3_interpolateCubehelix;

  function d3_interpolateCubehelix(γ) {
    return function(a, b) {
      a = d3.hsl(a);
      b = d3.hsl(b);

      var ah = (a.h + 120) * radians,
          bh = (b.h + 120) * radians - ah,
          as = a.s,
          bs = b.s - as,
          al = a.l,
          bl = b.l - al;

      if (isNaN(bs)) bs = 0, as = isNaN(as) ? b.s : as;
      if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah;

      return function(t) {
        var h = ah + bh * t,
            l = Math.pow(al + bl * t, γ),
            a = (as + bs * t) * l * (1 - l);
        return "#"
            + hex(l + a * (-0.14861 * Math.cos(h) + 1.78277 * Math.sin(h)))
            + hex(l + a * (-0.29227 * Math.cos(h) - 0.90649 * Math.sin(h)))
            + hex(l + a * (+1.97294 * Math.cos(h)));
      };
    };
  }

  function hex(v) {
    var s = (v = v <= 0 ? 0 : v >= 1 ? 255 : v * 255 | 0).toString(16);
    return v < 0x10 ? "0" + s : s;
  }
})();