|
<!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> |