Skip to content

Instantly share code, notes, and snippets.

@ajfarkas
Last active Aug 29, 2015
Embed
What would you like to do?
Luminance Histograms for Red, Green, and Blue Hues

Histograms of the range of luminance values for all unmixed hues of red, green, and blue.

Middle gray for our eyes is 18% gray for computer monitors (rgb(47,47,47) is L*a*b*(50.015), where L*.domain([0, 100])). The background color is the RGB value closest to L* == 50.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: helvetica, sans-serif;
font-size: 14px;
background-color: #20b9cb;
}
#red-luminance,
#green-luminance,
#blue-luminance {
width: 480px;
height: 250px;
float: left;
}
text { fill: black; }
line,
path {
fill: transparent;
stroke-width: 2;
stroke: black;
}
text.title {
font-size: 18px;
fill: white;
}
.title tspan { font-size: 14px; }
.bar rect {
stroke-width: 1;
}
</style>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<div id="red-luminance"></div>
<div id="green-luminance"></div>
<div id="blue-luminance"></div>
<script>
//find luminance values by one primary 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] }
}
for (var p = 0; p < 256; p++) {
for (var i = 0; i < 256; i++) {
//push the the integer of L* only
var luminance = Math.floor( xyz2Lab(rgb2xyz( colors[prime](p, i) ) )[0] )
results.push(luminance)
}
}
return results
}
//helper functions
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]
}
//create visualization
function colorVis(color) {
//config
var vis = {
'container': '#'+color+'-luminance',
'title': 'Histogram of ' +
color.replace(/\D+/, function(t) {
return t.charAt(0).toUpperCase() + t.substr(1).toLowerCase() }) +
' Luminance Values',
'subtitle': 'in thousands of colors',
'data': lumValsByPrime(color.slice(0,1))
}
//calculated config
vis.svg = {
width: d3.select(vis.container).node().clientWidth,
height: d3.select(vis.container).node().clientHeight
}
vis.pad = {top: 80, right: 40, bottom: 40, left: 40}
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 histogram = d3.layout.histogram()
.bins(x.ticks(100))
(vis.data)
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)
//display units as e3
.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+')')
var bar = graph.selectAll('.bar')
.data(histogram)
.enter().append('g')
.attr('class', 'bar')
.attr('transform', function(d) {
return 'translate('+x(d.x)+','+y(d.y)+')'
})
bar.append('rect')
.attr('x', 1)
.attr('width', x(histogram[0].dx) - 1)
.attr('height', function(d) {
return vis.height - y(d.y)
})
.attr('fill', function(d) {
var n = Math.round(d.x * 2.55)
return vis.colors[color.slice(0,1)](255, n)
})
graph.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + vis.height + ')')
.call(xAxis)
graph.append('g')
.attr('class', 'y axis')
.call(yAxis)
}
colorVis('red')
colorVis('green')
colorVis('blue')
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment