Decomposing an image's color information. Click an image to see it decomposed.
Alternative design to this block using circle packing instead of treemaps.
Images source: National Gallery of Art's Highlights
Decomposing an image's color information. Click an image to see it decomposed.
Alternative design to this block using circle packing instead of treemaps.
Images source: National Gallery of Art's Highlights
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>HSL Decomposition 2</title> | |
<style> | |
.images { | |
margin-left: 75px; | |
margin-right: 75px; | |
width: 200px; | |
float: left; | |
} | |
.chart { | |
width: 500px; | |
float: left; | |
} | |
.axis { | |
font: 10px sans-serif; | |
} | |
.axis path, | |
.axis line { | |
fill: none; | |
stroke: #000; | |
shape-rendering: crispEdges; | |
} | |
ul { | |
list-style-type: none; | |
margin: 0; | |
padding: 0; | |
} | |
li { | |
display: inline; | |
} | |
img { | |
cursor: pointer; | |
border: 2px solid white; | |
} | |
.selected { | |
border: 2px solid red; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="images"> | |
<ul> | |
<li><img src="van-gogh.jpg" alt="Van Gogh" class="selected" /></li> | |
<li><img src="fragonard.jpg" alt="Fragonard" /></li> | |
<li><img src="turner.jpg" alt="Turner" /></li> | |
<li><img src="whistler.jpg" alt="Whistler"></li> | |
<li><img src="da-vinci.jpg" alt="Da Vinci"></li> | |
<li><img src="picasso.jpg" alt="Picasso"></li> | |
<li><img src="rubens.jpg" alt="Rubens"></li> | |
<li><img src="vermeer.jpg" alt="Vermeer"></li> | |
<li><img src="lichtenstein.jpg" alt="Lichtenstein"></li> | |
<li><img src="matisse.jpg" alt="Matisse"></li> | |
</ul> | |
</div> | |
<div class="chart"></div> | |
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
<script> | |
var margin = { top: 10, left: 50, bottom: 50, right: 10 }, | |
width = 500 - margin.left - margin.right, | |
height = 500 - margin.top - margin.bottom, | |
range = d3.range(0, 1, 1/10); | |
var bins = { | |
h: d3.scale.quantize().domain([0, 360]).range(range), | |
s: d3.scale.quantize().domain([0, 1]).range(range), | |
l: d3.scale.quantize().domain([0, 1]).range(range), | |
}; | |
var scale = { | |
x: d3.scale.ordinal().domain(range).rangeRoundBands([0, width]), | |
y: d3.scale.ordinal().domain(range).rangeRoundBands([height, 0]), | |
size: d3.scale.pow().exponent(.25).range([0, 1]) | |
}; | |
var axis = { | |
x: d3.svg.axis().scale(scale.x).orient("bottom").ticks(5).tickFormat(d3.format(".2f")), | |
y: d3.svg.axis().scale(scale.y).orient("left").tickFormat(d3.format(".2f")) | |
}; | |
var svg = d3.select(".chart").append("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom) | |
.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
svg.append("g") | |
.attr("class", "x axis") | |
.attr("transform", "translate(0," + height + ")") | |
.call(axis.x) | |
.append("text") | |
.attr("x", width) | |
.attr("y", -6) | |
.style("text-anchor", "end") | |
.text("Lightness"); | |
svg.append("g") | |
.attr("class", "y axis") | |
.call(axis.y) | |
.append("text") | |
.attr("transform", "rotate(-90)") | |
.attr("y", 6) | |
.attr("dy", ".71em") | |
.style("text-anchor", "end") | |
.text("Saturation"); | |
var images = d3.selectAll("img") | |
.on("click", function() { | |
images.classed("selected", false); | |
var clicked = d3.select(this).classed("selected", true), | |
url = clicked.attr("src"); | |
decomposeImage(url); | |
}); | |
decomposeImage("van-gogh.jpg"); | |
function decomposeImage(url) { | |
getImageData(url, function(imageData) { | |
svg.call(render, binHsl(imageData, bins)); | |
}); | |
} | |
function render(selection, data) { | |
scale.size.domain([0, d3.max(data, function(d) { return d.freq; })]); | |
var pack = d3.layout.pack() | |
.children(function(d) { return d.values; }) | |
.value(function(d) { return d.freq; }) | |
.sort(function(a,b) { return a.value - b.value; }); | |
var packs = selection.selectAll(".pack").data(data); | |
packs.enter().append("g") | |
.attr("class", "pack"); | |
packs | |
.attr("transform", function(d) { | |
var size = scale.size(d.freq), | |
xShift = scale.x.rangeBand() * (1-size)/2, | |
yShift = scale.y.rangeBand() * (1-size)/2; | |
return "translate(" + (scale.x(d.l) + xShift) + "," + | |
(scale.y(d.s) + yShift) + ")"; | |
}); | |
packs.exit().remove(); | |
var circles = packs.selectAll("circle") | |
.data(function(d) { | |
var size = scale.size(d.freq), | |
dx = scale.x.rangeBand(), | |
dy = scale.y.rangeBand(); | |
pack = pack | |
.size([dx*size, dy*size]); | |
return pack.nodes(d); | |
}); | |
circles.enter().append("circle"); | |
circles | |
.filter(function(d) { return d.color !== undefined; }) | |
.attr("cx", function(d) { return d.x; }) | |
.attr("cy", function(d) { return d.y; }) | |
.attr("r", 0) | |
.style("fill", "white") | |
.transition().delay(function(d) { return d.s * d.l * 1000; }) | |
.attr("r", function(d) { return d.r; }) | |
.style("fill", function(d) { return d.color; }); | |
circles.exit().remove(); | |
} | |
function getImageData(url, callback) { | |
var img = new Image(); | |
img.src = url; | |
var canvas = document.createElement("canvas"); | |
var context = canvas.getContext("2d"); | |
img.onload = function() { | |
canvas.width = img.width; | |
canvas.height = img.height; | |
context.drawImage(img, 0, 0); | |
img.style.display = "none"; | |
var imageData = context.getImageData(0, 0, canvas.width, canvas.height); | |
callback(imageData); | |
} | |
} | |
function toMatrix(imageData) { | |
var data = imageData.data, | |
matrix = new Array(data.length); | |
for (var i = 0; i < data.length; i += 4) { | |
matrix[i/4] = data.subarray(i, i+4); | |
} | |
return matrix; | |
} | |
function binHsl(imageData, bins) { | |
var color = toMatrix(imageData) | |
.map(function(d) { | |
return d3.hsl("rgb(" + d[0] + "," + d[1] + "," + d[2] + ")"); | |
}) | |
.filter(function(d) { return d !== undefined; }); | |
color = d3.nest() | |
.key(function(d) { | |
return "s" + bins.s(d.s) + "-l" + bins.l(d.l); | |
}) | |
.key(function(d) { | |
return "h" + bins.h(d.h); | |
}) | |
.rollup(function(d) { return d.length; }) | |
.entries(color) | |
.map(function(sl) { | |
var s = +sl.key.split("-")[0].slice(1), | |
l = +sl.key.split("-")[1].slice(1), | |
freq = sl.values.reduce(function(a, b) { | |
return { values: a.values + b.values }; | |
}).values; | |
return { | |
key: sl.key, | |
s: s, | |
l: l, | |
freq: freq, | |
values: sl.values.map(function(d) { | |
var h = +d.key.slice(1), | |
freq = d.values, | |
color = d3.hsl( | |
d3.mean(bins.h.invertExtent(h)), | |
d3.mean(bins.s.invertExtent(s)), | |
d3.mean(bins.l.invertExtent(l)) | |
).toString(); | |
return { h:h, s:s, l:l, freq:freq, color:color }; | |
}) | |
}; | |
}); | |
return color; | |
} | |
</script> | |
</body> | |
</html> |