ggplot Multiline Title with Different Font Sizes - r

<SlightlyLookingAway>I am attempting to reproduce an excel plot in R.</SlightlyLookingAway> The Excel plot has a two line title. I know how to handle this by putting a '\n' in the title text. What I do not know how to handle is that the first line has a larger font size than the second row of the title... I have done some google searching and have come up with a general lack of response.
I realize that I might be able to cobble something together with an annotation of some kind but that seemed like a kludge. If that is the only answer then it is, but I wanted to ask the community first.
Any ideas?

It looks as though I have found a hacked solution which gets the job done but does not offer a lot of flexability. The idea is to put in a math expression using the atop() command along with the bold() and scriptstyle() functions.
myplot +
ggtitle(expression(atop(bold("This is the Top Line"), scriptstyle("This is the second line")))) +
theme(plot.title = element_text(size = 20))
If you know of a better solution with more control over the line spacing and even being able to adjust the font face, please let me know...

try this,
library(gridExtra)
titleGrob <- function(x=c("First line", "second line"), size=10, ...){
n <- length(x)
size <- rep(size, length.out=n)
one_label <- function(x, size, ...)
textGrob(x, gp=gpar(fontsize=size), ...)
lg <- mapply(one_label, x=x, size=size, ..., SIMPLIFY=FALSE)
wg <- lapply(lg, grobWidth)
hg <- lapply(lg, grobHeight)
widths <- do.call(unit.c, wg)
heights <- do.call(unit.c, hg)
maxwidth <- max(widths)
g <- frameGrob(layout = grid.layout(n, 1, width=maxwidth, height=heights) )
for(ii in seq_along(lg))
g <- placeGrob(g, lg[[ii]], row=ii)
g
}
grid.newpage()
g <- titleGrob(size=c(18,12))
grid.arrange(qplot(1,1), top=g)

To perfectly center everything (which \n will not do), adapt every size of text whatever the number of lines and at the same time being able to adjust the interlinear space, use this instead:
e.g. for smaller to larger text size
ggtitle(expression(atop(scriptscriptstyle("whateverline1"),atop(scriptstyle("whateverline2"),atop(scriptscriptstyle(""),textstyle("whateverline3"))))))
Then use labeller=label_parsed
This also works for facet_grid, xlab and ylab
Note the scriptscriptstyle("") to control spacing between lines. You can also use varied relative sizes of text using scriptstyle or scriptscriptstyle or textstyle depending on your needs and of course use element_text(size=whatevernumber) in the theme section

Related

R rasterVis levelplot: a white line erroneously appears

