Search code examples
rggplot2geom-area

ggplot2: Gannt chart with shaded seasons


I want to create a Gannt chart for a 3 year period, with the seasons lightly shaded, i.e., displayed as rectangles in background. The Y values are 'tasks' and I do not know how to get these character data accepted for the ymin and ymax arguments of geom_area(). It would also be great to know how to make a legend for the shading, and how to make it transparent. Here's what I have:

# make a data frame with timing of activities
act <- data.frame(
    task = c('Site preparation', 'Seed collection'),
    start=c('01/01/2026', '11/1/2026'),
    end = c('02/01/2026','12/15/2027'),
    group=c('TT-Livingston staff','TT-Pyrosilviculture'))

# Format start and end dates as Dates
act$start = with(act, as.Date(start, format = '%m/%d/%Y'))
act$end = with(act, as.Date(end, format = '%m/%d/%Y'))


#activ <- c(
#   "Site preparation", 
#   "Seed collection", 

#---------- Change tasks to ordered factors--
act$task <- with(act, factor(task, levels = rev(activ), ordered=TRUE))

#------- Create a data frame with information for background shading -----
st <- as.Date("2025-12-01")             # Start date
en <- as.Date("2028-09-01")             # End date
rect_L <- seq(st + 1, en + 1, by = "3 months") - 1   #Left side of rectangle
rect_R <- seq(st + 31, en + 31 , by = "3 months") - 1 # Right sides of rectangles
season <- rep(c("winter","spring","summer","autumn"),3)
shades <- rep(c("grey70","green","yellow","pink"),3)

shade <- data.frame(
  xmin = rect_L,
  xmax = rect_R ,
  ymin = rep("Seed collection", 12),
  ymax = rep("Site preparation", 12),
  season = season,
  shades = shades
    )

#-------- Do the graph ------

ggplot(act, aes(x = start, xend = end, y = task, yend = task, color = group)) +
  geom_segment(size = 6) +
  labs(title = "Schedule of Activities", x = "Date", y = "Task") +
  theme_minimal() +
  geom_area(data = shade, aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax=ymax,
            fill=shades, alpha=0.8))

Solution

  • As already suggested by @Kat in the comments, a geom_area makes no sense and you should/could use a geom_rect instead. Additionally, as you want a background shading I would suggest to switch the layers. Finally, to get a separate legend for the shadings and use an individual color palette I use the ggnewscale package which allows for multiples scales and legends for the same aesthetic:

    library(ggplot2)
    library(ggnewscale)
    
    cols_season <- setNames(
      unique(shade$shades),
      unique(shade$season)
    )
    shade$season <- factor(
      shade$season,
      c("winter", "spring", "summer", "autumn")
    )
    
    ggplot(act, aes(x = start, xend = end, y = task, yend = task, color = group)) +
      geom_rect(
        data = shade, aes(
          xmin = xmin, xmax = xmax,
          fill = season
        ), ymin = -Inf, ymax = Inf,
        alpha = 0.2, inherit.aes = FALSE
      ) +
      scale_fill_manual(
        values = cols_season
      ) +
      ggnewscale::new_scale_fill() +
      geom_segment(size = 6) +
      scale_fill_discrete() +
      theme_minimal() +
      labs(title = "Schedule of Activities", x = "Date", y = "Task")
    

    enter image description here