Steal like an Rtist: Creative Coding in R

Riso Grids
Ryan Miglinczy

Ijeamaka Anyene Fumagalli and Sharla Gelfand

posit::conf(2023)

Riso Grid 18

Riso Grid 16

Risograph printing

Invented by the Riso Kagaku Corporation in the mid-1980s in Tokyo

Risograph printer, via Moniker Press

Risograph printing

Images are created in layers - each layer is one color, and layers are combined to create different colors and combinations

Risograph process, via Damn Fine Print

 

Risograph printing

Risograph colour chart, via NYU Integrated Design & Media

Risograph colour chart, via peacock & the worm

Riso Grid 18

Layer 1

Layer 2

Using the shape types below, sketch two different 2x2 layers that could be combined to make a riso print

07:00

Polygon shapes

Oreo square

Oreo square

Important arguments

  • x, y: polygon vertices
  • group: which polygon it is
  • subgroup: to differentiate outer polygon points from holes

Oreo square

library(dplyr)

oreo_square_outer <- tribble(
   ~x,  ~y,
  1.5, 1.5,
  8.5, 1.5,
  8.5, 8.5,
  1.5, 8.5
  ) %>%
  mutate(subgroup = 1)

Oreo square

oreo_square_hole <- tribble(
    ~x,   ~y,
  2.75, 2.75,
  7.25, 2.75,
  7.25, 7.25,
  2.75, 7.25
  ) %>%
  mutate(subgroup = 2)

oreo_square_outer <- bind_rows(
  oreo_square_outer,
  oreo_square_hole
)

oreo_square_outer
# A tibble: 8 × 3
      x     y subgroup
  <dbl> <dbl>    <dbl>
1  1.5   1.5         1
2  8.5   1.5         1
3  8.5   8.5         1
4  1.5   8.5         1
5  2.75  2.75        2
6  7.25  2.75        2
7  7.25  7.25        2
8  2.75  7.25        2

Oreo square

library(ggplot2)

ggplot() +
  geom_polygon(
    data = oreo_square_outer,
    aes(
      x = x, y = y,
      subgroup = subgroup
    )
  ) +
  coord_fixed(
    xlim = c(0, 10),
    ylim = c(0, 10)
  )

Oreo square

oreo_square_inner <- tribble(
  ~x, ~y,
   4,  4,
   6,  4,
   6,  6,
   4,  6
  )

Oreo square

oreo_square <- bind_rows(
  oreo_square_outer %>%
    mutate(group = 1),
  oreo_square_inner %>% 
    mutate(group = 2)
)

oreo_square
# A tibble: 12 × 4
       x     y subgroup group
   <dbl> <dbl>    <dbl> <dbl>
 1  1.5   1.5         1     1
 2  8.5   1.5         1     1
 3  8.5   8.5         1     1
 4  1.5   8.5         1     1
 5  2.75  2.75        2     1
 6  7.25  2.75        2     1
 7  7.25  7.25        2     1
 8  2.75  7.25        2     1
 9  4     4          NA     2
10  6     4          NA     2
11  6     6          NA     2
12  4     6          NA     2

Oreo square

grey <- "#8F8E93"

ggplot() +
  geom_polygon(
    data = oreo_square,
    aes(
      x = x, y = y,
      group = group,
      subgroup = subgroup
    ),
    fill = grey
  ) +
  coord_fixed(xlim = c(0, 10), ylim = c(0, 10)) +
  theme_void()

Oreo square

Upper triangle

upper_triangle <- tribble(
  ~x, ~y,
   0,  0,
  10, 10,
   0, 10
  ) %>%
  mutate(
    group = NA,
    subgroup = NA
  )

upper_triangle
# A tibble: 3 × 4
      x     y group subgroup
  <dbl> <dbl> <lgl> <lgl>   
1     0     0 NA    NA      
2    10    10 NA    NA      
3     0    10 NA    NA      

Upper triangle

yellow <- "#DDA710"

ggplot() +
  geom_polygon(
    data = upper_triangle,
    aes(
      x = x, y = y,
      group = group,
      subgroup = subgroup
    ),
    fill = yellow
  ) +
  coord_fixed() +
  theme_void()

