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
xxxxxxxxxx
<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>
https://d3js.org/d3.v3.min.js