코로플레스

Choropleth

지역 단위마다 값의 크기를 색 농도로 칠하는 지도 차트. 이 데모는 실제 지도 대신 추상 타일 격자로 원리만 보여줍니다.

다른 이름: Choropleth map단계구분도
···
html
<div class="viz">
  <svg id="svg"></svg>
  <div class="scale minor" id="scale"><span>낮음</span><i></i><span>높음</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:56px;height:7px;border-radius:4px;background:linear-gradient(90deg, color-mix(in srgb, var(--accent) 10%, var(--surface)), var(--accent))}
.hex{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=5;
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 polys = [];

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 hexPoints(cx,cy,r){
  var pts = [];
  for (var i=0;i<6;i++){ var a = Math.PI/180*(60*i); pts.push((cx+r*Math.cos(a)).toFixed(1)+','+(cy+r*Math.sin(a)).toFixed(1)); }
  return pts.join(' ');
}

function build(){
  svg.innerHTML = '';
  polys = [];
  var padL=10, padR=10, padT=8, padB=8;
  var pw=W-padL-padR, ph=H-padT-padB;
  var hrW = pw/(1.5*COLS+0.5);
  var hrH = ph/(Math.sqrt(3)*(ROWS+0.5));
  var hr = Math.max(4, Math.min(hrW,hrH));
  var gridW = hr*(1.5*COLS+0.5);
  var gridH = Math.sqrt(3)*hr*(ROWS+0.5);
  var offX = padL+(pw-gridW)/2+hr;
  var offY = padT+(ph-gridH)/2+hr*Math.sqrt(3)/2;
  cells.forEach(function(cell){
    var cx = offX+cell.c*1.5*hr;
    var cy = offY+cell.r*Math.sqrt(3)*hr + (cell.c%2 ? Math.sqrt(3)*hr/2 : 0);
    var poly = se('polygon');
    sa(poly,{ points: hexPoints(cx,cy,hr*0.92), class:'hex' });
    svg.appendChild(poly);
    polys.push({ el:poly, cell:cell });
  });
  paint();
}

function paint(){
  polys.forEach(function(p){
    var pct = Math.max(6, Math.min(96, p.cell.v));
    p.el.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();
}, 2900);

히트맵과 색 규칙은 같습니다(단일 색상, 농도만 변화). 다른 점은 격자가 아니라 지역 경계라는 것뿐입니다. 그래서 가장 흔한 함정도 지도 특유의 것입니다 — 면적이 넓은 지역(인구가 적어도 땅이 넓은 지역)이 시각적으로 "더 큰 값"처럼 보이는 착시입니다. 총량(인구 수 등)을 그대로 칠하면 면적이 큰 지역이 실제 순위와 상관없이 과장되어 보입니다.

그래서 코로플레스는 원칙적으로 비율·밀도(인구 대비 비율, 1인당 값)를 칠해야 합니다 — 절대량을 지역별로 비교하고 싶다면 버블 차트(지역 중심에 크기가 값에 비례하는 원)가 더 정직합니다.

값의 구간(bin)을 몇 개로 나누느냐에 따라 지도의 인상이 크게 바뀝니다 — 구간을 연속(그라디언트)으로 둘지, 5~7단계로 나눌지는 목적에 따라 고르고, 항상 범례로 구간 경계를 밝힙니다.

언제 쓰나

지역별 비율·밀도를 공간 배치와 함께 한눈에 훑고 싶을 때. 절대량 비교엔 버블 차트가 낫습니다.