Steal like an Rtist: Creative Coding in R

o.T. (carré noir)
Vera Molnár

Ijeamaka Anyene Fumagalli and Sharla Gelfand

posit::conf(2023)

O.T., Serigraph on woven paper

Vera Molnár, by Catherine Panchout via Getty Images

o.T. (carré noir): introduction to the shape

Orientation of the letter u

Composition of o.T. (carré noir)

Color palette

library(scales)

ot_colors <- c("#1a1617", "#e8e6e5")
show_col(ot_colors)

Creating Letter U

Creating polygons in {ggplot2}

Creating polygons in {ggplot2}

geom_polygon()

We will use the following aesthetics

  • coordinates of data (x, y)
  • color of the border of the polygon (color)
  • color of the inside of the polygon (fill)
  • which coordinates belong to which polygon (group)

Creating polygons in {ggplot2}

geom_polygon()

We will use the following aesthetics

  • coordinates of data (x, y)
  • color of the border of the polygon (color)
  • color of the inside of the polygon (fill)
  • which coordinates belong to which polygon (group)

geom_polygon() tips

Order of coordinates matters (a lot!)

Only coordinates of interest are the vertices

Coordinates of Letter U

Coordinates of Letter U

Go counterclockwise, starting from bottom left

Coordinates of Letter U

Only need coordinates from vertices in the polygon

Coordinates of Letter U

library(dplyr)
library(tidyr)

u_shape <-
  tribble(
    ~x,  ~y,
     0,   0,
     1,   0,
     1,   1,
   2/3,   1,
   2/3, 1/3,
   1/3, 1/3,
   1/3,   1,
     0,   1
  )

Coordinates of Letter U

library(dplyr)
library(tidyr)

u_shape <-
  tribble(
    ~x,  ~y,
     0,   0,
     1,   0,
     1,   1,
   2/3,   1,
   2/3, 1/3,
   1/3, 1/3,
   1/3,   1,
     0,   1
  )

Coordinates of Letter U

library(dplyr)
library(tidyr)

u_shape <-
  tribble(
    ~x,  ~y,
     0,   0,
     1,   0,
     1,   1,
   2/3,   1,
   2/3, 1/3,
   1/3, 1/3,
   1/3,   1,
     0,   1
  )

Coordinates of Letter U

library(dplyr)
library(tidyr)

u_shape <-
  tribble(
    ~x,  ~y,
     0,   0,
     1,   0,
     1,   1,
   2/3,   1,
   2/3, 1/3,
   1/3, 1/3,
   1/3,   1,
     0,   1
  )

Coordinates of Letter U

library(dplyr)
library(tidyr)

u_shape <-
  tribble(
    ~x,  ~y,
     0,   0,
     1,   0,
     1,   1,
   2/3,   1,
   2/3, 1/3,
   1/3, 1/3,
   1/3,   1,
     0,   1
  )

Coordinates of Letter U

library(dplyr)
library(tidyr)

u_shape <-
  tribble(
    ~x,  ~y,
     0,   0,
     1,   0,
     1,   1,
   2/3,   1,
   2/3, 1/3,
   1/3, 1/3,
   1/3,   1,
     0,   1
  )

Coordinates of Letter U

library(dplyr)
library(tidyr)

u_shape <-
  tribble(
    ~x,  ~y,
     0,   0,
     1,   0,
     1,   1,
   2/3,   1,
   2/3, 1/3,
   1/3, 1/3,
   1/3,   1,
     0,   1
  )

Coordinates of Letter U

library(dplyr)
library(tidyr)

u_shape <-
  tribble(
    ~x,  ~y,
     0,   0,
     1,   0,
     1,   1,
   2/3,   1,
   2/3, 1/3,
   1/3, 1/3,
   1/3,   1,
     0,   1
  )

Coordinates of Letter U

library(dplyr)
library(tidyr)

u_shape <-
  tribble(
    ~x,  ~y,
     0,   0,
     1,   0,
     1,   1,
   2/3,   1,
   2/3, 1/3,
   1/3, 1/3,
   1/3,   1,
     0,   1
  )

Plotting Letter U

library(ggplot2)

u_shape %>%
  ggplot() +
  geom_polygon(
    aes(
      x = x,
      y = y
    )
  )

Plotting Letter U

u_shape %>%
  ggplot() +
  geom_polygon(
    aes(
      x = x,
      y = y
    )
  ) +
  coord_fixed(
    xlim = c(0, 2),
    ylim = c(0, 2)
  )

Plotting Letter U

Adding flexibility

create_initial_shape <- function(x0, y0) {
  tribble(
          ~x,       ~y,
      x0 + 0,   y0 + 0,
      x0 + 1,   y0 + 0,
      x0 + 1,   y0 + 1,
    x0 + 2/3,   y0 + 1,
    x0 + 2/3, y0 + 1/3,
    x0 + 1/3, y0 + 1/3,
    x0 + 1/3,   y0 + 1,
      x0 + 0,   y0 + 1,
      x0 + 0,   y0 + 0
  )
}

Plotting Letter U

Adding flexibility

create_initial_shape(0, 0) %>%
  ggplot() +
  geom_polygon(
    aes(
      x = x,
      y = y
    )
  ) +
  coord_fixed(
    xlim = c(0, 2),
    ylim = c(0, 2)
  )

Plotting Letter U

Adding flexibility

create_initial_shape(1, 1) %>%
  ggplot() +
  geom_polygon(
    aes(
      x = x,
      y = y
    )
  ) +
  coord_fixed(
    xlim = c(0, 2),
    ylim = c(0, 2)
  )

Exercise 1

  1. Open file: exercises/03-ot/exercise.Rmd
  2. On your graphing paper, select from letters in exercises/03-ot/letters/ and draw out the letter.
  3. Label all the vertices of the shape with the coordinates.
  4. Create a tribble() with the coordinates of the letter you selected.
  5. Create a new function, create_letter(), that takes x0 and y0 as arguments and moves the shape according to those arguments.
  6. Plot your shape at the x0 and y0 of your choosing.
  7. Share your output on Github discussion board.
  8. Remember to save your file when you are done with the exercise.
10:00

Orientation of the Letter U

Exploring Geometric Abstraction

“My life is in squares, triangles, lines”

Throughout her career, Vera Molnár experimented with repetitions and variations of letters, especially the letter M – as in Molnár.

She explored the balance between order and chaos.

A light trigonometry intermission

y_new = -x_old & x_new = y_old

A light trigonometry intermission

y_new = -y_old & x_new = -x_old

A light trigonometry intermission

y_new = x_old & x_new = -y_old

A light trigonometry intermission

  • Step 1: Place shape
  • Step 2: Subtract point of rotation off of each vertex
  • Step 3: Rotate
  • Step 4: Add back point of rotation
  • Step 5: Correct coordinates to back at “original origin”

Applying trigonometry lesson

Step 1: Place shape

x0 <- 3
y0 <- 3

initial_u_shape <-
  create_initial_shape(x0, y0)

initial_u_shape %>%
  ggplot() +
  geom_polygon(
    aes(
      x = x,
      y = y
    )
  ) +
  geom_point(
    aes(
      x = x0,
      y = y0
    ),
    size = 5,
    color = "red"
  ) +
  coord_fixed(
    xlim = c(-1, 4),
    ylim = c(-1, 4)
  )

Applying trigonometry lesson

Step 2: Subtract point of rotation off of each vertex

x0 <- 3
y0 <- 3

initial_u_shape <-
  create_initial_shape(x0, y0) %>%
  mutate(
    x = x - x0,
    y = y - y0
  )

initial_u_shape %>%
  ggplot() +
  geom_polygon(
    aes(
      x = x,
      y = y
    )
  ) +
  geom_point(
    aes(
      x = x0,
      y = y0
    ),
    size = 5,
    color = "red"
  ) +
  coord_fixed(
    xlim = c(-1, 4),
    ylim = c(-1, 4)
  )

Applying trigonometry lesson

Step 3: Rotate

x0 <- 3
y0 <- 3

initial_u_shape <-
  create_initial_shape(x0, y0) %>%
  mutate(
    x = x - x0,
    y = y - y0
  ) %>%
  mutate(
    x_new = y,
    y_new = -x
  )

initial_u_shape %>%
  ggplot() +
  geom_polygon(
    aes(
      x = x_new,
      y = y_new
    )
  ) +
  geom_point(
    aes(
      x = x0,
      y = y0
    ),
    size = 5,
    color = "red"
  ) +
  coord_fixed(
    xlim = c(-1, 4),
    ylim = c(-1, 4)
  )

Applying trigonometry lesson

Step 4: Add back point of rotation

x0 <- 3
y0 <- 3

initial_u_shape <-
  create_initial_shape(x0, y0) %>%
  mutate(
    x = x - x0,
    y = y - y0
  ) %>%
  mutate(
    x_new = y,
    y_new = -x
  ) %>%
  mutate(
    x = x_new + x0,
    y = y_new + y0
  )

initial_u_shape %>%
  ggplot() +
  geom_polygon(
    aes(
      x = x,
      y = y
    )
  ) +
  geom_point(
    aes(
      x = x0,
      y = y0
    ),
    size = 5,
    color = "red"
  ) +
  coord_fixed(
    xlim = c(-1, 4),
    ylim = c(-1, 4)
  )

Applying trigonometry lesson

Step 5: Correct coordinates to back at “original origin”

x0 <- 3
y0 <- 3
shape_width <- 1

initial_u_shape <-
  create_initial_shape(x0, y0) %>%
  mutate(
    x = x - x0,
    y = y - y0
  ) %>%
  mutate(
    x_new = y,
    y_new = -x
  ) %>%
  mutate(
    x = x_new + x0,
    y = y_new + y0 + shape_width
  )

initial_u_shape %>%
  ggplot() +
  geom_polygon(
    aes(
      x = x,
      y = y
    )
  ) +
  geom_point(
    aes(
      x = x0,
      y = y0
    ),
    size = 5,
    color = "red"
  ) +
  coord_fixed(
    xlim = c(-1, 4),
    ylim = c(-1, 4)
  )

Turning lesson into a function

rotate_shape <- function(data, x0, y0, degrees, shape_width) {
  if (degrees == 90) {
    data %>%
      mutate(
        x = x - x0,
        y = y - y0
      ) %>%
      mutate(
        x_new = y,
        y_new = -x
      ) %>%
      mutate(
        x = x_new + x0,
        y = y_new + y0 + shape_width
      )
  } else if (degrees == 180) {
    data %>%
      mutate(
        x = x - x0,
        y = y - y0
      ) %>%
      mutate(
        x_new = -x,
        y_new = -y
      ) %>%
      mutate(
        x = x_new + x0 + shape_width,
        y = y_new + y0 + shape_width
      )
  } else if (degrees == 270) {
    data %>%
      mutate(
        x = x - x0,
        y = y - y0
      ) %>%
      mutate(
        x_new = -y,
        y_new = x
      ) %>%
      mutate(
        x = x_new + x0 + shape_width,
        y = y_new + y0
      )
  } else if (degrees == 0) {
    data
  }
}

Turning lesson[s] into function[s]

create_rotate_shape <- function(x0, y0, degrees, shape_width = 1) {
  output <-
    create_initial_shape(x0 = x0, y0 = y0) %>%
    rotate_shape(
      data = .,
      x0 = x0,
      y0 = y0,
      degrees = degrees,
      shape_width = shape_width
    )

  return(output)
}

Turning lesson[s] into function[s]

x0 <- 3
y0 <- 3

rotated_shape <-
  create_rotate_shape(
    x0 = x0,
    y0 = y0,
    degrees = 180
  )

ggplot(data = rotated_shape) +
  geom_polygon(
    aes(
      x = x,
      y = y
    )
  ) +
  coord_fixed(
    xlim = c(0, 5),
    ylim = c(0, 5)
  )

Exercise 2

  1. Reopen file if needed: exercises/03-ot/exercise.Rmd
  • If you closed the file after the previous exercise, you may need to first re-run the code chunks from Exercise 1.
  1. Run the code chunks to have access to the initiate_rotate_shape() function.
  2. Experiment with changing the x0, y0, and the degrees argument within initiate_rotate_shape() to rotate and move your letter.
  3. What happens if you change shape_width to be greater or smaller than 1? Why does this happen?
  4. Remember to save your file when you are done with the exercise.
05:00

Composition of piece

Creating the grid

ncol <- 10
nrow <- 10
shape_width <- 1
perimeter_width <- shape_width + .25

Creating the grid

ncol <- 10
nrow <- 10
shape_width <- 1
perimeter_width <- shape_width + .25

grid <-
  expand_grid(
    x = seq(0,
      by = perimeter_width,
      length.out = ncol
    ),
    y = seq(0,
      by = perimeter_width,
      length.out = nrow
    )
  )

grid
# A tibble: 100 × 2
       x     y
   <dbl> <dbl>
 1     0  0   
 2     0  1.25
 3     0  2.5 
 4     0  3.75
 5     0  5   
 6     0  6.25
 7     0  7.5 
 8     0  8.75
 9     0 10   
10     0 11.2 
# ℹ 90 more rows

Creating the grid

ncol <- 10
nrow <- 10
shape_width <- 1
perimeter_width <- shape_width + .25

grid <-
  expand_grid(
    x = seq(0,
      by = perimeter_width,
      length.out = ncol
    ),
    y = seq(0,
      by = perimeter_width,
      length.out = nrow
    )
  ) %>%
  mutate(y = if_else(x >= 5 * perimeter_width,
    y + perimeter_width / 2,
    y
  ))

grid
# A tibble: 100 × 2
       x     y
   <dbl> <dbl>
 1     0  0   
 2     0  1.25
 3     0  2.5 
 4     0  3.75
 5     0  5   
 6     0  6.25
 7     0  7.5 
 8     0  8.75
 9     0 10   
10     0 11.2 
# ℹ 90 more rows
grid %>%
  ggplot() +
  geom_point(
    aes(
      x = x,
      y = y
    )
  ) +
  coord_fixed()

Creating our system

library(purrr)

make_molnar_system <- function() {
  ncol <- 10
  nrow <- 10
  shape_width <- 1
  perimeter_width <- shape_width + .25

  grid <-
    expand_grid(
      x = seq(0, by = perimeter_width, length.out = ncol),
      y = seq(0, by = perimeter_width, length.out = nrow)
    ) %>%
    mutate(y = if_else(x >= 5 * perimeter_width,
      y + perimeter_width / 2,
      y
    ))

  output <-
    map_dfr(
      1:nrow(grid),
      function(i) {
        create_rotate_shape(
          x = grid$x[i],
          y = grid$y[i],
          degrees = 180,
          shape_width = shape_width
        )
      }
    )

  return(output)
}

Applying our system

initial_output <-
  make_molnar_system()

initial_output %>%
  ggplot() +
  geom_polygon(aes(
    x = x,
    y = y
  )) +
  coord_fixed()

Creating polygons in {ggplot2}

geom_polygon()

We will use the following aesthetics

  • coordinates of data (x, y)
  • color of the border of the polygon (color)
  • color of the inside of the polygon (fill)
  • which coordinates belong to which polygon (group)

Applying our system

Using group argument

make_molnar_system <- function() {
  ncol <- 10
  nrow <- 10
  shape_width <- 1
  perimeter_width <- shape_width + .25

  grid <-
    expand_grid(
      x = seq(0, by = perimeter_width, length.out = nrow),
      y = seq(0, by = perimeter_width, length.out = ncol)
    ) %>%
    mutate(y = if_else(x >= 5 * perimeter_width,
      y + perimeter_width / 2,
      y
    ))

  output <-
    map_dfr(
      1:nrow(grid),
      function(i) {
        bind_cols(
          group = i,
          create_rotate_shape(
            x = grid$x[i],
            y = grid$y[i],
            degrees = 180,
            shape_width = shape_width
          )
        )
      }
    )

  return(output)
}

Applying our system

initial_output <-
  make_molnar_system()

initial_output %>%
  ggplot() +
  geom_polygon(
    aes(
      x = x,
      y = y,
      group = group
    )
  ) +
  coord_fixed()

Applying our system

Incorporating rotation

make_molnar_system <- function(seed) {
  set.seed(seed)

  ncol <- 10
  nrow <- 10
  shape_width <- 1
  perimeter_width <- shape_width + .25

  grid <-
    expand_grid(
      x = seq(0, by = perimeter_width, length.out = nrow),
      y = seq(0, by = perimeter_width, length.out = ncol)
    ) %>%
    mutate(y = if_else(x >= 5 * perimeter_width,
      y + perimeter_width / 2,
      y
    ))

  output <-
    map_dfr(
      1:nrow(grid),
      function(i) {
        bind_cols(
          group = i,
          create_rotate_shape(
            x = grid$x[i],
            y = grid$y[i],
            degrees = 180,
            shape_width = shape_width
          )
        )
      }
    )

  return(output)
}

Applying our system

Incorporating rotation

make_molnar_system <- function(seed) {
  set.seed(seed)

  ncol <- 10
  nrow <- 10
  shape_width <- 1
  perimeter_width <- shape_width + .25

  grid <-
    expand_grid(
      x = seq(0, by = perimeter_width, length.out = nrow),
      y = seq(0, by = perimeter_width, length.out = ncol)
    ) %>%
    mutate(y = if_else(x >= 5 * perimeter_width,
      y + perimeter_width / 2,
      y
    ))

  output <-
    map_dfr(
      1:nrow(grid),
      function(i) {
        bind_cols(
          group = i,
          create_rotate_shape(
            x = grid$x[i],
            y = grid$y[i],
            degrees = sample(c(0, 90, 180, 270),
              size = 1
            ),
            shape_width = shape_width
          )
        )
      }
    )

  return(output)
}

Applying our system

Incorporating rotation

initial_output <-
  make_molnar_system(seed = 150)

