v1.2.0
  1. ds
  2. Data display
  3. Bar charts

Bar charts

Columns, horizontal bars, grouped and stacked.

Bars compare amounts. Columns run along time or ordered categories; horizontal bars take long category names and ranked lists. Both are plain HTML sized by --v, so they animate when the value changes and need no library.

Columns

Queries per hour

Warehouse, today. Peak at 15:00.

Show code
<figure class="chart boxed">
  <figcaption class="ch-head">
    <div><p class="ch-title">Queries per hour</p><p class="ch-sub">Warehouse, today. Peak at 15:00.</p></div>
  </figcaption>
  <div class="plot" role="img" aria-label="Queries per hour, peak at 15:00 with 84 thousand">
  <div class="ch-grid"><i style="--at:0" data-v="0k"></i><i style="--at:0.5" data-v="50k"></i><i style="--at:1" data-v="100k"></i></div>
  <div class="ch-cols" style="--gap:0.25"><i class="s1" style="--v:0.12"></i><i class="s1" style="--v:0.08"></i><i class="s1" style="--v:0.06"></i><i class="s1" style="--v:0.05"></i><i class="s1" style="--v:0.05"></i><i class="s1" style="--v:0.07"></i><i class="s1" style="--v:0.14"></i><i class="s1" style="--v:0.32"></i><i class="s1" style="--v:0.58"></i><i class="s1" style="--v:0.71"></i><i class="s1" style="--v:0.76"></i><i class="s1" style="--v:0.74"></i><i class="s1" style="--v:0.69"></i><i class="s1" style="--v:0.72"></i><i class="s1" style="--v:0.8"></i><i class="s1 hl" style="--v:0.84"></i><i class="s1" style="--v:0.77"></i><i class="s1" style="--v:0.63"></i><i class="s1" style="--v:0.44"></i><i class="s1" style="--v:0.33"></i><i class="s1" style="--v:0.27"></i><i class="s1" style="--v:0.22"></i><i class="s1" style="--v:0.18"></i><i class="s1" style="--v:0.15"></i></div>
  <div class="ch-x"><span style="--at:0">00</span><span style="--at:0.2609">06</span><span style="--at:0.5217">12</span><span style="--at:0.7826">18</span><span style="--at:1">23</span></div>
</div>
</figure>

Columns sit in the same plot as lines: gridlines, gutter labels and x labels. .hl points at one bar by dimming the rest, which is also what hover does.

Stacked

Builds per day

main and release branches, last 14 days

  • passed
  • failed
Show code
<figure class="chart boxed">
  <figcaption class="ch-head">
    <div><p class="ch-title">Builds per day</p><p class="ch-sub">main and release branches, last 14 days</p></div>
    <ul class="ch-legend"><li class="s1">passed</li><li class="c-down">failed</li></ul>
  </figcaption>
  <div class="plot" role="img" aria-label="CI builds per day for two weeks, failures stacked on top. Failures peaked on day 9.">
  <div class="ch-grid"><i style="--at:0" data-v="0"></i><i style="--at:0.3333" data-v="50"></i><i style="--at:0.6667" data-v="100"></i><i style="--at:1" data-v="150"></i></div>
  <div class="ch-cols"><span class="stk" style="--v:0.7067;--top:0.7067"><i class="s1" style="--v:0.64"></i><i class="c-down" style="--v:0.0667"></i></span><span class="stk" style="--v:0.6533;--top:0.6533"><i class="s1" style="--v:0.5933"></i><i class="c-down" style="--v:0.06"></i></span><span class="stk" style="--v:0.7067;--top:0.7067"><i class="s1" style="--v:0.64"></i><i class="c-down" style="--v:0.0667"></i></span><span class="stk" style="--v:0.7067;--top:0.7067"><i class="s1" style="--v:0.6467"></i><i class="c-down" style="--v:0.06"></i></span><span class="stk" style="--v:0.54;--top:0.54"><i class="s1" style="--v:0.48"></i><i class="c-down" style="--v:0.06"></i></span><span class="stk" style="--v:0.1867;--top:0.1867"><i class="s1" style="--v:0.1733"></i><i class="c-down" style="--v:0.0133"></i></span><span class="stk" style="--v:0.16;--top:0.16"><i class="s1" style="--v:0.14"></i><i class="c-down" style="--v:0.02"></i></span><span class="stk" style="--v:0.58;--top:0.58"><i class="s1" style="--v:0.5467"></i><i class="c-down" style="--v:0.0333"></i></span><span class="stk" style="--v:0.5867;--top:0.5867"><i class="s1" style="--v:0.52"></i><i class="c-down" style="--v:0.0667"></i></span><span class="stk" style="--v:0.6733;--top:0.6733"><i class="s1" style="--v:0.6267"></i><i class="c-down" style="--v:0.0467"></i></span><span class="stk" style="--v:0.5733;--top:0.5733"><i class="s1" style="--v:0.52"></i><i class="c-down" style="--v:0.0533"></i></span><span class="stk" style="--v:0.5533;--top:0.5533"><i class="s1" style="--v:0.5067"></i><i class="c-down" style="--v:0.0467"></i></span><span class="stk" style="--v:0.1467;--top:0.1467"><i class="s1" style="--v:0.1333"></i><i class="c-down" style="--v:0.0133"></i></span><span class="stk" style="--v:0.1467;--top:0.1467"><i class="s1" style="--v:0.14"></i><i class="c-down" style="--v:0.0067"></i></span></div>
  <div class="ch-x"><span style="--at:0">Mon 1</span><span style="--at:0.2308">Thu 4</span><span style="--at:0.5385">Mon 8</span><span style="--at:0.7692">Thu 11</span><span style="--at:1">Sun 14</span></div>
</div>
</figure>

A .stk column holds its segments bottom up, separated by a 1px line in the surface colour. Failures are red because they are a state; the rest is the first series colour.

Grouped

Lake storage

End of month, terabytes

  • raw
  • curated
Show code
<figure class="chart boxed">
  <figcaption class="ch-head">
    <div><p class="ch-title">Lake storage</p><p class="ch-sub">End of month, terabytes</p></div>
    <ul class="ch-legend"><li class="s1">raw</li><li class="s2">curated</li></ul>
  </figcaption>
  <div class="plot" role="img" aria-label="Lake storage by month: raw grew from 41 to 71 TB, curated from 18 to 31 TB.">
  <div class="ch-grid"><i style="--at:0" data-v="0 TB"></i><i style="--at:0.25" data-v="20 TB"></i><i style="--at:0.5" data-v="40 TB"></i><i style="--at:0.75" data-v="60 TB"></i><i style="--at:1" data-v="80 TB"></i></div>
  <div class="ch-cols" style="--gap:0.45"><span class="grp" style="--v:0.5125;--top:0.5125"><i class="s1" style="--v:0.5125"></i><i class="s2" style="--v:0.225"></i></span><span class="grp" style="--v:0.575;--top:0.575"><i class="s1" style="--v:0.575"></i><i class="s2" style="--v:0.2625"></i></span><span class="grp" style="--v:0.65;--top:0.65"><i class="s1" style="--v:0.65"></i><i class="s2" style="--v:0.275"></i></span><span class="grp" style="--v:0.6875;--top:0.6875"><i class="s1" style="--v:0.6875"></i><i class="s2" style="--v:0.325"></i></span><span class="grp" style="--v:0.7875;--top:0.7875"><i class="s1" style="--v:0.7875"></i><i class="s2" style="--v:0.3375"></i></span><span class="grp" style="--v:0.8875;--top:0.8875"><i class="s1" style="--v:0.8875"></i><i class="s2" style="--v:0.3875"></i></span></div>
  <div class="ch-x"><span class="mid" style="--at:0.0833">Apr</span><span class="mid" style="--at:0.25">May</span><span class="mid" style="--at:0.4167">Jun</span><span class="mid" style="--at:0.5833">Jul</span><span class="mid" style="--at:0.75">Aug</span><span class="mid" style="--at:0.9167">Sep</span></div>
