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/Base

Trend

<Trend /> · 18 examples

Lines, areas, bars and steps over an ordered numeric x, with stacking, thresholds and two axes.

Overview

A trend chart is what most people picture when somebody says graph: a number on the left, an x axis along the bottom, a line wandering across. It is also the chart most likely to be used for the wrong thing, because a line is so persuasive. The moment two dots are joined, the reader believes something happened between them.

The form is old and has a known father. William Playfair drew the first line charts of economic data in the 1780s, showing England's trade balance as a pair of lines with the gap between them shaded. His point was that a picture of a number moving is worth a table of the number standing still. Two centuries later the point stands, and we still shade the gap.

Trend in zopra-charts is the general version. Its x is any numeric value that only goes up: speed, distance, depth, load. If x is a clock, Time Series is the same chart with the right axis. Each series can be a line, an area, bars or points, stack with its neighbours, sit on its own axis and change colour when it crosses a limit. It is a lot of chart. Use less of it than you can.

Props

API

<Trend> draws one or more series against a numeric x that only goes up: engine speed, distance, load, depth. It's Grafana's Time series graph with a numeric x instead of time, and takes all of its styling options. See Coming from Grafana.

use zopra_charts::*;
  • <Trend>
  • <TrendSeries>
  • Style props
  • Axes
  • Stacking
  • Colour, gradients and thresholds
  • Missing values
  • Annotations, exemplars and zoom
  • Tooltip, legend and keyboard
  • Enums
  • Coming from Grafana

Not sure which option fits? See the guide.


<Trend>

PropTypeDefault
dataVec<T>, Signal<Vec<T>>, Frame<N>, Signal<Frame<N>>, or your own Datasetrequired
xFn(&T) -> f64for a Frame: the x_field, or the first field
x_fieldfield name, for a Framethe first field
children<TrendSeries> tagsfor a Frame: one series per field other than x
x_axisAxisConfigAxisConfig::new()
y_axisAxisConfigAxisConfig::new()
legendLegendLegend::list() at the bottom
tooltipTooltipTooltip::single()
thresholdsThresholdsyour theme's good colour, then its bad colour from 80
schemeSchemenone
annotationsAnnotations: a Vec<Annotation>, Signal<Vec<Annotation>> or &'static [Annotation]none
exemplarsVec<(f64, f64)>: (x, y) points drawn as diamondsnone
exemplar_colorcolourmagenta
orientationVizOrientation: Auto, Horizontal, VerticalAuto (horizontal)
on_selectFn(SelectedRange, &mut Window, &mut App), after a drag-zoomnone
keyboardbool: arrow-key steppingtrue
classTailwind classesfills its parent
keyanything Into<ElementId>–
style propsdefaults for every series

x must be in ascending order. Rows with a missing x (f64::NAN) are skipped. If x goes down anywhere, the chart shows "Values must be in ascending order" instead of a graph, as Grafana does.

With no data, or a Vec<T> with no <TrendSeries>, the chart shows "No data".

<TrendSeries>

PropTypeDefault
yFn(&T) -> f64required
nametext"Series 1", "Series 2", …
colorHsla / Rgbathe next colour from the theme palette
y_axisAxisConfigthe chart's y_axis
thresholdsThresholdsthe chart's thresholds
schemeSchemethe chart's scheme
style propsinherited from <Trend>

Return f64::NAN from y for a missing value.

<Trend data={pulls} x={|p: &Pull| p.rpm}>
    <TrendSeries name="Torque" y={|p: &Pull| p.torque} />
    <TrendSeries name="Power" y={|p: &Pull| p.power} y_axis={AxisConfig::new().label("hp")} />
</Trend>

Style props

<Trend> and <TrendSeries> both take these. A value on the series wins over the chart's value, and the chart's value wins over the default.

PropTypeDefaultNotes
draw_styleDrawStyleLinelines, bars, or points only
interpolationInterpolationLinearlines only
line_widthf32 px1.0. draws no line
line_styleLineStyleSolidlines only
fill_opacityf32, 0.0–1.00.the area under a line, or the inside of a bar
gradientGradientModeNone
fill_below_toanother series' namenonefills the band between this series and that one
show_pointsPointsModeAuto
point_sizef32 px5.never smaller than line_width
show_valuesboolfalsethe value above each shown point or bar
span_nullsNullSpanNever
insert_nullsf64, in x unitsnonebreak the line where x jumps further than this
bar_alignmentBarAlignmentCenter
bar_width_factorf32, 0.1–1.00.6share of the space between two x values
bar_max_widthf32 px200.
stackingStackingNone
transformValueTransformNone
thresholds_styleThresholdsStyleOff
hide_fromHideFromnothing hiddenhide a series from the legend, the tooltip, or the chart

