Add multi-line footnote to tableGrob, while using gridextra in R - r

I have recently started using tableGrob and gridextra to combine multiple plots and tables. i want mt tableGrob to have a footnote and title.
The following link answers that nicely:
Adding text to a grid.table plot
But in the above code the footnote gets truncated it its too long. Can someone please suggest an alternative so that the footnote automatically wraps to the next line once it had reached the end of the table? If it can wrap in the middle of the word that is fine as well.
test <- data.frame(boo = c(20,1), do = c(2,10), no = c(3,5),co = c('ed','jeff'))
t1 <- tableGrob(test)
tw <- convertWidth(unit(grobWidth(t1),'npc'),
"in", valueOnly = T)
title <- textGrob("Title is long too or is it??",gp=gpar(fontsize=15))
footnote <- textGrob("footnote is pretty longgg but not unusually longgggggggggkjwd jwkldn", x=0, hjust=0,
gp=gpar( fontface="italic"))
padding <- unit(0.5,"line")
t1 <- gtable_add_rows(t1,
heights = grobHeight(title) + padding,
pos = 0)
t1 <- gtable_add_rows(t1,
heights = grobHeight(footnote)+ padding)
t1 <- gtable_add_grob(t1, list(title, footnote),
t=c(1, nrow(t1)), l=c(1,1),
r=ncol(t1))
grid.arrange(t1)
I want this to work when I have a plot and a table in grid arrange as well. Please help.
I tried using strwrap and setting the width to grobWidth but it did not work for me.

the RGraphics book/package offers a possible solution,
splitString <- function (text, width) {
strings <- strsplit(text, " ")[[1]]
newstring <- strings[1]
linewidth <- stringWidth(newstring)
gapwidth <- stringWidth(" ")
availwidth <- convertWidth(width, "in", valueOnly = TRUE)
for (i in 2:length(strings)) {
width <- stringWidth(strings[i])
if (convertWidth(linewidth + gapwidth + width, "in",
valueOnly = TRUE) < availwidth) {
sep <- " "
linewidth <- linewidth + gapwidth + width
}
else {
sep <- "\n"
linewidth <- width
}
newstring <- paste(newstring, strings[i], sep = sep)
}
newstring
}
tit <- "Title is long too or is it??"
foot <- "footnote is pretty longgg but not unusually longgggggggggkjwd jwkldn"
footnote <- textGrob(splitString(foot, sum(t1$widths)))
title <- textGrob(splitString(tit, sum(t1$widths)))
t1 <- gtable_add_rows(t1, heights = grobHeight(footnote))
t1 <- gtable_add_rows(t1, heights = grobHeight(title), 0)
t1 <- gtable_add_grob(t1, list(title, footnote),
t=c(1, nrow(t1)), l=1, r=ncol(t1))
grid.draw(t1)

Related

Save/export plots from list of plots as single .png files

