New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs: Getting started example #226

Open
ptoche opened this Issue Nov 30, 2018 · 30 comments

Comments

Projects
None yet
2 participants
@ptoche
Copy link

ptoche commented Nov 30, 2018

I've been a fan of gganimate. Great package! I am updating several animations I had made with the earlier API... I cannot seem to even get started. I have seen the wiki, but these examples are too advanced and I didn't manage to adapt them right away to my simple needs. I would like to suggest adding simpler examples to the README and/or wiki.

Suggestion: (Help) Create a few simple examples for dummies:

In the reprex below, I suggest 4 or 5 examples that a newbie could use to get started. Please help me use the correct syntax in those cases where I haven't worked it out. Thanks!

Below two basic plots to animate:

library(reprex)
    # Data 
    library(tibble)
    df <- tibble(x = -10:10, y = abs(x)) 
    # Plots
    library(ggplot2)
    ggplot(data = df, aes(x, y)) + 
        geom_point(size = 2)

    ggplot(data = df, aes(x, y)) + 
        geom_line(size = 2)

Created on 2018-12-11 by the reprex package (v0.2.1)

A convenience function to display GIFs side by side (used in the examples below).

    library(reprex)
    
# Combine plots side by side 
combine_gifs <- function(plot1, plot2) {
    require(magick) 
    # read the plots and store them
    plot1 <- image_read(plot1) 
    plot2 <- image_read(plot2) 
    # sync the number of frames in each plot
    n1 = length(plot1)
    n2 = length(plot2)
    # match number of frames of the two plots
    if (!(n1 == n2)) plot1 <- rep(plot1, n2) 
    if (!(n1 == n2)) plot2 <- rep(plot2, n1)
    # initialize the combined plot
    p <- image_append(c(plot1[1], plot2[1]))
    # grow the combined plot frame by frame
    n = ifelse(n1 == n2, n1, n1 * n2) 
    n = min(1000, n)  # set max to 1000
    for (i in 2:(n-1)) {
        tmp <- image_append(c(plot1[i], plot2[i]))
        p <- c(p, tmp)
    }
    return(p) 
}

@ptoche

This comment has been minimized.

Copy link
Author

ptoche commented Nov 30, 2018

Example 1:

Objective: reveal a single point sequentially: reveal (x_{i}, y_{i}) + hide (x_{i-1}, y_{i-1})

    library(reprex)
    # Packages + Data
    library(ggplot2)
    library(gganimate)
    library(tibble)
    df <- tibble(x = -10:10, y = abs(x)) 
    # Plots
    p1 <- ggplot(data = df, aes(x, y)) + 
        geom_point(size = 5) + 
        transition_manual(x) +
        labs(title = "transition_manual(x)")
    p1 <- animate(p1)  
#> nframes and fps adjusted to match transition
    p2 <- ggplot(data = df, aes(x, y)) + 
        geom_point(size = 5) + 
        transition_manual(y) +
        labs(title = "transition_manual(y)")
    p2 <- animate(p2)  
#> nframes and fps adjusted to match transition
    combine_gifs(plot1 = p1, plot2 = p2) 
#> Loading required package: magick
#> Linking to ImageMagick 6.9.9.39
#> Enabled features: cairo, fontconfig, freetype, lcms, pango, rsvg, webp
#> Disabled features: fftw, ghostscript, x11

Created on 2018-12-11 by the reprex package (v0.2.1)

Verdict: Success.
Comment: To reveal the points in a vertical direction, use transition_manual(y). Very intuitive.

@ptoche ptoche changed the title Getting started example Docs: Getting started example Nov 30, 2018

@ptoche

This comment has been minimized.

Copy link
Author

ptoche commented Nov 30, 2018

Following on from the previous examples, below are attempts to reveal points sequentially while keeping them on the graph. Thanks Thomas for showing me how to achieve this objective.

Example 2:

Objective: reveal all points sequentially: reveal (x_{i}, y_{i}) without hiding (x_{i-1}, y_{i-1})

    # Packages + Data    
    library(reprex)
    library(ggplot2)
    library(gganimate)
    library(tibble)
    df <- tibble(x = -10:10, y = abs(x)) 
    # Plots
    p1 <- ggplot(data = df, aes(x, y, group = seq_along(x))) + 
        geom_point(size = 5) + 
        transition_reveal(x) +
        labs(title = "transition_reveal(x)") 
    p1 <- animate(p1)  
    p2 <- ggplot(data = df, aes(x, y, group = seq_along(x))) + 
        geom_point(size = 5) + 
        transition_reveal(y) + 
        labs(title = "transition_reveal(y)")
    p2 <- animate(p2)  
    combine_gifs(plot1 = p1, plot2 = p2) 
#> Loading required package: magick
#> Linking to ImageMagick 6.9.9.39
#> Enabled features: cairo, fontconfig, freetype, lcms, pango, rsvg, webp
#> Disabled features: fftw, ghostscript, x11

    anim_save("transition_reveal.gif") 

Created on 2018-12-11 by the reprex package (v0.2.1)

Verdict: Success.
Comment: You need group = seq_along(x) in the aes and transition_reveal(). This will take some getting used to.

@ptoche

This comment has been minimized.

Copy link
Author

ptoche commented Dec 10, 2018

The default nframes is 100. This seems like an excessive number of frames for an animation that would be expected to have 21 frames. I wonder if the gifs above can be created with 21 frames by default or if one is expected to override the default manually... Will look into this.

length(-10:10)
## 21 
@thomasp85

This comment has been minimized.

Copy link
Owner

thomasp85 commented Dec 10, 2018

This is only possible for some transitions, e.g. transition_manual... In your case the only reason why it can be done with 21 frames is because there is only one layer and each time point fed into transition_reveal is distributed evenly across the range and no point has the same group aesthetic. I'm not going to create special cases for such things as it is likely to break all the time

@thomasp85

This comment has been minimized.

Copy link
Owner

thomasp85 commented Dec 10, 2018

also, sorry for breaking all your code, but breaking changes to transition_reveal has just been merged in, removing the id argument. You should now use the group aesthetic in each layer to set which elements belongs together

@ptoche

This comment has been minimized.

Copy link
Author

ptoche commented Dec 10, 2018

Thanks Thomas. Will look into this. I'm only just learning the new api, so I don't mind breaking changes.

Is there a way to override nframes from inside, say, transition_manual()? How about fps? Would it be too much work to allow the user to override them? I saw in the code that nframes is set to 100 by default inside create_scene (but if I understand correctly the number of frames also appears as the product of total and detail, set by default to the product of 100 and 1), but I didn't see if it can be accessed.

I haven't updated to the latest version, so let me do that first. But when I tried to set the nframes implicitly by giving the frames option the x vector, the result was that the animation lost its cumulative = TRUE effect (sorry for using old terminology here). Will check the latest update and explore further, but adding transition_manual(frames = x) correctly resulted in 21 frames being produced (instead of 100), but it also removed the "cumulative" effect. As I'm super new, no idea if what I write even makes sense.

ggplot(data = df, aes(x, y)) + 
    geom_point(size = 5) + 
    transition_reveal(x, x) +
    transition_manual(frames = x) 
@thomasp85

This comment has been minimized.

Copy link
Owner

thomasp85 commented Dec 10, 2018

The number of frames is not really something the transitions should worry about unless the data provided does not support the number of frames requested. The idea is that an animation specification is dimensionless and only upon requesting a render is anything determined. It is thus the responsibility of the animate() function to request a number of frames along with a framerate. animate is called implicitly upon printing the animation object, using the defaults (which is 100 frames and 10 fps), but can either be called explicitly with other values or the defaults can be overwritten - see ?animate for more details

@ptoche

This comment has been minimized.

Copy link
Author

ptoche commented Dec 10, 2018

I follow up with a few more attempts at making introductory examples. I'll update the code as I make progress. Comments welcome.

Example 3:

