As a kid, I loved making spirographs. I still do. Making them feels more like discovery than creativity, like finding hidden wings in the Mathematical Museum of Art. I have not yet found the point where spirographs no longer surprise me.
The surprising variety of forms generated by spirographs are manifestations of just one equation, the circular path troichoid. The shape of the spirograph depends on the radius of a fixed circle, radius of a cycling circle, and the distance of the pen from the center of the the cycling circle.
\[\begin{align} x (\theta) &= (R - r)\cos\theta + d\cos\left({R - r \over r}\theta\right)\\ y (\theta) &= (R - r)\sin\theta - d\sin\left({R - r \over r}\theta\right) \end{align} \]
Where
R is the radius of the fixed circle
r is the radius of the cycling circle
d is the distance of the pen from the center of the cycling circle
θ is the number of radians the cycling circle travels around the fixed circle
x(θ) is the position of x after the cycling circle travels θ radians
y(θ) is the position of y after the cycling circle travels θ radians
cycling_radius <- 1
fixed_radius <- 3
pen_radius <- 2
d_circle <- tibble(
x0 = c(0, fixed_radius - cycling_radius),
y0 = c(0, 0),
radius = c(fixed_radius, cycling_radius),
r_y = c(0, 0),
r_x = c(-fixed_radius / 2, fixed_radius - 1.5 * cycling_radius),
r_lab = c("Fixed\nRadius", "Cycling\nRadius"),
color = c("black", "royalblue")
)
d_segment <- tibble(
x = c(0, fixed_radius - cycling_radius, fixed_radius - cycling_radius),
y = c(0, 0, 0),
xend = c(-fixed_radius + 0.04, fixed_radius - cycling_radius * 2 + 0.04, fixed_radius - cycling_radius + pen_radius - 0.04),
yend = c(0, 0, 0),
color = c("black", "royalblue", "firebrick")
)
ggplot(data = d_circle) +
theme_void() +
ggforce::geom_circle(
aes(
x0 = x0,
y0 = y0,
r = radius,
color = color),
n = 1000) +
coord_equal() +
geom_text(
aes(
x = r_x,
y = r_y,
label = r_lab,
color = color),
vjust = 0.5,
nudge_y = 0.015,
angle = 0) +
annotate(
x = fixed_radius - cycling_radius + pen_radius / 2,
y = 0.015,
geom = "label",
color = "firebrick",
label = "Pen\nDistance",
label.size = 0,
label.padding = unit(3, "pt")) +
geom_segment(
data = d_segment,
aes(x = x, y = y, xend = xend, yend = yend, color = color),
geom = "segment",
linejoin = "mitre",
arrow = arrow(
length = unit(0.025, "npc"),
type = "closed",
angle = 15)) +
annotate(
x = fixed_radius - cycling_radius,
y = 0,
geom = "point",
color = "royalblue") +
scale_color_identity() +
theme(legend.position = "none")
Although I still like making spirographs by hand, I wanted to extend what could be done with the traditional spirograph. I wrote the spiro package in R to make images that would be impossible to create on paper.
I cannot usually predict what will happen when I play with the three primary numbers of the equation. However, once a certain combination strikes me as interesting, I play with cutting it into different color segments to see if something of further interest happens. Sometimes I merge many spirographs and spin them to see if the emerging patterns are pleasing.
Here I demonstrate what can be done with spiro package. I would love to see what you can do with it.
k <- 8
crossing(cycling_radius = 1:k, fixed_radius = k * 2 + 1) %>%
rowid_to_column("id") %>%
mutate(
colors = lacroix_palette("Coconut", n = k , "continuous"),
file = paste0("sdfds.", id, ".svg")
) %>%
select(-id) %>%
pmap(
spiro,
points_per_polygon = 2000,
draw_fills = F,
transparency = 0.9) %>%
image_merge(
output = "my_non_canonical_backstory.svg") %>%
add_background()
rainbow_colors <- hsv(
h = seq(1 / 16, 1, length.out = 16),
s = 0.7,
v = 0.7)
spiro(
fixed_radius = 16,
cycling_radius = 5,
pen_radius = 5,
file = "licorice_donut_vivisection.svg",
color_groups = 16,
color_cycles = 2,
points_per_polygon = 50,
colors = rainbow_colors,
transparency = 0.7) %>%
add_background_gradient(
colors = c("white", "black", "black", "white"),
stops = c(.27, .34, .93, 1),
rounding = 1,
radius = 1)
n <- 10
oslo_colors <- scico(
n = n,
palette = "oslo",
alpha = 0.9) %>%
rev()
spiro(
file = "oslo_aster.svg",
rotation = pi / 6,
points_per_polygon = 100) %>%
image_merge(
output = "oslo_aster.svg",
copies = n) %>%
add_fills(
colors = oslo_colors) %>%
image_scale(
scale = sqrt(0.75 ^ (seq(0, n - 1)))) %>%
image_spin(
rpm = 1:n + 1) %>%
add_background(
color = "black",
rounding = 1) %>%
add_restart()
set.seed(105)
k <- 15
bg_colors <- paste0("gray", sample(1:k, k))
bg_stops <- sort(runif(k))
spiro(
fixed_radius = 2 * 13 * 17,
cycling_radius = 3 * 11 * 19,
pen_radius = 171,
file = "but_for_the_darkness_nothing_shimmers.svg",
draw_fills = F,
line_width = 3,
color_groups = 380,
color_cycles = 31,
points_per_polygon = 100,
colors = c(
scico(60 * 2, palette = "lisbon", 0.8),
scico(40 * 2, palette = "cork", 0.25),
scico(20 * 2, palette = "lisbon", 1))) %>%
add_background_gradient(rounding = 0, colors = bg_colors)
k <- 36
files <- paste0("s", 1:k, ".svg")
pen_radii <- seq(3.8, 1.5, length.out = k)
alphas <- rep_len(c(0.85, rep(0.2, 4)), k)
colors <- rep_len(scico(6, palette = "devon"), k) %>%
alpha(., alpha = alphas)
tibble::tibble(
file = files,
pen_radius = pen_radii,
colors = colors) %>%
purrr::pmap_chr(
spiro,
fixed_radius = 7,
cycling_radius = 4,
rotation = -pi / 10,
points_per_polygon = 500,
draw_fills = T,
xlim = c(-7, 7),
ylim = c(-7, 7)) %>%
image_merge(
output = "youre_my_favorite.svg") %>%
add_lines(colors = c(rep(NA,k - 1), "gray")) %>%
image_rotate(degrees = (1:k / 2.5)) %>%
add_background_gradient(
colors = c(
"#FFFFFF",
"#26588E",
"#E5E3F9",
"#283568",
"#C8C3F3"),
radius = 1,
rounding = 1,
stops = c(0.42,0.93,0.96,0.97,1))
n <- 20
spiro(
fixed_radius = 4,
cycling_radius = 5,
pen_radius = 1,
file = "suspension_of_disbelief.svg") %>%
image_merge(
copies = n,
output = "suspension_of_disbelief.svg") %>%
add_fills(
transparency = 1 / n,
colors = "blue") %>%
image_scale(scale = seq(1,0.1,length.out = n)) %>%
image_spin(rpm = seq(0.5,10, length.out = n)) %>%
add_restart()
set.seed(23)
k <- 20
low <- 5
high <- 10
bg_colors <- paste0("gray", sample(low:high, k, replace = T))
bg_stops <- sort(runif(k, min = 0, max = .77))
spiro(
fixed_radius = 359,
cycling_radius = 261,
pen_radius = 40,
color_groups = 36,
color_cycles = 36,
draw_fills = F,
points_per_polygon = 20,
line_width = 3.5,
file = "ride_ahead_to_make_the_fire.svg",
colors = c(
div_gradient_pal(
low = "royalblue4",
mid = "black",
high = "firebrick4")(seq(0, 1, length.out = 18)),
div_gradient_pal(
low = "royalblue",
mid = "white",
high = "firebrick")(seq(0, 1, length.out = 18)))) %>%
add_background_gradient(rounding = 0, colors = bg_colors, stops = bg_stops) %>%
add_circle(color = "gray10", r = c(0.429, 0.48, 0.559,0.669,0.786, 0.886), line_width = 1.5)
my_purple <- scales::muted(scales::alpha("purple",alpha = 0.8))
tibble(
colors = c(my_purple, "black"),
fixed_radius = c(16, 15),
cycling_radius = c(15, 14),
file = c("purple.svg", "black.svg")) %>%
pmap(
spiro,
pen_radius = 1.5,
draw_fills = FALSE,
line_width = 4) %>%
image_merge(output = "violet_blackout.svg") %>%
image_spin(rpm = c(0.5, -0.5)) %>%
add_restart(
color = my_purple,
fill = "black") %>%
add_background()
spiro(
fixed_radius = 800,
cycling_radius = 677,
pen_radius = 100,
color_groups = 10,
color_cycles = 61,
windings = 677 * 0.5,
transparency = 1,
start_angle = 0,
points_per_polygon = 300,
colors = scico(n = 10, palette = "cork"),
draw_fills = F,
file = "nanoscale_predictions.svg"
) %>%
add_background_gradient(
colors = c("black", "black", "gray40"))
n <- 25
cc <- ochre_palettes$emu_woman_paired[c(6, 11, 2, 7, 9)] %>%
rep(5)
tibble::tibble(
fixed_radius = n + 2,
cycling_radius = 1:n,
pen_radius = 1:n + 0,
transparency = 0.85,
rotation = pi / 6,
colors = cc,
file = paste0("asdf", 1:n, ".svg")) %>%
purrr::pmap(spiro) %>%
image_merge(
output = "emu_woman_sunset.svg") %>%
image_scale(scale = seq(1, 0.1, length.out = n) ^ 0.85)
spiro(
file = "purple_midnight.svg",
fixed_radius = 800,
cycling_radius = 751,
pen_radius = 40,
color_groups = 4,
color_cycles = 2,
points_per_polygon = 5000,
colors = c(
"midnightblue",
"white",
"purple4",
"white")) %>%
add_lines(
colors = "black",
line_width = 0.15) %>%
add_background_gradient(
stops = c(0,0.25,0.63,0.67,0.70,1),
colors = c(
"black",
"purple4",
"black",
"midnightblue",
"black",
"gray20"))
set.seed(365)
k <- 12
bg_colors <- scico(31, palette = "vik") %>%
rev() %>%
scales::muted(., l = 7, c = 7) %>%
`[`(sample(1:31, k, replace = T))
bg_stops <- sort(runif(k))
spiro(
fixed_radius = 1231,
cycling_radius = 529,
pen_radius = 1233,
color_groups = 67,
color_cycles = 59,
windings = 101,
points_per_polygon = 100,
transparency = 1,
colors = rev(scico(67, palette = "vik")),
file = "skyscraper_sunrise.svg") %>%
add_background_gradient(bg_colors, stops = bg_stops)
k <- 80
spiro(3,1,0.5,
file = "asdf.svg",
color_groups = 3,
transparency = 0.5,
colors = c("#AC1014", "#C0C0C0", "#175C02"),
points_per_polygon = 100) %>%
image_merge(
copies = k,
output = "counterspin_triangles.svg") %>%
image_scale(scale = seq(1, 0.1, length.out = k)) %>%
image_spin(rpm = rep(c(1, -1), k / 2) * seq(1, 3, length.out = k)) %>%
add_background(rounding = 1) %>%
add_restart()
k <- 80
spiro(
4,
3,
3,
file = "asdf.svg",
color_groups = 4,
colors = rgb(
c(0, .1418, .2118, .7012),
c(.0039, .1608, .6392, 1),
c(.3059, 0.9569, .9922, .9647),
) ,
transparency = 0.5,
points_per_polygon = 100
) %>%
image_merge(
copies = k,
output = "illusively_elusive_allusion.svg") %>%
image_scale(scale = seq(1, 0.2, length.out = k)) %>%
image_spin(rpm = rep(c(1, -1), k / 2) * seq(1, 10, length.out = k)) %>%
add_restart()
spiro(
fixed_radius = 167,
cycling_radius = 173,
pen_radius = 14,
windings = 173,
color_groups = 52,
color_cycles = 72 ,
points_per_polygon = 50,
file = "blurry_eye.svg",
colors = scico(
n = 52,
alpha = 0.9,
palette = "roma")) %>%
image_merge(
copies = 6,
output = "blurry_metal_eye.svg") %>%
image_scale(seq(1, 0.95, length.out = 6)) %>%
add_background(rounding = 1)
c(spiro(
fixed_radius = 21,
cycling_radius = -20,
pen_radius = 35,
transparency = 0.2,
colors = "black",
file = "not_forgotten1.svg") %>%
add_lines(colors = "#FFFFFFAA", line_width = .25),
spiro(
fixed_radius = 21,
cycling_radius = -20,
pen_radius = 35,
transparency = .75,
colors = "black",
rotation = pi / 21,
file = "not_forgotten2.svg") %>%
add_lines(colors = "#FFFFFFAA", line_width = .5)) %>%
image_merge(output = "not_forgotten.svg") %>%
add_background_gradient(
rounding = 1,
radius = 1,
colors = c("lightcyan2",
rep(c("royalblue4", "lightcyan2"), 13),"royalblue4",
rep("white",2)),
stops = c(0,
0.05, 0.07,
0.11, 0.1578947,
0.2105263, 0.28,
0.3395, 0.39,
0.441, 0.485,
0.53, 0.571,
0.609, 0.6415,
0.682, 0.711,
0.745, 0.77,
0.80, 0.823,
0.84, 0.86,
0.88, 0.895,
0.905, 0.91,
0.92, 0.93, 1))
k <- 40
tibble(
fixed_radius = 5,
cycling_radius = 3,
pen_radius = seq(12, 3, length.out = k),
colors = viridis::viridis(k, option = "D", alpha = 0.15),
rotation = seq(5 * pi / k,0, length.out = k)) %>%
pmap_chr(spiro) %>%
image_merge(
output = "wanna_rollerskate.svg") %>%
image_rotate(degrees = -360 / 20) %>%
# image_spin(1:k / k) %>%
add_background()
ngroups <- 4 * 6
cc <- c(
rainbow(ngroups / 4, s = 0.9, v = 0.3),
rev(rainbow(ngroups / 4, s = 0.9, v = 0.3)),
rainbow(ngroups / 4, s = 0.3, v = .9),
rev(rainbow(ngroups / 4, s = 0.3, v = .9)))
cc[seq(2, ngroups, 2)] <- "black"
spiro(
fixed_radius = 199,
cycling_radius = 120,
pen_radius = 43,
color_groups = ngroups,
color_cycles = 1440 %/% ngroups,
points_per_polygon = 100,
transparency = .3,
draw_fills = T,
colors = cc,
file = "checkered_future.svg") %>%
add_background_gradient(
colors = c(
"black", "black",
"gray90", "black",
"gray95", "black",
"gray50"),
stops = c(
.00, .18,
.20, .3,
.50, .60,
1))
spiro(
fixed_radius = 1,
cycling_radius = sqrt(7),
pen_radius = sqrt(7),
windings = 450,
draw_fills = F,
colors = c(
scico(11, palette = "tofino", direction = 1),
scico(4, palette = "nuuk")),
color_groups = 16,
points_per_polygon = 100,
color_cycles = 450 * 8,
line_width = 0.75,
lend = 1,
file = "expensive_plaid.svg") %>%
add_background()
cc <- hcl(
h = seq(0,1,length.out = 52) * 220 + 70,
c = 50,
l = 28)
cc[1:14] <- hcl(
h = seq(0,1,length.out = 14) * 250 + 80,
c = 60,
l = 42)
# cc <- scales::muted(cc, l = 40)
spiro(
fixed_radius = 44,
cycling_radius = 13,
pen_radius = 13,
file = "asdf.svg",
colors = cc,
color_cycles = 4,
draw_fills = F,
color_groups = 4 * 13,
points_per_polygon = 15,
line_width = 0.3) %>%
spiro::image_merge(.,
output = "groovy_church.svg",
copies = 38) %>%
image_rotate(1:36 * 0.21) %>%
add_background_gradient(
colors = c("gray15", "black",
"black", "gray10", "black",
"black", "gray15"),
stops = c(0,0.27,
0.271, 0.5, .655,
0.656,1))
k <- 12
crossing(
fixed_radius = k,
cycling_radius = seq(1,k),
pen_radius = k) %>%
rowid_to_column("file") %>%
mutate(
file = paste0(file,".svg"),
colors = c(
scico(
n = nrow(.) / 2,
palette = "devon",
alpha = .9),
scico(
n = nrow(.) / 2,
palette = "devon",
alpha = .7))) %>%
pmap(
spiro,
draw_fills = T) %>%
rev() -> p
p[-1:-3] %>%
rev %>%
map2(
seq(1,0.5, length.out = length(.)) ^ 0.4 ,
image_scale) %>%
image_merge(
output = "yes_you_may.svg") %>%
add_lines(line_width = 0.2)
l <- 27
crossing(
fixed_radius = 17,
cycling_radius = 1:9,
pen_radius = 11
) %>%
mutate(colors = LaCroixColoR::lacroix_palette("Coconut", n = 9) %>% scales::alpha(0.9)) %>%
pmap(
spiro,
draw_fills = T,
xlim = c(-l, l),
ylim = c(-l, l),
color_cycles = 17 * 6,
points_per_polygon = 500
) %>%
image_merge(output = "foreglow.svg") %>%
add_background_gradient(
colors = rep(
LaCroixColoR::lacroix_palette("Coconut", 9) %>% colorspace::darken(.1),
3
),
stops = c(0.7 * seq(0, 1, length.out = l - 3), 0.78 , 0.88, 0.98)
)
n <- 36
tibble(
fixed_radius = 13,
cycling_radius = 11,
pen_radius = seq(1, n) / 2,
file = paste0("sp", 1:n, ".svg"),
colors = rep(scico(
n = n / 4,
alpha = 0.6,
palette = "tofino"),
times = 4)) %>%
pmap(spiro,
xlim = c(-20, 20),
ylim = c(-20, 20)) %>%
rev() %>%
image_merge(
output = "bending_is_not_a_compromise.svg") %>%
image_rotate(sqrt(1:n * 50)) %>%
add_background()
c(
spiro(
file = "restoration_agreement1.svg",
fixed_radius = 800,
cycling_radius = 677,
pen_radius = 101,
color_groups = 8,
color_cycles = 677 * 3 ,
windings = 677 * 0.5,
transparency = 1,
rotation = pi / 13,
points_per_polygon = 3,
colors = paste0("gray", c(92,93,94,95,12,13,14,15))
),
spiro(
file = "restoration_agreement2.svg",
fixed_radius = 800,
cycling_radius = 677,
pen_radius = 101,
color_groups = 8,
color_cycles = 677 * 3 ,
windings = 677 * 0.5,
transparency = 1,
points_per_polygon = 3,
colors = scico(n = 8, palette = "cork")
)) %>%
image_merge(output = "restoration_agreement.svg") %>%
add_background(color = "gray8")
n <- 36
spiro(fixed_radius = 3,
cycling_radius = 1,
file = "reconciliation491.svg",
colors = "#5262CA10",
points_per_polygon = 300) %>%
image_scale(scale = 0.7) %>%
image_merge(copies = n,
output = "reconciliation491.svg") %>%
add_lines(colors = "#5262CAAA", line_width = 0.5) %>%
image_spin(rpm = seq(1,n) / 12,
rotation_point = c(0.6135,0.6135)) %>%
image_shift(x = -81.5, y = 81.5) %>%
add_restart(color = colorspace::lighten("#5262CA", .5),
fill = colorspace::darken("#5262CA", .85)) %>%
add_background_gradient(colors = map(c(0.98, 0.98, 0.95, 0.8),
colorspace::darken,
col = "#5262CA"))
lim <- 5
k <- 36
tibble(
pen_radius = seq(3, 1, length.out = k),
colors = scico(n = k, palette = "cork") %>%
scales::muted(., l = 50, c = 40),
transparency = rep_len(c(rep(0.4, 3), 1), k),
line_width = rep_len(c(rep(0.5, 3), 1), k)
) %>%
pmap(
spiro,
fixed_radius = 5,
cycling_radius = 3,
rotation = -pi / 2,
xlim = c(-lim, lim),
ylim = c(-lim, lim),
draw_fill = F
) %>%
image_merge("convoluted_candor.svg", delete_input = T)
spiro(
fixed_radius = 8,
cycling_radius = 7,
pen_radius = 1.069,
color_groups = 8,
color_cycles = 2,
start_angle = 0,
rotation = -pi / 8 ,
transparency = .5,
points_per_polygon = 5000,
windings = 7 * 6,
file = "epistemological_modesty.svg",
colors = dutchmasters_pal(
palette = "milkmaid",
reverse = T)(8))
spiro(
fixed_radius = sqrt(13),
cycling_radius = sqrt(11),
pen_radius = sqrt(0.5),
windings = 401,
color_groups = 5,
points_per_polygon = 40,
color_cycles = 906,
colors = c("darkred","gray90","darkgreen","gray20","forestgreen"),
transparency = 0.55,
file = "christmas_wreath.svg") %>%
add_background_gradient(
rounding = 1,
radius = 1,
colors = c("white",rep(c("gray30","black"), 5),"white"),
stops = c(0.4,seq(.45,.9,length.out = 10),0.93))
n <- 10
spiro(
fixed_radius = 6,
cycling_radius = 11,
pen_radius = -6 ,
colors = "white",
file = "web_windows.svg",
draw_fills = F,
transparency = 0.9,
line_width = 0.75,
rotation = pi / 6,
points_per_polygon = 1000) %>%
image_merge(
output = "web_windows.svg",
copies = n) %>%
add_fills(viridis_pal(
end = 0.97,
alpha = 0.9)(n)) %>%
image_scale(scale = 1.07 * sqrt(0.8 ^ (seq(0, n - 1)))) %>%
image_spin(rpm = 1:n * 0.15) %>%
add_restart(
fill = "#00000000",
color = "white",
) %>%
add_background()
tibble(colors = c("#004D47", "#128277")) %>%
pmap(
spiro,
fixed_radius = pi,
cycling_radius = sqrt(8),
pen_radius = 0.5 * sqrt(8) / pi,
windings = 81,
start_angle = 0,
points_per_polygon = 40001,
transparency = 1) %>%
tibble(
input = .,
duration = c(7, 11)) %>%
pmap(image_animate,
attribute = "opacity",
values = c(0.2, 0.8, 0.2)) %>%
image_merge(
output = "colliding_scopes.svg",
delete_input = T) %>%
image_spin(rpm = c(0.1,-0.1)) %>%
add_restart()
n <- 60
spiro(3, 1, 1,
color_groups = 6,
colors = LaCroixColoR::lacroix_palettes$Tangerine[1,, drop = T] %>%
scales::alpha(0.7),
file = "delta.svg") %>%
image_merge(copies = n,
output = "the_increasing_returns_of_having_travelled.svg") %>%
image_scale(seq(0.0001,1, length.out = n) ^ (1/1.25)) %>%
image_rotate(seq(0,360 * 5.65, length.out = n)) %>%
spiro::add_lines(input = ., colors = "gray10") %>%
add_background_gradient(colors = c("gray30", "gray5","gray30","gray5"))
my_palette <-
scales::colour_ramp(c(
rgb(.74, .19, .22, .9),
rgb(.86, .76, .22, .2),
rgb(.91, .92, .92, .9),
rgb(.11, .22, .22, .95),
rgb(.22, .22, .22, .1),
rgb(.24, .22, .71, .9)
))
spiro(
fixed_radius = 226,
pen_radius = 235,
cycling_radius = 15,
points_per_polygon = 10,
file = "old_errors.svg",
color_groups = 40,
color_cycles = 61,
colors = my_palette(seq(0, 1, length.out = 40))
) %>%
add_background_gradient(colors = c("gray20", "white", "gray90", "gray30","gray5"),
stops = c(0, .1, 0.6, .65, 1))
my_colors <- c("#969594FF","#00000000","#00000000",
"#F9F8F7FF","#00000000","#00000000",
"#585756FF","#00000000","#00000000",
"#D0CFCDFF","#00000000","#00000000","#171615FF")
tibble(fixed_radius = 541,
cycling_radius = 601,
pen_radius = 191,
windings = 200,
color_groups = 20,
points_per_polygon = 40 * 19:23) %>%
pmap(spiro, colors = my_colors) %>%
image_merge(output = "nanaras.svg") %>%
image_scale(c(1.07, .56, .29, 0.15, 0.075)) %>%
image_rotate(degrees = seq(0, 80, 20)) %>%
add_background("black")
spiro(
fixed_radius = 800,
cycling_radius = 749,
pen_radius = 51,
color_groups = 4,
color_cycles = 2,
transparency = 0.25,
start_angle = 0,
points_per_polygon = 10000,
rotation = -pi / 8,
windings = 749,
file = "the_past_was_never_simple.svg",
colors = c(
"firebrick4",
"purple4",
"midnightblue",
"black")) %>%
add_background_gradient(
rounding = 0,
stops = c(0, .08, 0.083, .6, 0.7, 1),
colors = c(
"white",
"gray95",
"gray40",
"white",
"white",
"white"))
string_bezier(
file = "portal_to_the_80s.svg",
x = c(-1, 1, 0,-1, 1,-1, 0, 1,-1, 1),
y = c(0, 0, 1, 0, 0, 0,-1, 0, 0, 0),
color = c("#006B6B", "#FFFFFF", "black", "black", "black"),
n = 200,
lwd = 5,
ljoin = 1) %>%
add_background(color = "#108998")
library(ggforce)
k <- 30
r <- rep(0, k)
r[1:2] <- 1
for (i in 3:k) {
r[i] <- r[i - 1] * 1 / cos(pi / i)
}
xy <- function(n, r, dtheta) {
tibble(x = r * cos((0:n) * dtheta + pi / 2),
y = r * sin((0:n) * dtheta + pi / 2))
}
svg("superhero.svg", width = 8, height = 8)
par(pty = "s")
k <- 30
r <- rep(0, k)
r[1:2] <- 1
for (i in 3:k) {
r[i] <- r[i - 1] * 1 / cos(pi / i)
}
plot(
x = c(1, 2),
y = c(1, 2),
xlim = c(-8.7, 8.7),
ylim = c(-8.7, 8.7),
type = "n",
axes = F,
xlab = NA,
ylab = NA,
ann = F
)
bg <- "slateblue"
plotrix::draw.circle(
0,
0,
radius = 8.7,
col = bg,
border = NA,
nv = 200
)
tibble(n = 2:k, r = r[-1]) %>%
mutate(dtheta = 2 * pi / n,
d_xy = pmap(list(n, r, dtheta), xy)) %>%
select(-dtheta) %>%
arrange(-n) %>%
purrr::pwalk(., function(n, r, d_xy) {
plotrix::draw.circle(
0,
0,
radius = r,
col = paste0("gray", n * 3),
border = bg,
lwd = 2 * (2 / n) ^ (1 / 5),
nv = 200
)
if (n > 2)
polygon(
d_xy,
col = paste0("gray", 2),
border = bg,
lwd
= 2 * (2 / n) ^ (1 / 5)
)
})
dev.off()
spiro(
fixed_radius = 5,
cycling_radius = 1,
pen_radius = 11 / 3,
color_groups = 5,
colors = rep("gray95", 5),
start_angle = pi,
draw_fills = FALSE,
rotation = pi / 10,
file = "one_minute_reunion.svg",
points_per_polygon = 250) %>%
add_pathdot(
colors = "firebrick",
duration = c(2,3,4,5,6,10,12,15,20,30,60)) %>%
# add_restart(color = "#333333",fill = scales::alpha("gray95")) %>%
add_background("gray95")
When Ryan was learning about Least Common Multiples, he asked me if I ever used them outside of a math class. Here ya go, buddy!
What is the least common multiple of 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, and 60?
k <- 9
n_dots <- 120
cc <- lacroix_palette(
name = "Coconut",
n = n_dots) %>%
alpha(0.7)
s <- spiro(
fixed_radius = 9,
cycling_radius = 4,
pen_radius = 6,
draw_fills = F,
colors = alpha(rep("black", k), 0.01),
color_groups = k,
points_per_polygon = 50,
file = "asdf.svg")
for (i in seq(0, n_dots - 1)) {
s <- add_pathdot(
s,
delay = i / (n_dots / 12),
colors = cc[i + 1],
radius = 2,
duration = 32)
}
image_merge(
s,
copies = 5,
output = "sunrises_are_sunsets_recycled.svg") %>%
image_spin(rpm = 1:5 / 5) %>%
add_restart(color = "white", fill = "black") %>%
add_background()
tibble(
cycling_radius = c(-5,5),
color_cycles = c(64,32),
start_angle = c(0 * pi / 64 * 3, 20 * pi / 8),
line_width = c(4 * 0.68, 4),
file = c("s3.svg","s4.svg")) %>%
pmap(
spiro,
fixed_radius = 8,
pen_radius = 5,
color_groups = 64,
points_per_polygon = 2,
draw_fills = F,
ljoin = 2,
end_at_beginning = T,
colors = c(
viridis_pal()(32),
rev(viridis_pal()(32)))) %>%
tibble(input = ., scale = c(1,0.68)) %>%
pmap(image_scale) %>%
image_merge(
output = "graceful_exit.svg",
delete_input = T) %>%
add_background_gradient(colors = c("gray5", "gray25"))