Skip to content

Instantly share code, notes, and snippets.

@ajfarkas
Last active Aug 29, 2015
Embed
What would you like to do?
z-stacked multi-series bar chart w animation

A 3-series histogram of red, green, and blue hue luminance distribution. The bars are z-axis sorted by height, and the graph is sortable by color, with animation.

<!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': 'in thousands of colors'
}
//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 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+')')
//sort and combine all three histograms so shortest bar is in front
var sortColors = function() {
var result = []
var colorOrder = function(d, i) {
var red = d,
green = histG[i],
blue = histB[i]
//mark color, for fill function
red.c = 'r'
green.c = 'g'
blue.c = 'b'
//sort largest (in back) to smallest (up front)
var order = [red, green, blue].sort(function (a, b) {
return b.y - a.y;
})
order.forEach(function(col) {
result.push(col)
})
}
histR.forEach(function(d, i) {
colorOrder(d,i)
})
return result
}
var histogram = sortColors()
var bar = graph.selectAll('.bar')
.data(histogram)
.enter().append('g')
.attr('class', 'bar')
.attr('transform', function(d) {
return 'translate('+x(d.x)+',0)'//,'+y(d.y)+')'
})
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)
graph.append('g')
.attr('class', 'y axis')
.call(yAxis)
//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>
body {
font-family: helvetica, sans-serif;
font-size: 14px;
}
#rgb-luminance {
width: 100vw;
height: 100vh;
}
text { fill: black; }
line,
path {
fill: transparent;
stroke-width: 2;
stroke: black;
}
text.title { font-size: 18px; }
.title tspan { font-size: 14px; }
.bar rect {
stroke-width: 1;
}
.axis line,
.domain {
stroke-width: 2;
stroke: gray;
}
ul {
list-style: none;
position: absolute;
top: 15%;
left: 4%;
}
button {
width: 60px;
height: 26px;
margin-bottom: 2px;
border: none;
border-radius: 4px;
background-color: #3e3e3e;
color: white;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment