Steal like an Rtist: Creative Coding in R

Infinite Truchet
Roni Kaufman

Ijeamaka Anyene Fumagalli and Sharla Gelfand

posit::conf(2023)

Infinite Truchet by Roni Kaufman

  • Roni Kaufman is a self-described, creative coder and generative artist
  • He regularly shares the code to his projects on openprocessing.org. This sketch is available at Open Processing
  • Released under a CC BY-NC-SA 3.0 license (share and remix with attribution for noncommercial purposes)

Infinite Truchet: Color palette

library(scales)
roni_colors <- c(
  "#050505", "#fc8405", "#fffbe6"
)
show_col(roni_colors)

Infinite Truchet: Composition

Infinite Truchet: Tiles

Introduction to tiles

Introduction to tiles

Introduction to tiles

Introduction to tiles

Truchet Tiles

The Father of Truchet Tiles

Jean Truchet, Father Sebastien Truchet
  • French Dominican priest, 1657–1729
  • Studied typography and invented the first typographic point
  • Published “Memoir sur les combinaisons” that invented the idea of truchet tiles

Infinitely Variable Tiling Patterns: From Truchet to Sol LeWitt Revisited

“Memoir sur les combinaisons”

LES PLANCHES DE PAVAGES DE TRUCHET by Jacques André
  • Studied a pattern where square tiles are split by diagonal lines into two triangles with contrasting colors.
  • By placing these tiles adjacent to each other but in different orientations, many different patterns can be formed.

Truchet Tiles

LES PLANCHES DE PAVAGES DE TRUCHET by Jacques André

Initially, the underlying idea of truchet tiles is that adjacent tiles of right triangles make larger connecting patterns.

Infinitely Variable Tiling Patterns: From Truchet to Sol LeWitt Revisited

Truchet Tiles

Cyril Smith, 1987, analyzed truchet tiles and abstracted them into 1) diagonal lines, then 2) two arcs starting and ending at midpoints of the tiles edges.

Robert J. Krawczyk, 2020

Infinitely Variable Tiling Patterns: From Truchet to Sol LeWitt Revisited

Multi-Scale Truchet Patterns

Christopher Carlson

70s Pop Series One, Daniel Catt

Exercise 1

  • Open the exercises/05-infinite-truchet/truchet_tiles_examples directory. These are different examples of truchet tiles. Choose one example.
  • Draw four squares on your graphing paper in a 2x2 format (see above).
  • Draw four different rotations of your selected truchet tile example.
  • Take a picture of your piece when finished and share it on the Github Discussions page.
10:00

Who actually needs trigonometric functions?

Creating arcs in {ggforce}

geom_arc_bar()

We will use the following aesthetics

  • x0, y0: x, y coordinate, center of arc
  • start: radian value of where arc starts
  • end: radian value of where arc ends
  • r0: radius of inner arc
  • r: radius of outer arc
  • color, fill: color of arc

r vs r0

  • In this example, we have r0 as the inner radius and r as the outer radius. {ggforce} allows you to also have it be vice versa.

  • Most important thing is to remain consistent in your definitions across your code.

The simplifying of trigonometry

Creating arcs in {ggforce}

geom_arc_bar()

We will use the following aesthetics

  • x0, y0: x, y coordinate, center of arc
  • start: radian value of where arc starts
  • end: radian value of where arc ends
  • r0: radius of inner arc
  • r: radius of outer arc
  • color, fill: color of arc

geom_arc_bar() arguments: x0, y0, start, end

geom_arc_bar() arguments: x0, y0, start, end

Always travel counterclockwise, starting in bottom left

Corner 1

library(dplyr)

x <- 0
y <- 0
sq_width <- 15

tribble(
           ~x0,          ~y0,  ~start,    ~end,
  )

Corner 1: x0, y0

library(dplyr)

x <- 0
y <- 0
sq_width <- 15

tribble(
           ~x0,          ~y0,  ~start,    ~end,
             x,            y
  )

Corner 1: start, end

x <- 0
y <- 0
sq_width <- 15

tribble(
           ~x0,          ~y0,  ~start,    ~end,
             x,            y,       0,    pi/2
  )

Corner 2

x <- 0
y <- 0
sq_width <- 15

tribble(
           ~x0,          ~y0,  ~start,    ~end,
             x,            y,       0,    pi/2
  )

Corner 2: x0, y0

x <- 0
y <- 0
sq_width <- 15

tribble(
           ~x0,          ~y0,  ~start,    ~end,
             x,            y,       0,    pi/2,
  x + sq_width,            y  
  )

Corner 2: start, end

