Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active March 29, 2023 00:13
Show Gist options
  • Save mbostock/3014589 to your computer and use it in GitHub Desktop.
Save mbostock/3014589 to your computer and use it in GitHub Desktop.
Lab and HCL Color Spaces
license: gpl-3.0

D3 supports CIELAB (Lab) and CIELCH (HCL) color spaces, which are designed for humans rather than computers.

Lab and HCL color spaces are special in that the perceived difference between two colors is proportional to their Euclidean distance in color space. This special property, called perceptual uniformity, makes them ideal for accurate visual encoding of data. In contrast, the more familiar RGB and HSL color spaces distort data when used for visualization.

You can create Lab or HCL colors in D3 directly using d3.lab or d3.hcl. For example:

var steelblue = d3.lab(52, -4, -32);
var steelblue = d3.hcl(-97, 32, 52);

You can also convert from other color spaces, such as RGB or HSL. This is useful for creating brighter or darker colors with uniform changes in perception:

var lightsteelblue = d3.lab("#4682b4").brighter();
var darksteelblue = d3.hcl("hsl(207, 44%, 49%)").darker();

Most importantly, you can use d3.interpolateLab or d3.interpolateHcl in conjunction with quantitative scales and transitions:

var color = d3.scale.linear()
    .range(["steelblue", "brown"])
    .interpolate(d3.interpolateHcl);

By using Lab or HCL color interpolation, you can create custom color scales that are comparable in quality to Colorbrewer.

For more on this topic:

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.space {
position: absolute;
}
.space div {
position: absolute;
top: 0;
left: 20px;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var spaces = [
{name: "HSL", interpolate: d3.interpolateHsl},
{name: "HCL", interpolate: d3.interpolateHcl},
{name: "Lab", interpolate: d3.interpolateLab},
{name: "RGB", interpolate: d3.interpolateRgb}
];
var y = d3.scale.ordinal()
.domain(spaces.map(function(d) { return d.name; }))
.rangeRoundBands([0, 500], .09);
var margin = y.range()[0],
width = 960 - margin - margin,
height = y.rangeBand();
var color = d3.scale.linear()
.domain([0, width])
.range(["hsl(62,100%,90%)", "hsl(222,30%,20%)"]);
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, i) { return y(d.name) + "px"; });
space.append("canvas")
.attr("width", width)
.attr("height", 1)
.style("width", width + "px")
.style("height", height + "px")
.each(render);
space.append("div")
.style("line-height", height + "px")
.text(function(d) { return d.name; });
function render(d) {
var context = this.getContext("2d"),
image = context.createImageData(width, 1);
color.interpolate(d.interpolate);
for (var i = 0, j = -1, c; i < width; ++i) {
c = d3.rgb(color(i));
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