막대 차트

Bar chart

범주별 값을 막대 길이로 비교하는 가장 기본적인 차트. 길이는 눈으로 가장 정확하게 비교할 수 있는 인코딩입니다.

다른 이름: Column chart바 차트
···
html
<div class="viz">
  <svg id="svg"></svg>
</div>
css
body{display:block}
.viz{position:relative;width:100%;height:100%}
svg{display:block;width:100%;height:100%;overflow:visible}
.axis{stroke:var(--line);stroke-width:1}
.tick{fill:var(--muted);font-weight:600}
.val{fill:var(--fg);font-weight:700}
.minor{display:none}
@media (min-width:460px){.minor{display:inline}}
.bar{fill:var(--accent)}
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 CATS = ['A','B','C','D','E','F'];
var cur = CATS.map(function(){ return 0; });
var W = 300, H = 200;

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(){
  svg.innerHTML = '';
  var padL=26, padR=10, padT=18, padB=22;
  var pw=W-padL-padR, ph=H-padT-padB;
  var maxV=100, n=CATS.length;
  var fs = Math.max(8, Math.min(13, W/70));
  var gap = pw*0.16/n;
  var bw = Math.min(34,(pw-gap*(n-1))/n);
  var totalW = bw*n+gap*(n-1);
  var startX = padL+(pw-totalW)/2;

  var base = se('line');
  sa(base,{x1:padL,y1:H-padB,x2:W-padR,y2:H-padB,class:'axis'});
  svg.appendChild(base);

  var gtop = se('line');
  sa(gtop,{x1:padL,y1:padT,x2:W-padR,y2:padT,class:'axis minor'});
  svg.appendChild(gtop);
  var gl = se('text');
  sa(gl,{x:padL-5,y:padT+3,'text-anchor':'end',class:'tick minor','font-size':fs-1});
  gl.textContent = maxV;
  svg.appendChild(gl);
  var zl = se('text');
  sa(zl,{x:padL-5,y:H-padB+3,'text-anchor':'end',class:'tick','font-size':fs-1});
  zl.textContent = '0';
  svg.appendChild(zl);

  CATS.forEach(function(c,i){
    var v = cur[i];
    var bh = (v/maxV)*ph;
    var x = startX+i*(bw+gap);
    var y = H-padB-bh;
    var rect = se('rect');
    sa(rect,{x:x,y:y,width:bw,height:Math.max(bh,0.5),rx:4,ry:4,class:'bar'});
    svg.appendChild(rect);
    var lab = se('text');
    sa(lab,{x:x+bw/2,y:Math.max(y-5,padT+fs),'text-anchor':'middle',class:'val','font-size':fs});
    lab.textContent = Math.round(v);
    svg.appendChild(lab);
    var cat = se('text');
    sa(cat,{x:x+bw/2,y:H-padB+14,'text-anchor':'middle',class:'tick','font-size':fs-1});
    cat.textContent = c;
    svg.appendChild(cat);
  });
}

function animate(target){
  var from = cur.slice();
  var t0 = performance.now();
  function step(t){
    var p = Math.min(1,(t-t0)/650);
    var e = 1-Math.pow(1-p,3);
    cur = from.map(function(v,i){ return v+(target[i]-v)*e; });
    draw();
    if (p<1) requestAnimationFrame(step);
  }
  requestAnimationFrame(step);
}

new ResizeObserver(function(){ measure(); draw(); }).observe(svg);
measure();
draw();
animate(CATS.map(function(){ return R(28,96); }));
setInterval(function(){
  animate(CATS.map(function(){ return R(15,98); }));
}, 3400);

"어느 항목이 더 큰가"에 답하는 차트입니다. 막대는 반드시 0부터 시작해야 길이 비교가 정직합니다 — 길이가 곧 값이기 때문에, 축을 중간에서 자르면(truncated y-axis) 작은 차이도 거대한 차이처럼 보입니다.

색은 굳이 범주마다 다르게 칠할 필요가 없습니다. 항목들이 같은 종류(같은 측정값의 다른 대상)라면 막대 전부를 같은 색 하나로 칠하는 게 맞습니다 — 색을 범주마다 바꾸는 건 이미 막대 길이가 보여주는 정보를 색으로 한 번 더 중복 인코딩하는 것일 뿐입니다. 굵기는 얇게, 막대 사이 간격은 막대 너비의 절반 이하로 둡니다.

세로(컬럼)냐 가로(바)냐는 라벨 길이로 정합니다. 범주 이름이 길면 가로 막대가 라벨이 안 잘립니다.

언제 쓰나

범주가 10개 미만이고 정확한 크기 비교가 목적일 때. 시간 흐름이 주인공이면 선 차트를 쓰세요.