library(tidyverse)
places <- tribble(~cities, ~hour,
"Melbourne/\nSydney", 6,
"Auckland", 8,
"San\nFrancisco", 13,
"Denver/\nSalt Lake", 14,
"Chicago/\nDallas", 15,
"New York/\nAtlanta", 16,
"Sao Paolo", 17,
"London/\nEdinburgh", 21,
"Amsterdam/\nBerlin", 22,
"Kampala", 23)
lag_around <- function(x, n){
c(x[(n+1):length(x)], x[1:n])
}
1:10 |> lag_around(5)
## [1] 6 7 8 9 10 1 2 3 4 5
{ggplot(data = summer_times_places) +
aes(x = hour_pretty |> fct_inorder()) +
aes(y = 1.15) +
coord_polar(start = 0, clip = F) +
theme_void(ink = "darkgrey") +
aes(label = hour_pretty) +
geom_label(linewidth = 0) +
aes(hjust = 1) +
aes(yend = 0, xend = hour) +
geom_segment(aes(y = 1.2)) +
aes(angle = -((hour-.5)/24)*360+90) +
aes(linetype = hour %% 3 |>
as.logical()) +
aes(vjust = 1) +
theme(legend.position = "none") +
aes(linewidth = I(.3)) +
geom_label(fill = "white",
linewidth = 0,
aes(x = hour, y = 1.22,
label = cities,
hjust = 0,
vjust = .5,
lineheight = .8,
xend = NULL,
linetype = NULL)) +
geom_hline(yintercept = 1.2) +
labs(title = "Virtual meetup coordinators' wheel, Summer 2026") +
aes(label = lag_around(hour_pretty, 1)) +
aes(label = lag_around(hour_pretty, 2)) +
aes(label = lag_around(hour_pretty, 3)) +
aes(label = lag_around(hour_pretty, 4)) +
aes(label = lag_around(hour_pretty, 5)) +
aes(label = lag_around(hour_pretty, 6)) +
aes(label = lag_around(hour_pretty, 7)) +
aes(label = lag_around(hour_pretty, 8)) +
aes(label = lag_around(hour_pretty, 9)) +
aes(label = lag_around(hour_pretty, 10)) +
aes(label = lag_around(hour_pretty, 11)) +
aes(label = lag_around(hour_pretty, 12)) +
aes(label = lag_around(hour_pretty, 13)) +
aes(label = lag_around(hour_pretty, 14)) +
aes(label = lag_around(hour_pretty, 15)) +
aes(label = lag_around(hour_pretty, 16)) +
aes(label = lag_around(hour_pretty, 17)) +
aes(label = lag_around(hour_pretty, 18)) +
aes(label = lag_around(hour_pretty, 19)) +
aes(label = lag_around(hour_pretty, 20)) +
aes(label = lag_around(hour_pretty, 21)) +
aes(label = lag_around(hour_pretty, 22)) +
aes(label = lag_around(hour_pretty, 23))
} |>
codehover::ch_hover()
| ggplot(data = summer_times_places) + |
| aes(x = fct_inorder(hour_pretty)) + |
| aes(y = 1.15) + |
| coord_polar(start = 0, clip = F) + |
| theme_void(ink = "darkgrey") + |
| aes(label = hour_pretty) + |
| geom_label(linewidth = 0) + |
| aes(hjust = 1) + |
| aes(yend = 0, xend = hour) + |
| geom_segment(aes(y = 1.2)) + |
| aes(angle = -((hour - 0.5)/24) * 360 + 90) + |
| aes(linetype = as.logical(hour%%3)) + |
| aes(vjust = 1) + |
| theme(legend.position = "none") + |
| aes(linewidth = I(0.3)) + |
geom_label(fill = "white", linewidth = 0, aes(x = hour, y = 1.22, label = cities, hjust = 0, vjust = 0.5, lineheight = 0.8, xend = NULL, linetype = NULL)) + |
| geom_hline(yintercept = 1.2) + |
| labs(title = "Virtual meetup coordinators' wheel, Summer 2026") + |
| aes(label = lag_around(hour_pretty, 1)) + |
| aes(label = lag_around(hour_pretty, 2)) + |
| aes(label = lag_around(hour_pretty, 3)) + |
| aes(label = lag_around(hour_pretty, 4)) + |
| aes(label = lag_around(hour_pretty, 5)) + |
| aes(label = lag_around(hour_pretty, 6)) + |
| aes(label = lag_around(hour_pretty, 7)) + |
| aes(label = lag_around(hour_pretty, 8)) + |
| aes(label = lag_around(hour_pretty, 9)) + |
| aes(label = lag_around(hour_pretty, 10)) + |
| aes(label = lag_around(hour_pretty, 11)) + |
| aes(label = lag_around(hour_pretty, 12)) + |
| aes(label = lag_around(hour_pretty, 13)) + |
| aes(label = lag_around(hour_pretty, 14)) + |
| aes(label = lag_around(hour_pretty, 15)) + |
| aes(label = lag_around(hour_pretty, 16)) + |
| aes(label = lag_around(hour_pretty, 17)) + |
| aes(label = lag_around(hour_pretty, 18)) + |
| aes(label = lag_around(hour_pretty, 19)) + |
| aes(label = lag_around(hour_pretty, 20)) + |
| aes(label = lag_around(hour_pretty, 21)) + |
| aes(label = lag_around(hour_pretty, 22)) + |
| aes(label = lag_around(hour_pretty, 23)) |

