How to Draw a Line Across a Multiple-Figure Environment in R

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

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()

Sample Image

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()

Sample Image

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"))

How to add a line across multiple plots in R

Here's a rough and dirty solution (for more fine-grained solutions, see How do you draw a line across a multiple-figure environment in R?):

DATA:

par(mfrow = c(2,1))
plot(rnorm(100))
plot(rnorm(100,50))

SOLUTION:

Use par(xpd = NA)to clip plotting to the device region:

par(xpd = NA)
abline(v = 80)

RESULT:
Sample Image

Connecting multiple panels with lines

This is the example I found, it may be useful for you:

layout(matrix(c(1,1,2,3), 2, 2, byrow = TRUE))
plot(runif(10), type='b', ylim=c(0,1))
x.tmp <- grconvertX(4, to='ndc')
y.tmp <- grconvertY(0.9, to='ndc')
plot(runif(20), type='l', ylim=c(0,1))
par(xpd=NA)
segments( 10, 1,
grconvertX(x.tmp, from='ndc'), grconvertY(y.tmp, from='ndc'), col='red' )
plot(runif(20), type='l')

R Two graphs with lines going from one to the other

The grid graphical system (which underlies both the lattice and ggplot2 graphics packages) is much better suited to this sort of operation than is R's base graphical system. Unfortunately, both of your plots use the base graphical system. Fortunately, though, the superb gridBase package supplies functions that allow one to translate between the two systems.

In the following (which starts with your call to par(mfrow=c(2,1),...)), I've marked the lines I added with comments indicating that they are My addition. For another, somewhat simpler example of this strategy in action, see here.

library(grid)      ## <-- My addition
library(gridBase) ## <-- My addition

par(mfrow=c(2,1),mar=c(0,5,4,6))
plot(fdata$y ~ fdata$x, xaxt = "n", ylab = "Comp.2", xlab = "",
col = color[fdata$city],pch=20)
vps1 <- do.call(vpStack, baseViewports()) ## <-- My addition
axis(3)
mtext(side = 3,"-Comp.1",line=3)
par(mar = rep(1,4))

#plot the map
plot(longlat,removeMargin=F)
vps2 <- do.call(vpStack, baseViewports()) ## <-- My addition
points(cities$lat ~ cities$long, col= color[cities$name],cex=1,pch=20)
text(cities$long,cities$lat-0.005,labels=cities$name)

## My addition from here on out...

## A function that draws a line segment between two points (each a
## length two vector of x-y coordinates), the first point in the top
## plot and the second in the bottom plot.
drawBetween <- function(ptA, ptB, gp = gpar()) {
## Find coordinates of ptA in "Normalized Parent Coordinates"
pushViewport(vps1)
X1 <- convertX(unit(ptA[1],"native"), "npc")
Y1 <- convertY(unit(ptA[2],"native"), "npc")
popViewport(3)
## Find coordinates of ptB in "Normalized Parent Coordinates"
pushViewport(vps2)
X2 <- convertX(unit(ptB[1],"native"), "npc")
Y2 <- convertY(unit(ptB[2],"native"), "npc")
popViewport(3)
## Plot line between the two points
grid.move.to(x = X1, y = Y1, vp = vps1)
grid.line.to(x = X2, y = Y2, vp = vps2, gp = gp)
}

## Try the function out on one pair of points
ptA <- fdata[1, c("x", "y")]
ptB <- cities[1, c("long", "lat")]
drawBetween(ptA, ptB, gp = gpar(col = "gold"))

## Using a loop, draw lines from each point in `fdata` to its
## corresponding city in `cities`
for(i in seq_len(nrow(fdata))) {
ptA <- fdata[i, c("x", "y")]
ptB <- cities[match(fdata[i,"city"], cities$name), c("long", "lat")]
drawBetween(ptA, ptB, gp = gpar(col = color[fdata[i,"city"]]))
}

Sample Image

R: Draw lines which fit the same shape as a reference line

I don't think Johannes' answer generalizes, e.g:

y_ref = c(0, 0, 0)
y_test = c(.03,.03, -.06) #then test_line fails even though, let:
y_test = y_test +.011
abs(y_test - y_ref) #never outside the .05 range
test_line(y_test) #failed

I think you want something like:

n = length(y_test)
d1 = y_test[-1] - y_test[-n]
d2 = y_ref[-1] - y_ref[-n]
max(cumsum(d2 - d1)) - min(cumsum(d2 - d1)) #shouldn't be >= .1

Creating line plot with time scale and labels in r

For constructing novel plots "from the ground up", and for maximal control over individual graphical elements, the grid graphical system is hard to beat:

library(grid)

## Set up plotting area with reasonable x-y limits
## and a "native" scale related to the scale of the data.
x <- -1:1
y <- extendrange(c(myd$myears, myd2$myears))
dvp <- dataViewport(x, y, name = "figure")

grid.newpage()
pushViewport(dvp)

## Plot the central timeline
grid.lines(unit(0, "native"), unit(c(0,245), "native"),
gp = gpar(col="dodgerblue"))

## Annotate LHS
grid.segments(x0=0.5, x1=0.47,
y0=unit(c(0, myd$myears), "native"),
y1=unit(c(0, myd$myears), "native"),
gp=gpar(col="dodgerblue"))
grid.text(label=c(0, myd$myears), x=0.44, y=unit(c(0, myd$myears), "native"))
grid.text(label=myd$period, x=0.3, y=unit(myd$label, "native"),
just=0, gp=gpar(col="dodgerblue", fontface="italic"))

## Annotate RHS
## Create a function that plots a pointer to the specified coordinate
pointer <- function(x, y, width=1) {
grid.polygon(x = x + unit(width*(c(0, .1, .1)), "npc"),
y = y + unit(width*(c(0, .03, -.03)), "npc"),
gp = gpar(fill="dodgerblue", col="blue", lwd=2))
}
## Call it once for each milestone
for(y in myd2$myears) {
pointer(unit(.5, "npc"), y=unit(y, "native"), width=0.3)
}
## Or, if you just want blue line segments instead of those gaudy pointers:
## grid.segments(x0=0.5, x1=0.53,
## y0=unit(c(myd2$myears), "native"),
## y1=unit(c(myd2$myears), "native"), gp=gpar(col="dodgerblue"))

grid.text(label=myd2$event, x=0.55, y=unit(myd2$myears, "native"),
just=0)

Sample Image



Related Topics



Leave a reply



Submit