Tuesday, 28 May 2019

Package Spotlight: anim.plots

The package anim.plots behaves like a sort of user-friendly shell on top of animate that makes animations of some of the most common types of plots in base R in a more intuitive fashion that animate.



This package depends on two other important packages:

-   magick, which is an R implementation of imageMagick, which itself is software used to create animated gifs from still images.

-   animation, which is an R library that can be used to create animations from any collection of plots.
  
The package ggAnimate is also worth mentioning, which is a ggPlot-based equivalent of anim.plots. For higher quality, but higher overhead images and animations, I recommend the ggPlot/ggAnimate path.


Basic plots: Dot/Scatter Plots

library(anim.plots)

### First, some data about the animations:
## resol will be the number of data points plotted for each frame of the animation.
## Nframes is the number of frames
## times is an index that dictates which data point is plotted in which frame.
resol = 100 ## Resolution
Nframes = 10 ## Number of frames
times = rep(1: Nframes, each= resol)

### Next, let’s set up some x and y coords to plot
x = rep(1:resol/Nframes, Nframes)
y = sin(x*times/4)

## So the first frame will be everything where times is 1.
x[times == 1]
y[times == 1]

## ..up to the last frame
x[times == Nframes]
y[times == Nframes]

### The actual animation code will split the x and y by the ‘times’ argument and draw a frame for each one. Since
anim.plot(x,y,times, type="l", col="orange", lwd=2)


Animation speed

The default speed is 1 frame per second. That can be changed with the speed argument.
# 2 FPS
anim.plot(x,y,times, type="l", col="orange", lwd=2, speed=2)
# 4 FPS
anim.plot(x,y,times, type="l", col="orange", lwd=2, speed=4)

### You can also make times a single number, which will dictate the number of frames. The x and y coordinates used will be split evenly.
anim.plot(1:10, 1:10, times=5, cex=3)



The window argument

Alternatively, the ‘window’ argument will dictate which of the x and y are used for each frame. The number of frames is the length of x (and y).
It uses an internal variable called t.
If you want only the t’th x coord used on the t’th frame, use only t.
anim.plot(1:10, 1:10, window=t, cex=3)

## If you want all the coords up to and including the t’th one on the t’th frame, use 1:t.
anim.plot(1:10, 1:10, window=1:t, cex=3)

## If you want more of ‘moving window’ effect, use (t-2):t
anim.plot(1:10, 1:10, window=1:t, cex=3)

## Setting a fixed value or range (without using t) just makes a static animation with all the frames having that range.
anim.plot(1:10, 1:10, window=3:7, cex=3)


Replaying and modifying animations

The replay() function can be used to play an animation that has already been defined.
Modifications to the animation can be made as additional arguments inside replay
myAnim = anim.plot(x,y,times, type="l", col="orange", lwd=2, speed=4)

replay(myAnim) ## Replay once

for(k in 1:5) ## Loop 5 times
{
replay(myAnim, speed=10)
}

Much more than that, the before and after arguments allow you to evaluate expressions before and after each frame is rendered, respectively.
after is particularly useful for drawing additional details like legends.

replay(myAnim, after=legend("topleft", legend="My legend"))

replay(myAnim, after=abline(h=0))

If you are looking to run more than one line after (or before) each frame, the best way to do this is to create your own function of all the things you want to do and put a call to that function as the "after" argument instead.

guidelines = function(distance, shift, location=NA)
{
      if(is.na(location))
      {
        location  = shift + (-50:50)*distance
      }
     abline(h = location)
}


fourlegs = function()
{
legend("topleft", legend="Leg 1")


legend("topright", legend="Leg 2")
legend("bottomleft", legend="Leg 3")
legend("bottomright", legend="Leg 4")  
}

doboth = function(distance, shift, location=NA)
{
     guidelines(distance, shift, location)
     fourlegs()
}

replay(myAnim, after=guidelines(1,0))
replay(myAnim, after=fourlegs(1,0))
replay(myAnim, after=doboth(1,0)) 

 

 

 

Combining / merging animations

For more complicated additions, the merge() function is also useful.
merge(a,b) will render the frames of b on top of the frames of a, provided that both have the same number of frames and the same speed.
Note that in the following example, part2 is using anim.points() which is the animation of the points() command for adding points to an existing plot.

part1 = anim.plot(1:5, 1:5, speed=2, pch=17, cex=3)
part2 = anim.points(1:5, 5:1,col="red", speed=2, pch=8, cex=3)
both = merge(part1, part2)
replay(both)

# Merges of three or more animations are possible, but you have to use the individual parts.
part3 = anim.points(1:5, rep(3,5) ,col="blue", speed=2, pch=12, cex=2)
all3 = merge(part1,part2,part3)
replay(all3)


Saving your animations

The function anim.plot() is a wrapper around the animation package’s saveGIF() function. saveGIF() can already save files as .gif images and .mp4 movies, where anim.plot() can also save plots to .swf Shockwave Flash animations, and .html and .latex files (I presume in base64 encoding)

The animations that are saved loop infinitely.

First, check your working directory. This is where all your .gif files are going to be saved to. By default it WILL overwrite files with the original file’s name.

getwd()

### Save an animation
tmp <- anim.plot(1:10, 1:10, pch=1:10, cex=3, show=FALSE)
anim.save(tmp, "mygif.gif")

## You can save outputs of replays as well.
anim.save(replay(all3), "all3.gif")
anim.save(replay(myAnim, after=doboth(1,0)), "doboth.gif")

Other possible animations

Try these examples, or copy/paste then from the help() files

help(anim.barplot) ## Get the details directly
example(anim.barplot, give.lines=TRUE) # Get the example code without running it.

example(anim.barplot) # Run the example code
example(anim.contour)
example(anim.curve)
example(anim.hist)


For full details, see:

No comments:

Post a Comment