Lower triangle

lower_triangle <- tribble(
  ~x, ~y,
   0,  0,
  10,  0,
  10, 10
  ) %>%
  mutate(
    group = NA,
    subgroup = NA
  )

lower_triangle
# A tibble: 3 × 4
      x     y group subgroup
  <dbl> <dbl> <lgl> <lgl>   
1     0     0 NA    NA      
2    10     0 NA    NA      
3    10    10 NA    NA      

Lower triangle

ggplot() +
  geom_polygon(
    data = lower_triangle,
    aes(
      x = x, y = y,
      group = group,
      subgroup = subgroup
    ),
    fill = yellow
  ) +
  coord_fixed() +
  theme_void()

Circle shapes

Making circles

geom_circle() from {ggforce} draws a circle based on the center point (x0, y0) and the radius (r)

library(ggforce)

ggplot() +
  geom_circle(
    aes(
      x0 = 0,
      y0 = 0,
      r = 10
    )
  ) +
  coord_fixed()

Making parts of circles

Another brief math interlude…

~the parametric equation~

For a circle:

  • centered at x0, y0
  • with a radius of r

The coordinates a point at an angle tau from the center are:

  • x = x0 + sin(tau) * r

  • y = y0 + cos(tau) * r

Making parts of circles

Another brief math interlude…

start <- 0
end <- 2 * pi
x0 <- 0
y0 <- 0
r <- 1

tau <- seq(start, end, length.out = 48)
x <- x0 + sin(tau) * r
y <- y0 + cos(tau) * r

Making parts of circles

Another brief math interlude…

start <- 0
end <- pi / 2
x0 <- 0
y0 <- 0
r <- 1

tau <- seq(start, end, length.out = 12)
x <- x0 + sin(tau) * r
y <- y0 + cos(tau) * r

Making parts of circles

Another brief math interlude…

start <- pi / 2
end <- pi
x0 <- 0
y0 <- 0
r <- 1

tau <- seq(start, end, length.out = 12)
x <- x0 + sin(tau) * r
y <- y0 + cos(tau) * r

Making parts of circles

Another brief math interlude…

start <- pi
end <- 3 * pi / 2
x0 <- 0
y0 <- 0
r <- 1

tau <- seq(start, end, length.out = 12)
x <- x0 + sin(tau) * r
y <- y0 + cos(tau) * r

Making parts of circles

Another brief math interlude…

start <- 3 * pi / 2
end <- 2 * pi
x0 <- 0
y0 <- 0
r <- 1

tau <- seq(start, end, length.out = 12)
x <- x0 + sin(tau) * r
y <- y0 + cos(tau) * r

Making parts of circles

Another brief math interlude…

start <- 0
end <- 2 * pi
x0 <- 3
y0 <- 3
r <- 1

tau <- seq(start, end, length.out = 48)
x <- x0 + sin(tau) * r
y <- y0 + cos(tau) * r

Making parts of circles

Another brief math interlude…

start <- 0
end <- 2 * pi
x0 <- 0
y0 <- 0
r <- 0.5

tau <- seq(start, end, length.out = 48)
x <- x0 + sin(tau) * r
y <- y0 + cos(tau) * r

Making parts of circles

Another brief math interlude…

 

Making parts of circles

Another brief math interlude…

geom_arc_bar is used to make parts of circles

  • x0, y0: center of circle
  • r: radius of circle
  • start: starting angle
  • end: ending angle

Making parts of circles

ggplot() +
  geom_arc_bar(
    aes(
      x0 = 0,
      y0 = 0,
      r = 10,
      r0 = 0,
      start = 0,
      end = pi / 2
    )
  ) +
  coord_fixed()

Making parts of donuts

  • r: radius of circle
  • r0: radius of inner circle (hole)

ggplot() +
  geom_arc_bar(
    aes(
      x0 = 0,
      y0 = 0,
      r = 10,
      r0 = 5,
      start = 0,
      end = pi / 2
    )
  ) +
  coord_fixed()

Quarter donut, quadrant 1

quarter_donut_1 <- tibble(
  x = 0,
  y = 0,
  r = 10,
  r0 = 5,
  start = 0,
  end = pi / 2
)

ggplot() +
  geom_arc_bar(
    data = quarter_donut_1,
    aes(
      x0 = x, y0 = y,
      r = r, r0 = r0,
      start = start, end = end
    ),
    fill = yellow,
    color = NA
  ) +
  coord_fixed() +
  theme_void()

Quarter donut, quadrant 2

quarter_donut_2 <- tibble(
  x = 0,
  y = 10,
  r = 10,
  r0 = 5,
  start = pi / 2,
  end = pi
)

quarter_donut_2
# A tibble: 1 × 6
      x     y     r    r0 start   end
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1     0    10    10     5  1.57  3.14

Quarter donut, quadrant 2

ggplot() +
  geom_arc_bar(
    data = quarter_donut_2,
    aes(
      x0 = x, y0 = y,
      r = r, r0 = r0,
      start = start, end = end
    ),
    fill = grey,
    color = NA
  ) +
  coord_fixed() +
  theme_void()

Quarter donut, quadrant 3

  1. Open the file file exercises/02-riso-grids/exercise-1.Rmd
  2. Determine the x, y, start, and end of the bottom left circle, pictured here
  3. Plot it using geom_arc_bar()
05:00

Quarter donut, quadrant 4

quarter_donut_4 <- tibble(
  x = 10,
  y = 0,
  r = 10,
  r0 = 5,
  start = 3 * pi / 2,
  end = 2 * pi
)

quarter_donut_4
# A tibble: 1 × 6
      x     y     r    r0 start   end
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1    10     0    10     5  4.71  6.28

Putting it all together

Generate shape data

generate_shape_data <- function(shape, color) {
  switch(shape,
    "oreo square" = oreo_square,
    "upper triangle" = upper_triangle,
    "lower triangle" = lower_triangle,
    "quarter donut q1" = quarter_donut_1,
    "quarter donut q2" = quarter_donut_2,
    "quarter donut q3" = quarter_donut_3,
    "quarter donut q4" = quarter_donut_4
  ) %>%
    mutate(
      color = color,
      shape = shape
    )
}

Generate shape data

yellow_oreo_square <- generate_shape_data(
  "oreo square",
  yellow
)

yellow_oreo_square
# A tibble: 12 × 6
       x     y subgroup group color   shape      
   <dbl> <dbl>    <dbl> <dbl> <chr>   <chr>      
 1  1.5   1.5         1     1 #DDA710 oreo square
 2  8.5   1.5         1     1 #DDA710 oreo square
 3  8.5   8.5         1     1 #DDA710 oreo square
 4  1.5   8.5         1     1 #DDA710 oreo square
 5  2.75  2.75        2     1 #DDA710 oreo square
 6  7.25  2.75        2     1 #DDA710 oreo square
 7  7.25  7.25        2     1 #DDA710 oreo square
 8  2.75  7.25        2     1 #DDA710 oreo square
 9  4     4          NA     2 #DDA710 oreo square
10  6     4          NA     2 #DDA710 oreo square
11  6     6          NA     2 #DDA710 oreo square
12  4     6          NA     2 #DDA710 oreo square

Shape plotter

generate_shape_plotter <- function(data) {
  shape <- data[["shape"]][[1]]

  if (shape %in% c("oreo square", "upper triangle", "lower triangle")) {
    geom_polygon(
      data = data,
      aes(
        x = x, y = y, group = group, subgroup = subgroup, fill = color
      )
    )
  } else if (shape %in% c("quarter donut q1", "quarter donut q2", "quarter donut q3", "quarter donut q4")) {
    geom_arc_bar(
      data = data,
      aes(
        x0 = x, y0 = y, r = r, r0 = r0, start = start, end = end, fill = color
      ),
      color = NA
    )
  }
}

Shape plotter