Objective: reveal a single line segment sequentially: show (x_{i}, y_{i}) -- (x_{i-1}, y_{i-1}) + hide (x_{i-1}, y_{i-1}) -- (x_{i-2}, y_{i-2})

Verdict: Thomas says there is no easy way to do this right now... To be continued.

@ptoche

This comment has been minimized.

Copy link
Author

ptoche commented Dec 10, 2018

Example 4:
Objective: reveal a line sequentially: reveal (x_{i}, y_{i}) -- (x_{i-1}, y_{i-1}) without hiding (x_{i-1}, y_{i-1})-(x_{i-2}, y_{i-2})

# Packages + Data
library(reprex)
library(ggplot2)
library(gganimate)
library(tibble)
df <- tibble(x = -10:10, y = abs(x)) 
# Plots
p1 <- ggplot(data = df, aes(x, y)) + 
    geom_path(size = 2) + 
    transition_reveal(x) +
    labs(title = "geom_path() with transition_reveal(x)")
p1 <- animate(p1)  
p2 <- ggplot(data = df, aes(x, y)) + 
    geom_path(size = 2) + 
    transition_reveal(y) +
    labs(title = "geom_path() with transition_reveal(y)")
p2 <- animate(p2)  
combine_gifs(plot1 = p1, plot2 = p2)
#> Loading required package: magick
#> Linking to ImageMagick 6.9.9.39
#> Enabled features: cairo, fontconfig, freetype, lcms, pango, rsvg, webp
#> Disabled features: fftw, ghostscript, x11

Created on 2018-12-11 by the reprex package (v0.2.1)

Verdict: Success.
Comment: geom_line() is not recommended for animations. While it works in this example, in general Thomas recommends geom_path() in conjunction with transition_reveal().
Comment: In this example, transition_manual() does not work.
Comment: Traces the path of the points over 100 frames, but this can be changed with (say) animate(p1, nframes = 10, fps = 1)

Example 4b: Things that go "wrong" when attempting to animate geom_line(). Use geom_path() instead.

    # Packages + Data
    library(reprex)
    library(ggplot2)
    library(gganimate)
    library(tibble)
    df <- tibble(x = -10:10, y = abs(x)) 
    # Plots
    p1 <- ggplot(data = df, aes(x, y)) + 
        geom_line(size = 2) + 
        transition_reveal(x) +
        labs(title = "geom_line() with transition_reveal(x)")
    p1 <- animate(p1)  
    p2 <- ggplot(data = df, aes(x, y)) + 
        geom_line(size = 2) + 
        transition_reveal(y) +
        labs(title = "geom_line() with transition_reveal(y)")
    p2 <- animate(p2)  
    combine_gifs(plot1 = p1, plot2 = p2)
#> Loading required package: magick
#> Linking to ImageMagick 6.9.9.39
#> Enabled features: cairo, fontconfig, freetype, lcms, pango, rsvg, webp
#> Disabled features: fftw, ghostscript, x11

# Verdict: Not recommended.

Created on 2018-12-11 by the reprex package (v0.2.1)

@thomasp85

This comment has been minimized.

Copy link
Owner

thomasp85 commented Dec 10, 2018

In general, transition_manual should only be used if you want to forego gganimate's automatic handling altogether... using it to reveal a line is not what it does...

@thomasp85

This comment has been minimized.

Copy link
Owner

thomasp85 commented Dec 10, 2018

You should generally be hesitant to use geom_line with transition_reveal unless you reveal along the x-axis. using geom_path will give you the expected result

@ptoche

This comment has been minimized.

Copy link
Author

ptoche commented Dec 11, 2018

Thanks for the feedback Thomas. I have updated the examples above. Below an updated summary.

Example 1: Objective: reveal a single point sequentially: reveal (x_{i}, y_{i}) + hide (x_{i-1}, y_{i-1}). Verdict: Success.

Example 2: Objective: reveal all points sequentially: reveal (x_{i}, y_{i}) without hiding (x_{i-1}, y_{i-1}). Verdict: Success.

