Inserting an image as a facet in ggplot2 - r

I'm using ggplot2 to produce faceted plots in a function (i.e., the number of facets varies). For each plot that I've produced, I'd like to insert one image (e.g. imported jpg) in a free facet at the end (i.e., ggplot2 has to calculate the facet_wrap design in a way that foresees the existence of an additional facet, but this facet would be filled with a non-ggplot2 image afterwards). Here is an MWE of a faceted plot, in this case I'd like to insert an image as the 4th facet:
p <- ggplot(mtcars, aes(wt, mpg))
p + geom_point() + facet_wrap(~cyl, ncol = 2)

library(ggplot2)
library(png)
img <- readPNG(system.file("img", "Rlogo.png", package="png"))
p <- ggplot(mtcars, aes(wt, mpg)) + geom_point() +
facet_wrap(~cyl, ncol = 2)
g <- ggplotGrob(p)
library(gtable)
g <- gtable_add_grob(g, rasterGrob(img), nrow(g)-4, ncol(g)-2)
grid.newpage()
grid.draw(g)

baptiste's solution was throwing an error for me; I had to direct some of the grid functions to the {grid} package, the code below should overcome this. Though I am not seeing the R logo appear.
library(ggplot2)
library(png)
img <- readPNG(system.file("img", "Rlogo.png", package="png"))
p <- ggplot(mtcars, aes(wt, mpg)) + geom_point() +
facet_wrap(~cyl, ncol = 2)
g <- ggplotGrob(p)
library(gtable)
g <- gtable_add_grob(g, grid::rasterGrob(img), nrow(g)-4, ncol(g)-2)
grid::grid.newpage()
grid::grid.draw(g)

Related

How to align grob with ggplot using ggplotGrob and annotation_custom?

I am creating a grob from a ggplot using ggplotGrob and then adding it as a background layer in a complex ggplot construction using annotation_custom since the performance is much improved in a facet_wrap plot with a large dataset. However I am unable to align the underlying grob correctly with the ggplot.
This simple example shows the issue I am trying to solve.
library(ggplot2)
p <- ggplot(mtcars, aes(wt, mpg)) +
geom_point(color = "red")
ggplot(mtcars, aes(wt, mpg)) +
annotation_custom(grob = ggplotGrob(p)) +
geom_point()
I want the plot with red points to be perfectly underneath the plot with black points.
You'd probably want to grab just the panel without axis, margins etc. before adding it as a custom annotation then. In example below, I made the red points larger so you can see that they overlap.
library(ggplot2)
p <- ggplot(mtcars, aes(wt, mpg)) +
geom_point(color = "red", size = 3)
grab_panel <- function(p) {
gt <- ggplotGrob(p)
layout <- gt$layout
is_panel <- which(layout$name == "panel")[[1]]
i <- layout$t[is_panel]
j <- layout$l[is_panel]
gt[i,j]
}
ggplot(mtcars, aes(wt, mpg)) +
annotation_custom(grob = grab_panel(p)) +
geom_point()
Created on 2021-03-20 by the reprex package (v1.0.0)

ggsave() options for grid arrangements in ggplot

ggsave() doesn't seem to work with the grid package (see below). How do I save this combination of plot p1 and plot p2. The following code only save the last plot p2 that ggplot() sees.
library(tidyverse)
p1 <- ggplot(mpg, aes(fl)) + geom_bar()
p2 <- ggplot(mpg, aes(cty, hwy)) + geom_col()
grid.newpage()
grid.draw(rbind(ggplotGrob(p1), ggplotGrob(p2), size = "last"))
ggsave("mpg.png")
Consider using gridExtra. As explained in this vignette, gridExtra, building off of gtable (a higher-level layout scheme), provides more facility in arranging multiple grobs on a page, while grid package provides low-level functions to create graphical objects (grobs).
library(ggplot2)
library(gridExtra)
p1 <- ggplot(mpg, aes(fl)) + geom_bar()
p2 <- ggplot(mpg, aes(cty, hwy)) + geom_col()
p <- grid.arrange(p1, p2)
ggsave(plot=p, filename="myPlot.png")
I think you can do something like this.
#plotFile
g1=file.path(HomeDir,plotFile)
f1=grid.arrange(p1,p2, ncol=2, top=textGrob("Multiple Plots", gp=gpar(fontsize=12, font = 2))) #arranges plots within grid
g <- arrangeGrob(f1) #generates g
#save
ggsave(g1, g,width = 29.7, height = 21, units = 'cm') #saves g
You have to assign the new combination first then use ggsave() to print it.
# here I name it to_print
to_print <- rbind(ggplotGrob(p1), ggplotGrob(p2), size = "last")
ggsave(filename = "mpg.png", plot = to_print)
hope this helps!

