Command Palette

Search for a command to run...

Hectal
PHASE 16Advanced ~41 min· topic 1 of 8

Topic 16.1

Dashboard Layout and KPI Tiles

In one line

A dashboard is read in a glance, not studied. Put the few numbers that matter first, give each one a comparison and a trend, lay the rest out on a grid, and stop adding charts when each question already has one.

Think of it like this

The dashboard of a car. Speed is big and in the middle. Fuel and temperature are small. A warning light is silent until something is wrong. You never read it: you glance, and in one second you know whether to keep driving. A web dashboard is named after this panel for the same reason.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

KPI
Key performance indicator: one number the team watches to know how things are going.
tile
One box of a dashboard: a label, a number, a comparison and a small trend.
sparkline
A tiny line chart without axes that shows the shape of a series next to a number.
context
What a number is compared with: last week, a target, the same day last year.
hero chart
The one large chart that answers the dashboard's main question.
above the fold
What you see without scrolling. On a wall screen this is everything.

Step by step

01Start with the question, then the numbers

Tomás's question is "How are the bikes doing?" The answer in numbers: rides in the last 7 days, compared with the 7 days before, and a few others. The first example computes them from daily.csv and builds a tile for each.

Each tile shows the label, the number, the change in words and a sparkline. The log prints the same text a screen reader would hear.

02Make the change safe

The second example feeds the change helper every awkward input: a normal rise, a fall, no change, a zero in before, a missing before, a tiny base. None prints Infinity or NaN.

It then draws five sparkline tiles for odd series: one point, all equal, a gap, a long series, and a series that only falls.

Make the change safediagram
Rendering diagram…

03Lay the tiles on a grid

In the first example the tiles sit in a flex row that wraps (flex-wrap: wrap), and the hero chart takes the full width below. The same HTML fills one row on a wall screen and stacks on a phone, with no media query.

The project's .grid uses repeat(auto-fit, minmax(300px, 1fr)) for charts for the same reason. Keep one gap size everywhere.

04Count the charts

Open the plan table from Phase 2 and tick each chart that answers a daily question. Charts with no tick move behind a click or go. The first screen of Lumen's dashboard shows the KPI row, the rides line and the live station box, and nothing else.

Try it yourself

  1. 1

    Compare with another week

    In the first example change rows.slice(-14, -7) to rows.slice(-21, -14). The change line then compares with the week before last. Always say what the comparison is in context.

  2. 2

    Flip what is good

    In the second example remove higherIsGood: false from "late returns". The change line becomes good although more late returns is bad news. Colour follows the flag, so the flag must be right.

  3. 3

    Give a tile a zero before

    In the second example change the before of "only falls" to 0. The line now reads no earlier value to compare with, the arrow turns into a dash and the colour class changes from bad to flat: the tile refuses to print an infinite percentage.

Code & diagrams

KPI tiles from the real data New tab

The tile code is the real src/parts/kpi.js of the Lumen project, pasted in because the preview cannot import local files.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Rides in 2025: 933,651 | no change line
Last 7 days: 9,224 | ▼ down 6.7% vs the 7 days before
Rainy days (30 days): 13 | ▲ up 30.0% vs the 30 days before
Sparklines drawn: 3
The change line and the trend line with awkward input New tab

Nothing prints Infinity or NaN. A rise in late returns is bad, so its change line is marked bad even though the number went up.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

normal rise        -> up 14.5% vs last week
fall               -> down 10.0% vs last week
no change          -> no change vs last week
before is 0        -> no earlier value to compare with
before is missing  -> no earlier value to compare with
now is NaN         -> no earlier value to compare with
tiny base          -> up 150.0% vs last week
tiles with a sparkline: all equal, a gap, only falls, late returns
change colours: bad, bad

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Divide without checking

