Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active August 29, 2015 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nitaku/52d32929238ec97bc2c8 to your computer and use it in GitHub Desktop.
Save nitaku/52d32929238ec97bc2c8 to your computer and use it in GitHub Desktop.
HCL decomposition: HSL rainbow

This example shows a rainbow scale and its decomposition in the Hue-Chroma-Lightness color space (aka HCL or CIELCH).

The HCL decomposition clearly shows that lightness and chroma are not constant, and change in a non-linear, erratic fashion, making this scale a bad choice for encoding linear or categorical data.

For more details, see also this example on HCL and this analysis of a rainbow color scale by Mike Bostock.

palette = d3.range(256).map (i) -> d3.hsl(360*i/256, 1, 0.5)
svg = d3.select('svg')
svg.selectAll('.colored_band')
.data(palette)
.enter().append('rect')
.attr('class', 'colored_band')
.attr('x', (d,i)->32+3.5*i)
.attr('y', 21)
.attr('width', 3.5)
.attr('height', 120)
.attr('fill', (d)->d )
svg.selectAll('.hue_band')
.data(palette)
.enter().append('rect')
.attr('class', 'hue_band')
.attr('x', (d,i)->32+3.5*i)
.attr('y', 166)
.attr('width', 3.5)
.attr('height', 60)
.attr('fill', (d) ->
hcl = d3.hcl(d)
hcl.c = 60
hcl.l = 60
return hcl
)
svg.selectAll('.chroma_band')
.data(palette)
.enter().append('rect')
.attr('class', 'chroma_band')
.attr('x', (d,i)->32+3.5*i)
.attr('y', 237)
.attr('width', 3.5)
.attr('height', 116)
.attr('fill', (d) ->
hcl = d3.hcl(d)
hcl.l = hcl.c/1.4
hcl.h = 80
hcl.c = 25
return hcl
)
svg.selectAll('.luminance_band')
.data(palette)
.enter().append('rect')
.attr('class', 'luminance_band')
.attr('x', (d,i)->32+3.5*i)
.attr('y', 364)
.attr('width', 3.5)
.attr('height', 116)
.attr('fill', (d) ->
hcl = d3.hcl(d)
hcl.c = 0
return hcl
)
x = d3.scale.linear()
.domain([0,255])
.range([32, 928])
h_y = d3.scale.linear()
.domain([0,360])
.range([166+60, 166])
c_y = d3.scale.linear()
.domain([0,150])
.range([237+116, 237])
l_y = d3.scale.linear()
.domain([0,100])
.range([364+116, 364])
h_line = d3.svg.line()
.x((_, i) -> x(i))
.y((d) ->
h = d3.hcl(d).h
return h_y( if h > 0 then h else h+360 )
)
c_line = d3.svg.line()
.x((_, i) -> x(i))
.y((d) -> c_y( d3.hcl(d).c ) )
l_line = d3.svg.line()
.x((_, i) -> x(i))
.y((d) -> l_y( d3.hcl(d).l ) )
svg.append('path')
.datum(palette)
.attr('class', 'hue')
.attr('d', h_line)
svg.append('path')
.datum(palette)
.attr('class', 'chroma')
.attr('d', c_line)
svg.append('path')
.datum(palette)
.attr('class', 'luminance')
.attr('d', l_line)
h_axis = d3.svg.axis()
.scale(h_y)
.tickValues([0,360])
.orient('right')
.tickSize(3,5)
c_axis = d3.svg.axis()
.scale(c_y)
.ticks(4)
.orient('right')
.tickSize(3,5)
l_axis = d3.svg.axis()
.scale(l_y)
.ticks(2)
.orient('right')
.tickSize(3,5)
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(932,0)')
.call(h_axis)
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(932,0)')
.call(c_axis)
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(932,0)')
.call(l_axis)
body {
background: #272822;
margin: 0;
padding: 0;
}
svg {
background: white;
}
.label {
text-anchor: middle;
font-size: 24px;
}
rect {
shape-rendering: crispEdges;
}
path {
fill: none;
}
path.hue {
stroke: white;
stroke-width: 1;
}
path.chroma {
stroke: #FF3D00;
stroke-width: 1;
}
path.luminance {
stroke: black;
stroke-width: 0.6;
}
.axis .domain, .axis .tick line {
stroke: black;
shape-rendering: crispEdges;
}
.axis {
font-size: 8px;
font-family: sans-serif;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="HCL decomposition: HSL rainbow" />
<title>HCL decomposition: HSL rainbow</title>
<link rel="stylesheet" href="index.css">
<script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<svg height="500" width="960">
<text class="label" x="16" y="198" dy="0.35em">H</text>
<text class="label" x="16" y="295" dy="0.35em">C</text>
<text class="label" x="16" y="420" dy="0.35em">L</text>
</svg>
<script src="index.js"></script>
</body>
</html>
(function() {
var c_axis, c_line, c_y, h_axis, h_line, h_y, l_axis, l_line, l_y, palette, svg, x;
palette = d3.range(256).map(function(i) {
return d3.hsl(360 * i / 256, 1, 0.5);
});
svg = d3.select('svg');
svg.selectAll('.colored_band').data(palette).enter().append('rect').attr('class', 'colored_band').attr('x', function(d, i) {
return 32 + 3.5 * i;
}).attr('y', 21).attr('width', 3.5).attr('height', 120).attr('fill', function(d) {
return d;
});
svg.selectAll('.hue_band').data(palette).enter().append('rect').attr('class', 'hue_band').attr('x', function(d, i) {
return 32 + 3.5 * i;
}).attr('y', 166).attr('width', 3.5).attr('height', 60).attr('fill', function(d) {
var hcl;
hcl = d3.hcl(d);
hcl.c = 60;
hcl.l = 60;
return hcl;
});
svg.selectAll('.chroma_band').data(palette).enter().append('rect').attr('class', 'chroma_band').attr('x', function(d, i) {
return 32 + 3.5 * i;
}).attr('y', 237).attr('width', 3.5).attr('height', 116).attr('fill', function(d) {
var hcl;
hcl = d3.hcl(d);
hcl.l = hcl.c / 1.4;
hcl.h = 80;
hcl.c = 25;
return hcl;
});
svg.selectAll('.luminance_band').data(palette).enter().append('rect').attr('class', 'luminance_band').attr('x', function(d, i) {
return 32 + 3.5 * i;
}).attr('y', 364).attr('width', 3.5).attr('height', 116).attr('fill', function(d) {
var hcl;
hcl = d3.hcl(d);
hcl.c = 0;
return hcl;
});
x = d3.scale.linear().domain([0, 255]).range([32, 928]);
h_y = d3.scale.linear().domain([0, 360]).range([166 + 60, 166]);
c_y = d3.scale.linear().domain([0, 150]).range([237 + 116, 237]);
l_y = d3.scale.linear().domain([0, 100]).range([364 + 116, 364]);
h_line = d3.svg.line().x(function(_, i) {
return x(i);
}).y(function(d) {
var h;
h = d3.hcl(d).h;
return h_y(h > 0 ? h : h + 360);
});
c_line = d3.svg.line().x(function(_, i) {
return x(i);
}).y(function(d) {
return c_y(d3.hcl(d).c);
});
l_line = d3.svg.line().x(function(_, i) {
return x(i);
}).y(function(d) {
return l_y(d3.hcl(d).l);
});
svg.append('path').datum(palette).attr('class', 'hue').attr('d', h_line);
svg.append('path').datum(palette).attr('class', 'chroma').attr('d', c_line);
svg.append('path').datum(palette).attr('class', 'luminance').attr('d', l_line);
h_axis = d3.svg.axis().scale(h_y).tickValues([0, 360]).orient('right').tickSize(3, 5);
c_axis = d3.svg.axis().scale(c_y).ticks(4).orient('right').tickSize(3, 5);
l_axis = d3.svg.axis().scale(l_y).ticks(2).orient('right').tickSize(3, 5);
svg.append('g').attr('class', 'axis').attr('transform', 'translate(932,0)').call(h_axis);
svg.append('g').attr('class', 'axis').attr('transform', 'translate(932,0)').call(c_axis);
svg.append('g').attr('class', 'axis').attr('transform', 'translate(932,0)').call(l_axis);
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment