rggplot2animationfibonaccigganimate

How to Animate Both Growing Squares and Growing Fibonacci Spiral in R Using `ggplot2` and `gganimate`?


I'm trying to animate a Fibonacci sequence in R, where both the squares and the spiral grow together. I have managed to create two separate animations:

  1. Growing squares but no growing spiral: The squares appear one by one, but the spiral is static.

  2. Growing spiral but no growing squares: The spiral grows, but the squares are static.

I want to combine both effects in a single animation, where each square appears with its corresponding arc in the spiral. Below are my attempts:

Code 1: Growing Squares, No Growing Spiral

library(ggplot2)
library(gganimate)

# Define a function to generate Fibonacci numbers
fibonacci <- function(n) {
  fib_seq <- numeric(n)
  fib_seq[1] <- 1
  fib_seq[2] <- 1
  for (i in 3:n) {
    fib_seq[i] <- fib_seq[i-1] + fib_seq[i-2]
  }
  return(fib_seq)
}

# Function to generate quarter circle arc data
quarter_circle_arc <- function(x, y, size, direction) {
  theta <- seq(0, pi/2, length.out = 100)
  if (direction == 1) {
    # Bottom-left arc
    data.frame(
      x = x + size * (1 - cos(theta)),
      y = y + size * (1 - sin(theta))
    )
  } else if (direction == 2) {
    # Bottom-right arc
    data.frame(
      x = x + size * (sin(theta) - 1) + size,
      y = y + size * (1 - cos(theta))
    )
  } else if (direction == 3) {
    # Top-right arc
    data.frame(
      x = x + size * (cos(theta) - 1) + size,
      y = y + size * (sin(theta) - 1) + size
    )
  } else if (direction == 0) {
    # Top-left arc
    data.frame(
      x = x + size * (1 - sin(theta)),
      y = y + size * (cos(theta) - 1) + size
    )
  }
}

# Generate the first 10 Fibonacci numbers
fib_seq <- fibonacci(10)

# Initialize the data frame for squares
squares <- data.frame(
  x = numeric(10), 
  y = numeric(10), 
  size = fib_seq, 
  direction = numeric(10),
  frame = 1:10
)

# Initialize the data frame for arcs
arcs <- data.frame(
  x = numeric(0), 
  y = numeric(0), 
  frame = integer(0)
)

# Set the initial position and direction
x <- 0
y <- 0
direction <- 1 

for (i in 1:10) {
  if (direction == 1) {
    y <- y - ifelse(x == 0, 0, fib_seq[i])
    squares[i, "x"] <- x
    squares[i, "y"] <- y
  } else if (direction == 2) {
    x <- x + fib_seq[i-1]
    squares[i, "x"] <- x
    squares[i, "y"] <- y
  } else if (direction == 3) {
    x <- x - fib_seq[i-2]
    y <- y + fib_seq[i-1]
    squares[i, "x"] <- x
    squares[i, "y"] <- y
  } else if (direction == 4) {
    x <- x - fib_seq[i]
    y <- y - fib_seq[i-2]
    squares[i, "x"] <- x
    squares[i, "y"] <- y
  }
  
  # Store the direction for the arcs
  squares[i, "direction"] <- direction
  
  # Generate the arc for the current square and append it to arcs data frame
  new_arc <- quarter_circle_arc(
    squares$x[i], squares$y[i], squares$size[i], direction = (i %% 4)
  )
  new_arc$frame <- i  # Assign the frame for animation
  arcs <- rbind(arcs, new_arc)
  
  # Update the direction
  direction <- (direction %% 4) + 1
}

# Create the base plot
p <- ggplot() +
  coord_fixed(ratio = 1) +
  theme_void()

# Plot squares and arcs with gradual appearance
p_anim <- p + 
  geom_rect(data = squares, aes(xmin = x, ymin = y, xmax = x + size, ymax = y + size, frame = frame), 
            fill = "white", color = "black") +
  geom_path(data = arcs, aes(x = x, y = y, frame = frame), color = "black") +
  transition_manual(frames = squares$frame, cumulative = TRUE)  # Keep previous frames visible

# Render the animation
animate(p_anim, nframes = 100, fps = 10)

Code 2: Growing Spiral, No Growing Squares

library(ggplot2)
library(gganimate)

# Define a function to generate Fibonacci numbers
fibonacci <- function(n) {
  fib_seq <- numeric(n)
  fib_seq[1] <- 1
  fib_seq[2] <- 1
  for (i in 3:n) {
    fib_seq[i] <- fib_seq[i-1] + fib_seq[i-2]
  }
  return(fib_seq)
}

# Function to generate quarter circle arc data
quarter_circle_arc <- function(x, y, size, direction) {
  theta <- seq(0, pi/2, length.out = 100)
  if (direction == 1) {
    # Bottom-left arc
    data.frame(
      x = x + size * (1 - cos(theta)),
      y = y + size * (1 - sin(theta))
    )
  } else if (direction == 2) {
    # Bottom-right arc
    data.frame(
      x = x + size * (sin(theta) - 1) + size,
      y = y + size * (1 - cos(theta))
    )
  } else if (direction == 3) {
    # Top-right arc
    data.frame(
      x = x + size * (cos(theta) - 1) + size,
      y = y + size * (sin(theta) - 1) + size
    )
  } else if (direction == 0) {
    # Top-left arc
    data.frame(
      x = x + size * (1 - sin(theta)),
      y = y + size * (cos(theta) - 1) + size
    )
  }
}

# Generate the first 10 Fibonacci numbers
fib_seq <- fibonacci(10)

# Initialize the data frame for squares
squares <- data.frame(
  x = numeric(10), 
  y = numeric(10), 
  size = fib_seq, 
  direction = numeric(10),
  frame = 1:10
)

# Initialize the data frame for arcs
arcs <- data.frame(
  x = numeric(0), 
  y = numeric(0), 
  frame = integer(0)
)

# Set the initial position and direction
x <- 0
y <- 0
direction <- 1 

for (i in 1:10) {
  if (direction == 1) {
    y <- y - ifelse(x == 0, 0, fib_seq[i])
    squares[i, "x"] <- x
    squares[i, "y"] <- y
  } else if (direction == 2) {
    x <- x + fib_seq[i-1]
    squares[i, "x"] <- x
    squares[i, "y"] <- y
  } else if (direction == 3) {
    x <- x - fib_seq[i-2]
    y <- y + fib_seq[i-1]
    squares[i, "x"] <- x
    squares[i, "y"] <- y
  } else if (direction == 4) {
    x <- x - fib_seq[i]
    y <- y - fib_seq[i-2]
    squares[i, "x"] <- x
    squares[i, "y"] <- y
  }
  
  # Store the direction for the arcs
  squares[i, "direction"] <- direction
  
  # Generate the arc for the current square and append it to arcs data frame
  new_arc <- quarter_circle_arc(
    squares$x[i], squares$y[i], squares$size[i], direction = (i %% 4)
  )
  new_arc$frame <- i  # Assign the frame for animation
  arcs <- rbind(arcs, new_arc)
  
  # Update the direction
  direction <- (direction %% 4) + 1
}

# Create the base plot
p <- ggplot() +
  coord_fixed(ratio = 1) +
  theme_void()

# Plot squares with gradual appearance
for (i in 1:10) {
  p <- p + 
    geom_rect(data = squares[1:i,], aes(xmin = x, ymin = y, xmax = x + size, ymax = y + size), 
              fill = "white", color = "black") +
    geom_path(data = arcs[arcs$frame <= i,],

 aes(x = x, y = y), color = "black")
}

# Animate the plot with growing spiral and preserving squares
p_anim <- p + 
  transition_reveal(arcs$frame) +
  enter_fade() + 
  exit_fade()

# Render the animation
animate(p_anim, nframes = 100, fps = 10)

