Zopra/Charts
Guide
Getting startedAll chartsWhich chart to use?Domain specific
Base charts7
XY ChartTrendCandlestickHeatmapState TimelineFlame GraphBar Gauge
Derived charts73
Time SeriesHistogramStatus HistoryStatGaugeHoneyCombTracesNode GraphHeikin-AshiRenkoKagiPoint & FigureParetoIshikawaControl ChartBland-AltmanLollipopDumbbellSlopeBumpBack-to-BackWaterfallBulletFunnelMekkoForestBarcodeBeeswarmRidgelineHorizonSpectrogramAttention MapWaffleVennUpSetWarped WaffleWord CloudGanttSankeySankey MatrixTreemap ForceChord DiagramCircosArc DiagramAlluvialSankey FunnelDendrogramIcicleSunburstSpiderNightingale RoseParallel CoordinatesOscilloscopeEfficient FrontierPhase PortraitPoincaré MapBifurcationH-R DiagramRamachandranHarkerRoot LocusRadiation PatternQuiverHodographSmith ChartEye DiagramSequence LogoTernaryTernary ContourPiperStüveTephigramChoropleth

Charts/Core charts

Stat

<Stat /> · 14 examples

One big number, how it got there, and whether it has moved since you last looked.

Overview

Most dashboards are built by people who love charts, and it shows. You open one to answer a single question, is the thing on fire?, and you are handed a wall of lines, axes, legends and a time picker. The stat panel is the opposite instinct. It takes one value, makes it as large as the tile allows, and gets out of the way.

The format is old and humble: the scoreboard. A speedometer reading, the number on a petrol pump, the total at the bottom of a receipt. Dashboards inherited it as the "single stat", and Grafana's Stat panel is the version most people have seen: a headline number, a faint sparkline behind it for context, and a colour that changes when the number crosses a line you care about. This chart is a port of that design, so if you know it, you already know this.

The sparkline is what keeps it honest. A bare number says 94%. A number with a sparkline says 94%, and it was 40% an hour ago. Same digits, very different Tuesday.

Props

API

<Stat> shows one big number per value: the current CPU, today's revenue, the error count. It can add a sparkline of the history, colour the number or the whole tile, and show the change since the start. It is a port of Grafana's Stat panel; see Coming from Grafana.

use zopra_charts::*;
  • <Stat>
  • Which values are shown
  • Layout
  • Colour
  • Percent change
  • Enums
  • Coming from Grafana

Not sure which option fits? See the guide.


<Stat>

<Stat data={samples} value={|s: &Sample| s.cpu} x={|s: &Sample| s.time} format={percent} />
PropTypeDefault
dataVec<T>, Signal<Vec<T>>, Frame<N>, Signal<Frame<N>>, or your own Datasetrequired
valueFn(&T) -> f64every field of a Frame
nameFn(&T) -> &str: the name of each row, with Reduce::Allfield names for a Frame
reduceReduceReduce::Calc(Calc::Last)
fields&'static [&'static str]: which fields of a Frameevery field except x_field
xFn(&T) -> f64: the time of each row, for the sparklinethe row number
x_fieldfield name: a Frame's time field, used for the sparkline and not shown as a valuenone
color_modeStatColorModeValue
graph_modeGraphModeArea
justify_modeJustifyModeAuto
text_modeTextModeAuto
orientationBarOrientationAuto
wide_layoutbooltrue
show_percent_changeboolfalse
percent_change_colorPercentChangeColorStandard
title_size, value_size, percent_sizePixelsfitted to the tile
min, maxf64the smallest and largest number in the data
thresholdsThresholdsyour theme's good colour, then its bad colour from 80
colorGaugeColorThresholds
mappings&'static ValueMappingsnone
formatFn(f64) -> SharedStringnone
decimalsusizeup to 3, trailing zeros dropped
class, key

Which values are shown

reduce decides how many tiles there are:

Your datareduceTiles
rows with valueReduce::Calc(calc) (default Last)one tile: calc over every row, with a sparkline
rows with value and nameReduce::All { limit }one tile per row, no sparkline
a FrameReduce::Calc(calc)one tile per field, each with its own sparkline
a FrameReduce::All { limit }one tile per cell

Layout

Each tile picks one of Grafana's four layouts from its size:

TileLayout
more than 2.5 times wider than tall, with a sparkline and taller than 50 pxname on the left, value on the right, sparkline in the bottom half
more than 2.5 times wider than tall, otherwisename and value side by side, centred vertically
taller than 100 px, with a sparklinename above value, sparkline filling the rest
otherwisename above value, centred vertically
  • wide_layout={false} always uses the stacked layouts.
  • justify_mode={JustifyMode::Center} centres the text. Tiles without a name are always centred.
  • Text is sized to fit. With several tiles, every tile uses the size that fits the longest name and value, so they line up. title_size, value_size and percent_size fix the sizes instead.
  • text_mode: Auto shows only the value for a single tile and the name and value for several. Name shows the name in place of the value; None shows neither and leaves the colour and sparkline.
  • Several tiles. BarOrientation::Auto packs tiles into the grid that keeps them closest to square. Horizontal stacks them as rows and Vertical places them side by side.

