An example of using D3 v4 for making quilted block designs (also called color weaving; see Albers et al. work on visual aggregation for more information). There’s probably a way to abstract the quilted block generation in a d3 module, but hopefully the intuition is pretty clear from the code.
The data is randomly generated from a normal distribution, with the color scale quantized around two standard deviations from the mean. Refresh the page to see different distributions of data.
forked from yelper‘s block: Quilted Blocks in D3 v4
<!DOCTYPE html>
<meta charset="utf-8">
<style>
canvas, svg {
position: absolute;
top: 0;
left: 0;
}
svg {
z-index: 10;
}
.axis-grid line {
stroke: #fff;
stroke-width: 2;
}
</style>
<body>
<script src="//d3js.org/d3.v4.js"></script>
<script>
var svgWidth = 960, svgHeight = 500;
var margin = {top: 200, right: 40, bottom: 200, left: 40};
var width = svgWidth - margin.left - margin.right;
var height = svgHeight - margin.top - margin.bottom;
var normalNoise = d3.randomNormal();
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
var data = months.map(function(month) {
return {
month: month,
data: d3.range(20).map(function(i) {
return {
key: i,
value: normalNoise()
};
})
};
});
var x = d3.scaleTime()
.domain([new Date(2016, 0, 1) - 1, new Date(2016, 11, 31)])
.rangeRound([0, width]);
var bandwidth = x(new Date(2016, 1, 1));
// colorbrewer.PiYG[11]
var colorScale = d3.scaleQuantize()
.domain([-2, 2])
.range(["#8e0152", "#c51b7d", "#de77ae", "#f1b6da", "#fde0ef", "#f7f7f7", "#e6f5d0", "#b8e186", "#7fbc41", "#4d9221", "#276419"]);
var svg = d3.select('body').append('svg')
.attr('width', svgWidth)
.attr('height', svgHeight)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
svg.append('g')
.attr('class', 'axis axis-x')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x)
.ticks(d3.timeMonth)
.tickFormat(d3.timeFormat("%B"))
.tickSize(10))
.selectAll("text")
.attr('dx', x(new Date(2016, 0, 15)))
.attr('text-anchor', 'middle');
svg.append('g')
.attr('class', 'axis axis-grid')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x)
.ticks(d3.timeMonth)
.tickSize(-height)
.tickFormat(function() { return null; }))
.append('line')
.attr('y2', -100)
.attr('x1', width)
.attr('x2', width);
var canvas = d3.select('body').append('canvas')
.attr('width', svgWidth)
.attr('height', svgHeight);
var ctx = canvas.node().getContext('2d');
render();
function render() {
data.forEach(function(month, m) {
// containing box is the following:
var w = bandwidth;
var h = height;
var xstart = x(new Date(2016, m, 1)) + margin.left;
var ystart = margin.top;
var mData = month.data.map(function(d) { return d.value; });
// all pixels to fill
for (var i = 0; i < w * h; i++) {
var di = i % mData.length;
if (di == 0) shuffle(mData);
var curx = i % w + xstart;
var cury = Math.floor(i / w) + ystart;
var curdatum = mData[di];
ctx.fillStyle = colorScale(curdatum);
ctx.fillRect(curx, cury, 1, 1);
}
});
// get rid of hanging excess due to rounding of scales
ctx.clearRect(width + margin.left, margin.top, margin.right, height);
};
// fisher-yates shuffling
function shuffle(arr) {
var i = arr.length;
var tmp, ri;
while (i !== 0) {
ri = Math.floor(Math.random() * i--);
tmp = arr[i];
arr[i] = arr[ri];
arr[ri] = tmp;
}
};
</script>