How do you draw a line across a multiple-figure environment in R?
Asked Answered
Y

3

11

Take a very simple example, mfrow=c(1,3); each figure is a different histogram; how would I draw a horizontal line (akin to abline(h=10)) that went across all 3 figures? (That is, even the margins between them.) Obviously, I could add an abline to each figure, but that's not what I want. I can think of a very complicated way to do this by really only having 1 figure, and drawing each 'figure' within it using polygon etc. That would be ridiculous. Isn't there an easy way to do this?

Yellowgreen answered 2/4, 2012 at 23:2 Comment(1)
par(mfrow=c(1,3)); barplot(VADeaths); from <- grconvertX(par('usr')[1], 'user', 'ndc'); barplot(VADeaths); barplot(VADeaths); to <- grconvertX(par('usr')[2], 'user', 'ndc'); segments(grconvertX(from, 'ndc'), 50, grconvertX(to, 'ndc'), xpd = NA)Conveyance
P
17

As @joran noted, the grid graphical system offers more flexible control over arrangement of multiple plots on a single device.

Here, I first use grconvertY() to query the location of a height of 50 on the y-axis in units of "normalized device coordinates". (i.e. as a proportion of the total height of the plotting device, with 0=bottom, and 1=top). I then use grid functions to: (1) push a viewport that fills the device; and (2) plot a line at the height returned by grconvertY().

## Create three example plots
par(mfrow=c(1,3))
barplot(VADeaths, border = "dark blue") 
barplot(VADeaths, border = "yellow") 
barplot(VADeaths, border = "green") 

## From third plot, get the "normalized device coordinates" of 
## a point at a height of 50 on the y-axis.
(Y <- grconvertY(50, "user", "ndc"))
# [1] 0.314248

## Add the horizontal line using grid
library(grid)
pushViewport(viewport())
grid.lines(x = c(0,1), y = Y, gp = gpar(col = "red"))
popViewport()

enter image description here

EDIT: @joran asked how to plot a line that extends from the y-axis of the 1st plot to the edge of the last bar in the 3rd plot. Here are a couple of alternatives:

library(grid)
library(gridBase)
par(mfrow=c(1,3))

# barplot #1
barplot(VADeaths, border = "dark blue") 
X1 <- grconvertX(0, "user", "ndc")
# barplot #2
barplot(VADeaths, border = "yellow") 
# barplot #3
m <- barplot(VADeaths, border = "green") 
X2 <- grconvertX(tail(m, 1) + 0.5, "user", "ndc") # default width of bars = 1
Y <- grconvertY(50, "user", "ndc")

## Horizontal line
pushViewport(viewport())
grid.lines(x = c(X1, X2), y = Y, gp = gpar(col = "red"))
popViewport()

enter image description here

Finally, here's an almost equivalent, and more generally useful approach. It employs the functions grid.move.to() and grid.line.to() demo'd by Paul Murrell in the article linked to in @mdsumner's answer:

library(grid)
library(gridBase)
par(mfrow=c(1,3))

barplot(VADeaths); vps1 <- do.call(vpStack, baseViewports())
barplot(VADeaths) 
barplot(VADeaths); vps3 <- do.call(vpStack, baseViewports())

pushViewport(vps1)
Y <- convertY(unit(50,"native"), "npc")
popViewport(3)

grid.move.to(x = unit(0, "npc"), y = Y, vp = vps1)
grid.line.to(x = unit(1, "npc"), y = Y, vp = vps3, 
             gp = gpar(col = "red"))
Passageway answered 3/4, 2012 at 1:17 Comment(5)
+1 Nice! Do you know of a way to massage the units/coordinates/clipping such that the line extends from the left most y axis to just the right edge of the green bars?Ideomotor
I do have an idea, and I'll add it if it works out. Right now, I'm off to mow the lawn before the sun sets...Spirometer
Thanks, this is really helpful! I appreciate your willingness to defer lawn-mowing on my account ;-)Yellowgreen
@Ideomotor -- OK, I added a pair of ideas. The first one may look better, but wouldn't generalize well, even to barplots with juxtaposed (as opposed to stacked) bars. The second one terminates the line on the RHS of the plotting region (rather than on the RHS of the last bar), but it would function similarly for any kind of base R plot.Spirometer
@Ideomotor -- I think I've now figured out a relatively clean and general way to add lines of the sort you asked about. If you're interested, see the third (recently edited) code block.Spirometer
I
6

This is the best I can do without thinking about it harder:

par(mfrow = c(1,3),xpd = NA)

for (i in 1:3){
    x <- rnorm(200,i)
    hist(x)
    if (i == 1) segments(par("usr")[1],10,30,10)
}

enter image description here

I'm not sure how to make sure the line ends at the right spot without tinkering. Plotting a segment in each region would solve that, but introduce the issue of having the heights line up properly. But this might be a good starting point, at least.

I'd guess this is easier in grid graphics, but I'd have to do some research to verify.

Ideomotor answered 2/4, 2012 at 23:23 Comment(0)
O
4

This article by Paul Murrell shows the use of grid graphics to draw lines between two different coordinate systems, in this case lines that have end points specified in the native space of two separate sub-plots:

Paul Murrell. The grid graphics package. R News, 2(2):14-19, June 2002

It's on page 17 of the PDF article:

http://cran.r-project.org/doc/Rnews/Rnews_2002-2.pdf

Onlybegotten answered 2/4, 2012 at 23:48 Comment(1)
Yeah, thanks for that great link. I just added a 3rd code block to my answer, which uses a couple of the functions (grid.move.to() and grid.line.to()) demonstrated in Murrell's article.Spirometer

© 2022 - 2024 — McMap. All rights reserved.