04 · Data display

Tables, figures and the containers around them

The thinnest part of journeymro_site — the marketing pages never needed a real data grid, and the report screens each grew their own. This page is where that gets settled: one table idiom, one stat idiom, one chart container, all in the frame's own vocabulary.

01

Principles

The grid is the borders
No zebra striping, no vertical rules, no box around the table. A bright hairline under the head, a faint one under each row, and that is the whole structure. Anything more competes with the frame the table sits inside.
Numbers are tabular
Tables switch on tnum, lnum, ss01 automatically. Numeric columns are right-aligned so the eye can compare down them; text columns are left-aligned. Never centre either.
Colour means status
A red figure is a shortfall, an amber one needs watching, a cyan one is the series being discussed. Colour is never applied to distinguish one row from its neighbour.
The container is transparent
A chart panel is a 1px hairline with no fill, so the page grid shows through. That is what makes a figure read as part of the drawing rather than as a screenshot pasted onto it.
Reserve the space
Give every plot an aspect-ratio box before the data arrives. A chart that sizes itself after a library measures it reflows the page under the reader.
02

Tables

Always wrap a table in .table-wrap. It is the only thing standing between a wide table and a horizontally scrolling page, and on a phone it is what lets the table keep its shape instead of collapsing into cards.

Standard table · .table-wrap > table
PartDescriptionTier On handROP ValueStatus
4400-118-22Bearing, roller, 220mmA1,184860$412,900Healthy
4400-206-08Seal, mechanical, 80mmA42180$38,400Below ROP
4400-311-14Filter element, hydraulicB960640$74,100Healthy
4400-455-91Belt, drive, 4-ribC1824$2,140Low
4400-502-33Coupling, flexibleB012$0Stockout
5 of 14,203 items2,2041,716$527,540
Showing 1–5 of 14,203
Dense table with sortable heads · .table-dense .table-sticky
POVendorLinePromisedActualDrift
PO-88412Northline Bearings00114d19d+5
PO-88412Northline Bearings00214d14d0
PO-88510Corveq Seals00128d51d+23
PO-88533Arlon Filtration0017d6d−1
PO-88533Arlon Filtration0027d9d+2
PO-88604Corveq Seals00128d44d+16
PO-88711Westgate Hose00121d22d+1
PO-88742Northline Bearings00114d31d+17
A sticky head needs an opaque background. th carries background: var(--bg) for exactly this reason — a transparent sticky head lets the rows scroll through it.
03

In-cell graphics

A magnitude read without leaving the row. Keep it to one graphic column per table — two competing bars is a chart pretending to be a table.

.cell-bar · .lt-bar · .spark
VendorShare of spendLead-time drift12-month trendLines
Northline Bearings412
Corveq Seals288
Arlon Filtration190
Westgate Hose96
Delta Fasteners54
Within promise Late Promised
04

Stats & meters

The lattice comes from borders on the cells, not from a wrapper, so .stat-row reflows from five across to two to one without the rules ever going wrong. auto-fit and minmax(160px, 1fr) do the whole job — resize this window.

.stat-row
14,203
Active items
▲ 412 vs. Q1
$41.2m
Stock value
▼ $2.1m
2.8×
Cover, mean
312
Below reorder point
▼ 44
96.4%
Service level
.score-strip — a number with its own track
78
Master data quality
41
Lead-time reliability
88
Transaction coverage
63
Criticality assigned
.meter — a ranked list with magnitude
Rotables$8.4m
Bearings$6.1m
Seals$4.7m
Filters$3.8m
Belts$2.9m
05

Chart containers

The container is the design; the plot inside it is left alone. Every figure below is hand-drawn SVG using only the classes in journeymro.css — no charting library — which is the point: a library configured to these same values will land in the same place.

Fig. 01 — On-hand units against reorder point · 4400-118-2252 weeks
Reorder point 70011001500W01W13W26W39W52
On hand Forecast Reorder point
Source · issues ledger

The forecast continues as a dashed ghost in --series-ghost, never as a second solid line. A reader should be able to tell measured from modelled without reading the legend.

Fig. 02 — Costed variance by categoryAUD m
306090RotablesBearingsSealsFiltersBeltsFastenersHoses84614738291812

