Good Day All,
I want to add text floating in my wireframe plot and I am rather confused. I can certainly add the text as a title (e.g. main="Hello World") but I would rather not have my particular text in the title
Here is a sample wireframe:
library(lattice)
#set up some simplified data
x <- seq(-.8, .8, .1)
y <- seq(-.8, .8, .1)
myGrid <- data.frame(expand.grid(x,y))
colnames(myGrid) <- c("x","y")
myGrid$z <- myGrid$x + myGrid$y
wireframe(
myGrid$z ~ myGrid$x * myGrid$y,
xlab="X", ylab="Y", zlab="Z",
scales = list(z.ticks=5, arrows=FALSE, col="black", font=3, tck=1)
)
If I wanted to add "Hello World" in this plot floating somewhere how would I do that ?
Override the panel function and add text with grid.text.
wireframe(
myGrid$z ~ myGrid$x * myGrid$y,
xlab="X", ylab="Y", zlab="Z",
scales = list(z.ticks=5, arrows=FALSE, col="black", font=3, tck=1),
panel = function(...)
{
panel.wireframe(...)
grid.text("some text", 0, 0, default.units = "native")
}
)
Alternatively you could add the text after you plotted your wireframe with
grid::grid.text("some text", x=unit(0.7, "npc"), y=unit(0.8, "npc"))
The unit function allows you to specify the location of the text. If you use "npc" as your unit, the total width and height of your graph is 1. So the example above would display your text in the top right corner while x=y=unit(0.5, "npc") would plot it in the center.
Related
I'm building a custom ggplot theme to standardize the look & feel of graphs I produce. The goal is more complex than this minimal example, so I'm looking for a general solution. I have a few key goals:
I want all graphs to export at the same size (3000 pixels wide, 1500 pixels high).
I want to control the aspect ratio of the plot panel itself.
I want to use textGrobs to include figure numbers.
I want the image to be left-aligned
The challenge I'm facing is that when combining these two constraints, the image that gets saved centers the ggplot graph within the window, which makes sense as a default, but looks bad in this case.
I'm hoping there's a general solution to left-align the ggplot panel when I export. Ideally, this will also work similarly for faceted graphs.
It seems that something should be possible using one of or some combination of the gridExtra, gtable, cowplot, and egg packages, but after experimenting for a few hours I'm at a bit of a loss. Does anybody know how I can accomplish this? My code is included below.
This is the image that gets produced. As you can see, the caption is left-aligned at the bottom, but the ggplot itself is horizontally centered. I want the ggplot graph left-aligned as well.
Graph output: https://i.stack.imgur.com/5EM2c.png
library(ggplot2)
# Generate dummy data
x <- paste0("var", seq(1,10))
y <- LETTERS[1:10]
data <- expand.grid(X=x, Y=y)
data$Z <- runif(100, -2, 2)
# Generate heatmap with fixed aspect ratio
p1 <- ggplot(data, aes(X, Y, fill= Z)) +
geom_tile() +
labs(title = 'A Heatmap Graph') +
theme(aspect.ratio = 1)
# A text grob for the footer
figure_number_grob <- grid::textGrob('Figure 10',
x = 0.004,
hjust = 0,
gp = grid::gpar(fontsize = 10,
col = '#01A184'))
plot_grid <- ggpubr::ggarrange(p1,
figure_number_grob,
ncol = 1,
nrow = 2,
heights = c(1,
0.05))
# save it
png(filename = '~/test.png', width = 3000, height = 1500, res = 300, type = 'cairo')
print(plot_grid)
dev.off()
I was able to find a solution to this that works for my needs, though it does feel a bit hacky.
Here's the core idea:
Generate the plot without a fixed aspect ratio.
Split the legend from the plot as its own component
Use GridExtra's arrangeGrob to combine the plot, a spacer, the legend, and another spacer horizontally
Set the width of the plot to some fraction of npc (normal parent coordinates), in this case 0.5. This means that the plot will take up 50% of the horizontal space of the output file.
Note that this is not exactly the same as setting a fixed aspect ratio for the plot. If you know the size of the output file, it's close to the same thing, but the size of axis text & axis titles will affect the output aspect ratio for the panel itself, so while it gets you close, it's not ideal if you need a truly fixed aspect ratio
Set the width of the spacers to the remaining portion of the npc (in this case, 0.5 again), minus the width of the legend to horizontally center the legend in the remaining space.
Here's my code:
library(ggplot2)
# Generate dummy data
x <- paste0("var", seq(1,10))
y <- LETTERS[1:10]
data <- expand.grid(X=x, Y=y)
data$Z <- runif(100, -2, 2)
# Generate heatmap WITHOUT fixed aspect ratio. I address this below
p1 <- ggplot(data, aes(X, Y, fill= Z)) +
geom_tile() +
labs(title = 'A Heatmap Graph')
# Extract the legend from our plot
legend = gtable::gtable_filter(ggplotGrob(p1), "guide-box")
plot_output <- gridExtra::arrangeGrob(
p1 + theme(legend.position="none"), # Remove legend from base plot
grid::rectGrob(gp=grid::gpar(col=NA)), # Add a spacer
legend, # Add the legend back
grid::rectGrob(gp=grid::gpar(col=NA)), # Add a spacer
nrow=1, # Format plots in 1 row
widths=grid::unit.c(unit(0.5, "npc"), # Plot takes up half of width
(unit(0.5, "npc") - legend$width) * 0.5, # Spacer width
legend$width, # Legend width
(unit(0.5, "npc") - legend$width) * 0.5)) # Spacer width
# A text grob for the footer
figure_number_grob <- grid::textGrob('Figure 10',
x = 0.004,
hjust = 0,
gp = grid::gpar(fontsize = 10,
col = '#01A184'))
plot_grid <- ggpubr::ggarrange(plot_output,
figure_number_grob,
ncol = 1,
nrow = 2,
heights = c(1,
0.05))
# save it
png(filename = '~/test.png', width = 3000, height = 1500, res = 300, type = 'cairo')
print(plot_grid)
dev.off()
And here's the output image: https://i.stack.imgur.com/rgzFy.png
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:
http://cran.r-project.org/doc/Rnews/Rnews_2002-2.pdf
I would like to put a title on a page of plots created using R lattice. For example I can put four plots on a page as follows:
#load lattice
require(lattice).
# data
a<-c(1,3,4)
b<-c(1,2,3)
# make plots
plt1<-xyplot(a~b,main="plt1")
plt2<-xyplot(a~b,main="plt2")
plt3<-xyplot(a~b,main="plt3")
plt4<-xyplot(a~b,main="plt4")
# plot plots
plot(plt1, split=c(1,1,2,2),newpage=FALSE)
plot(plt2, split=c(1,2,2,2),newpage=FALSE)
plot(plt3, split=c(2,1,2,2),newpage=FALSE)
plot(plt4, split=c(2,2,2,2),newpage=FALSE)
Now how do a put the title "My Page of Plots" centered in the top margin above plt1 and plt3?
You could use grid to push a viewport and add the title:
library(grid)
vp2 <- viewport(x = 0.5, y = 1, width = 1, height = .1, just = c("center", "top"))
pushViewport(vp2)
grid.rect(gp = gpar(vol = "blue")) # just to see dimensions/position of the viewport
grid.text("My Title", gp = gpar(cex = 2))
You have to play with the position and dimensions of the viewport a bit. And ideally you would also add a top margin to your lattice call, such that you create some white space for your title.
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:
http://cran.r-project.org/doc/Rnews/Rnews_2002-2.pdf
I tried simply plotting some data in R with the y-axis label horizontal and left of the y-axis tick labels. I thought the code below would work:
set.seed(1)
n.obs <- 390
vol.min <- .20/sqrt(252 * 390)
eps <- rnorm(n = n.obs, sd = vol.min)
mar.default <- c(5,4,4,2) + 0.1
par(mar = mar.default + c(0, 4, 0, 0)) # add space to LHS of plot
pdf("~/myplot.pdf", width=5.05, height=3.8)
plot(eps, main = "Hello World!", las=1, ylab="") # suppress the y-axis label
mtext(text="eps", side=2, line=4, las=1) # add horiz y-axis label
# 4 lines into the margin
Instead, as you may see, the y-axis label almost fell completely outside of the graphics window. This phenomenon still exists no matter how much I expand the LHS margin.
Q: What am I doing wrong? Is there something I need to do with the oma parameter? What do I need to do to plot things the way I'm intending? I'm a little overwhelmed by all of this!
This is a classic one, maybe should be a FAQ. You have to set the par settings after the call to pdf, which creates the plotting device. Otherwise you're modifying the settings on the default device:
set.seed(1)
n.obs <- 390
vol.min <- .20/sqrt(252 * 390)
eps <- rnorm(n = n.obs, sd = vol.min)
# add space to LHS of plot
pdf("~/myplot.pdf", width=5.05, height=3.8)
mar.default <- c(5,4,4,2) + 0.1
par(mar = mar.default + c(0, 4, 0, 0))
plot(eps, main = "Hello World!", las=1, ylab="") # suppress the y-axis label
mtext(text="eps", side=2, line=4, las=1)
dev.off()