I need to arrange several plots for a figure. I am creating individual plots using base and grid graphics. In order to arrange them in a single figure I have been using grid.echo(), grid.grab() to convert to grobs and then arrangeGrob() and grid.arrange() to build the final figure. A few weeks ago my tentative figure was working fine but now when I rerun the code it produces a figure with whitespace in the margins of the plots.
I add a minimal example that shows the problem that I am facing...
##minimal example
library(grid)
library(gridExtra)
library(gridGraphics)
##test plot
plot_n1<-plot(1:10,1:10, asp=1)
##convert test plot to grob
grid.echo()
test_p<-grid.grab()
##simulate several plots arranged in a more complex layout
multi<-arrangeGrob(test_p, test_p, test_p, test_p, ncol=1, heights=c(1/4,1/4,1/4,1/4))
##create graph
png(filename="minimal_multiplot.png", res=300, width=20, height=20, units="cm")
grid.arrange(test_p, multi, ncol=2, widths=c(2/3,1/3))
dev.off()
What am I doing wrong?
There does indeed appear to be a problem when converting a graphics plot into a grid plot, then using grid.grab() to grab and then draw the plot into a smaller regions (i.e., using your method). For instance, using viewports to define a slightly smaller region (coloured grey in the image below), axis material is missing.
# Packages
library(grid)
library(gridGraphics)
plot(1:10,1:10)
grid.echo()
test_p = grid.grab()
grid.newpage()
pushViewport(viewport(x = 0, width = .85, just = "left"))
grid.rect(gp = gpar(col = NA, fill = "grey90"))
grid.draw(test_p)
upViewport()
grid.rect(gp = gpar(col = "grey90", size = .1, fill = NA))
But Paul Murrell (author of the gridGraphics package) offers an alternative (see the examples at ?gridGraphics::grid.echo, and pp. 156-157 of The gridGraphics package in The R Journal v7/1). One can define a function that draws the plot, then that function becomes the argument of grid.echo() at the time of drawing the plot within the viewport. newpage = FALSE stops grid from opening a new page. Note that none of the axis material is chopped off.
pf = function() {
plot(1:10,1:10)
}
grid.newpage()
pushViewport(viewport(x = 0, width = .85, just = "left"))
grid.rect(gp = gpar(col =NA, fill = "grey90"))
grid.echo(pf, newpage=FALSE)
upViewport()
grid.rect(gp = gpar(col = "grey90", size = .1, fill = NA))
So to get your desired plot, I would do something like this - but still using viewports.
pf = function() {
par(mar=c(7.2, 7.2, 1, 1), mex = .3, tcl = .15, mgp = c(3, .15, 0))
plot(1:10, 1:10, cex.axis = .75, cex.lab = .75)
}
grid.newpage()
pushViewport(viewport(layout = grid.layout(3, 2,
widths = unit(c(2, 1), "null"),
heights = unit(c(1, 1, 1), "null"))))
pushViewport(viewport(layout.pos.col = 1, layout.pos.row = 1:3))
grid.echo(pf, newpage = FALSE)
upViewport()
for(i in 1:3) {
pushViewport(viewport(layout.pos.col = 2, layout.pos.row = i))
grid.echo(pf, newpage = FALSE)
upViewport()
}
upViewport()
grid.rect(gp = gpar(col = "grey90", size = .1, fill = NA))
Related
I'm trying to develop a multiplot heatmap.2 saved to a pdf. I'm having some success but the axis labels are getting chopped off. Subplot titles are also desirable but again the labels are getting chopped.
Here's my reproducible code:
library(gridExtra)
library(grid)
library(gridGraphics)
library(gplots)
Col = colorRampPalette(c("red","orange","yellow", "white"))
grab_grob <- function(){
grid.echo()
grid.grab()
}
par(cex.main=0.1, mar = c(1,1,1,1) )
#data<-read.table("heatmap.input.matrix.data.txt")
lmat = rbind(c(2,3),c(4,1),c(4,1))
lwid = c(2.5,4)
lhei = c(0.5,4,3)
labRowvec <- c(rep(NULL, dim(matrix(runif(1000, 1,10),ncol=50))[1]))
labColvec <- c(rep(NULL, dim(matrix(runif(1000, 1,10),ncol=50))[2]))
gl <- lapply(1:12, function(i){
heatmap.2(matrix(runif(1000, 1,10),ncol=50), dendrogram = "none",offsetRow=-0.5, offsetCol=-1,srtCol=0, density="density",
lmat =lmat,lhei = lhei,lwid = lwid, xlab="", ylab = "",labRow = labRowvec,labCol = labColvec,
trace = "none", cexRow = 0.2, cexCol = 0.2, margins = c(0.5,2), scale = "row",Colv = FALSE,Rowv = FALSE,
col = Col,key.title = paste0(i,")"),key.xlab = "year",key.ylab = NA,
keysize = 1, key.par=list(mar=c(1,1, 1, 1)))
grab_grob()
})
grid.newpage()
library(gridExtra)
# margin = theme(plot.margin = unit(c(0.1,0.1,0.1,0.1), "cm"))
# grid.arrange(grobs = lapply(gl, "+", margin),ncol=2,clip=TRUE)
g = arrangeGrob(grobs = gl, ncol=2,clip=TRUE)
ggsave(file="whatever.pdf", g)
Can someone help me maintain the axis labels and add a subplot title?
thanks.
Here's what the code generates:
I am trying to create an image showing a scatter plot and a heat map side by side. I create the scatter plot with geom_point and the heatmap with heatmap.2. I then use grid.draw to put them in the same image HOWEVER I cannot get the images to be the same size. How can I make sure they are the same height (this is important as they are ordered the same way and match each other)?
The code I have is:
grab_grob <- function(){
grid.echo()
grid.grab()
}
g1 <- ggplot(x, aes(x=VIPscore, y=reorder(metabolite, VIPscore))) + geom_point(colour="blue") + labs(y="", x="VIP score")
heatmap.2(xhm, cexRow=0.5, cexCol=1, Colv=FALSE, Rowv = FALSE, keep.dendro = FALSE, trace="none", key=FALSE, lwid = c(0.5, 0.5), col=heat.colors(ncol(xhm)))
g2 <- grab_grob()
grid.newpage()
lay <- grid.layout(nrow = 1, ncol=2)
pushViewport(viewport(layout = lay))
print(g1,vp=viewport(layout.pos.row = 1, layout.pos.col = 1))
grid.draw(editGrob(g2, vp=viewport(layout.pos.row = 1, layout.pos.col = 2, clip=TRUE)))
upViewport(1)
I have also tried the geom_tile (instead of heatmap.2) followed by grid.arrange; although the images now match in size colors are awful - they look flat across my data set.
A package called plotly might be of help here. Check out their API docs
library(plotly)
df <- data.frame(x = 1:1000,
y = rnorm(1000))
p1 <- plot_ly(df, x = x, y = y, mode = "markers")
p2 <- plot_ly(z = volcano, type = "heatmap")%>% layout(title = "Scatterplot and Heatmap Subplot")
subplot(p1, p2)
A drop-in solution could be to use the package "ComplexHeatmap".
https://bioconductor.org/packages/release/bioc/vignettes/ComplexHeatmap/inst/doc/s4.heatmap_annotation.html
Using the sample data below, how can I generate rasters and spatial points plot with the same colorkey as in the "manually" joined plot shown below?
library(rasterVis)
library(raster)
library(colorRamps)
col=colorRampPalette(matlab.like2(255))
s <- stack(replicate(2, raster(matrix(runif(100), 10))))
xy <- data.frame(coordinates(sampleRandom(s, 10, sp=TRUE)),
z1=runif(10), z2=runif(10))
levelplot(s, margin=FALSE, at=seq(0, 1, 0.05),col.regions=col)
x=xy$x;y=xy$y;z=xy$z1
levelplot(z ~ x + y,contour=F, panel = panel.levelplot.points,
margin=FALSE,col.regions=col,
par.settings=list(axis.line=list(lwd=3), strip.border=list(lwd=3)),
cex=1.4, scales=list(x=list(cex=1.7),y=list(cex=1.7)),xlab=list(label="Longitude",cex=2),
ylab=list(label="Latitude",cex=2))
Thanks to #fdestch I was able to generate the following plot using:
latticeCombineGrid(mget(rep("pp", 24)), layout = c(3, 8))
following my comments on printing multiple plots with the same colorkey.
An issue that remains to be clarified:
1) How can one decide on the order of panels? That is, which row & column to place a particular plot just as in levelplot using index.cond.
First of all, you should probably make sure that the breaks in the points plot are identical with those defined in the first levelplot.
## raster plot with colorkey disabled
pr <- levelplot(s, margin = FALSE, at = seq(0, 1, 0.05), col.regions = col,
colorkey = FALSE, xlab = list("Longitude", col = "transparent"))
## points plot
pp <- levelplot(z ~ x + y, panel = panel.levelplot.points, cex = 1.4,
contour = FALSE, margin = FALSE, col.regions = col,
colorkey = list(at = seq(0, 1, .05), width = .6, height = .6),
xlab = "Longitude", ylab = "Latitude")
Please note the definition of a transparent xlab when creating the raster plot. This little workaround comes in quite handy when using downViewport later on to ensure that the actual plot boundaries of pr and pp overlap (feel free to run grid.rect() right after print(pr, newpage = FALSE) to see what I mean).
The actual plot arrangement can then easily be achieved by using viewports from the grid package.
library(grid)
library(lattice)
## initialize new grid device
grid.newpage()
## add raster plot
vp1 <- viewport(x = 0, y = 0, width = .5, height = 1,
just = c("left", "bottom"))
pushViewport(vp1)
print(pr, newpage = FALSE)
## add points plot
downViewport(trellis.vpname("page"))
vp2 <- viewport(x = 1, y = 0, width = .75, height = 1,
just = c("left", "bottom"))
pushViewport(vp2)
print(pp, newpage = FALSE)
Here is my solution using latticeExtra::c.trellis:
library(raster)
library(rasterVis)
s <- stack(replicate(2, raster(matrix(runif(100), 10))))
xy <- data.frame(coordinates(sampleRandom(s, 10, sp=TRUE)),
z1=runif(10), z2=runif(10))
## Define theme and breaks
myTheme <- BTCTheme()
my.at <- seq(0, 1, 0.05)
Plot the Raster* object, using rasterVis::levelplot:
p1 <- levelplot(s, margin=FALSE,
at = my.at,
par.settings = myTheme)
Plot the points, using lattice::levelplot:
p2 <- levelplot(z1 ~ x + y, data = xy,
at = my.at,
panel = panel.levelplot.points,
par.settings = myTheme)
Join them with latticeExtra::c.trellis:
p3 <- c(p1, p2, layout = c(3, 1))
Unfortunately, c.trellis does not assign the strip labels correctly, so you have to define them directly:
update(p3,
strip = strip.custom(factor.levels = c(names(s), "Points")))
I'm trying to align a legend at the bottom of a chart, centered respectively to that chart. But I'm having trouble aligning it.
The pictures below show the current rendering, where you can clearly see the legend is misaligned (red line for guidance).
library(grid)
draw <- function() {
masterLayout <- grid.layout(
nrow = 4,
ncol = 1,
heights = unit(c(0.1, 0.7, 0.1, 0.1), rep("null", 4)))
vp1 <- viewport(layout.pos.row=1, layout.pos.col = 1, name="title")
vp2 <- viewport(layout.pos.row=2, layout.pos.col = 1, name="plot")
vp3 <- viewport(layout.pos.row=3, layout.pos.col = 1, name="legend")
vp4 <- viewport(layout.pos.row=4, layout.pos.col = 1, name="caption")
pushViewport(
vpTree(viewport(layout = masterLayout, name = "master"),
vpList(vp1, vp2, vp3, vp4)))
## Draw main plot
seekViewport("plot")
pushViewport(viewport(width=unit(.8, "npc")))
grid.rect(gp=gpar("fill"="red")) # dummy chart
popViewport(2)
## Draw legend
seekViewport("legend")
colors <- list(first="red", second="green", third="blue")
data.names <- names(colors)
legend.cols <- length(data.names)
pushViewport(viewport(
width = unit(0.8, "npc"),
layout = grid.layout(ncol=legend.cols * 2,
nrow=1,
widths=unit(2.5, "cm"),
heights=unit(0.25, "npc"))))
idx <- 0
for(name in data.names) {
idx <- idx + 1
pushViewport(viewport(layout.pos.row=1, layout.pos.col=idx))
grid.circle(x=0, r=0.35, gp=gpar(fill=colors[[name]], col=NA))
popViewport()
idx <- idx + 1
pushViewport(viewport(layout.pos.row=1, layout.pos.col=idx))
grid.text(x=unit(-0.8, "npc"), "text", just="left")
popViewport()
}
popViewport(2)
}
draw()
I don't understand why you're doing so much with individual viewports. It makes it very complex. I would have thought it was much easier to have one viewport for the legend and then control the x coordinate of the text and circles relative to that. Something like this; I'm not sure it's exactly what you want but it feels it should be easy to control if you need to tweak it:
library(grid)
draw <- function() {
masterLayout <- grid.layout(
nrow = 4,
ncol = 1,
heights = unit(c(0.1, 0.7, 0.1, 0.1), rep("null", 4)))
vp1 <- viewport(layout.pos.row=1, layout.pos.col = 1, name="title")
vp2 <- viewport(layout.pos.row=2, layout.pos.col = 1, name="plot")
vp3 <- viewport(layout.pos.row=3, layout.pos.col = 1, name="legend")
vp4 <- viewport(layout.pos.row=4, layout.pos.col = 1, name="caption")
pushViewport(
vpTree(viewport(layout = masterLayout, name = "master"),
vpList(vp1, vp2, vp3, vp4)))
## Draw main plot
seekViewport("plot")
pushViewport(viewport(width=unit(.8, "npc")))
grid.rect(gp=gpar("fill"="red")) # dummy chart
popViewport(2)
## Draw legend
seekViewport("legend")
colors <- list(first="red", second="green", third="blue")
lab_centers <- seq(from = 0.2, to = 0.8, length = length(colors))
disp <- 0.03 # how far to left of centre circle is, and to right text is, in each label
for(i in 1:length(colors)){
grid.circle(x = lab_centers[i] - disp, r = 0.1, gp=gpar(fill = colors[[i]], col=NA))
grid.text("text", x = lab_centers[i] + disp)
}
popViewport(2)
}
draw()
grid.lines(c(0.5, 0.5), c(0, 1))
If your legend labels aren't all the same length, you probably need to left align them and tweak the way I've used a disp parameter but shouldn't be too hard.
I want to have two (or more) Venn diagrams in a ".eps" file. I used this post by fridaymeetssunday, which refers to this post by mnel, and I just changed the code from "pdf" to "eps" by postscript.
The problem is, when I run the code and open the file the diagrams are colorless and do not have any colors. Should I have done something else?
# libraries
library(VennDiagram)
library(grid)
library(gridBase)
library(lattice)
# create the diagrams
temp1 <- venn.diagram(list(B = 1:1800, A = 1571:2020),
fill = c("red", "green"), alpha = c(0.5, 0.5), cex = 1,cat.fontface = 2,
lty =2, filename = NULL)
temp2 <- venn.diagram(list(A = 1:1800, B = 1571:2020),
fill = c("red", "green"), alpha = c(0.5, 0.5), cex = 1,cat.fontface = 2,
lty =2, filename = NULL)
# start new page
plot.new()
#pdf("testpdf", width = 14, height = 7)
postscript("test.eps", height=8, width=8, paper="special", family="Helvetica", fonts=c("serif","Helvetica"), horizontal=FALSE)
# setup layout
gl <- grid.layout(nrow=1, ncol=2)
# grid.show.layout(gl)
# setup viewports
vp.1 <- viewport(layout.pos.col=1, layout.pos.row=1)
vp.2 <- viewport(layout.pos.col=2, layout.pos.row=1)
# init layout
pushViewport(viewport(layout=gl))
# access the first position
pushViewport(vp.1)
# start new base graphics in first viewport
par(new=TRUE, fig=gridFIG())
grid.draw(temp2)
# done with the first viewport
popViewport()
# move to the next viewport
pushViewport(vp.2)
grid.draw(temp2)
# done with this viewport
popViewport(1)
dev.off()