영역 차트

Area chart

선 아래를 색으로 채운 차트. 선보다 "양감"과 누적된 크기를 더 강하게 암시합니다.

다른 이름: Area graph면적 차트
···
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}}
.fill{fill:var(--accent);fill-opacity:.14;stroke:none}
.ln{fill:none;stroke:var(--accent);stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round}
.dot{fill:var(--accent)}
.ring{fill:var(--surface)}
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 N = 10;
var cur = new Array(N).fill(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=14, padT=16, padB=20;
  var pw=W-padL-padR, ph=H-padT-padB;
  var fs = Math.max(8, Math.min(12, W/85));
  var base = padT+ph;
  var pts = cur.map(function(v,i){
    return { x: padL + (pw*i)/(N-1), y: padT + ph - (v/100)*ph };
  });

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

  var line = pts.map(function(p,i){ return (i===0?'M':'L') + p.x.toFixed(1) + ',' + p.y.toFixed(1); }).join(' ');
  var area = 'M' + pts[0].x.toFixed(1) + ',' + base + ' ' + line.slice(1) + ' L' + pts[pts.length-1].x.toFixed(1) + ',' + base + ' Z';

  var af = se('path');
  sa(af,{d:area,class:'fill'});
  svg.appendChild(af);
  var lp = se('path');
  sa(lp,{d:line,class:'ln'});
  svg.appendChild(lp);

  var last = pts[pts.length-1];
  var ring = se('circle');
  sa(ring,{cx:last.x,cy:last.y,r:5,class:'ring'});
  svg.appendChild(ring);
  var dot = se('circle');
  sa(dot,{cx:last.x,cy:last.y,r:3.2,class:'dot'});
  svg.appendChild(dot);
  var lab = se('text');
  sa(lab,{x:last.x-6,y:last.y-8,'text-anchor':'end',class:'val','font-size':fs});
  lab.textContent = Math.round(cur[cur.length-1]);
  svg.appendChild(lab);
}

function animate(target){
  var from = cur.slice();
  var t0 = performance.now();
  function step(t){
    var p = Math.min(1,(t-t0)/750);
    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);
}

function nextTarget(){
  var v = 40, out=[];
  for (var i=0;i<N;i++){ v = Math.max(8,Math.min(90, v+R(-20,24))); out.push(v); }
  return out;
}

new ResizeObserver(function(){ measure(); draw(); }).observe(svg);
measure();
draw();
animate(nextTarget());
setInterval(function(){ animate(nextTarget()); }, 3500);

구조는 선 차트와 같지만 아래를 채운다는 점이 의미를 바꿉니다. 채워진 면적은 "이만큼 쌓였다"는 인상을 주기 때문에, 선 차트라면 괜찮았던 "0부터 시작하지 않는 y축"이 영역 차트에서는 거짓말이 됩니다 — 면적 자체가 곧 값처럼 읽히므로 반드시 0에서 시작해야 합니다.

채우기는 진하게 칠하면 안 됩니다. 시리즈 색을 10% 안팎의 옅은 톤으로 깔고, 선 자체(2px)로 정확한 값의 경계를 그려줍니다. 면이 진하면 그 아래 그리드나 다른 레이어가 가려지고, 여러 영역을 겹칠 때 뒤쪽이 안 보입니다.

여러 시리즈를 쌓지 않고 겹쳐서 그릴 땐(overlapping area) 2개까지만 권장합니다 — 그 이상은 겹친 부분의 순서(어느 영역이 위인지)에 따라 값이 왜곡되어 보입니다. 누적이 목적이면 스택 영역 차트를 따로 씁니다.

언제 쓰나

누적된 양이나 한 값의 전체적인 흐름을 강조하고 싶을 때. 여러 값을 정밀 비교할 땐 선 차트가 낫습니다.