From static to motion: Animated charts

Lecture 19

Dr. Benjamin Soltoff

Cornell University
INFO 3312/5312 - Spring 2026

April 7, 2026

Announcements

Announcements

  • Mini-project oral exams
  • Project 02 proposals
  • Homework 05

Learning objectives

  • Identify rationale for animated data visualizations
  • Define the grammar of animation
  • Review the major components of the grammar of animation
  • Implement an animated beeswarm plot using {gganimate}

Animation

Philosophy

  • The purpose of interactivity is to display more than can be achieved with persistent plot elements, and to invite the reader to engage with the plot.

  • Animation allows more information to be displayed, but developer keeps control

  • Beware that it is easy to forget what was just displayed, so keeping some elements persistent, maybe faint, can be useful for the reader

{gganimate}

  • {gganimate} extends the grammar of graphics as implemented by {ggplot2} to include the description of animation

  • It provides a range of new grammar classes that can be added to the plot object in order to customize how it should change with time

How does {gganimate} work?

  • Start with a {ggplot2} specification

  • Add layers with graphical primitives (geoms)

  • Add formatting specification

  • Add animation specification

A simple example

ggplot(
  data = babynames |> filter(name == "Benjamin", sex == "M")
  )

A simple example

ggplot(
  data = babynames |> filter(name == "Benjamin", sex == "M"),
  mapping = aes(x = year, y = n)
  )

A simple example

ggplot(
  data = babynames |> filter(name == "Benjamin", sex == "M"),
  mapping = aes(x = year, y = n)
  ) +
  geom_line()

A simple example

ggplot(
  data = babynames |> filter(name == "Benjamin", sex == "M"),
  mapping = aes(x = year, y = n)
) +
  geom_line() +
  labs(
    x = "Year", y = "Number of births",
    title = "Popularity of 'Benjamin' over time"
  )

A simple example

ggplot(
  data = babynames |> filter(name == "Benjamin", sex == "M"),
  mapping = aes(x = year, y = n)
) +
  geom_line() +
  labs(
    x = "Year", y = "Number of births",
    title = "Popularity of 'Benjamin' over time"
  ) +
  transition_reveal(year)

Grammar of animation

Grammar of animation

  • Transitions: transition_*() defines how the data should be spread out and how it relates to itself across time
  • Views: view_*() defines how the positional scales should change along the animation
  • Shadows: shadow_*() defines how data from other points in time should be presented in the given point in time
  • Entrances/Exits: enter_*()/exit_*() defines how new data should appear and how old data should disappear during the course of the animation
  • Easing: ease_aes() defines how different aesthetics should be eased during transitions

Transitions

How the data changes through the animation.

Function Description
transition_manual() Build an animation frame by frame (no tweening applied).
transition_states() Transition between frames of a plot (like moving between facets).
transition_time() Like transition_states, except animation pacing respects time.
transition_components() Independent animation of plot elements (by group).
transition_reveal() Gradually extends the data used to reveal more information.
transition_layers() Animate the addition of layers to the plot. Can also remove layers.
transition_filter() Transition between a collection of subsets from the data.
transition_events() Define entrance and exit times of each visual element (row of data).

Transitions

Which transition was used in the following animations?

transition_layers()

New layers are being added (and removed) over the dots.

Transitions

Which transition was used in the following animations?

transition_filter()

The data is being filtered across each frame.

Views

How the plot window changes through the animation.

Function Description
view_follow() Change the view to follow the range of current data.
view_step() Similar to view_follow(), except the view is static between transitions.
view_step_manual() Same as view_step(), except view ranges are manually defined.
view_zoom() Similar to view_step(), but appears smoother by zooming out then in.
view_zoom_manual() Same as view_zoom(), except view ranges are manually defined.

Views

Which view was used in the following animations?

view_follow()

Plot axis follows the range of the data.

Shadows

How the history of the animation is shown. Useful to indicate speed of changes.

Function Description
shadow_mark() Previous (and/or future) frames leave permananent background marks.
shadow_trail() Similar to shadow_mark(), except marks are from tweened data.
shadow_wake() Shows a shadow which diminishes in size and/or opacity over time.

Shadows

Which shadow was used in the following animations?

shadow_wake()

The older tails of the points shrink in size, leaving a “wake” behind it.

Shadows

Which shadow was used in the following animations?

shadow_mark()

Permanent marks are left by previous points in the animation.

Entrances and exits

How elements of the plot appear and disappear.

Function Description
enter_appear/exit_disappear() Poof! Instantly appears or disappears.
enter_fade/exit_fade() Opacity is used to fade in or out the elements.
enter_grow/exit_shrink() Element size will grow from or shrink to zero.
enter_recolor/exit_recolor() Change element colors to blend into the background.
enter_fly/exit_fly() Elements will move from/to a specific x,y position.
enter_drift/exit_drift() Elements will shift relative from/to their x,y position.
enter_reset/exit_reset() Clear all previously added entrace/exits.

Animation controls

How data moves from one position to another.

p + ease_aes({aesthetic} = {ease})
p + ease_aes(x = "cubic")

Deeper dive

A not-so-simple example, the datasaurus dozen

Pass in the dataset to ggplot

ggplot(datasaurus_dozen)

A not-so-simple example, the datasaurus dozen

For each dataset we have x and y values, in addition we can map dataset to color

ggplot(datasaurus_dozen,
       aes(x, y, color = dataset)) 

A not-so-simple example, the datasaurus dozen

Trying a simple scatter plot first, but there is too much information

ggplot(datasaurus_dozen,
       aes(x, y, color = dataset)) +
  geom_point()

A not-so-simple example, the datasaurus dozen

We can use facets to split up by dataset, revealing the different distributions

ggplot(datasaurus_dozen,
       aes(x, y, color = dataset)) +
  geom_point() +
  facet_wrap(facets = vars(dataset)) +
  coord_cartesian(ratio = 1) +
  guides(color = "none")

A not-so-simple example, the datasaurus dozen

We can just as easily turn it into an animation, transitioning between dataset states!

ggplot(datasaurus_dozen,
       aes(x, y)) +
  geom_point() +
  coord_cartesian(ratio = 1) +
  transition_states(dataset, 3, 1) +
  labs(title = "Dataset: {closest_state}")

Tips

Animation options

Sometimes you need more frames, sometimes fewer

  • Save plot object, and use animate() with arguments like
    • nframes: number of frames to render (default 100)
    • fps: framerate of the animation in frames/sec (default 10)
    • duration: length of the animation in seconds (unset by default)
    • etc.
  • In Quarto, specify the arguments to animate() in the chunk options using gganimate
```{r}
#| gganimate: !expr list(nframes = 50, fps = 20)
# add code here
```

Considerations in making effective animations

  • Pace: speed of animation

    Quick animations may be hard to follow. Slow animations are boring and tedious.

  • Perplexity: amount of information

    It is easy for animations to be overwhelming and confusing. Multiple simple animations can be easier to digest.

  • Purpose: Usefulness of using animation

    Is animation needed? Does it provide additional value?

Application exercise

ae-18

Instructions

  • Go to the course GitHub org and find your ae-18 (repo name will be suffixed with your GitHub name).
  • Clone the repo in Positron, run renv::restore() to install the required packages, open the Quarto document in the repo, and follow along and complete the exercises.
  • Render, commit, and push your edits by the AE deadline – end of the day

Wrap-up

Recap

  • Animation is our first step towards dynamic communications using data
  • Provides a controlled, dynamic user experience to engage with the data
  • Avoid animation for the sake of animation
  • Use {gganimate} to animate {ggplot2} charts

Acknowledgements