Points with Auto are shown when there's room, about one point every two point-widths, and hidden on dense data. Points that stand alone between missing values are always shown, so they don't vanish. Bars show no points with Auto.

Axes

The x axis spans exactly the smallest to the largest x. The y axes round out to tidy tick values, like the XY Chart's. Every AxisConfig method works here: label, placement, soft and hard limits, log and symlog scales, grid, border, centred zero, colour mode, decimals and format.

More than one y axis. Give a series its own y_axis to put it on a second scale.

  • Series whose y_axis has the same label share one axis.
  • With AxisPlacement::Auto, the first axis goes on the left and the next ones on the right.
  • AxisColorMode::Series colours an axis's ticks like its first series.
  • The grid follows the first axis.

Stacking

StackingEffect
Stacking::Noneeach series on its own
Stacking::Normal(group), or Stacking::NORMAL for group "A"series in the same group are added on top of each other
Stacking::Percent(group), or Stacking::PERCENTeach series is shown as its share of the group's total; the axis reads 0% to 100%

Positive and negative values stack separately, so a negative series grows down from zero. A stacked series with a fill_opacity fills down to the series below it. The tooltip and legend show the real values, not the stacked totals. Series hidden from the legend drop out of the stack.

Colour, gradients and thresholds

GradientModeLineFill
Nonethe series colourthe series colour at fill_opacity
Opacitythe series colourfades from fill_opacity at the top of the chart to transparent at the bottom
Hueshifts in hue from top to bottomthe same, at fill_opacity
Schemechanges colour with the valuethe same, at fill_opacity

GradientMode::Scheme follows the series' scheme if it has one, blending between its colours from the lowest to the highest value. Otherwise it follows its thresholds, switching colour at each step.

ThresholdsStyleWhat's drawn
Offnothing
Line / Dasheda line at each threshold step
Areaeach band between steps shaded in its colour
LineAndArea / DashedAndAreaboth
Seriesthe series itself coloured by threshold, like GradientMode::Scheme with thresholds

Threshold lines and areas sit behind the series, once per y axis. Percentage thresholds are a share of the axis's min–max, or of the data when those aren't set.

Missing values

A missing value is f64::NAN in y.

NullSpanThe line at a missing value
Neverbreaks
Alwaysjoins the values either side
Within(gap)joins them when they're no more than gap apart in x, otherwise breaks

insert_nulls breaks the line wherever two values are further apart in x than its value, even if neither is missing. Use it for data that has gaps without explicit missing rows.

Annotations, exemplars and zoom

let notes = vec![
    Annotation::at(deployed, "Deploy v1.4.2").text("Rolled out to all pods"),
    Annotation::region(start, end, "Incident #218").color(rgb(0xd20f39)),
];
<Trend data={rows} x={|r: &Row| r.x} annotations={notes} exemplars={slow} on_select={zoomed}>
  • Annotations mark a point (a dashed line) or a region (a shaded band) with a marker along the bottom edge. Hover the marker for its title, time and text. color overrides the default cyan.
  • Exemplars are individual samples, such as one slow request among the percentiles, drawn as diamonds on the first y axis. Hover one for its x and y.
  • Zoom. Drag across the chart to zoom the x axis; double-click to zoom out. on_select receives the selected range.
  • Orientation. VizOrientation::Vertical turns the chart so x runs down the left and values run left to right. Everything else (fills, gradients, thresholds, tooltips, zoom) works the same.

Tooltip, legend and keyboard

Tooltip::multi().sort(SortOrder::Descending).hide_zeros(true).max_width(px(240.))
MethodEffect
Tooltip::single()the series nearest the pointer at the hovered x
Tooltip::multi()every series at the hovered x
Tooltip::hidden()no tooltip; the hover line still shows
sortSortOrder of the rows in a multi tooltip
hide_zerosleave out series whose value is 0
max_width / max_heightPixels

The tooltip title is the x value. A vertical line marks the hovered x and highlights each series' point there.

The legend works like the XY Chart's: click a series to show only it, Ctrl-click to toggle it. Calcs use the real values.

Keyboard. Click the chart, then press ← / → (or ↑ / ↓) to move the hover by one x value, Shift + arrow to move by ten, and Esc to clear it. keyboard={false} turns this off.

Enums

