Example of binning an array of points and then creating a heatmap from it.
Built with blockbuilder.org
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
</style>
</head>
<body>
<div id='chart'></div>
<script>
const points = [[-5, 5],[5,-5],[0,10], [0, 5], [0,2], [0, 3], [10,15], [20,25], [20, 24]];
const gridX = 10;
const gridY = 10;
// could also compute bbox from points array
// upper left, lower right; x, y
const bbox = [
[-10, -10],
[30, 30]
];
function makeGridStructure(gridX, gridY, bbox) {
const width = Math.abs(bbox[0][0] - bbox[1][0]) / gridX;
const height = Math.abs(bbox[0][1] - bbox[1][1]) / gridY;
let data = [];
function makeDatum(i, j) {
// i represents x pos
// j represents y pos
let datum = {
id: '' + i + j,
upperLeft: [],
lowerRight: [],
points: [],
pointCount: 0
};
datum.upperLeft[0] = (i * width) - Math.abs(bbox[0][0]);
datum.upperLeft[1] = (j * height) - Math.abs(bbox[0][1]);
datum.lowerRight[0] = (i * width) - Math.abs(bbox[0][0]) + width;
datum.lowerRight[1] = (j * height) - Math.abs(bbox[0][1]) + height;
points.forEach(function(p) {
// to fix: this will currently count points twice if they fall on a datum's bbox edge
if (p[0] >= datum.upperLeft[0] && p[0] <= datum.lowerRight[0] &&
p[1] >= datum.upperLeft[1] && p[1] <= datum.lowerRight[1]) {
datum.pointCount += 1;
datum.points.push(p);
}
});
return datum;
}
for (let i = 0; i < gridX; i += 1) {
for (let j = 0; j < gridY; j += 1) {
const d = makeDatum(i, j);
data.push(d);
}
}
return data;
}
const data = makeGridStructure(gridX, gridY, bbox);
// Render using D3
const margin = { top: 10, bottom: 20, left: 30, right: 10 };
const width = 500 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom;
const rectWidth = width / gridX;
const rectHeight = height / gridY;
const x = d3.scaleLinear()
.domain([
d3.min(data, function(d) { return d.upperLeft[0]; }),
d3.max(data, function(d) { return d.lowerRight[0]; })
])
.range([0, width]);
const y = d3.scaleLinear()
.domain([
d3.min(data, function(d) { return d.upperLeft[1]; }),
d3.max(data, function(d) { return d.lowerRight[1]; })
])
.range([height, 0]);
const color = d3.scaleSequential(d3.interpolateGnBu)
.domain([0, d3.max(data, function(d) { return d.pointCount; })]);
const yaxis = d3.axisLeft()
.scale(y);
const xaxis = d3.axisBottom()
.scale(x);
const 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 + ')');
const rects = svg.selectAll('rect')
.data(data, function(d) { return d; })
.enter().append('rect')
.attr('x', function(d, i) {
return x(d.upperLeft[0]);
})
.attr('y', function(d) {
return y(d.lowerRight[1]);
})
.attr('width', function(d) {
return rectWidth;
})
.attr('height', function(d) {
return rectHeight;
})
.attr('fill', function(d) {
return color(d.pointCount);
})
.on('mouseover', function(d) {
console.log(d.pointCount);
});
svg.append('g')
.call(yaxis);
svg.append('g')
.attr("transform", "translate(0," + height + ")")
.call(xaxis);
</script>
</body>
https://d3js.org/d3.v4.min.js
https://d3js.org/d3-scale-chromatic.v1.min.js