Intro Thoughts

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"))

Experiment

Closing remarks, Other Relevant Work, Caveats