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?
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()
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()
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"))
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)
}
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.
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:
grid.move.to()
and grid.line.to()
) demonstrated in Murrell's article. –
Spirometer © 2022 - 2024 — McMap. All rights reserved.
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