generate_shape_plotter <- function(data) {
  shape <- data[["shape"]][[1]]

  if (shape %in% c("oreo square", "upper triangle", "lower triangle")) {
    geom_polygon(
      data = data,
      aes(
        x = x, y = y, group = group, subgroup = subgroup, fill = color
      )
    )
  } else if (shape %in% c("quarter donut q1", "quarter donut q2", "quarter donut q3", "quarter donut q4")) {
    geom_arc_bar(
      data = data,
      aes(
        x0 = x, y0 = y, r = r, r0 = r0, start = start, end = end, fill = color
      ),
      color = NA
    )
  }
}

Shape plotter

generate_shape_plotter(yellow_oreo_square)
mapping: x = ~x, y = ~y, group = ~group, subgroup = ~subgroup, fill = ~color 
geom_polygon: na.rm = FALSE, rule = evenodd
stat_identity: na.rm = FALSE
position_identity 

Returns only the geom_*() function, which can be used directly in a ggplot2 pipeline, or with multiple together in a list()

ggplot() +
  generate_shape_plotter(yellow_oreo_square) +
  scale_fill_identity() +
  coord_fixed(xlim = c(0, 10), ylim = c(0, 10)) +
  theme_void()

Grid of shapes

layer_1 <- tribble(
  ~row, ~column,             ~shape,
     1,       1, "quarter donut q3",
     1,       2,   "upper triangle",
     2,       2, "quarter donut q1",
     2,       1,   "lower triangle"
  )  %>%
  mutate(
    color = yellow,
    id = row_number()
  )

layer_1
# A tibble: 4 × 5
    row column shape            color      id
  <dbl>  <dbl> <chr>            <chr>   <int>
1     1      1 quarter donut q3 #DDA710     1
2     1      2 upper triangle   #DDA710     2
3     2      2 quarter donut q1 #DDA710     3
4     2      1 lower triangle   #DDA710     4

Create the grid of shapes

layer_1 %>%
  split(.$id)
$`1`
# A tibble: 1 × 5
    row column shape            color      id
  <dbl>  <dbl> <chr>            <chr>   <int>
1     1      1 quarter donut q3 #DDA710     1

$`2`
# A tibble: 1 × 5
    row column shape          color      id
  <dbl>  <dbl> <chr>          <chr>   <int>
1     1      2 upper triangle #DDA710     2

$`3`
# A tibble: 1 × 5
    row column shape            color      id
  <dbl>  <dbl> <chr>            <chr>   <int>
1     2      2 quarter donut q1 #DDA710     3

$`4`
# A tibble: 1 × 5
    row column shape          color      id
  <dbl>  <dbl> <chr>          <chr>   <int>
1     2      1 lower triangle #DDA710     4

Create the grid of shapes

library(purrr)

layer_1 %>%
  split(.$id) %>%
  map(function(data) {
    generate_shape_data(data[["shape"]], data[["color"]])
  })
$`1`
# A tibble: 1 × 8
      x     y     r    r0 start   end color   shape           
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <chr>   <chr>           
1    10    10    10     5  3.14  4.71 #DDA710 quarter donut q3

$`2`
# A tibble: 3 × 6
      x     y group subgroup color   shape         
  <dbl> <dbl> <lgl> <lgl>    <chr>   <chr>         
1     0     0 NA    NA       #DDA710 upper triangle
2    10    10 NA    NA       #DDA710 upper triangle
3     0    10 NA    NA       #DDA710 upper triangle

$`3`
# A tibble: 1 × 8
      x     y     r    r0 start   end color   shape           
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <chr>   <chr>           
1     0     0    10     5     0  1.57 #DDA710 quarter donut q1

$`4`
# A tibble: 3 × 6
      x     y group subgroup color   shape         
  <dbl> <dbl> <lgl> <lgl>    <chr>   <chr>         
1     0     0 NA    NA       #DDA710 lower triangle
2    10     0 NA    NA       #DDA710 lower triangle
3    10    10 NA    NA       #DDA710 lower triangle

List of plotters

library(purrr)

layer_1_plotting <- layer_1 %>%
  split(.$id) %>%
  map(function(data) {
    generate_shape_data(data[["shape"]], data[["color"]])
  }) %>%
  map(function(data) {
    data %>%
      generate_shape_plotter()
  })

