library(tidyverse)
## Warning: package 'ggplot2' was built under R version 4.4.1

Status Quo: “A pie chart is just a bar chart with polar coordinates!*”

*But you do need to make sure that you do a couple of things with your standard geom_bar specification to make sure you you have a good experience.

  1. You’ve got to supply x or y constant
  2. Ideally, for #1, it’s y that you supply, so you don’t need to mess with the theta specification in coord_polar
  3. If you do any faceting, you’ll want to have position set to “fill” in the geom_bar.
  4. Also (not addressed here) reverse direction? clockwise from the top?
mpg_three <- mpg |> 
  filter(manufacturer %in% c("audi", "chevrolet", "dodge")) |>
  mutate(cyl = factor(cyl))

mpg_three |>
  ggplot() + 
  aes(y = 0) + # remember, use constant y (not x)
  aes(fill = cyl) + 
  geom_bar(
    position = "fill" # remember, if you want to facet, you'll need to reset this argument so your pies are all complete
    ) + 
  coord_polar() + 
  facet_wrap(~manufacturer) + 
  scale_x_reverse() 

last_plot() + 
  aes(fill = fct_infreq(cyl))

Let’s look at a high-level, first cut of geom_pie() that takes care of some of these finicky “*” requirements with make_constructor!

geom_pie <- make_constructor(GeomBar, 
                             stat = "count", 
                             position = "fill", 
                             mapping = aes(y = 1))


mpg_three |>
  ggplot() + 
  aes(fill = cyl) + 
  geom_pie() + 
  coord_polar() + 
  facet_wrap(~manufacturer)

but does not play nice with declaring aes locally 😔

mpg_three |>
  ggplot() + 
  geom_pie(aes(fill = cyl)) + # uh-oh
  coord_polar() + 
  facet_wrap(~manufacturer)
## Error in `geom_pie()`:
## ! Problem while computing stat.
## ℹ Error occurred in the 1st layer.
## Caused by error in `setup_params()`:
## ! `stat_count()` requires an x or y aesthetic.

maybe we can use modify list to specify aes(y = 0) by default

geom_pie
## function (mapping = aes(y = 1), data = NULL, stat = "count", 
##     position = "fill", ..., lineend = "butt", linejoin = "mitre", 
##     na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) 
## {
##     layer(mapping = mapping, data = data, geom = "bar", stat = stat, 
##         position = position, show.legend = show.legend, inherit.aes = inherit.aes, 
##         params = list2(na.rm = na.rm, lineend = lineend, linejoin = linejoin, 
##             ...))
## }
## <environment: 0x7fd34b610a38>
geom_pie <- function (mapping = NULL, data = NULL, stat = "count", 
    position = "fill", ..., lineend = "butt", linejoin = "mitre", 
    na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) 
{
  
  the_usual_required_mapping <- aes(y = 0)  #< new
  
  full_mapping <- if(is.null(mapping)){
      the_usual_required_mapping
    }else{
      modifyList(the_usual_required_mapping, mapping) #< new
    }
    

  
    layer(mapping = full_mapping, #< new
          data = data, geom = "bar", stat = stat, 
        position = position, show.legend = show.legend, inherit.aes = inherit.aes, 
        params = rlang::list2(na.rm = na.rm, lineend = lineend, linejoin = linejoin, 
            ...))
}


# or a bit problematic (like what if you really wanted to set y yourself) but easier to think about 
# geom_pie <- function (mapping = NULL, data = NULL, stat = "count",
#     position = "fill", ..., lineend = "butt", linejoin = "mitre",
#     na.rm = FALSE, show.legend = NA, inherit.aes = TRUE)
# {
# 
#   list(
#     layer(mapping = mapping,
#           data = data, geom = "bar", stat = stat,
#         position = position, show.legend = show.legend, inherit.aes = inherit.aes,
#         params = rlang::list2(na.rm = na.rm, lineend = lineend, linejoin = linejoin,
#             ...)),
#   aes(y = 0))
# 
#   }

Hooray! A geom_pie() that’s really like, 1) bar chart + 2) polar coords!

mpg_three |>
  ggplot() + 
  geom_pie(aes(fill = cyl)) 

last_plot()  + 
  facet_wrap(~manufacturer)

last_plot() + 
  coord_polar()

# demonstrate w/ global aes.
mpg_three |>
  ggplot() + 
  aes(fill = cyl) +
  geom_pie() + 
  coord_polar() 

but then we might also want to know relative size of pies - I would!

And is there any hacky way to do this with GeomBar? Seems like with width possibly but unsure…

mpg_three |>
  ggplot() + 
  aes(alpha = manufacturer) +
  geom_pie() + 
  coord_polar()
## Warning: Using alpha for a discrete variable is not advised.

ggforce::GeomArc
## <ggproto object: Class GeomArc, GeomPath, 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: linewidth colour linetype
##     optional_aes: 
##     parameters: function
##     rename_size: TRUE
##     required_aes: x y
##     setup_data: function
##     setup_params: function
##     use_defaults: function
##     super:  <ggproto object: Class GeomPath, Geom, gg>