43  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, and every recipe below puts it there.

43.1 How did it change?

The actuals table holds one sales figure per year. The question is whether sales rose or fell, and a line with year on x answers it:

actuals: all 5 rows
year sales
2019 120
2020 135
2021 128
2022 152
2023 168
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 amount matters more than the rise and fall, swap line for area: it fills to zero, so the filled area is the amount.

43.2 How did several things change?

The gapminder_asia table holds five countries across the same years. One line over all of them would join the five into a single stroke. A categorical color splits the rows into one line per category and colors them at the same time:

gapminder_asia: first 5 of 60 rows
country continent year life population gdp
China Asia 1952 44.00000 556263527 400.4486
China Asia 1957 50.54896 637408000 575.9870
China Asia 1962 44.50136 665770000 487.6740
China Asia 1967 58.38112 754550000 612.7057
China Asia 1972 63.11888 862030000 676.9001
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.

“Given gapminder Asia: lines, x is year, y is life, color by country.”

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

43.3 What about a date column?

The six_weeks table records orders by day, and its day column holds calendar dates. The year above is a plain number. A column of calendar dates is temporal, so you get a calendar axis without asking for one. Ticks land on calendar boundaries, and weekly steps land on Mondays:

six_weeks: first 5 of 42 rows
day orders weekday week
2024-03-01 20 Fri Week 1
2024-03-02 23 Sat Week 1
2024-03-03 25 Sun Week 1
2024-03-04 28 Mon Week 1
2024-03-05 30 Tue Week 1
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

“Given the six weeks table: a line, x is day, y is orders.”

You never have to write scale = "time": the column’s type already says it. Scales owns the calendar’s rules: three-month steps that read as quarters, and a midnight tick labeled with the day’s name.

43.4 What happened, and what comes next?

The actuals table ends in 2023, and the forecast table holds the three years after it. The two belong on one plot, with the actuals and the forecast drawn as different marks. The first data() is the plot’s table; a later one applies to the mark written after it.

forecast: all 3 rows
year sales
2024 180
2025 195
2026 210
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 line ends at 2023, the dots start at 2024, and each layer reads its own rows. Data owns multi-table plots.

43.5 What this section refuses

You may want a scatter of wealth against life expectancy for each year, to compare the years side by side. One panel per year is faceting, but a facet variable names its panels, and year here is a number. The refusal says what to do with a year you mean as a label:

data(gapminder_asia) + point + x(gdp) + y(life) | facet(year)
data(gapminder_asia) + point + x(col.gdp) + y(col.life) | facet(col.year)
data(gapminder_asia) + point + x(:gdp) + y(:life) | facet(:year)
plot(data(gapminder_asia), point, x(col.gdp), y(col.life),
  across(col.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.