Lollipop chart

롤리팝 차트

A bar-chart variant that swaps the thick fill for a thin stem and a dot at the tip — less ink, while the exact endpoint stays just as precise.

Also known as: Stem 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 = ['A','B','C','D','E','F'];
var cur = CATS.map(function(){ return 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=22,padR=30,padT=10,padB=8;
  var pw=W-padL-padR, ph=H-padT-padB;
  var n=CATS.length;
  var rowH = ph/n;
  var fs = Math.max(8,Math.min(11,W/40));
  var axis = se('line');
  sa(axis,{ x1:padL, y1:padT, x2:padL, y2:padT+ph, stroke:'var(--line)', 'stroke-width':1 });
  svg.appendChild(axis);
  CATS.forEach(function(name,i){
    var y = padT+rowH*(i+0.5);
    var x = padL+(cur[i]/100)*pw;
    var lab = se('text');
    sa(lab,{ x:padL-6, y:y+3, 'text-anchor':'end', fill:'var(--muted)', 'font-size':fs, 'font-weight':600 });
    lab.textContent = name;
    svg.appendChild(lab);
    var stem = se('line');
    sa(stem,{ x1:padL, y1:y, x2:x, y2:y, stroke:'var(--accent)', 'stroke-width':2 });
    svg.appendChild(stem);
    var dot = se('circle');
    sa(dot,{ cx:x, cy:y, r:5, fill:'var(--accent)' });
    svg.appendChild(dot);
    var val = se('text');
    sa(val,{ x:x+8, y:y+3, fill:'var(--fg)', 'font-size':fs, 'font-weight':700 });
    val.textContent = Math.round(cur[i]);
    svg.appendChild(val);
  });
}

function animate(target){
  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(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(20,92); }));
setInterval(function(){ animate(CATS.map(function(){ return R(12,95); })); }, 3400);

It answers the same question a bar chart does — which item is bigger — with the same precision, but draws a thin stem instead of a thick rectangle. Think of it as Edward Tufte's data-ink ratio pushed to an extreme: the only information a bar actually needs is where its endpoint sits, and filling in the rest between baseline and endpoint spends ink without adding information.

With many categories (15+) or values close enough that bars would crowd together, thick bars tend to blur into one mass at their boundaries, while thin stems stay distinguishable even at tight spacing — which is why this shape works especially well for long ranked lists.

Draw the stem too thick and it quietly turns back into a skinny bar chart, losing the whole point of switching (less ink, staying legible when items are dense). The dot at the tip also needs to stay clearly bigger than the stem — shrink it too far and it stops working as a hover target or a visual anchor for the eye.

When to use

Use it when there are many categories (a ranked list) or values are close enough that bars would blur together. If a bar’s thickness itself carries brand or emphasis, use a regular bar chart instead.