šŸŽØ How to Code an Interactive Mandelbrot Set in R

Learn how to create a beautiful, interactive fractal visualizer with click-to-zoom functionality using pure R. This tutorial will walk you through every step, from the mathematical foundations to the interactive interface.

šŸ“š Table of Contents

  1. What is the Mandelbrot Set?
  2. The Mathematics
  3. Step 1: The Core Algorithm
  4. Step 2: Creating the Visualization
  5. Step 3: Adding Interactivity
  6. Complete Code
  7. Further Improvements

🌌 What is the Mandelbrot Set?

The Mandelbrot set is one of the most famous fractals in mathematics. It's a set of complex numbers that produces an infinitely complex boundary when visualized. No matter how much you zoom in, you'll always find new intricate patterns.

Why is it special? The Mandelbrot set demonstrates how simple mathematical rules can create infinite complexity. It's defined by a remarkably simple equation, yet its boundary has infinite detail.

šŸ”¢ The Mathematics

The Mandelbrot Iteration:
For each complex number c, we start with zā‚€ = 0 and repeatedly apply:
z(n+1) = z(n)² + c

If this sequence stays bounded (|z| ≤ 2), then c is in the Mandelbrot set.

In practice, we count how many iterations it takes for |z| to exceed 2. Points that never escape (up to our maximum iterations) are colored black and are part of the set. Points that escape quickly are colored based on how fast they escaped.

Step 1: The Core Algorithm

1Create the Mandelbrot Function

This function tests whether a complex number is in the Mandelbrot set by iterating the formula.

