rggplot2animated-gifrmagickgganimate

Fix plot position in a Gif - animate plot with dynamic axis labels


I am trying to make a gif(for each frame I want to plot the variable in the y axis, and the value in x axis), but bit struggled to fix the plot position since it will move once the length of the variable in y axis changes.

Here is one reproducible example:

packs <- c("data.table","tidyverse","scales","magick")
lapply(packs,require,character.only=T)

data <- data.table(variable=c(letters[1:6],"agdjfhgbbkf"),
                   values=c(1:7))

data$variable_levels <- fct_reorder(data$variable, data$values)


generate_frame <- function(data, i) {
  
  # create the plot
  p <- ggplot(data[1:i,], aes(x = values, y = variable_levels)) +
    geom_col(fill = "#4285F4", position = position_dodge()) +
    scale_x_continuous(limits = c(0, max(data$values)),labels = scales::comma) +
    scale_y_discrete(limits = rev(levels(data$variable_levels)),
                     labels = function(x) {
                       wrap_format(10)(ifelse(x %in% data$variable_levels[1:i], x, ""))
                     }) +
    # set y-axis limits
    theme_bw() +
    theme(axis.text = element_text(family = "mono"),
          panel.grid.major.y = element_blank(),
          axis.line.y = element_blank(),
          axis.text.y = element_text(hjust = 1, size = 10),
          axis.ticks.y = element_blank(),
          axis.title.y = element_blank(),
          plot.margin = margin(t = 1, r = 1, b = 1, l = 5, unit = "cm"))+
    labs(x="Value",y="",title="")
  
  
  # save the plot as a png file
  ggsave(paste0("frame_", i, ".png"), p, width = 800, height = 400, dpi = 150,units='px')
}

# generate all the frames
for (i in 1:nrow(data)) {
  generate_frame(data, i)
}


# create an empty image list
image_list <- image_blank(width = 800, height = 400)

# read in each frame and add it to the image list
for (i in 1:nrow(data)) {
  frame_file <- paste0("frame_", i, ".png")
  frame_image <- image_read(frame_file)
  image_list <- c(image_list, frame_image)
}
# combine all frames into a GIF and save it
image_write(image_list, "animation.gif", format = "gif")

I have tried to use expand() in scale_y_discrete() , but it does not work.

I would appreciate it if someone can show me how to fix the plot position and define the interval between each frame.

PS: also tried to use gganimate, but not sure how to make the occurence of the variable in y axis dynamically

 library(gganimate)
 library(ggplot2)

ggplot(data, aes(x = variable, y = values)) +
  geom_bar(stat = "identity", position = "dodge") +
  scale_x_continuous(limits = c(0, max(data$values)),labels = scales::comma) +
  scale_y_discrete(limits = rev(levels(data$variable_levels)),
                   labels = function(x) {
                     wrap_format(20)(ifelse(x %in% data$variable_levels, x, ""))
                   }
                   ) +
  # set y-axis limits
  theme_bw() +
  theme(axis.text = element_text(family = "mono"),
        panel.grid.major.y = element_blank(),
        axis.line.y = element_blank(),
        axis.text.y = element_text(hjust = 1, size = 10),
        axis.ticks.y = element_blank(),
        axis.title.y = element_blank())+
  labs(x="Value",y="")+
  transition_states(variable_levels, wrap = F)  +
  shadow_mark()

Solution

  • This is possible with gganimate. If your aim is to get animated axis labels, I don't currently see a way around custom annotation - e.g., axis labels with geom_text and ticks with geom_segment. This is a bit painful, as you need to partially hard code your label positions and also add a margin to your plot. However, you only need to do this once.

    Additionally, you will need shadow_mark.

    You can save the output with anim_save. To specify your image resolution, follow Define size for .gif created by gganimate - change dimension / resolution

    suppressMessages(library(tidyverse))
    library(gganimate)
    
    data <- data.frame(variable = c(letters[1:6], "agdjfhgbbkf"), values = c(1:7))
    
    data$variable_levels <- fct_reorder(data$variable, data$values)
    
    p <-
      ggplot(data, aes(x = values, y = variable_levels)) +
      geom_col(fill = "#4285F4", position = position_dodge()) +
      ## size and x nudge are hard coded - might need adjustment. 
      geom_text(aes(x = 0, label = variable_levels), size = 10 * 5 / 14, hjust = 1, nudge_x = -.1) +
      ## now the ticks with geom_segment
      geom_segment(aes(x = -.05, xend = 0, yend = variable_levels)) +
      scale_x_continuous(expand = c(0, 0), labels = scales::comma) +
      ## need to turn off clipping
      coord_cartesian(clip = "off", xlim = c(0, NA)) +
      theme( ## remove y axis labels and ticks
        axis.text.y = element_blank(),
        axis.ticks.y = element_blank(),
        ## add a margin to y title and to the plot
        plot.margin = margin(l = 60, r = 2)
      ) +
      ## use NULL to remove titles
      labs(x = "Value", y = NULL)
    
    p_anim <- p + transition_states(values) + shadow_mark()
    
    animate(p_anim)
    

    Created on 2023-03-07 with reprex v2.0.2