Skip to content

Animating a spinner using ggplot2 and ImageMagick

It’s Sunday, and I [Bob] am just sitting on the couch peacefully ggplotting to illustrate basic sample spaces using spinners (a trick I’m borrowing from Jim Albert’s book Curve Ball). There’s an underlying continuous outcome (i.e., where the spinner lands) and a quantization into a number of regions to produce a discrete outcome (e.g., “success” and “failure”). I’m quite pleased with myself for being able to use polar coordinates to create the spinner and arrow. ggplot works surprisingly well in polar coordinates once you figure them out; almost everything people have said about them online is confused and the doc itself assumes you’re a bit more of a ggplotter and geometer than me.

I’m so pleased with it that I show the plot to Mitzi. She replies, “Why don’t you animate it?” I don’t immediately say, “What a waste of time,” then get back to what I’m doing. Instad, I boast, “It’ll be done when you get back from your run.” Luckily for me, she goes for long runs—I just barely had the prototype working as she got home. And then I had to polish it and turn it into a blog post. So here it is, for your wonder and amazement.

Here’s the R magic.

draw_curve <- function(angle) {
  df <- data.frame(outcome = c("success", "failure"), prob = c(0.3, 0.7)) 
  plot <-
    ggplot(data=df, aes(x=factor(1), y=prob, fill=outcome)) +
    geom_bar(stat="identity", position="fill") +
    coord_polar(theta="y", start = 0, direction = 1) +
    scale_y_continuous(breaks = c(0.12, 0.7), labels=c("success", "failure")) +
    geom_segment(aes(y= angle/360, yend= angle/360, x = -1, xend = 1.4), arrow=arrow(type="closed"), size=1) +
    theme(axis.title = element_blank(),
          axis.ticks = element_blank(),
          axis.text.y = element_blank()) +
    theme(panel.grid = element_blank(),
          panel.border = element_blank()) +
    theme(legend.position = "none") +
    geom_point(aes(x=-1, y = 0), color="#666666", size=5)
ds <- c()
pos <- 0
for (i in 1:66) {
  pos <- (pos + (67 - i)) %% 360
  ds[i] <- pos
ds <- c(rep(0, 10), ds)
ds <- c(ds, rep(ds[length(ds)], 10))

for (i in 1:length(ds)) {
  ggsave(filename = paste("frame", ifelse(i < 10, "0", ""), i, ".png", sep=""),
         plot = draw_curve(ds[i]), device="png", width=4.5, height=4.5)

I probably should've combined theme functions. Ben would've been able to define ds in a one-liner and then map ggsave. I hope it's at least clear what my code does (just decrements the number of degrees moved each frame by one---no physics involved).

After producing the frames in alphabetical order (all that ifelse and paste mumbo-jumbo), I went to the output directory and ran the results through ImageMagick (which I'd previously installed on my now ancient Macbook Pro) from the terminal, using

> convert *.png -delay 3 -loop 0 spin.gif

That took a minute or two. Each of the pngs is about 100KB, but the final output is only 2.5MB or so. Maybe I should've went with less delay (I don't even know what the units are!) and fewer rotations and maybe a slower final slowing down (maybe study the physics). How do the folks at Pixar ever get anything done?

P.S. I can no longer get the animation package to work in R, though it used to work in the past. It just wraps up those calls to ImageMagick.

P.P.S. That salmon and teal color scheme is the default!


  1. Mike Lawrence says:

    Have you seen the gganimate packages? It automates this as I recall.

  2. jrc says:

    Andrew, you got upstaged on this one: I bring you the “Everything You and Your Colleagues Have Ever Done is Useless in The Field Of:” Spinner:

  3. Myfanwy says:

    Next step: fidget spinner

  4. MJT says:

    if you want to stay within R, just use Jeroen’s R wrapper to image magick

  5. “How do the folks at Pixar ever get anything done?” The answer is they almost never do. They just hire a gazillion people each of which does one tiny thing. Those movies cost tens to hundreds of millions to make.

  6. Rahul says:

    I tried this to visualize random variables in response to a post on Terry Tao’s blog a while ago:

    • Keith O'Rourke says:

      I actually found its better to walk folks through simple ways to use digits of Pi to generate pseudo-random numbers that emulate random draws from various distributions – starting with pairs of digits, the first divided by 10 giving a value between 0 and 1 and second scaled to determining whether that value gets seen (rejected if greater than the chosen density at that value).

      Then get more and more involved to emulate more realistically (e.g. more digits, trickier starting values and trying to avoid appearances of patterns).

      They seem to get it without getting too annoyed and then they have some sense of how the animations are being produced.
      (May also helps with continuity concepts showing others can’t rule out a number you might generate even though they should know there are only so many digits a computer can generate/print into a file).

Leave a Reply