【问题标题】:Using a gtable object with gganimate将 gtable 对象与 gganimate 一起使用
【发布时间】:2019-08-17 04:18:29
【问题描述】:

我正在使用gganimate 制作一个 gif,我想对绘图格式进行一些调整,这只能通过将 ggplot 对象转换为 gtable 来完成。例如,我想更改情节标题的位置,使其始终出现在情节的最左角。

以下是情节调整的示例:

library(ggplot2)
library(gganimate)
library(dplyr)

# Helper function to position plot title all the way to left of plot
align_titles_left <- function(p, newpage = TRUE) {
  p_built <- invisible(ggplot2::ggplot_build(p))
  gt <- invisible(ggplot2::ggplot_gtable(p_built))
  
  gt$layout[which(gt$layout$name == "title"), c("l", "r")] <- c(2, max(gt$layout$r))
  gt$layout[which(gt$layout$name == "subtitle"), c("l", "r")] <- c(2, max(gt$layout$r))
  
  
  # Prints the plot to the current graphical device
  # and invisibly return the object
  gridExtra::grid.arrange(gt, newpage = newpage)
  invisible(gt)
}

# Create an example plot
static_plot <- iris %>% 
  ggplot(aes(x = Sepal.Length, y = Sepal.Width,
             color = Species)) +
  geom_point() +
  labs(title = "This title should appear in the far left.")

# Print the static plot using the adjustment function
align_titles_left(static_plot)

如何在gganimate 中使用此功能?

这里有一些示例 gganimate 代码,用于将此示例中的情节转换为动画。

# Produce the animated plot
static_plot +
  transition_states(Species,
                    transition_length = 3,
                    state_length = 1)

【问题讨论】:

    标签: r ggplot2 gganimate


    【解决方案1】:

    这是结果。解释如下:

    任何对 grobs 的修改都应动画情节的各个帧被创建之后,但在它们被绘制到相关的图形设备上之前进行。此窗口出现在gganimate:::Sceneplot_frame 函数中。

    我们可以定义我们自己的 Scene 版本,它继承自原始版本,但使用修改后的 plot_frame 函数并插入了 grob hack 行:

    Scene2 <- ggproto(
      "Scene2",
      gganimate:::Scene,
      plot_frame = function(self, plot, i, newpage = is.null(vp), 
                            vp = NULL, widths = NULL, heights = NULL, ...) {
        plot <- self$get_frame(plot, i)
        plot <- ggplot_gtable(plot)
    
        # insert changes here
        plot$layout[which(plot$layout$name == "title"), c("l", "r")] <- c(2, max(plot$layout$r))
        plot$layout[which(plot$layout$name == "subtitle"), c("l", "r")] <- c(2, max(plot$layout$r))
    
        if (!is.null(widths)) plot$widths <- widths
        if (!is.null(heights)) plot$heights <- heights
        if (newpage) grid::grid.newpage()
        grDevices::recordGraphics(
          requireNamespace("gganimate", quietly = TRUE),
          list(),
          getNamespace("gganimate")
        )
        if (is.null(vp)) {
          grid::grid.draw(plot)
        } else {
          if (is.character(vp)) seekViewport(vp)
          else pushViewport(vp)
          grid::grid.draw(plot)
          upViewport()
        }
        invisible(NULL)
      })
    

    此后,我们必须在动画过程中将Scene替换为我们的版本Scene2。我在下面列出了两种方法:

    1. 定义一个单独的动画函数animate2,以及使用Scene2而不是Scene所需的中间函数。在我看来,这更安全,因为它不会改变 gganimate 包中的任何内容。但是,它确实涉及更多代码,并且如果函数定义在源头发生更改,将来可能会中断。

    2. 覆盖gganimate 包中的现有函数为此会话(基于答案here)。这需要在每个会话中进行手动操作,但是所需的实际代码更改非常小,并且可能不会那么容易中断。但是,它也存在使用户感到困惑的风险,因为相同的函数可能会导致不同的结果,这取决于它是在更改之前还是之后调用的。

    方法 1

    定义函数:

    library(magrittr)
    
    create_scene2 <- function(transition, view, shadow, ease, transmuters, nframes) {
      if (is.null(nframes)) nframes <- 100
      ggproto(NULL, Scene2, transition = transition, 
              view = view, shadow = shadow, ease = ease, 
              transmuters = transmuters, nframes = nframes)
    }
    
    ggplot_build2 <- gganimate:::ggplot_build.gganim
    body(ggplot_build2) <- body(ggplot_build2) %>%
      as.list() %>%
      inset2(4,
             quote(scene <- create_scene2(plot$transition, plot$view, plot$shadow, 
                                          plot$ease, plot$transmuters, plot$nframes))) %>%
      as.call()
    
    prerender2 <- gganimate:::prerender
    body(prerender2) <- body(prerender2) %>%
      as.list() %>%
      inset2(3,
             quote(ggplot_build2(plot))) %>%
      as.call()
    
    animate2 <- gganimate:::animate.gganim
    body(animate2) <- body(animate2) %>%
      as.list() %>%
      inset2(7,
             quote(plot <- prerender2(plot, nframes_total))) %>%
      as.call()
    

    用法:

    animate2(static_plot +
               transition_states(Species,
                                 transition_length = 3,
                                 state_length = 1))
    

    方法2

    在控制台中运行trace(gganimate:::create_scene, edit=TRUE),并在弹出的编辑窗口中将Scene更改为Scene2

    用法:

    animate(static_plot +
              transition_states(Species,
                                transition_length = 3,
                                state_length = 1))
    

    (两种方法的结果相同。)

    【讨论】:

    • 这太不可思议了!非常感谢您的 cmets 关于这两种方法的优缺点。方法 1 似乎是最安全的,它可以通过将该代码存放在帮助脚本(例如“Custom_Animation_Functions.R”)中并从主脚本(例如“Produce_Animated_Plots.R”)中获取它来实现。
    猜你喜欢
    • 2023-03-17
    • 1970-01-01
    • 1970-01-01
    • 2016-05-24
    • 2012-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多