
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.
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.
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.
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)
}
c is a complex number (e.g., -0.5 + 0.3i)z starts at 0 and gets squared and added to c repeatedlyabs(z) exceeds 2, we know it will escape to infinityplot_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?
center_x, center_y: The point we're looking atzoom: How much we've zoomed in (1 = default view)scale: Size of the viewing window (smaller = more zoomed) # 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.
# 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)
})
outer() applies a function to every combination of x and y values. Here, we:
re + 1i * immandelbrot() function to each complex numbermax_iter as we zoom in to reveal more detail!
# 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).
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")
}
locator(1) waits for one mouse click and returns coordinates# 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()
png() to save beautiful renders at high resolutionparallel package to speed up calculations