Bars above the intervention threshold take --danger. Everything else is the accent — not a per-category colour.

Fig. 03 — Promised against actual lead timeBy PO line
Parity 2550751000204060 Promised · days

The parity line is the ghost stroke. Points more than 40% late take --danger; the colour carries the finding, so no annotation is needed.

Anatomy
<div class="viz-panel">
  <div class="viz-title">
    <span>Fig. 01 — On-hand units against reorder point</span>
    <span class="tag">52 weeks</span>
  </div>

  <!-- ratio-wide (16:9) · ratio-std (4:3) · ratio-sq (1:1)
       reserves the height so the page cannot reflow under the reader.
       Draw the viewBox at the SAME ratio — 800×450 here — or the plot is
       stretched to fit and the stroke weights distort with it. -->
  <div class="viz ratio-wide">
    <svg viewBox="0 0 800 450">
      <g class="grid"><line …/></g>         <!-- horizontal only -->
      <path class="fill-1" d="…"/>
      <path class="series-1" d="…"/>
      <path class="ghost" d="…"/>            <!-- forecast, always dashed -->
      <g class="axis"><line …/></g>
      <text class="tick" …>700</text>
    </svg>
  </div>

  <div class="viz-foot">
    <div class="chart-legend"><span><span class="key"></span>On hand</span></div>
    <span class="mono mono-sm">Source · issues ledger</span>
  </div>
  <p class="viz-note">One sentence saying what to notice.</p>
</div>
Rules
Do

Leave the panel's background transparent so the page grid shows through. That is what stops a figure reading as a pasted screenshot.

Don't

Give the chart a heading. The title is mono chrome — a chart is a figure inside a section, not a section of its own.

Do

Draw gridlines horizontally only, in --hairline-faint. Vertical gridlines duplicate the page grid behind them.

Don't

Let a library pick colours. Read --series-1…4 off :root at runtime so the light theme follows.

Do

Write a .viz-note saying what to notice. A figure without a claim is decoration.

Don't

Go past four series on one plot. Beyond that a line chart is the wrong chart — use small multiples.

Reading the tokens from JavaScript

Every charting library takes explicit colours. Take them from the stylesheet, not from a copy — then a theme switch is one redraw and not a second palette to maintain.

function palette() {
  const s = getComputedStyle(document.documentElement);
  const t = (n) => s.getPropertyValue(n).trim();
  return {
    series: [t('--series-1'), t('--series-2'), t('--series-3'), t('--series-4')],
    ghost:  t('--series-ghost'),
    grid:   t('--grid-line'),
    axis:   t('--axis-line'),
    text:   t('--axis-text'),
    font:   t('--font-mono'),
  };
}

// journeymro.js fires this whenever the theme changes
window.addEventListener('jm:theme', () => redraw(palette()));
06

Series palette

Ordered. Take them in sequence rather than picking by taste — the second series is deliberately a neutral, so it reads as the other one rather than as a rival hue.

Categorical colour is a last resort. Five categories in five hues is five things to memorise. Small multiples, direct labels, or ordering by value will nearly always beat it — and all three survive the light theme without re-tuning.
07

Dashboard layout

Everything on this page, assembled the way a real screen would be: a toolbar on the frame, a stat row, a chart and a table sharing one hairline lattice. Note that nothing here is a floating "widget" with its own shadow — the lattice is the layout.

Assembled screen
Site 04 · Mount Isa

Stock position

Below ROP × Tier A Live
14,203
Active items
$41.2m
Stock value
312
Below ROP
96.4%
Service level
On-hand units against reorder point52 weeks
Reorder point 70011001500W01W13W26W39W52
PartDescriptionOn handROPCoverStatus
4400-206-08Seal, mechanical, 80mm42180Below ROP
4400-502-33Coupling, flexible012Stockout
4400-455-91Belt, drive, 4-rib1824Low
4400-118-22Bearing, roller, 220mm1,184860Healthy
Negative margins are doing real work here. The chart panel and the table meet on a single shared hairline (margin-bottom: -1px), so the lattice stays 1px throughout instead of doubling where two components touch. Same trick as the notch lapping onto the rail.