This d3 histogram shows the range of luminance values (0 - 100) for all saturation levels of red, green, and blue hues, respectively. I've used opacity on a dark background to mimic proper color-mixing. D3 transitions provide smooth animation when showing and hiding individual histograms.
The particularly interesting result here is that there is a much larger variety of light colors than of dark colors; if you were to choose an rgb()
(or hex
) color at random, bet on it being > 50 L*.
Last active
August 29, 2015 14:20
-
-
Save ajfarkas/4e9dd4842772ddde0b15 to your computer and use it in GitHub Desktop.
Multi-Series Histogram with Blended Color
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<link href="stack.css" type="text/css" rel="stylesheet"/> | |
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
<body> | |
<section id="rgb-luminance"> | |
<ul> | |
<li> | |
<button class="rgb" type="button">All</button> | |
</li> | |
<li> | |
<button class="red" type="button">Red</button> | |
</li> | |
<li> | |
<button class="green" type="button">Green</button> | |
</li> | |
<li> | |
<button class="blue" type="button">Blue</button> | |
</li> | |
</ul> | |
</section> | |
</body> | |
<script> | |
//helpers | |
function rgb2xyz (color) { | |
color = [color[0]/255, color[1]/255, color[2]/255] | |
var xyz = [0, 0, 0], | |
matrix = [[0.412453, 0.357580, 0.180423], | |
[0.212671, 0.715160, 0.072169], | |
[0.019334, 0.119193, 0.950227]] | |
matrix.forEach(function(row, i) { | |
row.forEach(function(cell, j) { | |
xyz[i] += color[j] * cell * 100 | |
}) | |
}) | |
return xyz | |
} | |
function xyz2Lab (color) { | |
var d65 = [95.0456, 100, 108.8754], | |
xyzN = [], | |
L, a, b | |
//find X/Xn, Y/Yn, Z/Zn | |
color.forEach(function(d, i) { | |
xyzN.push(d/d65[i]) | |
}) | |
var f = function(t) { | |
if (t > 0.008856) | |
return Math.pow(t, 1/3) | |
else | |
return 7.787 * t + 16/116 | |
} | |
if (xyzN[1] > 0.008856) | |
L = 116 * Math.pow(xyzN[1], 1/3) - 16 | |
else | |
L = 903.3 * xyzN[1] | |
a = 500 * ( f(xyzN[0]) - f(xyzN[1]) ) | |
b = 200 * ( f(xyzN[1]) - f(xyzN[2]) ) | |
return [L, a, b] | |
} | |
function rgb2Lab(color) { | |
return xyz2Lab(rgb2xyz(color)) | |
} | |
function lumValsByPrime(prime) { | |
var results = [], | |
colors = { | |
r: function(r, i) { return [r, i, i] }, | |
g: function(g, i) { return [i, g, i] }, | |
b: function(b, i) { return [i, i, b] } | |
} | |
prime = prime.toLowerCase() | |
for (var p = 0; p < 256; p++) { | |
for (var i = 0; i < 256; i++) { | |
var luminance = Math.floor( rgb2Lab( colors[prime](p, i) )[0] ) | |
results.push(luminance) | |
} | |
} | |
return results | |
} | |
//draw visualization | |
//config | |
var vis = { | |
'container': '#rgb-luminance', | |
'title': 'Histogram of Red, Green, and Blue Hue Luminance Values', | |
'subtitle': 'including full range of saturation' | |
} | |
//calculated config | |
vis.svg = { | |
width: d3.select(vis.container).node().clientWidth, | |
height: d3.select(vis.container).node().clientHeight | |
} | |
vis.pad = {top: 80, right: 80, bottom: 80, left: 80} | |
vis.width = vis.svg.width - vis.pad.left - vis.pad.right | |
vis.height = vis.svg.height - vis.pad.top - vis.pad.bottom | |
vis.colors = { | |
r: function(r, i) { return 'rgb('+r+','+i+','+i+')' }, | |
g: function(g, i) { return 'rgb('+i+','+g+','+i+')' }, | |
b: function(b, i) { return 'rgb('+i+','+i+','+b+')' } | |
} | |
var x = d3.scale.linear() | |
.domain([0, 100]) | |
.range([0, vis.width]) | |
var histR = d3.layout.histogram() | |
.bins(x.ticks(100)) | |
(lumValsByPrime('r')) | |
var histG = d3.layout.histogram() | |
.bins(x.ticks(100)) | |
(lumValsByPrime('g')) | |
var histB = d3.layout.histogram() | |
.bins(x.ticks(100)) | |
(lumValsByPrime('b')) | |
var y = d3.scale.linear() | |
.domain([0, 2000]) | |
.range([vis.height, 0]) | |
var xAxis = d3.svg.axis() | |
.scale(x) | |
.orient('bottom') | |
var yAxis = d3.svg.axis() | |
.scale(y) | |
.ticks(4) | |
.tickFormat(function(d) { return d / 1000 }) | |
.orient('left') | |
var svg = d3.select(vis.container).append('svg') | |
.attr('width', vis.svg.width) | |
.attr('height', vis.svg.height) | |
var title = svg.append('text') | |
.attr('class', 'title') | |
.attr('text-anchor', 'middle') | |
.attr('transform', 'translate(' + | |
vis.svg.width / 2 + ',' + vis.pad.top / 2 + ')') | |
.text(vis.title) | |
.append('tspan') | |
.attr('x', 0) | |
.attr('dy', 1.1+'em') | |
.text(vis.subtitle) | |
var graph = svg.append('g') | |
.attr('class', 'graph') | |
.attr('width', vis.width) | |
.attr('height', vis.height) | |
.attr('transform', 'translate('+vis.pad.left+','+vis.pad.top+')') | |
//mark color, for fill function | |
histR.forEach(function(_, i) { | |
histR[i].c = 'r' | |
histG[i].c = 'g' | |
histB[i].c = 'b' | |
}) | |
var histogram = histR.concat(histG).concat(histB) | |
var bar = graph.selectAll('.bar') | |
.data(histogram) | |
.enter().append('g') | |
.attr('class', 'bar') | |
.attr('transform', function(d) { | |
return 'translate('+x(d.x)+',0)' | |
}) | |
bar.append('rect') | |
.attr('x', 1) | |
.attr('y', function(d) { return y(d.y) }) | |
.attr('width', x(histR[0].dx) - 1) | |
.attr('height', function(d) { | |
return vis.height - y(d.y) | |
}) | |
.attr('fill', function(d) { | |
//map colors 0 - 150 (for legibility) | |
var n = Math.round(d.x * 1.5) | |
return vis.colors[d.c](255, n)//'rgb(255,'+n+','+n+')' | |
}) | |
graph.append('g') | |
.attr('class', 'x axis') | |
.attr('transform', 'translate(0,' + vis.height + ')') | |
.call(xAxis) | |
// add axis label | |
.append('text') | |
.attr('class', 'label') | |
.attr('x', vis.width/2) | |
.attr('dy', svg.select('.x').node().getBBox().height + 14) | |
.text('Luminance Value') | |
graph.append('g') | |
.attr('class', 'y axis') | |
.call(yAxis) | |
// add axis label | |
.append('text') | |
.attr('class', 'label') | |
.attr('x', -vis.height/2) | |
.attr('dy', - svg.select('.y').node().getBBox().width) | |
.attr('transform', 'rotate(-90)') | |
.text('Thousands of Colors') | |
//hide colors on button click | |
var visNode = d3.select('#rgb-luminance'), | |
rects = visNode.selectAll('rect'), | |
btns = ['rgb', 'red', 'green', 'blue'] | |
btns.forEach(function(btnColor) { | |
visNode.select('.'+btnColor).on('click', function() { | |
rects.transition() | |
.duration(500) | |
.attr('height', function(d) { | |
if (d.c !== btnColor.substr(0,1) && btnColor !== 'rgb') | |
return 0 | |
else | |
return vis.height - y(d.y) | |
}) | |
.attr('y', function(d) { | |
if (d.c !== btnColor.substr(0,1) && btnColor !== 'rgb') | |
return vis.height | |
else | |
return y(d.y) | |
}) | |
}) | |
}) | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
* { | |
margin: 0; | |
padding: 0; | |
} | |
body { | |
font-family: helvetica, sans-serif; | |
font-size: 14px; | |
} | |
#rgb-luminance { | |
width: 100vw; | |
height: 100vh; | |
background-color: black; | |
} | |
text { fill: white; } | |
line, | |
path { | |
fill: transparent; | |
stroke-width: 2; | |
stroke: white; | |
} | |
text.title { font-size: 18px; } | |
.title tspan { font-size: 14px; } | |
.bar rect { | |
stroke-width: 1; | |
opacity: .5; | |
} | |
.axis line, | |
.domain { | |
stroke-width: 2; | |
stroke: gray; | |
} | |
.label { text-anchor: middle; } | |
ul { | |
list-style: none; | |
position: absolute; | |
top: 80px; | |
left: 100px; | |
} | |
button { | |
width: 53px; | |
height: 24px; | |
margin-bottom: 4px; | |
border: 1px solid white; | |
border-radius: 8px; | |
color: white; | |
text-transform: uppercase | |
} | |
button.rgb { | |
background-color: #595959; | |
border-color: #7a7a7a; | |
} | |
button.rgb:hover { background-color: #7a7a7a; } | |
button.red { | |
background-color: #a92222; | |
border-color: #cb4444; | |
} | |
button.red:hover { background-color: #cb4444; } | |
button.green { | |
background-color: #0b710b; | |
border-color: #2d932d; | |
} | |
button.green:hover { background-color: #2d932d; } | |
button.blue { | |
background-color: #191992; | |
border-color: #3a3aa4; | |
} | |
button.blue:hover { background-color: #3a3aa4; } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment