How can I add a title to a tableGrob plot? - r

I have a table, and I want to print a title above it:
t1 <- tableGrob(top_10_events_by_casualties, cols=c("EVTYPE", "casualties"), rows=seq(1,10))
grid.draw(t1)
A similar question was asked here: Adding text to a grid.table plot
I've tried something similar and it doesn't work:
> title <- textGrob("Title",gp=gpar(fontsize=50))
> table <- gtable_add_rows(t1,
+ heights = grobHeight(title) + padding,
+ pos = 0)
Error: is.gtable(x) is not TRUE

Not sure what the problem was, but here is a working example:
library(grid)
library(gridExtra)
library(gtable)
t1 <- tableGrob(head(iris))
title <- textGrob("Title",gp=gpar(fontsize=50))
padding <- unit(5,"mm")
table <- gtable_add_rows(
t1,
heights = grobHeight(title) + padding,
pos = 0)
table <- gtable_add_grob(
table,
title,
1, 1, 1, ncol(table))
grid.newpage()
grid.draw(table)

Another option is:
library(gridExtra)
grid.arrange(top="Iris dataset", tableGrob(head(iris)))
You still might want to do some tweaking with the padding.

Related

Adding a subtitle text to a grid.table plot

I have a question very similar to what discussed here:
Adding text to a grid.table plot
my ultimate goal however is to have a title 60mm from the top of the table, and a subtitle 2mm below the title.
I came up with this code that is almost there but not there, meaning, the subtitle is 2mm from the top of the table, and 2mm below the title, as expected.
library(gridExtra)
library(grid)
library(gtable)
d <- head(iris)
table <- tableGrob(d)
title <- textGrob("Title",gp=gpar(fontsize=50))
subtitle <- textGrob("subtitle", x=0, hjust=0,
gp=gpar( fontface="italic"))
padding <- unit(2,"mm")
table <- gtable_add_rows(table,
heights = grobHeight(subtitle)+ padding,
pos = 0)
padding <- unit(60,"mm")
table <- gtable_add_rows(table,
heights = grobHeight(title) + padding,
pos = 0)
table <- gtable_add_grob(table, list(title, subtitle),
t=c(1, 2), l=c(1,1),
r=ncol(table))
png('tmp.png', width = 480, height = 480, bg = "#FFECDB")
grid.newpage()
grid.draw(table)
dev.off()
I wonder if anybody has a suggestion on how to fix it.
Thank you
I'm new to grid tables, but it appears the order of your grobs matters, at least for padding. Is this the result you are expecting?
library(gridExtra)
library(grid)
library(gtable)
d <- head(iris)
table <- tableGrob(d)
title <- textGrob("Title", gp = gpar(fontsize=50))
subtitle <- textGrob("subtitle", x=0, hjust=0, gp=gpar( fontface="italic"))
table <- gtable_add_rows(table, heights = grobHeight(subtitle) + unit(58,"mm"), pos = 0)
table <- gtable_add_rows(table, heights = grobHeight(title) - unit(60,"mm"), pos = 0)
table <- gtable_add_grob(table, list(title, subtitle), t=c(1,2), l=c(1,1), r=ncol(table))
png('tmp.png', width = 480, height = 480, bg = "#FFECDB")
grid.newpage()
grid.draw(table)
dev.off()

Extend axis limits without plotting (in order to align two plots by x-unit)

