폴라 에어리어 차트

Polar area chart

파이 차트처럼 원형이지만 각 조각의 각도는 똑같이 나누고, 대신 반지름의 길이로 값을 인코딩하는 차트.

다른 이름: Nightingale rose 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}
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 = ['1월','2월','3월','4월','5월','6월','7월','8월'];
var n = CATS.length;
var cur = CATS.map(function(){ return 30; });
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 cx=W/2, cy=H/2;
  var rMax = Math.max(24, Math.min(W,H)/2-26);
  [0.33,0.66,1].forEach(function(f){
    var c = se('circle');
    sa(c,{ cx:cx, cy:cy, r:rMax*f, fill:'none', stroke:'var(--line)', 'stroke-width':1 });
    svg.appendChild(c);
  });
  var ga = (Math.PI*2)/n;
  var maxV = 100;
  var fs = Math.max(7,Math.min(9,W/60));
  CATS.forEach(function(name,i){
    var a0 = -Math.PI/2+i*ga, a1 = a0+ga;
    var r = rMax*Math.sqrt(cur[i]/maxV);
    var s0 = a0+ga*0.06, s1 = a1-ga*0.06;
    var x0 = cx+Math.cos(s0)*r, y0 = cy+Math.sin(s0)*r;
    var x1 = cx+Math.cos(s1)*r, y1 = cy+Math.sin(s1)*r;
    var large = (s1-s0)>Math.PI?1:0;
    var d = 'M'+cx.toFixed(1)+','+cy.toFixed(1)+' L'+x0.toFixed(1)+','+y0.toFixed(1)+' A'+r.toFixed(1)+','+r.toFixed(1)+' 0 '+large+' 1 '+x1.toFixed(1)+','+y1.toFixed(1)+' Z';
    var p = se('path');
    sa(p,{ d:d, fill:'var(--accent)', 'fill-opacity': 0.35+0.55*(cur[i]/maxV) });
    svg.appendChild(p);
    var lx = cx+Math.cos((a0+a1)/2)*(rMax+9), ly = cy+Math.sin((a0+a1)/2)*(rMax+9);
    var t = se('text');
    sa(t,{ x:lx, y:ly+3, 'text-anchor':'middle', fill:'var(--muted)', 'font-size':fs, 'font-weight':600 });
    t.textContent = name;
    svg.appendChild(t);
  });
}

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);
}

new ResizeObserver(function(){ measure(); draw(); }).observe(svg);
measure();
draw();
animate(CATS.map(function(){ return R(15,95); }));
setInterval(function(){ animate(CATS.map(function(){ return R(10,98); })); }, 3600);

플로렌스 나이팅게일이 크림전쟁 사망 원인을 월별로 보여주기 위해 고안한 "로즈 다이어그램"이 원형입니다. 파이 차트는 값이 각도가 되지만, 폴라 에어리어는 모든 조각이 똑같은 각도(예: 12개면 각각 30도)를 차지하고 그 대신 반지름이 값에 따라 늘어납니다 — 그래서 월·요일·방위처럼 "자리 자체가 이미 고정된 범주"를 다룰 때, 파이처럼 조각 너비가 들쭉날쭉해지지 않고 나열 순서가 그대로 유지됩니다.

반지름을 값에 비례시키는 것이 가장 흔하고 가장 치명적인 실수입니다. 사람 눈은 조각을 면적으로 지각하는데, 면적은 반지름의 제곱에 비례해서 커집니다 — 그래서 반지름을 값에 선형으로 비례시키면 실제보다 큰 값이 훨씬 더 커 보이는 착시가 생깁니다(버블 차트의 반지름 함정과 정확히 같은 문제). 올바른 공식은 반지름 = k × sqrt(값)이며, 실제로 널리 쓰이는 차트 라이브러리 중에도 이 계산을 선형으로 잘못 구현해 배포한 사례가 있었을 만큼 자주 틀리는 지점입니다.

조각이 12개를 넘어가면 반지름이 촘촘히 겹쳐 인접한 두 조각의 차이를 가늠하기 어려워지므로, 파이 차트와 마찬가지로 조각 수를 적게 유지해야 합니다.

언제 쓰나

월·요일·방위처럼 이미 정해진 순환 순서가 있는 범주의 값을 비교하고 싶을 때. 순서가 없는 범주라면 파이 차트나 막대 차트가 더 자연스럽습니다.