R Markdown

library(ggplot2)
library(tidyverse)

my_setup <- function(data, params){
  
  if(data$group[1] == -1){
    nrows <- nrow(data)
    data$group <- seq_len(nrows)
  }
  data
}


cars %>% 
  mutate(group = -1L) %>% # specifies 
  my_setup()


my_setup <- function(data, params)  {
    if (anyDuplicated(data$group)) {
      data$group <- paste(data$group, seq_len(nrow(data)), sep = "-")
    }
    data
  }

cars %>% 
  mutate(group = -1L) %>% 
  my_setup()

my_compute_panel <- function(data, scales, n = 5, r = 1){
  
  angles <- seq(
    from = 0,
    to = 2*pi,
    length.out = n + 1
  )
  
  nrows = nrow(data)
  
  df <- data.frame() 
  
  for(i in 1:nrows){
    
  bind_rows(df,   
  data.frame(
    x = cos(angles) * data$r[i] + data$x0[i],
    y = cos(angles) * data$r[i] + data$y0[i],
    group = i
  ) 
  ) ->
    df
  }
  
}

cars %>%
  rename(x0 = dist) %>%
  rename(y0 = speed) %>%
  mutate(r = y0/12) %>%
  my_compute_panel


StatCircle <- ggproto(`_class` = 'StatCircle',
                      `_inherit` = Stat, 
                      setup_data = my_setup,
                      compute_panel = my_compute_panel,
                      required_aes = c('x0', 'y0', 'r'))


geom_circle <- function(mapping = NULL, data = NULL, stat = "circle", 
                        position = "identity", ..., r = 1,  
                        n = 50, arrow = NULL, lineend = "butt", linejoin = "round", 
                        na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) {
  layer(
    data = data, 
    mapping = mapping, 
    stat = stat, 
    geom = GeomPolygon, 
    position = position, 
    show.legend = show.legend, 
    inherit.aes = inherit.aes, 
    params = list(
      r = r, 
      n = n, 
      arrow = arrow, 
      lineend = lineend, 
      linejoin = linejoin, 
      na.rm = na.rm, 
      ...
    )
  )
}


ggplot(cars) + 
  aes(x = speed) + 
  aes(y = dist) + 
  geom_point() + 
  aes(r = dist, x0 = speed, y0 = dist) +
  geom_circle()

https://evamaerey.github.io/flipbooks/extending_ggplot2.html#86

library(ggplot2)
library(tidyverse)
## ── Attaching core tidyverse packages ─────────────────── tidyverse 2.0.0.9000 ──
## ✔ dplyr     1.1.0     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.0
## ✔ lubridate 1.9.2     ✔ tibble    3.2.1
## ✔ purrr     1.0.1     ✔ tidyr     1.3.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
create_circle <- function(x0, y0, r = 1, n = 100){
  angels <- seq(
    from = 0,
    to = 2*pi,
    length.out = n + 1
  )
  data.frame(
    x = cos(angels) * r + x0,
    y = sin(angels) * r + y0
  )
}

the_compute_panel <- function(data, scales, r = 1, n = 50) {
    
    cols_to_keep <- setdiff(names(data), c("x0", "y0"))
    
    circles <- lapply(seq_len(nrow(data)), function(i) {
      
      circles_path <- create_circle(data$x0[i], data$y0[i], data$r[i], n)
      cbind(circles_path, unclass(data[i, cols_to_keep]))
      
    })
    
    do.call(rbind, circles)
}

cars %>% 
  .[1:2,] %>% 
  rename(x0 = speed) %>% 
  rename(y0 = dist) %>% 
  mutate(r = y0) %>% 
  mutate(group = row_number()) %>% 
  the_compute_panel(n = 5)
##            x           y  r group
## 1   6.000000  2.00000000  2     1
## 2   4.618034  3.90211303  2     1
## 3   2.381966  3.17557050  2     1
## 4   2.381966  0.82442950  2     1
## 5   4.618034  0.09788697  2     1
## 6   6.000000  2.00000000  2     1
## 7  14.000000 10.00000000 10     2
## 8   7.090170 19.51056516 10     2
## 9  -4.090170 15.87785252 10     2
## 10 -4.090170  4.12214748 10     2
## 11  7.090170  0.48943484 10     2
## 12 14.000000 10.00000000 10     2
the_setup_data <- function(data, params) {
    if (anyDuplicated(data$group)) {
      data$group <- paste(data$group, seq_len(nrow(data)), sep = "-")
    }
    data
  }

cars %>% 
  .[1:2,] %>% 
  mutate(group = row_number()) %>% 
  the_setup_data()
##   speed dist group
## 1     4    2     1
## 2     4   10     2
StatCircle <- ggproto("StatCircle", Stat, 
  # setup_params = function(data, params) {
  #   if (is.null(params$r)) {
  #     params$r <- 1
  #   } else if (params$r == 0) {
  #     rlang::abort("Circles cannot be defined with a radius of 0")
  #   }
  #   if (is.null(params$n)) {
  #     params$n <- 50
  #   } else if (params$n <= 0) {
  #     rlang::abort("Circles must be defined with `n` greater than 0")
  #   }
  #   params
  # },
  setup_data = the_setup_data,
  compute_panel = the_compute_panel,
  required_aes = c("x0", "y0", "r")
)


