Suppose I have the following chart
dat <- data.frame(x = 1:10, y = 1:10)
ggplot(dat, aes(x=x, y=y)) + geom_point()
but I'm not actually interested in the values 2.5, 5, 7.5, 10 on either axis. All I want to mark is something like "Low to High". I know I can mark + xlab("Low to High") but I would prefer "Low" to be at the far left of the axis (under the origin) and "High" to be at the far right (under 10.0) with perhaps an arrow from Low ---> High. I could specify the breaks manually, but that seems too much of a kludge.
Something like this might help,
dat <- data.frame(x = 1:10, y = 1:10)
p <- ggplot(dat, aes(x=x, y=y)) + geom_point() +
scale_x_continuous('', breaks=NULL)+
scale_y_continuous('', breaks=NULL)
g <- ggplotGrob(p)
library(gtable)
library(grid)
my_axis <- function(low="low", high="high", axis=c("x", "y"), ...){
axis <- match.arg(axis)
if(axis == "x"){
g1 <- textGrob(low, x=unit(0,"npc"), hjust=0)
g3 <- textGrob(high, x=unit(1,"npc"), hjust=1)
g2 <- segmentsGrob(grobWidth(g1) + unit(2,"mm"), unit(0.5,"npc"),
unit(1,"npc") - grobWidth(g3)- unit(2,"mm"),
unit(0.5,"npc"), ...)
} else if(axis == "y"){
g1 <- textGrob(low, y=unit(0,"npc"), rot=90, hjust=0)
g3 <- textGrob(high, y=unit(1,"npc"), rot=90, hjust=1)
g2 <- segmentsGrob(unit(0.5,"npc"),grobHeight(g1) + unit(2,"mm"),
unit(0.5,"npc"),
unit(1,"npc") - grobHeight(g3)- unit(2,"mm"),
...)
}
grobTree(g1,g2,g3)
}
g <- gtable_add_grob(g, my_axis(arrow=arrow(length=unit(2,"mm"))),
t=nrow(g)-2, b=nrow(g)-1, l=4)
g <- gtable_add_grob(g, my_axis(axis="y", arrow=arrow(length=unit(2,"mm"))),
t=3, l=1,r=3)
grid.newpage()
grid.draw(g)
As an alternative, and since you did ask for "Low ---> High", here's an option that doesn't involve disassembling the plot
dat <- data.frame(x = 1:10, y = 1:10)
low_to_high <- paste0("low ", paste0(rep("-", 35), collapse = ""), "> high")
library(ggplot2)
ggplot(dat, aes(x, y)) +
geom_point() +
labs(x = low_to_high, y = low_to_high) +
coord_equal() +
theme_bw() +
theme(axis.title = element_text(size=20),
axis.text = element_blank(),
axis.ticks = element_blank())
Admittedly, the number of - required will vary and needs to be adjusted per your plot size, and it's not as pretty as the properly rendered arrow. Still, this is quick to make from scratch.
Related
A similar question was asked here, however I cant adapt the answer to my issue.
I am trying to correctly align two legends when using cowplot. For example, if I create some data and a cowplot with two legends like so:
library(cowplot)
library(ggplot2)
# create some data
dat <- NULL
for(i in 1:20){
x <- LETTERS[1:5]
y <- paste0("var", seq(1,5))
dat[[i]] <- expand.grid(X=x, Y=y)
dat[[i]]$Z <- runif(25, 0, 1)
}
# plotting function
plotFun <- function(data){
ggplot(data, aes(X, Y, fill= Z)) +
geom_tile() +
theme(aspect.ratio = 1,
legend.justification = c(0,1),
axis.text.x=element_blank(),
axis.ticks.x=element_blank(),
axis.text.y=element_blank(),
axis.ticks.y=element_blank()) +
xlab("") + ylab("")
}
# set up to plot on a grid
allPlots <- lapply(dat, plotFun)
allPlotsAlter <- lapply(allPlots, function(x) x + theme(legend.position = "none"))
n <- length(allPlotsAlter)
nRow <- floor(sqrt(n))
plotGrid <- gridExtra::arrangeGrob(grobs=allPlotsAlter, nrow=nRow)
# create a different type of legend
newPlot <- ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, fill = Species)) +
geom_bar(stat = 'identity') + theme(legend.justification = c(0,1))
# get both legends and combine
legend <- cowplot::get_legend(allPlots[[1]])
legend1 <- cowplot::get_legend(newPlot)
combineLegend <- cowplot::plot_grid(
legend,
legend1,
nrow = 2)
# now make plot
cowplot::plot_grid(plotGrid,
combineLegend,
rel_widths = c(0.9, 0.11),
ncol = 2)
That creates this type of plot:
As you can see, the two legends have quite a bit of vertical space between them and they are not centred with the plot.
Is there a way to align the two legends so they look something like this:
I'm not sure if it is possible using cowplot... or is there a way to maybe use ggplot's annotate to place the legends?
I would probably go for patchwork, as Stefan suggests, but within cowplot you probably need to adjust the legend margins:
theme_margin <- theme(legend.box.margin = margin(100, 10, 100, 10))
legend <- cowplot::get_legend(allPlots[[1]] + theme_margin)
legend1 <- cowplot::get_legend(newPlot + theme_margin)
combineLegend <- cowplot::plot_grid(
legend,
legend1,
nrow = 2)
# now make plot
cowplot::plot_grid(plotGrid,
combineLegend,
rel_widths = c(0.9, 0.11),
ncol = 2)
If switching to another package is an option for you I would suggest to use patchwork to glue your plots together. One feature offered by patchwork is that using plot_spacer you could easily add some empty panels above and below your legends to "move" them to the center and thereby getting rid of the empty space. Depending on your final result or the height of your final plot you probably have to play a bit around with the heights and/or widths arguments:
library(cowplot)
library(ggplot2)
library(patchwork)
set.seed(123)
# create some data
dat <- NULL
for (i in 1:20) {
x <- LETTERS[1:5]
y <- paste0("var", seq(1, 5))
dat[[i]] <- expand.grid(X = x, Y = y)
dat[[i]]$Z <- runif(25, 0, 1)
}
# plotting function
plotFun <- function(data) {
ggplot(data, aes(X, Y, fill = Z)) +
geom_tile() +
theme(
aspect.ratio = 1,
legend.justification = c(0, 1),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.y = element_blank(),
axis.ticks.y = element_blank()
) +
labs(x = NULL, y = NULL)
}
# set up to plot on a grid
allPlots <- lapply(dat, plotFun)
allPlotsAlter <- lapply(allPlots, function(x) x + theme(legend.position = "none"))
n <- length(allPlotsAlter)
nRow <- floor(sqrt(n))
plotGrid <- wrap_plots(grobs = allPlotsAlter, nrow = nRow)
# create a different type of legend
newPlot <- ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, fill = Species)) +
geom_bar(stat = "identity") +
theme(legend.justification = c(0, 1))
# get both legends and combine
legend <- cowplot::get_legend(allPlots[[1]])
legend1 <- cowplot::get_legend(newPlot)
combineLegend <- plot_spacer() + legend + legend1 + plot_spacer() + plot_layout(ncol = 1, heights = c(.5, 1, 1, .5))
wrap_elements(plotGrid) + combineLegend + plot_layout(widths = c(4, 1))
I am trying to combine two FACETED ggplot objects with coord_equal() using cowplot::plot_grid() or egg::ggarrange() and vertically align them.
The egg::ggarrange() approach works fine for UNFACETED plots, with the solution posted here.
However, the egg::ggarrange() solution breaks down when faceting is included. The plots are correctly aligned, but the units of the y-axes are twice as large as those of the x-axes. Any suggestions for how to generalize this for faceting?
dat1 <- data.frame(x = rep(1:10, 2), y = 1:20, z = rep(c("A", "B"), 10))
dat2 <- data.frame(x = 1:10, y = 1:10, z = rep(c("A", "B"), 5))
plot1 <- ggplot(dat1, aes(x=x, y=y)) +
geom_point() + coord_equal() + facet_wrap(~z)
plot2 <- ggplot(dat2, aes(x=x, y=y)) +
geom_point() + coord_equal() + facet_wrap(~z)
egg::ggarrange(plot1, plot2, ncol = 1)
it seems to be a simple fix,
library(egg)
b <- body(gtable_frame)
b[6] <- parse(text="if (fixed_ar) {
ar <- as.numeric(g$heights[tt[1]]) / as.numeric(g$widths[ll[1]])
height <- width * (ar / length(ll))
g$respect <- FALSE
}")
body(gtable_frame) <- b
assignInNamespace("gtable_frame", gtable_frame, ns = 'egg')
The main problem is that plot1 and plot2 have different aspect ratios.
This is plot1:
And this plot2:
You can try to keep the aspect ratio using, i.e. theme(aspect.ratio=1) instead of coord_equal():
require(ggplot2)
dat1 <- data.frame(x = rep(1:10, 2), y = 1:20, z = rep(c("A", "B"), 10))
dat2 <- data.frame(x = 1:10, y = 1:10, z = rep(c("A", "B"), 5))
plot1 <- ggplot(dat1, aes(x=x, y=y)) + geom_point() + theme(aspect.ratio=1)+
facet_wrap(~z)
plot2 <- ggplot(dat2, aes(x=x, y=y)) + geom_point() + theme(aspect.ratio=1)+
facet_wrap(~z)
egg::ggarrange(plot1, plot2, ncol = 1,heights = c(1,10))
Hope it serves.
I would like plot a line below a ggplot2 graph with text above it, something like this:
where the starting and ending point of the gene on the x axis can be specified.
My attempt so far:
require(ggplot2)
require(grid)
require(gridExtra)
data = data.frame(y = -log10(runif(100)), x = 1:100)
p = ggplot(data=data, aes(x, y)) + geom_point()
p = p + theme(plot.margin=unit(c(1, 1, 5, 1), "lines"))
t1 = textGrob("Gene1")
p1 = p + annotation_custom(grob=t1, xmin=0, ymin=0, xmax = 3, ymax=-.1)
print(p1)
which gives:
If I try to move the text down by adjusting ymax, then it disappears.
In my answer, I have changed a couple things:
1 - I changed the name of your data to "df", as "data" can cause confusion between objects and arguments.
2 - I removed the extra panel space around the main data plot, so that the annotation wasn't so far away.
require(ggplot2)
require(grid)
require(gridExtra)
# make the data
df <- data.frame(y = -log10(runif(100)), x = 1:100)
p <- ggplot(data=df, aes(x, y)) + geom_point()
# remove this line of code:
# p <- p + theme(plot.margin=unit(c(1, 1, 5, 1), "lines"))
# set up the plot theme for the annotation
blank_axes_and_thin_margin <- theme(axis.text = element_text(color="white"),
axis.title = element_text(color="white"),
axis.ticks = element_blank(),
panel.grid = element_blank(),
panel.border = element_blank(),
plot.margin=unit(c(0, 2, 0,2),"mm"))
# define the position of the arrow (you would change this part)
arrow_start <- min(df$x)
arrow_end <- mean(c(min(df$x), max(df$x)))
arrow_height <- 1
# here's the rectangle with the arrow
t2 <- ggplot(df, aes(x,y))+
theme_bw()+
geom_rect(aes(xmin=min(x), xmax = max(x)),
ymin=0, ymax=4,fill="gray50")+
coord_cartesian(ylim=c(0,4))+
annotate(geom="text", label="Gene1",
x=20, y=2, size=6, color="black")+
geom_segment(x=arrow_start, xend=arrow_end,
y=arrow_height, yend=arrow_height,
color="black", arrow=arrow(ends="both"))+
blank_axes_and_thin_margin
t2
# arrange the graphic objects here
# I use arrangeGrob because it allows you to use ggsave(), unlike grid.arrange
plot_both <- arrangeGrob(p, t2, nrow=2, heights=unit(c(0.75,0.25), "null"))
plot_both
# ta-da !
you can turn clipping off,
g <- ggplotGrob(p1)
g$layout$clip[g$layout$name == "panel"] <- "off"
grid.newpage()
grid.draw(g)
I'm interested in trying to create simple corner labels for a multipanel figure I am preparing in ggplot. This is similar to this previously asked question, but the answers only explained how to include a label at the top of the plot, not produce a corner label in the format required by many journals. I hope to replicate something similar to the plotrix function corner.label() in ggplot2.
Here is an example using plottrix of what I would like to recreate in ggplot2.
require(plotrix)
foo1<-rnorm(50,25,5)
foo2<-rpois(50,25)
foo3<-rbinom(50,25,0.5)
foo4<-rnbinom(50,25,0.5)
par(mfrow=c(2,2))
hist(foo1)
corner.label(label='a',figcorner=T)
hist(foo2)
corner.label(label='b',figcorner=T)
hist(foo3)
corner.label(label='c',figcorner=T)
hist(foo4)
corner.label(label='d',figcorner=T)
This produces the following:
Thanks for any help in advance!
Two recent changes have made this a lot easier:
The latest release of ggplot2 has added the tag caption which can be used to label subplots.
The package patchwork makes it really easy to plot multiple ggplot objects. https://github.com/thomasp85/patchwork
This means that no altering of grobs is required. Adapting the reproducible example provided by Kev:
library(ggplot2)
# install.package("patchwork")
library(patchwork)
a <- 1:20
b <- sample(a, 20)
c <- sample(b, 20)
d <- sample(c, 20)
mydata <- data.frame(a, b, c, d)
myplot1 <- ggplot(mydata, aes(x=a, y=b)) + geom_point() + labs(tag = "A")
myplot2 <- ggplot(mydata, aes(x=b, y=c)) + geom_point() + labs(tag = "B")
myplot3 <- ggplot(mydata, aes(x=c, y=d)) + geom_point() + labs(tag = "C")
myplot4 <- ggplot(mydata, aes(x=d, y=a)) + geom_point() + labs(tag = "D")
myplot1 + myplot2 + myplot3 + myplot4
Extension: Changing Style:
If you want to change the labelling style, you can either set this individually for each plot or set a theme default. I would recommend the second approach. Add the following line before you build your plots to make the font bold and blue
ggplot2::theme_update(plot.tag = element_text(face = "bold", colour = "blue"))
For more information on customising the theme of ggplot2, see here.
I had the same problem and came up with the following solution, which is a bit different:
loading r packages
library(ggplot2)
library(grid)
library(gridExtra)
example data
a <- 1:20
b <- sample(a, 20)
c <- sample(b, 20)
d <- sample(c, 20)
create a data frame
mydata <- data.frame(a, b, c, d)
create example plots
myplot1 <- ggplot(mydata, aes(x=a, y=b)) + geom_point()
myplot2 <- ggplot(mydata, aes(x=b, y=c)) + geom_point()
myplot3 <- ggplot(mydata, aes(x=c, y=d)) + geom_point()
myplot4 <- ggplot(mydata, aes(x=d, y=a)) + geom_point()
set corner labels
myplot1 <- arrangeGrob(myplot1, top = textGrob("A", x = unit(0, "npc")
, y = unit(1, "npc"), just=c("left","top"),
gp=gpar(col="black", fontsize=18, fontfamily="Times Roman")))
myplot2 <- arrangeGrob(myplot2, top = textGrob("B", x = unit(0, "npc")
, y = unit(1, "npc"), just=c("left","top"),
gp=gpar(col="black", fontsize=18, fontfamily="Times Roman")))
myplot3 <- arrangeGrob(myplot3, top = textGrob("C", x = unit(0, "npc")
, y = unit(1, "npc"), just=c("left","top"),
gp=gpar(col="black", fontsize=18, fontfamily="Times Roman")))
myplot4 <- arrangeGrob(myplot4, top = textGrob("D", x = unit(0, "npc")
, y = unit(1, "npc"), just=c("left","top"),
gp=gpar(col="black", fontsize=18, fontfamily="Times Roman")))
plotting all plots on one page
grid.arrange(myplot1, myplot2, myplot3, myplot4, ncol = 2)
An example:
d <- data.frame(x = runif(16),
y = runif(16),
grp = rep(letters[1:4],each = 4))
ggplot(d,aes(x = x,y = y)) +
facet_wrap(~grp) +
geom_point() +
theme(strip.text = element_text(hjust = -0.05),
strip.background = element_blank())
Here's a solution using a custom labeller function. This doesn't invovle any manipulations to the data. Currently it only works with 1-dimensional facets (facet_wrap). I'm still working on how to increment along a 2-D grid...
Define the labeller function
make_labelstring <- function(mypanels) {
mylabels <- sapply(mypanels,
function(x) {LETTERS[which(mypanels == x)]})
return(mylabels)
}
label_panels <- ggplot2::as_labeller(make_labelstring)
Pass label_panels as the labeller to facet_wrap
library(ggplot2)
data("diamonds")
# create a faceted plot
ggplot(data = diamonds, aes(x = depth, y = price)) +
geom_point() +
facet_wrap(~cut, labeller = label_panels) +
theme(strip.text = element_text(hjust = -0),
strip.background = element_blank())
I realize that the align.plots function from the ggExtra package has been deprecated and removed. However, I am using my own version as it seems to provide the specific functionality I need. I have looked into faceting to solve my problem but I don't think it will work for my particular issue. What seems to be the problem is that the top-to-bottom images don't align when I use coord_equal on one of them. This doesn't seem to affect left-to-right though. Here is a simplified (or at least as simple as I can make it) version of what I am trying to achieve.
Create some dummy data frames:
source('https://raw.github.com/jbryer/multilevelPSA/master/r/align.R')
require(psych)
df = data.frame(x=rnorm(100, mean=50, sd=10),
y=rnorm(100, mean=48, sd=10),
group=rep(letters[1:10], 10))
dfx = describe.by(df$x, df$group, mat=TRUE)[,c('group1', 'mean', 'n', 'min', 'max')]
names(dfx) = c('group', 'x', 'x.n', 'x.min', 'x.max')
dfy = describe.by(df$y, df$group, mat=TRUE)[,c('group1', 'mean', 'n', 'min', 'max')]
names(dfy) = c('group', 'y', 'y.n', 'y.min', 'y.max')
df2 = cbind(dfx, dfy[,2:ncol(dfy)])
range = c(0,100)
This will setup the three plots:
p1a = ggplot(df2, aes(x=x, y=y, colour=group)) + geom_point() +
opts(legend.position='none') +
scale_x_continuous(limits=range) + scale_y_continuous(limits=range)
p1 = p1a + coord_equal(ratio=1)
p2 = ggplot(df, aes(x=x, y=group, colour=group)) + geom_point() +
scale_x_continuous(limits=range) + opts(legend.position='none')
p3 = ggplot(df, aes(x=group, y=y, colour=group)) + geom_point() +
scale_y_continuous(limits=range) + opts(legend.position='none')
The alignment top to bottom does not work with coord_equal
grid_layout <- grid.layout(nrow=2, ncol=2, widths=c(1,2), heights=c(2,1))
grid.newpage()
pushViewport( viewport( layout=grid_layout, width=1, height=1 ) )
align.plots(grid_layout, list(p1, 1, 2), list(p3, 1, 1), list(p2, 2, 2))
Broken Plot http://bryer.org/alignplots1.png
The fix is to add respect=TRUE to the grid.layout call:
grid_layout <- grid.layout(nrow=2, ncol=2, widths=c(1,2), heights=c(2,1), respect=TRUE)
But if I don't use coord_equal the alignment works fine:
grid_layout <- grid.layout(nrow=2, ncol=2, widths=c(1,2), heights=c(2,1))
grid.newpage()
pushViewport( viewport( layout=grid_layout, width=1, height=1 ) )
align.plots(grid_layout, list(p1a, 1, 2), list(p3, 1, 1), list(p2, 2, 2))
Working Plot http://bryer.org/alignplots2.png
ggplot2 now has ggplotGrob(), which may help with this.
First, we need to update the code used to generate the plots:
p1a = ggplot(df2, aes(x=x, y=y, colour=group)) + geom_point() +
scale_x_continuous(limits=range) + scale_y_continuous(limits=range)
p1 = p1a + coord_equal(ratio=1) + theme_minimal() + theme(legend.position='none')
p2 = ggplot(df, aes(x=x, y=group, colour=group)) + geom_point() +
scale_x_continuous(limits=range) + theme_minimal() + theme(legend.position='none')
p3 = ggplot(df, aes(x=group, y=y, colour=group)) + geom_point() +
scale_y_continuous(limits=range) + theme_minimal() + theme(legend.position='none')
p4 <- ggplot(df, aes(x = group, y = y)) +
geom_blank() +
theme(line = element_blank(),
rect = element_blank(),
text = element_blank(),
title = element_blank())
p4 will be blank; we just need the grob to draw.
Then we load the grid package and draw the grobs in a list arranged in rows and columns using cbind() and rbind().
library(grid)
grid.newpage()
grid.draw(
cbind(
rbind(ggplotGrob(p3), ggplotGrob(p4), size = "first"),
rbind(ggplotGrob(p1), ggplotGrob(p2), size = "first"),
size = "first"))
I'm not sure if this method will let you plot p3 in a different width and p2 in a different height, as you have them in the original example; I normally need a grid of similarly-sized graphs, and haven't needed to figure out different sizes.
This answer is partially based on partially based on https://stackoverflow.com/a/17463184/393354
Here is an example:
m <- matrix(c(3, 1, 0, 2), 2, byrow = T)
lay <- gglayout(m, widths = c(1, 3), heights = c(3, 1))
ggtable(p1, p2, p3, layout = lay)
you can use this by
install.packages('devtools')
library(devtools)
dev_mode()
install_github("ggplot2", "kohske", "cutting-edge")
library(ggplot2)
note that this branch is experimental, so maybe there are bugs.
To solve the problem using the align.plots method, specify respect=TRUE on the layout call:
grid_layout <- grid.layout(nrow=2, ncol=2, widths=c(1,2), heights=c(2,1), respect=TRUE)