Fireworks

Animating fireworks with ggplot2 and gganimate

Edward Visel

4 minute read

Since Thomas Lin Pedersen took over gganimate, I’ve been building animations. Mostly what I’ve built is not for any particular data visualization purpose. My motivations vary, but have included

  • I want to try out new features of the package
  • I’m bored
  • I’m trying to understand trig functions in polar space
  • I have an idea
  • I can’t focus
  • I want to play with matrix transformations
  • I want to make pretty things

…but I mostly make them because they make me happy. The results could be charitably called generative art, though they’re more a pale homage to artists deserving of the title like @beesandbombs.

Today I went back to an old idea stemming from some work with spatial grids in sf. Plotting the grids with a projection, I discovered that when covering the whole globe, projected grids sometimes look like fireworks, e.g.

library(tidyverse)
library(sf)
theme_set(hrbrthemes::theme_ipsum_ps())

grid_plot <- expand.grid(lon = seq(-180, 180, 10), 
                         lat = seq(-90, 90, 10)) %>% 
    pmap(~st_point(c(...))) %>%    # convert each pair to an sf point
    st_sfc(crs = 4326) %>%    # convert all points to sf column
    st_sf() %>%    # convert to sf data frame
    ggplot() + 
    geom_sf()

grid_plot

grid_plot + coord_sf(crs = "+proj=laea +lat_0=-90 +ellps=WGS84 +no_defs")

Grids as fireworks sounded like a prime candidate for animation, which I finally got back to today. Instead of using sf, I stuck to polar coordinates, which can produce a similar effect with less effort.

Most of my plots were not what I wanted. Some didn’t move. Some moved too slow, or with the wrong acceleration. Some were just expanding rings. But here I present some of my more interesting failures, together with a couple results that could be convincingly called fireworks.


A hole

Exponential decay doesn’t slow down, it goes backwards:

library(gganimate)
theme_set(theme_void() + theme(
    panel.background = element_rect(fill = 'black')
))
p <- crossing(x = 1:30, nesting(t = 1:10, y = .5^(seq(t)))) %>% 
    ggplot(aes(x, y)) + 
    geom_point(color = 'white') + 
    coord_polar() + 
    transition_time(t) + 
    shadow_wake(0.5)

animate(p, fps = 30)

Straight

p <- map_dfr(1:10, ~crossing(x = 1:30, nesting(
        y = seq(1, .x, length.out = 10)^0.5, 
        t = 1:10
    ))) %>% 
    ggplot(aes(x, y)) + 
    geom_point(color = 'white') + 
    coord_polar() + 
    transition_time(t) + 
    shadow_wake(0.3)

animate(p, fps = 30)

I thought this was what I wanted, but seeing it, while it may be how we think fireworks look (a bunch of points shot out at equally-spaced intervals on a circle), it doesn’t actually look right, because fireworks aren’t circles, they’re spheres.

Particles and gnats

I couldn’t figure out the spacing, so I tried random uniform numbers instead of a sequence:

p <- map_dfr(1:10, ~data_frame(y = seq(1, .x, length.out = 10), t = 1:10)) %>% 
    mutate(x = runif(n())) %>% 
    ggplot(aes(x, y)) + 
    geom_point(color = 'white') + 
    coord_polar() + 
    transition_time(t) + 
    shadow_wake(0.5)

animate(p, nframes = 300, fps = 30)

…which returned a bunch of weird particles, or with shorter timing

animate(p, nframes = 30)

..gnat-swarm fireworks. Which are fireworks, but not the type I wanted.

Mandala

I don’t know what happened here, but it’s trippy.

p <- map_dfr(1:10, ~crossing(
        x = 1:30, 
        nesting(
            y = runif(10), 
            t = 1:10
        )
    )) %>% 
    ggplot(aes(x, y)) + 
    geom_point(color = 'white') + 
    coord_polar() + 
    transition_time(t) + 
    shadow_wake(0.5)

animate(p, nframes = 20)

Explosion

Ok, this is what I meant, but it just looks like an explosion.

p <- map_dfr(1:10, ~crossing(
        x = runif(30), 
        nesting(
            y = seq(1, .x, length.out = 10)^0.5, 
            t = 1:10)
        )
    ) %>% 
    ggplot(aes(x, y)) + 
    geom_point(color = 'white') + 
    coord_polar() + 
    transition_time(t) + 
    shadow_wake(0.5)

animate(p, fps = 30)

Spirals

Getting pretty close.

p <- map_dfr(1:10, ~crossing(
        x = {
            x = seq(30) + 0.3*.x; 
            ifelse(x > 30, x - 30, x)
        }, 
        nesting(
            y = seq(1, .x, length.out = 10)^0.5, 
            t = 1:10)
        )
    ) %>% 
    ggplot(aes(x, y)) + 
    geom_point(color = 'white') + 
    coord_polar() + 
    transition_time(t) + 
    shadow_wake(0.3)

animate(p, fps = 30)

In-n-out

Wait what happened here

p <- map_dfr(0:10, ~crossing(
        x = {
            x = seq(30) + 0.6*.x; 
            ifelse(x >= 30, x - 30, x)
        }, 
        nesting(
            y = seq(1, .x, length.out = 10)^0.5, 
            t = 1:10)
        )
    ) %>% 
    ggplot(aes(x, y)) + 
    geom_point(color = 'white') + 
    coord_polar() + 
    transition_time(t) + 
    shadow_wake(0.5)

animate(p, fps = 30)

Firework

p <- map_dfr(1:10, ~crossing(
        x = {
            x = seq(30) + 0.6*.x; 
            ifelse(x > 30, x - 30, x)
        }, 
        nesting(
            y = seq(1, .x, length.out = 10)^0.5, 
            t = 1:10)
        )
    ) %>% 
    ggplot(aes(x, y)) + 
    geom_point(color = 'white') + 
    coord_polar() + 
    transition_time(t) + 
    shadow_wake(0.3)

animate(p, fps = 30)

Yep, that’s definitely a firework. Done.

comments powered by Disqus