How to properly position labels with leader lines on faceted pie charts?

81 Views Asked by At

I'm trying to recreate a figure but am unable to get the positioning of the labels inside the pie charts right.

This is my current code:

ggplot(flying63, aes(x = '', y = percentage, fill = baby_on_plane)) +
  geom_col(width = 1, position = position_fill()) +
  coord_polar('y') +
  facet_grid(gender ~ age) +
  geom_label_repel(aes(label = sprintf("%.2f%%", percentage)),
             position = position_fill(), size = 3, max.time = 8) +
  theme(axis.text = element_blank(),
        axis.title = element_blank(),
        axis.ticks = element_blank())

Which looks like this: enter image description here

However, I want the labels to look as follows: enter image description here

Update:

input data:

flying63 <- structure(list(gender = c("Female", "Female", "Female", "Female", 
"Male", "Male", "Male", "Male", "Female", "Female", "Female", 
"Female", "Male", "Male", "Male", "Male", "Female", "Female", 
"Female", "Female", "Male", "Male", "Male", "Male"), age = structure(c(1L, 
2L, 3L, 4L, 1L, 2L, 3L, 4L, 1L, 2L, 3L, 4L, 1L, 2L, 3L, 4L, 1L, 
2L, 3L, 4L, 1L, 2L, 3L, 4L), levels = c("18-29", "30-44", "45-60", 
"> 60"), class = c("ordered", "factor")), baby_on_plane = c("Yes, very rude", 
"Yes, very rude", "Yes, very rude", "Yes, very rude", "Yes, very rude", 
"Yes, very rude", "Yes, very rude", "Yes, very rude", "Yes, somewhat rude", 
"Yes, somewhat rude", "Yes, somewhat rude", "Yes, somewhat rude", 
"Yes, somewhat rude", "Yes, somewhat rude", "Yes, somewhat rude", 
"Yes, somewhat rude", "No, not at all rude", "No, not at all rude", 
"No, not at all rude", "No, not at all rude", "No, not at all rude", 
"No, not at all rude", "No, not at all rude", "No, not at all rude"
), n = c(5L, 5L, 6L, 6L, 8L, 11L, 15L, 5L, 23L, 11L, 14L, 10L, 
19L, 34L, 19L, 17L, 47L, 62L, 75L, 71L, 35L, 50L, 74L, 55L), 
    percentage = c(6.66666666666667, 6.41025641025641, 6.31578947368421, 
    6.89655172413793, 12.9032258064516, 11.5789473684211, 13.8888888888889, 
    6.49350649350649, 30.6666666666667, 14.1025641025641, 14.7368421052632, 
    11.4942528735632, 30.6451612903226, 35.7894736842105, 17.5925925925926, 
    22.0779220779221, 62.6666666666667, 79.4871794871795, 78.9473684210526, 
    81.6091954022989, 56.4516129032258, 52.6315789473684, 68.5185185185185, 
    71.4285714285714), label_position = c(3.33333333333333, 3.2051282051282, 
    3.1578947368421, 3.44827586206896, 6.4516129032258, 5.78947368421055, 
    6.94444444444445, 3.24675324675324, 15.3333333333333, 7.05128205128205, 
    7.3684210526316, 5.7471264367816, 15.3225806451613, 17.8947368421052, 
    8.7962962962963, 11.038961038961, 31.3333333333333, 39.7435897435898, 
    39.4736842105263, 40.8045977011494, 28.2258064516129, 26.3157894736842, 
    34.2592592592593, 35.7142857142857)), class = c("grouped_df", 
"tbl_df", "tbl", "data.frame"), row.names = c(NA, -24L), groups = structure(list(
    gender = c("Female", "Female", "Female", "Female", "Female", 
    "Female", "Female", "Female", "Female", "Female", "Female", 
    "Female", "Male", "Male", "Male", "Male", "Male", "Male", 
    "Male", "Male", "Male", "Male", "Male", "Male"), age = structure(c(1L, 
    1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L, 4L, 4L, 4L, 1L, 1L, 1L, 2L, 
    2L, 2L, 3L, 3L, 3L, 4L, 4L, 4L), levels = c("18-29", "30-44", 
    "45-60", "> 60"), class = c("ordered", "factor")), baby_on_plane = c("No, not at all rude", 
    "Yes, somewhat rude", "Yes, very rude", "No, not at all rude", 
    "Yes, somewhat rude", "Yes, very rude", "No, not at all rude", 
    "Yes, somewhat rude", "Yes, very rude", "No, not at all rude", 
    "Yes, somewhat rude", "Yes, very rude", "No, not at all rude", 
    "Yes, somewhat rude", "Yes, very rude", "No, not at all rude", 
    "Yes, somewhat rude", "Yes, very rude", "No, not at all rude", 
    "Yes, somewhat rude", "Yes, very rude", "No, not at all rude", 
    "Yes, somewhat rude", "Yes, very rude"), .rows = structure(list(
        17L, 9L, 1L, 18L, 10L, 2L, 19L, 11L, 3L, 20L, 12L, 4L, 
        21L, 13L, 5L, 22L, 14L, 6L, 23L, 15L, 7L, 24L, 16L, 8L), ptype = integer(0), class = c("vctrs_list_of", 
    "vctrs_vctr", "list"))), row.names = c(NA, -24L), .drop = TRUE, class = c("tbl_df", 
"tbl", "data.frame")))
3

