Time for something different! Can you create a seasonally-themed image?
It could be a winter scene or have a Christmas/Hanukkah theme. Use the concept of layers (e.g. ggplot2) to build an image. Can be static or animated. Can use AI tools (e.g. Claude/ChatGPT) to develop code using a series of prompts.
First, we have to create a perfect winter background.
To start, we simulate a winter sky filled with snowflakes of various
sizes.
The function below places N snowflakes randomly within a defined canvas,
ensuring that no two snowflakes appear closer than a user-specified
minimum distance d.
generate_snowflakes <- function(N, d, xmin = 0, xmax = 1, ymin = 0, ymax = 1) {
pts <- data.frame(NA, nrow = N, ncol = 3)
count <- 0
i <- 0
while (count < N && i < 5000) {
i <- i + 1
x <- runif(1, xmin, xmax)
y <- runif(1, ymin, ymax)
size <- round(runif(1, 3, 8))
if (count == 0) {
flag <- TRUE
}
if (count != 0) {
dists <- sqrt((pts[1:count, 1] - x)^2 + (pts[1:count, 2] - y)^2)
flag <- all(dists >= d)
}
if (flag == TRUE) {
count <- count + 1
pts[count, ] <- c(x, y, size)
}
}
colnames(pts) <- c("x", "y", "size")
return(pts)
}
snowflakes <- generate_snowflakes(
N = 300,
d = 25,
xmin = 0, xmax = 450,
ymin = 0, ymax = 450
)
Once the snowflake coordinates are generated, we place them onto a light blue background representing bright winter sky. Snowflakes are represented using the ’*’ (asterisk) character.
snowy_background <- ggplot(data = snowflakes) +
annotate("rect", xmin = 0, xmax = 450, ymin = 0, ymax = 450, fill = "lightskyblue", alpha = 0.8) +
geom_text(aes(x = x, y = y, label = '*'), color="white", size = snowflakes$size) +
theme_classic() +
theme(
line = element_blank(),
text = element_blank()
)
snowy_background
To give the scene some depth, we add a row of distant fir trees.
fur_tree <- function(xstart, length, colour) {
pts <- data.frame(NA)
x_coord <- c(xstart, (2*xstart+length)/2, xstart+length)
y_coord <- c(0, 2*sqrt(2)*length, 0)
pts <- data.frame(x_coord, y_coord, colour)
tree <- geom_polygon(data = pts, aes(x = x_coord, y = y_coord), fill = colour, alpha = 0.5)
return(tree)
}
Now we can add fur trees to the snowy background:
snowy_background +
fur_tree(xstart = 0, length = 50, colour = "palegreen4") +
fur_tree(xstart = 20, length = 80, colour = "darkolivegreen") +
fur_tree(xstart = 50, length = 75, colour = "palegreen4") +
fur_tree(xstart = 100, length = 45, colour = "darkolivegreen") +
fur_tree(xstart = 120, length = 50, colour = "darkolivegreen") +
fur_tree(xstart = 150, length = 35, colour = "palegreen4") +
fur_tree(xstart = 170, length = 55, colour = "darkolivegreen") +
fur_tree(xstart = 200, length = 70, colour = "palegreen4") +
fur_tree(xstart = 250, length = 45, colour = "darkolivegreen") +
fur_tree(xstart = 280, length = 50, colour = "palegreen4") +
fur_tree(xstart = 300, length = 35, colour = "darkolivegreen") +
fur_tree(xstart = 320, length = 75, colour = "palegreen4") +
fur_tree(xstart = 330, length = 120, colour = "palegreen4") +
fur_tree(xstart = 350, length = 55, colour = "darkolivegreen") +
fur_tree(xstart = 375, length = 40, colour = "darkolivegreen") +
fur_tree(xstart = 390, length = 60, colour = "palegreen4")
We remove a central section of trees to make space for the main Christmas tree.
furry_snowy_background <- snowy_background +
fur_tree(xstart = 0, length = 50, colour = "palegreen4") +
fur_tree(xstart = 20, length = 80, colour = "darkolivegreen") +
fur_tree(xstart = 50, length = 75, colour = "palegreen4") +
fur_tree(xstart = 100, length = 45, colour = "darkolivegreen") +
fur_tree(xstart = 320, length = 75, colour = "palegreen4") +
fur_tree(xstart = 330, length = 120, colour = "palegreen4") +
fur_tree(xstart = 350, length = 55, colour = "darkolivegreen") +
fur_tree(xstart = 375, length = 40, colour = "darkolivegreen") +
fur_tree(xstart = 390, length = 60, colour = "palegreen4")
furry_snowy_background
We overlay decorative lights - curved lines in festive colours.
library(ggnewscale)
library(gganimate)
lights <- function(shift = 0, coef, col1, col2, col3) {
pts <- NA
x_coord <- c()
y_coord <- c()
colour <- c()
cl <- NA
cl <- rep(c("r", "b", "y"), 150)
for (i in 1:450) {
x_coord <- append(x_coord, i)
y_coord <- append(y_coord, 450-coef*abs(sin((shift + i)/36)))
if (i %% 5 == 0) {
colour[i] <- cl [i]
}
else colour[i] <- "black"
i = i + 1
}
pts <- data.frame(x_coord, y_coord, colour)
light <- list(
geom_line(data = pts, aes(x = x_coord, y = y_coord), colour = "grey2", linetype = "dotted"),
geom_point(data = pts[pts$colour != "black",], aes(x = x_coord, y = y_coord, colour = colour), size = 2, alpha = 0.2),
geom_point(data = pts[pts$colour != "black",], aes(x = x_coord, y = y_coord, colour = colour), size = 1.3, alpha = 0.45),
geom_point(data = pts[pts$colour != "black",], aes(x = x_coord, y = y_coord, colour = colour), size = 0.5),
scale_color_manual(values = c("y" = col1, "r" = col2, "b" = col3)),
new_scale_color(),
theme(legend.position = "none")
)
return(light)
}
happy_background <- furry_snowy_background +
lights(shift = 0, coef = 80, col1 = "yellow", col2 = "red", col3 = "blue") +
lights(shift = 50, coef = 60, col1 = "red", col2 = "yellow", col3 = "green4")
happy_background
The main Christmas tree will be constructed as a geometric fractal built using an L-system and turtle graphics.
First, we have to define necessary functions to draw a fractal. I prompted ChatGPT to write an L-system generator and a function that turns the string into a “turtle” graphics coordinates. I further adapted and refined both functions, in order to create a fractal with the desired characteristics.
More about L-system and turtle graphics could be found from this excellent lecture.
l_system() — expands an L-system string for n iterations:
l_system <- function(axiom, rules, iter = 3) {
current <- axiom
for (i in seq_len(iter)) {
chars <- strsplit(current, "")[[1]]
next_str <- paste0(sapply(chars, function(ch) rules[[ch]] %||% ch), collapse = "")
current <- next_str
}
current
}
`%||%` <- function(a, b) if (!is.null(a)) a else b
interpret_lsystem() — turns the string into turtle graphics coordinates:
lsystem_to_df <- function(xstart, ystart, string, angle, step) {
angle_rad <- angle * pi/180
# turtle state
x <- xstart; y <- ystart
dir <- 0
stack <- list()
segs <- list()
idx <- 1
chars <- strsplit(string, "")[[1]]
for (ch in chars) {
if (ch == "A") {
x_new <- x + step * cos(dir)
y_new <- y + step * sin(dir)
segs[[idx]] <- data.frame(x = round(x), y = round(y), xend = round(x_new), yend = round(y_new))
idx <- idx + 1
x <- x_new; y <- y_new
} else if (ch == "+") {
dir <- dir + angle_rad
} else if (ch == "-") {
dir <- dir - angle_rad
} else if (ch == "[") {
stack <- append(stack, list(list(x = x, y = y, dir = dir)))
} else if (ch == "]") {
state <- stack[[length(stack)]]
stack <- stack[-length(stack)]
x <- state$x; y <- state$y; dir <- state$dir
}
}
do.call(rbind, segs)
}
Our Christmas tree will consists of four (perfect :)) Sierpinski triangles of different sizes stacked to form a silhouette of a tree.
iters <- 8
rules <- list(
"A" = "B-A-B",
"B" = "A+B+A"
)
axiom <- "A"
ls <- l_system(axiom, rules, iters)
angle <- 60; step <- 2
tri1 <- lsystem_to_df(xstart = 100, ystart = 0, string = ls, angle = angle, step = step)
angle <- 60; step <- 1.5
tri2 <- lsystem_to_df(xstart = 100+2^5, ystart = 2^6*sqrt(3), string = ls, angle = angle, step = step)
angle <- 60; step <- 1.25
tri3 <- lsystem_to_df(xstart = 100+3*2^4, ystart = 7*2^4*sqrt(3), string = ls, angle = angle, step = step)
angle <- 60; step <- 1
tri4 <- lsystem_to_df(xstart = 100+2^6, ystart = 19*2^3*sqrt(3), string = ls, angle = angle, step = step)
sierpinski <- list(
geom_segment(data = tri1, aes(x = x, y = y, xend = xend, yend = yend), linewidth = 0.4, color = "#0C4F22")
,geom_segment(data = tri2, aes(x = x, y = y, xend = xend, yend = yend), linewidth = 0.4, color = "#0C4F22")
,geom_segment(data = tri3, aes(x = x, y = y, xend = xend, yend = yend), linewidth = 0.3, color = "#0C4F22")
,geom_segment(data = tri4, aes(x = x, y = y, xend = xend, yend = yend), linewidth = 0.3, color = "#0C4F22")
)
christmas_tree <- happy_background +
sierpinski
christmas_tree
lights2 <- function(x1, x2, y1, y2, colour) {
pts <- NA
x_coord = c(x1, x2)
y_coord <- c(y1, y2)
pts <- data.frame(x_coord, y_coord)
lights <- list(
geom_curve(data = pts, aes(x = x1, xend = x2, y = y1, yend = y2), linetype = "solid", colour = "gray", linewidth = 0.5, alpha = 0.5),
geom_curve(data = pts, aes(x = x1, xend = x2, y = y1, yend = y2), linetype = "dotted", colour = colour, linewidth = 3, alpha = 0.7),
geom_curve(data = pts, aes(x = x1, xend = x2, y = y1, yend = y2), linetype = "dotted", colour = "yellow", linewidth = 1)
)
return(lights)
}
happy_christmas_tree <- christmas_tree +
lights2(x1 = 145, x2 = 300, y1 = 80, y2 = 210, colour="deeppink") +
lights2(x1 = 165, x2 = 330, y1 = 180, y2 = 50, colour="deeppink") +
lights2(x1 = 165, x2 = 290, y1 = 180, y2 = 240, colour="deeppink") +
lights2(x1 = 180, x2 = 290, y1 = 290, y2 = 240, colour="deeppink") +
lights2(x1 = 180, x2 = 270, y1 = 290, y2 = 310, colour="deeppink") +
lights2(x1 = 200, x2 = 270, y1 = 335, y2 = 310, colour="deeppink") +
lights2(x1 = 200, x2 = 245, y1 = 335, y2 = 355, colour="deeppink")
happy_christmas_tree
present <- function(x0, y0, w, h, colour_box, colour_bow)
{
box <- NA
ribbon_v <- NA
ribbon_h <- NA
box <- data.frame(x_coord = x0 + c(0, 0, w, w),
y_coord = y0 + c(0, h, h, 0))
# ribbon width = 10% of the height
ribbon_w <- w * 0.10
ribbon_v <- data.frame(x_coord = x0 + c(w/2 - ribbon_w, w/2 - ribbon_w, w/2 + ribbon_w, w/2 + ribbon_w),
y_coord = y0 + c(0, h, h, 0))
ribbon_h <- data.frame(x_coord = x0 + c(0, 0, w, w),
y_coord = y0 + c(h/2 - ribbon_w, h/2 + ribbon_w, h/2 + ribbon_w, h/2 - ribbon_w))
bow_l <- data.frame(x_coord = x0 + c(0.5*w, 0.25*w, 0.15*w, 0.2*w),
y_coord = y0 + c(h, 1.5*h, 1.2*h, h))
bow_r <- data.frame(x_coord = x0 + c(0.5*w, 0.75*w, 0.85*w, 0.8*w),
y_coord = y0 + c(h, 1.5*h, 1.2*h, h))
gift <- list(
geom_polygon(data = box, aes(x_coord, y_coord), fill = colour_box, color = "black"),
geom_polygon(data = ribbon_v, aes(x_coord, y_coord), fill = colour_bow, color = "black"),
geom_polygon(data = ribbon_h, aes(x_coord, y_coord), fill = colour_bow, color = "black"),
geom_polygon(data = bow_l, aes(x_coord, y_coord), fill = colour_bow, color = "black"),
geom_polygon(data = bow_r, aes(x_coord, y_coord), fill = colour_bow, color = "black")
)
return(gift)
}
christmas_tree_with_gifts <- happy_christmas_tree +
present(x0 = 0, y0 = 0, w = 50, h = 50, colour_box = "red", colour_bow = "gold1") +
present(x0 = 50, y0 = 0, w = 60, h = 60, colour_box = "green", colour_bow = "cornsilk") +
present(x0 = 110, y0 = 0, w = 50, h = 50, colour_box = "skyblue", colour_bow = "yellow2") +
present(x0 = 160, y0 = 0, w = 20, h = 20, colour_box = "red", colour_bow = "darkgoldenrod1") +
present(x0 = 180, y0 = 0, w = 20, h = 20, colour_box = "white", colour_bow = "blueviolet") +
present(x0 = 200, y0 = 0, w = 20, h = 20, colour_box = "chocolate1", colour_bow = "firebrick1") +
present(x0 = 220, y0 = 0, w = 30, h = 30, colour_box = "brown1", colour_bow = "cadetblue3") +
present(x0 = 250, y0 = 0, w = 50, h = 50, colour_box = "dodgerblue3", colour_bow = "goldenrod") +
present(x0 = 300, y0 = 0, w = 60, h = 60, colour_box = "coral", colour_bow = "aliceblue") +
present(x0 = 360, y0 = 0, w = 40, h = 40, colour_box = "red", colour_bow = "blue") +
present(x0 = 400, y0 = 0, w = 50, h = 50, colour_box = "white", colour_bow = "deeppink3")
christmas_tree_with_gifts
library(showtext)
## Loading required package: sysfonts
## Loading required package: showtextdb
font_add_google("Pacifico", "pacifico")
showtext_auto()
greet <- function(size, x, y, col1, col2, alpha=1){
greeting <- list(
annotate("text", x = x, y = y, family = "pacifico", size = size, label = "Merry", colour = col1)
,annotate("text", x = x, y = y-40, family = "pacifico", size = size, label = "Christmas", colour = col2)
,annotate("text", x = x, y = y-80, family = "pacifico", size = size, label = "and a Happy", colour = col1)
,annotate("text", x = x, y = y-120, family = "pacifico", size = size, label = "New Year!", colour = col2)
)
}
greetings <- christmas_tree_with_gifts +
greet(size=10, x = 79, y = 319, col1 = "white", col2 = "white", alpha = 0.8) +
greet(size=10, x = 80, y = 320, col1 = "darkgreen", col2 = "red2")
greetings