Colour

The value's colour comes from a mapping if one matches, otherwise from color: the threshold step it reaches, its position on a scheme between min and max, a palette colour per tile, or one fixed colour.

color_modeEffect
Valuethe number in the value's colour
Backgroundthe tile filled with a gradient of the value's colour, text in black or white for contrast
BackgroundSolidthe tile filled with the value's colour
Nonethe number in your theme's text colour

The sparkline uses the value's colour, or white on a background. GraphMode::Area fills under the line; Line draws only the line; None hides it.

Percent change

With show_percent_change, each tile reduced from several rows shows the change from the first to the last number, as Grafana computes it: (last - first) / first × 100. An arrow shows the direction.

percent_change_colorColour
Standardgreen when it rises, red when it falls or stays the same
Invertedred when it rises, green when it falls: for errors, latency, cost
SameAsValuethe value's colour

No change is shown when the first number is 0.

Enums

TypeValues
StatColorModeValue (default), Background, BackgroundSolid, None
GraphModeArea (default), Line, None
JustifyModeAuto (default), Center
PercentChangeColorStandard (default), Inverted, SameAsValue
TextModeAuto (default), ValueAndName, Value, Name, None
BarOrientationAuto (default), Horizontal, Vertical

Coming from Grafana

Grafana optionHere
Calculation, Fields, Show (calculate / all values), Limitreduce, fields
Orientationorientation
Text mode, Wide layouttext_mode, wide_layout
Color mode, Graph mode, Text alignmentcolor_mode, graph_mode, justify_mode
Show percent change, Percent change color modeshow_percent_change, percent_change_color
Text size: title, value, percenttitle_size, value_size, percent_size
Min, Max, Thresholds, Color scheme, Value mappingsmin, max, thresholds, color, mappings
Unit, Decimalsformat, decimals

Not available here: units from Grafana's unit registry, data links and the panel's time range for the sparkline (it spans the data instead).

Run them

14 examples

Press Run to load an example into the live render at the top of the page. Open one to read its code.

How to read it

  1. 01Read the number first. The tile is sized so that it is the biggest thing on the screen.
  2. 02Check the colour. It is the threshold step the value reached, so green or red tells you whether to keep reading.
  3. 03Glance at the sparkline for direction only. Each sparkline is scaled to its own range, so two tiles with similar-looking lines can differ by orders of magnitude.
  4. 04If the percent change is on, it compares the first and last readings in the tile, and the arrow shows the direction. There is nothing to click through to: the tile is the whole answer, which is the point.

When to use it, and when not to

Reach for it when

  • You have one question per tile and the answer is a number: current CPU, orders today, open incidents, p99 latency.
  • The reader will glance at it from across a room or on a wall monitor. A background colour (BackgroundSolid) is readable from further away than any axis.
  • You want a row of KPIs with a bit of history each. Pass a Frame and you get one tile per field, with matching text sizes.
  • The status is a code, not a quantity: map 200 to OK and 503 to Down with value mappings and let the tile show the word.

Skip it when

  • The shape of the history is the story. A sparkline has no axis and no labels on purpose, if people need to read values off the line, use Time Series.
  • You want the number against a known range with limits drawn on it. That is what Gauge is for.
  • You are comparing many values with each other. Tiles of different colours are hard to rank. A Bar Gauge lines them up on one scale.
  • The number is only meaningful with its distribution around it, such as a latency that is fine on average and terrible at the tail.

The math behind it

From rows to one number

Take a column of readings y1,…,yny_1, \dots, y_ny1​,…,yn​ in time order. A stat tile first reduces them to a single value vvv with the calculation you picked in reduce. The default is the last finite reading. Others are the mean, minimum, maximum or sum.

Non-finite readings (missing samples, NaN) are skipped before reducing. If you ask for Reduce::All instead, nothing is reduced: every row becomes its own tile and there is no sparkline.

v=reduce⁡(y1,…,yn),e.g. v=yn   or   v=1n∑iyiv = \operatorname{reduce}(y_1,\dots,y_n), \qquad \text{e.g. } v = y_n \;\text{ or }\; v=\tfrac{1}{n}\textstyle\sum_i y_iv=reduce(y1​,…,yn​),e.g. v=yn​ or v=n1​∑i​yi​

Percent change and the sparkline

With show_percent_change, the tile shows the change from the first finite reading yay_aya​ to the last finite reading yby_byb​, the same way Grafana does. Note that this is the first-to-last change, not the change of the reduced value. If ya=0y_a = 0ya​=0 the quotient is not finite and nothing is shown.