ggplyr::layers_wipe(i = 3) +
aes(label = cities) +
geom_label(fill = "white",
linewidth = 0,
aes(x = hour, y = 1.22,
label = hour_pretty,
hjust = 0,
vjust = .5,
lineheight = .8,
xend = NULL,
linetype = NULL))
## NULL
compute_panel_around <- function(data, scales, around_start = 0, radius = 1){
data |>
mutate(row_id = row_number()) |>
mutate(around = around_start* 2*pi/360 + 2 * pi * row_id/n(),
x = radius*cos(around),
y = radius*sin(around),
angle = 360*around/(2*pi),
end = 0)
}
StatAround <- ggproto("StatAround", Stat,
compute_panel = compute_panel_around,
default_aes = aes(hjust = after_stat(1),
xend = after_stat(end),
yend = after_stat(end)))
summer_times_places |>
ggplot() +
aes(label = cities) +
coord_equal(ylim = c(-1.2,1.2), xlim = c(-1.2,1.2)) +
geom_text(stat = StatAround, radius = .975) +
geom_text(stat = StatAround,
radius = 1.025, around_start = 90,
aes(label = hour_pretty),
hjust = 0) +
geom_segment(stat = StatAround,
around_start = pi/24 + 1,
linetype = "dashed") +
geom_polygon(stat = StatAround, color = "darkgray", fill = NA,
data = data.frame(x = 1:80), inherit.aes = F) +
labs(title = "Virtual meetup coordinators' wheel, Summer 2026",
subtitle = "From the ggplot2 extenders ❤️") +
theme_void(base_size = 9, ink = "darkgray")

geom_text_inner <- make_constructor(GeomText, stat = StatAround, radius = .975)
geom_text_outer <- make_constructor(GeomText, stat = StatAround, radius = 1.025, hjust = 0)
stamp_segment_pie_cuts <- make_constructor(GeomSegment, stat = StatAround, around_start = pi/24 + 1,
linetype = "dotted", inherit.aes = FALSE)
GeomPolygonHollow <- ggproto("GeomPolygonHollow", GeomPolygon,
default_aes = GeomPolygon$default_aes |>
modifyList(aes(color = from_theme(ink), fill = NA)))
stamp_polygon_circle <- make_constructor(GeomPolygonHollow, stat = StatAround, data = data.frame(x = 1:200), inherit.aes = FALSE)
theme_timezone_wheel <- function(...){
theme_void(base_size = 9, ink = "darkgray", ...)
}
chart_timezone_wheel <- function(
mapping.geom.text.outer = NULL,
mapping.geom.text.inner = NULL,
coord.equal.padded = coord_equal(ylim = c(-1.2,1.2), xlim = c(-1.2,1.2)),
geom.text.inner = geom_text_inner(mapping = mapping.geom.text.inner),
geom.text.outer = geom_text_outer(mapping = mapping.geom.text.outer),
stamp.segment.pie.cuts = stamp_segment_pie_cuts(),
stamp.polygon.circle = stamp_polygon_circle()){
list(
coord.equal.padded,
geom.text.inner,
geom.text.outer,
stamp.segment.pie.cuts,
stamp.polygon.circle)
}
theme_timezone_wheel() |> theme_set()
summer_times_places |>
ggplot() +
aes(label = cities) +
chart_timezone_wheel(aes(label = hour_pretty)) +
labs(title = "Virtual meetup coordinators' wheel, Summer 2026",
subtitle = "From the ggplot2 extenders ❤️")
