thomasp85 / gganimate

A Grammar of Animated Graphics
https://gganimate.com
Other
1.94k stars 310 forks source link

broken line at end of animation #502

Open richarddmorey opened 6 days ago

richarddmorey commented 6 days ago

When running the following code, I should get a smooth line starting from x=.5 tracing to x=0, monotonically increasing.

library(ggplot2)
library(gganimate)
library(ragg)
library(gifski)

dplyr::tibble(
  x = seq(0,.5,length.out=256),
  y = 1-pnorm(qnorm(x)/5)
  ) -> df

res = 300
w = 7
h = 4
# If this is run in knitr, w and h are in inches, otherwise
# they are in pixels
anim_w = ifelse(interactive(),w*res,w)
anim_h = ifelse(interactive(),h*res,h)

ggplot(df,aes(x=x,y=y)) +
  geom_line() +
  geom_point() +
  coord_cartesian(
    xlim=c(0,.5),ylim=c(.5,1),expand = FALSE
  ) -> gg

  ggsave(plot = gg, filename = 'test.png', 
         device = ragg::agg_png, dpi = res, width = w, height=h,
         units = 'in')

anim = gg + transition_reveal(x, range = c(.5,0)) 

animate(anim, device='ragg_png', res=res, width=anim_w, height=anim_h,
        renderer = gifski_renderer(file='test.gif',
                                   loop=FALSE))

The plain ggplot shows this clearly:

test

However, the end of the resulting animation appears to be broken:

test

This may be related to https://github.com/thomasp85/gganimate/issues/486. However, I did try version 1.0.7 and the dev version (1.0.9.9) and they all seemed to show the issue, so I'm not sure.

Edited to remove second apparently unrelated issue


> xfun::session_info()
R version 4.3.2 (2023-10-31)
Platform: aarch64-apple-darwin20 (64-bit)
Running under: macOS Sonoma 14.7, RStudio 2023.6.1.524

Locale: en_US.UTF-8 / en_US.UTF-8 / en_US.UTF-8 / C / en_US.UTF-8 / en_US.UTF-8

Package version:
  class_7.3.22         classInt_0.4.10      cli_3.6.3            colorspace_2.1-1    
  compiler_4.3.2       cpp11_0.5.0          crayon_1.5.3         DBI_1.2.3           
  dplyr_1.1.4          e1071_1.7.16         fansi_1.0.6          farver_2.1.2        
  generics_0.1.3       gganimate_1.0.9.9000 ggplot2_3.5.1        gifski_1.12.0-2     
  glue_1.8.0           graphics_4.3.2       grDevices_4.3.2      grid_4.3.2          
  gtable_0.3.5         hms_1.1.3            isoband_0.2.7        KernSmooth_2.23.22  
  labeling_0.4.3       lattice_0.22.5       lifecycle_1.0.4      lpSolve_5.6.21      
  magrittr_2.0.3       MASS_7.3.60          Matrix_1.6.3         methods_4.3.2       
  mgcv_1.9.0           munsell_0.5.1        nlme_3.1.163         pillar_1.9.0        
  pkgconfig_2.0.3      prettyunits_1.2.0    progress_1.2.3       proxy_0.4.27        
  R6_2.5.1             ragg_1.3.3           RColorBrewer_1.1.3   Rcpp_1.0.13         
  renv_1.0.9           rlang_1.1.4          rstudioapi_0.15.0    s2_1.1.7            
  scales_1.3.0         sf_1.0.17            splines_4.3.2        stats_4.3.2         
  stringi_1.8.4        systemfonts_1.1.0    textshaping_0.3.7    tibble_3.2.1        
  tidyselect_1.2.1     tools_4.3.2          transformr_0.1.5     tweenr_2.0.3        
  units_0.8.5          utf8_1.2.4           utils_4.3.2          vctrs_0.6.5         
  viridisLite_0.4.2    withr_3.0.1          wk_0.9.3             xfun_0.47  
richarddmorey commented 5 days ago

The first issue appears to be caused by the call to ggplot's compute_geom_1 function at https://github.com/thomasp85/gganimate/blob/b46d1ff9bfee89c33ca8d6c4fd3fa1074c649027/R/plot-build.R#L76

The data is in the correct order prior to this, but after this call, the rows in each frame are sorted in the opposite order to what the should be. In this case, since the movement in the x direction is negative, the rows should be sorted in descending order. However, this function sorts them in ascending order so the order of the points is wrong in each frame.

The only workarounds that I see is to make as many frames as there are rows in the data (obviously with thousands of rows this would be prohibitive) or to detect the order before calling the function, then fix it after calling. However, I don't know enough about the internal workings of gganimate or ggplot2 to do this without possibly breaking something else!

Edited to add:

To confirm this is the case, I did the following:

  1. debug(gganimate:::ggplot_build.gganim)
  2. Step to after the problematic line 76 above
  3. Run this to sort the rows properly:
    data <- lapply(data, 
    function(el){
        el[order(el$x, decreasing = TRUE),]
    })
  4. Continue

This results in the following correct plot: test

richarddmorey commented 3 days ago

If you're reading this and facing a similar issue to mine, the code below (or something like it) demonstrates a temporary work-around with version 1.0.9 and 1.0.9.9000 (line numbers in the trace call might have to be changed if that function changes in a subsequent version):

Basically, it inserts code into the gganimate method that builds the animation that sorts the x aesthetic in descending order, so that the plot renders correctly. This is super hacky and I would not expect it to work generally (so YMMV).


# Define code to be inserted
e <- quote(data <- lapply(data, function(el){
  if(is.null(el$x)) # Assuming x is the aesthetic that needs sorted
    return(el)
  el[order(el$x, decreasing = TRUE),] # Sort the aesthetic correctly
})
)

# Insert the code
trace(gganimate:::ggplot_build.gganim, e, at = 31, print = FALSE)

## RENDER THE ANIMATION HERE
# ...
##

# Revert to the gganimate's version of the function 
untrace(gganimate:::ggplot_build.gganim)

Thanks to G. Grothendieck on Stack Overflow for suggesting trace: https://stackoverflow.com/a/79057552/1129889