In this article, we will make a mathematical rose like this:
rose
function
The mathematical function that draws the rose is: \[(x,y) = \left(a \cos(kt) \cos(t), a \cos(kt) \sin(t)\right)\] where \(a\) and \(k\) are the parameters controlling the size and the shape of the rose respectively.
rose <- function(theta, a = 1, k = 3) {
# `a` controls the size, and `k` controls the shape of the rose
list(x = a * cos(k * theta) * cos(theta), y = a * cos(k * theta) * sin(theta))
}
The parameter \(k\) is specified with \(k = p/q\), where \(p\) and \(q\) should be coprime, e.g. 1/3, 2/7, not 2/6, 3/6. If \(p\) and \(q\) are not coprime, then the rose will be drawn more than once.
p <- 2
q <- 7
k <- p / q
Some calculation is needed to work out the number of time steps required to get to the end of the drawing of the rose. Assuming \(p\) and \(q\) are coprime, then the period is \(\pi q\) if both \(p\) and \(q\) are odd, and \(2 \pi q\) otherwise.
Now we compute the points on the rose with the parameters
a
and k
for time=0 to
time=period
.
time <- seq(from = 0, to = period, length.out = 400) # 400 time steps
pts <- rose(time, a = 1, k = k)
library(animate)
border_with_round_corners <- "border:1px solid lightgray; border-radius:5px;"
device <- animate$new(600, 600, virtual = TRUE, attr = list(style = border_with_round_corners))
attach(device)
line 1: library(animate)
loads the
animate
library.
line 2: We define a style to add a border around the plot so that we know it is there even when nothing has been drawn. The style is specified using CSS; if you haven’t used it before, don’t worry too much about that, the package website maintains a list of the most used ones for you to copy and paste to your code.
line 3: animate$new(600, 600, ...)
sets the canvas
size to be 600 by 600 (width by height).
line 3: An additional flag virtual = TRUE
is needed
only for in-line RMD usage, i.e. you are animating the plot directly in
the code chunk rather than importing it from a file.
line 4: attach(device)
lets us use the functions in
device
directly rather than typing
device$some_function
every time. To detach from the device,
use detach(device)
afterwards.
set_max_stacksize(0)
par(xlim = extendrange(pts$x), ylim = extendrange(pts$y))
for (step in seq_along(time)) {
points(pts$x[step], pts$y[step], id = "point-1")
lines(pts$x[1:step], pts$y[1:step], id = "line-1")
}
rmd_animate(device, click_to_loop(start = 3, wait = 10))
set_max_stacksize(0)
sets the internal memory
of the device to 0, so that it does not use any memory.par(xlim = ..., ylim = ...)
sets the ranges of
the x-axis and the y-axis to be fixed at the specified values.points(...)
and lines(...)
draw
points and lines on the screen. The argument id
needs to be
provided so that the graphics elements are updated, rather than redrawn,
on the screen.If you are making the plot in the R console (not in RMD), you may
want to clear
the plot, switch off
and detach
the device when you are done.
clear()
off()
detach(device)
Continuing from above, we will draw a 4 x 4 grid of roses. First, we generate the roses using the previous code but looping through a range of \(p\) and \(q\) values.
ps <- 1:4
qs <- c(5, 7, 11, 13)
roses <- list()
for (p in ps) {
for (q in qs) {
period <- ifelse(is_odd(p) && is_odd(q), pi * q, 2 * pi * q)
time <- seq(from = 0, to = period, length.out = 400)
roses[[length(roses) + 1]] <- rose(time, a = 1, k = p / q)
}
}
Next, we create 15 additional plot areas using the svg
function. Then when we want to make a plot at a specific area, we simply
call set(PLOT_AREA_ID)
just before the drawing commands. As
the generated plot is quite large in size, we will do this directly in
the R console.
library(animate)
device <- animate$new(150, 150)
attach(device)
set_max_stacksize(0)
# Create 15 additional plot areas. The id of the first plot area is by default "SVG_1";
# we follow the convention and name the rest as "SVG_i", where i = 2, ..., 16.
for (i in 2:16) {
svg(150, 150, id = paste0("SVG_", i))
}
# Loop over the plot area and draw the corresponding rose.
for (step in 1:400) {
for (i in 1:16) {
set(paste0("SVG_", i)) # Make plot area named "SVG_i" active
pts <- roses[[i]]
par(xlim = extendrange(pts$x), ylim = extendrange(pts$y))
points(pts$x[step], pts$y[step], id = "point-1")
lines(pts$x[1:step], pts$y[1:step], id = "line-1")
}
}