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

Time Series

<TimeSeries /> · 14 examples

Trend with a clock: lines, areas and bars over time, with annotations, timezones and yesterday drawn underneath.

Overview

If you have ever been paged, you have looked at a time series. A line, wandering across hours, and a sinking feeling at one particular place on it. Every operational question eventually becomes one of three: when did it start?, what else happened then? and is this normal for a Tuesday? A time series chart exists to answer those without making you open another tab.

Time is a strange x axis. Hours have sixty minutes, days have twenty-four hours, except twice a year when they do not, and nobody agrees where midnight is. So this chart takes everything Trend can do (stacking, thresholds, two axes, smooth lines) and adds the time-specific parts: ticks that land on round clock times, labels in the timezones your team lives in, and a way to overlay the same series from a day or a week ago.

The first time series charts predate computers by a long way. William Playfair was drawing economic data against years in the 1780s, and the format has been getting more decorated ever since. The modern version is mostly an argument about how many annotations you can add before the line disappears. The answer, for the record, is fewer than you think.

Props

API

<TimeSeries> plots values over time: metrics, latency percentiles, request rates. It is a port of Grafana's Time series panel. It shares everything with <Trend>, which in Grafana is built on Time series too, and adds the time-specific options: a time axis by default, several timezones and time compare.

use zopra_charts::*;
  • <TimeSeries>
  • Time axis and timezones
  • Time compare
  • Coming from Grafana

Not sure which option fits? See the guide.


<TimeSeries>

<TimeSeries data={metrics} x={|m: &Metric| m.time} annotations={deploys}>
    <TrendSeries name="p99" y={|m: &Metric| m.p99} />
    <TrendSeries name="p50" y={|m: &Metric| m.p50} line_style={LineStyle::DASH} />
</TimeSeries>

Every prop of <Trend> works the same here. Series are <TrendSeries> tags, with the same style props, axes, stacking, colours and thresholds, missing values, annotations, exemplars and zoom and tooltip, legend and keyboard.

These props differ or are new:

PropTypeDefault
xFn(&T) -> f64: milliseconds since the Unix epochfor a Frame: the x_field, or the first field
x_axisAxisConfigAxisConfig::new().time(), in UTC
timezones&'static [i32]: UTC offsets in minutes[0]
time_comparef64: a shift in millisecondsnone

Time axis and timezones

The x axis is a time axis even when you pass your own x_axis. Tick labels pick a round step (seconds up to years) and show only what changes, as in Grafana.

timezones adds one row of tick labels per timezone, under the first, all at the same ticks. The first entry is the main axis and is also used in the tooltip:

timezones={&[0, 330, -300]}

shows UTC, UTC+5:30 and UTC−5. With orientation={VizOrientation::Vertical}, each timezone gets a column of labels instead.

Time compare

time_compare={24. * HOUR} draws every series a second time, shifted forward by that amount, so yesterday's values sit under today's. The copies:

  • are dashed, unfilled and never stacked, in the colour of their series;
  • are named "<name> (1d ago)", and show in the legend and tooltip like any series;
  • take each value from the row exactly one shift earlier, within half the smallest gap between rows. Rows without a match are left as gaps.

Your data must cover the earlier period too: to compare the last 24 hours with the 24 before, pass 48 hours of rows.

Coming from Grafana

Everything in Trend's table, plus:

Grafana optionHere
Time zone, additional time zonestimezones
Time shift / comparetime_compare
Orientationorientation
Annotations, Exemplarsannotations, exemplars
Disable keyboard eventskeyboard={false}

Not available here: the legend's faceted label filter (series here have no labels), dashboard time pickers and annotation editing.

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. 01Check the time range and the zone first. A chart that is reading UTC when you think in local time can make a deploy look like it happened at the wrong end of the day.
  2. 02Find the change and then the cause: annotations and aligned series often explain it.
  3. 03If time compare is on, look at where the dashed copy and the solid line part ways. Agreement means normal. Divergence is what you are here for.
  4. 04Hover for a shared tooltip with every series at one moment. Drag to zoom, double-click to reset.

When to use it, and when not to

Reach for it when

  • Values change over time and you want to see when: metrics, latency percentiles, request rates, temperatures.
  • You need to read a spike against events. Annotations mark deploys, incidents and maintenance on the chart itself.
  • The question is whether today is unusual. Time compare puts yesterday or last week underneath.
  • Several teams in several timezones look at the same chart.
  • You want to link an aggregate to individual samples: exemplars connect a percentile line to the slow requests behind it.

Skip it when

  • x is not time. Trend takes any ordered numeric x.
  • Each time slot has a range, not one value. Candlestick keeps open, high, low and close.
  • You want a whole distribution per time slot. A line of p99 hides the shape. Use a Heatmap.
  • The data is a state that holds until it changes, across many things. State Timeline merges it into readable blocks.

The math behind it

Time as a number