I'm a bit stuck on this issue. I have this data obtained from a likert survey (so I make everything a factor):
df1<-data.frame(A=c(1,2,2,3,4,5,1,1,2,3),
B=c(4,4,2,3,4,2,1,5,2,2),
C=c(3,3,3,3,4,2,5,1,2,3),
D=c(1,2,5,5,5,4,5,5,2,3),
E=c(1,4,2,3,4,2,5,1,2,3),
dummy1=c("yes","yes","no","no","no","no","yes","no","yes","yes"),
dummy2=c("high","low","low","low","high","high","high","low","low","high"))
df1[colnames(df1)] <- lapply(df1[colnames(df1)], factor)
I then create a list of dataframes to be used in each plot:
vals <- colnames(df1)[1:5]
dummies <- colnames(df1)[-(1:5)]
step1 <- lapply(dummies, function(x) df1[, c(vals, x)])
step2 <- lapply(step1, function(x) split(x, x[, 6]))
names(step2) <- dummies
tbls <- unlist(step2, recursive=FALSE)
tbls<-lapply(tbls, function(x) x[(names(x) %in% names(df1[c(1:5)]))])
This is the plotting function I made (I used the likert package)
plot_likert <- function(x){
y<-deparse(substitute(x))
y<-sub("\\$", " - ",y)
p<-plot(likert(x),
type ="bar",center=3,
group.order=names(x))+
labs(x = "Theme", subtitle=paste("Number of observations:",nrow(x)))+
guides(fill=guide_legend("Rank"))+
ggtitle(paste("How do they rank? -",gsub("\\.",": ",y)))
png(filename=paste("Ranking -",y,".png"), width = 3000, height = 2000, res=300)
print(p)
dev.off()
}
So that now I can make the plot by writing:
plot_likert(tbls$dummy1.no)
Finally, I apply the function over the whole table by using
lapply(tbls,function(x) {
y<-deparse(substitute(x))
y<-sub("\\$", " - ",y)
plot(likert(x),
type ="bar",center=3,
group.order=names(x))+
labs(x = "Theme", subtitle=paste("Number of observations:",nrow(x)))+
guides(fill=guide_legend("Rank"))+
ggtitle(paste("How do these themes rank? -",gsub("\\.",": ",y)))
}) -> list_plots
But now I don't know how to save each graph in the list as a separate .png file! I managed to put everything in a pdf like this, but it's not what I actually want:
ggsave(
filename = "plots.pdf",
plot = marrangeGrob(list_plots, nrow=1, ncol=1),
width = 15, height = 9
)
Do you have any suggestions on how to fix this? Also, if you have anything to add about my function/procedure overall, everything is welcome! I'm still quite new to R.
Thanks in advance
we can use:
sapply(1:length(list_plots), function(i) ggsave(
filename = paste0("plots ",i,".pdf"),
plot = list_plots[[i]],
width = 15, height = 9
))
For names: see https://stackoverflow.com/a/73370416/5224236
mynames <- sapply(names(tbls), function(x) {
paste("How do they rank? -",gsub("\\.",": ",x))
})
myfilenames <- names(tbls)
plot_likert <- function(x, myname, myfilename){
p <- plot(likert(x),
type ="bar",center=3,
group.order=names(x))+
labs(x = "Theme", subtitle=paste("Number of observations:",nrow(x)))+
guides(fill=guide_legend("Rank"))+
ggtitle(myname)
p
}
list_plots <- lapply(1:length(tbls),function(i) {
plot_likert(tbls[[i]], mynames[i], myfilenames[i])
})

Add title to PDF table in R using grid and gridExtra

I was hoping to expand on print to pdf file using grid.table in r - too many rows to fit on one page in order to add a title to the PDF
title <- "Table 1: Iris Data"
d <- iris[sample(nrow(iris), 187, TRUE),]
d$another <- "More Data"
d$column <- "Even More Will it Be off the Page"
The Provided Answer
library(gridExtra)
library(grid)
d <- iris[sample(nrow(iris), 187, TRUE),]
d$another <- "More Data"
d$column <- "Even More Will it Be off the Page"
tg <- tableGrob(d, rows = seq_len(nrow(d)))
fullheight <- convertHeight(sum(tg$heights), "cm", valueOnly = TRUE)
margin <- unit(0.51,"in")
margin_cm <- convertHeight(margin, "cm", valueOnly = TRUE)
a4height <- 29.7 - margin_cm
nrows <- nrow(tg)
npages <- ceiling(fullheight / a4height)
heights <- convertHeight(tg$heights, "cm", valueOnly = TRUE)
rows <- cut(cumsum(heights), include.lowest = FALSE,
breaks = c(0, cumsum(rep(a4height, npages))))
groups <- split(seq_len(nrows), rows)
gl <- lapply(groups, function(id) tg[id,])
pdf("multipage.pdf", paper = "a4", width = 0, height = 0)
for(page in seq_len(npages)){
grid.newpage()
grid.rect(width=unit(21,"cm") - margin,
height=unit(29.7,"cm")- margin)
grid.draw(gl[[page]])
}
## alternative to explicit loop:
## print(marrangeGrob(grobs=gl, ncol=1, nrow=1, top=NULL))
dev.off()
How can I change this code so that I can add title to the first page of the PDF?