I am plotting maps of atmospheric pollutant fields, or meteorological field, difference between such fields, often overlayed with orography.
My fields are gridded.
A white line misteriously appears, sometimes two.
This seems to happen a bit randomly. I mean: same code and fields, same line; but when I change fields, or color scales, it changes position, or it disappears, or another one appears. Sometimes horizontal, sometimes vertical.
Here is my code
#!/usr/bin/env Rscript
library(rasterVis)
library(RColorBrewer)
NX <- 468
NY <- 421
hgt <- matrix(0.,NX,NY)
# read from file:
ucon <- file("hgt.dat", open="rb")
for (n in seq(1,NX)) {
hgt[n,] <- readBin(ucon, "numeric", n=NY, size=4)
}
close(ucon)
hgtbks <- c(-100,10,500,1000,1500,2000,2500,3000,3500)
hgtcols <- colorRampPalette(c("gray30","white"))(length(hgtbks)-1)
tit <- "Orography"
bkstart=50.0; bkmax=1500.; bkby=100.
bks <- seq(bkstart, bkmax, bkby)
nbks <- length(bks)
cols <- rev(colorRampPalette(brewer.pal(11,"Spectral"))(nbks-2))
cols <- c("white",cols)
legendbreaks <- seq(1,nbks)
legendlabels <- formatC(bks,digits=3)
legendlabpos <- legendbreaks
rpl <-
levelplot(hgt, margin=FALSE , col.regions= hgtcols, at= hgtbks
, main= list(label=tit, cex=1.8)
, colorkey=list(draw= TRUE, col=cols, at=legendbreaks
, labels=list(labels=legendlabels, at=legendlabpos, cex=1.2))
, xlab=NULL, ylab=NULL, scales= list(draw= FALSE))
png("whiteline.png", width=800, height=840)
plot(rpl)
graphics.off()
I would really like to upload a file with my data, but for the moment
I could not find a way to do it (I don't think I can do it, not even an ASCII file). The data matrix (468x421) is too big to be explicitly included in the code, but it really is the orography file
shown in the picture (elevation in meters above mean sea level).
And here is the resulting "white line" map:
Really, I think this might be a levelplot bug. It seems to happen both when hgt is a matrix and when it is a proper raster object: this doesn't seem to make a difference.
Any idea?
I think I found a workaround.
By setting zero padding on the 4 sides, I managed to make the whiteline disappear from a series of maps.
First I defined:
zpadding <- list(layout.heights= list(top.padding=0, bottom.padding=0),
layout.widths= list(left.padding=0, right.padding=0))
then I added, among the parameters of the levelplot call:
par.settings=zpadding
As I said, I don't think this is a proper solution, but a workaround.
The problem seems related to any rescaling of the plot area.
In fact, when a rescaling is forced by, for example, having 4 or 5 digits (instead of 2 or 3) in the colorbar labels, a white line may reappear.
I hope this may point in the right direction other people, either users or developers of levelplot and related software.

Printing out a dataframe in R: grid.table outputs cropped tables, doesn't respond to fontsize

I am trying to automate a series of analyses which are intended to save a number of plots for later inspection. One of the plots will be accompanied by a table of values. I'd like to have them in the same pdf so that the users don't have to jump between files.
I have checked numerous questions on SO regarding outputting data frames to pdf, here are a couple of reasons why existing answers aren't satisfactory in my case:
Not familiar with knitr/Sweave
Batch generation of figures mean that I cannot do it manually via RStudio Viewer
grid.table based solutions do not generate the entire table.
Which brings me to my problems, say I have a table 48 x 5 in proportions. If I try to plot it out with grid.table(geno) it results in a cropped table showing some 20-30 rows in the middle. If I go with grid.table(geno, gp = gpar(fontsize=8)) to decrease the fontsize I get the following error message.
Error in gtable_table(d, name = "core", fg_fun = theme$core$fg_fun, bg_fun = theme$core$bg_fun, :
unused argument (gp = list(fontsize = 8)
)
Essentially I would like to be able to use it in this way:
library(grid)
library(gridExtra)
pdf(file="gtype.pdf", title = "Genotype data")
plotGenotype(geno, text_size = 10) # outputs a custom plot
grid.newpage()
grid.table(geno) # grid.table(geno, gp = gpar(fontsize=8))
dev.off()
The problem here is that I either get a cropped table or nothing at all, on the second page. I noticed that many people add height=11, width=8.5 to the pdf() call. I am not sure if/why that would make a difference but setting paper="a4" or height/width according to A4 does not make any difference in my case.
Q1: Is it not possible to get grid.table to resize based on content and not paper?
Q2: Is there some other way to get a data frame printed to a pdf without having to go through LaTeX based solutions?
(I am currently running R 3.3.1 and gridExtra 2.2.1)
Q1: Is it not possible to get grid.table to resize based on content and not paper?
It is possible, but generally not desirable. A table is meant to be read, and if text and spacings were determined by the page rather than the content, it would often yield unreadable results. Thus the usual advice: manually tweak the font size and padding, or split the table.
It is by no means a technical limitation: feel free to set the cell size to fit the page:
grid.newpage()
pushViewport(viewport(width=unit(0.8,"npc"), height=unit(0.8,"npc")))
g <- g2 <- tableGrob(iris[1:4, 1:3], cols = NULL, rows=NULL)
g2$heights <- unit(rep(1/nrow(g2), nrow(g2)), "npc")
grid.arrange(rectGrob(), rectGrob(), nrow=1, newpage = FALSE)
grid.arrange(g, g2, nrow=1, newpage = FALSE)
but with too much content for the page it's unclear what result is better
grid.newpage()
pushViewport(viewport(width=unit(0.8,"npc"), height=unit(0.8,"npc")))
g <- g2 <- tableGrob(iris[1:20, 1:3], cols = NULL, rows=NULL)
g3 <- tableGrob(iris[1:20, 1:3], cols = NULL, rows=NULL, theme=ttheme_default(base_size=7))
g2$heights <- g3$heights <- unit(rep(1/nrow(g2), nrow(g2)), "npc")
grid.arrange(rectGrob(), rectGrob(), rectGrob(), nrow=1, newpage = FALSE)
grid.arrange(g, g2, g3, nrow=1, newpage = FALSE)
If the page size can be changed, it is usually the best option. One can query the table size before drawing, convert it to inches, and pass it to the device.
g1 <- tableGrob(iris[1:4, 1:5])
g2 <- tableGrob(iris[1:20, 1:5])
maxheight <- convertHeight(sum(g2$heights), "in", TRUE)
pdf("fit.pdf", height=maxheight)
grid.draw(g1)
grid.newpage()
grid.draw(g2)
dev.off()
However, as far as I know all pages in a given pdf will have to have the same size (there might be ways around it, but tricky).

Subscript in axis label when a line break is included?

I've gone round in circles with this.
What I would like is the axis label to read "Difference in relative oxygen consumption" with "(V0sub(2peak))" below [the '2peak' in subscript]. I've tried substitute, bquote and a number of different combinations with expression and paste/paste0, whenever I have the link break in there it seems to add a large gap before the subscript "2peak".
qplot(1:10)+
labs(x="", y=expression(paste0("Difference in relative oxygen consumption\n",(V0[2][peak]))))
Using paste instead of paste0 will get around having the big gap, but then the second line is right aligned.
Thanks
paste is generally not needed and even confuses things. If you read the ?plotmath page you see that you cannot get linefeeds with \n. The atop plotmath function can be used:
qplot(1:10)+ labs(x="", y=expression(atop(
Difference~'in'~relative~oxygen~consumption,
"("*V0[`2peak`]*")")) )
Your intent w.r.t. the O\sub2 and "peak" was not clear. One glitch was that the word "in" is reserved which was why it needed to be quoted. The parser does not recognize tokens with initial digits which was why the '2peak' needed quoting.
I think #BondedDust's suggestion is best, but I also adapted a solution for a different question to possibly work as well. Here we do some pretty low-level grob hacking.
#helper function
library(grid)
doubleYTitle <- function(a,b) {
gTree(children=gList(
textGrob(a, gp=gpar(fontsize=13, fontface=2), y=.5, x=0,
vp=viewport(layout.pos.row=1, layout.pos.col=1), rot=90),
textGrob(b, gp=gpar(fontsize=12, fontface=1), y=.5, x=0,
vp=viewport(layout.pos.row=1, layout.pos.col=2), rot=90)
), vp=viewport(layout=grid.layout(nrow=1, ncol=2)), cl="doubleytitle")
}
widthDetails.doubleytitle <- function(x, recording=T) {
Reduce(`+`, lapply(x$children, grid:::widthDetails.text)) * 5
}
Now we put it to use
gg <- qplot(1:10)+labs(x="", y="Difference")
gb <- ggplot_build(gg)
gt <- ggplot_gtable(gb)
xx <- doubleYTitle("Difference in relative oxygen consumption",
expression((V0[2][peak])))
gt$grobs[[7]]<-xx
plot(gt)

R ggplot: geom_tile lines in pdf output

I'm constructing a plot that uses geom_tile and then outputting it to .pdf (using pdf("filename",...)). However, when I do, the .pdf result has tiny lines (striations, as one person put it) running through it. I've attached an image showing the problem.
Googling let to this thread, but the only real advice in there was to try passing size=0 to geom_tile, which I did with no effect. Any suggestions on how I can fix these? I'd like to use this as a figure in a paper, but it's not going to work like this.
Minimal code:
require(ggplot2)
require(scales)
require(reshape)
volcano3d <- melt(volcano)
names(volcano3d) <- c("x", "y", "z")
v <- ggplot(volcano3d, aes(x, y, z = z))
pdf("mew.pdf")
print(v + geom_tile(aes(fill=z)) + stat_contour(size=2) + scale_fill_gradient("z"))
This happens because the default colour of the tiles in geom_tile seems to be white.
To fix this, you need to map the colour to z in the same way as fill.
print(v +
geom_tile(aes(fill=z, colour=z), size=1) +
stat_contour(size=2) +
scale_fill_gradient("z")
)
Try to use geom_raster:
pdf("mew.pdf")
print(v + geom_raster(aes(fill=z)) + stat_contour(size=2) + scale_fill_gradient("z"))
dev.off()
good quality in my environment.
I cannot reproduce the problem on my computer (Windows 7), but I remember it was a problem discussed on the list for certain configurations. Brian Ripley (if I remember) recommended
CairoPDF("mew.pdf") # Package Cairo
to get around this
In the interests of skinning this cat, and going into waaay too much detail, this code decomposes the R image into a mesh of quads (as used by rgl), and then shows the difference between a raster plot and a "tile" or "rect" plot.
library(raster)
im <- raster::raster(volcano)
## this is the image in rgl corner-vertex form
msh <- quadmesh::quadmesh(im)
## manual labour for colour scaling
dif <- diff(range(values(im)))
mn <- min(values(im))
scl <- function(x) (x - mn)/dif
This the the traditional R 'image', which draws a little tile or 'rect()' for every pixel.
list_image <- list(x = xFromCol(im), y = rev(yFromRow(im)), z = t(as.matrix(im)[nrow(im):1, ]))
image(list_image)
It's slow, and though it calls the source of 'rect()' under the hood, we can't also set the border colour. Use 'useRaster = TRUE' to use 'rasterImage' for more efficient drawing time, control over interpolation, and ultimately - file size.
Now let's plot the image again, but by explicitly calling rect for every pixel. ('quadmesh' probably not the easiest way to demonstrate, it's just fresh in my mind).
## worker function to plot rect from vertex index
rectfun <- function(x, vb, ...) rect(vb[1, x[1]], vb[2,x[1]], vb[1,x[3]], vb[2,x[3]], ...)
## draw just the borders on the original, traditional image
apply(msh$ib, 2, rectfun, msh$vb, border = "white")
Now try again with 'rect'.
## redraw the entire image, with rect calls
##(not efficient, but essentially the same as what image does with useRaster = FALSE)
cols <- heat.colors(12)
## just to clear the plot, and maintain the plot space
image(im, col = "black")
for (i in seq(ncol(msh$ib))) {
rectfun(msh$ib[,i], msh$vb, col = cols[scl(im[i]) * (length(cols)-1) + 1], border = "dodgerblue")
}

add labels to lattice barchart

I would like to place the value for each bar in barchart (lattice) at the top of each bar. However, I cannot find any option with which I can achieve this. I can only find options for the axis.
Create a custom panel function, e.g.
library("lattice")
p <- barchart((1:10)^2~1:10, horiz=FALSE, ylim=c(0,120),
panel=function(...) {
args <- list(...)
panel.text(args$x, args$y, args$y, pos=3, offset=1)
panel.barchart(...)
})
print(p)
I would have suggested using the new directlabels package, which can be used with both lattice and ggplot (and makes life very easy for these labeling problems), but unfortunately it doesn't work with barcharts.
Since I had to do this anyway, here's a close-enough-to-figure it out code sample along the lines of what #Alex Brown suggests (scores is a 2D array of some sort, which'll get turned into a grouped vector):
barchart(scores, horizontal=FALSE, stack=FALSE,
xlab='Sample', ylab='Mean Score (max of 9)',
auto.key=list(rectangles=TRUE, points=FALSE),
panel=function(x, y, box.ratio, groups, errbars, ...) {
# We need to specify groups because it's not actually the 4th
# parameter
panel.barchart(x, y, box.ratio, groups=groups, ...)
x <- as.numeric(x)
nvals <- nlevels(groups)
groups <- as.numeric(groups)
box.width <- box.ratio / (1 + box.ratio)
for(i in unique(x)) {
ok <- x == i
width <- box.width / nvals
locs <- i + width * (groups[ok] - (nvals + 1)/2)
panel.arrows(locs, y[ok] + 0.5, scores.ses[,i], ...)
}
} )
I haven't tested this, but the important bits (the parts determining the locs etc. within the panel function) do work. That's the hard part to figure out. In my case, I was actually using panel.arrows to make errorbars (the horror!). But scores.ses is meant to be an array of the same dimension as scores.
I'll try to clean this up later - but if someone else wants to, I'm happy for it!
If you are using the groups parameter you will find the labels in #rcs's code all land on top of each other. This can be fixed by extending panel.text to work like panel.barchart, which is easy enough if you know R.
I can't post the code of the fix here for licencing reasons, sorry.

Resources