Add annotation and segments to groups of legend elements

前端 未结 2 766
予麋鹿
予麋鹿 2020-12-14 12:55

My ggplot has the following legend:

I want to group my individual legend variables, and add the group names and \"brackets\" like shown in legend below:

2条回答
  •  臣服心动
    2020-12-14 13:23

    It's an interesting question and a legend like that would look very nice. There's no data so I just tried it on a different plot - the code could probably be generalized much more but it is a first step :)

    First, the plot

    library(ggplot2)
    library(gtable)
    library(grid)
    
    df <- data.frame(
      x = rep(c(2, 5, 7, 9, 12), 2),
      y = rep(c(1, 2), each = 5),
      z = factor(rep(1:5, each = 2)),
      w = rep(diff(c(0, 4, 6, 8, 10, 14)), 2)
    )
    
    p <- ggplot(df, aes(x, y)) +
      geom_tile(aes(fill = z, width = w), colour = "grey50") +
      scale_fill_manual(values = c("1" = "red2", "2" = "darkorange3",
                                   "3" = "gold2", "4" = "olivedrab3",
                                   "5" = "olivedrab2"),
                        labels = c("High", "High", "High", "Low", "Low"))
    p
    

    And then the changes using gtable and grid libraries.

    grb <- ggplotGrob(p)
    
    # get legend gtable
    legend_idx <- grep("guide", grb$layout$name)
    
    leg <- grb$grobs[[legend_idx]]$grobs[[1]]
    
    # separate into labels and rest
    leg_labs <- gtable_filter(leg, "label")
    leg_rest <- gtable_filter(leg, "background|title|key")
    
    # connectors = 2 horizontal lines + one vertical one
    connectors <- gTree(children = gList(linesGrob(x = unit(c(0.1, 0.8), "npc"), y = unit(c(0.1, 0.1), "npc")),
                                        linesGrob(x = unit(c(0.1, 0.8), "npc"), y = unit(c(0.9, 0.9), "npc")),
                                        linesGrob(x = unit(c(0.8, 0.8), "npc"), y = unit(c(0.1, 0.9), "npc"))))
    
    # add both .. if many, could loop this
    leg_rest <- gtable_add_grob(leg_rest, connectors, t = 4, b = 6, l = 3, r = 4, name = "high.group.lines")
    leg_rest <- gtable_add_grob(leg_rest, connectors, t = 7, b = 8, l = 3, r = 4, name = "low.group.lines")
    
    # get unique labels indeces (note that in the plot labels are High and Low, not High-1 etc.)
    lab_idx <- cumsum(summary(factor(sapply(leg_labs$grobs, function(x) x$children[[1]]$label))))
    
    # add cols for extra space, then add the unique labels. 
    # theyre centered automatically because i specify top and bottom, and x=0.5npc
    leg_rest <- gtable_add_cols(leg_rest, convertWidth(rep(grobWidth(leg_labs$grobs[[lab_idx[1]]]), 2), "cm"))
    leg_rest <- gtable_add_grob(leg_rest, leg_labs$grobs[[lab_idx[1]]], t = 4, b = 6, l = 5, r = 7, name = "label-1")
    leg_rest <- gtable_add_grob(leg_rest, leg_labs$grobs[[lab_idx[2]]], t = 7, b = 8, l = 5, r = 7, name = "label-2")
    
    # replace original with new legend
    grb$grobs[[legend_idx]]$grobs[[1]] <- leg_rest
    
    grid.newpage()
    grid.draw(grb)
    

    Potential problems are

    • the group connector line-width depending on the original label width .. any fix for that?
    • the t, l, b, r coordinates being chosen by hand here (but this can be generalized using the lab_idx i created)
    • legend being pushed into plot because of expanded width (just have to add col_space to main gtable I think)

提交回复
热议问题