I'm trying to make my multipanel ggplot with a shared legend more flexible in a ShinyApp by allowing the user to choose how many panels to plot.
Currently my code writes out the panel objects 1 at a time like this.
grid_arrange_shared_legend(p1,p2,p3,p4, ncol = 4, nrow = 1)
I do not fully understand why I can not find a way to tell the grid_arrange_shared_legend to accept a list of plots (list object) rather than writing them out 1 after the other.
It throws this error:
Error in UseMethod("ggplot_build") :
no applicable method for 'ggplot_build' applied to an object of class "NULL"
library(ggplot2)
library(lemon)
plotlist <- list()
dsamp <- diamonds[sample(nrow(diamonds), 300), ]
plotlist$p1 <- qplot(carat, price, data = dsamp, colour = clarity)
plotlist$p2 <- qplot(cut, price, data = dsamp, colour = clarity)
plotlist$p3 <- qplot(color, price, data = dsamp, colour = clarity)
plotlist$p4 <- qplot(depth, price, data = dsamp, colour = clarity)
grid_arrange_shared_legend(plotlist, ncol = 4, nrow = 1)
with the use of a list, it would not matter how many plots are in the list, and I would calculate ncol or nrow based on the length of the list...
My homebrew version of the function gets that by adding a plotlist parameter, and adding the plots <- c(list(...), plotlist) line as the first line of code. That way it can take both a list of plots or separate plot objects.
grid_arrange_shared_legend_plotlist <- function(...,
plotlist=NULL,
ncol = length(list(...)),
nrow = NULL,
position = c("bottom", "right")) {
plots <- c(list(...), plotlist)
if (is.null(nrow)) nrow = ceiling(length(plots)/ncol)
position <- match.arg(position)
g <- ggplotGrob(plots[[1]] + theme(legend.position = position))$grobs
legend <- g[[which(sapply(g, function(x) x$name) == "guide-box")]]
lheight <- sum(legend$height)
lwidth <- sum(legend$width)
gl <- lapply(plots, function(x) x + theme(legend.position="none"))
gl <- c(gl, ncol = ncol, nrow = nrow)
combined <- switch(position,
"bottom" = arrangeGrob(do.call(arrangeGrob, gl),
legend,
ncol = 1,
heights = unit.c(unit(1, "npc") - lheight, lheight)),
"right" = arrangeGrob(do.call(arrangeGrob, gl),
legend,
ncol = 2,
widths = unit.c(unit(1, "npc") - lwidth, lwidth)))
grid.newpage()
grid.draw(combined)
# return gtable invisibly
invisible(combined)
}
Using your example:
library(gridExtra)
library(grid)
library(ggplot2)
plots <- list()
dsamp <- diamonds[sample(nrow(diamonds), 300), ]
plots$p1 <- qplot(carat, price, data = dsamp, colour = clarity)
plots$p2 <- qplot(cut, price, data = dsamp, colour = clarity)
plots$p3 <- qplot(color, price, data = dsamp, colour = clarity)
plots$p4 <- qplot(depth, price, data = dsamp, colour = clarity)
grid_arrange_shared_legend_plotlist(plotlist = plots, ncol = 4)
The ugly text string paste solution:
Since the answers provided do not seem to work, or are not suitable (rebuilding an entirely different set of plots than the list of plot objects I already have from extensive code, I played around a bit with eval(parse(text = ....) and paste0 to dynamically generate a text string that ends up being the fully written out code (which works) without actually writing it out
nplots = 4
nrow = 2
ncol = ceiling(nplots/nrow)
eval(parse( text = paste0("grid_arrange_shared_legend(", paste0("plotlist", "[[", c(1:nplots), "]]", sep = '', collapse = ','), ",ncol =", ncol, ",nrow =", nrow, ", position = 'right', top=grid::textGrob('My title', gp=grid::gpar(fontsize=18)))", sep = '')))
which produces:
[1]
"grid_arrange_shared_legend(plotlist[[1]],plotlist[[2]],plotlist[[3]],plotlist[[4]],ncol
=2,nrow =2, position = 'right', top=grid::textGrob('My title', gp=grid::gpar(fontsize=18)))"
Related
I am trying to come up with a way to consistently colour multiple tidygraph plots. Right now, the issue is, when I plot multiple plots to the screen at once, tidygraph chooses a different colour for each variable. hopefully my example below will explain the issue.
To begin, I create some data, turn them into tidygraph objects, and put them together into a list:
library(tidygraph)
library(ggraph)
library(gridExtra)
# create some data for the tbl_graph
nodes <- data.frame(name = c("x4", NA, NA),
label = c("x4", 5, 2))
nodes1 <- data.frame(name = c("x4", "x2", NA, NA, "x1", NA, NA),
label = c("x4", "x2", 2, 1, "x1", 2, 7))
edges <- data.frame(from = c(1,1), to = c(2,3))
edges1 <- data.frame(from = c(1, 2, 2, 1, 5, 5),
to = c(2, 3, 4, 5, 6, 7))
# create the tbl_graphs
tg <- tbl_graph(nodes = nodes, edges = edges)
tg_1 <- tbl_graph(nodes = nodes1, edges = edges1)
# put into list
myList <- list(tg, tg_1)
Then I have a plotting function that allows me to display all the plots at once. I do this using grid.arrange from the gridExtra package, like so:
plotFun <- function(List){
ggraph(List, "partition") +
geom_node_tile(aes(fill = name), size = 0.25) +
geom_node_label(aes(label = label, color = name)) +
scale_y_reverse() +
theme_void() +
theme(legend.position = "none")
}
# Display all plots
allPlots <- lapply(myList, plotFun)
n <- length(allPlots)
nRow <- floor(sqrt(n))
do.call("grid.arrange", c(allPlots, nrow = nRow))
This will produce something like this:
As you can see, it colours by the variable label for each individual plot. This results in the same variable label being coloured differently in each plot. For example, x4 in the first plot is red and in the second plot is blue.
I'm trying to find a way to make the colours for the variable's label consistent across all plots. Maybe using grid.arrange isn't the best solution!?
Any help is appreciated.
Since each plot doesn't know anything about the other plots, it's best to assign colors yourself. First you can extract all the node names and assign them a color
nodenames <- unique(na.omit(unlist(lapply(myList, .%>%activate(nodes) %>% pull(name) ))))
nodecolors <- setNames(scales::hue_pal(c(0,360)+15, 100, 64, 0, 1)(length(nodenames)), nodenames)
nodecolors
# x4 x2 x1
# "#F5736A" "#00B734" "#5E99FF"
We use scales::hue_pal to get the "default" ggplot colors but you could use whatever you like. Then we just need to customize the color/fill scales for the plots with these colors.
plotFun <- function(List, colors=NULL){
plot <- ggraph(List, "partition") +
geom_node_tile(aes(fill = name), size = 0.25) +
geom_node_label(aes(label = label, color = name)) +
scale_y_reverse() +
theme_void() +
theme(legend.position = "none")
if (!is.null(colors)) {
plot <- plot + scale_fill_manual(values=colors) +
scale_color_manual(values=colors, na.value="grey")
}
plot
}
allPlots <- lapply(myList, plotFun, colors=nodecolors)
n <- length(allPlots)
nRow <- floor(sqrt(n))
do.call("grid.arrange", c(allPlots, nrow = nRow))
I used the solution proposed here to color the strips of facets created with facet_wrap based on a variable supplied with the data frame.
I need to add the legend for the strip colors (size), which is plotted in the dummy.
Any idea of how I can grab it from g2$layout, or any other way?
library(gtable)
library(grid)
d <- data.frame(fruit = rep(c("apple", "orange", "plum", "banana", "pear", "grape")),
farm = rep(c(0,1,3,6,9,12), each=6),
weight = rnorm(36, 10000, 2500),
size=rep(c("small", "large")))
p1 = ggplot(data = d, aes(x = farm, y = weight)) +
geom_jitter(position = position_jitter(width = 0.3),
aes(color = factor(farm)), size = 2.5, alpha = 1) +
facet_wrap(~fruit)
dummy <- ggplot(data = d, aes(x = farm, y = weight))+ facet_wrap(~fruit) +
geom_rect(aes(fill=size), xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) +
theme_minimal()
g1 <- ggplotGrob(p1)
g2 <- ggplotGrob(dummy)
gtable_select <- function (x, ...)
{
matches <- c(...)
x$layout <- x$layout[matches, , drop = FALSE]
x$grobs <- x$grobs[matches]
x
}
panels <- grepl(pattern="panel", g2$layout$name)
strips <- grepl(pattern="strip-t", g2$layout$name)
g2$layout$t[panels] <- g2$layout$t[panels] - 1
g2$layout$b[panels] <- g2$layout$b[panels] - 1
new_strips <- gtable_select(g2, panels | strips)
grid.newpage()
grid.draw(new_strips)
gtable_stack <- function(g1, g2){
g1$grobs <- c(g1$grobs, g2$grobs)
g1$layout <- transform(g1$layout, z= z-max(z), name="g2")
g1$layout <- rbind(g1$layout, g2$layout)
g1
}
new_plot <- gtable_stack(g1, new_strips)
grid.newpage()
grid.draw(new_plot)
Borrowing the following function from this answer, you can first extract the legend from your dummy plot.
# Extract only the legend from "dummy" plot
g_legend <- function(dummy){
tmp <- ggplot_gtable(ggplot_build(dummy))
leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
legend <- tmp$grobs[[leg]]
return(legend)}
# Assign the legend to a separate object
facet.legend <- g_legend(dummy)
You can then use grid.arrange() from package gridExtra...
library(gridExtra)
jpeg("plot-with-facet-legend.jpg", width = 8, height = 6, units = "in", res = 300)
print(grid.arrange(new_plot, facet.legend, nrow = 2, widths = c(7, 1), heights = c(6, 0.01)))
dev.off()
... to produce the following plot:
Alternatively: A more compact solution that grabs the legend straight from your g2 object, before executing the same jpeg(...), print(grid.arrange(...)) code:
facet.legend <- g2$grobs[[which(sapply(g2$grobs, function(x) x$name) %in% "guide-box")]]
Of course, you could play around with the widths and heights arguments to produce a more tidy plot, and there might exist another solution that is a little less hackneyed than mine, but hopefully this is at least roughly what you seek.
I have used the method indicated here to align graphs sharing the same abscissa.
But I can't make it work when some of my graphs have a legend and others don't.
Here is an example:
library(ggplot2)
library(reshape2)
library(gridExtra)
x = seq(0, 10, length.out = 200)
y1 = sin(x)
y2 = cos(x)
y3 = sin(x) * cos(x)
df1 <- data.frame(x, y1, y2)
df1 <- melt(df1, id.vars = "x")
g1 <- ggplot(df1, aes(x, value, color = variable)) + geom_line()
print(g1)
df2 <- data.frame(x, y3)
g2 <- ggplot(df2, aes(x, y3)) + geom_line()
print(g2)
gA <- ggplotGrob(g1)
gB <- ggplotGrob(g2)
maxWidth <- grid::unit.pmax(gA$widths[2:3], gB$widths[2:3])
gA$widths[2:3] <- maxWidth
gB$widths[2:3] <- maxWidth
g <- arrangeGrob(gA, gB, ncol = 1)
grid::grid.newpage()
grid::grid.draw(g)
Using this code, I have the following result:
What I would like is to have the x axis aligned and the missing legend being filled by a blank space. Is this possible?
Edit:
The most elegant solution proposed is the one by Sandy Muspratt below.
I implemented it and it works quite well with two graphs.
Then I tried with three, having different legend sizes, and it doesn't work anymore:
library(ggplot2)
library(reshape2)
library(gridExtra)
x = seq(0, 10, length.out = 200)
y1 = sin(x)
y2 = cos(x)
y3 = sin(x) * cos(x)
y4 = sin(2*x) * cos(2*x)
df1 <- data.frame(x, y1, y2)
df1 <- melt(df1, id.vars = "x")
g1 <- ggplot(df1, aes(x, value, color = variable)) + geom_line()
g1 <- g1 + theme_bw()
g1 <- g1 + theme(legend.key = element_blank())
g1 <- g1 + ggtitle("Graph 1", subtitle = "With legend")
df2 <- data.frame(x, y3)
g2 <- ggplot(df2, aes(x, y3)) + geom_line()
g2 <- g2 + theme_bw()
g2 <- g2 + theme(legend.key = element_blank())
g2 <- g2 + ggtitle("Graph 2", subtitle = "Without legend")
df3 <- data.frame(x, y3, y4)
df3 <- melt(df3, id.vars = "x")
g3 <- ggplot(df3, aes(x, value, color = variable)) + geom_line()
g3 <- g3 + theme_bw()
g3 <- g3 + theme(legend.key = element_blank())
g3 <- g3 + scale_color_discrete("This is indeed a very long title")
g3 <- g3 + ggtitle("Graph 3", subtitle = "With legend")
gA <- ggplotGrob(g1)
gB <- ggplotGrob(g2)
gC <- ggplotGrob(g3)
gB = gtable::gtable_add_cols(gB, sum(gC$widths[7:8]), 6)
maxWidth <- grid::unit.pmax(gA$widths[2:5], gB$widths[2:5], gC$widths[2:5])
gA$widths[2:5] <- maxWidth
gB$widths[2:5] <- maxWidth
gC$widths[2:5] <- maxWidth
g <- arrangeGrob(gA, gB, gC, ncol = 1)
grid::grid.newpage()
grid::grid.draw(g)
This results in the following figure:
My main problem with the answers found here and in other questions regarding the subject is that people "play" quite a lot with the vector myGrob$widths without actually explaining why they are doing it. I have seen people modify myGrob$widths[2:5] others myGrob$widths[2:3] and I just can't find any documentation explaining what those columns are.
My objective is to create a generic function such as:
AlignPlots <- function(...) {
# Retrieve the list of plots to align
plots.list <- list(...)
# Initialize the lists
grobs.list <- list()
widths.list <- list()
# Collect the widths for each grob of each plot
max.nb.grobs <- 0
longest.grob <- NULL
for (i in 1:length(plots.list)){
if (i != length(plots.list)) {
plots.list[[i]] <- plots.list[[i]] + theme(axis.title.x = element_blank())
}
grobs.list[[i]] <- ggplotGrob(plots.list[[i]])
current.grob.length <- length(grobs.list[[i]])
if (current.grob.length > max.nb.grobs) {
max.nb.grobs <- current.grob.length
longest.grob <- grobs.list[[i]]
}
widths.list[[i]] <- grobs.list[[i]]$widths[2:5]
}
# Get the max width
maxWidth <- do.call(grid::unit.pmax, widths.list)
# Assign the max width to each grob
for (i in 1:length(grobs.list)){
if(length(grobs.list[[i]]) < max.nb.grobs) {
grobs.list[[i]] <- gtable::gtable_add_cols(grobs.list[[i]],
sum(longest.grob$widths[7:8]),
6)
}
grobs.list[[i]]$widths[2:5] <- as.list(maxWidth)
}
# Generate the plot
g <- do.call(arrangeGrob, c(grobs.list, ncol = 1))
return(g)
}
Expanding on #Axeman's answer, you can do all of this with cowplot without ever needing to use draw_plot directly. Essentially, you just make the plot in two columns -- one for the plots themselves and one for the legends -- and then place them next to each other. Note that, because g2 has no legend, I am using an empty ggplot object to hold the place of that legend in the legends column.
library(cowplot)
theme_set(theme_minimal())
plot_grid(
plot_grid(
g1 + theme(legend.position = "none")
, g2
, g3 + theme(legend.position = "none")
, ncol = 1
, align = "hv")
, plot_grid(
get_legend(g1)
, ggplot()
, get_legend(g3)
, ncol =1)
, rel_widths = c(7,3)
)
Gives
The main advantage here, in my mind, is the ability to set and skip legends as needed for each of the subplots.
Of note is that, if all of the plots have a legend, plot_grid handles the alignment for you:
plot_grid(
g1
, g3
, align = "hv"
, ncol = 1
)
gives
It is only the missing legend in g2 that causes problems.
Therefore, if you add a dummy legend to g2 and hide it's elements, you can get plot_grid to do all of the alignment for you, instead of worrying about manually adjusting rel_widths if you change the size of the output
plot_grid(
g1
, g2 +
geom_line(aes(color = "Test")) +
scale_color_manual(values = NA) +
theme(legend.text = element_blank()
, legend.title = element_blank())
, g3
, align = "hv"
, ncol = 1
)
gives
This also means that you can easily have more than one column, but still keep the plot areas the same. Simply removing , ncol = 1 from above yields a plot with 2 columns, but still correctly spaced (though you'll need to adjust the aspect ratio to make it useable):
As #baptiste suggested, you can also move the legends over so that they are all aligned to the left of in the "legend" portion of the plot by adding theme(legend.justification = "left") to the plots with the legends (or in theme_set to set globally), like this:
plot_grid(
g1 +
theme(legend.justification = "left")
,
g2 +
geom_line(aes(color = "Test")) +
scale_color_manual(values = NA) +
theme(legend.text = element_blank()
, legend.title = element_blank())
, g3 +
theme(legend.justification = "left")
, align = "hv"
, ncol = 1
)
gives
The patchwork package by Thomas Lin Pedersen does this all automagically:
library(patchwork)
g1 + g2 + plot_layout(ncol = 1)
Can hardly get any easier than that.
There might now be easier ways to do this, but your code was not far wrong.
After you have ensured that the widths of columns 2 and 3 in gA are the same as those in gB, check the widths of the two gtables: gA$widths and gB$widths. You will notice that the gA gtable has two additional columns not present in the gB gtable, namely widths 7 and 8. Use the gtable function gtable_add_cols() to add the columns to the gB gtable:
gB = gtable::gtable_add_cols(gB, sum(gA$widths[7:8]), 6)
Then proceed with arrangeGrob() ....
Edit: For a more general solution
Package egg (available on github) is experimental and fragile, but works nicely with your revised set of plots.
# install.package(devtools)
devtools::install_github("baptiste/egg")
library(egg)
grid.newpage()
grid.draw(ggarrange(g1,g2,g3, ncol = 1))
Thanks to this and that, posted in the comments (and then removed), I came up with the following general solution.
I like the answer from Sandy Muspratt and the egg package seems to do the job in a very elegant manner, but as it is "experimental and fragile", I preferred using this method:
#' Vertically align a list of plots.
#'
#' This function aligns the given list of plots so that the x axis are aligned.
#' It assumes that the graphs share the same range of x data.
#'
#' #param ... The list of plots to align.
#' #param globalTitle The title to assign to the newly created graph.
#' #param keepTitles TRUE if you want to keep the titles of each individual
#' plot.
#' #param keepXAxisLegends TRUE if you want to keep the x axis labels of each
#' individual plot. Otherwise, they are all removed except the one of the graph
#' at the bottom.
#' #param nb.columns The number of columns of the generated graph.
#'
#' #return The gtable containing the aligned plots.
#' #examples
#' g <- VAlignPlots(g1, g2, g3, globalTitle = "Alignment test")
#' grid::grid.newpage()
#' grid::grid.draw(g)
VAlignPlots <- function(...,
globalTitle = "",
keepTitles = FALSE,
keepXAxisLegends = FALSE,
nb.columns = 1) {
# Retrieve the list of plots to align
plots.list <- list(...)
# Remove the individual graph titles if requested
if (!keepTitles) {
plots.list <- lapply(plots.list, function(x) x <- x + ggtitle(""))
plots.list[[1]] <- plots.list[[1]] + ggtitle(globalTitle)
}
# Remove the x axis labels on all graphs, except the last one, if requested
if (!keepXAxisLegends) {
plots.list[1:(length(plots.list)-1)] <-
lapply(plots.list[1:(length(plots.list)-1)],
function(x) x <- x + theme(axis.title.x = element_blank()))
}
# Builds the grobs list
grobs.list <- lapply(plots.list, ggplotGrob)
# Get the max width
widths.list <- do.call(grid::unit.pmax, lapply(grobs.list, "[[", 'widths'))
# Assign the max width to all grobs
grobs.list <- lapply(grobs.list, function(x) {
x[['widths']] = widths.list
x})
# Create the gtable and display it
g <- grid.arrange(grobs = grobs.list, ncol = nb.columns)
# An alternative is to use arrangeGrob that will create the table without
# displaying it
#g <- do.call(arrangeGrob, c(grobs.list, ncol = nb.columns))
return(g)
}
One trick is to plot and align the graphs without any legends, and then plotting the legend separately next to it. cowplot has a convenience function for quickly getting the legend from a plot, and plot_grid allows for automatic allignment.
library(cowplot)
theme_set(theme_grey())
l <- get_legend(g1)
ggdraw() +
draw_plot(plot_grid(g1 + theme(legend.position = 'none'), g2, ncol = 1, align = 'hv'),
width = 0.9) +
draw_plot(l, x = 0.9, y = 0.55, width = 0.1, height = 0.5)
Using grid.arrange
library(ggplot2)
library(reshape2)
library(gridExtra)
x = seq(0, 10, length.out = 200)
y1 = sin(x)
y2 = cos(x)
y3 = sin(x) * cos(x)
df1 <- data.frame(x, y1, y2)
df1 <- melt(df1, id.vars = "x")
g1 <- ggplot(df1, aes(x, value, color = variable)) + geom_line()
df2 <- data.frame(x, y3)
g2 <- ggplot(df2, aes(x, y3)) + geom_line()
#extract the legend from the first graph
temp <- ggplotGrob(g1)
leg_index <- which(sapply(temp$grobs, function(x) x$name) == "guide-box")
legend <- temp$grobs[[leg_index]]
#remove the legend of the first graph
g1 <- g1 + theme(legend.position="none")
#define position of each grobs/plots and width and height ratio
grid_layout <- rbind(c(1,3),
c(2,NA))
grid_width <- c(5,1)
grid_heigth <- c(1,1)
grid.arrange(
grobs=list(g1, g2,legend),
layout_matrix = grid_layout,
widths = grid_width,
heights = grid_heigth)
I have a z-scores matrix:
set.seed(1)
z.score.mat <- matrix(rnorm(1000),nrow=100,ncol=10)
which are the result of some biological experimental data, and a corresponding p-value matrix:
p.val.mat <- pnorm(abs(z.score.mat),lower.tail = F)
Both have identical dimnames:
rownames(z.score.mat) <- paste("p",1:100,sep="")
colnames(z.score.mat) <- paste("c",1:10,sep="")
rownames(p.val.mat) <- paste("p",1:100,sep="")
colnames(p.val.mat) <- paste("c",1:10,sep="")
I'm plotting a hierarchically clustered heatmap of the z-scores like this:
hc.col <- hclust(dist(z.score.mat))
dd.col <- as.dendrogram(hc.col)
col.ord <- order.dendrogram(dd.col)
hc.row <- hclust(dist(t(z.score.mat)))
dd.row <- as.dendrogram(hc.row)
row.ord <- order.dendrogram(dd.row)
clustered.mat <- z.score.mat[col.ord,row.ord]
clustered.mat.names <- attr(clustered.mat,"dimnames")
clustered.mat.df <- as.data.frame(clustered.mat)
colnames(clustered.mat.df) <- clustered.mat.names[[2]]
clustered.mat.df[,"process"] <- clustered.mat.names[[1]]
clustered.mat.df[,"process"] <- with(clustered.mat.df,factor(clustered.mat.df[,"process"],levels=clustered.mat.df[,"process"],ordered=TRUE))
require(reshape2)
clustered.mat.df <- reshape2::melt(clustered.mat.df,id.vars="process")
colnames(clustered.mat.df)[2:3] <- c("condition","z.score")
clustered.mat.df$p.value <- sapply(1:nrow(clustered.mat.df),function(x) p.val.mat[which(rownames(p.val.mat) == clustered.mat.df$process[x]),which(colnames(p.val.mat) == clustered.mat.df$condition[x])])
lab.legend <- colnames(clustered.mat.df)[3]
lab.row <- colnames(clustered.mat.df)[1]
lab.col <- colnames(clustered.mat.df)[2]
require(ggplot2)
ggplot(clustered.mat.df,aes(x=condition,y=process))+
geom_tile(aes(fill=z.score))+
scale_fill_gradient2(lab.legend,high="darkred",low="darkblue")+
theme_bw()+
theme(legend.key=element_blank(),
legend.position="right",
panel.border=element_blank(),
strip.background=element_blank(),
axis.text.x=element_text(angle=45,vjust=0.5)
)
My question is if it is possible, and how, to have on one side of the legend bar the z-score range (which is currently on the right hand) and on the other side the corresponding p-value range?
This is quite fiddly when the plot dimensions change, but you do get the required result:
br <- seq(-3, 3, 1)
lab <- round(pnorm(abs(br),lower.tail = F), 3)
p <- ggplot(clustered.mat.df,aes(x=condition,y=process))+
geom_tile(aes(fill=z.score), show.legend = FALSE)+
scale_fill_gradient2(lab.legend, high="darkred", low="darkblue", breaks = br)
p1 <- ggplot(clustered.mat.df,aes(x=condition,y=process))+
geom_tile(aes(fill=z.score))+
scale_fill_gradient2(lab.legend, high="darkred", low="darkblue", breaks = br) +
guides(fill = guide_colorbar(title = '', label.position = 'right', barheight = 10))
p2 <- ggplot(clustered.mat.df,aes(x=condition,y=process))+
geom_tile(aes(fill=z.score))+
scale_fill_gradient2(lab.legend, high="darkred", low="darkblue", breaks = br, labels = lab) +
guides(fill = guide_colorbar('', label.position = 'left', barheight = 10))
library(cowplot)
l1 <- get_legend(p1)
l2 <- get_legend(p2)
ggdraw() +
draw_plot(p, width = 0.85) +
draw_grob(l1, 0.89, 0, 0.1, 1) +
draw_grob(l2, 0.85, 0, 0.1, 1) +
draw_label('p z', 0.88, 0.675, hjust = 0)
This approach uses gtable and grid functions. It takes the legend from your plot, edits the legend so that the p values appear on the left side, then puts the edited legend back into the plot.
# Your data
set.seed(1)
z.score.mat <- matrix(rnorm(1000),nrow=100,ncol=10)
# which are the result of some biological experimental data, and a corresponding p-value matrix:
p.val.mat <- pnorm(abs(z.score.mat),lower.tail = F)
rownames(z.score.mat) <- paste("p",1:100,sep="")
colnames(z.score.mat) <- paste("c",1:10,sep="")
rownames(p.val.mat) <- paste("p",1:100,sep="")
colnames(p.val.mat) <- paste("c",1:10,sep="")
hc.col <- hclust(dist(z.score.mat))
dd.col <- as.dendrogram(hc.col)
col.ord <- order.dendrogram(dd.col)
hc.row <- hclust(dist(t(z.score.mat)))
dd.row <- as.dendrogram(hc.row)
row.ord <- order.dendrogram(dd.row)
clustered.mat <- z.score.mat[col.ord,row.ord]
clustered.mat.names <- attr(clustered.mat,"dimnames")
clustered.mat.df <- as.data.frame(clustered.mat)
colnames(clustered.mat.df) <- clustered.mat.names[[2]]
clustered.mat.df[,"process"] <- clustered.mat.names[[1]]
clustered.mat.df[,"process"] <- with(clustered.mat.df,factor(clustered.mat.df[,"process"],levels=clustered.mat.df[,"process"],ordered=TRUE))
require(reshape2)
clustered.mat.df <- reshape2::melt(clustered.mat.df,id.vars="process")
colnames(clustered.mat.df)[2:3] <- c("condition","z.score")
clustered.mat.df$p.value <- sapply(1:nrow(clustered.mat.df),function(x) p.val.mat[which(rownames(p.val.mat) == clustered.mat.df$process[x]),which(colnames(p.val.mat) == clustered.mat.df$condition[x])])
lab.legend <- colnames(clustered.mat.df)[3]
lab.row <- colnames(clustered.mat.df)[1]
lab.col <- colnames(clustered.mat.df)[2]
# Your plot
require(ggplot2)
p = ggplot(clustered.mat.df,aes(x=condition,y=process))+
geom_tile(aes(fill=z.score))+
scale_fill_gradient2(lab.legend,high="darkred",low="darkblue") +
theme_bw()+
theme(legend.key=element_blank(),
legend.position="right",
panel.border=element_blank(),
strip.background=element_blank(),
axis.text.x=element_text(angle=45,vjust=0.5))
library(gtable)
library(grid)
# Get the ggplot grob
g = ggplotGrob(p)
# Get the legend
index = which(g$layout$name == "guide-box")
leg = g$grobs[[index]]
# Get the legend labels
# and calculate corresponding p values
z.breaks = as.numeric(leg$grobs[[1]]$grobs[[3]]$label)
p.breaks = as.character(round(pnorm(abs(z.breaks), lower.tail = F), 3))
# Get the width of the longest p.break string, taking account of font and font size
w = lapply(na.omit(p.breaks), function(x) grobWidth(textGrob(x,
gp = gpar(fontsize = leg$grobs[[1]]$grobs[[3]]$gp$fontsize,
fontfamily = leg$grobs[[1]]$grobs[[3]]$gp$fontfamily))))
w = do.call(unit.pmax, w)
w = convertX(w, "mm")
# Add columns to the legend gtable to take p.breaks,
# setting the width of relevant column to w
leg$grobs[[1]] = gtable_add_cols(leg$grobs[[1]], leg$grobs[[1]]$widths[3], 1)
leg$grobs[[1]] = gtable_add_cols(leg$grobs[[1]], w, 1)
# Construct grob containing p.breaks
# Begin with the z.score grob, then make relevant changes
p.values = leg$grobs[[1]]$grobs[[3]]
p.values[c("label", "x", "hjust")] = list(p.breaks, unit(1, "npc"), 1)
# Put the p.values grob into the legend gtable
leg$grobs[[1]] = gtable_add_grob(leg$grobs[[1]], p.values, t=4, l=2,
name = "p.values", clip = "off")
# Put 'p' and 'z' labels into the legend gtable
leg$grobs[[1]] = gtable_add_grob(leg$grobs[[1]], list(textGrob("p"), textGrob("z")),
t=2, l=c(2,6), clip = "off")
# Drop the current legend title
leg$grobs[[1]]$grobs[[4]] = nullGrob()
# Put the legend back into the plot,
# and make sure the relevant column is wide enough to take the new legend
g$grobs[[index]] = leg
g$widths[8] = g$widths[8] + sum(leg$grobs[[1]]$widths[2:3])
# Draw the plot
grid.newpage()
grid.draw(g)
Not precisely what you described, but you could put both p values and z values into the same labels on one side of the legend:
z.breaks = c(-2,0,2)
p.breaks = pnorm(abs(z.breaks),lower.tail = F)
ggplot(clustered.mat.df,aes(x=condition,y=process)) +
geom_tile(aes(fill = z.score)) +
scale_fill_gradient2("z score (p value)", high="darkred",low="darkblue",
breaks = z.breaks,
labels = paste0(z.breaks, ' (p = ', round(p.breaks,2), ')') ) +
theme_bw() +
theme(legend.key = element_blank(),
legend.position = 'right',
panel.border = element_blank(),
strip.background = element_blank(),
axis.text.x=element_text(angle=45,vjust=0.5))
I have two ggplots on the same page, and I'd like their panels to be the same width.
Some sample data:
dfr1 <- data.frame(
time = 1:10,
value = runif(10)
)
dfr2 <- data.frame(
time = 1:10,
value = runif(10, 1000, 1001)
)
One plot below the other:
p1 <- ggplot(dfr1, aes(time, value)) + geom_line()
p2 <- ggplot(dfr2, aes(time, value)) + geom_line()
grid.newpage()
pushViewport(viewport(layout = grid.layout(2, 1)))
print(p1, vp = viewport(layout.pos.row = 1, layout.pos.col = 1))
print(p2, vp = viewport(layout.pos.row = 2, layout.pos.col = 1))
How do I specify the panel widths and positions in each plot, in order to make them line up?
(I don't want to combine the plots with faceting; it isn't appropriate in my real-world example.)
Original solution:
# install.packages("ggExtra", repos="http://R-Forge.R-project.org")
# library(ggExtra)
# align.plots(p1, p2)
Edit (22/03/13):
Since ggExtra doesn't exist anymore (and many internals of ggplot2 have changed), it's better to use the merging functions (rbind, cbind) provided by the gtable package,
gl = lapply(list(p1,p2), ggplotGrob)
library(gtable)
g = do.call(rbind, c(gl, size="first"))
g$widths = do.call(unit.pmax, lapply(gl, "[[", "widths"))
grid.newpage()
grid.draw(g)