I am trying to combine two ggplot objects with patchwork - two plots with different subsets of data, but the same x variable (and therefore same unit). I would like to align the plots according to the x values - Each x unit should have the same physical width in the final plot.
This is very easy when actually plotting the entire width of the larger data set (see plot below) - but I struggle to plot only parts of the data and keeping the same alignment.
library(ggplot2)
library(patchwork)
library(dplyr)
p1 <-
ggplot(mtcars, aes(mpg)) +
geom_density(trim = TRUE) +
scale_x_continuous(limits = c(10,35))
p2 <-
ggplot(filter(mtcars, mpg < 20), aes(mpg)) +
geom_histogram(binwidth = 1, boundary = 1) +
scale_x_continuous(limits = c(10,35))
p1/p2
Created on 2019-08-07 by the reprex package (v0.3.0)
The desired output
That's photoshopped
adding coord_cartesian(xlim = c(10,(20 or 35)), clip = 'off'), and/or changing scale_x limits to c(0,(20 or 35)) doesn't work.
patchwork also won't let me set the widths of both plots when they are in two rows, which makes sense in a way. So I could create an empty plot for the second row and set the widths for those, but this seems a terrible hack and I feel there must be a much easier solution.
I am not restricted to patchwork, but any solution allowing to use it would be very welcome.
I modified the align_plots function from the cowplot package for this, so that its plot_grid function can now support adjustments to the dimensions of each plot.
(The main reason I went with cowplot rather than patchwork is that I haven't had much tinkering experience with the latter, and overloading common operators like + makes me slightly nervous.)
Demonstration of results
# x / y axis range of p1 / p2 have been changed for illustration purpose
p1 <- ggplot(mtcars, aes(mpg, 1 + stat(count))) +
geom_density(trim = TRUE) +
scale_x_continuous(limits = c(10,35)) +
coord_cartesian(ylim = c(1, 3.5))
p2 <- ggplot(filter(mtcars, mpg >= 15 & mpg < 30), aes(mpg)) +
geom_histogram(binwidth = 1, boundary = 1)
plot_grid(p1, p2, ncol = 1, align = "v") # plots in 1 column, x-axes aligned
plot_grid(p1, p2, nrow = 1, align = "h") # plots in 1 row, y-axes aligned
Plots in 1 column (x-axes aligned for 15-28 range):
Plots in 1 row (y-axes aligned for 1 - 3.5 range):
Caveats
This hack assumes the plots that the user intends to align (either horizontally or vertically) have reasonably similar axes of comparable magnitude. I haven't tested it on more extreme cases.
This hack expects simple non-faceted plots in Cartesian coordinates. I'm not sure what one could expect from aligning faceted plots. Similarly, I'm not considering polar coordinates (what's there to align?) or map projections (haven't looked into this, but they feel rather complicated).
This hack expects the gtable cell containing the plot panel to be in the 7th row / 5th column of the gtable object, which is based on my understanding of how ggplot objects are typically converted to gtables, and may not survive changes to the underlying code.
Code
Modified version of cowplot::align_plots:
align_plots_modified <- function (..., plotlist = NULL, align = c("none", "h", "v", "hv"),
axis = c("none", "l", "r", "t", "b", "lr", "tb", "tblr"),
greedy = TRUE) {
plots <- c(list(...), plotlist)
num_plots <- length(plots)
grobs <- lapply(plots, function(x) {
if (!is.null(x)) as_gtable(x)
else NULL
})
halign <- switch(align[1], h = TRUE, vh = TRUE, hv = TRUE, FALSE)
valign <- switch(align[1], v = TRUE, vh = TRUE, hv = TRUE, FALSE)
vcomplex_align <- hcomplex_align <- FALSE
if (valign) {
# modification: get x-axis value range associated with each plot, create union of
# value ranges across all plots, & calculate the proportional width of each plot
# (with white space on either side) required in order for the plots to align
plot.x.range <- lapply(plots, function(x) ggplot_build(x)$layout$panel_params[[1]]$x.range)
full.range <- range(plot.x.range)
plot.x.range <- lapply(plot.x.range,
function(x) c(diff(c(full.range[1], x[1]))/ diff(full.range),
diff(x)/ diff(full.range),
diff(c(x[2], full.range[2]))/ diff(full.range)))
num_widths <- unique(lapply(grobs, function(x) {
length(x$widths)
}))
num_widths[num_widths == 0] <- NULL
if (length(num_widths) > 1 || length(grep("l|r", axis[1])) > 0) {
vcomplex_align = TRUE
warning("Method not implemented for faceted plots. Placing unaligned.")
valign <- FALSE
}
else {
max_widths <- list(do.call(grid::unit.pmax,
lapply(grobs, function(x) {x$widths})))
}
}
if (halign) {
# modification: get y-axis value range associated with each plot, create union of
# value ranges across all plots, & calculate the proportional width of each plot
# (with white space on either side) required in order for the plots to align
plot.y.range <- lapply(plots, function(x) ggplot_build(x)$layout$panel_params[[1]]$y.range)
full.range <- range(plot.y.range)
plot.y.range <- lapply(plot.y.range,
function(x) c(diff(c(full.range[1], x[1]))/ diff(full.range),
diff(x)/ diff(full.range),
diff(c(x[2], full.range[2]))/ diff(full.range)))
num_heights <- unique(lapply(grobs, function(x) {
length(x$heights)
}))
num_heights[num_heights == 0] <- NULL
if (length(num_heights) > 1 || length(grep("t|b", axis[1])) > 0) {
hcomplex_align = TRUE
warning("Method not implemented for faceted plots. Placing unaligned.")
halign <- FALSE
}
else {
max_heights <- list(do.call(grid::unit.pmax,
lapply(grobs, function(x) {x$heights})))
}
}
for (i in 1:num_plots) {
if (!is.null(grobs[[i]])) {
if (valign) {
grobs[[i]]$widths <- max_widths[[1]]
# modification: change panel cell's width to a proportion of unit(1, "null"),
# then add whitespace to the left / right of the plot's existing gtable
grobs[[i]]$widths[[5]] <- unit(plot.x.range[[i]][2], "null")
grobs[[i]] <- gtable::gtable_add_cols(grobs[[i]],
widths = unit(plot.x.range[[i]][1], "null"),
pos = 0)
grobs[[i]] <- gtable::gtable_add_cols(grobs[[i]],
widths = unit(plot.x.range[[i]][3], "null"),
pos = -1)
}
if (halign) {
grobs[[i]]$heights <- max_heights[[1]]
# modification: change panel cell's height to a proportion of unit(1, "null"),
# then add whitespace to the bottom / top of the plot's existing gtable
grobs[[i]]$heights[[7]] <- unit(plot.y.range[[i]][2], "null")
grobs[[i]] <- gtable::gtable_add_rows(grobs[[i]],
heights = unit(plot.y.range[[i]][1], "null"),
pos = -1)
grobs[[i]] <- gtable::gtable_add_rows(grobs[[i]],
heights = unit(plot.y.range[[i]][3], "null"),
pos = 0)
}
}
}
grobs
}
Utilising the above modified function with cowplot package's plot_grid:
# To start using (in current R session only; effect will not carry over to subsequent session)
trace(cowplot::plot_grid, edit = TRUE)
# In the pop-up window, change `grobs <- align_plots(...)` (at around line 27) to
# `grobs <- align_plots_modified(...)`
# To stop using
untrace(cowplot::plot_grid)
(Alternatively, we can define a modified version of plot_grid function that uses align_plots_modified instead of cowplot::align_plots. Results would be the same either way.)
Here is an option with grid.arrange that does not use a blank plot, but requires a manual of adjustment of:
plot margin
x axis expansion
number of decimal places in y axis labels
library(ggplot2)
library(dplyr)
library(gridExtra)
p1 <-
ggplot(mtcars, aes(mpg)) +
geom_density(trim = TRUE) +
scale_x_continuous(limits = c(10,35), breaks=seq(10,35,5), expand = expand_scale(add=c(0,0)))
p2 <-
ggplot(filter(mtcars, mpg < 20), aes(mpg)) +
geom_histogram(binwidth = 1, boundary = 1) +
scale_x_continuous(limits = c(10,20), breaks=seq(10,20,5), expand = expand_scale(add=c(0,0))) +
scale_y_continuous(labels = scales::number_format(accuracy = 0.01)) +
theme(plot.margin = unit(c(0,1,0,0), "cm"))
grid.arrange(p1, p2,
layout_matrix = rbind(c(1, 1), c(2, NA))
)
Should make this plot:

Keeping plotted heights fixed when laying out a list ggplot together

I'm creating a list of ggplot heatmaps, which have the same number of rows but different number of columns and different lengths of their x-axis tick labels:
plot.list <- vector(mode="list",length(3))
n.cols <- c(600,30,300)
x.labs <- c("medium","this is a long label","sh")
library(ggplot2)
for(i in 1:3){
set.seed(1)
df <- reshape2::melt(matrix(rnorm(100*n.cols[i]),100,n.cols[i],dimnames = list(paste0("G",1:100),paste0("S",1:n.cols[i]))))
plot.list[[i]] <- ggplot(data=df,mapping=aes(x=Var2,y=Var1,fill=value))+
geom_tile()+theme_minimal()+scale_fill_gradient2(name="Scaled Value",low="darkblue",mid="gray",high="darkred")+
scale_x_discrete(name=NULL,breaks=unique(df$Var2)[floor(length(unique(df$Var2))/2)],labels=x.labs[i])+
scale_y_discrete(name=NULL)+
theme(legend.position=NULL,axis.title.x=element_blank(),axis.text.x=element_text(angle=90,hjust=1,vjust=0.5))
if(i != 1) plot.list[[i]] <- plot.list[[i]]+theme(axis.text.y=element_blank())
if(i != 3) plot.list[[i]] <- plot.list[[i]]+theme(legend.position = "none")
}
I then want to combine them together horizontally with a very small margin separating them, and have their widths be relative to the numbers of columns.
Trying to achieve this using gridExtra's arrangeGrob:
gridExtra::arrangeGrob(grobs=plot.list,ncol=length(plot.list),widths=n.cols,padding=0.01)
Or with cowplot's plot_grid:
cowplot::plot_grid(plotlist=plot.list,align="v",axis="tb",ncol=length(plot.list),rel_widths=n.cols)
Gives me:
So my questions are:
How to get them to have the same height and have the x-axis labels extend downwards to varying lengths?
Shrink the spaces between them? I tried reducing the padding value but see no change
Note that I know that using facet_grid might be the obvious way to create this in the first place, but I specifically need to first create the list of plots and only then combine them.
Both egg:ggarrange and cowplot::plot_grid() can accomplish this.
As far as answering 1, try out:
library(egg)
plot1 <- plot.list[[1]]
plot2 <- plot.list[[2]]
plot3 <- plot.list[[3]]
ggarrange(plot1, plot2, plot3, ncol = 3, widths = c(600,30,300)) #originally had the 20,3,10, but I don't think it scales right.
As far as 2, you can set you plot.margins beforehand and arrange like before.
plot1 <- plot.list[[1]] + theme(plot.margin = margin(1,0,1,1)) # order is top, right, bottom, left. Go negative if you want them to touch.
plot2 <- plot.list[[2]] + theme(plot.margin = margin(1,0,1,0))
plot3 <- plot.list[[3]] + theme(plot.margin = margin(1,1,1,0))
ggarrange(plot1, plot2, plot3, ncol = 3, widths = c(600,30,300))
plot_grid will give you the same image as below.
cowplot::plot_grid(plot1, plot2, plot3, ncol = 3, axis = "b", align = "h", rel_widths = c(600,30,300))

Figure caption scientific names + symbols in textGrob gtable

First of all I would like to thank Sir Baptiste for helping me improve my R script by adding a caption at the bottom left the of the combined plots using gtable/textGrob as shown below:
library(grid)
library(gridExtra)
library(ggplot2)
p1 <- p2 <- ggplot()
g1 <- ggplotGrob(p1)
g2 <- ggplotGrob(p2)
g <- rbind(g1, g2)
caption <- textGrob("Figure 1. This is a caption", hjust=0, x=0)
g <- gtable::gtable_add_rows(g, unit(2,"mm") + grobHeight(caption), -1)
g <- gtable::gtable_add_grob(g, caption, nrow(g), l = 4, r = ncol(g))
grid.newpage()
grid.draw(g)
However, I want to add two more things:
(1) Insert a scientific name to the caption, which should be written in italics.
- For example, based on the caption mentioned above, I want to italize only the word "is" while the rest are in plain text.
(2) I will also add symbols in the caption, e.g. point shapes=c(1,22); colours=c("black", "red"); fill=c("red", "black").
How am I going to do these? I am a novice user of R program, hence your help is much appreciated. Thank you.
UPDATE:
I have already addressed query 1 with the help of #Docconcoct, #user20650 and #baptiste using this script:
library(grid)
library(gridExtra)
library(ggplot2)
g1 <- ggplotGrob(pl)
g2 <- ggplotGrob(pl1)
g <- rbind(g1, g2)
caption <- textGrob(expression(paste("Figure 1. This", italic(" is"), " a caption")), hjust=0, x=0)
g <- gtable::gtable_add_rows(g, unit(2,"mm") + grobHeight(caption), -1)
g <- gtable::gtable_add_grob(g, caption, nrow(g), l = 4, r = ncol(g))
grid.newpage()
grid.draw(g)
For query 2, as stated by Sir #baptiste, in my original email to him, I already have a legend on the combined plots. However, in the figure caption, I need to state what are those symbols in the legend mean, and some other details of the plot. Based on the example given by Sir baptiste, I need to include what supp means, as well as the symbols of OJ (dark circle) and VC (dark triangle) in the caption.
Again, many thanks!
Based on the comments, I suggest the following strategy: create a dummy plot with your figure caption (text) as legend title, extract its legend, and place it at the bottom of your gtable.
library(grid)
library(gridExtra)
library(ggplot2)
library(gtable)
p1 <- ggplot()
p2 <- ggplot(ToothGrowth, aes(len, dose, shape=supp)) + geom_point() +
theme(legend.position="bottom",
legend.background=element_rect(colour="black"))
title <- expression("Figure 1. This "*italic(is)*" now a legendary caption")
dummy <- ggplotGrob(p2 + guides(shape = guide_legend(title = title)))
g1 <- ggplotGrob(p1)
g2 <- ggplotGrob(p2)
caption <- gtable_filter(dummy,"guide")[["grobs"]][[1]]
caption$widths <- grid:::unit.list(caption$widths)
caption$widths <- unit.c(unit(0,"mm"), caption$widths[2], unit(1,"null"))
g <- rbind(g1, g2)
g <- gtable::gtable_add_rows(g, unit(2,"mm") + grobHeight(caption), -1)
g <- gtable::gtable_add_grob(g, caption, nrow(g), l = 4, r = ncol(g))
grid.newpage()
grid.draw(legend)
grid.draw(g)
I think a good solution would rely on LaTeX or similar for the text rendering and particularly the tricky issue of line-wrapping, but something could be designed at R level to facilitate the inclusion of plotting symbols that correspond to a given graphic. Something along those lines,
gl = extract_legend_grobs(p)
caption = caption_plot("Figure 1. We are referring to the points {{gl$points[supp == OG'']}}.
The theoretical model is shown as {{gl$lines[type == 'theory']}}.", gl)
print(caption, output="latex")
## "Figure 1. We are referring to the points \includegraphics{gl_p_1.png}.
## The theoretical model is shown as \includegraphics{gl_l_1.png}."
Interesting thought, but probably a lot of work to get it right.
A quick-and-dirty R graphics output could also be devised, though it's uncommon to want captions to be part of the figure (and R graphics isn't particularly good with text).
Here's a weak attempt at making a caption grob mixing symbols and text. Ideally the text would be split into individual words first (to offer more options for line breaks), but plotmath expressions make it inconvenient.
Next step would be to add a few convenient wrappers to generate common symbols, and to interleave the two lists of grobs.
library(grid)
library(gridExtra)
inwidth <- function(x, margin=unit(1,"mm")) {
if(inherits(x, "text"))
convertWidth(grobWidth(x)+margin, "in", valueOnly = TRUE) else
convertWidth(unit(1,"line")+margin, "in", valueOnly = TRUE)
}
captionGrob <- function(..., width = unit(4, "in"), debug = FALSE){
maxw <- convertWidth(width, "in", valueOnly = TRUE)
lg <- list(...)
lw <- lapply(lg, inwidth)
stopifnot(all(lw < maxw))
# find breaks
cw <- cumsum(lw)
bks <- which(c(0, diff(cw %% maxw)) < 0 )
# list of lines
tg <- list()
starts <- c(1, bks)
ends <- c(bks -1, length(lg))
for(line in seq_along(starts)){
ids <- seq(starts[line], ends[line])
sumw <- do.call(sum,lw[ids])
neww <- maxw - sumw # missing width to fill
filler <- rectGrob(gp=gpar(col=NA, fill=NA),
width=unit(neww, "in"),
height=unit(1, "line"))
grobs <- c(lg[ids], list(filler))
# store current line
tg[[line]] <- arrangeGrob(grobs=grobs, nrow = 1,
widths = unit(c(lw[ids], neww), "in"))
}
# arrange all lines in one column
grid.arrange(grobs=tg, ncol=1,
heights = unit(rep(1, length(tg)), "line"))
if(debug) grid.rect(width=width, gp=gpar(fill=NA, lty=2))
}
tg <- lapply(c(expression(bold(Figure~1.)~italic(Those)~points),
"are important, ", "nonetheless", "and", "have value too."),
textGrob)
pGrob <- function(fill, size=1, ...){
rectGrob(..., width=unit(size,"line"), height=unit(size,"line"), gp=gpar(fill=fill))
}
pg <- mapply(pGrob, fill=1:5, size=0.5, SIMPLIFY = FALSE)
grid.newpage()
captionGrob(tg[[1]], pg[[1]], pg[[2]], pg[[3]], tg[[2]], tg[[3]], pg[[4]], tg[[4]], pg[[5]], tg[[5]])

Adding text to a grid.table plot

I have recently started using the grid.table function from the gridExtra package to turn tabular data into png image files for use on the web. I've been delighted with it so far as it produces very good-looking output by default, sort of like a ggplot2 for tables. Like the person who asked this question I would love to see the ability to specify the justification for individual columns but that would be icing on what is an already more-ish cake.
My question is whether it is possible to add text around a grid.table so that I can give plotted tables a title and a footnote. It seems to me this should be feasible, but I don't know enough about grid graphics to be able to work out how to add grobs to the table grob. For example, this code:
require(gridExtra)
mydf <- data.frame(Item = c('Item 1','Item 2','Item 3'),
                    Value = c(10,15,20), check.names = FALSE)
grid.table(mydf,
gpar.coretext=gpar(fontsize = 16),
gpar.coltext = gpar(fontsize = 16),
gpar.rowtext = gpar(fontsize = 16),
gpar.corefill = gpar(fill = "blue", alpha = 0.5, col = NA),
h.even.alpha = 0.5,
equal.width = FALSE,
show.rownames = FALSE,
show.vlines = TRUE,
padding.h = unit(15, "mm"),
padding.v = unit(8, "mm")
)
generates this plot:
when I would really like to be able to do something like the following in code rather than by editing the image with another application:
To place text close to the table you'll want to evaluate the table size first,
library(gridExtra)
d <- head(iris)
table <- tableGrob(d)
grid.newpage()
h <- grobHeight(table)
w <- grobWidth(table)
title <- textGrob("Title", y=unit(0.5,"npc") + 0.5*h,
vjust=0, gp=gpar(fontsize=20))
footnote <- textGrob("footnote",
x=unit(0.5,"npc") - 0.5*w,
y=unit(0.5,"npc") - 0.5*h,
vjust=1, hjust=0,gp=gpar( fontface="italic"))
gt <- gTree(children=gList(table, title, footnote))
grid.draw(gt)
Edit (17/07/2015) With gridExtra >=2.0.0, this approach is no longer suitable. tableGrob now returns a gtable, which can be more easily customised.
library(gridExtra)
d <- head(iris)
table <- tableGrob(d)
library(grid)
library(gtable)
title <- textGrob("Title",gp=gpar(fontsize=50))
footnote <- textGrob("footnote", x=0, hjust=0,
gp=gpar( fontface="italic"))
padding <- unit(0.5,"line")
table <- gtable_add_rows(table,
heights = grobHeight(title) + padding,
pos = 0)
table <- gtable_add_rows(table,
heights = grobHeight(footnote)+ padding)
table <- gtable_add_grob(table, list(title, footnote),
t=c(1, nrow(table)), l=c(1,2),
r=ncol(table))
grid.newpage()
grid.draw(table)
If you want just the title (no footnote), here is a simplified version of #baptiste's example:
title <- textGrob("Title", gp = gpar(fontsize = 50))
padding <- unit(0.5,"line")
table <- gtable_add_rows(
table, heights = grobHeight(title) + padding, pos = 0
)
table <- gtable_add_grob(
table, list(title),
t = 1, l = 1, r = ncol(table)
)
grid.newpage()
grid.draw(table)

Resources