rggplot2plot

ggplot2: rectangular background behind axis text in circular bar plot


Goal

Replicating this circular bar plot:

Reference

Data

data <- data.frame(
  labels = c("A","B","C","D","E","F","G","H"),
  values = c(88, 61, 56, 77, 83, 63, 42, 60)
) %>%
  mutate(
    bar_color = case_when(
      values < 62 ~ "#ff7d48",
      values < 85 ~ "#76c5d0",
      values >= 85 ~ "#4b7d84",
    )
  )

My Code

ggplot(data) +
  geom_bar(
    stat = "identity",
    aes(
      x = labels,
      y = values,
      fill = bar_color
    ),
    size = 1,
    color = "gray20",
    width = 1,
    alpha = 0.9
  ) +
  coord_polar() +
  scale_fill_identity() +
  theme(
    panel.grid.major.x = element_line(
      linewidth = .8,
      color = "grey30"
    ),
    panel.grid.major.y = element_line(
      linewidth = .8,
      color = alpha("grey70", 0.5)
    ),
    axis.title = element_blank(),
    panel.background = element_blank(),
    axis.line.x.top = element_line(
      color = "grey20"
    ),
    axis.ticks.y = element_blank(),
    axis.text.y = element_blank()
  )

Initial result based on my code:

Initial plot

Need help on:

  1. The top line of x axis need to be grey30. The axis.line.x.top seems has no effect. Even after I remove coord_polar() just to test, it also didnt show any grey line.
  2. The I want the x axis text to have rectangular background. I've tried two attemps for this:

using geom_label

  1. How to make panel.grid.major.x on top of panel.grid.major.y. In return, now i just set the color of the second to alpha("grey70", 0.5)

Solution

  • Edit:

    You can 'automate' some of the plotting (e.g. calculate how many groups you have before plotting), but working out the breaks is complicated will require tweaking depending on your dataset (e.g. +1 or -1 accordingly):

    library(tidyverse)
    library(scales)
    
    data <- data.frame(
      labels = c("A","B","C","D","E","F","G","H"),
      values = c(88, 61, 56, 77, 83, 63, 42, 60)
    ) %>%
      mutate(
        bar_color = case_when(
          values < 62 ~ "#f2452e",
          values < 85 ~ "#76c5d0",
          values >= 85 ~ "#4b7d84",
        )
      )
    
    n <- length(unique(data$labels))
    max_val <- max(data$values)
    breaks_ext <- extended_breaks()(data$values)
    
    ggplot(data, aes(x = labels, y = values)) +
      geom_bar(aes(fill = bar_color),
               stat = "identity", linewidth = 1,
               color = "gray20", width = 1,
               alpha = 0.8) +
      coord_polar(start = -(pi / n)) +
      scale_fill_identity() +
      theme(
        panel.grid.major.y = element_line(
          linewidth = .8,
          color = c(rep(alpha("grey80", 0.5),
                        ifelse(max(breaks_ext) > max_val,
                               length(breaks_ext) - 2)), # change this manually to suit
                    "transparent")
        ),
        axis.title = element_blank(),
        panel.background = element_blank(),
        axis.ticks.y = element_blank(),
        axis.text = element_blank(),
      ) +
      geom_hline(yintercept = max_val + (max_val * 0.1),
                 color = "grey90", linewidth = 3) +
      geom_segment(aes(x = 1.5:(n + 0.5), y = 0,
                       yend = max_val + (max_val * 0.075)),
                   inherit.aes = FALSE, color = alpha("grey20", 0.5)) +
      geom_hline(yintercept = max_val + (max_val * 0.07),
                 color = "grey20", linewidth = 1) +
      geom_label(aes(label = labels, y = max_val + (max_val * 0.09)),
                 fill = "white", size = 6)
    

    With 'new' data:

    
    ####
    set.seed(1234)
    data <- data.frame(
      labels = c("A","B","C","D","E","F","G","H", "I", "J"),
      values = sample(1:100, 10, replace = TRUE)
    ) %>%
      mutate(
        bar_color = case_when(
          values < 62 ~ "#f2452e",
          values < 85 ~ "#76c5d0",
          values >= 85 ~ "#4b7d84",
        )
      )
    
    n <- length(unique(data$labels))
    max_val <- max(data$values)
    breaks_ext <- extended_breaks()(data$values)
    
    ggplot(data, aes(x = labels, y = values)) +
      geom_bar(aes(fill = bar_color),
               stat = "identity", linewidth = 1,
               color = "gray20", width = 1,
               alpha = 0.8) +
      coord_polar(start = -(pi / n)) +
      scale_fill_identity() +
      theme(
        panel.grid.major.y = element_line(
          linewidth = .8,
          color = c(rep(alpha("grey80", 0.5),
                        ifelse(max(breaks_ext) > max_val,
                               length(breaks_ext))), # change this manually to suit
                    "transparent")
        ),
        axis.title = element_blank(),
        panel.background = element_blank(),
        axis.ticks.y = element_blank(),
        axis.text = element_blank(),
      ) +
      geom_hline(yintercept = max_val + (max_val * 0.1),
                 color = "grey90", linewidth = 3) +
      geom_segment(aes(x = 1.5:(n + 0.5), y = 0,
                       yend = max_val + (max_val * 0.075)),
                   inherit.aes = FALSE, color = alpha("grey20", 0.5)) +
      geom_hline(yintercept = max_val + (max_val * 0.07),
                 color = "grey20", linewidth = 1) +
      geom_label(aes(label = labels, y = max_val + (max_val * 0.09)),
                 fill = "white", size = 6)
    

    Created on 2024-12-16 with reprex v2.1.0


    Original answer:

    Here is one potential approach:

    library(tidyverse)
    
    data <- data.frame(
      labels = c("A","B","C","D","E","F","G","H"),
      values = c(88, 61, 56, 77, 83, 63, 42, 60)
    ) %>%
      mutate(
        bar_color = case_when(
          values < 62 ~ "#f2452e",
          values < 85 ~ "#76c5d0",
          values >= 85 ~ "#4b7d84",
        )
      )
    
    ggplot(data, aes(x = labels, y = values)) +
      geom_bar(aes(fill = bar_color),
               stat = "identity", size = 1,
               color = "gray20", width = 1,
               alpha = 0.8) +
      coord_polar(start = -(pi / 8)) +
      scale_fill_identity() +
      theme(
        panel.grid.major.y = element_line(
          linewidth = .8,
          color = c("transparent", alpha("grey80", 0.5))
        ),
        axis.title = element_blank(),
        panel.background = element_blank(),
        axis.ticks.y = element_blank(),
        axis.text = element_blank(),
      ) +
      geom_hline(yintercept = 96.5,
                 color = "grey90", size = 3) +
      scale_y_continuous(breaks = seq(0, 95, 5)) +
      geom_segment(aes(x = 1.5:8.5, y = 0, yend = 95),
                   inherit.aes = FALSE, color = alpha("grey20", 0.5)) +
      geom_hline(yintercept = 94,
                 color = "grey20", size = 1) +
      geom_label(aes(label = labels, y = 96), fill = "white", size = 6)
    #> Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
    #> ℹ Please use `linewidth` instead.
    #> This warning is displayed once every 8 hours.
    #> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
    #> generated.
    

    Created on 2024-12-13 with reprex v2.1.0