There are 3 best solutions below

2
dandrews On

I fudged up some data so I could work on this. I think what you are looking for is the box.padding = ... argument. You can play with the value to change the length of the offsets.

flying63 <- tibble(percentage = rep(c(80,7,13),4),
                   baby_on_plane=rep(c('Not Rude','Somewhat','Very'),4),
                   gender = sample(c('M','F'),12, replace = T),
                   age = sample(c('18','30','45','60'),12,replace = T))


ggplot(flying63, aes(x = '', y = percentage, fill = baby_on_plane)) +
  geom_col(width = 1, position = position_fill()) +
  coord_polar('y') +
  facet_grid(gender ~ age) +
  geom_label_repel(aes(label = sprintf("%.2f%%", percentage)),
                   position = position_fill(), size = 3, max.time = 8,
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                   box.padding = 1) + # This is the change
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  theme(axis.text = element_blank(),
        axis.title = element_blank(),
        axis.ticks = element_blank())

enter image description here

0
M-- On
library(tidyverse)
library(ggrepel)

datasets::iris %>% 
  transmute(age = as.numeric(cut_number(Sepal.Length,3)),
         gender = as.numeric(cut_number(Petal.Length,2)),
         respond = Species) %>% 
  mutate(n1 = n(), .by = c(age, gender, respond)) %>%
  mutate(n2 = n(), .by = c(age, gender)) %>%
  mutate(percentage = 100 * n1/n2, .by = c(age, gender)) %>% 
  unique() %>% 
  arrange(age, gender) %>% 
ggplot(aes(x = '', y = percentage, fill = respond)) +
  geom_col(width = 1, position = position_fill()) +
  coord_polar('y') +
  facet_grid(gender ~ age) +
  theme(axis.text = element_blank(),
        axis.title = element_blank(),
        axis.ticks = element_blank()) +
  geom_label_repel(aes(label = sprintf("%.0f%%", percentage)),
                   position = position_fill(vjust = 0.2), size = 3, max.time = 8, 
                   box.padding = 1)

Created on 2023-12-28 with reprex v2.0.2

0
TarJae On

To place label in the middle of each bar in a stacked barplot, you need to set the vjust parameter of position_stack():

What you need is position = position_stack(vjust = 0.5) Here is the original example:

library(dplyr)
library(ggplot2)

flying63 %>% 
  ggplot(aes(x = 1, y = percentage, fill = baby_on_plane)) +
  geom_col() +
  geom_text(aes(label = paste0(round(percentage, 1), "%")), 
            position = position_stack(vjust = 0.5),
            size = 6)+
  coord_polar("y", start=0) +
  facet_grid(gender ~ paste("Age: ",age)) +
  labs(fill = "Baby on plane")+
  theme_void()+
  theme(text = element_text(size=16),
        legend.position = "bottom")

enter image description here

data:

