Status Quo
library(tidyverse)
library(ggfoundry)
library(readr)
library(dplyr)
library(forcats)
coffee <-
read_csv(
"https://raw.githubusercontent.com/cgoo4/ggfoundry/refs/heads/main/data-raw/cappuccino/cappuccino_index.csv",
show_col_types = FALSE
) |>
arrange(desc(n)) |>
slice_head(n = 10) |>
mutate(country = fct_reorder(country, desc(index)))
{coffee |>
ggplot() +
labs(title = "How much work buys a cappuccino?") +
theme_minimal(base_size = 12) +
aes(x = index, xend = 0) +
labs(x = "Minutes of a barista's wages") +
labs(subtitle = "The ten countries with the largest café samples") +
aes(y = country, yend = country) +
theme(panel.grid.major.y = element_blank()) +
labs(y = NULL) +
geom_segment(colour = "grey85", linewidth = 0.6) +
theme(panel.grid.minor = element_blank()) +
geom_casting(shape = "cup", size = 0.135) +
aes(fill = index) +
aes(label = index) +
geom_text(nudge_x = 2.5, hjust = 0, size = 3.5) +
aes(color = I("#493A30")) +
aes(label = sprintf("%.1f", index)) +
scale_fill_gradient(low = "#EED8B9", high = "#A86135", guide = "none") +
scale_x_continuous(expand = expansion(mult = c(0.01, 0.16))) +
labs(caption = "Source: James Hoffmann / TidyTuesday, 8 September 2026\nSample-based country index; tips excluded.") +
theme(plot.title = element_text(face = "bold")) +
theme(plot.caption = element_text(hjust = 0)) +
theme(plot.title.position = "plot",
plot.caption.position = "plot")
} |>
codehover::ch_hover()
| ggplot(coffee) + |
| labs(title = "How much work buys a cappuccino?") + |
| theme_minimal(base_size = 12) + |
| aes(x = index, xend = 0) + |
| labs(x = "Minutes of a barista's wages") + |
| labs(subtitle = "The ten countries with the largest café samples") + |
| aes(y = country, yend = country) + |
| theme(panel.grid.major.y = element_blank()) + |
| labs(y = NULL) + |
| geom_segment(colour = "grey85", linewidth = 0.6) + |
| theme(panel.grid.minor = element_blank()) + |
| geom_casting(shape = "cup", size = 0.135) + |
| aes(fill = index) + |
| aes(label = index) + |
| geom_text(nudge_x = 2.5, hjust = 0, size = 3.5) + |
| aes(color = I("#493A30")) + |
| aes(label = sprintf("%.1f", index)) + |
| scale_fill_gradient(low = "#EED8B9", high = "#A86135", guide = "none") + |
| scale_x_continuous(expand = expansion(mult = c(0.01, 0.16))) + |
| labs(caption = "Source: James Hoffmann / TidyTuesday, 8 September 2026\nSample-based country index; tips excluded.") + |
| theme(plot.title = element_text(face = "bold")) + |
| theme(plot.caption = element_text(hjust = 0)) + |
| theme(plot.title.position = "plot", plot.caption.position = "plot") |

geom_casting
## function (mapping = NULL, data = NULL, stat = "identity", position = "identity",
## ..., na.rm = FALSE, show.legend = NA, inherit.aes = TRUE)
## {
## layer(data = data, mapping = mapping, stat = stat, geom = GeomCasting,
## position = position, show.legend = show.legend, inherit.aes = inherit.aes,
## params = list(na.rm = na.rm, ...))
## }
## <bytecode: 0x12dff5040>
## <environment: namespace:ggfoundry>
GeomCasting
## <ggproto object: Class GeomCasting, Geom, gg>
## aesthetics: function
## default_aes: ggplot2::mapping, uneval, gg, S7_object
## draw_group: function
## draw_key: function
## draw_layer: function
## draw_panel: function
## extra_params: na.rm
## handle_na: function
## non_missing_aes: size shape colour
## optional_aes:
## parameters: function
## rename_size: FALSE
## required_aes: x y
## setup_data: function
## setup_params: function
## use_defaults: function
## super: <ggproto object: Class Geom, gg>
df <- shapes_cast() |>
mutate(x = row_number(),
.by = set)
df |> head()
## set shape x
## 1 circle circleF 1
## 3 circle circleL 2
## 5 circle circleR 3
## 7 container bowl0 1
## 9 container bowl1 2
## 11 container bowl2 3
df$shape
## [1] "circleF" "circleL" "circleR" "bowl0" "bowl1"
## [6] "bowl2" "cup" "jar" "mug" "takeaway"
## [11] "tube" "cross1" "cross2" "sunflower1" "sunflower2"
## [16] "sunflower3" "sunflower4" "sunflower5" "sunflower6" "sunflower7"
## [21] "sunflower8" "coffeebean" "jackolantern" "pumpkin" "box"
## [26] "dendro" "ribbon" "violin" "hibiscus" "oak"
## [31] "adelie" "chinstrap" "gentoo" "heptagon" "hexagon"
## [36] "octagon" "pentagon"
{df |>
ggplot() +
aes(x = x) +
aes(y = set) +
geom_text(aes(label = shape), nudge_y = -0.5, colour = "grey40", size = 3) +
geom_casting(size = 0.11, fill = "skyblue") +
aes(shape = "bowl0" |> I()) +
aes(shape = shape |> I()) +
scale_x_continuous(expand = expansion(add = 0.5)) +
scale_y_discrete(expand = expansion(add = c(0.7, 1.6))) +
labs(caption = "The quill anchors its ink contact to the plotting coordinate") +
theme_minimal() +
theme(
text = element_text(colour = "grey70"),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
legend.position = "none"
)} |>
codehover::ch_hover()
| ggplot(df) + |
| aes(x = x) + |
| aes(y = set) + |
geom_text(aes(label = shape), nudge_y = -0.5, colour = "grey40", size = 3) + |
| geom_casting(size = 0.11, fill = "skyblue") + |
| aes(shape = I("bowl0")) + |
| aes(shape = I(shape)) + |
| scale_x_continuous(expand = expansion(add = 0.5)) + |
| scale_y_discrete(expand = expansion(add = c(0.7, 1.6))) + |
| labs(caption = "The quill anchors its ink contact to the plotting coordinate") + |
| theme_minimal() + |
theme(text = element_text(colour = "grey70"), axis.text.x = element_blank(), axis.ticks.x = element_blank(), legend.position = "none") |
library(ggplot2)
library(ggfoundry)
{diamonds |>
ggplot() +
aes(x = cut) +
geom_bar() +
geom_casting(stat = "count",
fill = "skyblue") +
aes(shape = cut) +
scale_shape_manual(values = c("bowl0", "cup", "adelie", "gentoo", "takeaway"))} |>
codehover::ch_hover()
## Warning: Using shapes for an ordinal variable is not advised
| ggplot(diamonds) + |
| aes(x = cut) + |
| geom_bar() + |
| geom_casting(stat = "count", fill = "skyblue") + |
| aes(shape = cut) + |
scale_shape_manual(values = c("bowl0", "cup", "adelie", "gentoo", "takeaway")) |