Example 3: Objective: reveal a single line segment sequentially: reveal (x_{i}, y_{i}) -- (x_{i-1}, y_{i-1}) + hide (x_{i-1}, y_{i-1}) -- (x_{i-2}, y_{i-2}). Verdict: Failure.

Example 4: Objective: reveal a line sequentially: reveal (x_{i}, y_{i}) -- (x_{i-1}, y_{i-1}) without hiding (x_{i-1}, y_{i-1})-(x_{i-2}, y_{i-2}). Verdict: Success.

@thomasp85

This comment has been minimized.

Copy link
Owner

thomasp85 commented Dec 11, 2018

For example 2, a simple modification is all it takes:

ggplot(data = df, aes(x, y, group = seq_along(x)) + #group just need to be unique for each point
        geom_point(size = 5) + 
        transition_reveal(x) +
        labs(title = "transition_reveal(x)")
@thomasp85

This comment has been minimized.

Copy link
Owner

thomasp85 commented Dec 11, 2018

I still don't understand what you wan to achieve in example 3

@ptoche

This comment has been minimized.

Copy link
Author

ptoche commented Dec 11, 2018

Thanks for your help Thomas. For me, with or without group = seq_along(x) I get the same output. Let me try to clarify what I'm looking for in Example 3 and get back to you.

@thomasp85

This comment has been minimized.

Copy link
Owner

thomasp85 commented Dec 11, 2018

library(gganimate)
#> Loading required package: ggplot2
df <- data.frame(x = -10:10, y = abs(-10:10)) 
ggplot(data = df, aes(x, y, group = seq_along(x))) + #group just need to be unique for each point
  geom_point(size = 5) + 
  transition_reveal(x)

Created on 2018-12-11 by the reprex package (v0.2.1)

@ptoche

This comment has been minimized.

Copy link
Author

ptoche commented Dec 11, 2018

stupid me I had the group outside the aes(). So sorry! And thanks again for your patience!!

Example 4 with fewer frames than default. The first 5 frames

print_frames <- function(data, nframes = NULL) {
    require(ggplot2)
    if (is.null(nframes)) nframes = nrow(data) 
    for (i in 1:nframes) {
        filename <- paste0("frame-", i, ".png") 
        title <- paste0("Frame", i) 
        ggplot(data[1:i, ], aes(x, y)) + 
            geom_line(size = 2)  +
            scale_x_continuous(limits = c(min(data[1]), max(data[1]))) +
            scale_y_continuous(limits = c(min(data[2]), max(data[2]))) +
            labs(title = title)  
        ggsave(filename, width = 5, height = 5)
    }    
}

print_frames(data = df, nframes = 5) 

frame-1
frame-2
frame-3
frame-4
frame-5

@thomasp85

This comment has been minimized.

Copy link
Owner

thomasp85 commented Dec 11, 2018

That is basically example 4 with a low framerate

@thomasp85

This comment has been minimized.

Copy link
Owner

thomasp85 commented Dec 11, 2018

library(gganimate)
#> Loading required package: ggplot2
df <- data.frame(x = -10:10, y = abs(-10:10)) 
# Plots
p1 <- ggplot(data = df, aes(x, y)) + 
  geom_path(size = 2) + 
  transition_reveal(x) +
  labs(title = "geom_path() with transition_reveal(x)")
animate(p1, nframes = 10, fps = 1)  

Created on 2018-12-11 by the reprex package (v0.2.1)

@ptoche

This comment has been minimized.

Copy link
Author

ptoche commented Dec 11, 2018

Oh I understand now: you set nframes inside the animate() function. Excellent. Indeed I was looking for a frame rate that would match the supplied data. Thanks.

I wrote too fast, I meant this for Example 3:

print_frames <- function(data, nframes = NULL) {
        require(ggplot2)
        if (is.null(nframes)) nframes = nrow(data) 
        for (i in 1:nframes) {
            filename <- paste0("frame-", i, ".png") 
            title <- paste("Frame", i) 
            ggplot(data[i:(i+1), ], aes(x, y)) + 
                geom_line(size = 2)  +
                scale_x_continuous(limits = c(min(data[1]), max(data[1]))) +
                scale_y_continuous(limits = c(min(data[2]), max(data[2]))) +
                labs(title = title)  
            ggsave(filename, width = 5, height = 5)
        }    
    }

    print_frames(data = df, nframes = 5) 

frame-1
frame-2
frame-3
frame-4
frame-5

@thomasp85

This comment has been minimized.

Copy link
Owner

thomasp85 commented Dec 11, 2018

there is currently no way to do that gracefully

@ptoche

This comment has been minimized.

Copy link
Author

ptoche commented Dec 11, 2018

I have updated Examples 1, 2, and 4. Feel free to put them together inside a "Getting Started" wiki, if you think they could be useful. I certainly feel much more comfortable with the package having worked through them and been able to compare different options side by side. I'll close by thanking you for a great package and your kind help!

@ptoche

This comment has been minimized.

Copy link
Author

ptoche commented Dec 13, 2018

Below an animation intended to illustrate the use of counters for transition_manual() and transition_reveal(). Get the current counter with print("{frame}"). Get the total number of frames with print("{nframes}").

Example 5: Printing counters

library(reprex)
library(ggplot2)
library(gganimate)


# Data
df <- structure(list(x = 1:50, y = c(1.13220278129597, 1.19052387615662, 
                                     1.08245817019419, 1.092026178772, 1.10044827996026, 1.10761637669097, 
                                     1.15670117446199, 1.10614650904771, 1.10850359605767, 1.10888736691465, 
                                     1.13057762527772, 1.1097108873825, 1.10079690884314, 1.06299876187194, 
                                     1.02176309803305, 1.00468521705448, 1.01677335399114, 1.05763641886816, 
                                     1.04518028693855, 1.03553964517436, 1.05894958690745, 1.05640164807922, 
                                     1.03859260999251, 1.02183113310639, 1.02216937673504, 1.02203574505795, 
                                     1.02211730017492, 1.02587886449655, 1.02756514422318, 1.03358427512605, 
                                     1.04717537432021, 1.04464239078604, 1.05032082010357, 1.04757090239588, 
                                     1.04442488099086, 1.04616044456226, 1.0379115427868, 1.03534523721584, 
                                     1.02857437409865, 1.03808867100352, 1.0402482762807, 1.04328589395803, 
                                     1.03610034851675, 1.0311223037094, 1.02037361781279, 1.00817879202418, 
                                     1.02145919273311, 1.02037759867125, 1.01944090162799, 1.02264408869067
)), row.names = c(NA, 50L), class = "data.frame")


# cumulative = FALSE
n = nrow(df)  # number of frames to match sample size
p1 <- ggplot(data = df, aes(x = x, y = y)) + 
    transition_manual(x) + 
    geom_point(size = 2, color = "darkblue") +
    labs(x = "sample size", 
         y = "sampling mean",
         title = "Number of samples = {frame},  Maximum size = {nframes}") 
p1 <- animate(p1, nframes = n, fps = 1)  


# cumulative = TRUE
n = nrow(df)  # number of frames to match sample size
p2 <- ggplot(data = df, aes(x = x, y = y, group = seq_along(x))) + 
    transition_reveal(x) + 
    geom_point(size = 2, color = "darkblue") +
    labs(x = "sample size", 
         y = "sampling mean",
         title = "Number of samples = {frame},  Maximum size = {nframes}")  
p2 <- animate(p2, nframes = n, fps = 1) 

combine_gifs(plot1 = p1, plot2 = p2)
#> Loading required package: magick
#> Linking to ImageMagick 6.9.9.39
#> Enabled features: cairo, fontconfig, freetype, lcms, pango, rsvg, webp
#> Disabled features: fftw, ghostscript, x11

Created on 2018-12-14 by the reprex package (v0.2.1)

Comments: 1. having to add group = seq_along() in the aes() is not easy to get used to. Would it be feasible to add an option inside the transition_manual(x, cumulative = TRUE)? Or does it go too far against the "grammar"?

2. The following are ways to access frame information in different settings: {current_frame}, {frame_along}, {closest_state}, {frame_time}. Are there more? It seems like rather a lot. IMHO it would be more user-friendly to have a single universal variable for all that. It could be a short-hand, something like {frame_counter}. I do think {nframes} is a good, easy to remember name.

Edit: Thomas explained to me that the counter may be accessed with {frame}. I have updated the code accordingly. Thanks Thomas!

@thomasp85

This comment has been minimized.

Copy link
Owner

thomasp85 commented Dec 13, 2018

For 1) You don't need to set grouping when using transition_manual, as there is really no tweeting happening at all