Adding a logo to Multi plot output in R or ggplot2

I have trying to add a logo to the output derived from grid.arrange or arrangeGrob.
I have the below code:
library(ggplot2)
p1 <- ggplot(ChickWeight, aes(x=Time, y=weight, colour=Diet, group=Chick)) +
geom_line() +
ggtitle("Growth curve for individual chicks")
p2 <- ggplot(ChickWeight, aes(x=Time, y=weight, colour=Diet)) +
geom_point(alpha=.3) +
geom_smooth(alpha=.2, size=1) +
ggtitle("Fitted growth curve per diet")
p3 <- ggplot(subset(ChickWeight, Time==21), aes(x=weight, colour=Diet))
+ geom_density() +
ggtitle("Final weight, by diet")
p4 <- ggplot(subset(ChickWeight, Time==21), aes(x=weight, fill=Diet)) +
geom_histogram(colour="black", binwidth=50) +
ggtitle("Final weight, by diet")
I have used grid.arrange(p1,p2,p3,p4,ncol=2,clip=4) to put multiple plots to a single plot.
But I am having issue while inserting a logo to the above grid.arrange output.
I tried the below method, but got the below error message.
b <- rasterGrob(img, width=unit(5,"cm"), x = unit(40,"cm"))
z1 <- ggplotGrob(grid.arrange(p1,p2,p3,p4,ncol=2,clip=4))
z1<- gtable_add_grob(z1,b, t=1,l=1, r=5)
grid.newpage()
grid.draw(z1)
Error: No layers in plot
Is there a way or method to add a logo to the output after arrangeGrob or grid.arrange.
Not a gtable answer, but this is a slightly different way to add the logo that might help
library(ggplot2)
library(grid)
library(png)
library(gridExtra)
# Read png
img <- readPNG(system.file("img", "Rlogo.png", package="png"), FALSE)
# Create grobs to add to plot
my_g <- grobTree(rectGrob(gp=gpar(fill="black")),
textGrob("Some text", x=0, hjust=0, gp=gpar(col="white")),
rasterGrob(img, x=1, hjust=1))
# Plot
p <- ggplot(mtcars , aes(wt , mpg)) +
geom_line() +
theme(plot.margin=unit(c(1, 1, 1,1), "cm"))
# Add as a strip along top
grid.arrange(my_g, arrangeGrob(p,p,p,p, ncol=2), heights=c(1, 9))

How can I change the size of the strip on facets in a ggplot?

Is there any way to control the size of the strips on facets in a ggplot? I tried using strip.background=element_rect(size=n) but as far as I can tell it didn't actually do anything. Is this even possible?
converting the plot to a gtable manually lets you tweak the strip height,
library(ggplot2)
library(gtable)
d <- ggplot(mtcars, aes(x=gear)) +
geom_bar(aes(y=gear), stat="identity", position="dodge") +
facet_wrap(~cyl)
g <- ggplotGrob(d)
g$heights[[3]] = unit(1,"in")
grid.newpage()
grid.draw(g)

Separate y-axis labels by facet OR remove legend but keep the space

