rggplot2bar-chartpolar-coordinatesgeom-segment

Draw geom_segment that crosses zero in ggplot2 coord_polar


I am modifying this tutorial to create a circular calendar of a year. I am trying to subdivide the calendar into seasons, but because the geom_segment for winter crosses zero degrees on my polar plot, the winter geom_segment goes counter clockwise instead of clockwise from a starting point of 12 to an ending point of 2, intersecting the geom_segments of the other seasons. I know that crossing zero is the issue because in my first pass I binned the seasons starting at 1, just to make the plot run, and the segments and labels didn't intersect.

Here's what the segments should look like, but I've removed winter because it's causing problems. enter image description here

Here's what it looks like when I include winter, which intersects the other segments instead of crossing zero. enter image description here

Here is my code:

library(ggplot2)
library(tidyverse)
library(viridis)
library(forcats)
library(data.table)
library(dplyr)


# Create dataset
data <- data.frame(
  month=factor(c("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")),
  group=factor(c( rep('WI', 3), rep('SP', 3), rep('SU', 3), rep('FA', 3))) ,
  value1=1.5,
  value2=5,
  value3=1.5,
  value4=10,
  value5=20,
  value6=30
)

# Transform data in a tidy format (long format)
data <- data %>% gather(key = "observation", value="value", -c(1,2))

# Get the name and the y position of each label
label_data <- data %>% group_by(month = factor(month, levels = unique(month))) %>% summarize(tot=sum(value))
number_of_bar <- nrow(label_data)
label_data <- rownames_to_column(label_data)
names(label_data)[names(label_data) == "rowname"] <- "id"
label_data$id <- as.numeric(label_data$id)
angle <- 90 - 360 * (label_data$id-0.5) /number_of_bar     # I substract 0.5 because the letter must have the angle of the center of the bars. Not extreme right(1) or extreme left (0)
label_data$hjust <- ifelse(angle < -90, 1, 0)
label_data$month <- c("J","F","M","A","M","J","J","A","S","O","N","D")

group <- c("WI","SP","SU","FA")
start <- c(12,3,6,9)
end <- c(2,5,8,11)

base_data <- data.frame(group,start,end)
base_data <- base_data %>% 
  mutate(title = (start + end)/2)
# base_data <- base_data[-1,] # adding this row removes the winter segment and text completely, leaving it in causes the problem

# prepare a data frame for grid (scales)
grid_data <- base_data
grid_data$end <- grid_data$end[ c( nrow(grid_data), 1:nrow(grid_data)-1)] + 1
grid_data$start <- grid_data$start - 1
grid_data <- grid_data[-1,]

p <- ggplot(data) +      
  
  # Add the stacked bar
  geom_bar(aes(x=factor(month), y=value, fill=observation), stat="identity", alpha=0.5) +
  scale_fill_viridis_d() +
  
  ylim(-30,70) +
  theme_minimal() +
  theme(
    legend.position = "none",
    axis.text = element_blank(),
    axis.title = element_blank(),
    panel.grid = element_blank(),
    plot.margin = unit(rep(-1,4), "cm") 
  ) +
  coord_polar() +
  
  # Add labels on top of each bar
  geom_text(data=label_data, aes(x=id, y=63.5, label=month, hjust=hjust), color="black", fontface="bold",alpha=0.6, size=4, inherit.aes = FALSE ) +
  
  # Add base line information
  geom_segment(data=base_data, aes(x = start, y = -5, xend = end, yend = -5), colour = "black", alpha=0.8, size=0.6 , inherit.aes = FALSE )  +
  geom_text(data=base_data, aes(x = title, y = -10, label=group), colour = "black", alpha=0.8, size=4, fontface="bold", inherit.aes = FALSE) 
  
p

Solution

  • One option to achieve your desired result would be to first draw a full circle, then add the gaps between the seasons using a second geom_segment or geom_path for which you set the color to "white".

    Note: In the code below I use annotate with geom="path"/"text" to add the segments and the text.

    library(ggplot2)
    
    p <- ggplot(data) +
      # Add the stacked bar
      geom_bar(aes(x = factor(month), y = value, fill = observation),
        stat = "identity", alpha = 0.5
      ) +
      scale_fill_viridis_d() +
      ylim(-30, 70) +
      theme_minimal() +
      theme(
        legend.position = "none",
        axis.text = element_blank(),
        axis.title = element_blank(),
        panel.grid = element_blank(),
        plot.margin = unit(rep(-1, 4), "cm")
      ) +
      # Add labels on top of each bar
      geom_text(
        data = label_data, aes(x = id, y = 63.5, label = month, hjust = hjust),
        color = "black", fontface = "bold", alpha = 0.6, size = 4, inherit.aes = FALSE
      ) +
      # Add base line information
      annotate(
        geom = "path",
        x = c(.5, 12.5), y = -5, 
        color = "black", alpha = 0.8, linewidth = 0.6, 
      ) +
      annotate(
        geom = "path",
        x = c(2, 3, 5, 6, 8, 9, 11, 12), y = -5,
        group = c(0, 0, 1, 1, 2, 2, 3, 3),
        color = "white", linewidth = 1, 
      ) +
      annotate(
        geom = "text",
        x = c(1, 4, 7, 10), y = -12,
        label = c("WI", "SP", "SU", "FA"),
        alpha = 0.8, size = 4, fontface = "bold"
      ) +
      coord_polar()
    
    p