To answer 2): if you simply want a counter for which frame you're at, then the frame variable can be used. This is simply counting the frame along the animation and is not related to the data you are displaying (other such variables are nframes, and progress). The reason why the other variables have different names is that they are not alike. They are not simple counters but calculated from the data as well as the specific transformations that the transition makes. Giving them the same name would imply they are alike

@ptoche

This comment has been minimized.

Copy link
Author

ptoche commented Dec 14, 2018

Thanks Thomas.

Oh I didn't realize there was a frame variable for that. Great. I'll update the code with that information.

Do you mean there's a simpler way to obtain the "cumulative" effect?

Right now, this is my understanding:

# Animate points on/off:
ggplot(data = df, aes(x, y)) + 
    transition_manual(x) +
    geom_point() + 

# Animate points on/on
ggplot(data = df, aes(x, y, group = seq_along(x))) + 
    transition_reveal(x) +
    geom_point()  

So it's easy enough to remember this:

  • animate on/off = transition_manual(x)
  • animate on/on = transition_reveal(x)

But much harder to remember to also add group = seq_along(x) to obtain the "cumulative" effect.

I for one am likely to remember this, now that I've talked about it so much, I just think it's a bit of a hurdle for a beginner. But perhaps I've missed an easier way to do this (that is, Example 2 above)?

