v1.2.0
  1. ds
  2. Data display
  3. Bullet chart

Bullet chart

Actual against target, with bands for good and bad.

Stephen Few's replacement for the gauge: one measure as a bar, its target as a tick, and two or three grey bands for poor, fair and good behind it. It fits in a row, so ten of them stack into a scorecard.

  • Availability30 days, target 99.9% 99.95%target 99.9
  • p95 latencylower is better 355 mstarget 250
  • Pipeline freshnessminutes behind source 9 mintarget 15
  • Test coveragemain 78%target 80
Show code
<ul class="bullets">
  <li>
    <span class="bl-l">Availability<small>30 days, target 99.9%</small></span>
    <span class="bullet" style="--v:.86;--t:.75;--b1:.5;--b2:.75" role="img" aria-label="99.95% against a 99.9% target"><i></i><b></b></span>
    <span class="bl-v">99.95%<small>target 99.9</small></span>
  </li>
  <li>
    <span class="bl-l">p95 latency<small>lower is better</small></span>
    <span class="bullet inv down" style="--v:.71;--t:.5;--b1:.5;--b2:.8" role="img" aria-label="355 ms against a 250 ms target"><i></i><b></b></span>
    <span class="bl-v">355 ms<small>target 250</small></span>
  </li>
  <li>
    <span class="bl-l">Pipeline freshness<small>minutes behind source</small></span>
    <span class="bullet" style="--v:.18;--t:.3;--b1:.3;--b2:.6" role="img" aria-label="9 minutes against a 15 minute target"><i></i><b></b></span>
    <span class="bl-v">9 min<small>target 15</small></span>
  </li>
  <li>
    <span class="bl-l">Test coverage<small>main</small></span>
    <span class="bullet" style="--v:.78;--t:.8;--b1:.6;--b2:.8" role="img" aria-label="78% against an 80% target"><i></i><b></b></span>
    <span class="bl-v">78%<small>target 80</small></span>
  </li>
</ul>

The bar is ink and the target is the accent, so the eye goes to the gap between them. The bands are ink at 13%, 8% and 4%: they set context without competing. A measure that misses its target in the bad direction gets .down and turns red. For a "lower is better" metric add .inv: the darkest band moves to the right, as in the latency row, where everything past 0.8 is poor.

ClassOnWhat it does
.bulletsulA scorecard: label, bullet, value in three columns.
.bulletspanThe bands and the bar. --v the measure, --t the target, --b1 and --b2 where the bands change, all 0 to 1. .down for a miss, .inv when lower is better.
.bullet > i / bi, bThe measure bar and the target tick. --c recolours the bar, --c-t the tick.
.bl-l / .bl-vspanLabel with a small second line; value with the target under it.
Do
  • Say the direction in the label when lower is better.
  • Same scale for rows that measure the same thing.
Don't
  • Colour in the bands. Grey is the point.
  • A bullet without the target value in text.

Accessibility

  • Each bullet is role="img" with "measure against target" as its label; the value column repeats both as text.