Mastodon

gganimate has transitioned to a state of release

announcement
animation
gganimate
ggplot2
package
visualization

January 3, 2019

Just to start of the year in a positive way, I’m happy to announce that gganimate is now available on CRAN. This release is the result of a pretty focused development starting in the spring 2018 prior to my useR keynote about it.

Some History

The gganimate package has been around for quite some time now with David Robinson making the first commit in early 2016. David’s vision of gganimate revolved around the idea of frame-as-an-aesthetic and this easy-to-grasp idea gave it an early success. The version developed by David never made it to CRAN, and as part of ramping down his package development he asked me if I was interested in taking over maintenance. I was initially reluctant because I wanted a completely different API, but he insisted that he supported a complete rewrite. The last version of gganimate as maintained by David is still available but I very quickly made some drastic changes:

While this commit was done in the autumn 2017, nothing further happened until I decided to make gganimate the center of my useR 2018 keynote, at which point I was forced (by myself) to have some sort of package ready by the summer of 2018.

A fair amount of users have shown displeasure in the breaking changes this history has resulted in. Many blog posts have already been written focusing on the old API, as well as code on numerous computers that will no longer work. I understand this frustration, of course, but both me and David agreed that doing it this way was for the best in the end. I’m positive that the new API has already greatly exceeded the mind-share of the old API and given a year the old API will be all but a distant memory…

The Grammar

Such drastic breaking changes were required because of a completely different vision for how animation fitted into the grammar of graphics. Davids idea was that it was essentially a third dimension in the graphic and the animation was simply flipping through slices along the third dimension in the same way as you would look through the output of a CT scan. Me, on the other hand, wanted a grammar that existed in parallel to the grammar of graphics — not as part of it.

My useR keynote goes in to a lot of detail about my motivation and inspiration for taking on this approach, and I’ll not rehash it in this release post. Feel free to take a 1h break from reading as you watch the talk

The gist of it all is that animations are a multifaceted beast and requires much more than an additional aesthetic to be tamed. One of the cornerstones of the talk is the separation of animations into scenes and segues. In short, a segue is an animated change in the underlying laws of the graphic (e.g. changes to coordinate systems, scales, mappings, etc.), whereas a scene is a change in the data on display. Scenes are concerned with what and segues are concerned with how. This separation is important for several reasons: It gives me a natural focus area for the current version of gganimate (scenes), it serves as a theoretical backbone to group animation operation, and it is a central limit in animation good practices: “You should never change how and what at the same time”.

So, the version I’m presenting here is a grammar of animation uniquely focused on scenes. This does not mean that I’ll never look into segues, but they are both much harder, and less important than getting a scene grammar to make sense, so segues have to play second fiddle for now.

What’s in a scene

There are two main components to a scene: What we are looking at, and where we are looking from. The former is handled by transitions and shadows, whereas the latter is handled by views. In brief:

  • transitions populates the frames of the animation with data, based on the data assigned to each layer. Several different transitions exists that interpret the layer data differently.
  • shadows gives memory to each frame by letting each frame include data from prior or future frames.
  • views allow you to modify the range of the positional scales (zoom and pan) either directly or as a function of the data assigned to the frame.

On top of these three main grammar components there is a range of functions to modify how key parts of animations behave — for a general introduction to the ins and outs of the API, please see the *Getting Started** guide.

Grammar vs API

While it may appear that grammar and API are the same, this is not the case. A grammar is a theoretical construct, a backbone from which an API can be defined. Several APIs could implement the same grammar in multiple, incompatible, ways. For gganimate I have tried to align the API as much as possible with the ggplot2 API, so that the line between the two packages becomes blurred. You change a plot to an animation by adding functions from gganimate to it, and the animation is rendered when printing the animation object in the same way as ggplots are rendered when printing the object. An example of this is adding transition_reveal() to a plot to make it appear gradually along a numeric variable:

library(ggplot2)
library(gganimate)