flying63 <- structure(list(gender = c("Female", "Female", "Female", "Female", 
"Male", "Male", "Male", "Male", "Female", "Female", "Female", 
"Female", "Male", "Male", "Male", "Male", "Female", "Female", 
"Female", "Female", "Male", "Male", "Male", "Male"), age = structure(c(1L, 
2L, 3L, 4L, 1L, 2L, 3L, 4L, 1L, 2L, 3L, 4L, 1L, 2L, 3L, 4L, 1L, 
2L, 3L, 4L, 1L, 2L, 3L, 4L), levels = c("18-29", "30-44", "45-60", 
"> 60"), class = c("ordered", "factor")), baby_on_plane = c("Yes, very rude", 
"Yes, very rude", "Yes, very rude", "Yes, very rude", "Yes, very rude", 
"Yes, very rude", "Yes, very rude", "Yes, very rude", "Yes, somewhat rude", 
"Yes, somewhat rude", "Yes, somewhat rude", "Yes, somewhat rude", 
"Yes, somewhat rude", "Yes, somewhat rude", "Yes, somewhat rude", 
"Yes, somewhat rude", "No, not at all rude", "No, not at all rude", 
"No, not at all rude", "No, not at all rude", "No, not at all rude", 
"No, not at all rude", "No, not at all rude", "No, not at all rude"
), n = c(5L, 5L, 6L, 6L, 8L, 11L, 15L, 5L, 23L, 11L, 14L, 10L, 
19L, 34L, 19L, 17L, 47L, 62L, 75L, 71L, 35L, 50L, 74L, 55L), 
    percentage = c(6.66666666666667, 6.41025641025641, 6.31578947368421, 
    6.89655172413793, 12.9032258064516, 11.5789473684211, 13.8888888888889, 
    6.49350649350649, 30.6666666666667, 14.1025641025641, 14.7368421052632, 
    11.4942528735632, 30.6451612903226, 35.7894736842105, 17.5925925925926, 
    22.0779220779221, 62.6666666666667, 79.4871794871795, 78.9473684210526, 
    81.6091954022989, 56.4516129032258, 52.6315789473684, 68.5185185185185, 
    71.4285714285714), label_position = c(3.33333333333333, 3.2051282051282, 
    3.1578947368421, 3.44827586206896, 6.4516129032258, 5.78947368421055, 
    6.94444444444445, 3.24675324675324, 15.3333333333333, 7.05128205128205, 
    7.3684210526316, 5.7471264367816, 15.3225806451613, 17.8947368421052, 
    8.7962962962963, 11.038961038961, 31.3333333333333, 39.7435897435898, 
    39.4736842105263, 40.8045977011494, 28.2258064516129, 26.3157894736842, 
    34.2592592592593, 35.7142857142857)), class = c("grouped_df", 
"tbl_df", "tbl", "data.frame"), row.names = c(NA, -24L), groups = structure(list(
    gender = c("Female", "Female", "Female", "Female", "Female", 
    "Female", "Female", "Female", "Female", "Female", "Female", 
    "Female", "Male", "Male", "Male", "Male", "Male", "Male", 
    "Male", "Male", "Male", "Male", "Male", "Male"), age = structure(c(1L, 
    1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L, 4L, 4L, 4L, 1L, 1L, 1L, 2L, 
    2L, 2L, 3L, 3L, 3L, 4L, 4L, 4L), levels = c("18-29", "30-44", 
    "45-60", "> 60"), class = c("ordered", "factor")), baby_on_plane = c("No, not at all rude", 
    "Yes, somewhat rude", "Yes, very rude", "No, not at all rude", 
    "Yes, somewhat rude", "Yes, very rude", "No, not at all rude", 
    "Yes, somewhat rude", "Yes, very rude", "No, not at all rude", 
    "Yes, somewhat rude", "Yes, very rude", "No, not at all rude", 
    "Yes, somewhat rude", "Yes, very rude", "No, not at all rude", 
    "Yes, somewhat rude", "Yes, very rude", "No, not at all rude", 
    "Yes, somewhat rude", "Yes, very rude", "No, not at all rude", 
    "Yes, somewhat rude", "Yes, very rude"), .rows = structure(list(
        17L, 9L, 1L, 18L, 10L, 2L, 19L, 11L, 3L, 20L, 12L, 4L, 
        21L, 13L, 5L, 22L, 14L, 6L, 23L, 15L, 7L, 24L, 16L, 8L), ptype = integer(0), class = c("vctrs_list_of", 
    "vctrs_vctr", "list"))), row.names = c(NA, -24L), .drop = TRUE, class = c("tbl_df", 
"tbl", "data.frame")))