0

I'm trying to create an animated GIF using tmap and display it in my Shiny app. When I use tm_shape() + tm_polygons() for a single date, the image produced is always OK. However, when I use tm_facets() and feed the result to tmap_animation, the resulting GIF has random dark green frames, as per the below.enter image description here

Here is the code I am using the generate the animation:

data(World)

confirmed = read.csv('./data/time_series_covid_19_confirmed.csv', stringsAsFactors = T) %>%
  select(-Province.State, -Country.Region) %>%
  pivot_longer(!c('Lat', 'Long', 'iso3'), names_to = 'date', values_to = 'confirmed') %>%
  mutate(date = as.Date(gsub('X', '', date), '%m.%d.%Y')) %>%
  group_by(iso3, date) %>%
  summarise(confirmed = sum(confirmed)) %>%
  mutate(perc_change = 100 * ifelse(
      lag(confirmed, default = 0) == 0 | confirmed < 1000 | lag(confirmed) > confirmed, 
      0, 
      (confirmed - lag(confirmed)) / lag(confirmed)
    )
  ) %>%
  inner_join(select(World, iso_a3, geometry), by=c('iso3' = 'iso_a3')) %>%
  st_sf()

conf_anim = confirmed %>%
  filter(date < '2020-02-28') %>%
  tm_shape() + tm_polygons('perc_change', style='cont') +
  # tm_fill('perc_change', palette='Blues', style='fixed', 
  #         breaks=c(0, 5, 10, 15, 20, 25, 30, 35, 40, 45, Inf)) + tm_borders() +
  tm_facets(along='date', free.coords = F)

tmap_animation(conf_anim, filename = './www/conf_anim.gif', delay=50)

Anyone know how I could fix this?

Jay
  • 45
  • 1
  • 7

2 Answers2

1

I had the same issue. I asked about it on the tmap GitHub issues tab and the team traced it to an issue with the gifski package tmap uses to generate gif animations. I found the same thing was happening for animations made with gifski without using tmap.

The workaround mtennekes suggested for now was to save the animation as an mp4 rather than gif, which uses the av package:

tmap_animation(conf_anim, filename = './www/conf_anim.mp4', delay=50)

Not a full fix, but hope it helps for your immediate application.

G-Lomax
  • 61
  • 6
  • +1 for a faster method. I posted an answer below that allows you to generate a gif using gifski and tmap which I have marked as the correct answer, as the original question was how to generate a gif and because I'm shameless XD – Jay Mar 11 '21 at 18:32
  • 1
    FYI i'm not convinced that the issue is with gifski - setting the width and height in gifski::save_gif to the values of a png generated by tmap_save solved the issue. Maybe this has something to do with resizing? – Jay Mar 11 '21 at 18:33
  • @Jay Interesting. I ran the example code for the gifski package (generating a ggplot animation using gapminder data) and it also contained green frames, so to me that suggests that at least the problem is not in tmap. Glad you worked out a solution for generating a GIF! – G-Lomax Mar 12 '21 at 11:30
0

In case anyone else is struggling with this, I was able to create a workaround using tmap, gifski (which tmap uses to generate GIFs), dplyr, and the png library. This method is NOT fast and generating a GIF this way may take a long time. Another answer mentioned using an MP4 format instead of GIF - this method is far faster if that is an option for you.

Assuming you have an sf object named df_sf which you are trying to facet along facet_along_col_str and the first argument to tm_polygons is polygon_col_str, here is code that will help you generate a GIF:

library(tmap)
library(gifski)
library(png)
library(dplyr)

generate_gif = function(sf_df, facet_along_col_str, polygon_col_str, gif_file, delay) {
  dims = get_dims(sf_df, facet_along_col_str, polygon_col_str)
  save_gif(get_maps(sf_df), height=dims[1], width=dims[2], delay=delay, gif_file=gif_file)
}

get_dims = function(sf_df, facet_along, polygon_col) {
  slice = st_drop_geometry(select(sf_df, !!as.symbol(map_slice_col)))[[1]][1]
  t = tm_shape(filter(sf_df, !!as.symbol(map_slice_col) == slice)) +
    tm_polygons(polygons_col)
  filename = tempfile(fileext='.png')
  tmap_save(t, filename=filename)
  dims = dim(readPNG(filename))
  file.remove(filename)

  return(dims)
}

get_maps = function(sf_df, facet_along, polygon_col) {
  slices = st_drop_geometry(select(sf_df, !!as.symbol(map_slice_col)))[[1]]
  slices = sort(unique(slices))
  for (i in 1:length(slices)) {
    t = tm_shape(filter(sf_df, !!as.symbol(facet_along) == slices[i])) +
      tm_polygons(polygon_col)
    print(t)
  }
}
Jay
  • 45
  • 1
  • 7