layer_1_plotting
$`1`
mapping: x0 = ~x, y0 = ~y, r = ~r, r0 = ~r0, start = ~start, end = ~end, fill = ~color 
geom_arc_bar: expand = 0, radius = 0
stat_arc_bar: na.rm = FALSE, n = 360
position_identity 

$`2`
mapping: x = ~x, y = ~y, group = ~group, subgroup = ~subgroup, fill = ~color 
geom_polygon: na.rm = FALSE, rule = evenodd
stat_identity: na.rm = FALSE
position_identity 

$`3`
mapping: x0 = ~x, y0 = ~y, r = ~r, r0 = ~r0, start = ~start, end = ~end, fill = ~color 
geom_arc_bar: expand = 0, radius = 0
stat_arc_bar: na.rm = FALSE, n = 360
position_identity 

$`4`
mapping: x = ~x, y = ~y, group = ~group, subgroup = ~subgroup, fill = ~color 
geom_polygon: na.rm = FALSE, rule = evenodd
stat_identity: na.rm = FALSE
position_identity 
ggplot() +
  layer_1_plotting +
  scale_fill_identity() +
  coord_fixed() +
  theme_void()

Using a list of plotters together

ggplot() +
  layer_1_plotting +
  scale_fill_identity() +
  coord_fixed() +
  theme_void()

???

Shifting data

Shifting data

Shifting data

Shift everything over using the size of one grid and the row and column

layer_1_data <- layer_1 %>%
  split(.$id) %>%
  map_dfr(
    function(data) {
      generate_shape_data(data[["shape"]], data[["color"]])
    },
    .id = "id"
  ) %>%
  mutate(id = as.numeric(id)) %>%
  left_join(layer_1, by = c("id", "color", "shape"))

layer_1_data %>% select(-id)
# A tibble: 8 × 12
      x     y     r    r0 start   end color   shape  group subgroup   row column
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <chr>   <chr>  <lgl> <lgl>    <dbl>  <dbl>
1    10    10    10     5  3.14  4.71 #DDA710 quart… NA    NA           1      1
2     0     0    NA    NA NA    NA    #DDA710 upper… NA    NA           1      2
3    10    10    NA    NA NA    NA    #DDA710 upper… NA    NA           1      2
4     0    10    NA    NA NA    NA    #DDA710 upper… NA    NA           1      2
5     0     0    10     5  0     1.57 #DDA710 quart… NA    NA           2      2
6     0     0    NA    NA NA    NA    #DDA710 lower… NA    NA           2      1
7    10     0    NA    NA NA    NA    #DDA710 lower… NA    NA           2      1
8    10    10    NA    NA NA    NA    #DDA710 lower… NA    NA           2      1

Shifting data

Shift everything over using the size of one grid and the row and column

grid_size <- 10

layer_1_data_shifted <- layer_1_data %>%
  mutate(
    x = x + column * grid_size,
    y = y + row * grid_size
  )

layer_1_data_shifted %>% select(-id)
# A tibble: 8 × 12
      x     y     r    r0 start   end color   shape  group subgroup   row column
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <chr>   <chr>  <lgl> <lgl>    <dbl>  <dbl>
1    20    20    10     5  3.14  4.71 #DDA710 quart… NA    NA           1      1
2    20    10    NA    NA NA    NA    #DDA710 upper… NA    NA           1      2
3    30    20    NA    NA NA    NA    #DDA710 upper… NA    NA           1      2
4    20    20    NA    NA NA    NA    #DDA710 upper… NA    NA           1      2
5    20    20    10     5  0     1.57 #DDA710 quart… NA    NA           2      2
6    10    20    NA    NA NA    NA    #DDA710 lower… NA    NA           2      1
7    20    20    NA    NA NA    NA    #DDA710 lower… NA    NA           2      1
8    20    30    NA    NA NA    NA    #DDA710 lower… NA    NA           2      1

Now plot!

layer_1_plotting <- layer_1_data_shifted %>%
  split(.$id) %>%
  map(function(data) {
    data %>%
      generate_shape_plotter()
  })

