---
title: "How the animation works"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{How the animation works}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  dev = "ragg_png",
  dpi = 100,
  fig.width = 7.36,
  fig.height = 4.2,
  out.width = "100%"
)
```

```{r setup}
library(ggextreme)
```

Most of the difference between an animation that reads well and one that does
not comes down to three decisions: what is interpolated, what is not, and
whether the geometry holds still. This vignette sets out the choices
`ggrace()` makes, since they are the parts worth arguing with.

## Values move at a constant rate

Observations are keyframes. `ggrace()` lays a uniform grid of `duration * fps`
time points across the observed range and interpolates each entity's value
linearly onto it.

Two things follow. Bars grow at a steady rate rather than easing in and out
of every observation, which would read as a pulse once per time step. And the
grid is uniform in real time, not in keyframes, so unevenly spaced
observations play at their true relative speed instead of each gap taking the
same number of frames.

```{r}
race <- ggrace(clefts_qci, qci, country, year, top_n = 15,
               duration = 15, fps = 20)

range(diff(race$times))
```

An entity that is absent at some time points is bridged linearly between the
observations that do exist. One that is absent at the ends holds its nearest
observed value, and is kept out of the ranking while it is missing.

## Rank does not move at a constant rate

Rank is treated differently, and this is the decision that most affects how
the animation reads. Interpolating rank linearly between two observations
puts every overtaking bar in motion for the whole time step, so a field of
any size is permanently drifting and the labels never settle.

Instead, every frame is ranked on its own interpolated values, which gives a
step function per entity, and the drawn position eases from the old rank to
the new one over `swap` seconds with a cubic in and out curve. Bars therefore
rest in place and change positions in one short, deliberate move.

```{r}
kenya <- subset(race$frames, name == "Kenya")
plot(kenya$time, -kenya$rank, type = "l", lwd = 2, col = "#22928F",
     xlab = "year", ylab = "position", yaxt = "n",
     main = "One country's position through the race")
axis(2, at = -(1:15), labels = 1:15, las = 1)
```

The flat sections are the bar sitting still; the short ramps are the swaps.
Shorten `swap` for a snappier chart, lengthen it for a slower one.

```{r}
sum(abs(diff(kenya$rank)) > 1e-8)  # frames spent moving
nrow(kenya)                        # frames in total
```

## Ranks are clamped just below the visible window

Anything outside the top `top_n` is parked at rank `top_n + 1`, one slot below
the bottom edge. An entity in twentieth place that climbs into a top ten
therefore enters from just under the axis rather than flying up from far off
screen, and the same clamp handles entities that are missing from the data at
a given time.

```{r}
top5 <- ggrace(clefts_qci, qci, country, year, top_n = 5, duration = 15)
range(top5$frames$rank)
```

## The geometry never moves

A frame is a single `ggplot` drawn in card units: the title, axis, bars,
timeline and footer are all layers in one coordinate system, positioned by
number rather than by a layout engine.

That is deliberate. If the bar labels were axis text, the width of the label
column would depend on which entities happen to be visible, so the panel
would shift sideways by a few pixels whenever the longest name entered or
left the top `n`. Over a few hundred frames that shift is a visible jitter.
Here the label column has a fixed width and the panel edges are constants.

The axis carries no headroom. The longest bar always reaches the right edge
of the plotting area and the axis maximum is the largest value in the current
frame, so gridlines drift as the field grows.

Colors are assigned once across the whole field, not per frame, so an entity
keeps its color when it drops out of the visible window and comes back.

```{r}
head(race$colors, 4)
```

## What a frame is made of

Each frame is five or six layers, not one per element. Collapsing the
rectangles into a single `geom_rect()` and the text into a single
`geom_text()` is what keeps a long render from crawling, since a ggplot's
build cost scales with the number of layers.

```{r}
frame <- race_frame(race, 100)
length(frame$layers)
vapply(frame$layers, function(l) class(l$geom)[1], character(1))
```

Because it is a plain `ggplot`, a frame can be modified like any other, with
the caveat that the coordinate system is in card units rather than data
units.

## Cost

Drawing is the expensive part, at roughly a third of a second per frame on
eight cores. A thirty second animation at 30 frames per second is 900 frames,
so a few minutes. `fps` and `duration` are the two knobs that matter;
`width` and `res` change the cost far less than they change the file size.
