Chaos games and gganimate

I’ve recently had my mind shaken by this video from Numberphile that shows how to draw fractal shapes using random sampling. I found this concept so cool that I wrote a quick script in R to create these animated patterns using ggplot and gganimate.
 

The script creates these fractal patterns for any regular shape and for any number of drawn points. We’ll start by defining a function called shapes that takes the number of starting points and the number of iterations as arguments:

shapes <- function(num, iter){

We’ll do away with the random starting points and draw our shape with a refresher in high school geometry. Remembering that each x, y pair of a regular n-sided shape can be expressed as:

     \begin{equation*}\begin{aligned} &\{x_{i}, y_{i}\} = \left\{\cos\left(2 \cdot \pi \cdot \frac{i}{n} \right), \ \sin\left(2 \cdot \pi \cdot \frac{i}{n}\right) \right\} \\ & i = 1,..,n \\ \end{aligned}\end{equation}

We’ll create the vectors of the start points in R:

  # initialise vectors for start points
  basex <- rep(0, num)
  basey <- rep(0, num)
 
  # select start points
  for (i in 1:num){
    basex[i] = 10 * cos(2 * pi * i / num)
    basey[i] = 10 * sin(2 * pi * i / num)
  }

In the video, to create the pattern with a triangle we moved halfway to each point each iteration. We can generalise the code away from triangles to any regular shape; for the n-sided shape:

     \begin{equation*}\begin{aligned} &\left\{x_{t+1} , y_{t+1}\right\} = \left\{ x_{t} + \left(x_{t} - x_{\text{start}}\right)\frac{\left(n-2\right)}{\left(n-1\right)}, y_{t} + \left(y_{t} - y_{\text{start}}\right)\frac{\left(n-2\right)}{\left(n-1\right)}\right\}\\ \end{aligned}\end{equation}

So for the fractal pattern for squares we move two-thirds of the distance, for pentagons three-quarters and so on. We draw from a random uniform distribution to choose the starting point to move towards with each iteration and save each new iteration in a dataframe:

# initialise vectors for each point
  iterx <- rep(0, iter)
  itery <- rep(0, iter)
 
  # generate random uniform numbers 
  iterx[1] <- runif(n = 1, min = min(basex), max = max(basex))
  itery[1] <- runif(n = 1, min = min(basey), max = max(basey))
 
  # use the random sampling to draw each point
  pick <- ceiling(runif(n = iter, min = 0, max = 1) * num)
  for (i in 2:iter){
    iterx[i] <- iterx[(i-1)] + ((basex[pick[i]] - iterx[(i-1)]) * ((num - 2) / (num - 1)))
    itery[i] <- itery[(i-1)] + ((basey[pick[i]] - itery[(i-1)]) * ((num - 2) / (num - 1)))
  }
 
  # create a data frame for output
  start_points <- data.frame(x = basex, y = basey)
  iter_points <- data.frame(point = (1:iter), x = iterx, y = itery)
 
  return(iter_points)
}

 
Pretty simple stuff. Running our function for four starting points and 2,500 iterations yields the following pretty picture:

 
My favourite, five starting points and 2,500 iterations:

Using gganimate we can animate the process showing each of the dots being drawn. gganimate’s GitHub page has all the information on how to install the package and required image software. gganimate is a easy tool for turning ggplot objects into animation. It requires adding a frame argument into the the plot aesthetic and the package does the rest. We add the cumulative argument equal to true to get the pattern to build up over time:

# create the points with the shapes function
triangle <- shapes(3, 2500)
 
# create the ggplot object
tri <- ggplot(triangle, aes(x = x, y = y, frame = point)) + 
  geom_point(aes(cumulative = T)) +
  theme_classic()
 
# animate away
gganimate(tri, filepath, interval = 0.01)

A few lines of code and a Sierpinski triangle is created!


Magical stuff.

Leave a comment