In percentChange, replace the first line (if (!Number.isFinite(now) …) with nothing, so the function always returns ((now - before) / Math.abs(before)) * 100. Then run the second example.

terminal
$ Console
── what you'll see ──
before is 0 -> up Infinity% vs last week
before is missing -> up Infinity% vs last week
now is NaN -> down NaN% vs last week

Myth vs fact

Myth

A dashboard should show everything, so nobody has to ask.

Fact

A dashboard that shows everything shows nothing. It answers the few questions people ask daily and links to detail for the rest.

Myth

Green means good and red means bad.

Fact

Up is not always good (late returns), and colour alone is invisible to many readers. Use words and arrows, then let colour follow.

Myth

Sparklines of two tiles can be compared by height.

Fact

Each sparkline has its own scale, so only the shape is comparable. If heights must compare, share one scale and say so.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    A percentage hides the base. "Up 150%" from 2 to 5 is three rides. Show both when the base is small, or hide the percentage below a minimum base that the team agrees on.

  • ▸

    d3.scaleLinear with a zero-width domain returns the middle of the range, so the all-equal sparkline is flat and centred instead of NaN. Check with d3.scaleLinear([5, 5], [0, 100])(5), which is 50.

  • ▸

    Tiles built with textContent cannot inject markup. A station name such as <img onerror=…> from a data file would show as text. Use innerHTML for a tile only if you escape every value.

  • ▸

    Big numbers need a rule too. 933,651 fits a tile, but 1,234,567,890 may not. new Intl.NumberFormat("en-GB", { notation: "compact" }).format(933651) gives 934K (and 1.2M for 1,234,567). Show the short form in the tile and the full number in a title or in the table twin, so nothing is rounded away for good.

  • ▸

    On a wall display, test from the real distance. Text under about 24 px on a TV is unreadable from across a room, and wall screens often run at 1920 × 1080 with the browser zoomed out.

Remember this

  1. 1

    The problem this solves: the board wants to look at one screen on the wall and know how the bikes are doing. Twenty equal charts give no starting point: every chart shouts, so nothing is heard. Good layout decides what is read first, second and last.

  2. 2

    What goes first. In languages that read left to right, eyes start at the top left. Put the question with the most consequence there: how many rides, and are we up or down? A row of 3 to 6 KPI tiles (KPI means key performance indicator: one number the team watches) comes first. One large chart that answers the main question comes next, then smaller supporting charts, then detail on request.

  3. 3

    A KPI tile is four things. A label ("Last 7 days"), the number (24,120), a comparison (up 4.2% vs the 7 days before) and a small trend. A number alone is not a fact: is 3,451 rides good or bad? Say what it is compared with, in words, so nobody guesses. The kpiTile function in this topic builds all four. Before the numbers arrive, the same row shows grey placeholder tiles (skeletonTiles, Topic 16.4), so the page does not jump when they load.

  4. 4

    Computing the change safely. (now − before) ÷ before × 100 breaks when before is 0 (division by zero gives Infinity), when it is missing (null counts as 0 in arithmetic and undefined becomes NaN) and when it is tiny (2 rides to 5 rides is "up 150%", which means nothing). The helper returns null and the tile says no earlier value to compare with instead of printing Infinity%. For small bases, show the plain difference too.

  5. 5

    Colour never works alone. Green for good and red for bad hides the message from the one in twelve men with a colour-vision difference, and from Meera's screen reader. Each change line has an arrow and words (up, down), and the colour only follows them. Also, "up" is not always good: more late returns is bad, so the tile takes higherIsGood.

  6. 6

    Sparklines. A sparkline is a word-sized chart without axes (the name is Edward Tufte's). It shows the shape of the last days, not the values, so each tile has its own scale and the heights of two sparklines must not be compared. Draw a dot on the last point, break the line at a gap, draw nothing for fewer than two points, and draw a flat line when every value is equal.

  7. 7

    Layout on a grid. CSS Grid with repeat(auto-fit, minmax(160px, 1fr)) lets the tiles fill a wall screen in one row and stack on a phone with no media query. Give the hero chart the full width, and let supporting charts sit two or three across. Keep the same gap everywhere: uneven space reads as grouping that is not there (Topic 2.4).

  8. 8

    Too many charts. Each chart must answer a question somebody asked (the plan table from Phase 2). If a chart answers none, remove it. When a box needs more than one chart, use a tab or a click that opens detail, and keep the first screen small. A dashboard that needs a scroll bar on the wall has already failed at its job.

Explain it without notes

01

Why does a KPI tile show a comparison and not only a number?

02

Why does the change helper return null instead of a number in some cases?

03

Why must a change line use words and arrows as well as colour?

04

How do you decide whether a chart belongs on the first screen?

Practice

01

Write tileText(label, now, before) that returns "label: now (up X% vs before)", using describeChange. What does it print for ("Rides", 100, 0)?

02

A tile shows the busiest station. Which comparison would you give it, and what would you show when the busiest station changed since last week?

Trade-offs

  • ↔

    Fewer tiles are read faster, but a team that needs ten numbers will open another screen. Group the extra numbers behind a click rather than shrinking everything.

  • ↔

    A sparkline adds context at the cost of space and of one more thing to describe for screen readers. Use it when the trend matters more than the exact value.

Done when you can

  • I can say what goes first on a dashboard and why.

  • I can build a KPI tile with a number, a comparison and a trend.

  • I can make the change helper safe for zero, missing and tiny numbers.

  • I can make a sparkline for one point, equal values and a gap.

  • I can lay tiles out on a grid that fits a wall screen and a phone.

  • I can decide which charts do not belong on the first screen.