Step 0. Do it with base ggplot2
library(tidyverse)
data.frame(x0 = 0:1, y0 = 0:1, r = 1:2/3) |>
mutate(group = row_number()) |>
crossing(tibble(z = 0:15)) |>
# mutate(join_var = 1) |>
# left_join(tibble(z = 0:15, join_var = 1),
# multiple = "all") |>
mutate(around = 2*pi*z/max(z)) |>
mutate(x = x0 + cos(around)*r,
y = y0 + sin(around)*r) |>
ggplot() +
aes(x, y, label = z) +
geom_text() +
geom_path(aes(group = group))
Step 1. Compute
compute_panel_equilateral <- function(data, scales, n = 15){
data |>
mutate(group = row_number()) |>
crossing(tibble(z = 0:n)) |>
# mutate(join_var = 1) |>
# left_join(tibble(z = 0:(n), join_var = 1),
# multiple = "all") |>
mutate(around = 2*pi*z/max(z)) |>
mutate(x = x0 + cos(around)*r,
y = y0 + sin(around)*r)
}
tibble(x0 = 1:2, y0 = 1:2, r = 1 ) |>
compute_panel_equilateral()
## # A tibble: 32 × 8
## x0 y0 r group z around x y
## <int> <int> <dbl> <int> <int> <dbl> <dbl> <dbl>
## 1 1 1 1 1 0 0 2 1
## 2 1 1 1 1 1 0.419 1.91 1.41
## 3 1 1 1 1 2 0.838 1.67 1.74
## 4 1 1 1 1 3 1.26 1.31 1.95
## 5 1 1 1 1 4 1.68 0.895 1.99
## 6 1 1 1 1 5 2.09 0.5 1.87
## 7 1 1 1 1 6 2.51 0.191 1.59
## 8 1 1 1 1 7 2.93 0.0219 1.21
## 9 1 1 1 1 8 3.35 0.0219 0.792
## 10 1 1 1 1 9 3.77 0.191 0.412
## # ℹ 22 more rows
Step 2. Pass to ggproto
StatCircle <- ggproto(
`_class` = "StatCircle",
`_inherit` = ggplot2::Stat,
compute_panel = compute_panel_equilateral,
required_aes = c("x0", "y0", "r")
)
Step 3. Write geom_* or stat_*
geom_circle <- function(
mapping = NULL,
data = NULL,
position = "identity",
na.rm = FALSE,
show.legend = NA,
inherit.aes = TRUE, ...) {
ggplot2::layer(
stat = StatCircle, # proto object from Step 2
geom = ggplot2::GeomPolygon, # inherit other behavior
data = data,
mapping = mapping,
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list(na.rm = na.rm, ...)
)
}
Step 4: Enjoy (test)
data.frame(x0 = 0:1, y0 = 0:1, r = 1:2/3) |>
ggplot() +
aes(x0 = x0, y0 = y0, r = r) +
geom_circle() +
aes(fill = r)
diamonds |>
slice_sample(n = 80) |>
ggplot() +
aes(x0 = as.numeric(cut), y0 = carat , r = as.numeric(clarity)/20) +
geom_circle(alpha = .2) +
aes(fill = after_stat(r)) +
coord_equal()
cars |>
sample_n(12) |>
ggplot() +
aes(x0 = speed, y0 = dist, r = dist/speed) +
geom_circle(color = "black") +
coord_equal()
last_plot() +
aes(alpha = speed > 15) +
aes(linetype = dist > 20) +
aes(fill = speed > 18) +
facet_wrap(~ dist > 40)
## Warning: Using alpha for a discrete variable is not advised.
Discussion: Why not compute_group?
StatCircle2 <- ggproto(
`_class` = "StatCircle2",
`_inherit` = ggplot2::Stat,
compute_group = compute_panel_equilateral,
required_aes = c("x0", "y0", "r"))
geom_circle_CG <- function(
mapping = NULL,
data = NULL,
position = "identity",
na.rm = FALSE,
show.legend = NA,
inherit.aes = TRUE, ...) {
ggplot2::layer(
stat = StatCircle2, # proto object from Step 2
geom = ggplot2::GeomPolygon, # inherit other behavior
data = data,
mapping = mapping,
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list(na.rm = na.rm, ...)
)
}
cars |>
sample_n(12) |>
ggplot() +
aes(x0 = speed, y0 = dist, r = dist/speed) +
geom_circle_CG(color = "black") +
coord_equal() +
aes(alpha = speed > 15) +
aes(linetype = dist > 20) +
aes(fill = speed > 18) +
facet_wrap(~ dist > 40)
## Warning: Using alpha for a discrete variable is not advised.
Exercise: Write the function, geom_heart() that will take the compute below and do it within the geom_* function
data.frame(x0 = 0:1, y0 = 0:1, r = 1:2/3, rotation = 0) %>%
mutate(group = row_number()) %>%
tidyr::crossing(around = 0:15/15) %>%
dplyr::mutate(
y = y0 + r * (
.85 * cos(2*pi*around)
- .35 * cos(2 * 2*pi*around)
- .25 * cos(3 * 2*pi*around)
- .05 * cos(4 * 2*pi*around)
) - rotation * pi,
x = x0 + r * (sin(2*pi*around)^3) - rotation * pi) %>%
ggplot() +
aes(x = x, y = y, group = group) +
geom_polygon(alpha = .5, fill = "darkred") +
coord_equal()