The sparkline maps time to the tile's width and value to its height. Writing [xmin⁡,xmax⁡][x_{\min}, x_{\max}][xmin​,xmax​] for the time range and [ymin⁡,ymax⁡][y_{\min}, y_{\max}][ymin​,ymax​] for the value range of that tile's own data, a point lands at the fractions below. If the series is flat, the range is widened by 10% of its magnitude (or set to 0 to 1 when the value is zero) so the line sits in the middle instead of dividing by zero.

Δ=yb−yaya×100\Delta = \frac{y_b - y_a}{y_a}\times 100Δ=ya​yb​−ya​​×100
u=x−xmin⁡xmax⁡−xmin⁡,w=y−ymin⁡ymax⁡−ymin⁡u = \frac{x - x_{\min}}{x_{\max}-x_{\min}}, \qquad w = \frac{y - y_{\min}}{y_{\max}-y_{\min}}u=xmax​−xmin​x−xmin​​,w=ymax​−ymin​y−ymin​​

Colour and layout

The colour comes from a value mapping if one matches, otherwise from the thresholds: the step whose lower bound is the largest one not above vvv. Percentage thresholds are resolved against min and max, which default to the smallest and largest number in the data.

Each tile then picks one of four layouts from its own shape. With width WWW and height HHH, a tile counts as wide when W/H>2.5W/H > 2.5W/H>2.5. A wide tile with a sparkline and H>50H > 50H>50 px puts the name left, the value right and the sparkline in the bottom half. A tall tile with a sparkline needs H>100H > 100H>100 px to stack name, value and sparkline. Text is sized to fit by measuring it, and with several tiles every tile takes the size that fits the longest name and value, so the row lines up.

Real life usage

The dashboard nobody had to open

Marguerite ran support operations for a company that sold smart thermostats, which is to say she answered for every house that was too cold at 6 a.m. and every customer who had opinions about it. Her team's wall monitor showed a Grafana-style board of eleven panels, and by her own count three of them had never been looked at on purpose.

The trouble was the morning stand-up. It started at 9:00 and spent the first eight minutes on one question: how bad is the queue? Somebody would lean toward the screen, squint at a multi-line chart of ticket volume and say something like "looks a bit higher than yesterday?", which is what people say about every chart.

Marguerite wanted four numbers a person could read from the coffee machine: open tickets, median first response in minutes, tickets older than a day, and today's volume against the same hour last week. Each needed a colour that meant something, and each needed just enough history to separate "high" from "high and climbing".

She built the board in a small desktop app using zopra-charts. A single <Stat> fed with a frame of the four fields gave her four equal tiles, text sized to the longest label, sparklines underneath. She set thresholds per field, switched the colour mode to BackgroundSolid, and flipped percent_change_color to Inverted, because for ticket counts a rise is bad news and a tile that turned green when things got worse would have been funny exactly once.

The first morning, the aged-tickets tile was a deep, unmissable red with a 62% arrow beside it. Nobody leaned toward the screen. Somebody said "it's the weekend backlog" from the doorway, and that was the whole discussion.

Over the next month the stand-up lost its first eight minutes. Median first response dropped from 41 to 27 minutes, mostly because a red tile is difficult to defer and a line chart is easy to. Aged tickets stayed under 30 for three straight weeks, a number that had not previously been seen in the wild.

The other seven panels are still on the old board, as a monument. The wall monitor now shows four squares, and the squares are the only thing anyone has ever complained about not being large enough.

Pitfalls

  • Reduce chooses the story. Last is "now", Mean is "over this period", Max is "the worst moment". The same data gives three different numbers, so say in the title which one you picked.
  • Sparklines are scaled per tile. A flat-looking line on a tile whose values range between 99.1 and 99.3 is as dramatic as a spike elsewhere. Do not compare slopes across tiles.
  • Percent change is first to last. If the series dipped and recovered, the change is small even though the middle was ugly. And a first value of zero produces no change at all.
  • Colour direction. Up is not always good. For errors, latency and cost use PercentChangeColor::Inverted, and set thresholds so that the high end is the bad end.
  • Fixed text sizes. Setting value_size stops the text from fitting the tile. That is useful when panels must match, and clipped when the number grows an extra digit.

From around the web

Watch and read

YouTubeIntroducing Percent Change in Grafana Stat Visualizations (10.3) | GrafanaGrafanaGrafana's own demo of the stat panel's percent change display, a close relative of this chart.
YouTubeStat Visualization Percent Change Color Mode in Grafana 11.1 (Demo) | GrafanaGrafanaGrafana demo of the stat panel percent change colour mode.

Keep going

Related charts

GaugeWhen the number has a known range and the limits are what matter.Bar GaugeWhen you need to compare several values against the same range.Time SeriesWhen the history itself is the answer and people need to read it.
PreviousStatus HistoryNextGauge

On this page

  • Live
  • Overview
  • API
  • Examples
  • How to read it
  • When to use it
  • The math
  • Story
  • Pitfalls
  • Watch and read
  • Related