Canvas API-based heatmap
by flek
HTML
<div id="heatmap">
<canvas id="canvas"></canvas>
<div id="scroll-container">
<div id="scroll"></div>
</div>
</div>
CSS
#heatmap {
position: absolute;
top: 1rem;
left: 1rem;
right: 1rem;
bottom: 1rem;
}
#canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
#scroll-container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: auto;
}
#scroll {
position: absolute;
top: 0;
left: 0;
}
JavaScript
const t00 = performance.now();
const canvas = document.getElementById('canvas');
const scrollContainer = document.getElementById('scroll-container');
const scroll = document.getElementById('scroll');
const ctx = canvas.getContext("2d");
const nCols = 1000;
const nRows = 100;
const cellSize = 6;
const cellSpacing = 1;
const width = nCols * cellSize + (nCols - 1) * cellSpacing;
const height = nRows * cellSize + (nRows - 1) * cellSpacing;
const scale = window.devicePixelRatio;
const stageBBox = canvas.getBoundingClientRect();
canvas.width = Math.floor(stageBBox.width * scale);
canvas.height = Math.floor(stageBBox.height * scale);
scroll.style.width = `${width}px`;
scroll.style.height = `${height}px`;
ctx.scale(scale, scale);
function roundRect(ctx, x, y, w, h, r) {
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
ctx.beginPath();
ctx.moveTo(x+r, y);
ctx.arcTo(x+w, y, x+w, y+h, r);
ctx.arcTo(x+w, y+h, x, y+h, r);
ctx.arcTo(x, y+h, x, y, r);
ctx.arcTo(x, y, x+w, y, r);
ctx.closePath();
return ctx;
}
const d = {
x: [],
y: [],
color: []
}
for (let i = 0; i < nRows; i++) {
for (let j = 0; j < nCols; j++) {
const c = Math.round(Math.random() * 255).toString(16);
d.x.push(j * (cellSize + cellSpacing));
d.y.push(i * (cellSize + cellSpacing));
d.color.push(`#${c}${c}${c}`);
}
}
function draw() {
const t0 = performance.now();
ctx.setTransform(
scale, 0, 0, scale, 0, 0
);
ctx.clearRect(
0, 0, canvas.width, canvas.height
);
ctx.setTransform(
scale, 0, 0, scale,
-scrollContainer.scrollLeft,
-scrollContainer.scrollTop
);
const n = d.x.length;
const w = canvas.width / scale;
const h = canvas.height / scale;
let k = 0;
for (let i = 0; i < n; i++) {
const x = d.x[i];
const y = d.y[i];
const tX = x - scrollContainer.scrollLeft / scale;
const tY = y - scrollContainer.scrollTop / scale;
if (
tX < -cellSize || tX > w ||
tY <...