Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to draw lines outside of plot area in ggplot2?

Tags:

r

ggplot2

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

like image 833
Max C Avatar asked May 10 '12 00:05

Max C


2 Answers

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

like image 141
Sandy Muspratt Avatar answered Sep 22 '22 00:09

Sandy Muspratt


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

like image 41
Max C Avatar answered Sep 24 '22 00:09

Max C