27
votes

I'm trying to make a plot with arrows in ggplot2 looking something like this, which was made using base R grapics. (colors are not important)

enter image description here

Using ggplot2:

library(ggplot2)
library(scales)
library(grid)


df3 <- structure(list(value1 = c(51L, 57L, 59L, 57L, 56L, 56L, 60L, 
66L, 61L, 61L), value2 = c(56L, 60L, 66L, 61L, 61L, 59L, 61L, 
66L, 63L, 63L), group = c("A", "B", "C", "D", "E", "A", "B", 
"C", "D", "E"), time = c("1999", "1999", "1999", "1999", "1999", 
"2004", "2004", "2004", "2004", "2004"), y_position = c(1L, 2L, 
3L, 4L, 5L, 1L, 2L, 3L, 4L, 5L)), .Names = c("value1", "value2", 
"group", "time", "y_position"), row.names = c(NA, -10L), class = "data.frame") 




ggplot( df3, aes( x = value1, y = y_position, group = time, color = time)) +

geom_segment( x = min(df3$value1, df3$value2),  xend = max( df3$value1, df3$value2 ),
              aes( yend = y_position), color = "lightgrey", size = 19)  +


scale_y_continuous(  labels = df3$group, breaks = df3$y_position) + 

theme_classic() + theme( axis.line = element_blank(), axis.title = element_blank()  ) + 

geom_segment( aes( yend = y_position, xend = value2, color = time, group = time), size = 19, alpha = 0.9,

              arrow = arrow(length = unit(40, "points"),type = "closed", angle = 40)  )

I get this:

enter image description here

The problem is that the arrows look wrong (in that they don't look like the first plot). Using geom_segment() is not important.

This question may give the answer but I was hoping for something less hacky: Specifying gpar settings for grid arrows in R

2

2 Answers

18
votes

update: ggplot2 v2.1.0.9001

If the plot is in your current window you can edit the shape of the arrow directly with

grid.force()
# change shape of arrows
grid.gedit("segments", gp=gpar(linejoin ='mitre'))
# change the shape in legend also
grid.gedit("layout", gp=gpar(linejoin ='mitre'))

If the plot is in your current window you can edit the shape of the arrow directly with

grid.gedit("segments", gp=gpar(linejoin ='mitre'))

ggplot now seems to have changed the legend key to an arrow shape, so if you want to change the shape of these as well, you can do this across the full plot with

grid.gedit("gTableParent", gp=gpar(linejoin ='mitre'))

original answer

Not less hacky, but perhaps easier?? You can edit the grobs returned by ggplotGrob.

If p is your plot:

g <-  ggplotGrob(p)

idx <- grep("panel", g$layout$name)

nms <- sapply(g$grobs[[idx]]$children[[3]]$children , '[[', "name")

for(i in nms) {
    g$grobs[[idx]]$children[[3]] <- 
              editGrob(g$grobs[[idx]]$children[[3]], nms[i], 
                        gp=gpar(linejoin ='mitre'), grep=TRUE)
}

grid.newpage()
grid.draw(g)

enter image description here

5
votes

The challenge seems to be that the arrow constructor from the grid package gets messed up if size is invoked in the geom_segment block.

so

p <- ggplot(df3) + coord_flip()

p1 <- p + geom_bar(aes(x=group,y=max(c(value1,value2))*1.1),width=0.2, stat="identity",position="identity",alpha=0.2)

df1<-filter(df3,time=="1999")

p1 + geom_segment(data=df1,aes(x=group,xend=group,y=value1,yend=value2),color="blue",size=8,arrow=arrow(angle=20,type="closed",ends="last",length=unit(1,"cm")))

looks ridiculous as you show. I tried the workaround of of separating the segment into just a fat segment and an arrow on a skinny segment (two layers) like so:

p2<-p1 + geom_segment(data=df1,aes(x=group,xend=group,y=value1,yend=value2), color="blue",arrow=arrow(angle=20,type="closed",ends="last",length=unit(1,"cm")))

p2 + geom_segment(data=df1,aes(x=group,xend=group,y=value1,yend=value2), color="blue",size=8)

but now the fat segment end is not mitred and so obscures the arrow.

Fixing the arrow parameter seems to be needed.