38  Over time

How did it change over time? Time is not a chart type in this grammar; it is a column, and you choose which channel carries it. On x it makes the familiar time series; the recipes here work through what that unlocks.

38.1 How did it change?

data(actuals) + line + x(year) + y(sales)
data(actuals) + line + x(col.year) + y(col.sales)
data(actuals) + line + x(:year) + y(:sales)
plot(data(actuals), line, x(col.year), y(col.sales))
2019 2020 2021 2022 2023 120 130 140 150 160 170 Sales Year

“Given the actuals: a line, x is year, y is sales.” A line connects the rows in x order; with years on x, that order is time.

One atom away. If the quantity (not the path) is the story, swap in area: it fills to zero, so the ink is the amount.

38.2 How did several things change?

A categorical color splits the rows into one line per category, then colors them, the same one-two it performs everywhere:

data(gapminder_asia) + line + x(year) + y(life) + color(country)
data(gapminder_asia) + line + x(col.year) + y(col.life) + color(col.country)
data(gapminder_asia) + line + x(:year) + y(:life) + color(:country)
plot(data(gapminder_asia), line, x(col.year), y(col.life),
  color(col.country))
1960 1980 2000 40 50 60 70 80 Life Year Country China India Indonesia Japan Korea, Rep.

One atom away. Too many categories for a legend to earn its space? group splits without coloring; see Many groups.

38.3 What about real dates?

A Date column is temporal, so the axis reads as a calendar without your saying anything. Ticks land on calendar boundaries, and weekly steps land on Mondays:

data(six_weeks) + line + x(day) + y(orders)
data(six_weeks) + line + x(col.day) + y(col.orders)
data(six_weeks) + line + x(:day) + y(:orders)
plot(data(six_weeks), line, x(col.day), y(col.orders))
Mar 4 Mar 11 Mar 18 Mar 25 Apr 1 Apr 8 20 30 Orders Day

There is no scale = "time" to remember: the column’s type already said it. Scales owns the calendar’s rules: months that read as quarters, midnight borrowing the day’s name.

38.4 What happened, and what do we expect?

History in one table, expectation in another. Bind each layer to its own table: each data() applies to the mark written directly after it.

data(actuals) + x(year) + y(sales) +
  line +
  data(forecast) + point
(data(actuals) + x(col.year) + y(col.sales) +
  line +
  data(forecast) + point)
data(actuals) + x(:year) + y(:sales) + line + data(forecast) + point
plot(data(actuals), x(col.year), y(col.sales), line, data(forecast),
  point)
2020 2022 2024 2026 120 140 160 180 200 Sales Year

“Given the actuals: x is year, y is sales, a line; then given the forecast: points.” The forecast dots pick up exactly where the actual line ends, and each layer read its own rows. Data owns multi-table plots.

38.5 What this section refuses

One panel per year sounds like faceting, but a facet variable names panels, and a number names nothing. The refusal says what to do with a year you mean as a label:

data(gapminder_asia) + point + x(gdp) + y(life) | facet(year)
Error:
! gog: `facet(year)` splits on a number column, but a facet variable names the panels, so it must be a category column. Make `year` text — in R, `factor(year)` — or cut it into named groups first.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.