Script
Script.Rmd
This vignette contains one large script for all the code in this package. This file makes it easy if you want to change, adapt, or tear apart what happens here.
# Libraries
library(tidyverse)
library(tidyr)
library(purrr)
library(ambient)
library(mvtnorm)
library(gifski)
library(stringr)
# Base colors
get_color <- function(angle, percentage) {
vectors <- data.frame(
v1 = c(
0.99258009214842,
0,
-0.121592601216663
),
v2 = c(
0.0172593263893888,
0.989874705747223,
0.14089067596698
),
p = c(
0,
0,
74.8443331534229
)
)
v <- vectors[["p"]] +
(percentage / 100 * 63) * cos(angle * pi / 180) * vectors[["v1"]] +
(percentage / 100 * 63) * sin(angle * pi / 180) * vectors[["v2"]]
hue <- (atan2(v[2], v[1]) * 180 / pi) %% 360
chroma <- sqrt(v[1]^2 + v[2]^2)
luminance <- v[3]
hcl(hue, chroma, luminance, fixup = FALSE)
}
# Subset colors
get_color_subset <- function(center, width, angle, percentage) {
get_color(width * sin(angle * pi / 180) + center, percentage)
}
# Directional vectors
get_vectors <- function(points, seeds) {
vectors <- points %>%
mutate(
x_direction = gen_simplex(x,
y,
frequency = .01,
seed = seeds[1]
),
y_direction = gen_simplex(x,
y,
frequency = .01,
seed = seeds[2]
)
) %>%
mutate(vector_length = sqrt(x_direction^2 +
y_direction^2)) %>%
mutate(
x_direction = x_direction / vector_length,
y_direction = y_direction / vector_length
) %>%
select(-vector_length)
}
# Move points
move_points <- function(points, seeds) {
points <- points %>%
get_vectors(seeds) %>%
mutate(
x = x + x_direction * .5,
y = y + y_direction * .5,
time = time + 1
)
return(points)
}
# Set up anchor points
get_anchor_points <- function(seeds, size, anchor_layout,
hue_turn, color_scheme,
color_subset_center, color_subset_width) {
if (anchor_layout == "random") {
# random layout
points <- as.data.frame(rmvnorm(
n = size,
sigma = diag(size * 4 / (floor(log10(size)) + 1),
nrow = 2
)
)) %>% # sd is number 4/digits
rename(
x = V1,
y = V2
) %>%
mutate(id = row_number())
} else if (anchor_layout == "spiral") {
# spiral layout
golden <- ((sqrt(5) + 1) / 2) * (2 * pi)
points <- data.frame(
x = sqrt(seq(1, size)) * cos(golden * seq(1, size)) * 2.5,
y = sqrt(seq(1, size)) * sin(golden * seq(1, size)) * 2.5
) %>%
mutate(id = row_number())
} else {
# diamond grid
grid_width <- ifelse(size <= 1500 / 2, ceiling(sqrt(size)), floor(sqrt(size)))
points <- expand_grid(
x_start = seq(1, grid_width) - (grid_width / 2) - .5,
y_start = seq(1, grid_width) - (grid_width / 2) - .5
) %>%
mutate(
x = (x_start * cos(45 * pi / 180) -
y_start * sin(45 * pi / 180)) * 5,
y = (x_start * sin(45 * pi / 180) +
y_start * cos(45 * pi / 180)) * 5,
id = row_number()
) %>%
select(-x_start, -y_start)
}
if (color_scheme == "subset") {
points <- get_vectors(points, seeds) %>%
mutate(distance = gen_simplex(x,
y,
frequency = .01,
seed = seeds[3]
)) %>%
mutate(
angle =
(atan2(y_direction, x_direction) * 180 / pi) %%
360 + hue_turn,
percentage = pnorm(distance,
mean = 0,
sd(distance)
) * 100
) %>%
rowwise() %>%
mutate(
hex_color = get_color_subset(
color_subset_center,
color_subset_width,
angle,
percentage
),
time = 0
) %>%
ungroup() %>%
select(
id, x, y,
hex_color, percentage, time
)
} else {
points <- get_vectors(points, seeds) %>%
mutate(distance = gen_simplex(x,
y,
frequency = .01,
seed = seeds[3]
)) %>%
mutate(
angle =
(atan2(y_direction, x_direction) * 180 / pi) %%
360 + hue_turn,
percentage = pnorm(distance,
mean = 0,
sd(distance)
) * 100
) %>%
rowwise() %>%
mutate(
hex_color = get_color(angle, percentage),
time = 0
) %>%
ungroup() %>%
select(
id, x, y,
hex_color, percentage, time
)
}
return(points)
}
# Create the paths
get_paths <- function(points, seeds) {
paths <- accumulate(
.x = rep(list(seeds), 100),
.f = move_points,
.init = points
)
paths <- bind_rows(paths)
paths <- paths %>%
group_by(id) %>%
mutate(
xend = lead(x),
yend = lead(y)
) %>%
filter(!is.na(xend)) %>%
filter(time <= percentage)
}
get_point_paths <- function(points, paths) {
# Handle paths that were dropped because they didn't go anywhere
point_paths <- points %>%
anti_join(paths, by = "id") %>%
mutate(
hex_color = get_color(0, 0),
alpha_value = 1
)
}
# Create a png file
create_png <- function(seeds, size, anchor_layout, hue_turn, color_scheme,
color_subset_center, color_subset_width,
alpha_taper, output_file) {
points <- get_anchor_points(
seeds, size, anchor_layout,
hue_turn, color_scheme,
color_subset_center, color_subset_width
)
paths <- get_paths(points, seeds)
point_paths <- get_point_paths(points, paths)
if (alpha_taper == "start") {
paths <- paths %>%
group_by(id) %>%
mutate(alpha_value = 1 - (max(time) - time) /
(max(time) + 1)) %>%
ungroup()
} else if (alpha_taper == "end") {
paths <- paths %>%
group_by(id) %>%
mutate(alpha_value = 1 - (time / (max(time) + 1))) %>%
ungroup()
} else {
paths <- paths %>%
group_by(id) %>%
mutate(alpha_value = 1 - (abs(time - median(time)) /
(median(time) + 1))) %>%
ungroup()
}
axes_limits <- max(c(abs(c(
paths$x,
paths$y,
paths$xend,
paths$yend
))))
ggplot() +
geom_point(
data = point_paths,
aes(
x = x, y = y,
color = hex_color,
alpha = alpha_value
),
size = .25,
stroke = 0, shape = 16
) +
geom_segment(
data = paths,
aes(
x = x, y = y,
xend = xend, yend = yend,
color = hex_color,
alpha = alpha_value
),
lineend = "round",
linejoin = "round",
size = .15
) +
scale_color_identity() +
scale_alpha_identity() +
scale_x_continuous(limits = c(-axes_limits, axes_limits)) +
scale_y_continuous(limits = c(-axes_limits, axes_limits)) +
coord_equal() +
theme_void() +
theme(plot.background = element_rect(
color = "white",
fill = "white"
))
ggsave(
filename = output_file,
device = "png",
widt = 2.5,
height = 2.5
)
}
# Create a gif file
create_gif <- function(seeds, size, anchor_layout, hue_turn, color_scheme,
color_subset_center, color_subset_width,
movement, output_file) {
points <- get_anchor_points(
seeds, size, anchor_layout,
hue_turn, color_scheme,
color_subset_center, color_subset_width
)
paths <- get_paths(points, seeds)
point_paths <- get_point_paths(points, paths)
axes_limits <- max(c(abs(c(
paths$x,
paths$y,
paths$xend,
paths$yend
))))
tp_dr <- tempdir()
if (movement == "march") {
max_frame <- max(paths$time) + 9
for (frame in 0:max_frame) {
sub_paths <- paths %>%
ungroup() %>%
filter(time > frame - 10 & time <= frame) %>%
mutate(alpha_value = 1 - (max(time) - time) / 10)
if (frame <= 10) {
point_paths <- point_paths %>%
mutate(alpha_value = (10 - frame) / 10)
} else {
point_paths <- point_paths[0, ]
}
ggplot() +
geom_point(
data = point_paths,
aes(
x = x, y = y,
color = hex_color,
alpha = alpha_value
),
size = .25,
stroke = 0, shape = 16
) +
geom_segment(
data = sub_paths,
aes(
x = x, y = y,
xend = xend, yend = yend,
color = hex_color,
alpha = alpha_value
),
lineend = "round",
linejoin = "round",
size = .15
) +
scale_color_identity() +
scale_alpha_identity() +
scale_x_continuous(limits = c(-axes_limits, axes_limits)) +
scale_y_continuous(limits = c(-axes_limits, axes_limits)) +
coord_equal() +
theme_void() +
theme(plot.background = element_rect(
color = "white",
fill = "white"
))
ggsave(
filename = file.path(
tp_dr,
paste0("image_", str_pad(frame, 3, pad = "0"), ".png")
),
widt = 2.5,
height = 2.5
)
}
} else {
max_frame <- max(paths$time) * 2
for (frame in 0:max_frame) {
if (frame <= max_frame / 2) {
sub_paths <- paths %>%
filter(time <= frame)
} else {
sub_paths <- paths %>%
filter(time >= frame - max_frame / 2)
point_paths <- point_paths[0, ]
}
ggplot() +
geom_point(
data = point_paths,
aes(
x = x, y = y,
color = hex_color
),
size = .25,
stroke = 0, shape = 16
) +
geom_segment(
data = sub_paths,
aes(
x = x, y = y,
xend = xend, yend = yend,
color = hex_color
),
lineend = "round",
linejoin = "round",
size = .15
) +
scale_color_identity() +
scale_alpha_identity() +
scale_x_continuous(limits = c(-axes_limits, axes_limits)) +
scale_y_continuous(limits = c(-axes_limits, axes_limits)) +
coord_equal() +
theme_void() +
theme(plot.background = element_rect(
color = "white",
fill = "white"
))
ggsave(
filename = file.path(
tp_dr,
paste0("image_", str_pad(frame, 3, pad = "0"), ".png")
),
widt = 2.5,
height = 2.5
)
}
}
ggplot() +
scale_color_identity() +
scale_alpha_identity() +
scale_x_continuous(limits = c(-axes_limits, axes_limits)) +
scale_y_continuous(limits = c(-axes_limits, axes_limits)) +
coord_equal() +
theme_void() +
theme(plot.background = element_rect(
color = "white",
fill = "white"
))
ggsave(
filename = file.path(
tp_dr,
paste0("image_", str_pad(frame + 1, 3, pad = "0"), ".png")
),
widt = 2.5,
height = 2.5
)
imgs <- file.path(tp_dr, list.files(tp_dr, pattern = "^image_...\\.png$"))
gifski(imgs,
delay = 1 / 15,
gif_file = output_file,
width = 750,
height = 750,
progress = FALSE
)
# Clean up
unlink(imgs)
}
# Create a mp4 file
create_mp4 <- function(seeds, size, anchor_layout, hue_turn, color_scheme,
color_subset_center, color_subset_width,
movement, output_file) {
points <- get_anchor_points(
seeds, size, anchor_layout,
hue_turn, color_scheme,
color_subset_center, color_subset_width
)
paths <- get_paths(points, seeds)
point_paths <- get_point_paths(points, paths)
axes_limits <- max(c(abs(c(
paths$x,
paths$y,
paths$xend,
paths$yend
))))
tp_dr <- tempdir()
if (movement == "march") {
max_frame <- max(paths$time) + 9
for (frame in 0:max_frame) {
sub_paths <- paths %>%
ungroup() %>%
filter(time > frame - 10 & time <= frame) %>%
mutate(alpha_value = 1 - (max(time) - time) / 10)
if (frame <= 10) {
point_paths <- point_paths %>%
mutate(alpha_value = (10 - frame) / 10)
} else {
point_paths <- point_paths[0, ]
}
ggplot() +
geom_point(
data = point_paths,
aes(
x = x, y = y,
color = hex_color,
alpha = alpha_value
),
size = .25,
stroke = 0, shape = 16
) +
geom_segment(
data = sub_paths,
aes(
x = x, y = y,
xend = xend, yend = yend,
color = hex_color,
alpha = alpha_value
),
lineend = "round",
linejoin = "round",
size = .15
) +
scale_color_identity() +
scale_alpha_identity() +
scale_x_continuous(limits = c(-axes_limits, axes_limits)) +
scale_y_continuous(limits = c(-axes_limits, axes_limits)) +
coord_equal() +
theme_void() +
theme(plot.background = element_rect(
color = "white",
fill = "white"
))
ggsave(
filename = file.path(
tp_dr,
paste0("image_", str_pad(frame, 3, pad = "0"), ".png")
),
widt = 2.5,
height = 2.5
)
}
} else {
max_frame <- max(paths$time) * 2
for (frame in 0:max_frame) {
if (frame <= max_frame / 2) {
sub_paths <- paths %>%
filter(time <= frame)
} else {
sub_paths <- paths %>%
filter(time >= frame - max_frame / 2)
point_paths <- point_paths[0, ]
}
ggplot() +
geom_point(
data = point_paths,
aes(
x = x, y = y,
color = hex_color
),
size = .25,
stroke = 0, shape = 16
) +
geom_segment(
data = sub_paths,
aes(
x = x, y = y,
xend = xend, yend = yend,
color = hex_color
),
lineend = "round",
linejoin = "round",
size = .15
) +
scale_color_identity() +
scale_alpha_identity() +
scale_x_continuous(limits = c(-axes_limits, axes_limits)) +
scale_y_continuous(limits = c(-axes_limits, axes_limits)) +
coord_equal() +
theme_void() +
theme(plot.background = element_rect(
color = "white",
fill = "white"
))
ggsave(
filename = file.path(
tp_dr,
paste0("image_", str_pad(frame, 3, pad = "0"), ".png")
),
widt = 2.5,
height = 2.5
)
}
}
ggplot() +
scale_color_identity() +
scale_alpha_identity() +
scale_x_continuous(limits = c(-axes_limits, axes_limits)) +
scale_y_continuous(limits = c(-axes_limits, axes_limits)) +
coord_equal() +
theme_void() +
theme(plot.background = element_rect(
color = "white",
fill = "white"
))
ggsave(
filename = file.path(
tp_dr,
paste0("image_", str_pad(frame + 1, 3, pad = "0"), ".png")
),
widt = 2.5,
height = 2.5
)
imgs <- file.path(tp_dr, list.files(tp_dr, pattern = "^image_...\\.png$"))
av_encode_video(imgs,
output = output_file,
framerate = 15,
verbose = FALSE)
# Clean up
unlink(imgs)
}
The following snippet uses code from the prior section. This lists out a file for each of the different options for png and mp4.
## Test script
set.seed(1)
# 2 options for full or subset color scheme
# 3 options for anchor layout
# 5 options for (3) png alpha taper + (2) gif movement
for (i in seq(1, 2 * 3 * 5)) {
seeds <- sample(1:10000, 3)
size <- sample(seq(50, 1500), 1, replace = TRUE)
anchor_layout <- c("random", "spiral", "grid")[i %% 3 + 1]
hue_turn <- runif(1, 0, 360)
color_scheme <- c("full", "subset")[i %% 2 + 1]
color_subset_center <- runif(1, 0, 360)
color_subset_width <- runif(1, 30, 90)
movement <- c("", "march", "", "glide", "")[i %% 5 + 1]
alpha_taper <- c("start", "", "end", "", "both")[i %% 5 + 1]
if ((i %% 5 + 1) %in% c(1, 3, 5)) {
save_name <- paste0(
color_scheme, "_",
anchor_layout, "_",
alpha_taper, "_",
i, ".png"
)
create_png(
seeds, size, anchor_layout, hue_turn,
color_scheme, color_subset_center,
color_subset_width, alpha_taper,
save_name
)
} else {
save_name <- paste0(
color_scheme, "_",
anchor_layout, "_",
movement, "_",
i, ".mp4"
)
create_gif(
seeds, size, anchor_layout, hue_turn,
color_scheme, color_subset_center,
color_subset_width, movement,
save_name
)
}
}