x <- 0
y <- 0
sq_width <- 15

tribble(
           ~x0,          ~y0,  ~start,    ~end,
             x,            y,       0,    pi/2,
  x + sq_width,            y,  3*pi/2,    2*pi
  )

Corner 3

x <- 0
y <- 0
sq_width <- 15

tribble(
           ~x0,          ~y0,  ~start,    ~end,
             x,            y,       0,    pi/2,
  x + sq_width,            y,  3*pi/2,    2*pi
  )

Corner 3: x0, y0

x <- 0
y <- 0
sq_width <- 15

tribble(
           ~x0,          ~y0,  ~start,    ~end,
             x,            y,       0,    pi/2,
  x + sq_width,            y,  3*pi/2,    2*pi,
  x + sq_width, y + sq_width,      
  )

Corner 3: start, end

x <- 0
y <- 0
sq_width <- 15

tribble(
           ~x0,          ~y0,  ~start,    ~end,
             x,            y,       0,    pi/2,
  x + sq_width,            y,  3*pi/2,    2*pi,
  x + sq_width, y + sq_width,      pi,  3*pi/2
  )

Corner 4

x <- 0
y <- 0
sq_width <- 15

tribble(
           ~x0,          ~y0,  ~start,    ~end,
             x,            y,       0,    pi/2,
  x + sq_width,            y,  3*pi/2,    2*pi,
  x + sq_width, y + sq_width,      pi,  3*pi/2
  )

Corner 4: x0, y0

x <- 0
y <- 0
sq_width <- 15

tribble(
           ~x0,          ~y0,  ~start,    ~end,
             x,            y,       0,    pi/2,
  x + sq_width,            y,  3*pi/2,    2*pi,
  x + sq_width, y + sq_width,      pi,  3*pi/2,
             x, y + sq_width
  )

Corner 4: start, end

x <- 0
y <- 0
sq_width <- 15

tribble(
           ~x0,          ~y0,  ~start,    ~end,
             x,            y,       0,    pi/2,
  x + sq_width,            y,  3*pi/2,    2*pi,
  x + sq_width, y + sq_width,      pi,  3*pi/2,
             x, y + sq_width,    pi/2,      pi
  )

Wrap it up in a function

set_params <- function(x, y, sq_width) {
  tribble(
            ~x0,          ~y0,  ~start,    ~end,
              x,            y,       0,    pi/2,
   x + sq_width,            y,  3*pi/2,    2*pi,
   x + sq_width, y + sq_width,      pi,  3*pi/2,
              x, y + sq_width,    pi/2,      pi
  )
  }

Exercise 2

  1. Open the file exercise/05-infinite-truchet/exercise-2.Rmd
  2. Run the code chunk to have access to the set_incorrect_params() function and then the code chunk that plots the output of the function.
  3. As the title suggests, one of the corners in the plot has the incorrect parameters for the start and end arguments for geom_arc_bar(). Create a new function called set_correct_params() that fixes this mistake.
  4. Plot the output of your function to confirm, you corrected the mistake.
07:00

Building a System

Number of Arcs

Tile Type 1

Tile Type 2

Update set_params()

Number of Arcs

set_params <-
  function(x, y, sq_width, tile_type) {
    tile <- tribble(
           ~x0,          ~y0,  ~start,    ~end,
             x,            y,       0,    pi/2,
  x + sq_width,            y,  3*pi/2,    2*pi,
  x + sq_width, y + sq_width,      pi,  3*pi/2,
             x, y + sq_width,    pi/2,      pi
  )
    if (tile_type == 1) {
       tile %>%
        mutate(num_arcs = c(3, 4, 3, 4))
    }
  }

🔔 start bottom left, travel counter clockwise!

Tile Type 1

Update set_params()

Number of Arcs

set_params <-
  function(x, y, sq_width, tile_type) {
    tile <- tribble(
           ~x0,          ~y0,  ~start,    ~end,
             x,            y,       0,    pi/2,
  x + sq_width,            y,  3*pi/2,    2*pi,
  x + sq_width, y + sq_width,      pi,  3*pi/2,
             x, y + sq_width,    pi/2,      pi
  )
    if (tile_type == 1) {
       tile %>%
        mutate(num_arcs = c(3, 4, 3, 4))
    } else if (tile_type == 2) {
      tile %>%
        mutate(num_arcs = c(5, 2, 5, 2))
    }
  }

🔔 start bottom left, travel counter clockwise!

Tile Type 2

set_params() output

set_params(
  x = 0,
  y = 0,
  sq_width = 15,
  tile_type = 1
)
# A tibble: 4 × 5
     x0    y0 start   end num_arcs
  <dbl> <dbl> <dbl> <dbl>    <dbl>
1     0     0  0     1.57        3
2    15     0  4.71  6.28        4
3    15    15  3.14  4.71        3
4     0    15  1.57  3.14        4

This allows us to create one tile of width 15 that has the bottom left corner at x = 0, y = 0

Creating the grid

sq_width <- 15
ncol <- 4
nrow <- 4

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

Creating the grid

sq_width <- 15
ncol <- 4
nrow <- 4

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

grid
# A tibble: 16 × 2
       y     x
   <dbl> <dbl>
 1     0     0
 2     0    15
 3     0    30
 4     0    45
 5    15     0
 6    15    15
 7    15    30
 8    15    45
 9    30     0
10    30    15
11    30    30
12    30    45
13    45     0
14    45    15
15    45    30
16    45    45

Tile selection

Recreating Infinite Truchet

sq_width <- 15
ncol <- 4
nrow <- 4

grid <-
  expand_grid(
    y = seq(
      from = 0,
      by = sq_width,
      length.out = nrow
    ),
    x = seq(
      from = 0,
      by = sq_width,
      length.out = ncol
    )
  ) %>%
  mutate(
    tile_type =
      c(
        1, 1, 2, 2,
        1, 1, 1, 1,
        1, 2, 1, 1,
        1, 1, 1, 1
      )
  )

grid
# A tibble: 16 × 3
       y     x tile_type
   <dbl> <dbl>     <dbl>
 1     0     0         1
 2     0    15         1
 3     0    30         2
 4     0    45         2
 5    15     0         1
 6    15    15         1
 7    15    30         1
 8    15    45         1
 9    30     0         1
10    30    15         2
11    30    30         1
12    30    45         1
13    45     0         1
14    45    15         1
15    45    30         1
16    45    45         1

Creating the grid

Incorporating the element of chance

sq_width <- 15
ncol <- 4
nrow <- 4

grid <-
  expand_grid(
    y = seq(
      from = 0,
      by = sq_width,
      length.out = nrow
    ),
    x = seq(
      from = 0,
      by = sq_width,
      length.out = ncol
    )
  ) %>%
  mutate(
    tile_type =
      sample(c(1, 2),
        size = n(),
        replace = TRUE
      )
  )

grid
# A tibble: 16 × 3
       y     x tile_type
   <dbl> <dbl>     <dbl>
 1     0     0         2
 2     0    15         1
 3     0    30         2
 4     0    45         2
 5    15     0         2
 6    15    15         2
 7    15    30         2
 8    15    45         2
 9    30     0         2
10    30    15         1
11    30    30         2
12    30    45         2
13    45     0         1
14    45    15         2
15    45    30         1
16    45    45         2

Creating our system

library(purrr)

truchet_tiles <-
  function(seed) {
    ncol <- 4
    nrow <- 4
    sq_width <- 15

    set.seed(seed)

    grid <-
      expand_grid(
        y = seq(from = 0, by = sq_width, length.out = ncol),
        x = seq(from = 0, by = sq_width, length.out = nrow)
      ) %>%
      mutate(tile_type = sample(c(1, 2), size = n(), replace = TRUE))

    params_grid <-
      map_dfr(
        1:nrow(grid),
        function(i) {
          set_params(
            x = grid$x[i],
            y = grid$y[i],
            sq_width = sq_width,
            tile_type = grid$tile_type[i]
          )
        }
      )

    return(params_grid)
  }

Applying our system

output_grid <-
  truchet_tiles(seed = 150)

output_grid
# A tibble: 64 × 5
      x0    y0 start   end num_arcs
   <dbl> <dbl> <dbl> <dbl>    <dbl>
 1     0     0  0     1.57        3
 2    15     0  4.71  6.28        4
 3    15    15  3.14  4.71        3
 4     0    15  1.57  3.14        4
 5    15     0  0     1.57        5
 6    30     0  4.71  6.28        2
 7    30    15  3.14  4.71        5
 8    15    15  1.57  3.14        2
 9    30     0  0     1.57        3
10    45     0  4.71  6.28        4
# ℹ 54 more rows

Applying our system

library(ggforce)

output_grid %>%
  ggplot() +
  geom_arc_bar(aes(
    x0 = x0,
    y0 = y0,
    start = start,
    end = end,
    r0 = num_arcs,
    r = num_arcs + 1
  )) +
  coord_fixed()

Applying our system

num_arcs != r or r0