Internally, x is a number of milliseconds since the Unix epoch. Everything the chart does with x is linear: the visible range [t0,t1][t_0, t_1][t0​,t1​] is mapped onto the plot width WWW by p=W (t−t0)/(t1−t0)p = W\,(t - t_0)/(t_1 - t_0)p=W(t−t0​)/(t1​−t0​). Timezones change only what the labels say, never where points are drawn.

That separation is why you can show the same chart in two zones and not have the lines move.

p(t)=W t−t0t1−t0p(t) = W\,\frac{t - t_0}{t_1 - t_0}p(t)=Wt1​−t0​t−t0​​

Choosing tick marks

A number axis picks nice steps from 1, 2, 2.5, 5, 10. A time axis cannot, because a nice step for time is 15 minutes, not 12.5. The chart keeps a fixed ladder of 37 steps from one millisecond to years (1, 2, 5, 10, 20, 50, 100, 200, 500 ms, then seconds, minutes, hours, days and so on) and picks the smallest step that is at least the span divided by the number of labels that fit.

The first tick is the first multiple of the step at or after t0t_0t0​ in local time, which is to say after shifting by the zone's UTC offset δ\deltaδ. That is how ticks land on midnight and on the hour in the viewer's zone, not in UTC.

s=min⁡{ s′∈S:s′≥(t1−t0)/n },tfirst=⌈t0+δs⌉s−δs = \min\{\, s' \in \mathcal{S} : s' \ge (t_1 - t_0)/n \,\}, \qquad t_{\text{first}} = \left\lceil \frac{t_0 + \delta}{s} \right\rceil s - \deltas=min{s′∈S:s′≥(t1​−t0​)/n},tfirst​=⌈st0​+δ​⌉s−δ

Time compare

time_compare draws every series a second time, shifted forward by a fixed amount Δ\DeltaΔ (24 hours for yesterday, 7 days for last week). The shifted copy at time ttt is the original value at t−Δt - \Deltat−Δ. The copies are dashed and styled like their source so they read as the same measure at another time.

Your data has to cover the earlier period as well. To compare the last 24 hours with the 24 before, pass 48 hours of rows.

ycompare(t)=y(t−Δ)y^{\text{compare}}(t) = y(t - \Delta)ycompare(t)=y(t−Δ)

Stacking and percent

Stacked series accumulate positive and negative values separately, and Stacking::PERCENT divides each value by the sum of the absolute finite values at that time. The details match Trend exactly, because this chart is Trend with a time axis.

Real life usage

Tuesday looks different from Monday

Priya worked on the on-call rota for a payments company with engineers in Pune, Lisbon and Austin. Every incident review began with the same sentence, in three accents: what time was that, in what zone? Roughly a third of the review was spent on arithmetic.

A graph of error rate for the checkout flow showed a bump on a Tuesday afternoon. It was small. Was it an incident? The line was a little above where it had been the previous few hours, but not above where it had been last Wednesday, and nobody could remember what a normal Tuesday looked like.

Priya wanted one chart that made three things obvious: the zones the team lived in, the deploys that had gone out that day, and what the same hours had looked like exactly a week earlier. She did not want a second dashboard. She had a rule about dashboards that involved a pause and a sigh.

She built the panel with <TimeSeries> from zopra-charts: the error-rate series with a one-week time_compare, an annotation for each deploy pulled from the release log, and the three timezones on the axis so the labels read in Pune, Lisbon and Austin time at once.

The week-before line lay right on top of the bump. Every Tuesday at about 14:10 Lisbon time, the error rate climbed by about 0.4 percentage points as a batch job on the partner side retried a pile of failed charges. The deploys were not near it. The bump was not an incident. It was a habit.

That one chart saved the review. It also showed, in the same view, an actual incident two hours later, where the solid line left the dashed one and kept going up, right after the 16:30 deploy marker. The review took eleven minutes and nobody did any arithmetic.

The partner has since fixed their retry job. The dashed line and the solid line now lie on top of each other all week, which Priya describes as the most boring and most beautiful thing she owns.

Pitfalls

  • Wrong zone in the labels. Positions never change with the zone, only the text. Always check which zone the axis claims.
  • Time compare needs data. If you pass 24 hours and ask to compare with 24 hours ago, the dashed line has nothing to show.
  • Gaps. By default a missing value breaks the line, which is usually the truth. span_nulls bridges them. Use it only when missing means skipped.
  • Too many annotations. Fifty markers turn the chart into a barcode. Show the ones that explain something.
  • Averaging away the spike. Rate and average series smooth short bursts. Show a max or a percentile too.

From around the web

Watch and read

YouTubeDeep Dive - Time Series Panel Visualizations: What Are They? How to Get Started? | GrafanaGrafanaOfficial Grafana deep dive into the time series panel, which this chart is modelled on.

Keep going

Related charts

TrendThe same chart for any ordered numeric x, such as distance or speed.HeatmapWhen you want the whole distribution at each time slot.State TimelineWhen the data is categorical states rather than numbers.HorizonWhen you have dozens of series and each needs a thin strip.
PreviousBar GaugeNextHistogram

On this page

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