@thomasp85

This comment has been minimized.

Copy link
Owner

thomasp85 commented Dec 14, 2018

There are many different ways to achieve seemingly identical results on. The different transitions differ, not in what type of output they can create, but in how they interpret the layer data and create an animation for that. In much the same way that "complaining" that you have to sort the data by x in order to make geom_path behave like geom_line doesn't make much sense it also doesn't make much sense to complain that transition_reveal requires special operations to behave like transition_manual (I'm using "complain" here in a very informal and collegial way)

@ptoche

This comment has been minimized.

Copy link
Author

ptoche commented Dec 14, 2018

I see thanks Thomas. I'm not complaining, just giving feedback about a new user's experience with the package. My intention was just to give you constructive feedback. APOLOGIES if it sounded like a complain, it wasn't.

On another matter: Example 5 crashed at around 1,000 points (two plots are created each with 1,000 points and it crashes on my 2017 MacBook Pro). It's not a complaint (!). Gifs with so many points were not such a good idea to begin with. :-)

@thomasp85

This comment has been minimized.

Copy link
Owner

thomasp85 commented Dec 14, 2018

I know, hence the final parenthesis - I used "complain" for lack of better word...

Your crash is related to the magick package when assembling the two plots. ImageMagick loads all images in to memory before assembling the gif, so at a certain point there are too many frames to handle... gifski is much better to handle this as it only have a single frame in memory at a time

@ptoche

This comment has been minimized.

Copy link
Author

ptoche commented Dec 14, 2018

Oh thanks, I'll look into gifski !

@ptoche

This comment has been minimized.

Copy link
Author

ptoche commented Dec 21, 2018

I just saw your Getting Started doc (https://gganimate.com/articles/gganimate.html). As I don't know how to leave messages there, I'll leave a brief comment here: Very nice!

  • there's a typo: the definition of fps is the same as nframes.

  • For me the most useful part of that intro was showing how to use the counters.

  • I'm a bit puzzled by your first example: what is the use-case for having a scatter of points move away from their (x, y) data values to other coordinates that do not correspond to actual data? (except that it looks like a flock of birds and is pretty).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment