Is there an elegant way to align the tableGrob rows with the axis breaks?
I would like to juxtapose a tableGrob and ggplot chart in R (I need to reproduce some SAS output used in previous versions of a public report). Like this minimal reproducible example:
This post got me pretty far --- the tableGrob is in the same gtable row as the body of the chart; however, it requires lots of manual fiddling to get the rows in the tableGrob to line up with the axis labels.
I also found this post. Since I'm Sweaving a public report, I would prefer not to use code that isn't readily available in a package on CRAN. That being said, the experimental version of tableGrob appears to accept heights as an argument. If this code will do the trick, and I do choose to use this experimental version, how would I calculate the appropriate row heights?
If there is not an elegant way of doing this, I found these tricks to be helpful:
set fontsize AND cex in tableGrob to match ggplot2
set padding.v to space table rows in tableGrob
modify coordinate limits to accomodate column labels and align with bottom of last row
My MRE code:
library(ggplot2)
library(gridExtra)
library(gtable)
theme_set(theme_bw(base_size = 8))
df <- head(mtcars,10)
df$cars <- row.names(df)
df$cars <- factor(df$cars, levels=df$cars[order(df$disp, decreasing=TRUE)], ordered=TRUE)
p <- ggplot(data=df, aes(x=hp, y=cars)) +
geom_point(aes(x=hp, y=cars)) +
scale_y_discrete(limits=levels(df$cars))+
theme(axis.title.y = element_blank()) +
coord_cartesian(ylim=c(0.5,length(df$cars)+1.5))
t <- tableGrob(df[,c("mpg","cyl","disp","cars")],
cols=c("mpg","cyl","disp","cars"),
gpar.coretext = gpar(fontsize = 8, lineheight = 1, cex = 0.8),
gpar.coltext = gpar(fontsize = 8, lineheight = 1, cex = 0.8),
show.rownames = FALSE,
show.colnames = TRUE,
equal.height = TRUE,
padding.v = unit(1.65, "mm"))
g <- NULL
g <- ggplotGrob(p)
g <- gtable_add_cols(g, unit(2,"in"), 0)
g <- gtable_add_grob(g, t, t=3, b=3, l=1, r=1)
png('./a.png', width = 5, height = 2, units = "in", res = 100)
grid.draw(g)
dev.off()
I have left the car names on the y-axis breaks for troubleshooting purposes, but ultimately I will remove them.
There's now this experimental version of gtable_table
table <- gtable_table(df[,c("mpg","cyl","disp","cars")],
heights = unit(rep(1,nrow(df)), "null"))
g <- ggplotGrob(p)
g <- gtable_add_cols(g, sum(table$widths), 0)
g <- gtable_add_grob(g, table, t=3, b=3, l=1, r=1)
grid.newpage()
grid.draw(g)
#Baptiste's answer expanded to demonstrate column labels and cell parameters:
library(ggplot2)
library(gridExtra)
## I manually changed the dependency on
install.packages(".//gtable_0.2.tar.gz", repos = NULL, type="source")
## The forked version of gtable requires R version 3.2.0
## which is currently in development (as of 9/17/2014) due to change in grid
## (https://github.com/wch/r-source/commit/850a82c30e91feb47a0b6385adcbd82988d90413)
## I have not installed the development version.
## However, I was able, in limited testing, to get this to work with R 3.1.0
## and ggplot2_1.0.0
## YRMV
## The following code, commented out, may be more useful with release of R 3.2.0
## library(devtools)
## devtools::install_github("baptiste/gtable")
library(gtable)
theme_set(theme_bw(base_size = 10))
df <- mtcars
df$cars <- row.names(df)
df <- head(df[,c("mpg","cyl","disp","cars")],10)
df$cars <- factor(df$cars, levels=df$cars[order(df$disp, decreasing=TRUE)], ordered=TRUE)
p <- ggplot(data=df, aes(x=disp, y=cars)) +
geom_point(aes(x=disp, y=cars)) +
scale_y_discrete(limits=levels(df$cars))+
theme(axis.title.y = element_blank()) +
coord_cartesian(ylim = c(0.5,nrow(df)+1))
core <- gtable_table(df[order(df$disp, decreasing=FALSE),],
fg.par = list(fontsize=c(8)),
bg.par = list(fill=c(rep("lightblue",4),rep("white",4)), alpha=0.5),
heights = unit(rep(1,nrow(df)), "null"))
colHead <- gtable_table(t(colnames(df)),
fg.par = list(fontsize=c(8)),
bg.par = list(fill=c(1), alpha=0.2),
heights = unit(0.5, "null"))
table1 <- rbind(colHead, core)
g <- ggplotGrob(p)
g <- gtable_add_cols(g, sum(table1$widths), 0)
g <- gtable_add_grob(g, table1, t=3, b=3, l=1, r=1)
grid.newpage()
grid.draw(g)
Related
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:
I want to plot data for a linear model in a main plot and a plot of the effects (forest plot) as a subplot using arrangeGrob.
Here are the data:
set.seed(1)
main.df <- data.frame(sample=c(paste("E.plus.A.plus",1:3,sep="_"),paste("E.minus.A.plus",1:3,sep="_"),paste("E.plus.A.minus",1:3,sep="_"),paste("E.minus.A.minus",1:3,sep="_")),
replicate=rep(1:3,4),cpm=c(rnorm(12)),
factor.level=factor(c(rep("E.plus.A.plus",3),rep("E.minus.A.plus",3),rep("E.plus.A.minus",3),rep("E.minus.A.minus",3)),
levels=c("E.plus.A.plus","E.minus.A.plus","E.plus.A.minus","E.minus.A.minus")))
effects.df <- data.frame(factor.level=c("E.plus.A.plus-E.minus.A.plus","E.plus.A.plus-E.plus.A.minus","E.plus.A.plus-E.minus.A.minus",
"E.minus.A.plus-E.plus.A.minus","E.minus.A.plus-E.minus.A.minus","E.plus.A.minus-E.minus.A.minus"),
effect=rnorm(6),effect.df=runif(6,0,0.5),p.value=runif(6,0,1),y=1:6+0.2)
effects.df$effect.high <- effects.df$effect+effects.df$effect.df
effects.df$effect.low <- effects.df$effect-effects.df$effect.df
effects.df$factor.level <- factor(effects.df$factor.level,levels=effects.df$factor.level)
The ggplots:
require(ggplot2)
require(grid)
require(gridExtra)
main.plot <- ggplot(main.df,aes(x=replicate,y=cpm,color=factor.level))+geom_point(size=3)+
facet_wrap(~factor.level,ncol=length(levels(main.df$factor.level)))+
labs(x="replicate",y="cpm")+scale_x_continuous(breaks=unique(main.df$replicate))+theme_bw()+
theme(legend.key=element_blank(),panel.border=element_blank(),strip.background=element_blank(),axis.title=element_text(size=8),plot.title=element_text(size=9,hjust=0.5))
Which is:
sub.plot <- ggplot(effects.df,aes(x=effect,y=factor.level,color=factor.level))+geom_point(size=2.5,shape=19)+geom_errorbarh(aes(xmax=effect.high,xmin=effect.low),height=0.1)+
geom_vline(xintercept=0,linetype="longdash",colour="black",size=0.25)+theme_bw()+theme(legend.key=element_blank(),panel.border=element_blank(),strip.background=element_blank(),axis.title=element_text(size=7),axis.text=element_text(size=7),legend.text=element_text(size=7),legend.title=element_text(size=7))+
geom_text(aes(x=effects.df$effect,y=effects.df$y,label=format(signif(effects.df$p.value,2),scientific=T)),size=2.5)
And is:
And here's how I try to combine them into a single plot:
if(!is.null(dev.list())) dev.off()
blank <- grid.rect(gp = gpar(col = "white"))
sub.plot.grob <- arrangeGrob(blank,sub.plot,ncol=1)
combined.plot <- arrangeGrob(main.plot,sub.plot,ncol=2,widths=c(1,1))
grid.arrange(combined.plot)
which gives:
How do I adjust the position and dimensions so that sub.plot is smaller (all layers, e.g., text are reduced proportionally), and is positioned below the legend of main.plot?
I strongly recommend the package cowplot for this sort of task. Here, I am building three nested sets (the main plot to the left, then the two legends together at the top right, then the sub plot at the bottom right). Note the wonderful get_legend function that make pulling the legends incredibly easy.
plot_grid(
main.plot + theme(legend.position = "none")
, plot_grid(
plot_grid(
get_legend(main.plot)
, get_legend(sub.plot)
, nrow = 1
)
, sub.plot + theme(legend.position = "none")
, nrow = 2
)
, nrow = 1
)
gives:
Obviously I'd recommend changing one (or both) of the color palettes, but that should give what you want.
If you really want the legend with the sub.plot, instead of with the other legend, you could skip the get_legend.
You can also adjust the width/height of the sets using rel_widths and rel_heights if you want something other than the even sizes.
As an additional note, cowplot sets its own default theme on load. I generally revert to what I like by running theme_set(theme_minimal()) right after loading it.
here's a grid.arrange solution,
grid.arrange(grobs = replicate(4, ggplot(), simplify = FALSE),
layout_matrix = cbind(c(1,1), c(3,2), c(4, 2)),
widths = c(2,1,1))
with those bits and pieces,
get_legend <- function(p) {
g <- ggplotGrob(p)
id <- grep("guide", g$layout$name)
g$grobs[[id]]
}
leg1 <- get_legend(main.plot); leg2 <- get_legend(sub.plot)
gl <- list(main.plot + theme(legend.position = "none"),
sub.plot + theme(legend.position = "none"), leg1, leg2)
grid.arrange(grobs = gl,
layout_matrix = cbind(c(1,1), c(3,2), c(4, 2)),
widths = c(2,1,1))
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]])
Is it possible to overlay two plots with gridExtra (or other package)?
I want to rescale one plot and overlay it to a second one (specifying rescaling and coordinates)
require(ggplot2)
require(gridExtra)
df <- data.frame(value=rnorm(10), date=1:10)
p1 <- ggplot(data.frame(df), aes(value,date)) + geom_line()
p2 <- ggplot(data.frame(df), aes(value,date)) + geom_point()
to obtain something like this
Look at gtable package in combination with gridExtra. You can specify size and coordinates of plot as you like.
require(gtable)
p1 <- ggplotGrob(p1)
p2 <- ggplotGrob(p2)
gt <- gtable(widths = unit(c(1, 2), "null"), heights = unit(c(.2, 1, 1), "null"))
gt <- gtable_add_grob(gt, p2, t = 1, b = 3, l = 1, r = 2)
gt <- gtable_add_grob(gt, p1, t = 2, l = 2)
grid.draw(gt)
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)