Indeed, D3 is perfectly suited for this task (besides being joy to work with). The following is a very simple implementation, which delegates to CSS as much as possible. Much of any further customisation one may need can be implemented simply by fiddling with CSS. The whole thing should be easy to wrap up as a module.
We'll append the result to the #grid-container
div
:
<div id="grid-container"></div>
We'll work with this fake data:
function rint(limit) { return Math.floor(Math.random() * limit); }
var rowCount = rint(3) + 3;
var colCount = rint(3) + 3;
var data = d3.range(25).map(function(){ // fake data (refresh few times for variety)
return {
x: rint(colCount),
y: rint(rowCount)
};
});
We'll map data
into a three-dimensional array heat
according to those x
and y
values – heat
is what we'll visualise with the help of d3. Each heat[y][x]
will contain an array of objects with corresponding x
and y
values:
var heat = d3.range(rowCount).map(function(){
return d3.range(colCount).map(function(){
return [];
});
});
data.forEach(function(d){
heat[heat.length - d.y - 1][d.x].push(d);
});
Find the "hottest" value for the heat map – we'll use this later to generate a full range of colours:
var maxHeat = d3.max([].concat.apply([], heat), function(box){
return box.length;
});
Now we do the d3 thing. First, generate row
selection and populate it with heat
data:
var row = d3.select("#grid-container").selectAll(".row").data(heat);
Append the missing div.row
s:
row.enter().append("div").attr("class", "row");
Use row
selection to create a nested box
selection. Each box
will inherit the corresponding datum from row
's data:
var box = row.selectAll(".box").data(function(d) { return d; });
We now enter and append all the boxes in the grid:
box.enter().append("div")
.attr("class", function(d, x, y){
var klas = ["box"]
if (y == heat.length - 1) klas.push("bottom");
if (x == 0) klas.push("left");
return klas.join(" ");
})
.attr({
"data-row": function(d, x, y){ return heat.length - 1 - y; },
"data-col": function(d, x, y){ return x; }
})
.style("background-color", function(d) {
return d3.hsl( (1 - (d.length / maxHeat)) * 210, 0.8, 0.4 );
})
.text(function(d){
return d.length > 0 ? d.length : "";
});
Notice that we are tagging the left column and the bottom row of boxes with classes .left
and .bottom
, respectively. We are also appending data-row
and data-col
attributes that we can use to provide CSS content
for pseudo elements which will serve as column and row numbers:
.box.left:before {
content: attr(data-row);
}
.box.bottom:after {
content: attr(data-col);
}
Complete code at: http://jsfiddle.net/vpgsLw1v/2/