output_grid %>%
  ggplot() +
  geom_arc_bar(aes(
    x0 = x0,
    y0 = y0,
    start = start,
    end = end,
    r0 = num_arcs,
    r = num_arcs + 1
  )) +
  coord_fixed()

Creating arcs in {ggforce}

geom_arc_bar()

We will use the following aesthetics

  • x0, y0: x, y coordinate, center of arc
  • start: radian value of where arc starts
  • end: radian value of where arc ends
  • r0: radius of inner arc
  • r: radius of outer arc
  • color, fill: color of arc

geom_arc_bar() arguments: r0, r

Always travel counterclockwise, starting in bottom left

geom_arc_bar() arguments: r0, r

geom_arc_bar() arguments: r0, r

geom_arc_bar() arguments: r0, r

geom_arc_bar() arguments: r0, r

converting num_arcs to radius

geom_arc_bar() arguments: r0, r

converting num_arcs to radius

geom_arc_bar() arguments: r0, r

converting num_arcs to radius

geom_arc_bar() arguments: r0, r

converting num_arcs to radius

geom_arc_bar() arguments: r0, r

converting num_arcs to radius

r0 <- seq(from = 1, to = 7, by = 2)
r0
[1] 1 3 5 7

geom_arc_bar() arguments: r0, r

converting num_arcs to radius

r0 <- seq(from = 1, to = 7, by = 2)
r <- seq(from = 2, to = 8, by = 2)
r
[1] 2 4 6 8

geom_arc_bar() arguments: r0, r

converting num_arcs to radius

num_arcs <- 4

r0 <- seq(
  from = 1, by = 2,
  length.out = num_arcs
)

r0
[1] 1 3 5 7
r <- seq(
  from = 2, by = 2,
  length.out = num_arcs
)

r
[1] 2 4 6 8

Updating our system

truchet_tiles <-
  function(seed) {
    ncol <- 4
    nrow <- 4
    sq_width <- 15

    set.seed(seed)

    grid <-
      expand_grid(
        y = seq(from = 0, by = sq_width, length.out = ncol),
        x = seq(from = 0, by = sq_width, length.out = nrow)
      ) %>%
      mutate(tile_type = sample(c(1, 2), size = n(), replace = TRUE))

    params_grid <-
      map_dfr(
        1:nrow(grid),
        function(i) {
          set_params(
            x = grid$x[i],
            y = grid$y[i],
            sq_width = sq_width,
            tile_type = grid$tile_type[i]
          )
        }
      )

    output <-
      map_dfr(
        1:nrow(params_grid),
        function(i) {
          bind_cols(
            slice(params_grid, i),
            r0 = seq(from = 1, by = 2, length.out = params_grid$num_arcs[i]),
            r = seq(from = 2, by = 2, length.out = params_grid$num_arcs[i])
          )
        }
      )

    return(output)
  }

Applying our system

output_grid <-
  truchet_tiles(seed = 150)

output_grid %>%
  ggplot() +
  geom_arc_bar(aes(
    x0 = x0,
    y0 = y0,
    start = start,
    end = end,
    r0 = r0,
    r = r
  )) +
  coord_fixed()

Color, Fill

Original Infinite Truchet

Infinite Truchet available at Open Processing

geom_arc_bar() arguments: color and fill

Color order is the same in every corner

color <- c("#fffbe6", "#fc8405")

geom_arc_bar() arguments: color and fill

Color order is the same in every corner

max_num_arcs <- 5
color <- c("#fffbe6", "#fc8405")
color_seq <- rep(
  color,
  length.out = max_num_arcs
)
color_seq
[1] "#fffbe6" "#fc8405" "#fffbe6" "#fc8405" "#fffbe6"

Updating our system

truchet_tiles <-
  function(color1,
           color2,
           seed) {
    ncol <- 4
    nrow <- 4
    sq_width <- 15
    max_num_arcs <- 5

    set.seed(seed)

    grid <-
      expand_grid(
        y = seq(from = 0, by = sq_width, length.out = ncol),
        x = seq(from = 0, by = sq_width, length.out = nrow)
      ) %>%
      mutate(tile_type = sample(c(1, 2), size = n(), replace = TRUE))

    params_grid <-
      map_dfr(
        1:nrow(grid),
        function(i) {
          set_params(
            x = grid$x[i],
            y = grid$y[i],
            sq_width = sq_width,
            tile_type = grid$tile_type[i]
          )
        }
      )

    color <- c(color1, color2)
    color_seq <- rep(color, length.out = max_num_arcs)

    output <-
      map_dfr(
        1:nrow(params_grid),
        function(i) {
          bind_cols(
            slice(params_grid, i),
            r0 = seq(from = 1, by = 2, length.out = params_grid$num_arcs[i]),
            r = seq(from = 2, by = 2, length.out = params_grid$num_arcs[i]),
            color = color_seq[1:params_grid$num_arcs[i]]
          )
        }
      )

    return(output)
  }

