How to draw lines outside of plot area in ggplot2?
Asked Answered
I

3

28

I created this plot with ggplot2:

enter image description here

The outside lines need to correspond to the Y scale, (i.e the Y position of the lines for Text1 should be 100 and 85). The only way I can do it by drawing a blank plot to the right of the figure with the same scale as the barchart and then using annotate function to draw the lines. Another approach is to simply "manually" draw the lines with grid.lines, however the coordinates of grid.lines will not correspond to the Y scale of the plot.

Is it possible to somehow draw these lines using a different approach? I assume it would have to be done with grid.lines. How could I pass Y coordindates of the barchart to grid.lines?

Below is the minimal code used to create this figure:

library (ggplot2)
test= data.frame(
  group=c(rep(1,6), rep(2,6)),
  subgroup=c( 1,1,1,2,2,2,1,1,1,2,2,2),
  category=c( rep(1:3, 4)),
  count=c( 10,80,10,5,90,5,  10,80,10,5,90,5   )
  )

qplot(subgroup, 
      count, 
      data=test, 
      geom="bar", 
      stat="identity",
      fill =category,  
      facets =  .~ group,  width=0.9)+
      opts(legend.position="none",
           plot.margin = unit(c(0,9,2,0), "lines"))

enter image description here

How can I draw the lines to the right of the bars?

I recently asked a question about drawing text outside of plot area in ggplot2 and the solution was to use gt$layout and grid.draw.

Displaying text below the plot generated by ggplot2

Could the similar approach be used here? It is my understanding that annotation_custom is for text only and won't work with other graphical elements. Thanks

Italianism answered 10/5, 2012 at 0:22 Comment(0)
I
27

Update

