Search code examples
rggplot2bar-chartlegendstacked-chart

Sharing multiple ggplot legend symbols with one label


I am trying to create a stacked barplot for one response over time (Year) across 3 different sites that I've separated into different facets (facet_wrap). I would like different colours per facet, and I thought the easiest way of achieving this would be to create a new variable ('Col_Var'), but keeping the facet per site. This gives me almost exactly what I'm after.

enter image description here

But I would like a simpler legend whereby all of the darker colours (i.e., 3 boxes) are placed next to the 'Type A' label, and the 3 lighter colour boxes are places next to 'Type B' (my labels are ideally much longer than this so the legend becomes very chaotic). In other words, can I place multiple boxes / symbols next to 1 label in a ggplot legend? Some example code below, any help or advice much appreciated, many thanks.

Plot_df <- data.frame(Site = rep(c("Site A","Site B","Site C"),each =10), 
                      Type = rep(c("Type A","Type B"),times =15), 
                      Year = rep(rep(seq(2010,2014,1),each=2),times=3),
                      Value = rnorm(30, mean=50, sd=10))
Plot_df$Col_Var <- as.factor(paste(Plot_df$Site,Plot_df$Type))

Stacked_Plot_Colours <- "#027E5D" "#89EFC7" "#6F2D01" "#F5773A" "#3A3663" "#8F8ACD"

 Stacked_Plot <- ggplot(Plot_df) +
  geom_bar(aes(x = as.factor(Year), y = Value, fill = Col_Var),position = "stack",stat = 
"identity") +
  facet_wrap(~ Site,ncol=1) +  scale_fill_manual(values=Stacked_Plot_Colours)+
  theme(legend.position = "top",legend.title = element_blank(),
        panel.grid.major = element_blank(), panel.grid.minor = element_blank(),
        panel.background = element_blank(), axis.line = element_line(colour = "black"))
Stacked_Plot

Solution

  • For your legend one option would be to replace the first two labels per row by an empty string and do some cleaning for the last label:

    library(ggplot2)
    
    ggplot(Plot_df) +
      geom_bar(aes(x = as.factor(Year), y = Value, fill = Col_Var),
        position = "stack", stat =
          "identity"
      ) +
      facet_wrap(~Site, ncol = 1) +
      scale_fill_manual(
        labels = \(x) {
          x[!seq_along(x) %in% 5:6] <- ""
          gsub("^Site\\s.*?\\s", "", x)
        },
        values = Stacked_Plot_Colours
      ) +
      theme(
        legend.position = "top", legend.title = element_blank(),
        panel.grid.major = element_blank(), panel.grid.minor = element_blank(),
        panel.background = element_blank(), axis.line = element_line(colour = "black")
      )
    

    enter image description here

    UPDATE If you want to put the legend in one row you could reorder the categories or keys using the breaks argument. However, doing so also requires to account for the reordered breaks when replacing the "undesired" labels by the empty string:

    scale_fill_manual(
      breaks = \(x) {
        x[c(1, 3, 5, 2, 4, 6)]
      },
      labels = \(x) {
        x[!seq_along(x) %% 3 == 0] <- ""
        gsub("^Site\\s.*?\\s", "", x)
      },
      values = Stacked_Plot_Colours,
      guide = guide_legend(nrow = 1)
    )
    

    enter image description here