geom_circle <- function(mapping = NULL, data = NULL, stat = "circle", 
                        position = "identity", ..., r = 1,  
                        n = 50, arrow = NULL, lineend = "butt", linejoin = "round", 
                        na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) {
  layer(
    data = data, 
    mapping = mapping, 
    stat = stat, 
    geom = GeomPolygon, 
    position = position, 
    show.legend = show.legend, 
    inherit.aes = inherit.aes, 
    params = list(
      r = r, 
      n = n, 
      arrow = arrow, 
      lineend = lineend, 
      linejoin = linejoin, 
      na.rm = na.rm, 
      ...
    )
  )
}


set.seed(1244)
cars %>% 
  .[1:5,] %>%
  ggplot() +
  aes(x = speed, y = dist) +
  geom_point() +
  aes(x0 = speed, y0 = dist, r = dist^.5) +
  geom_circle(n = 3, alpha = .2) +
  geom_circle(n = 13, alpha = .2) +
  geom_circle(r = 2, alpha = .2) +
  geom_circle(alpha = .2)
## Warning in geom_circle(n = 3, alpha = 0.2): Ignoring unknown parameters:
## `arrow`
## Warning in geom_circle(n = 13, alpha = 0.2): Ignoring unknown parameters:
## `arrow`
## Warning in geom_circle(r = 2, alpha = 0.2): Ignoring unknown parameters:
## `arrow`
## Warning in geom_circle(alpha = 0.2): Ignoring unknown parameters: `arrow`
## Warning: Duplicated aesthetics after name standardisation: x and y
## Duplicated aesthetics after name standardisation: x and y
## Duplicated aesthetics after name standardisation: x and y
## Duplicated aesthetics after name standardisation: x and y
## Warning in validDetails.polygon(x): NAs introduced by coercion

## Warning in validDetails.polygon(x): NAs introduced by coercion

## Warning in validDetails.polygon(x): NAs introduced by coercion

## Warning in validDetails.polygon(x): NAs introduced by coercion

layer_data(last_plot(), 2)
## Warning: Duplicated aesthetics after name standardisation: x and y
## Warning: Duplicated aesthetics after name standardisation: x and y
## Duplicated aesthetics after name standardisation: x and y
## Duplicated aesthetics after name standardisation: x and y
##            x          y        r x.1 y.1 PANEL group colour   fill linewidth
## 1   5.414214  2.0000000 1.414214   4   2     1  -1-1     NA grey20       0.5
## 2   3.292893  3.2247449 1.414214   4   2     1  -1-1     NA grey20       0.5
## 3   3.292893  0.7752551 1.414214   4   2     1  -1-1     NA grey20       0.5
## 4   5.414214  2.0000000 1.414214   4   2     1  -1-1     NA grey20       0.5
## 5   7.162278 10.0000000 3.162278   4  10     1  -1-2     NA grey20       0.5
## 6   2.418861 12.7386128 3.162278   4  10     1  -1-2     NA grey20       0.5
## 7   2.418861  7.2613872 3.162278   4  10     1  -1-2     NA grey20       0.5
## 8   7.162278 10.0000000 3.162278   4  10     1  -1-2     NA grey20       0.5
## 9   9.000000  4.0000000 2.000000   7   4     1  -1-3     NA grey20       0.5
## 10  6.000000  5.7320508 2.000000   7   4     1  -1-3     NA grey20       0.5
## 11  6.000000  2.2679492 2.000000   7   4     1  -1-3     NA grey20       0.5
## 12  9.000000  4.0000000 2.000000   7   4     1  -1-3     NA grey20       0.5
## 13 11.690416 22.0000000 4.690416   7  22     1  -1-4     NA grey20       0.5
## 14  4.654792 26.0620192 4.690416   7  22     1  -1-4     NA grey20       0.5
## 15  4.654792 17.9379808 4.690416   7  22     1  -1-4     NA grey20       0.5
## 16 11.690416 22.0000000 4.690416   7  22     1  -1-4     NA grey20       0.5
## 17 12.000000 16.0000000 4.000000   8  16     1  -1-5     NA grey20       0.5
## 18  6.000000 19.4641016 4.000000   8  16     1  -1-5     NA grey20       0.5
## 19  6.000000 12.5358984 4.000000   8  16     1  -1-5     NA grey20       0.5
## 20 12.000000 16.0000000 4.000000   8  16     1  -1-5     NA grey20       0.5
##    linetype alpha
## 1         1   0.2
## 2         1   0.2
## 3         1   0.2
## 4         1   0.2
## 5         1   0.2
## 6         1   0.2
## 7         1   0.2
## 8         1   0.2
## 9         1   0.2
## 10        1   0.2
## 11        1   0.2
## 12        1   0.2
## 13        1   0.2
## 14        1   0.2
## 15        1   0.2
## 16        1   0.2
## 17        1   0.2
## 18        1   0.2
## 19        1   0.2
## 20        1   0.2