The Mandelbrot set is iconic and countless beautiful visualisations have been born from its deceptively simple recursive equation. R’s plotting ecosystem should be the perfect setting for generating these eye-catching visualisations, but to date the package support has been lacking.
Googling for Mandelbrot set implementations in R didn’t immediately strike pay dirt — for sure there are a few scripts and maybe one dusty package out there but nothing definitive. One of the more useful search results was an age-old academic’s page (presumably pre-dating CSS) with a zip archive of an R wrapper around a C implementation of a Mandelbrot set generator. What’s more, the accompanying README bore the epitaph:
Eventually, perhaps in 50 years or so, I’ll put everything together in a
proper R package.
—a siren song to any R developer with too much time on their hands!†
Mandelbrot R package
The first output was an R package, mandelbrot, which re-wraps the original C code by Mario dos Reis. This provides two interfaces to the underlying set generation algorithm:
mandelbrot() for generating an object for use with base R image
mandelbrot0() for generating a tidy data.frame for use with ggplot2 (equivalent to as.data.frame(mandelbrot()) but faster)
It also has a few other helper functions and utilities (see the docs for info). One of the examples in the README reuses a weird uneven palette made for a previous blog post to pretty good effect:
mb <- mandelbrot(xlim = c(-0.8335, -0.8325),
ylim = c(0.205, 0.206),
resolution = 1200L,
iterations = 1000)
# vaccination heatmap palette
cols <- c(
colorRampPalette(c("#e7f0fa", "#c9e2f6", "#95cbee",
"#0099dc", "#4ab04a", "#ffd73e"))(10),
colorRampPalette(c("#eec73a", "#e29421", "#e29421",
df <- as.data.frame(mb)
ggplot(df, aes(x = x, y = y, fill = value)) +
geom_raster(interpolate = TRUE) + theme_void() +
scale_fill_gradientn(colours = cols, guide = "none")
This is great for single views, but you pretty quickly want to explore and zoom interactively. Mario dos Reis and Jason Turner (via r-help) implemented this in R using
locator to read the cursor position and zoom. 14 years ago that was a pretty neat solution but today we can take this idea a bit further with the R web framework Shiny.
Shinybrot Shiny app
Shinybrot is a pretty simple Shiny app for exploring views generated by the mandelbrot package. It uses “brushing” for plot interaction, allowing the user to drag a rectangle selection which is then set to the x and y limits for the subsequent plot. This can be recursed to go deeper and deeper into the fractal and to get some appreciation of the set complexity.
Not sure why this happens…
Eventually when you go deep enough you bump up against some ggplot2 hard limit where the view is obscured by mysterious white grid lines. (Hadley points out that these aren’t grid lines but gaps between tiles — I think a problem that comes with approaching the limits of R’s numeric precision.) Still, you’re good for a fair few recursions.
Parameters from URI query string
One feature I wanted was static URIs which resolve to a given view. For example, if you want to link to some tiny but interesting region of “Seahorse Valley” (the crevasse between the two primary bulbs), you should be able to direct link to the view you’re looking at.
As usual, Shiny has great support for this out of the box.
parsedQueryString parses URI params in the form
/?param=value to a named list. Then, as parameters change they can be pushed back to the user’s address bar using updateQueryString. Using
mode = "push" with
updateQueryString pushes the parameterised URI to the browser’s history stack, meaning users get working forward and back buttons for free!
Try it out in the shinyapps hosted version:
The shinybrot app, view at shinyapps.io or serve locally with
Possible extensions would be Julia sets and maybe even more exotic fractal equations (Newton fractals, magnetic fractals, Beasley ferns?). Apparently there are also some reasonably straightforward optimisations to the Mandelbrot set algorithm, for example shortcuts for blocking out the primary bulbs rather than iterating over each point, however the simple existing C code Mario put together is already blazingly fast, to the point where generating a view is mostly rasterisation-bound rather than from performing the set calculations.
Issues and pull requests welcome:
† After speaking to the author it turns out he did recently get around to packaging his code, see mariodosreis/fractal