Ok, I'm stumped on a home-brew ggplot.
What I would like to do is have a three row, one column faceted plot with a different y-axis label for each facet. The units of the y-axis are all the same. This would be the most convenient, but googling tells me it may not be possible.
Alternatively, I found this solution using grid.arrange, which seems like it will work. However, I want to keep a legend only for one plot and remove it from the other two, but maintain the spacing as if it were still there so that everything lines up nice. Someone had the same problem a few years ago, but the suggested solution is depreciated and I can't sort out how to make it work in modern ggplot.
Any help is appreciated! Using facets would be easiest!
Edited to add copy of plot after using user20560's gridArrange solution below. Very nearly there, just would like to get back the box around the top and bottom facet panels!
I have assumed (possibly wrongly) that you are wanting to add separate y-axis titles rather than axis labels. [If it is the labels you want different you can use the scales argument in facet_grid]
There will be a ggplot way to do this but here are a couple of ways you could tweak the grobs yourself.
So using mtcars dataset as example
library(ggplot2)
library(grid)
library(gridExtra)
One way
p <- ggplot(mtcars, aes(mpg, wt, col=factor(vs))) + geom_point() +
facet_grid(gear ~ .)
# change the y axis labels manually
g <- ggplotGrob(p)
yax <- which(g$layout$name=="ylab")
# define y-axis labels
g[["grobs"]][[yax]]$label <- c("aa","bb", "cc")
# position of labels (ive just manually specified)
g[["grobs"]][[yax]]$y <- grid::unit(seq(0.15, 0.85, length=3),"npc")
grid::grid.draw(g)
Or using grid.arrange
# Create a plot for each level of grouping variable and y-axis label
p1 <- ggplot(mtcars[mtcars$gear==3, ], aes(mpg, wt, col=factor(vs))) +
geom_point() + labs(y="aa") + theme_bw()
p2 <- ggplot(mtcars[mtcars$gear==4, ], aes(mpg, wt, col=factor(vs))) +
geom_point() + labs(y="bb") + theme_bw()
p3 <- ggplot(mtcars[mtcars$gear==5, ], aes(mpg, wt, col=factor(vs))) +
geom_point() + labs(y="cc") + theme_bw()
# remove legends from two of the plots
g1 <- ggplotGrob(p1)
g1[["grobs"]][[which(g1$layout$name=="guide-box")]][["grobs"]] <- NULL
g3 <- ggplotGrob(p3)
g3[["grobs"]][[which(g3$layout$name=="guide-box")]][["grobs"]] <- NULL
gridExtra::grid.arrange(g1,p2,g3)
If it is the axis titles you want to add I should ask why you want a different titles - can the facet strip text not do?
Following the comments by Axeman and aosmith (thank you), here's a way to do this using the facet labels using ggplot2 version 2.2.0
library(ggplot2) # From sessionInfo(): ggplot2_2.2.0
ggplot(mtcars, aes(mpg, wt, col=factor(vs))) + geom_point() +
facet_grid(gear ~ ., switch = 'y') +
theme( axis.title.y = element_blank(), # remove the default y-axis title, "wt"
strip.background = element_rect(fill = 'transparent'), # replace the strip backgrounds with transparent
strip.placement = 'outside', # put the facet strips on the outside
strip.text.y = element_text(angle=180)) # rotate the y-axis text (optional)
# (see ?ggplot2::theme for a list of theme elements (args to theme()))
I know this is an old post, but after finding it, I could not get #user20560's response to work.
I've edited #user20560's grid.extra approach as follows:
library(ggplot2)
library(gridExtra)
library(grid)
# Create a plot for each level of grouping variable and y-axis label
p1 <- ggplot(mtcars[mtcars$gear==3, ], aes(mpg, wt, col=factor(vs))) +
geom_point() + labs(y="aa") + theme_bw()
p2 <- ggplot(mtcars[mtcars$gear==4, ], aes(mpg, wt, col=factor(vs))) +
geom_point() + labs(y="bb") + theme_bw()
p3 <- ggplot(mtcars[mtcars$gear==5, ], aes(mpg, wt, col=factor(vs))) +
geom_point() + labs(y="cc") + theme_bw()
# get the legend as a grob
legend <- ggplotGrob(p1)
legend <- legend$grobs[[which(legend$layout$name=="guide-box")]]
lheight <- sum(legend$height)
lwidth <- sum(legend$width)
# remove the legend from all the plots
p1 <- p1 + theme(legend.position = 'none')
p2 <- p2 + theme(legend.position = 'none')
p3 <- p3 + theme(legend.position = 'none')
# force the layout to the right side
layoutMat <- matrix(c(1,2,3,4,4,4),ncol = 2)
grid.arrange(p1,p2,p3,legend, layout_matrix = layoutMat, ncol = 2,
widths = grid::unit.c(unit(1,'npc') - lwidth, lwidth))
This example is somewhat specific to this particular layout. There is a more general approach on the ggplot2 wiki.
I too had trouble getting the first approach in the answer of user20560 (above) to work. This is probably because the internals of ggplot2 have evolved, and there is no guarantee that these internals should stay the same. In any case, here is a version that currently works:
library(ggplot2) # From sessionInfo(): ggplot2_2.1.0
library(grid)
p <- ggplot(mtcars, aes(mpg, wt, col=factor(vs))) + geom_point() + facet_grid(gear ~ .)
g <- ggplotGrob(p)
yax <- which(g$layout$name == "ylab")
g[["grobs"]][[yax]]$children[[1]]$label <- c('fo','bar','foobar')
g[["grobs"]][[yax]]$children[[1]]$y <- grid::unit(seq(0.15, 0.85, length=3), "npc")
grid.draw(g)
Note that this is the approach that keeps the facets and does not repeat the x-axes.

Resources