Title getting cut off using grid.arrange

I built a function for quickly plotting a table with a lot of help from this answer from #baptiste.
plotTable<-function(data, title=NULL, footnote=NULL, fontsize=9, plotIt=TRUE, show.rownames=TRUE){
# Generic method to plot tabular data
# Built the base table with/without row names
if(show.rownames){
table <- tableGrob(data, theme=ttheme_default(
core=list(fg_params=list(fontsize=fontsize)),
colhead=list(fg_params=list(fontsize=fontsize)),
rowhead=list(fg_params=list(fontsize=fontsize))))
} else{
table <- tableGrob(data, theme=ttheme_default(
core=list(fg_params=list(fontsize=fontsize)),
colhead=list(fg_params=list(fontsize=fontsize)),
rowhead=list(fg_params=list(fontsize=fontsize))), rows=NULL)
}
# Set the padding
padding <- unit(0.5,"line")
# Add the title if it's not NULL
if(!is.null(title)){
title.grob <- textGrob(title, gp=gpar(fontsize=fontsize+3))
table <- gtable_add_rows(table, heights = grobHeight(title.grob) + padding, pos = 0)
table <- gtable_add_grob(table, list(title.grob), t=1, l=1, r=ncol(table))
}
# Add the footnote if it's not NULL
if(!is.null(footnote)){
footnote.grob <- textGrob(footnote, x=0, hjust=0, gp=gpar(fontsize=fontsize, fontface="italic"))
table <- gtable_add_rows(table, heights = grobHeight(footnote.grob)+ padding)
table <- gtable_add_grob(table, list(footnote.grob), t=nrow(table), l=1, r=ncol(table))
}
# Either plot it or return the grob
if(plotIt) grid.arrange(table) else return(table)
}
But sometimes my title is longer than the actual table and it's getting cut off.
libs <- c("data.table", "grid", "gridExtra", "gtable")
lapply(libs, library, character.only = TRUE)
mytable <- data.table(x=c(1,2,3), y=c(3,2,1))
plotTable(mytable, title="Hello World")
How do I fix this?
You can set the table widths manually, but you'll need to decide which column(s) should be expanded and by how much. I guess a reasonable way to redistribute the widths is to add the same margin to each column, so that the total space gained accounts for the extra room needed for the title.
library(gridExtra)
d <- head(iris[,1:2])
table <- tableGrob(d, rows=NULL)
library(grid)
library(gtable)
title <- textGrob("my long title goes here",gp=gpar(fontsize=30))
padding <- unit(1,"line")
table <- gtable_add_rows(table,
heights = grobHeight(title) + padding,
pos = 0)
table <- gtable_add_grob(table, title,
t=1, l=1,
r=ncol(table))
# check whether the table width is smaller than the title width
missed <- convertWidth(sum(table$widths), "in", valueOnly = TRUE) -
convertWidth(grobWidth(title), "in", valueOnly = TRUE)
if(missed < 0 ) # need to do something about it
table$widths <- table$widths + unit(abs(missed)/ncol(table), "in")
grid.newpage()
grid.draw(table)
another option, if you don't want the cells to be resized, is to turn clipping off,
table$layout$clip <- "off"

vertical alignment of gridExtra tableGrob (R grid graphics/grob)

