Documenting my attempt at recreating the art I saw in this
tweet
with R.
Then trying to further develop the idea
This code is not written for speed or efficiency - using spatial
libraries is certainly overkill, but it’s the first way I prototyped
the code and I haven’t felt the need to go back and change it!
library(tidyverse )
library(sf )
library(lwgeom )
library(wesanderson )
Psuedo code for function
Compute the (x,y) vertices of a regular polygon by rotating a
line of length radius
through equal angle steps about the
point (ox
, oy
)
Convert the vertices to an {sf}
POLYGON
Compute a random point inside the polygon if px
and py
is
not provided
Create an {sf}
MULTILINESTRING containing a line from the
point (px, py) to each vertex of the regular polygon
Use the MULTILINESTRINGS to plit the regular polygon into sub
polygons
# ' Return the {sf} polygons
# '
# ' @param n_sides number of sides of regular polygon
# ' @param offset_degrees offset angle for orientation of regular polygon
# ' @param ox origin of regular polygon x coordinate
# ' @param oy origin of regular polygon y coordinate
# ' @param radius radius of or regular polygon (distance from origin to polygon vertices)
# ' @param px origin point for the splitting lines x coordinate
# ' @param py origin point for the splitting lines y coordinate
split_poly <- function (n_sides , offset_degrees , ox , oy , radius , px = NULL , py = NULL ){
# Create polygon angles and vertex xy coords
a_step <- (2 * pi )/ n_sides
a <- seq(pi / 2 + offset_degrees * (pi / 180 ), by = a_step , l = n_sides )
x <- ox + cos(a ) * radius
y <- oy + sin(a ) * radius
# Create POLYGON
# Close polygon by making the last point the same as the first point
shape_polygon <- st_polygon(x = list (matrix (c(c(x , x [1 ]), c(y , y [1 ])), ncol = 2 )))
# Compute a random point inside the polygon if no px or py is provided
if (is.null(px ) || is.null(py )){
p_xy <- st_coordinates(st_sample(shape_polygon , 1 ))
px <- p_xy [1 ,1 ]
py <- p_xy [1 ,2 ]}
# Create MULTILINESTRING from each polygon vertex to the random point
lines <-
st_multilinestring(
lapply(
X = seq_along(a ),
FUN = function (b ) matrix (c(c(x [b ], px ), c(y [b ], py )), ncol = 2 )))
# Split the polygon based on the MULTILINESTRING
lwgeom :: st_split(shape_polygon , lines ) | > st_collection_extract(" POLYGON" )}
Explore some different setups
Create uniform square grid of x and y coordinates for the regular
polygon centers
Add values for the regular polygon number of sides, offset and
radius against each grid point
Run split_poly()
on each point
Assign a colour to each if the sub polygons created from each
regular polygon
Use the wesanderson
palette Zissou1
Plot the result
set.seed(1 )
crossing(ox = 1 : 5 , oy = 1 : 5 ) | >
mutate(
n_sides = 4 ,
offset_degrees = 45 ,
radius = 0.55 ,
g = pmap(
.l =
list (
ox = ox ,
oy = oy ,
n_sides = n_sides ,
offset_degrees = offset_degrees ,
radius = radius ),
.f = split_poly )) | >
unnest(cols = g ) | >
group_by(ox , oy ) | >
mutate(col = sample(wes_palettes $ Zissou1 , size = n(), replace = FALSE )) | >
st_as_sf() | >
ggplot()+
geom_sf(aes(fill = I(col )), col = NA )+
scale_x_continuous(expand = expansion(add = c(1 ,1 )))+
scale_y_continuous(expand = expansion(add = c(1 ,1 )))+
theme_void()+
theme(
legend.position = " " ,
panel.background = element_rect(color = NA , fill = " grey95" ))
Choose colours from any of the wesanderson
palettes
set.seed(4 )
crossing(ox = 1 : 5 , oy = 1 : 5 ) | >
mutate(
n_sides = 4 ,
offset_degrees = 45 ,
radius = 0.55 ,
g = pmap(
.l =
list (
ox = ox ,
oy = oy ,
n_sides = n_sides ,
offset_degrees = offset_degrees ,
radius = radius ),
.f = split_poly )) | >
unnest(cols = g ) | >
group_by(ox , oy ) | >
mutate(col = sample(unlist(wes_palettes ), size = n(), replace = FALSE )) | >
st_as_sf() | >
ggplot()+
geom_sf(aes(fill = I(col )), col = NA )+
scale_x_continuous(expand = expansion(add = c(1 ,1 )))+
scale_y_continuous(expand = expansion(add = c(1 ,1 )))+
theme_void()+
theme(
legend.position = " " ,
panel.background = element_rect(color = NA , fill = " grey95" ))
Larger grid and vary the regular polygon radius value based on
position
set.seed(2 )
crossing(ox = 1 : 10 , oy = 1 : 10 ) | >
mutate(
n_sides = 4 ,
offset_degrees = 45 ,
radius = scales :: rescale(sqrt(abs(ox - 5.5 )^ 2 + abs(oy - 5.5 )^ 2 ), c(0.5 , 0.3 )),
g = pmap(
.l =
list (
ox = ox ,
oy = oy ,
n_sides = n_sides ,
offset_degrees = offset_degrees ,
radius = radius ),
.f = split_poly )) | >
unnest(cols = g ) | >
group_by(ox , oy ) | >
mutate(col = sample(wes_palettes $ Darjeeling2 , size = n(), replace = FALSE )) | >
st_as_sf() | >
ggplot()+
geom_sf(aes(fill = I(col )), col = 1 )+
scale_x_continuous(expand = expansion(add = c(1 ,1 )))+
scale_y_continuous(expand = expansion(add = c(1 ,1 )))+
theme_void()+
theme(
legend.position = " " ,
panel.background = element_rect(color = NA , fill = sample(unlist(wes_palettes ), 1 )))
Gradually change the offset angle across the image
set.seed(1 )
crossing(ox = 1 : 5 , oy = 1 : 5 ) | >
mutate(
n_sides = 3 ,
offset_degrees = seq(0 , 180 , l = n()),
radius = 0.5 ,
g = pmap(
.l =
list (
ox = ox ,
oy = oy ,
n_sides = n_sides ,
offset_degrees = offset_degrees ,
radius = radius ),
.f = split_poly )) | >
unnest(cols = g ) | >
group_by(ox , oy ) | >
mutate(
a = st_area(g ),
col = sample(wes_palettes $ Rushmore , size = n(), replace = FALSE )) | >
st_as_sf() | >
ggplot()+
geom_sf(aes(fill = I(col )), col = 1 )+
scale_x_continuous(expand = expansion(add = c(1 ,1 )))+
scale_y_continuous(expand = expansion(add = c(1 ,1 )))+
theme_void()+
theme(
legend.position = " " ,
panel.background = element_rect(color = NA , fill = " grey95" ))
Random integer (3:5) number of sides for regular polygons
set.seed(1 )
crossing(oy = 1 : 15 , ox = 1 : 15 ) | >
mutate(
n_sides = sample(3 : 5 , size = n(), replace = TRUE ),
offset_degrees = runif(n(), 0 , 360 ),
radius = scales :: rescale(sqrt(abs(ox - 4 )^ 2 + abs(oy - 4 )^ 2 ), c(0.5 , 0.1 )),
g = pmap(
.l =
list (
ox = ox ,
oy = oy ,
n_sides = n_sides ,
offset_degrees = offset_degrees ,
radius = radius ),
.f = split_poly )) | >
unnest(cols = g ) | >
group_by(ox , oy ) | >
mutate(
a = st_area(g ),
col = sample(wes_palettes $ Moonrise3 , size = n(), replace = FALSE )) | >
st_as_sf() | >
ggplot()+
geom_sf(aes(fill = I(col )), col = 1 )+
scale_x_continuous(expand = expansion(add = c(1 ,1 )))+
scale_y_continuous(expand = expansion(add = c(1 ,1 )))+
theme_void()+
theme(
legend.position = " " ,
panel.background = element_rect(color = NA , fill = " grey95" ))
Use non integer number of sides
set.seed(1 )
crossing(oy = 1 : 7 , ox = 1 : 7 ) | >
mutate(
n_sides = seq(3 , 4 , l = n()),
offset_degrees = seq(0 , 90 , l = n()),
radius = 0.45 ,
g = pmap(
.l =
list (
ox = ox ,
oy = oy ,
n_sides = n_sides ,
offset_degrees = offset_degrees ,
radius = radius ),
.f = split_poly )) | >
unnest(cols = g ) | >
group_by(ox , oy ) | >
mutate(
a = st_area(g ),
col = sample(unlist(wes_palettes ), size = n(), replace = FALSE )) | >
st_as_sf() | >
ggplot()+
geom_sf(aes(fill = I(col )), col = 1 )+
scale_x_continuous(expand = expansion(add = c(1 ,1 )))+
scale_y_continuous(expand = expansion(add = c(1 ,1 )))+
theme_void()+
theme(
legend.position = " " ,
panel.background = element_rect(color = NA , fill = " grey95" ))
All shapes using the same px/py point
set.seed(1 )
crossing(oy = 1 : 19 , ox = 1 : 19 ) | >
mutate(
n_sides = 4 ,
offset_degrees = 45 ,
radius = 0.45 ,
px = 10 ,
py = 10 ,
g = pmap(
.l =
list (
ox = ox ,
oy = oy ,
px = px ,
py = py ,
n_sides = n_sides ,
offset_degrees = offset_degrees ,
radius = radius ),
.f = split_poly )) | >
unnest(cols = g ) | >
group_by(ox , oy ) | >
mutate(
a = st_area(g ),
col = sample(unlist(wes_palettes ), size = n(), replace = FALSE )) | >
st_as_sf() | >
ggplot()+
geom_sf(aes(fill = I(col )), col = NA )+
scale_x_continuous(expand = expansion(add = c(1 ,1 )))+
scale_y_continuous(expand = expansion(add = c(1 ,1 )))+
theme_void()+
theme(
legend.position = " " ,
panel.background = element_rect(color = NA , fill = " grey95" ))
set.seed(1 )
crossing(oy = 1 : 19 , ox = 1 : 19 ) | >
mutate(
n_sides = 4 ,
offset_degrees = seq(0 , 90 , l = n()),
radius = 0.45 ,
px = 10 ,
py = 10 ,
g = pmap(
.l =
list (
ox = ox ,
oy = oy ,
px = px ,
py = py ,
n_sides = n_sides ,
offset_degrees = offset_degrees ,
radius = radius ),
.f = split_poly )) | >
unnest(cols = g ) | >
group_by(ox , oy ) | >
mutate(
a = st_area(g ),
col = sample(unlist(wes_palettes ), size = n(), replace = FALSE )) | >
st_as_sf() | >
ggplot()+
geom_sf(aes(fill = I(col )), col = NA )+
scale_x_continuous(expand = expansion(add = c(1 ,1 )))+
scale_y_continuous(expand = expansion(add = c(1 ,1 )))+
theme_void()+
theme(
legend.position = " " ,
panel.background = element_rect(color = NA , fill = " grey95" ))
set.seed(1 )
crossing(oy = 1 : 7 , ox = 1 : 7 ) | >
mutate(
n_sides = 4 ,
offset_degrees = 45 ,
radius = 0.45 ,
px = 3 ,
py = 3.2 ,
g = pmap(
.l =
list (
ox = ox ,
oy = oy ,
px = px ,
py = py ,
n_sides = n_sides ,
offset_degrees = offset_degrees ,
radius = radius ),
.f = split_poly )) | >
unnest(cols = g ) | >
group_by(ox , oy ) | >
mutate(
a = st_area(g ),
col = sample(unlist(wes_palettes ), size = n(), replace = FALSE )) | >
st_as_sf() | >
ggplot()+
geom_sf(aes(fill = I(col )), col = 1 )+
scale_x_continuous(expand = expansion(add = c(1 ,1 )))+
scale_y_continuous(expand = expansion(add = c(1 ,1 )))+
theme_void()+
theme(
legend.position = " " ,
panel.background = element_rect(color = NA , fill = " grey95" ))
Vary the px/py point across the shapes
set.seed(1 )
crossing(oy = 1 : 7 , ox = 1 : 7 ) | >
mutate(
n_sides = 4 ,
offset_degrees = 45 ,
radius = 0.45 ,
g = pmap(
.l =
list (
ox = ox ,
oy = oy ,
px = ox + scales :: rescale(ox , to = c(- 0.2 , 0.2 )),
py = oy + scales :: rescale(oy , to = c(- 0.2 , 0.2 )),
n_sides = n_sides ,
offset_degrees = offset_degrees ,
radius = radius ),
.f = split_poly )) | >
unnest(cols = g ) | >
group_by(ox , oy ) | >
mutate(
a = st_area(g ),
col = sample(wes_palettes $ Zissou1 , size = n(), replace = FALSE )) | >
st_as_sf() | >
ggplot()+
geom_sf(aes(fill = I(col )), col = 1 )+
scale_x_continuous(expand = expansion(add = c(1 ,1 )))+
scale_y_continuous(expand = expansion(add = c(1 ,1 )))+
theme_void()+
theme(
legend.position = " " ,
panel.background = element_rect(color = NA , fill = " grey95" ))
crossing(oy = 1 : 10 , ox = 1 : 10 ) | >
mutate(
n_sides = 4 ,
offset_degrees = 45 ,
radius = 0.45 ,
g = pmap(
.l =
list (
ox = ox ,
oy = oy ,
px = 5.5 + cos(seq(0 , 2 * pi , l = n()))* 2 ,
py = 5.5 + sin(seq(0 , 2 * pi , l = n()))* 2 ,
n_sides = n_sides ,
offset_degrees = offset_degrees ,
radius = radius ),
.f = split_poly )) | >
unnest(cols = g ) | >
group_by(ox , oy ) | >
mutate(
a = st_area(g ),
col = sample(wes_palettes $ Zissou1 , size = n(), replace = FALSE )) | >
st_as_sf() | >
ggplot()+
geom_sf(aes(fill = I(col )), col = 1 )+
scale_x_continuous(expand = expansion(add = c(1 ,1 )))+
scale_y_continuous(expand = expansion(add = c(1 ,1 )))+
theme_void()+
theme(
legend.position = " " ,
panel.background = element_rect(color = NA , fill = " grey95" ))+
theme_bw()
Approximate circles with high number of regular polygon sides
Move the px/py point and colour the sub polygons by their area
set.seed(1 )
crossing(oy = 1 : 7 , ox = 1 : 7 ) | >
mutate(
n_sides = 100 ,
offset_degrees = 0 ,
radius = 0.45 ,
g = pmap(
.l =
list (
ox = ox ,
oy = oy ,
px = ox + scales :: rescale(ox , to = c(- 0.2 , 0.2 )),
py = oy + scales :: rescale(oy , to = c(- 0.2 , 0.2 )),
n_sides = n_sides ,
offset_degrees = offset_degrees ,
radius = radius ),
.f = split_poly )) | >
unnest(cols = g ) | >
group_by(ox , oy ) | >
mutate(a = st_area(g )) | >
st_as_sf() | >
ggplot()+
geom_sf(aes(fill = a ), col = NA )+
scale_x_continuous(expand = expansion(add = c(1 ,1 )))+
scale_y_continuous(expand = expansion(add = c(1 ,1 )))+
scale_fill_viridis_c(option = " mako" )+
theme_void()+
theme(
legend.position = " " ,
panel.background = element_rect(color = NA , fill = " grey5" ))
A nasty look at hexagons using st_make_grid()
set.seed(1 )
nx <- 10
ny <- 10
hex_centers <-
sf :: st_make_grid(
x = st_polygon(list (matrix (c(0 , 0 , nx , nx , 0 , 0 , ny , ny , 0 , 0 ), ncol = 2 ))),
n = c(nx , ny ),
what = " centers" ,
square = FALSE ,
flat_topped = FALSE ) | >
st_coordinates() | >
as_tibble() | >
rename(ox = X , oy = Y )
hex_polys <-
sf :: st_make_grid(
x = st_polygon(list (matrix (c(0 , 0 , nx , nx , 0 , 0 , ny , ny , 0 , 0 ), ncol = 2 ))),
n = c(nx , ny ),
square = FALSE ,
flat_topped = FALSE )
f <- 1
hex_centers | >
mutate(
n_sides = 6 ,
offset_degrees = 0 ,
radius = (1 / sqrt(3 ))* f ,
g = pmap(
.l =
list (
ox = ox ,
oy = oy ,
n_sides = n_sides ,
offset_degrees = offset_degrees ,
radius = radius ),
.f = split_poly )) | >
unnest(cols = g ) | >
group_by(ox , oy ) | >
mutate(col = sample(wes_palettes | > unlist(), size = n(), replace = FALSE )) | >
st_as_sf() | >
ggplot()+
geom_sf(aes(fill = I(col )), col = NA ) +
# geom_sf(data = hex_polys, fill = NA, col = 1) +
scale_x_continuous(expand = expansion(add = c(1 ,1 )))+
scale_y_continuous(expand = expansion(add = c(1 ,1 )))+
theme_void()+
theme(
legend.position = " " ,
panel.background = element_rect(color = NA , fill = " grey95" ))
f <- 0.85
hex_centers | >
mutate(
n_sides = 6 ,
offset_degrees = seq(0 , 45 , l = n()),
radius = (1 / sqrt(3 ))* f ,
g = pmap(
.l =
list (
ox = ox ,
oy = oy ,
px = 2 ,
py = 2 ,
n_sides = n_sides ,
offset_degrees = offset_degrees ,
radius = radius ),
.f = split_poly )) | >
unnest(cols = g ) | >
group_by(ox , oy ) | >
mutate(col = sample(wes_palettes | > unlist(), size = n(), replace = FALSE )) | >
st_as_sf() | >
ggplot()+
geom_sf(aes(fill = I(col )), col = NA ) +
# geom_sf(data = hex_polys, fill = NA, col = 1) +
scale_x_continuous(expand = expansion(add = c(1 ,1 )))+
scale_y_continuous(expand = expansion(add = c(1 ,1 )))+
theme_void()+
theme(
legend.position = " " ,
panel.background = element_rect(color = NA , fill = " grey95" ))
Some additonal experimentation with triangles
set.seed(4 )
crossing(ox = 1 : 9 , oy = 1 : 9 ) | >
mutate(
n_sides = 3 ,
offset_degrees = rep(c(0 , 180 ), length.out = n()),
radius = 0.45 ,
g = pmap(
.l =
list (
ox = ox ,
oy = oy ,
n_sides = n_sides ,
offset_degrees = offset_degrees ,
radius = radius ),
.f = split_poly )) | >
unnest(cols = g ) | >
group_by(ox , oy ) | >
mutate(col = sample(unlist(wes_palettes ), size = n(), replace = FALSE )) | >
st_as_sf() | >
ggplot()+
geom_sf(aes(fill = I(col )), col = 1 )+
scale_x_continuous(expand = expansion(add = c(1 ,1 )))+
scale_y_continuous(expand = expansion(add = c(1 ,1 )))+
theme_void()+
theme(
legend.position = " " ,
panel.background = element_rect(color = NA , fill = " grey95" ))
set.seed(4 )
crossing(ox = 1 : 9 , oy = 1 : 9 ) | >
mutate(
n_sides = 3 ,
offset_degrees = rep(c(0 , 90 , 180 , 270 ), length.out = n()),
radius = 0.45 ,
g = pmap(
.l =
list (
ox = ox ,
oy = oy ,
n_sides = n_sides ,
offset_degrees = offset_degrees ,
radius = radius ),
.f = split_poly )) | >
unnest(cols = g ) | >
group_by(ox , oy ) | >
mutate(col = sample(unlist(wes_palettes ), size = n(), replace = FALSE )) | >
st_as_sf() | >
ggplot()+
geom_sf(aes(fill = I(col )), col = 1 )+
scale_x_continuous(expand = expansion(add = c(1 ,1 )))+
scale_y_continuous(expand = expansion(add = c(1 ,1 )))+
theme_void()+
theme(
legend.position = " " ,
panel.background = element_rect(color = NA , fill = " grey95" ))