tidyverse/ggplot2

How to get vertical lines in legend key using ggplot2 for geom_pointrange() type graphic

Closed this issue · 6 comments

As outlined on SO:

For ggplot2 graphics that have a symbol for a point estimate and a vertical line representing a range about that estimate (95% confidence interval, Inter-quartile Range, Minimum and Maximum, etc) I cannot get the legend key to show the symbol with a vertical line. Since geom_pointrange() only has arguments for ymin and ymax, I would think the intended (default) functionality of geom_pointrange(show_guide=T) would be to have vertical lines (I say default because I understand that with coord_flip one could make horizontal lines in the plot). I also understand that having vertical lines in the legend key when the legend position is right or left will have the vertical lines "run together"...but for legends in the top or bottom having a vertical line through the symbol means that the key will match what appears in the plot.

Yet the approaches I've tried still put horizontal lines in the legend key:

## set up
library(ggplot2)
set.seed(123)
ru <- 2*runif(10) - 1
dt <- data.frame(x   = 1:10, 
                 y   = rep(5,10)+ru, 
                 ylo = rep(1,10)+ru, 
                 yhi = rep(9,10)+ru,
                 s   = rep(c("A","B"),each=5),
                 f   = rep(c("facet1", "facet2"), each=5))

Default show_guide=T for geom_pointrange yields desired plot but has horizontal lines in legend key where vertical is desired (so as to match the plot):

ggplot(data=dt)+
  geom_pointrange(aes(x     = x, 
                      y     = y, 
                      ymin  = ylo, 
                      ymax  = yhi, 
                      shape = s), 
                  size=1.1,
                  show_guide=T)+
  theme(legend.position="bottom")

enter image description here

An attempt with geom_point and geom_segment together yields desired plot but has horizontal lines in legend key where vertical is desired (so as to match the plot):

ggplot(data=dt)+
  geom_point(aes(    x = x, 
                     y = y, 
                 shape = s), 
             size=3,
             show_guide=T)+
  geom_segment(aes(   x = x, 
                   xend = x, 
                      y = ylo, 
                   yend = yhi), 
               show_guide=T)+
  theme(legend.position="bottom")

enter image description here

An attempt with geom_point and geom_vline together yields desired legend key but does not respect the ymin and ymax values in the plot:

ggplot(data=dt)+
  geom_point(aes(x=x, y=y, shape=s), show_guide=T, size=3)+
  geom_vline(aes(xintercept=x, ymin=ylo, ymax=yhi ), show_guide=T)+
  theme(legend.position="bottom")

enter image description here

How do I get the legend key of the 3rd graph but the plot of one of the first two?

Attempted answers on (as outlined on SO):

1. Using geom_point(show_guide=T) + geom_segment(show_guide=F) + geom_vline(show_guide=T) where vline is plotted out of range of data and then coord_cartesian() excludes the vline.

My solution involves plotting a vertical line with geom_vline(show_guide=T) for an x-value that is out of the bounds of the displayed x-axis along with plotting geom_segment(show_guide=F):

ggplot(data=dt)+
  geom_point(aes(x=x, y=y, shape=s), show_guide=T, size=3)+
  geom_segment(aes(x=x, xend=x, y=ylo, yend=yhi), show_guide=F)+
  geom_vline(xintercept=-1, show_guide=T)+
  theme(legend.position="bottom")+
  coord_cartesian(xlim=c(0.5,10.5))

enter image description here

2. Using grid and gtable:

library(grid)
library(gtable)

gg <-     
ggplot(data=dt)+
  geom_pointrange(aes(x     = x, 
                      y     = y, 
                      ymin  = ylo, 
                      ymax  = yhi, 
                      shape = s), 
                  size=1.1,
                  show_guide=T)+
  theme(legend.position="bottom")

gb <- ggplot_build(gg)
gt <- ggplot_gtable(gb)

seg <- grep("segments", names(gt$grobs[[8]]$grobs[[1]]$grobs[[4]]$children))
gt$grobs[[8]]$grobs[[1]]$grobs[[4]]$children[[seg]]$x0 <- unit(0.5, "npc")
gt$grobs[[8]]$grobs[[1]]$grobs[[4]]$children[[seg]]$x1 <- unit(0.5, "npc")
gt$grobs[[8]]$grobs[[1]]$grobs[[4]]$children[[seg]]$y0 <- unit(0.1, "npc")
gt$grobs[[8]]$grobs[[1]]$grobs[[4]]$children[[seg]]$y1 <- unit(0.9, "npc")

seg <- grep("segments", names(gt$grobs[[8]]$grobs[[1]]$grobs[[6]]$children))
gt$grobs[[8]]$grobs[[1]]$grobs[[6]]$children[[seg]]$x0 <- unit(0.5, "npc")
gt$grobs[[8]]$grobs[[1]]$grobs[[6]]$children[[seg]]$x1 <- unit(0.5, "npc")
gt$grobs[[8]]$grobs[[1]]$grobs[[6]]$children[[seg]]$y0 <- unit(0.1, "npc")
gt$grobs[[8]]$grobs[[1]]$grobs[[6]]$children[[seg]]$y1 <- unit(0.9, "npc")

grid.newpage()
grid.draw(gt) 

enter image description here

You should probably redefine draw_key_pointrange.

draw_key_pointrange <- function(data, params, size) {
  grobTree(
    segmentsGrob(0.5, 0.1, 0.5, 0.9,
      gp = gpar(
        col = alpha(data$colour, data$alpha),
        lwd = data$size * .pt,
        lty = data$linetype,
        lineend = "butt"
      ),
      arrow = params$arrow
    ),
    draw_key_point(transform(data, size = data$size * 4), params)
  )
}

Minimal reprex:

df <- data.frame(x = 1:3, y = 1:3)
ggplot(df, aes(x, y, colour = factor(x))) +
  geom_pointrange(aes(ymin = y - 1, ymax = y + 1))

Fixed in 1ee49e2

How could the opposite be achieved (horizontal line ranges in legends), if we used horizontal geom_pointrange ?

Minimal reprex (showing vertical lines in legends):

df <- data.frame(x = 1:3, y = 1:3)
ggplot(df, aes(x, y, colour = factor(x))) +
  geom_pointrange(aes(ymin = y - 1, ymax = y + 1))+
  coord_flip()

Try ggstance?

require(ggstance)
require(ggplot2)
 df <- data.frame(x = 1:3, y = 1:3)
ggplot(df, aes(x, y, colour = factor(x))) +
     geom_pointrangeh(aes(xmin = x - 1, xmax = x + 1))

yes this works ( luckily ggstance has the geom_pointrangeh version ! )