Having some trouble aligning a grid graphics object -- have read all the docs I can find, including the Murrell book, but no success. I think what I'm trying to do is pretty straightforward, so hopefully I'm missing simple.
Here's a reproducible example that will make a PDF of all the air carriers by destination in Hadley's hflights package (mirrors what I am trying to do on a different data set).
require(hflights)
require(gridExtra)
require(Cairo)
make_table <- function(df) {
p <- tableGrob(
df
,padding.h=unit(.25, "mm")
,show.rownames=FALSE
,gpar.coretext = gpar(fontsize=8, lineheight=0)
#this doesn't seem to justify the table
,just = c("bottom")
,show.box = T
)
return(p)
}
dests <- unique(hflights$Dest)
#list to hold the plots
plot_list <- list()
#loop over destinations and make a simple report
for (i in dests) {
#just this destination
this_dest <- hflights[hflights$Dest == i, ]
#the title
title <- textGrob(label = i, gp = gpar(fontsize=72, fontface = 'bold'))
#a table of carriers
carriers <- unique(this_dest$UniqueCarrier)
carriers <- data.frame(
carrier=carriers
)
carrier_table <- make_table(carriers)
#put them together
p <- arrangeGrob(
title, carrier_table
,nrow=2
)
plot_list[[i]] <- p
}
#print the report
Cairo(
width = 11, height = 8.5
,file = paste('destinations.pdf', sep = ''), type="pdf"
,units = "in"
)
print(plot_list)
dev.off()
I want the entire table produced by tableGrob (in the make_table function) to justify to the top of the grob. Right now it is centered vertically and horizontally inside the grob. Do I need to do that in the call to tableGrob, or is it in the arrangeGrob call? To ask it another way, in case the above is not clear, how can I make the whole table (not the text inside of it) justify to the top/bottom/left/right of its container?
Thanks!
try this,
library(gridExtra)
justify <- function(x, hjust="center", vjust="center", draw=TRUE){
w <- sum(x$widths)
h <- sum(x$heights)
xj <- switch(hjust,
center = 0.5,
left = 0.5*w,
right=unit(1,"npc") - 0.5*w)
yj <- switch(vjust,
center = 0.5,
bottom = 0.5*h,
top=unit(1,"npc") - 0.5*h)
x$vp <- viewport(x=xj, y=yj)
if(draw) grid.draw(x)
return(x)
}
g <- tableGrob(iris[1:3,1:2])
grid.newpage()
justify(g,"right", "top")

Changing one word in character string to bold face using textplot()