The original solution used annotation_custom, but a problem with annotation_custom is that it draws the annotation in all panels. However, with a simple modification, annotation_custom can be made to draw in one panel only (taken from Baptiste's answer here)

annotation_custom2 <- 
function (grob, xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = Inf, data) 
{
  layer(data = data, stat = StatIdentity, position = PositionIdentity, 
        geom = ggplot2:::GeomCustomAnn,
        inherit.aes = TRUE, params = list(grob = grob, 
                                          xmin = xmin, xmax = xmax, 
                                          ymin = ymin, ymax = ymax))
}

library(ggplot2)
library(grid)

 #Some data
test = data.frame(
  group=c(rep(1,6), rep(2,6)),
  subgroup=c( 1,1,1,2,2,2,1,1,1,2,2,2),
  category=c( rep(1:3, 4)),
  count=c( 10,80,10,5,90,5,  10,80,10,5,90,5   )
  )

# base plot
p <- ggplot(test) +
   geom_bar(aes(subgroup, count, fill = category), stat = "identity") +
   facet_grid(. ~ group) +
  theme(legend.position = "none",  
        plot.margin = unit(c(1,5,1,1), "lines"))

# Create the text Grobs
Text1 = textGrob("Text 1")
Text2 = textGrob("Text 2")
Text4 = textGrob("Text 4")

## Add the annotations
# Which panel to attach the annotations
data = data.frame(group=2)

# Text 1
p1 = p + annotation_custom2(Text1,  xmin = 3., xmax = 3., ymin = 85, ymax = 100, data = data) +
    annotation_custom2(linesGrob(), xmin = 2.6, xmax = 2.75, ymin = 100, ymax = 100, data = data) +
    annotation_custom2(linesGrob(), xmin = 2.6, xmax = 2.75, ymin = 85, ymax = 85, data = data) +
    annotation_custom2(linesGrob(), xmin = 2.75, xmax = 2.75, ymin = 85, ymax = 100, data = data)

# Text 2
p1 = p1 + annotation_custom2(Text2,  xmin = 3, xmax = 3, ymin = 20, ymax = 80, data = data) +
    annotation_custom2(linesGrob(), xmin = 2.6, xmax = 2.75, ymin = 80, ymax = 80, data = data) +
    annotation_custom2(linesGrob(), xmin = 2.6, xmax = 2.75, ymin = 20, ymax = 20, data = data) +
    annotation_custom2(linesGrob(), xmin = 2.75, xmax = 2.75, ymin = 20, ymax = 80, data = data)

# Text 4
p1 = p1 + annotation_custom2(Text4,  xmin = 3, xmax = 3, ymin = 0, ymax = 15, data = data) +
    annotation_custom2(linesGrob(), xmin = 2.6, xmax = 2.75, ymin = 15, ymax = 15, data = data) +
    annotation_custom2(linesGrob(), xmin = 2.6, xmax = 2.75, ymin = 0, ymax = 0, data = data) +
    annotation_custom2(linesGrob(), xmin = 2.75, xmax = 2.75, ymin = 0, ymax = 15, data = data)


# Code to override clipping
gt <- ggplotGrob(p1)
gt$layout[grepl("panel", gt$layout$name), ]$clip <- "off"

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

Original Solution

I think almost any Grob created using grid() can be used in annotation_custom(). There might be neater ways to do this, but here's a way using grid, annotation_custom and @baptiste's code from here to override the clipping (as in the earlier post).

library (ggplot2)
library(grid)

test= data.frame(
  group=c(rep(1,6), rep(2,6)),
  subgroup=c( 1,1,1,2,2,2,1,1,1,2,2,2),
  category=c( rep(1:3, 4)),
  count=c( 10,80,10,5,90,5,  10,80,10,5,90,5   )
  )

## EDIT:  Updated qplot() command
p <- qplot(subgroup, count, 
  data = test, geom = "bar",  stat = "identity",
  fill = category,  
  facets = .~ group,  width = 0.9)+
  theme(legend.position="none",  plot.margin = unit(c(0,9,2,0), "lines"))



# Create the text Grobs
Text1 = textGrob("Text 1")
Text2 = textGrob("Text 2")
Text4 = textGrob("Text 4")

# Draw the plot
# Text 1
p1 = p + annotation_custom(grob = Text1,  xmin = 3., xmax = 3., ymin = 85, ymax = 100) +
    annotation_custom(grob = linesGrob(), xmin = 2.6, xmax = 2.75, ymin = 100, ymax = 100) +
    annotation_custom(grob = linesGrob(), xmin = 2.6, xmax = 2.75, ymin = 85, ymax = 85) +
    annotation_custom(grob = linesGrob(), xmin = 2.75, xmax = 2.75, ymin = 85, ymax = 100)

# Text 2
p1 = p1 + annotation_custom(grob = Text2,  xmin = 3, xmax = 3, ymin = 20, ymax = 80) +
    annotation_custom(grob = linesGrob(), xmin = 2.6, xmax = 2.75, ymin = 80, ymax = 80) +
    annotation_custom(grob = linesGrob(), xmin = 2.6, xmax = 2.75, ymin = 20, ymax = 20) +
    annotation_custom(grob = linesGrob(), xmin = 2.75, xmax = 2.75, ymin = 20, ymax = 80)

# Text 4
p1 = p1 + annotation_custom(grob = Text4,  xmin = 3, xmax = 3, ymin = 0, ymax = 15) +
    annotation_custom(grob = linesGrob(), xmin = 2.6, xmax = 2.75, ymin = 15, ymax = 15) +
    annotation_custom(grob = linesGrob(), xmin = 2.6, xmax = 2.75, ymin = 0, ymax = 0) +
    annotation_custom(grob = linesGrob(), xmin = 2.75, xmax = 2.75, ymin = 0, ymax = 15)

p1

# Code to override clipping
gt <- ggplot_gtable(ggplot_build(p1))
gt$layout$clip[gt$layout$name=="panel"] <- "off"
grid.draw(gt)

enter image description here

Insertion answered 10/5, 2012 at 1:53 Comment(7)
Sandy: Thanks again!!! I assumed the trick in in the text question only worked with text. This helps tremendously.Italianism
@Max Cherny, There's also a polylineGrob that should make the code a little more efficient.Insertion
A note of caution: The method will draw grobs in both facets. Try: p + annotation_custom(grob = textGrob("Text anywhere"), xmin = 1.5, xmax = 1.5, ymin = 50, ymax = 50)Insertion
Sandy: I am trying to use polylineGrob but having problems passing it to annotation_custom. I do not want to post a long program here or create another question, basically it seems to work, but I can't figure how x from polyline interact with those of annotation customItalianism
_______________________________________________________________Italianism
Mmmm. I've not used polylineGob with anotation_custom, and I think I was getting ahead of myself. I too can't see how to use the two and still be able to specify y-coordinates using the original Y axis scale. Sorry about that. Maybe someone else can chime in?Insertion
let us continue this discussion in chatItalianism
I
9

Updated opts has been deprecated; use theme instead.

Here's another solution. It gets round the problem of annotation_custom() drawing grobs in both panels. It draws two graphs: the first is your bar plot; the second contains only the annotations. Then the two are put together using grid.arrange() from the gridExtra package. However, your polyline issue remains.

There is still the issue of getting the y-axis scales the same in the two plots. But with care, it can be done. In the plot that contains the annnotations, notice how elements that could have an impact on the y-axis scale are not removed (via theme_blank(), but rather are hidden (using colour = NA).

library(ggplot2)
library(gridExtra)
library(grid)

test= data.frame(
  group=c(rep(1,6), rep(2,6)),
  subgroup=c( 1,1,1,2,2,2,1,1,1,2,2,2),
  category=c( rep(1:3, 4)),
  count=c( 10,80,10,5,90,5,  10,80,10,5,90,5))

# The bar plot
p1 <- ggplot(test, aes(subgroup, count, fill = category)) +
   geom_bar(stat = "identity") +
   facet_grid(.~ group) +
   theme(legend.position = "none",  
         plot.margin = unit(c(1,0,2,0), "lines"))

p1 <- p1 + ylim(0, 100)

# The empty plot to contain the annotations
p2 = ggplot(data.frame(x = c(1,2), y = c(0,100), z = c(1,1)), aes(x,y)) + theme_bw() + facet_wrap(~ z) +
   theme(axis.title.y = element_blank(),
        axis.title.x = element_text(colour = NA),
        axis.text.y = element_blank(),
        axis.text.x = element_text(colour = NA),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        axis.ticks = element_line(colour = NA),
        panel.border = element_rect(colour = NA),
        strip.background = element_rect(colour = NA, fill = NA),
        strip.text.x = element_text(colour = NA), 
        plot.margin = unit(c(1,0,2,0), "lines"))


# The annotations
Text1 = textGrob("Text 1")
Text2 = textGrob("Text 2")
Text4 = textGrob("Text 4")

p2 = p2 + annotation_custom(grob = Text1,  xmin = 1.4, xmax = 1.4, ymin = 85, ymax = 100) +
        annotation_custom(grob = linesGrob(), xmin = 1, xmax = 1.1, ymin = 100, ymax = 100) +
        annotation_custom(grob = linesGrob(), xmin = 1, xmax = 1.1, ymin = 85, ymax = 85) +
        annotation_custom(grob = linesGrob(), xmin = 1.1, xmax = 1.1, ymin = 85, ymax = 100)

p2 = p2 + annotation_custom(grob = Text2,  xmin = 1.4, xmax = 1.4, ymin = 20, ymax = 80) +
        annotation_custom(grob = linesGrob(), xmin = 1, xmax = 1.1, ymin = 80, ymax = 80) +
        annotation_custom(grob = linesGrob(), xmin = 1, xmax = 1.1, ymin = 20, ymax = 20) +
        annotation_custom(grob = linesGrob(), xmin = 1.1, xmax = 1.1, ymin = 20, ymax = 80)

p2 = p2 + annotation_custom(grob = Text4,  xmin = 1.4, xmax = 1.4, ymin = 0, ymax = 15) +
        annotation_custom(grob = linesGrob(), xmin = 1, xmax = 1.1, ymin = 15, ymax = 15) +
        annotation_custom(grob = linesGrob(), xmin = 1, xmax = 1.1, ymin = 0, ymax = 0) +
        annotation_custom(grob = linesGrob(), xmin = 1.1, xmax = 1.1, ymin = 0, ymax = 15)

# Putting the two plots together
plot = arrangeGrob(p1, p2, ncol = 2, widths = unit(c(10, 2), c("null", "null")))
grid.draw(plot)

bot

Insertion answered 10/5, 2012 at 21:46 Comment(4)
Sandy: Thank you so much. This is actually what I did it with my original plot - the figure in the very beggining was done this way. I mention this approach in my original question here. I also used the same approach to display text below the plot in my other post. However. I find this approach fairly complex, especially since my data will change from time and because I actually need to produce 4 plots per one report. This is why I decided to search for a better solution. Thank a lot again.Italianism
At this point, it does not look there is a an easy way to annotate GGPLOT. It can certainly be done but it is not as easy as with SAS DATA ANNOTATE facility or with base R functions.Italianism
Have you considered moving your question to the ggplot2 mailing list?Insertion
I did not have much luck with R mailing lists in the past. In any case, I think the solution here is good enough. I can certainly annotate the plot using your method and the method suggested at the other plot. ThanksItalianism
I
5

I added the lines/text using code from this link: Using grconvertX/grconvertY in ggplot2. This approach uses grid.text and grid.lines instead of grobs. I am not sure which approach is better.

I think grid.lines could be combined into grid.polyline statement or possibly done via a loop. The x and y positions can be set to one variable instead of hardcoding in every line.

The only possible complication is passing the scale to the viewport. However, as long as the the same scale is used in GGPLOT and the viewport this code should work. Note that the viewport is using the entire height of the plot as 0 to 100.

library (ggplot2)
library(grid)
library(gridBase)

test= data.frame(
  group=c(rep(1,6), rep(2,6)),
  subgroup=c( 1,1,1,2,2,2,1,1,1,2,2,2),
  category=c( rep(1:3, 4)),
  count=c( 10,80,10,5,90,5,  10,80,10,5,90,5   )
  )

qplot(subgroup, count, 
           data=test, geom="bar",  stat="identity",
           fill =category,  
           facets =  .~ group,  width=0.9)+
             opts(legend.position="none",  plot.margin = unit(c(0,9,2,0), "lines"))

current.vpTree()
downViewport('panel-4-6')
pushViewport(dataViewport( yscale=c(0,100), clip='off',xscale=c(0,1)))

grid.text(x=1.21,  y = 90, default.units='native' ,label="Text 1")
grid.text(x=1.21,  y = 55, default.units='native' ,label="Text 2")
grid.text(x=1.21,  y = 10, default.units='native' ,label="Text 3")

grid.lines(x=c(1.02,1.12), y = c(95,95), default.units='native' )
grid.lines(x=c(1.02,1.12), y = c(85, 85), default.units='native' )
grid.lines(x=c(1.12,1.12), y = c(85, 95), default.units='native' )

grid.lines(x=c(1.02,1.12), y = c(80, 80), default.units='native' )
grid.lines(x=c(1.02,1.12), y = c(20, 20), default.units='native' )
grid.lines(x=c(1.12,1.12), y = c(80, 20), default.units='native' )

grid.lines(x=c(1.02,1.12), y = c(5, 5), default.units='native' )
grid.lines(x=c(1.02,1.12), y = c(15, 15), default.units='native' )
grid.lines(x=c(1.12,1.12), y = c(5, 15), default.units='native' )

Apologies for any formatting problems - I simply pasted my code and used the code button to indent it.

enter image description here

Italianism answered 10/5, 2012 at 19:10 Comment(2)
If you remove the padding on the y-axis with scale_y_continuous(expand = c(0, 0)) then the scale does seem to be right.Insertion
Sandy: I Your solution with grobs worked better when applied to my original figure (not the mockup). Grobs refeence the actual height of the bars, while grid.lines/text have to be adjusted. I tried both approaches and yours is much better. Thanks a lot again!Italianism

© 2022 - 2024 — McMap. All rights reserved.