EnumVariants
DrawStyleLine (default), Bars, Points
InterpolationLinear (default), Smooth, StepBefore, StepAfter
GradientModeNone (default), Opacity, Hue, Scheme
PointsModeAuto (default), Always, Never
NullSpanNever (default), Always, Within(f64)
BarAlignmentBefore, Center (default), After
StackingNone (default), Normal(&str), Percent(&str), plus NORMAL and PERCENT for group "A"
ValueTransformNone (default), Constant (a flat line at the first value), NegativeY (drawn below zero)
ThresholdsStyleOff (default), Line, Dashed, Area, LineAndArea, DashedAndArea, Series
TooltipModeSingle (default), Multi, Hidden
SortOrderNone (default), Ascending, Descending
VizOrientationAuto (default, horizontal), Horizontal, Vertical

Smooth curves pass through every point without overshooting it. StepAfter holds each value until the next x; StepBefore jumps to the next value straight away.

Coming from Grafana

Grafana optionHere
X fieldx, or x_field for a Frame
Style, Line interpolation, Line width, Line styledraw_style, interpolation, line_width, line_style
Fill opacity (0–100)fill_opacity (0.0–1.0)
Gradient modegradient
Fill below tofill_below_to
Show points, Point size, Show valuesshow_points, point_size, show_values
Connect null values, Disconnect valuesspan_nulls, insert_nulls
Bar alignment, Bar width factor, Bar max widthbar_alignment, bar_width_factor, bar_max_width
Stack seriesstacking
Transformtransform
Axis placement, label, width, soft min/max, scale, centred zero, colour, border, gridy_axis on the chart or a series
Min, Max, Decimals, UnitAxisConfig::min, max, decimals, format
Thresholds, Show thresholdsthresholds, thresholds_style
Color schemecolor, or gradient with scheme / thresholds
Hide in areahide_from
Tooltip mode, sort, hide zeros, max width/heighttooltip
Legendlegend
Annotations, Exemplarsannotations, exemplars
Orientationorientation
Disable keyboard eventskeyboard={false}

Not available here: units from Grafana's unit registry (use AxisConfig::format), value mappings, data links and overrides by field regex (set the prop on each <TrendSeries> instead).

Run them

18 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 axis first, especially the left one. A line that goes up sharply on a 94 to 96 axis is a calm line on a 0 to 100 axis.
  2. 02Find the direction and the breaks. Gaps are real: the default behaviour is that a missing value breaks the line, and that is the chart telling you data was missing.
  3. 03For stacks, read the top edge as the total and the thickness of a band as that series' own value. Only the bottom band sits on the baseline, so compare the others by thickness, not by height.
  4. 04Hover to see every series at the same x. Click a legend entry to show only that series. Ctrl-click or Cmd-click toggles one.

When to use it, and when not to

Reach for it when

  • x is ordered and numeric (distance, speed, depth, time-like) and you want to see how one or more values change along it.
  • Several series share one x: you want them in one tooltip, aligned.
  • Parts of a total over x: stacked areas, or percent stacking when shares are the point.
  • A value has a limit: draw it as a threshold line or a shaded band, or let the line change colour when it crosses.
  • Two measures with different units, such as torque and power over rpm, each on its own axis.

Skip it when

  • x is a clock. Time Series gives you time ticks, timezones and compare.
  • x is a category. Joining Monday to Tuesday with a line implies Monday slowly turns into Tuesday.
  • x is not ordered, or you have several y values at one x. XY Chart keeps rows as given.
  • You have six series that all wander around the same range. The result is a plate of spaghetti. Pick the two that matter and mute the rest.

The math behind it

Stacking

Series that share a stacking group are drawn on top of each other. At each x index iii, series jjj in the group has a raw value vijv_{ij}vij​. Positive and negative values accumulate in separate piles, so a loss never eats into a gain. The base of a series is the current pile, and its plotted top is the pile plus its own value.

Missing values (not finite) are skipped: they add nothing to the pile and leave a break in that series' line. Hidden series and series with a fill_below_to floor never join the stack.

topij=baseij+vij,baseij=∑k<j, sign(vik)=sign(vij)vik\text{top}_{ij} = \text{base}_{ij} + v_{ij}, \qquad \text{base}_{ij} = \sum_{k<j,\ \mathrm{sign}(v_{ik}) = \mathrm{sign}(v_{ij})} v_{ik}topij​=baseij​+vij​,baseij​=k<j, sign(vik​)=sign(vij​)∑​vik​

Percent stacking

With Stacking::PERCENT each value is first divided by the column total of absolute finite values, Ti=∑j∣vij∣T_i = \sum_j |v_{ij}|Ti​=∑j​∣vij​∣, whenever that total is positive. The stack then always ends at 1, the axis is formatted as a percentage, and the totals disappear from the picture. Use it when you care about shares, and only then.