I'm using textplot() from the gplots package to write definitions that are then displayed next to other plots using par(mfrow=c(3,2)).
I want to change a single word in the character string to bold face (Usually the word being defined). Is there a metacharacter that will let me do this inside of the " "? Or another solution for picking out words and giving them bold attributes without assigning that to the whole string?
It's similar to this question, but I wasn't able to use the same technique in textplot():
text() R-function - how to change the font of a single word?
text(0.5,0.5, expression(paste(bold("bold")," not bold")))
Here's my code without a bolded term. Pretend "Definition" is desired to be bold face:
blurb<-strwrap("Definition: This is my text blurb",
width=60)
textplot(blurb, halign="left", valign="top", cex = 1, family="serif")
I've been playing with breaking the string apart and searching for a function that will assign bold face to the "Definition" portion, font=2, and then pasting the string back together, but I'm stumped. I can't find a function to use:
blurb1<-"Definition" ##How to change to bold face??
blurb2<-"This is my text blurb"
blurb<-paste0(blurb1,blurb2)
EDIT: The predominant barrier to using other solutions is that for my page layout, text() isn't entirely viable. I'm hoping to find a solution to editing the string either inside of textplot() or in a way that can be passed to textplot().
I'm creating something of a "Report Card" that will plot user data and provide a paragraph of explanation beside the plot. Different values would trigger a different textplot(). I like textplot() because it's easily placed with par(mfrow=c(4,2)), carving out a seperate space without overlapping other plots. I just can't seem to work text() in without a lot of play in the positioning.
You need to use bquote().
Here is a simple function which takes a text string and splits it and returns the appropriate expression for your bold plotting needs. I am sure you can adapt this as you see fit.
# Pass the function a string and a character to split on
# The splitting is greedy (i.e. it will split on all matches so make sure you are splitting on a unqiue character such as ":" in your example)
tsplit <- function( string , split ){
require( stringr )
blurb <- paste( string )
blurbs <- strsplit( blurb , paste(split) )
annot <- bquote( paste( bold( .( blurbs[[1]][1] ) ) , .(split) , .(blurbs[[1]][2]) , sep = "" ) )
return( annot )
}
#And the function in action...
j <- tsplit( "Define: This is my blurb" , ":" )
textplot( paste( " " ) ) #Get new plot
text(0.5 , 0.5 , j ) #paste the text
I hope this helps. The function assumes that there is only one unique character to split the string on and that you want the first word in bold and the rest of the string in normal format.
Cheers
EDIT
Sorry I realised in the question you said you couldn't use text for placement because it is problematic. A quick check of the available methods of textplot (showMethods(textplot)) and the source of the apporopriate method for plotting characters (getAnywhere(textplot.character)) shows that textplot does infact use a call to text to annotate the plot with your text object. Most of the code is concerned with taking out the heavy lifting of where you want the text. You can make a couple of simple adjustments to textplot.character() to create a custom function to do what you wanted. You can copy and paste this into R and it should work as per the example at the bottom.
tplot.cust <- function ( object , split , halign = c("center", "left", "right"), valign = c("center",
"top", "bottom"), cex, fixed.width = TRUE, cspace = 1, lspace = 1,
mar = c(0, 0, 3, 0) + 0.1, tab.width = 8, ...)
{
# extra code to split text according to 'split' argument and make text before the split bold.
require(stringr)
blurb <- paste( object )
blurbs <- strsplit( blurb , paste(split) )
annot <- bquote( paste( bold( .( blurbs[[1]][1] ) ) , .(split) , .(blurbs[[1]][2]) , sep = "" ) )
object <- paste(object, collapse = "\n", sep = "")
object <- gplots:::replaceTabs(object, width = tab.width) #you need to add gplots::: to this line because replaceTabs is a function that is not exported from the gplots namespace
halign = match.arg(halign)
valign = match.arg(valign)
plot.new()
opar <- par()[c("mar", "xpd", "cex", "family")]
on.exit(par(opar))
par(mar = mar, xpd = FALSE)
if (fixed.width)
par(family = "mono")
plot.window(xlim = c(0, 1), ylim = c(0, 1), log = "", asp = NA)
slist <- unlist(lapply(object, function(x) strsplit(x, "\n")))
slist <- lapply(slist, function(x) unlist(strsplit(x, "")))
slen <- sapply(slist, length)
slines <- length(slist)
if (missing(cex)) {
lastloop <- FALSE
cex <- 1
}
else lastloop <- TRUE
for (i in 1:20) {
oldcex <- cex
cwidth <- max(sapply(unlist(slist), strwidth, cex = cex)) *
cspace
cheight <- max(sapply(unlist(slist), strheight, cex = cex)) *
(lspace + 0.5)
width <- strwidth(object, cex = cex)
height <- strheight(object, cex = cex)
if (lastloop)
break
cex <- cex/max(width, height)
if (abs(oldcex - cex) < 0.001) {
lastloop <- TRUE
}
}
if (halign == "left")
xpos <- 0
else if (halign == "center")
xpos <- 0 + (1 - width)/2
else xpos <- 0 + (1 - width)
if (valign == "top")
ypos <- 1
else if (valign == "center")
ypos <- 1 - (1 - height)/2
else ypos <- 1 - (1 - height)
text(x = xpos, y = ypos, labels = annot , adj = c(0, 1),
cex = cex, ...) #add the newly created annot expression here
par(opar)
invisible(cex)
}
We can then use tplot.cust like so...
blurb <- "Define: This is my blurb"
tplot.cust(blurb, ":" , halign="left", valign="top", cex = 1, family="serif")
Hopefully this is what you want??

Resources