Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animated plot with a moving facet zoom via gganimate and ggforce?

enter image description here

Goal

I would like to zoom in on the GDP of Europe throughout the years. The phantastic ggforce::facet_zoom allows this for static plots (i.e., for one specific year) very easily.

Moving scales, however, prove harder than expected. gganimate seems to take the x-axis limits from the first frame (year == 1952) and continute until the end of the animation. This related, but code-wise outdated question did not yield an answer, unfortunately. Neither + coord_cartesian(xlim = c(from, to)), nor facet_zoom(xlim = c(from, to)) seems to be able to influence the facet_zoom window beyond static limits.

  • Is there any way to make gganimate 'recalculate' the facet_zoom scales for every frame?

Ideal result

First frame

2

Last frame

3

Current code
library(gapminder)
library(ggplot2)
library(gganimate)
library(ggforce)
p <- ggplot(gapminder, aes(gdpPercap, lifeExp, size = pop, color = continent)) +
    geom_point() + scale_x_log10() +
    facet_zoom(x = continent == "Europe") +
    labs(title = "{frame_time}") +
    transition_time(year) 

animate(p, nframes = 30)
like image 332
Roman Avatar asked Oct 17 '18 16:10

Roman


1 Answers

I don't think it's possible quite yet with the current dev version of gganimate as of Dec 2018; there seem to be some bugs which prevent facet_zoom from playing nice with gganimate. Fortunately, I don't think a workaround is too painful.

First, we can tween to fill in the intermediate years:

# Here I tween by fractional years for more smooth movement
years_all <- seq(min(gapminder$year), 
                 max(gapminder$year), 
                 by = 0.5)

gapminder_tweened <- gapminder %>%
  tweenr::tween_components(time = year, 
                           id   = country, 
                           ease = "linear", 
                           nframes = length(years_all))

Then, adopting your code into a function that takes a year as input:

render_frame <- function(yr) {
  p <- gapminder_tweened %>%
    filter(year == yr) %>%
    ggplot(aes(gdpPercap, lifeExp, size = pop, color = continent)) +
    geom_point() +
    scale_x_log10(labels = scales::dollar_format(largest_with_cents = 0)) +
    scale_size_area(breaks = 1E7*10^0:3, labels = scales::comma) +
    facet_zoom(x = continent == "Europe") +
    labs(title = round(yr + 0.01) %>% as.integer) 
    # + 0.01 above is a hack to override R's default "0.5 rounds to the
    #   closest even" behavior, which in this case gives more frames
    #   (5 vs. 3) to the even years than the odd years
  print(p) 
}  

Finally, we can save an animation by looping through through the years (which in this case include fractional years):

library(animation)
oopt = ani.options(interval = 1/10)
saveGIF({for (i in 1:length(years_all)) {
  render_frame(years_all[i])
  print(paste0(i, " out of ",length(years_all)))
  ani.pause()}
},movie.name="facet_zoom.gif",ani.width = 400, ani.height = 300) 

or, alternatively, using gifski for a smaller file <2MB:

gifski::save_gif({ for (i in 1:length(years_all) {
  render_frame(years_all[i])
  print(paste0(i, " out of ",length(years_all)))
}
},gif_file ="facet_zoom.gif", width = 400, height = 300, delay = 1/10, progress = TRUE) 

(When I have more time, I'll try to remove the distracting changes in the legends by using manually specified breaks.)

enter image description here

like image 199
Jon Spring Avatar answered Nov 09 '22 02:11

Jon Spring