Gauge chart

게이지 차트

Shows a single value as a needle or filled arc on a semicircular scale — a speedometer for "is this in the safe range."

Also known as: Speedometer 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 cur = 50;
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 arcPoint(cx,cy,r,deg){ var rad=deg*Math.PI/180; return { x:cx+r*Math.cos(rad), y:cy-r*Math.sin(rad) }; }
function arcPath(cx,cy,r,d0,d1){
  var p0=arcPoint(cx,cy,r,d0), p1=arcPoint(cx,cy,r,d1);
  var large = Math.abs(d0-d1) > 180 ? 1 : 0;
  return 'M'+p0.x+','+p0.y+' A'+r+','+r+' 0 '+large+' 1 '+p1.x+','+p1.y;
}

function draw(){
  svg.innerHTML = '';
  var cy = H*0.64;
  var r = Math.max(20, Math.min(W*0.4, cy-12));
  var cx = W/2;
  var sw = Math.max(6, r*0.22);
  var fs = Math.max(14, Math.min(30, r*0.42));

  var track = se('path');
  sa(track,{ d:arcPath(cx,cy,r,180,0), fill:'none', stroke:'var(--line)', 'stroke-width':sw, 'stroke-linecap':'round' });
  svg.appendChild(track);

  var valDeg = 180-(cur/100)*180;
  var val = se('path');
  sa(val,{ d:arcPath(cx,cy,r,180,valDeg), fill:'none', stroke:'var(--accent)', 'stroke-width':sw, 'stroke-linecap':'round' });
  svg.appendChild(val);

  var tip = arcPoint(cx,cy,r*0.72,valDeg);
  var needle = se('line');
  sa(needle,{ x1:cx, y1:cy, x2:tip.x, y2:tip.y, stroke:'var(--fg)', 'stroke-width':2.4, 'stroke-linecap':'round' });
  svg.appendChild(needle);
  var hub = se('circle');
  sa(hub,{ cx:cx, cy:cy, r:3.4, fill:'var(--fg)' });
  svg.appendChild(hub);

  var num = se('text');
  sa(num,{ x:cx, y:cy+fs*1.05, 'text-anchor':'middle', fill:'var(--fg)', 'font-size':fs, 'font-weight':800 });
  num.textContent = Math.round(cur);
  svg.appendChild(num);
  var lbl = se('text');
  sa(lbl,{ x:cx, y:cy+fs*1.05+fs*0.5, 'text-anchor':'middle', fill:'var(--muted)', 'font-size':fs*0.34, 'font-weight':700 });
  lbl.textContent = '서버 사용률 %';
  svg.appendChild(lbl);
}

function animate(target){
  var from = cur;
  var t0 = performance.now();
  function step(t){
    var p = Math.min(1,(t-t0)/900);
    var e = 1-Math.pow(1-p,3);
    cur = from+(target-from)*e;
    draw();
    if (p<1) requestAnimationFrame(step);
  }
  requestAnimationFrame(step);
}

new ResizeObserver(function(){ measure(); draw(); }).observe(svg);
measure();
draw();
animate(R(30,88));
setInterval(function(){ animate(R(15,95)); }, 3200);

A gauge earns its space in exactly one situation: one value has a clear target or safe range, and "inside or outside that range" is the actual question — a speed limit, server utilization, a satisfaction score. Otherwise, a number plus a trend (sparkline) usually packs far more information into the same footprint.

The same space spent on a bar or a stat tile carries the exact value, its trend, and a comparison; a gauge shows only "where it is right now." Lining up several KPIs as gauges across one dashboard is almost always overkill — save the form for the one spot that genuinely needs a dashboard-style "danger or not" glance.

If the arc is banded by zone (safe/caution/danger), label the threshold values explicitly. Don't make people guess the exact cutoff from color alone.

When to use

Use it only for a single value with a clear target or safe range, where "in range or not" is the whole point.