스파크라인

Sparkline

축도 눈금도 없이 텍스트 옆에 놓는 아주 작은 추세선. 숫자 하나에 "요즘 흐름"이라는 맥락을 덧붙입니다.

다른 이름: Inline trend line
···
html
<div class="viz">
  <div class="tile">
    <div class="lbl">주간 방문자</div>
    <div class="row">
      <div class="num" id="num">1,284</div>
      <div class="delta" id="delta">▲ 4.2%</div>
    </div>
  </div>
  <svg id="svg"></svg>
</div>
css
body{display:block}
.viz{display:grid;grid-template-rows:auto 1fr;width:100%;height:100%;padding:10px 12px;box-sizing:border-box;gap:2px}
.lbl{font:600 10px/1 system-ui,sans-serif;color:var(--muted)}
.row{display:flex;align-items:baseline;gap:8px}
.num{font:700 20px/1 system-ui,sans-serif;color:var(--fg)}
.delta{font:700 11px/1 system-ui,sans-serif}
.delta.up{color:var(--accent-3)}
.delta.down{color:var(--accent-2)}
svg{display:block;width:100%;height:100%;overflow:visible}
.ln{fill:none;stroke:var(--muted);stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;opacity:.55}
.dot{fill:var(--accent)}
js
var svg = document.getElementById('svg');
var numEl = document.getElementById('num');
var deltaEl = document.getElementById('delta');
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 = 14;
var series = [];
var v = 50;
for (var i=0;i<N;i++){ v = Math.max(15,Math.min(90, v+R(-14,14))); series.push(v); }
var cur = series.slice();
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 padX=4, padY=8;
  var pw=W-padX*2, ph=H-padY*2;
  var pts = cur.map(function(val,i){ return { x: padX+(pw*i)/(N-1), y: padY+ph-(val/100)*ph }; });
  var d = pts.map(function(p,i){ return (i===0?'M':'L')+p.x.toFixed(1)+','+p.y.toFixed(1); }).join(' ');
  var path = se('path');
  sa(path,{d:d,class:'ln'});
  svg.appendChild(path);
  var last = pts[pts.length-1];
  var dot = se('circle');
  sa(dot,{cx:last.x,cy:last.y,r:3,class:'dot'});
  svg.appendChild(dot);
}

function updateFacts(){
  var val = cur[cur.length-1];
  var prev = cur[cur.length-2];
  numEl.textContent = Math.round(val*14).toLocaleString();
  var pct = ((val-prev)/prev*100);
  var up = pct >= 0;
  deltaEl.textContent = (up?'▲ ':'▼ ') + Math.abs(pct).toFixed(1) + '%';
  deltaEl.className = 'delta ' + (up?'up':'down');
}

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

new ResizeObserver(function(){ measure(); draw(); }).observe(svg);
measure();
draw();
updateFacts();
setInterval(function(){
  series.shift();
  var last = series[series.length-1];
  series.push(Math.max(12,Math.min(92, last+R(-16,16))));
  cur = [series[0]].concat(cur.slice(1));
  animate();
  updateFacts();
}, 2600);

에드워드 터프티가 제안한 개념으로, "단어처럼 읽히는 그래픽(word-sized graphic)"입니다. 스파크라인은 정확한 값을 읽으라고 만든 게 아니라 상승/하락/변동성의 대략적인 인상만 전달하면 됩니다 — 그래서 축·눈금·범례가 없는 게 정상입니다.

혼자 있으면 아무 의미가 없습니다. 반드시 라벨(무엇의 추세인지)과 현재 값, 되도록 증감률까지 옆에 붙여야 스파크라인이 가리키는 숫자의 맥락이 완성됩니다. 마지막 점만 강조색으로 찍어 "지금 여기"를 표시합니다.

툴팁을 달거나 확대하려 하지 마세요 — 그 순간 스파크라인이 아니라 작은 선 차트가 됩니다. 자세히 봐야 하는 값이라면 처음부터 선 차트를 쓰는 게 맞습니다.

언제 쓰나

표의 한 행, 카드의 숫자 옆처럼 좁은 자리에서 "최근 흐름"만 곁들이고 싶을 때.