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/0d563176b67c9d3062ae to your computer and use it in GitHub Desktop.
Save nitaku/0d563176b67c9d3062ae to your computer and use it in GitHub Desktop.
HCL decomposition: cubeYF

This example shows Matteo Niccoli's cube YF color scale, as well as its decomposition in the HCL color space (aka CIELCH). This perceptually-based color scale features a monotonic increase in lightness (depicted in the grayscale version below the scale), while also changing hue.

This scale can be used to perform a color encoding of a quantitative variable, for example when displaying a heat map. Classic choices for such a case are rainbow scales (which have the problem of artefacts, making the encoding excessively non-linear) or grayscales (which are fine, but lack color information, which can be useful to perceive small changes or better identify areas with a similar value).

ROYGBIV, rainbow, cube YF and gray color scales compared

This comparison (taken from this Matteo Niccoli's post) shows the use of ROYGBIV, rainbow, cube YF and gray color scales to map elevation data.

All palette data is available as CSV and other formats from this link.

palette = d3.csv.parse("""
R,G,B
123,2,144
123,3,146
124,4,149
125,5,151
126,6,153
127,7,156
128,8,158
129,9,160
129,10,163
130,11,165
131,12,168
131,12,171
132,12,174
133,13,177
133,15,179
134,17,181
134,19,184
134,21,186
134,24,188
134,26,190
134,29,192
134,31,194
134,34,196
134,36,198
133,39,200
133,41,203
133,43,204
133,45,206
132,47,208
132,49,210
132,51,212
131,53,214
131,55,216
130,56,218
130,58,219
129,60,221
129,62,223
128,64,224
128,66,226
127,68,228
127,69,230
126,71,232
125,72,234
125,74,235
122,77,238
121,78,240
120,80,241
119,81,242
118,83,244
117,84,245
117,86,247
115,87,248
115,89,250
114,90,251
113,91,251
113,92,252
112,94,252
111,95,252
110,97,253
110,98,253
109,100,253
108,101,254
108,103,254
106,104,254
106,106,254
105,107,255
105,108,255
104,110,255
104,112,254
104,113,254
103,115,253
103,116,253
102,118,252
101,119,251
100,120,250
99,122,249
98,124,248
97,126,246
97,128,245
96,130,244
95,131,243
94,132,242
93,134,241
92,135,240
91,136,239
90,137,238
90,138,237
89,139,236
88,141,234
87,142,233
86,143,232
85,144,231
84,145,230
84,147,228
82,148,227
82,150,225
81,151,224
80,152,223
79,153,222
78,154,221
77,156,219
76,157,218
76,158,217
75,159,216
74,160,215
73,161,214
72,163,212
71,164,211
70,165,210
69,167,208
67,169,205
66,170,203
65,171,202
64,172,200
64,173,199
63,174,197
62,175,196
61,176,195
60,177,193
60,178,191
59,179,190
58,180,189
57,181,187
56,182,185
56,182,184
55,184,182
55,184,181
56,185,179
57,186,177
57,187,176
58,188,175
58,188,173
59,189,171
59,190,170
60,191,168
61,192,166
61,192,164
62,193,163
62,193,161
63,194,159
64,195,158
64,196,156
65,197,153
66,198,150
67,199,148
67,200,147
68,201,145
69,202,143
69,202,141
70,203,140
70,203,138
70,204,136
71,205,134
71,205,133
72,206,131
72,207,129
73,208,128
73,208,126
74,209,124
75,210,122
75,210,120
76,211,118
76,211,116
77,212,114
77,212,113
78,213,111
79,214,109
79,214,107
80,215,106
80,215,104
81,216,102
82,217,101
82,217,100
83,218,98
84,219,95
85,220,92
86,220,90
87,221,88
88,222,87
88,222,85
89,223,84
89,223,82
89,224,81
90,225,80
90,225,79
91,226,78
91,226,76
92,227,74
92,227,73
93,228,71
95,229,71
96,229,71
98,229,72
99,229,72
101,230,72
103,230,73
104,231,73
106,231,74
108,231,74
110,232,75
111,232,75
113,232,76
115,233,77
116,233,77
118,233,77
120,233,77
123,233,78
126,233,79
130,234,80
132,235,80
134,235,80
136,235,81
138,235,81
140,235,82
142,235,82
145,235,82
147,236,83
149,236,83
152,236,84
154,236,84
156,236,84
159,236,85
161,236,85
163,236,85
164,236,85
165,236,85
166,236,86
168,236,86
171,236,86
172,236,86
173,236,86
174,236,86
176,236,87
178,236,87
180,236,87
180,236,87
182,236,87
183,236,88
184,236,88
184,236,88
187,236,88
189,236,88
191,236,89
192,236,89
192,236,89
194,236,89
197,236,89
199,236,90
200,236,90
201,236,90
202,236,90
204,236,90
204,236,90
205,236,90
207,236,91
209,235,91
""").map (d) -> d3.rgb(d.R, d.G, d.B)
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: cubeYF" />
<title>HCL decomposition: cubeYF</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.csv.parse("R,G,B\n123,2,144\n123,3,146\n124,4,149\n125,5,151\n126,6,153\n127,7,156\n128,8,158\n129,9,160\n129,10,163\n130,11,165\n131,12,168\n131,12,171\n132,12,174\n133,13,177\n133,15,179\n134,17,181\n134,19,184\n134,21,186\n134,24,188\n134,26,190\n134,29,192\n134,31,194\n134,34,196\n134,36,198\n133,39,200\n133,41,203\n133,43,204\n133,45,206\n132,47,208\n132,49,210\n132,51,212\n131,53,214\n131,55,216\n130,56,218\n130,58,219\n129,60,221\n129,62,223\n128,64,224\n128,66,226\n127,68,228\n127,69,230\n126,71,232\n125,72,234\n125,74,235\n122,77,238\n121,78,240\n120,80,241\n119,81,242\n118,83,244\n117,84,245\n117,86,247\n115,87,248\n115,89,250\n114,90,251\n113,91,251\n113,92,252\n112,94,252\n111,95,252\n110,97,253\n110,98,253\n109,100,253\n108,101,254\n108,103,254\n106,104,254\n106,106,254\n105,107,255\n105,108,255\n104,110,255\n104,112,254\n104,113,254\n103,115,253\n103,116,253\n102,118,252\n101,119,251\n100,120,250\n99,122,249\n98,124,248\n97,126,246\n97,128,245\n96,130,244\n95,131,243\n94,132,242\n93,134,241\n92,135,240\n91,136,239\n90,137,238\n90,138,237\n89,139,236\n88,141,234\n87,142,233\n86,143,232\n85,144,231\n84,145,230\n84,147,228\n82,148,227\n82,150,225\n81,151,224\n80,152,223\n79,153,222\n78,154,221\n77,156,219\n76,157,218\n76,158,217\n75,159,216\n74,160,215\n73,161,214\n72,163,212\n71,164,211\n70,165,210\n69,167,208\n67,169,205\n66,170,203\n65,171,202\n64,172,200\n64,173,199\n63,174,197\n62,175,196\n61,176,195\n60,177,193\n60,178,191\n59,179,190\n58,180,189\n57,181,187\n56,182,185\n56,182,184\n55,184,182\n55,184,181\n56,185,179\n57,186,177\n57,187,176\n58,188,175\n58,188,173\n59,189,171\n59,190,170\n60,191,168\n61,192,166\n61,192,164\n62,193,163\n62,193,161\n63,194,159\n64,195,158\n64,196,156\n65,197,153\n66,198,150\n67,199,148\n67,200,147\n68,201,145\n69,202,143\n69,202,141\n70,203,140\n70,203,138\n70,204,136\n71,205,134\n71,205,133\n72,206,131\n72,207,129\n73,208,128\n73,208,126\n74,209,124\n75,210,122\n75,210,120\n76,211,118\n76,211,116\n77,212,114\n77,212,113\n78,213,111\n79,214,109\n79,214,107\n80,215,106\n80,215,104\n81,216,102\n82,217,101\n82,217,100\n83,218,98\n84,219,95\n85,220,92\n86,220,90\n87,221,88\n88,222,87\n88,222,85\n89,223,84\n89,223,82\n89,224,81\n90,225,80\n90,225,79\n91,226,78\n91,226,76\n92,227,74\n92,227,73\n93,228,71\n95,229,71\n96,229,71\n98,229,72\n99,229,72\n101,230,72\n103,230,73\n104,231,73\n106,231,74\n108,231,74\n110,232,75\n111,232,75\n113,232,76\n115,233,77\n116,233,77\n118,233,77\n120,233,77\n123,233,78\n126,233,79\n130,234,80\n132,235,80\n134,235,80\n136,235,81\n138,235,81\n140,235,82\n142,235,82\n145,235,82\n147,236,83\n149,236,83\n152,236,84\n154,236,84\n156,236,84\n159,236,85\n161,236,85\n163,236,85\n164,236,85\n165,236,85\n166,236,86\n168,236,86\n171,236,86\n172,236,86\n173,236,86\n174,236,86\n176,236,87\n178,236,87\n180,236,87\n180,236,87\n182,236,87\n183,236,88\n184,236,88\n184,236,88\n187,236,88\n189,236,88\n191,236,89\n192,236,89\n192,236,89\n194,236,89\n197,236,89\n199,236,90\n200,236,90\n201,236,90\n202,236,90\n204,236,90\n204,236,90\n205,236,90\n207,236,91\n209,235,91").map(function(d) {
return d3.rgb(d.R, d.G, d.B);
});
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