Skip to content

Instantly share code, notes, and snippets.

@ajfarkas
Last active August 29, 2015 14:20
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 ajfarkas/4e9dd4842772ddde0b15 to your computer and use it in GitHub Desktop.
Save ajfarkas/4e9dd4842772ddde0b15 to your computer and use it in GitHub Desktop.
Multi-Series Histogram with Blended Color

Luminance Histogram for R, G, B Hues

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*.

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