27

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

Community
  • 1
  • 1
Rasmus Larsen
  • 5,721
  • 8
  • 47
  • 79

2 Answers2

18

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

user20650
  • 24,654
  • 5
  • 56
  • 91
  • Is it possible to make the second arrow begin where the first one ends, automatically, or do I have to do that manually somehow? – Rasmus Larsen Apr 20 '15 at 07:13
  • I dont think this can be easily done Rasmus. If you notice the end of the arrow goes beyond the actual xend position, but how far beyond (in native scale) depends on the width of the plot. So it would be hard to specify a start position for the second arrow (would be great if someone could show how). Perhaps, a quick fix would be to reverse the levels so the second arrow is underneath the first/? – user20650 Apr 22 '15 at 08:59
  • @user20650 Unfortunately it seems like your very nice answer no longer works in `ggplot 2.2.0`. If you have any time and possibility to update your answer, it will highly appreciated. Thanks in advance. – Henrik Nov 25 '16 at 15:57
  • @Henrik ; oh again, that's annoying. The answers of messing with the grobs don't really stand the test of time! Can you try that please: `grid.force()` seems to do the trick: shame that it is so interactive – user20650 Nov 25 '16 at 18:45
  • @Henrik ; actually it may be more robust now to tweak the geom (this one `GeomSegment$draw_panel` and `GeomSegment$draw_key()`? ) to allow for the arrow type to be explicit – user20650 Nov 25 '16 at 18:57
  • @user20650 Thank for taking your time! I tried `grid.force` - the upper part of the new arrowhead looks fine, but the lower part is truncated/cut horizontally just below the actual segment...(FWIW I'm on Windows). – Henrik Nov 25 '16 at 19:14
  • @Henrik : Sorry these render okay for me: do you have `type="closed"`. The upper and lower arrows get truncated due to the plot edge but by reducing the size they get plot okay (im on ubuntu) – user20650 Nov 25 '16 at 19:35
  • @user20650 I was using the code of OP, so yes I have `type = "closed"`. And I should have been clearer - the lower part of _each_ arrow is cut. Well, well, I have noted some problems before with `grid` graphics which seemed to be Windows-related. Again, thanks a lot for your rapid response! – Henrik Nov 25 '16 at 19:47
  • 1
    @Henrik ; okay, I tried this on Windows, and yup the bottom part of the arrow doesn't get drawn correctly: using either the op's code or when editing the grob. [ i tried updating the geom_segment function to take a linejoin argument, [here](http://chat.stackoverflow.com/rooms/129253/henrik-ggplot), and it works on ubuntu, but issue remains on windows) – user20650 Nov 28 '16 at 15:33
  • In order to make the solution to work for pdf device, you'll need to specify `pdf(..., onefile = FALSE)` – Mikko Jun 15 '22 at 08:38
6

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.

Art
  • 1,165
  • 6
  • 18