v~ij=vijTi,Ti=∑j∣vij∣\tilde v_{ij} = \frac{v_{ij}}{T_i}, \qquad T_i = \sum_j |v_{ij}|v~ij​=Ti​vij​​,Ti​=j∑​∣vij​∣

Smooth lines

Interpolation::Smooth is not a spline through anything clever. It is a monotone cubic Bézier: for neighbouring points PkP_kPk​ and Pk+1P_{k+1}Pk+1​ with spacing hk=xk+1−xkh_k = x_{k+1} - x_khk​=xk+1​−xk​ and slope dkd_kdk​, the tangent at an interior point is a weighted harmonic mean of the two neighbouring slopes, and it is set to zero when they disagree in sign.

The two Bézier control points sit one third of the way along the segment, each offset vertically by tangent times h/3h/3h/3. The zero-tangent rule is what stops the curve from overshooting: a smooth line never goes higher than the highest of the two points it joins, which a plain cubic spline cheerfully does.

mk={0dk−1dk≤03 (hk−1+hk)2hk+hk−1dk−1+hk+2hk−1dkotherwisem_k = \begin{cases} 0 & d_{k-1} d_k \le 0 \\[4pt] \dfrac{3\,(h_{k-1}+h_k)}{\frac{2h_k + h_{k-1}}{d_{k-1}} + \frac{h_k + 2h_{k-1}}{d_k}} & \text{otherwise} \end{cases}mk​=⎩⎨⎧​0dk−1​2hk​+hk−1​​+dk​hk​+2hk−1​​3(hk−1​+hk​)​​dk−1​dk​≤0otherwise​
C1=(xk+hk3,  yk+mkhk3),C2=(xk+1−hk3,  yk+1−mk+1hk3)C_1 = \bigl(x_k + \tfrac{h_k}{3},\; y_k + m_k \tfrac{h_k}{3}\bigr), \quad C_2 = \bigl(x_{k+1} - \tfrac{h_k}{3},\; y_{k+1} - m_{k+1}\tfrac{h_k}{3}\bigr)C1​=(xk​+3hk​​,yk​+mk​3hk​​),C2​=(xk+1​−3hk​​,yk+1​−mk+1​3hk​​)

Real life usage

Two axes, one gearbox

Tomasz built electric motor controllers for a company that made cargo bikes in a converted tram depot in Gdansk. His job that month was to explain to the sales team why the new motor felt weaker than the old one, even though the datasheet said it was stronger.

The sales team did not want to hear about motor constants. They wanted a picture. Specifically they wanted a picture that could be sent to a customer who had written, in capital letters, that the bike felt like pushing a fridge up a hill.

The measurement was simple: torque in newton-metres and power in watts, both against rotation speed. Two units, one x axis, and a very obvious temptation to draw two charts and let the reader align them with their finger.

He used <Trend> from zopra-charts instead, with torque as one <TrendSeries> and power as a second one on its own right-hand axis, coloured to match the line. Old motor was dashed, new motor solid, thresholds drawn as a dashed line at the controller's current limit.

The chart made the answer obvious in about four seconds. The new motor had far more peak power, but it came at 480 rpm higher than the old one. At the low rpm of climbing a hill the torque curve was actually 12 per cent below the old motor. The datasheet quoted the peak, the customer lived at the bottom.

Tomasz sent the chart to firmware with one line, asking for a field-weakening profile that moved the torque up in that region. Three weeks later a new curve appeared on the same chart, and the low rpm torque was 9 per cent above the old motor. The customer was sent the new chart and a bike, and replied with a single exclamation mark.

The fridge, reportedly, has since been returned to the kitchen.

Pitfalls

  • Joining points that should not be joined. Use Points for raw samples, StepAfter for a value that holds until it changes, and Line for continuous measurements.
  • Stacking different units. Stacks add numbers. Adding Nm to hp gives you a number in no unit at all. Stack like with like.
  • Truncated axes. Auto ranges are chosen to fit the data, not to be honest. Fix the min to zero for bars and areas.
  • Unsorted x. Trend expects x to go up. If it does not, lines join in the wrong order and you should use the XY chart.
  • Smoothing as decoration. A smooth line has no data between the points. It is a presentation choice, not a measurement.

From around the web

Watch and read

YouTubePlotting Non Time-Series Data on the X-axis in Grafana | PrescientPrescientShows plotting non-time data on the x-axis in Grafana, the idea behind the trend panel.

Keep going

Related charts

Time SeriesThe same chart with a time axis, timezones, annotations and time compare.XY ChartWhen x is not ordered or you have several y values at one x.CandlestickWhen each x carries open, high, low and close rather than one value.HorizonWhen you have dozens of series and need each in a thin strip.
PreviousXY ChartNextCandlestick

On this page

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