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 <...