Waffle chart

와플 차트

Divides the whole into 100 small squares and fills as many as the percentage calls for, so a share of the whole can literally be counted, not just eyeballed.

Also known as: Square pie chartIsotype grid픽토그램 차트
···
html
<div class="viz">
  <svg id="svg"></svg>
  <div class="legend" id="lg"></div>
</div>
css
:root{--viz-4:#e8a23a}
body{display:block}
.viz{position:relative;width:100%;height:100%}
svg{display:block;width:100%;height:100%;overflow:visible}
.legend{position:absolute;bottom:2px;left:0;right:0;display:flex;gap:10px;justify-content:center;font:600 9px/1 system-ui,sans-serif;color:var(--muted)}
.legend b{display:inline-flex;align-items:center;gap:3px;font-weight:600}
.legend i{width:7px;height:7px;border-radius:2px;display:inline-block}
js
var svg = document.getElementById('svg');
var lg = document.getElementById('lg');
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 CATS = [
  { name:'완료', color:'var(--accent)' },
  { name:'진행중', color:'var(--viz-4)' },
  { name:'대기', color:'var(--line)' }
];
var cur = [62,25,13];
var thresh = [62,87,100];
var W=300, H=200;

function computeThresh(){
  var c0=Math.round(cur[0]), c1=Math.round(cur[1]);
  var c2=Math.max(0,100-c0-c1);
  thresh = [c0, c0+c1, 100];
}
function updateLegend(){
  var c0=thresh[0], c1=thresh[1]-thresh[0], c2=100-thresh[1];
  var pcts=[c0,c1,c2];
  lg.innerHTML = CATS.map(function(c,i){ return '<b><i style="background:'+c.color+'"></i>'+c.name+' '+pcts[i]+'%</b>'; }).join('');
}

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

function draw(progress){
  svg.innerHTML = '';
  var pad=10, legendH=14;
  var pw=W-pad*2, ph=H-pad*2-legendH;
  var gap=Math.max(1,Math.min(3,W/140));
  var size=Math.min((pw-gap*9)/10,(ph-gap*9)/10);
  var gx=pad+(pw-(size*10+gap*9))/2, gy=pad+(ph-(size*10+gap*9))/2;
  for (var idx=0; idx<100; idx++){
    var row=Math.floor(idx/10), col=idx%10;
    var cat = idx<thresh[0] ? 0 : (idx<thresh[1] ? 1 : 2);
    var appear = Math.max(0,Math.min(1,(progress*115-idx)/15));
    if (appear<=0) continue;
    var rect = se('rect');
    sa(rect,{ x:gx+col*(size+gap), y:gy+row*(size+gap), width:size, height:size, rx:1.5, fill:CATS[cat].color, 'fill-opacity':appear });
    svg.appendChild(rect);
  }
}

function animateReveal(){
  var t0 = performance.now();
  function step(t){
    var p = Math.min(1,(t-t0)/1100);
    draw(p);
    if (p<1) requestAnimationFrame(step);
  }
  requestAnimationFrame(step);
}

function nextTarget(){
  var a=R(35,70), b=R(10,Math.min(40,95-a));
  return [Math.round(a),Math.round(b)];
}

new ResizeObserver(function(){ measure(); draw(1); }).observe(svg);
measure();
computeThresh(); updateLegend();
animateReveal();
setInterval(function(){
  var t = nextTarget();
  cur = [t[0],t[1]];
  computeThresh(); updateLegend();
  animateReveal();
}, 3600);

It answers the same question a pie or donut does — what share of the whole is this — with a different encoding. A pie shows proportion as a continuous quantity (angle), so comparing two slices means judging by eye; a waffle uses a 100-square grid, so "62%" literally means 62 filled squares, countable if you want to check. It swaps an imprecise perceptual judgment (angle, area) for a much easier one: counting.

That's exactly why the grid has to be precisely 10×10, 100 squares. Use 90 or 120 instead and one square stops meaning 1%, which erases the chart's whole advantage — you're back to eyeballing it.

When several categories fill the grid in sequence (done / in progress / pending), the fill order needs to stay fixed every time — say, left to right, top row down. Change the order each time and the sense of "up through which square" stops meaning anything consistent across different datasets. Past five categories, individual squares' colors get hard to tell apart, inheriting the same ceiling a pie chart runs into.

When to use

Use it to show a percentage of the whole as an intuitive count instead of an angle or area judgment. For precise comparison across several values, a bar chart works better.