Applying our system

output_grid <-
  truchet_tiles(
    seed = 150,
    color1 = "#fffbe6",
    color2 = "#fc8405"
  )

output_grid
# A tibble: 224 × 8
      x0    y0 start   end num_arcs    r0     r color  
   <dbl> <dbl> <dbl> <dbl>    <dbl> <dbl> <dbl> <chr>  
 1     0     0  0     1.57        3     1     2 #fffbe6
 2     0     0  0     1.57        3     3     4 #fc8405
 3     0     0  0     1.57        3     5     6 #fffbe6
 4    15     0  4.71  6.28        4     1     2 #fffbe6
 5    15     0  4.71  6.28        4     3     4 #fc8405
 6    15     0  4.71  6.28        4     5     6 #fffbe6
 7    15     0  4.71  6.28        4     7     8 #fc8405
 8    15    15  3.14  4.71        3     1     2 #fffbe6
 9    15    15  3.14  4.71        3     3     4 #fc8405
10    15    15  3.14  4.71        3     5     6 #fffbe6
# ℹ 214 more rows

Final Touches

Final Piece

output_grid %>%
  ggplot() +
  geom_arc_bar(aes(
    x0 = x0,
    y0 = y0,
    start = start,
    end = end,
    r0 = r0,
    r = r,
    fill = color
  )) +
  coord_fixed() +
  scale_fill_identity()

Final Piece

if you set color= to NA you can see the gaps between the arcs

output_grid %>%
  ggplot() +
  geom_arc_bar(
    aes(
      x0 = x0,
      y0 = y0,
      start = start,
      end = end,
      r0 = r0,
      r = r,
      fill = color
    ),
    color = NA
  ) +
  coord_fixed() +
  scale_fill_identity()

Final Piece

if you set color= to NA you can see the gaps between the arcs

output_grid %>%
  ggplot() +
  geom_arc_bar(
    aes(
      x0 = x0,
      y0 = y0,
      start = start,
      end = end,
      r0 = r0,
      r = r,
      fill = color
    ),
    color = NA
  ) +
  coord_fixed() +
  scale_fill_identity() +
  theme_void() +
  theme(panel.background = element_rect(
    fill = "black",
    color = "black"
  ))

Final Piece

output_grid %>%
  ggplot() +
  geom_arc_bar(aes(
    x0 = x0,
    y0 = y0,
    start = start,
    end = end,
    r0 = r0,
    r = r,
    fill = color,
    color = color
  )) +
  coord_fixed() +
  scale_fill_identity() +
  scale_color_identity()

Final Piece

expand= set to FALSE, removes margins

output_grid %>%
  ggplot() +
  geom_arc_bar(aes(
    x0 = x0,
    y0 = y0,
    start = start,
    end = end,
    r0 = r0,
    r = r,
    fill = color,
    color = color
  )) +
  coord_fixed(expand = FALSE) +
  scale_fill_identity() +
  scale_color_identity()

Final Piece

output_grid %>%
  ggplot() +
  geom_arc_bar(aes(
    x0 = x0,
    y0 = y0,
    start = start,
    end = end,
    r0 = r0,
    r = r,
    fill = color,
    color = color
  )) +
  coord_fixed() +
  scale_fill_identity() +
  scale_color_identity() +
  theme_void() +
  theme(
    panel.background =
      element_rect(
        fill = "#050505",
        color = "#050505"
      )
  )

Final Piece

Final Piece vs. Infinite Truchet

Infinite Truchet

Infinite Truchet, recreation

Exercise 3

  1. Open file exercises/05-infinite-truchet/exercise-3.Rmd
  2. Run all of the code chunks in Part 1. This will give you access to the system exercise_truchet_tiles().
  3. Within exercises/05-infinite-truchet/ there is an image luft_13_roni.png. Use https://mattdesl.github.io/colorgrab/ to select two colors to use in your system.
  4. Pass the hex codes for these colors to color1 and color2. Modify the seed to experiment with different outputs.
  5. Save your image using ggsave() code provided and share on the GitHub discussion
05:00

Recap

  • Learned about truchet tiles and their history (a technique from the 18th century!)
  • Returned to geom_arc_bar() to create arcs and its arguments - using trigonometry without ever having to sin() a thing!
  • Discussed why you need the fill= and color= for the shapes in truchet tile