2

I have simple plot:

sample_data <-
  data.frame(
    x = 1:100
    , y = 1:100
  )

temp_plot <-
  ggplot(sample_data
         , aes(x = x
               , y = y)) +
  geom_line(
    size = 3
    , arrow = arrow()
    , lineend = "round"
    , linejoin = "round"
  ) +
  theme_minimal()

that looks like this:

enter image description here

And I want to animate it with gganimate like so:

temp_animate <-
  temp_plot +
  transition_reveal(x)

anim_save("temp_animate.gif"
          , temp_animate
          , "~/Downloads/"
          , end_pause = 10)

However, when I do, the arrow is pointing the wrong direction right up until the very last frame (paused to make it clear that it is correct at that point).

enter image description here

I've tried playing with the values in arrow (including various angles, including negative) but nothing that I do seems to correct the orientation of the arrow (which should point along the current vector in each frame).

How can I get the arrow to point in the correct direction throughout? (I am cross-posting this as an issue in the github directory).

Mark Peterson
  • 9,370
  • 2
  • 25
  • 48
  • 2
    I think your example might be missing a line of code or two, because I don't see how `temp_plot` becomes `temp_animate` anywhere... – Z.Lin Sep 26 '20 at 08:08
  • Thanks, @Z.Lin. I missed that when I was copy-pasting. It is just adding `transition_reveal(x)`, and it is now in the question. – Mark Peterson Sep 27 '20 at 12:00

1 Answers1

4

Explanation

This phenomenon arises because transition_reveal tweens values to get the transition position (where the arrowhead is located) in each frame. Whenever the calculated transition position coincides with an actual point on the dataset, there would be two sets of coordinates for the same location. This results in the reversed arrow.

(In your example, the arrow is reversed all the way because the default number of frames is the same as the number of rows in your data, so each calculated transition position is a duplicate of an existing data point. If the frame number is some other number, e.g. 137, the arrow would reverse in some frames & point straight in others.)

We can demonstrate this phenomenon with a smaller dataset:

p <- ggplot(data.frame(x = 1:4, y = 1:4),
            aes(x, y)) +
  geom_line(size = 3, arrow = arrow(), lineend = "round", linejoin = "round") +
  theme_minimal() +
  transition_reveal(x)

animate(p + ggtitle("4 frames"), nframes = 4, fps = 1) # arrow remains reversed till the end
animate(p + ggtitle("10 frames"), nframes = 10, fps = 1) # arrow flips back & forth throughout

4 frames

10 frames

Workaround

The key function here is expand_data from the ggproto object TransitionReveal. I wrote a modified version that adds a check for duplicated positions before returning the expanded dataset:

TransitionReveal2 <- ggproto(
  "TransitionReveal2", TransitionReveal,
  expand_panel = function (self, data, type, id, match, ease, enter, exit, params, 
                           layer_index) {    
    row_vars <- self$get_row_vars(data)
    if (is.null(row_vars)) 
      return(data)
    data$group <- paste0(row_vars$before, row_vars$after)
    time <- as.numeric(row_vars$along)
    all_frames <- switch(type,
                         point = tweenr:::tween_along(data, ease, params$nframes, 
                                                      !!time, group, c(1, params$nframes),
                                                      FALSE, params$keep_last),
                         path = tweenr:::tween_along(data, ease, params$nframes, 
                                                     !!time, group, c(1, params$nframes),
                                                     TRUE, params$keep_last),
                         polygon = tweenr:::tween_along(data, ease, params$nframes, 
                                                        !!time, group, c(1, params$nframes),
                                                        TRUE, params$keep_last),
                         stop(type, " layers not currently supported by transition_reveal", 
                              call. = FALSE))
    all_frames$group <- paste0(all_frames$group, "<", all_frames$.frame, ">")
    all_frames$.frame <- NULL
    
    # added step to filter out transition rows with duplicated positions
    all_frames <- all_frames %>%
      filter(!(.phase == "transition" &
                 abs(x - lag(x)) <= sqrt(.Machine$double.eps) &
                 abs(y - lag(y)) <= sqrt(.Machine$double.eps)))
    
    all_frames
  }
)

We can define an alternate version of transition_reveal that uses the above instead:

transition_reveal2 <- function (along, range = NULL, keep_last = TRUE) {
  along_quo <- enquo(along)
  gganimate:::require_quo(along_quo, "along")
  ggproto(NULL, TransitionReveal2, # instead of TransitionReveal
          params = list(along_quo = along_quo, range = range, keep_last = keep_last))
}

Demonstrate with the original data:

temp_plot + transition_reveal2(x)

100 frames

Z.Lin
  • 28,055
  • 6
  • 54
  • 94
  • It still isn't clear to me *why* the arrow is reversed when there are duplicate coordinates, but it is enough to know that it *is*. I will link to your answer on the Github issue that I opened. Maybe they can incorporate your version of `transition_reveal` in the next update. Thanks! – Mark Peterson Oct 01 '20 at 11:23
  • The arrow reversal reason isn't clear to me either, but at that point it's a `grid` issue rather than a `gganimate` issue, and I really haven't done enough to understand the former. =P – Z.Lin Oct 01 '20 at 11:33