ggplot(airquality) + 
  geom_line(aes(x = Day, y = Temp, group = Month))

last_plot() + 
  transition_reveal(Day)

For the most part, the marriage between the ggplot2 and gganimate APIs is a happy one, though it does show at points that the ggplot2 API was never designed with animation in mind. I am particularly pleased with how powerful the API has turned out, and I have already seen countless uses I had never anticipated.

Making Fireworks

While a proper introduction to its use is better kept for a separate document (such as the Getting Started guide mentioned earlier), I think I would do gganimate a disservice by not showing of at least a single fully fledged example. Below is the code needed to make fireworks with gganimate:

# Firework colours
colours <- c(
  'lawngreen',
  'gold',
  'white',
  'orchid',
  'royalblue',
  'yellow',
  'orange'
)
# Produce data for a single blast
blast <- function(n, radius, x0, y0, time) {
  u <- runif(n, -1, 1)
  rho <- runif(n, 0, 2*pi)
  x <- radius * sqrt(1 - u^2) * cos(rho) + x0
  y <- radius * sqrt(1 - u^2) * sin(rho) + y0
  id <- sample(.Machine$integer.max, n + 1)
  data.frame(
    x = c(x0, rep(x0, n), x0, x),
    y = c(0, rep(y0, n), y0, y),
    id = rep(id, 2),
    time = c((time - y0) * runif(1), rep(time, n), time, time + radius + rnorm(n)),
    colour = c('white', rep(sample(colours, 1), n), 'white', rep(sample(colours, 1), n)),
    stringsAsFactors = FALSE
  )
}
# Make 20 blasts
n <- round(rnorm(20, 30, 4))
radius <- round(n + sqrt(n))
x0 <- runif(20, -30, 30)
y0 <- runif(20, 40, 80)
time <- runif(20, max = 100)
fireworks <- Map(blast, n = n, radius = radius, x0 = x0, y0 = y0, time = time)
fireworks <- dplyr::bind_rows(fireworks)

All of the above is just data preparation. blast() simply creates segments from the center of the blast and out to the periphery, sampling colours from the colour vector. The end result, if plotted statically, looks like this:

ggplot(fireworks) + 
  geom_path(aes(x = x, y = y, group = id, colour = colour)) + 
  scale_colour_identity()

Now, to make it all move, as well as style it a bit for a better effect

ggplot(fireworks) + 
  geom_point(aes(x, y, colour = colour, group = id), size = 0.5, shape = 20) + 
  scale_colour_identity() + 
  coord_fixed(xlim = c(-65, 65), expand = FALSE, clip = 'off') +
  theme_void() + 
  theme(plot.background = element_rect(fill = 'black', colour = NA), 
        panel.border = element_blank()) + 
  # Here comes the gganimate code
  transition_components(time, exit_length = 20) + 
  ease_aes(x = 'sine-out', y = 'sine-out') + 
  shadow_wake(0.05, size = 3, alpha = TRUE, wrap = FALSE, 
              falloff = 'sine-in', exclude_phase = 'enter') + 
  exit_recolour(colour = 'black')

While I wont go into detail, transition_component() allow all the points to follow their own trajectory and timeline independently, ease_aes() ensures that the velocity of the points taper off, shadow_wake() is responsible for the trail after each point, and exit_recolour() makes sure the points gradually fades into the black background once they “burn out”.

The Future

While this release is a milestone for gganimate, it is not a signal of it being done as many things are still missing (even if we ignore the whole segue part of the grammar). It does signal a commitment to stability from now on, though so you should feel confident in using this package without fearing that your code will break in the future. You can follow the state of the package at its website, <www.gganimate.com>, where I’ll also try to add additional guides and tutorials with time. If you create something with gganimate please share it on twitter, as I’m eager to see what people will make of it.

I’ll do a sort-of live cookbook talk on gganimate at this years RStudio conf in Austin, so if you are there and interested to learn more about the package do swing by.

Now, Go Animate!