I want the squares to appear gradually along with the corresponding arcs forming a growing spiral. However, I haven't been able to achieve this.


Solution

  • library(ggplot2)
    library(gganimate)
    
    # Define a function to generate Fibonacci numbers
    fibonacci <- function(n) {
      fib_seq <- numeric(n)
      fib_seq[1] <- 1
      fib_seq[2] <- 1
      for (i in 3:n) {
        fib_seq[i] <- fib_seq[i-1] + fib_seq[i-2]
      }
      return(fib_seq)
    }
    
    # Function to generate quarter circle arc data
    quarter_circle_arc <- function(x, y, size, direction) {
      theta <- seq(0, pi/2, length.out = 100)
      if (direction == 1) {
        # Bottom-left arc
        data.frame(
          x = x + size * (1 - cos(theta)),
          y = y + size * (1 - sin(theta))
        )
      } else if (direction == 2) {
        # Bottom-right arc
        data.frame(
          x = x + size * (sin(theta) - 1) + size,
          y = y + size * (1 - cos(theta))
        )
      } else if (direction == 3) {
        # Top-right arc
        data.frame(
          x = x + size * (cos(theta) - 1) + size,
          y = y + size * (sin(theta) - 1) + size
        )
      } else if (direction == 0) {
        # Top-left arc
        data.frame(
          x = x + size * (1 - sin(theta)),
          y = y + size * (cos(theta) - 1) + size
        )
      }
    }
    
    # Generate the first 10 Fibonacci numbers
    fib_seq <- fibonacci(10)
    
    # Initialize the data frame for squares
    squares <- data.frame(
      x = numeric(10), 
      y = numeric(10), 
      size = fib_seq, 
      direction = numeric(10),
      frame = 1:10
    )
    
    # Initialize the data frame for arcs
    arcs <- data.frame(
      x = numeric(0), 
      y = numeric(0), 
      frame = integer(0)
    )
    
    # Set the initial position and direction
    x <- 0
    y <- 0
    direction <- 1 
    
    for (i in 1:10) {
      if (direction == 1) {
        y <- y - ifelse(x == 0, 0, fib_seq[i])
        squares[i, "x"] <- x
        squares[i, "y"] <- y
      } else if (direction == 2) {
        x <- x + fib_seq[i-1]
        squares[i, "x"] <- x
        squares[i, "y"] <- y
      } else if (direction == 3) {
        x <- x - fib_seq[i-2]
        y <- y + fib_seq[i-1]
        squares[i, "x"] <- x
        squares[i, "y"] <- y
      } else if (direction == 4) {
        x <- x - fib_seq[i]
        y <- y - fib_seq[i-2]
        squares[i, "x"] <- x
        squares[i, "y"] <- y
      }
      
      # Store the direction for the arcs
      squares[i, "direction"] <- direction
      
      # Generate the arc for the current square and append it to arcs data frame
      new_arc <- quarter_circle_arc(
        squares$x[i], squares$y[i], squares$size[i], direction = (i %% 4)
      )
      new_arc$frame <- i  # Assign the frame for animation
      arcs <- rbind(arcs, new_arc)
      
      # Update the direction
      direction <- (direction %% 4) + 1
    }
    # Create the base plot
    p <- ggplot() +
      coord_fixed(ratio = 1) +
      theme_void()
    
    p <- p +
      geom_rect(
        data = squares, aes(xmin = x, ymin = y, xmax = x + size, ymax = y + size),
        fill = "white", color = "black"
      ) +
      geom_path(
        data = arcs,
        aes(x = x, y = y), color = "black"
      )
    
    
    # Animate the plot with growing spiral and preserving squares
    p_anim <- p +
      transition_manual(frames = frame, cumulative = TRUE) + # Keep previous frames visible
      enter_fade() +
      exit_fade()
    
    # Render the animation
    animate(p_anim, nframes = 100, fps = 10)
    

    requested animated spiral image