</div>
</figure>

Two or three series per group, 1px apart. Centre the x labels under each group with .mid.

Horizontal

  1. orders_daily41.2 TB
  2. events_raw28.7 TB
  3. customer_36017.9 TB
  4. ledger_snapshots9.4 TB
  5. ml_features_v36.1 TB
  6. tmp_backfill_09182.3 TB
Show code
<ol class="hbars">
  <li><span class="hb-l"><code>orders_daily</code></span><span class="hb-track"><i class="s1" style="--v:1.000"></i></span><span class="hb-v">41.2 TB</span></li>
  <li><span class="hb-l"><code>events_raw</code></span><span class="hb-track"><i class="s1" style="--v:0.697"></i></span><span class="hb-v">28.7 TB</span></li>
  <li><span class="hb-l"><code>customer_360</code></span><span class="hb-track"><i class="s1" style="--v:0.434"></i></span><span class="hb-v">17.9 TB</span></li>
  <li><span class="hb-l"><code>ledger_snapshots</code></span><span class="hb-track"><i class="s1" style="--v:0.228"></i></span><span class="hb-v">9.4 TB</span></li>
  <li><span class="hb-l"><code>ml_features_v3</code></span><span class="hb-track"><i class="s1" style="--v:0.148"></i></span><span class="hb-v">6.1 TB</span></li>
  <li><span class="hb-l"><code>tmp_backfill_0918</code></span><span class="hb-track"><i class="s6" style="--v:0.056"></i></span><span class="hb-v">2.3 TB</span></li>
</ol>

Label, track and value in three grid columns, so every bar starts at the same x however long the name. Order by value. A temporary table in the neutral tells you it is not part of the set you care about.

  • Compute$8,420 76%
  • Storage$1,440 13%
  • Network$1,210 11%
  • prod
  • staging
  • dev
Show code
<ul class="hbars thin">
  <li><span class="hb-l">Compute</span><span class="hb-track"><i class="s1" style="--v:.46"></i><i class="s2" style="--v:.21"></i><i class="s3" style="--v:.09"></i></span><span class="hb-v">$8,420 <small>76%</small></span></li>
  <li><span class="hb-l">Storage</span><span class="hb-track"><i class="s1" style="--v:.08"></i><i class="s2" style="--v:.05"></i></span><span class="hb-v">$1,440 <small>13%</small></span></li>
  <li><span class="hb-l">Network</span><span class="hb-track"><i class="s1" style="--v:.07"></i><i class="s3" style="--v:.04"></i></span><span class="hb-v">$1,210 <small>11%</small></span></li>
</ul>
<ul class="ch-legend"><li class="s1">prod</li><li class="s2">staging</li><li class="s3">dev</li></ul>

Several <i> in one track stack left to right. .thin makes a 6px rounded track for dense lists.

ClassOnWhat it does
.ch-colsdivColumns inside a .plot. --gap is the share of each column left empty, from 0 to 1 (0.3). .values prints data-v above each.
.ch-cols > iiA bar of height --v (0 to 1 of the axis maximum). Colour from a series or status class. .hl dims the others to point at it.
.grp / .stkspanA group of bars side by side, or segments stacked bottom up. Each child <i> has its own --v.
.hbarsol, ulHorizontal bars. Each li holds .hb-l, .hb-track, .hb-v. Modifiers: thin, bare.
.hb-track > iiA segment of width --v. Several stack left to right.
colPlot()src/lib/viz.tsBuilds a column plot from numbers or arrays, with round ticks.
Do
  • Start bars at zero. Always.
  • Sort horizontal bars by value unless the order means something.
  • Put the value at the end of a horizontal bar.
Don't
  • 3D, gradients or rounded tops taller than 2px.
  • More than three series in a group.
  • Stacks with more than four segments: only the bottom one is readable.

Accessibility

  • A column chart is one role="img" with the takeaway as its label. Horizontal bars keep their values as text, so they read as a list.
  • Failures are red and also stacked on top, so position tells them apart without colour.