Heatmap

히트맵

Crosses two categorical axes into a grid and fills each cell by magnitude — the fastest way to scan a big table at a glance.

Also known as: Matrix heatmap히트 매트릭스
···
html
<div class="viz">
  <svg id="svg"></svg>
  <div class="scale minor" id="scale"><span>0</span><i></i><span>100</span></div>
</div>
css
body{display:block}
.viz{display:flex;flex-direction:column;width:100%;height:100%}
svg{flex:1;min-height:0;display:block;width:100%;overflow:visible}
.scale{flex:none;display:flex;align-items:center;justify-content:center;gap:6px;padding:2px 4px 4px;font:600 9px/1 system-ui,sans-serif;color:var(--muted)}
.scale i{width:60px;height:7px;border-radius:4px;background:linear-gradient(90deg, color-mix(in srgb, var(--accent) 12%, var(--surface)), var(--accent))}
.cell{transition:fill .6s ease}
js
var svg = document.getElementById('svg');
var NS = 'http://www.w3.org/2000/svg';
function se(t){ return document.createElementNS(NS,t); }
function sa(e,o){ for (var k in o) e.setAttribute(k,o[k]); }
function R(a,b){ return a + Math.random()*(b-a); }

var COLS = 8, ROWS = 4;
var cells = [];
for (var r=0;r<ROWS;r++) for (var c=0;c<COLS;c++) cells.push({ r:r, c:c, v:R(10,90) });
var W=300, H=200;
var rects = [];

function measure(){
  var rc = svg.getBoundingClientRect();
  W = Math.max(rc.width,10); H = Math.max(rc.height,10);
  sa(svg,{ viewBox: '0 0 ' + W + ' ' + H });
}

function build(){
  svg.innerHTML = '';
  rects = [];
  var padL=8, padR=8, padT=8, padB=8;
  var pw=W-padL-padR, ph=H-padT-padB;
  var gap=2;
  var cw=(pw-gap*(COLS-1))/COLS, ch=(ph-gap*(ROWS-1))/ROWS;
  cells.forEach(function(cell){
    var x = padL+cell.c*(cw+gap), y = padT+cell.r*(ch+gap);
    var rect = se('rect');
    sa(rect,{x:x,y:y,width:cw,height:ch,rx:2,class:'cell'});
    svg.appendChild(rect);
    rects.push(rect);
  });
  paint();
}

function paint(){
  cells.forEach(function(cell,i){
    var pct = Math.max(6,Math.min(96, cell.v));
    rects[i].setAttribute('fill', 'color-mix(in srgb, var(--accent) ' + pct + '%, var(--surface))');
  });
}

new ResizeObserver(function(){ measure(); build(); }).observe(svg);
measure();
build();
setInterval(function(){
  cells.forEach(function(cell){ cell.v = R(8,92); });
  paint();
}, 2800);

Color must stay a single hue, varying only in lightness — darker means bigger. Never a rainbow gradient: once color cycles through multiple hues, the brain loses any sense of "which way is up," and boundaries appear where none exist in the data.

Color alone can't carry exact values (people can't reliably judge fine lightness differences in absolute terms), so a legend showing min-to-max is mandatory, and if the numbers actually matter, print them inside the cells too.

Row/column order shapes what pattern you see. Sorting so similar values cluster together can reveal structure that looked like noise before — and a careless default order can just as easily hide a real pattern that's there.

When to use

Use it when there are too many row/column combinations for a plain table to scan, and you need to spot overall patterns or outliers fast.