mandelbrot <- function(c, max_iter = 100) {
  z <- 0
  for (i in 1:max_iter) {
    z <- z^2 + c
    if (abs(z) > 2) return(i)
  }
  return(max_iter)
}
šŸ’” How it works:
  • c is a complex number (e.g., -0.5 + 0.3i)
  • z starts at 0 and gets squared and added to c repeatedly
  • If abs(z) exceeds 2, we know it will escape to infinity
  • We return the iteration count when it escapes (or max_iter if it doesn't)

Step 2: Creating the Visualization

2Set Up the Coordinate System

plot_mandelbrot <- function(center_x = -0.5, center_y = 0, zoom = 1, 
                           width = 800, height = 600) {
  
  # Calculate viewing window
  scale <- 3.5 / zoom
  x_min <- center_x - scale * width / (2 * height)
  x_max <- center_x + scale * width / (2 * height)
  y_min <- center_y - scale / 2
  y_max <- center_y + scale / 2

What's happening here?

3Create the Coordinate Grid

  # Create coordinate grid
  x <- seq(x_min, x_max, length.out = width)
  y <- seq(y_min, y_max, length.out = height)

This creates two vectors: one for the real axis (x) and one for the imaginary axis (y). We sample 800 points horizontally and 600 vertically.

4Calculate Iterations for All Points

  # Calculate iterations for each point
  max_iter <- min(100 + floor(zoom * 20), 500)
  m <- outer(x, y, function(re, im) {
    sapply(re + 1i * im, mandelbrot, max_iter = max_iter)
  })
šŸ’” The Magic of outer():
outer() applies a function to every combination of x and y values. Here, we:
  1. Combine real (re) and imaginary (im) parts into complex numbers: re + 1i * im
  2. Apply our mandelbrot() function to each complex number
  3. Get a matrix where each value is the iteration count
We also increase max_iter as we zoom in to reveal more detail!

5Add Color and Plot

  # Create beautiful color palette
  colors <- colorRampPalette(c("#000033", "#000055", "#0000BB", 
                               "#5500BB", "#BB00BB", "#FF0055", 
                               "#FF5500", "#FFBB00", "#FFFF00"))(max_iter)
  
  # Plot the Mandelbrot set
  par(mar = c(3, 3, 3, 1))
  image(x, y, m, col = colors, useRaster = TRUE,
        main = sprintf("Mandelbrot Set (Zoom: %.1fx)", zoom),
        xlab = sprintf("Real (center: %.6f)", center_x),
        ylab = sprintf("Imaginary (center: %.6f)", center_y))

Color Scheme: We create a smooth gradient from deep blue (low iterations) through purple and red to bright yellow (high iterations before escaping).

Step 3: Adding Interactivity

6Create the Interactive Loop

zoom_loop <- function() {
  view <- list(center_x = -0.5, center_y = 0, zoom = 1)
  
  cat("Interactive Mandelbrot Explorer\n")
  cat("Click anywhere to zoom in 2x\n")
  cat("Press ESC to exit\n\n")
  
  repeat {
    # Plot current view
    view_params <- plot_mandelbrot(view$center_x, view$center_y, view$zoom)
    
    # Get click coordinates
    click <- locator(1)
    if (is.null(click)) break
    
    # Update view to zoom at clicked location
    view$center_x <- click$x
    view$center_y <- click$y
    view$zoom <- view$zoom * 2
    
    cat(sprintf("Zooming to (%.6f, %.6f) at %.1fx\n", 
                view$center_x, view$center_y, view$zoom))
  }
  
  cat("Exiting...\n")
}
šŸ’” How the interaction works:
  • locator(1) waits for one mouse click and returns coordinates
  • We use those coordinates as the new center point
  • We double the zoom level (zoom * 2)
  • The loop continues until you press ESC (which makes locator return NULL)

āœ… Complete Code

# Interactive Mandelbrot Set with Click-to-Zoom

mandelbrot <- function(c, max_iter = 100) {
  z <- 0
  for (i in 1:max_iter) {
    z <- z^2 + c
    if (abs(z) > 2) return(i)
  }
  return(max_iter)
}

plot_mandelbrot <- function(center_x = -0.5, center_y = 0, zoom = 1, 
                           width = 800, height = 600) {
  scale <- 3.5 / zoom
  x_min <- center_x - scale * width / (2 * height)
  x_max <- center_x + scale * width / (2 * height)
  y_min <- center_y - scale / 2
  y_max <- center_y + scale / 2
  
  x <- seq(x_min, x_max, length.out = width)
  y <- seq(y_min, y_max, length.out = height)
  
  max_iter <- min(100 + floor(zoom * 20), 500)
  m <- outer(x, y, function(re, im) {
    sapply(re + 1i * im, mandelbrot, max_iter = max_iter)
  })
  
  colors <- colorRampPalette(c("#000033", "#000055", "#0000BB", 
                               "#5500BB", "#BB00BB", "#FF0055", 
                               "#FF5500", "#FFBB00", "#FFFF00"))(max_iter)
  
  par(mar = c(3, 3, 3, 1))
  image(x, y, m, col = colors, useRaster = TRUE,
        main = sprintf("Mandelbrot Set (Zoom: %.1fx)", zoom),
        xlab = sprintf("Real (center: %.6f)", center_x),
        ylab = sprintf("Imaginary (center: %.6f)", center_y))
  
  list(center_x = center_x, center_y = center_y, zoom = zoom,
       x_range = c(x_min, x_max), y_range = c(y_min, y_max))
}

zoom_loop <- function() {
  view <- list(center_x = -0.5, center_y = 0, zoom = 1)
  
  cat("Interactive Mandelbrot Explorer\n")
  cat("Click anywhere to zoom in 2x\n")
  cat("Press ESC to exit\n\n")
  
  repeat {
    view_params <- plot_mandelbrot(view$center_x, view$center_y, view$zoom)
    click <- locator(1)
    if (is.null(click)) break
    
    view$center_x <- click$x
    view$center_y <- click$y
    view$zoom <- view$zoom * 2
    
    cat(sprintf("Zooming to (%.6f, %.6f) at %.1fx\n", 
                view$center_x, view$center_y, view$zoom))
  }
  
  cat("Exiting...\n")
}

# Start exploring!
zoom_loop()

šŸš€ Further Improvements

Ideas to Enhance Your Mandelbrot Explorer

  1. Add a zoom out option: Detect right-clicks to zoom out instead of in
  2. Different color schemes: Create multiple palettes and let users switch between them
  3. Save images: Use png() to save beautiful renders at high resolution
  4. Smooth coloring: Use fractional iteration counts for smoother gradients
  5. Julia sets: Modify the algorithm to explore related Julia set fractals
  6. Performance: Use the parallel package to speed up calculations
  7. History: Keep a stack of previous views so users can go back
šŸŽÆ Cool Coordinates to Explore: Try zooming in on these locations to see beautiful structures!