initial_output %>%
  ggplot() +
  geom_polygon(
    aes(
      x = x,
      y = y,
      group = group
    )
  ) +
  coord_fixed()

Patterns vs. Pure Random

STRUCTURE À PARTIR DE LA LETTRE M, 2020

DE 432 OBLIQUES À 9 CARRÉS N°9, 2011

Patterns vs. Pure Random

Source: Saturn Clouds Icons by Ijeamaka

Source: Saturn Clouds Icons by Ijeamaka

Patterns vs. Pure Random

Source: Saturn Clouds Icons by Ijeamaka

Layout: 1, 2, 3, Skip, 4, 1, 2

Patterns vs. Pure Random

set.seed(150)
degree_pattern_options <- c(0, 90, 180, 270)

Patterns vs. Pure Random

set.seed(150)
degree_pattern_options <- c(0, 90, 180, 270)
degree_pattern <- sample(degree_pattern_options, size = 6, replace = TRUE)
degree_pattern
[1] 180  90 180   0 180 270

Patterns vs. Pure Random

make_molnar_system <-
  function(seed) {
    set.seed(seed)

    ncol <- 10
    nrow <- 10
    shape_width <- 1
    perimeter_width <- shape_width + .25

    degree_pattern_options <- c(0, 90, 180, 270)
    degree_pattern <- sample(degree_pattern_options, size = 6, replace = TRUE)

    grid <-
      expand_grid(
        x = seq(0,
          by = perimeter_width,
          length.out = nrow
        ),
        y = seq(0,
          by = perimeter_width,
          length.out = ncol
        )
      ) %>%
      mutate(y = if_else(x >= 5 * perimeter_width,
        y + perimeter_width / 2,
        y
      ))

    degree_pattern_exp <- rep(degree_pattern,
      length.out = nrow(grid)
    )

    output <-
      map_dfr(
        1:nrow(grid),
        function(i) {
          bind_cols(
            group = i,
            create_rotate_shape(
              x = grid$x[i],
              y = grid$y[i],
              degrees = degree_pattern_exp[i],
              shape_width = shape_width
            )
          )
        }
      )

    return(output)
  }

Patterns vs. Pure Random

final_output <-
  make_molnar_system(seed = 150)

final_output %>%
  ggplot() +
  geom_polygon(
    aes(
      x = x,
      y = y,
      group = group
    )
  ) +
  coord_fixed()

Patterns vs. Pure Random

final_output <-
  make_molnar_system(seed = 55)

final_output %>%
  ggplot() +
  geom_polygon(
    aes(
      x = x,
      y = y,
      group = group
    )
  ) +
  coord_fixed()

Final Touches

Final Piece

final_output %>%
  ggplot() +
  geom_polygon(
    aes(
      x = x,
      y = y,
      group = group
    ),
    fill = "#1a1617",
    color = "#1a1617"
  ) +
  coord_fixed()

Final Piece

final_output %>%
  ggplot() +
  geom_polygon(
    aes(
      x = x,
      y = y,
      group = group
    ),
    fill = "#1a1617",
    color = "#1a1617"
  ) +
  coord_fixed() +
  theme_void()

Final Piece

final_output %>%
  ggplot() +
  geom_polygon(
    aes(
      x = x,
      y = y,
      group = group
    ),
    fill = "#1a1617",
    color = "#1a1617"
  ) +
  coord_fixed() +
  theme_void() +
  theme(
    plot.background =
      element_rect(
        fill = "#e8e6e5",
        color = "#e8e6e5"
      )
  )

Final Piece

O.T., Serigraph on woven paper

O.T., Serigraph on woven paper, recreation

Exercise 3

  1. Reopen file if needed: exercises/03-ot/exercise.Rmd
  2. Review the code chunk that contains the make_exercise_molnar() function which encompasses the whole system.
  3. Update the arguments degree_pattern_options= and degree_sample_size= for the function make_exercise_molnar() to experiment with modifying the rotation patterns to one of your choice. (Degrees options are 0, 90, 80, 270)
  4. Save your image using ggsave() code provided and share on GitHub discussion board.
  5. If you have extra time, here are some suggestion for customizing your piece:
  • Experiment with colors in the fill= and color= in geom_polygon()
  • Experiment with colors in the plot.background in theme()
10:00

Recap

  • Created a shape using geom_polygon()
  • Explored the intricacies of geom_polygon() arguments: order matters and group is important!
  • Rotated a shape 0, 90, 180, and 270 degrees using trigonometry
  • Explored implementing pure randomness or randomness to create patterns