Search code examples
rggplot2r-gridgtable

changing the legends in ggplot2 to have groups of similar labels


this question builds off of this solution provided at enter link description here

as follows.

library(ggplot2)
library(gtable)
library(grid)

diamonds$cut = factor(diamonds$cut, levels=c("Fair","Good"," ","Very Good",
                                             "Premium","Ideal"))

p = ggplot(diamonds, aes(color, fill = cut)) + 
       geom_bar() + 
       scale_fill_manual(values = 
              c(hcl(seq(15, 325, length.out = 5), 100, 65)[1:2], 
              "white",
              hcl(seq(15, 325, length.out = 5), 100, 65)[3:5]),
              drop = FALSE) +
  guides(fill = guide_legend(ncol = 2, title.position = "top")) +
  theme(legend.position = "bottom", 
        legend.key = element_rect(fill = "white"))

# Get the ggplot grob
g = ggplotGrob(p)

# Get the legend
leg = g$grobs[[which(g$layout$name == "guide-box")]]$grobs[[1]]

# Set up the two sub-titles as text grobs
st = lapply(c("First group", "Second group"), function(x) {
   textGrob(x, x = 0, just = "left", gp = gpar(cex = 0.8)) } )

# Add a row to the legend gtable to take the legend sub-titles
leg = gtable_add_rows(leg, unit(1, "grobheight", st[[1]]) + unit(0.2, "cm"), pos =  3)

# Add the sub-titles to the new row
leg = gtable_add_grob(leg, st, 
            t = 4, l = c(2, 6), r = c(4, 8), clip = "off")

# Add a little more space between the two columns
leg$widths[[5]] = unit(.6, "cm")

# Move the legend to the right
 leg$vp = viewport(x = unit(.95, "npc"), width = sum(leg$widths), just = "right")

# Put the legend back into the plot
g$grobs[[which(g$layout$name == "guide-box")]] = leg

# Draw the plot
grid.newpage()
grid.draw(g)

This provides the figure (from there).

enter image description here

However, I want to do two things.

  1. First group: I want to relabel "Fair" and "Good" to "Very Good" and "Premium" respectively. So they are Very Good and Premium of each group (first and second).

  2. The second thing I would like to do is to put them in one line (in two columns of First group and second group side by side, everything in one line, to save vertical space.

How do I do this? Please let me know if my questions are not clear. I feel like I am quite close here, but this solution does not quite answer my question. Many thanks!


Solution

  • First, you could simply change the labels appearing in the legend via the labels argument of scale_fill_manual. Second, instead of fiddling around with the gtable which probably is even more demanding if you want the groups in one line you could make use of the ggnewscale package as already proposed by this answer in the post you referenced.

    While that answer additionally makes use of the relayer package my approach is a bit different in that I rearrange the order of the layers such that the relayer package is not necessary.

    library(ggplot2)
    library(ggnewscale)
    
    diamonds$cut = factor(diamonds$cut, levels=c("Fair","Good", "Very Good",
                                                 "Premium","Ideal"))
    
    labels <- levels(diamonds$cut)
    labels <- setNames(labels, labels)
    labels["Fair"] <- "Very Good"
    labels["Good"] <- "Premium"
    
    colors <- hcl(seq(15, 325, length.out = 5), 100, 65)
    colors <- setNames(colors, levels(diamonds$cut))
    
    ggplot() + 
      geom_bar(data = diamonds, aes(color, fill = cut)) + 
      scale_fill_manual(aesthetics = "fill", values = colors, labels = labels[1:2],
                        breaks = names(colors)[1:2], name = "First Group:",
                        guide = guide_legend(title.position = "left", order = 1)) +
      new_scale_fill() +
      geom_bar(data = diamonds, aes(color, fill = cut)) + 
      scale_fill_manual(aesthetics = "fill", values = colors, labels = labels[3:5],
                        breaks = names(colors)[3:5], name = "Second Group:",
                        guide = guide_legend(title.position = "left", order = 0)) +
      theme(legend.position = "bottom", 
            legend.direction = "horizontal",
            legend.key = element_rect(fill = "white"))