ggplot() +
  layer_1_plotting +
  scale_fill_identity() +
  coord_fixed() +
  theme_void()

Do the same for the second layer!

layer_2 <- tribble(
  ~row, ~column,             ~shape,
     1,       1,      "oreo square",
     1,       2, "quarter donut q2",
     2,       2,      "oreo square",
     2,       1, "quarter donut q4"
  ) %>%
  mutate(
    color = grey,
    id = row_number()
  )

layer_2_plotting <- layer_2 %>%
  split(.$id) %>%
  map_dfr(
    function(data) {
      generate_shape_data(data[["shape"]], data[["color"]])
    },
    .id = "id"
  ) %>%
  mutate(id = as.numeric(id)) %>%
  left_join(layer_2, by = c("id", "color", "shape")) %>%
  mutate(
    x = x + column * grid_size,
    y = y + row * grid_size
  ) %>%
  split(.$id) %>%
  map(function(data) {
    generate_shape_plotter(data)
  })

ggplot() +
  layer_2_plotting +
  scale_fill_identity() +
  coord_fixed() +
  theme_void()

Exercise

  1. Open the file file exercises/02-riso-grids/exercise-2-3.Rmd
  2. Take the two layers you sketched and turn them into tribbles with columns row, column, shape, and color. Try using two of the colors listed in the file, which are common colors in riso printing.
  3. Run generate_and_plot_two_layers() with your two layers and save the plots in the correct dimension using ggsave()
  4. Share your two plots in the GitHub discussion
10:00

Put them together

ggplot() +
  list(
    layer_1_plotting,
    layer_2_plotting
  ) +
  scale_fill_identity() +
  coord_fixed() +
  theme_void()

Risograph effect please?

Risograph effect please?

Risograph effect please?

layer_1 <- ggplot() +
  layer_1_plotting +
  scale_fill_identity() +
  coord_fixed() +
  theme_void()

layer_1_file <- "riso-layer-1.png"

ggsave(layer_1_file, layer_1, height = 5, width = 5)

layer_2 <- ggplot() +
  layer_2_plotting +
  scale_fill_identity() +
  coord_fixed() +
  theme_void()

layer_2_file <- "riso-layer-2.png"

ggsave(layer_2_file, layer_2, height = 5, width = 5)

Risograph effect please?

library(magick)

layer_1_img <- image_read(layer_1_file)
layer_2_img <- image_read(layer_2_file)

paper <- image_blank(
  width = image_info(layer_1_img)[["width"]],
  height = image_info(layer_1_img)[["height"]],
  color = "#E1DED7"
)

paper

Risograph effect please?

library(magick)

layer_1_img <- image_read(layer_1_file)
layer_2_img <- image_read(layer_2_file)

paper <- image_blank(
  width = image_info(layer_1_img)[["width"]],
  height = image_info(layer_1_img)[["height"]],
  color = "#E1DED7"
)

paper %>%
  image_composite(layer_1_img)

Risograph effect please?

library(magick)

layer_1_img <- image_read(layer_1_file)
layer_2_img <- image_read(layer_2_file)

paper <- image_blank(
  width = image_info(layer_1_img)[["width"]],
  height = image_info(layer_1_img)[["height"]],
  color = "#E1DED7"
)

paper %>%
  image_composite(layer_1_img) %>%
  image_composite(
    layer_2_img,
    operator = "multiply"
  )

That’s not so bad!

Riso Grid 18

Riso Grid 18, recreation

Exercise

  1. Open the file exercises/02-riso-grids/exercise-2-3.Rmd
  2. If needed, rerun the code to define, plot, and save your two layers
  3. Use the given code to combine a “paper” layer with your two layers to create the riso print effect
  4. Share your final image on GitHub
05:00

Wrap it up!

  • Risograph printing combines multiple layers, one color each, to create complex images
  • geom_polygon() and geom_arc_bar() can be used to create more complex shapes
  • A list of geom_*()s in a ggplot2 pipeline combine the layers
  • {magick} is a handy package for image processing and getting the risograph effect!