I want to make a plot consisting of multiple plots consisting of multiple plots, say a 5x2 grid with three plots in each cell. To be more precise, what I need is not just one figure but finding a way of using my plotting function multiple times in a single plot.
I have written a function that uses layout to stack plots, with a common axis in outer margin. I actually need it for seqIplot and seqdplot functions from the TraMineR package, but as far as I understand the problem is not related to those, so here is a minimal working example with barplot.
stackedplot <- function(data){
layout(matrix(c(1:3), nrow=3))
par(mar=c(0,0,0,0), oma=c(4,1,1,1), mgp=c(3,0.5,0), cex=1)
barplot(data[[1]], axes=F, xlab="", ylab="", horiz=TRUE)
barplot(data[[2]], axes=F, xlab="", ylab="", horiz=TRUE)
barplot(data[[3]], axes=F, xlab="", ylab="", horiz=TRUE)
axis(1, at=c(0:10)/10, outer=TRUE)
mtext("Label", line=2, side=1)
}
stackedplot(list(1:10, 10:1, rep(1,10)))
What I would like to do is to then use something like layout again and use stackedplot for the grids of the layout, i.e. something like this (which, of course, does not work):
layout(matrix(c(1:2), nrow=1))
stackedplot(list(1:10, 10:1, rep(1,10)))
stackedplot(list(rep(1,10), 1:10, 10:1))
I have tried split.screen, with no success:
split.screen(c(1,2))
screen(1)
stackedplot(list(1:10, 10:1, rep(1,10)))
screen(2)
stackedplot(list(rep(1,10), 1:10, 10:1))
close.screen(all = TRUE)
I also tried grid package, but apparently it is not compatible with base graphics.
grid.newpage()
pushViewport(viewport(x=0, y=0, width=0.5, height=1,
default.units="native"))
print(stackedplot(list(1:10, 10:1, rep(1,10))), newpage=FALSE)
pushViewport(viewport(x=0.5, y=0, width=0.5, height=1,
default.units="native"))
print(stackedplot(list(rep(1,10), 1:10, 10:1)), newpage=FALSE)
After more research and some help I am now answering my own question in case it would be useful to someone else.
Nested layouts can be created with the grid package, which can be used for base graphics using the gridBase package. The function for the stacked plots is written as follows.
library(grid)
library(gridBase)
stackedplot <- function(data, main=""){
top.vp <- viewport(layout=grid.layout(nrow=5, ncol=1,
heights=unit(c(3, 1, 1, 1, 5),
c("lines", "null", "null", "null", "lines"))),
width=unit(0.9, "npc"))
title <- viewport(layout.pos.row=1, layout.pos.col=1, name="title")
p1 <- viewport(layout.pos.row=2, layout.pos.col=1, name="plot1")
p2 <- viewport(layout.pos.row=3, layout.pos.col=1, name="plot2")
p3 <- viewport(layout.pos.row=4, layout.pos.col=1, name="plot3")
xaxis <- viewport(layout.pos.row=5, layout.pos.col=1, name="xaxis")
splot <- vpTree(top.vp, vpList(title, p1, p2, p3, xaxis)) # Defining the hierarchy of the viewports
pushViewport(splot) # Creating viewports for plotting with the definitions of splot
upViewport() # Navigating up in the viewport tree
downViewport("plot1") # Navigating down in the viewport tree, searching for viewport "plot1"
grid.rect() # Plotting a rectangle (borders for the viewport)
par(plt=gridPLT(), new=TRUE) # Taking the dimensions of the viewport for a base graphics plot
# Adding plot to an existing plot
barplot(data[[1]], axes=FALSE, xlab="", ylab="", horiz=TRUE)
upViewport()
downViewport("plot2")
grid.rect()
par(plt=gridPLT(), new=TRUE)
barplot(data[[2]], axes=FALSE, xlab="", ylab="", horiz=TRUE)
upViewport()
downViewport("plot3")
grid.rect()
par(plt=gridPLT(), new=TRUE)
barplot(data[[3]], xlab="", ylab="", horiz=TRUE)
upViewport()
downViewport("xaxis")
grid.text("X label", y = unit(2, "lines"))
upViewport()
downViewport("title")
grid.text(main, y = unit(1, "lines"))
upViewport(2)
}
The function first describes a viewport (of 90% of the window's width) that is divided into a 5x1 grid of viewports with differing heights. Each viewport in the grid is given a name that can be later called. The tree of viewports (splot) is described with vpTree which defines the hierarchical structure of the viewports. After describing the viewports those are actually prepared for plotting with pushViewport.
Now each named viewport is first seeked and then opened for plotting with upViewport (which goes up in the viewport tree) and downViewport (which seeks for the requested viewport down in the viewport tree).
For plotting base graphics, gridPLT is needed here (alternatively gridFIG or gridOMI can be used, see the manual of gridBase for further info). After that any base graphics function can be used to plot into the current viewport.
After the requested plots, upViewport(2) is used to navigate back to the root (2 viewports up in the hierarchy).
Now the stackedplot function can be called multiple times in another grid as follows.
opar <- par(no.readonly=TRUE) # Saving graphical parameters
plot.new() # Needed for par(new=TRUE) in stackedplot()
multitop.vp <- viewport(layout=grid.layout(1,2), width = unit(0.95, "npc"))
pl1 <- viewport(layout.pos.col=1, layout.pos.row=1, name="A")
pl2 <- viewport(layout.pos.col=2, layout.pos.row=1, name="B")
vpall <- vpTree(multitop.vp, vpList(pl1,pl2))
pushViewport(vpall)
upViewport()
downViewport("A")
stackedplot(data=list(1:10,10:1,rep(10,10)),main="A")
upViewport()
downViewport("B")
stackedplot(data=list(10:1,rep(10,10),1:10),main="B")
upViewport(2)
par(opar) # Returning the graphical parameters saved earlier
Related
I would like to increase the font size of axis annotations in a hexbinplot.
library(hexbin)
df <- data.frame(x=rnorm(1000),y=rnorm(1000))
hb <- hexbin(x=df$x, df$y)
myPlot <- plot(hb, xlab="", ylab="", legend=FALSE)
I would like the -3, ..., 2 and the -2, ..., 3 on the axes to be larger.
This earlier thread already helped me with axis labels, but the suggestion about how to change the annotations ("use grid.ls()" - how?) is a little too cryptic for me. I am more fluent in base graphics than in lattice.
Try this.
library(grid)
myPlot <- plot(hb, xlab="", ylab="", legend=FALSE)
grid.ls()
# GRID.rect.250
# GRID.xaxis.251
# GRID.yaxis.252
# GRID.polygon.253
grid.edit("GRID.xaxis.251", gp=gpar(fontsize=20))
grid.edit("GRID.yaxis.252", gp=gpar(fontsize=20))
The grid.ls() function shows the parts of the graph. The axis labels are GRID.xaxis.251 and GRID.yaxis.252. The name labels should be the same but the numbers will be different so you will have to modify the grid.edit() lines to match the output from grid.ls().
I'm creating a graphic that has a few different graph elements, using layout() to define plotting regions. I have a separate region for labels that need to align to bars on a barplot in an adjacent plotting region.
I can take a guess at where to plot the labels so that they line up - but the number of these locations will vary so this is not an ideal solution.
Here's an example of what I'm trying to do:
labs <- paste("Some text", letters[1:9])
datA <- table(sample(letters[1:9], size=200, replace=TRUE, prob=rep(c(0.1,0.2,0.3),3)))
layout(matrix(c(1,2,3,3), 2, 2, byrow=TRUE), widths=c(1,2), heights=c(6,1))
plot.new()
text(x=1, y=seq(0.05,1.0,0.111), labels=labs, adj=1, cex=1.4)
barplot(datA, horiz=TRUE, las=1, axes=F, yaxt="n")
How can I find the correct values to plot the labels?
(I'm aware that it looks like this can be solved by just plotting the labels with the barplot - this is not a viable solution for what I'm doing).
The output of barplot gives the heights so:
bp <- barplot(datA, horiz=TRUE, las=1, axes=F, yaxt="n")
text(0*bp, bp, labs, col = "blue", pos = 4)
I am creating graphs for a publication and would like them to have the same font size.
When I create a figure with multiple plots, the font size decreases even though I haven't changed the tiff() resolution or pointsize parameter.
I increased the figure size according to ultimately fit the number of plots, and made sure the margins are equivalent for single and multiple plot figures.
Following is an example code (The font size is consistent between 1x1 and 2x1 figure, but decreases for 3x2 figure):
tiff("1x1.tif", width=3,height=2.5,units="in",res=600,pointsize=8,
compression="lzw",restoreConsole=T)
par(mfrow=c(1,1),mar=c(4,4,.5,.5)+0.1)
plot(x=rnorm(10),y=rnorm(10))
dev.off()
tiff("2x1.tif", height=2.5*2,width=3,units="in",res=600,pointsize=8,
compression="lzw",restoreConsole=T)
par(mfrow=c(2,1),mar=c(2,4,2.5,0.5)+0.1)
plot(x=rnorm(10),y=rnorm(10),xaxt="n",xlab="")
par(mar=c(4,4,0.5,0.5)+0.1)
plot(x=rnorm(10),y=rnorm(10))
dev.off()
tiff("3x2.tif", height=2.5*3,width=3*2,units="in",res=600,pointsize=8,
compression="lzw",restoreConsole=T)
par(mfrow=c(3,2),mar=c(.5,4,4,0.5)+0.1)
plot(x=rnorm(10),y=rnorm(10),xaxt="n",xlab="")
par(mar=c(.5,2,4,2.5)+0.1)
plot(x=rnorm(10),y=rnorm(10),xaxt="n",xlab="",yaxt="n",ylab="")
par(mar=c(2.5,4,2,0.5)+0.1)
plot(x=rnorm(10),y=rnorm(10),xaxt="n",xlab="")
par(mar=c(2.5,2,2,2.5)+0.1)
plot(x=rnorm(10),y=rnorm(10),xaxt="n",xlab="",yaxt="n",ylab="")
par(mar=c(4.5,4,0,0.5)+0.1)
plot(x=rnorm(10),y=rnorm(10))
par(mar=c(4.5,2,0,2.5)+0.1)
plot(x=rnorm(10),y=rnorm(10),yaxt="n",ylab="")
dev.off()
Why is this happening?
P.S.: I'm not using ggplot2 or lattice because I'm using my own error bar function on the "actual" figures (I can't remember why right now but I tried working with the ggplot2 error bars and didn't get what I wanted).
The parameter controlling the overall relative size of objects in the plot (including text) is called cex. When you use many panels it is decreased by default, but it can be overridden by manually setting it to 1.
par(mfrow=c(3,2), mar=c(.5,4,4,0.5)+0.1, cex=1)
Off-topic-tip
It looks like you should use oma (outer margin) rather than calling par(mar=...) between the calls to plot. I find it very useful, but hardly anyone seems to know of it. Also ann=FALSE turns off all anotations, las=1 turns axis tick labels horizontal.
par(mfrow=c(3,2), oma=c(4.5, 4, 4, 2.5), mar=rep(.1, 4), cex=1, las=1)
plot(x=rnorm(10), y=rnorm(10), ann=FALSE, xaxt="n")
plot(x=rnorm(10), y=rnorm(10), ann=FALSE, xaxt="n", yaxt="n")
plot(x=rnorm(10), y=rnorm(10), ann=FALSE, xaxt="n")
plot(x=rnorm(10), y=rnorm(10), ann=FALSE, xaxt="n", yaxt="n")
plot(x=rnorm(10), y=rnorm(10), ann=FALSE)
plot(x=rnorm(10), y=rnorm(10), ann=FALSE, yaxt="n")
title("My plot", outer=TRUE)
mtext("X-axis label", 1, 3, outer=TRUE)
mtext("Y-axis label", 2, 3, outer=TRUE, las=0)
If I have several plots in a single panel, I would like to be able to put text annotations that go across more than one plot area or that go in between plot areas. I'm sure there must be a way to do this, but I need some help figuring out how.
Here's some fake data to create four plots on one panel:
x1 = rnorm(100)
x2 = rnorm(100)
x3 = rnorm(100)
x4 = rnorm(100)
par(mfrow=c(2,2))
hist(x1, xlab="", main="Group A")
hist(x2, xlab="", main="Group B")
hist(x3, xlab="", main="")
hist(x4, xlab="", main="")
This creates the multi-plot panel below, except that I've also added text in red. I added the red text by annotating the PDF file after I created the graph panel in R and then saved it as a PDF. What I'd like to learn is how to annotate the graph panel programmatically within R, rather than having to save it and annotate the saved file.
Normally, I would just add an annotation using text() or mtext(). But that only works if you're annotating a single graph. Is there a way to add annotations relative to the coordinates of the entire panel of 4 graphs? I'm looking for a solution in base graphics right now, but if anyone knows how to do something similar in ggplot2 or lattice, I'd be interested in seeing that as well.
If you truly want finer control over these kinds of layout issues, you can use the aptly named layout.
m <- matrix(c(1,2,3,3,4,5,6,6),ncol = 2,byrow = TRUE)
layout(m,widths = c(0.5,0.5),heights = c(0.45,0.05,0.45,0.05))
par(mar = c(2,4,4,2) + 0.1)
hist(x1, xlab="", main="Group A")
hist(x2, xlab="", main="Group B")
par(mar = c(0,0,0,0))
plot(1,1,type = "n",frame.plot = FALSE,axes = FALSE)
u <- par("usr")
text(1,u[4],labels = "Here",col = "red",pos = 1)
par(mar = c(2,4,2,2) + 0.1)
hist(x3, xlab="", main="")
hist(x4, xlab="", main="")
par(mar = c(0,0,0,0))
plot(1,1,type = "n",frame.plot = FALSE,axes = FALSE)
u <- par("usr")
text(1,u[4],labels = "Here",col = "red",pos = 1)
You want to use mtext with outer=TRUE. For instance, mtext('Results2', side=1, line=-2, outer=TRUE) and mtext('Results1', side=3, line=-20, outer=TRUE).
For this example the layout function is simplest as has been already shown, but for more general cases you should also learn the grconvertX and grconvertY functions (along with setting clipping using par(xpd=. The convert functions can give you the coordinates in the current user coordinates for different coordinate systems, so for example you could center something on the page by converting 0.5 from the normalized device coordinates.
Here is an example using Greg Snow's approach:
par(mfrow=c(2, 2))
replicate(4, plot(matrix(runif(20), nrow=10)))
text(grconvertX(.5, "ndc", "user"), grconvertY(.25, "ndc", "user"), "TEST", cex=3, col="red", xpd=NA)
text(grconvertX(.5, "ndc", "user"), grconvertY(.75, "ndc", "user"), "TEST", cex=3, col="red", xpd=NA)
I'm generating numerous plots with xlim and ylim values that I'm calculating on a per-plot basis. I want to put my legend outside the plot area (just above the box around the actual plot), but I can't figure out how to get the maximum y-value of the box around my plot area.
Is there a method for even doing this? I can move the legend where I want it by manually changing the legend() x and y values, but this takes a LONG time for the amount of graphs I'm creating.
Thanks!
-JM
Here's a basic example illustrating what I think you're looking for using one of the code examples from ?legend.
#Construct some data and start the plot
x <- 0:64/64
y <- sin(3*pi*x)
plot(x, y, type="l", col="blue")
points(x, y, pch=21, bg="white")
#Grab the plotting region dimensions
rng <- par("usr")
#Call your legend with plot = FALSE to get its dimensions
lg <- legend(rng[1],rng[2], "sin(c x)", pch=21,
pt.bg="white", lty=1, col = "blue",plot = FALSE)
#Once you have the dimensions in lg, use them to adjust
# the legend position
#Note the use of xpd = NA to allow plotting outside plotting region
legend(rng[1],rng[4] + lg$rect$h, "sin(c x)", pch=21,
pt.bg="white", lty=1, col = "blue",plot = TRUE, xpd = NA)
The command par('usr') will return the coordinates of the bounding box, but you can also use the grconvertX and grconvertY functions. A simple example:
plot(1:10)
par(xpd=NA)
legend(par('usr')[1], par('usr')[4], yjust=0, legend='anything', pch=1)
legend( grconvertX(1, from='npc'), grconvertY(1, from='npc'), yjust=0,
xjust=1, legend='something', lty=1)
The oma, omd, and omi arguments of par() control boundaries and margins of plots - they can be queried using par()$omd (etc). and set (if needed) using par(oma=c()) (where the vector can have up to 4 values - see ?par)