R placing and scaling subplots / images - r

I've prepared a plot and two zoom areas, but am having problems inserting the zooms in the space underneath.
This is the main plot with some white space before the legend to insert the zoom plots:
I first thought of using subplot from the Hmisc package, but couldn't work out how to scale the inserts down to 30%.
Another option might be to just import the png images of all plots and then use the grid package to scale and place them, but I haven't tried this yet.
Any ideas?

Since you already have three plots - I've prepared a plot and two zoom areas
I was trying to quickly wrap up three pictures contains zoom relation.. but the whole idea is to show you how to use viewport to arrange several plots.
library(ggplot2)
library(grid)
data_x <- 5:10
data_y <- 6:11
a <- qplot(data_x, data_y, xlim=c(0, 15), ylim=c(0, 15), size=data_x)
b <- qplot(data_x, data_y, xlim=c(5, 10), ylim=c(5, 10), size=data_x) + theme(legend.position="none")
c <- qplot(data_y, data_y, xlim=c(7.5, 9.5), ylim=c(7.5, 10.5), size=data_x) + theme(legend.position="none")
vpb <- viewport(width = 0.3,
height = 0.3,
x = 0.3,
y = 0.8)
vpc <- viewport(width = 0.3,
height = 0.3,
x = 0.6,
y = 0.3)
# print and overlap
print(a)
print(b, vp = vpb)
print(c, vp = vpc)

Related

How can I add a logo to a ggplot visualisation?

I'm currently working on a ggplot column chart and I'm trying to add a logo to the bottom right. This is the code to the chart:
df <- data.frame(Names = c("2001", "2004", "2008", "2012", "2018"),
Value = c(47053, 68117, 171535, 241214, 234365))
p <- ggplot(df, aes(x = Names, y = Value)) +
geom_col(fill = "#DB4D43") + theme_classic() +
geom_text(aes(label = Value, y = Value + 0.05),
position = position_dodge(0.9),
vjust = 0)
I followed this tutorial I found online, but for some reason, it won't let me adjust the size of the logo and it ends up looking too small no matter what I type on the image_scale function.
img <- image_read("Logo.png")
img <- image_scale(img,"200")
img <- image_scale(img, "x200")
g <- rasterGrob(img)
size = unit(4, "cm")
heights = unit.c(unit(1, "npc") - size,size)
widths = unit.c(unit(1, "npc") - size, size)
lo = grid.layout(2, 2, widths = widths, heights = heights)
grid.show.layout(lo)
grid.newpage()
pushViewport(viewport(layout = lo))
pushViewport(viewport(layout.pos.row=1:1, layout.pos.col = 1:2))
print(p, newpage=FALSE)
popViewport()
pushViewport(viewport(layout.pos.row=2:2, layout.pos.col = 2:2))
print(grid.draw(g), newpage=FALSE)
popViewport()
g = grid.grab()
grid.newpage()
grid.draw(g)
rm(list=ls())
I found another tutorial and, after trying this, it doesn't show anything at all when I run it.
mypng <- readPNG('Logo.png')
print(mypng)
logocomp <- p + annotation_raster(mypng, ymin = 4.5,ymax= 5,xmin = 30,xmax = 35)
You could use the cowplot package to easily add an image to any plot made with ggplot. I used the R logo as the image that needs to be added to the plot (using magick package to read it). One advantage of using cowplot is that you can easily specify the size and position of both the plot and the image.
library(cowplot)
library(magick)
img <- image_read("Logo.png")
# Set the canvas where you are going to draw the plot and the image
ggdraw() +
# Draw the plot in the canvas setting the x and y positions, which go from 0,0
# (lower left corner) to 1,1 (upper right corner) and set the width and height of
# the plot. It's advisable that x + width = 1 and y + height = 1, to avoid clipping
# the plot
draw_plot(p,x = 0, y = 0.15, width = 1, height = 0.85) +
# Draw image in the canvas using the same concept as for the plot. Might need to
# play with the x, y, width and height values to obtain the desired result
draw_image(img,x = 0.85, y = 0.02, width = 0.15, height = 0.15)
Try using grid.raster, something like:
grid::grid.raster(img, x = 0.15, y = 0.05, width = unit(0.5, 'inches'))
x and y to define location of the image.
Adjust the number in unit() to resize the plot.

Left-aligning ggplot when saved while using a fixed aspect ratio

I'm building a custom ggplot theme to standardize the look & feel of graphs I produce. The goal is more complex than this minimal example, so I'm looking for a general solution. I have a few key goals:
I want all graphs to export at the same size (3000 pixels wide, 1500 pixels high).
I want to control the aspect ratio of the plot panel itself.
I want to use textGrobs to include figure numbers.
I want the image to be left-aligned
The challenge I'm facing is that when combining these two constraints, the image that gets saved centers the ggplot graph within the window, which makes sense as a default, but looks bad in this case.
I'm hoping there's a general solution to left-align the ggplot panel when I export. Ideally, this will also work similarly for faceted graphs.
It seems that something should be possible using one of or some combination of the gridExtra, gtable, cowplot, and egg packages, but after experimenting for a few hours I'm at a bit of a loss. Does anybody know how I can accomplish this? My code is included below.
This is the image that gets produced. As you can see, the caption is left-aligned at the bottom, but the ggplot itself is horizontally centered. I want the ggplot graph left-aligned as well.
Graph output: https://i.stack.imgur.com/5EM2c.png
library(ggplot2)
# Generate dummy data
x <- paste0("var", seq(1,10))
y <- LETTERS[1:10]
data <- expand.grid(X=x, Y=y)
data$Z <- runif(100, -2, 2)
# Generate heatmap with fixed aspect ratio
p1 <- ggplot(data, aes(X, Y, fill= Z)) +
geom_tile() +
labs(title = 'A Heatmap Graph') +
theme(aspect.ratio = 1)
# A text grob for the footer
figure_number_grob <- grid::textGrob('Figure 10',
x = 0.004,
hjust = 0,
gp = grid::gpar(fontsize = 10,
col = '#01A184'))
plot_grid <- ggpubr::ggarrange(p1,
figure_number_grob,
ncol = 1,
nrow = 2,
heights = c(1,
0.05))
# save it
png(filename = '~/test.png', width = 3000, height = 1500, res = 300, type = 'cairo')
print(plot_grid)
dev.off()
I was able to find a solution to this that works for my needs, though it does feel a bit hacky.
Here's the core idea:
Generate the plot without a fixed aspect ratio.
Split the legend from the plot as its own component
Use GridExtra's arrangeGrob to combine the plot, a spacer, the legend, and another spacer horizontally
Set the width of the plot to some fraction of npc (normal parent coordinates), in this case 0.5. This means that the plot will take up 50% of the horizontal space of the output file.
Note that this is not exactly the same as setting a fixed aspect ratio for the plot. If you know the size of the output file, it's close to the same thing, but the size of axis text & axis titles will affect the output aspect ratio for the panel itself, so while it gets you close, it's not ideal if you need a truly fixed aspect ratio
Set the width of the spacers to the remaining portion of the npc (in this case, 0.5 again), minus the width of the legend to horizontally center the legend in the remaining space.
Here's my code:
library(ggplot2)
# Generate dummy data
x <- paste0("var", seq(1,10))
y <- LETTERS[1:10]
data <- expand.grid(X=x, Y=y)
data$Z <- runif(100, -2, 2)
# Generate heatmap WITHOUT fixed aspect ratio. I address this below
p1 <- ggplot(data, aes(X, Y, fill= Z)) +
geom_tile() +
labs(title = 'A Heatmap Graph')
# Extract the legend from our plot
legend = gtable::gtable_filter(ggplotGrob(p1), "guide-box")
plot_output <- gridExtra::arrangeGrob(
p1 + theme(legend.position="none"), # Remove legend from base plot
grid::rectGrob(gp=grid::gpar(col=NA)), # Add a spacer
legend, # Add the legend back
grid::rectGrob(gp=grid::gpar(col=NA)), # Add a spacer
nrow=1, # Format plots in 1 row
widths=grid::unit.c(unit(0.5, "npc"), # Plot takes up half of width
(unit(0.5, "npc") - legend$width) * 0.5, # Spacer width
legend$width, # Legend width
(unit(0.5, "npc") - legend$width) * 0.5)) # Spacer width
# A text grob for the footer
figure_number_grob <- grid::textGrob('Figure 10',
x = 0.004,
hjust = 0,
gp = grid::gpar(fontsize = 10,
col = '#01A184'))
plot_grid <- ggpubr::ggarrange(plot_output,
figure_number_grob,
ncol = 1,
nrow = 2,
heights = c(1,
0.05))
# save it
png(filename = '~/test.png', width = 3000, height = 1500, res = 300, type = 'cairo')
print(plot_grid)
dev.off()
And here's the output image: https://i.stack.imgur.com/rgzFy.png

create gradient filled arrow in base R

I am looking for a way to add an arrow to a plot in base R, such that the arrow will be filled with a grey-gradient color, like this:
I have seen this solution, but that seems quite complex and not that flexible: I need to draw a great mahy arrows, in a great many plots, all potentially with different length and width.
I am aware of the shape package, but that seems to only be able to fill arrowheads, and does not provide a fillable "base" of the arrow.
Any suggestions?
Here is one way to get you started, along the lines of #MrFlick's suggestion. You probably want to encapsulate this inside a function that will allow you to exert more influence over the size of the arrowhead, the width of the base and arrow head, the smoothness of the gradient, etc.
#empty box
plot(c(-1, 2), c(-1, 10), ,type="n",axes=FALSE, xlab = "", ylab = "")
# plot the arrow, without a fill
polygon(c(0,0,-.25,.5,1.25,1,1,0), y = c(0,6,6, 8,6,6,0,0), border = NA)
# create gradient colors
nslices = 100
cols <- colorRampPalette(colors = c("white", "black"))(nslices)
# split the base of the arrow in nslices and fill each progressively
ys <- seq(0,6, len = nslices + 1)
for (i in 1:nslices) {
polygon(c(0,0,1,1), c(ys[i], ys[i+1], ys[i+1], ys[i]), col = cols[i], border = NA)
}
# add a filled arrowhead
polygon(c(-.25, .5, 1.25, -.25), c(6, 8, 6, 6), col = "black")
This would get you an arrow like this:
HTH, Peter
using the arrow defined in the linked question, now in base graphics
# create a black arrow, saved as external file
library(grid)
png("mask.png")
grid.polygon(c(-0.06, 0.06, 0.06, 0.15, 0, -0.15, -0.06),
c(-5, -5, 2.5, 2, 5, 2, 2.5), gp=gpar(fill="black"),
def="native",
vp=viewport(xs=c(-0.15, 0.15), ys=c(-5, 5)))
dev.off()
## read back in as colour matrix
library(png)
m <- readPNG("mask.png", native=FALSE)
mask <- matrix(rgb(m[,,1],m[,,2],m[,,3]),
nrow=nrow(m))
rmat <- matrix(grey(seq(0,1,length=nrow(m))),
nrow=nrow(m), ncol=ncol(m))
rmat[mask == "#FFFFFF"] <- NA
## use in base plot
set.seed(12321)
plot(1:10, rnorm(10))
rasterImage(rmat, 2, -1, 2.5, 0)
Edit:
you don't have to use a temporary file to create the mask, it's just (much more) convenient than fiddling with logical matrices. Here's a starting point to create the arrow directly as a matrix,
marrow <- function(nr=500, nc=300, col = grey(seq(0, 1, length=nr))){
skin <- matrix(col, nrow=nr, ncol=nc)
head <- lower.tri(matrix(TRUE, nrow=nc/2, ncol=nc/2))
skull <- cbind(head[seq(nc/2,1),], head[seq(nc/2,1),seq(nc/2,1)])
rib <- matrix(TRUE, nrow=nr-nrow(skull), ncol=nc/4)
trunk <- cbind(rib, !rib, !rib, rib)
skeleton <- rbind(skull, trunk)
skin[skeleton] <- NA_character_
skin
}
grid.newpage()
grid.raster(marrow(),
width = unit(1,"npc"),
height=unit(1,"npc"))

Add filename or other annotation to ggplot figures

I use ggplot to make most of my graphics. These can be single panels, or faceted. To make it easier to track revisions, I would like to generate a small label in the corner of the plot that includes some text.
In pseudo code, I am looking for something like this:
# generate the initial plot
p <- ggplot()
# add the label
p + someAnnotationFunction(label = "Version 1.0", x = 1, y = 0,
hjust = "right", vjust = "bottom" )
# print
print(p)
Or: plot my label nestled in the lower right corner of my figure without messing up the existing ggplot graphics.
So far I'm not having any luck finding a solution. This (very interesting) method doesn't work if you have a full m x n table of facets. Methods using gridExtra tend to mess with the plots too much. So, does anyone have a way to add arbitrary text anywhere on a plot that was generated using ggplot?
Here's a worked solution using gridExtra(), based on Baptiste's comment:
require("ggplot2")
require("gridExtra")
# set our working directory
working.dir <- '/Users/aclifton/Documents/projects/Rcode'
setwd(working.dir)
# create a data frame
df <- data.frame(x =runif(100, 1, 10),
y = runif(100, 1, 10))
#create a plot
p <- ggplot(data = df,
aes(x = x,
y = y)) +
geom_point()
print(p)
We now have our plot, and the trick is adding that label and saving the overall plot using ggsave():
# label to show
sub.label = textGrob("Some kind of label",
gp=gpar(fontsize=6),
x = unit(1, "npc"),
hjust = 1,
vjust = 0)
ggsave(filename=file.path(working.dir,'DemoPlot.png'),
plot = arrangeGrob(p,
sub = sub.label,
clip = FALSE),
scale = 1,
width = 6.5,
height = 3.5,
units = c("in"),
dpi = 300)
Which gives you this:
By making a data frame of your annotations, you can add them on top of your plot using geom_text.
note <- data.frame(xVarName = c(1, 5), yVarName = c(1, 10),
text = c("Version 1.0", "April 26, 2014")
p + geom_text(data = anno, aes(label = text))
"Version 1.0" will show up in the bottom left and "April 26, 2014" will show up in the top right.
By making your notes in a separate dataframe, you can add multiple notes to one graph if desired.

increase margins between venn.diagramm plots within grid.layout

I'm trying to display 4 venn.diagramm plots in a grid. I need to increase the space between diagrams in order that my legends don't overlap with other diagrams (or be outside of the plots).
I tried to do so by playing with margin in the function venn.diagram but this lead to increase the distance between the diagrams and their respective subtitles (what is not good).
I have seen some questions related to mine (e.g. controlling the inner figure margin within grid.layout) but they didn't work in my case.
Here is my code:
library(VennDiagram)
library(grid)
library(gridBase)
library(lattice)
# data
l1 <- list(Deletion=1:1420, Insertion=967:2042)
l2 <- list(Deletion=1:502, Insertion=324:660)
l3 <- list(Deletion=1:142, Insertion=85:184)
l4 <- list(Deletion=1:161, Insertion=22:217)
venns <- list(Subtargets=l1, Targets=l2, Genes=l3, Promoters=l4)
# set up grid layout
gl <- grid.layout(nrow=2, ncol=2)
# setup viewports
vp.1 <- viewport(layout.pos.col=1, layout.pos.row=1)
vp.2 <- viewport(layout.pos.col=2, layout.pos.row=1)
vp.3 <- viewport(layout.pos.col=1, layout.pos.row=2)
vp.4 <- viewport(layout.pos.col=2, layout.pos.row=2)
# init layout
pushViewport(viewport(layout=gl))
for (i in 1:4){
# access the relevant viewport
vp <- paste("vp.", i, sep="")
pushViewport(get(vp))
# draw the venn diagram
temp <- venn.diagram(venns[[i]], fill = c("red", "green"), alpha = c(0.5, 0.5),
cex = 1,cat.fontface = 2, lty =2, filename = NULL, sub=names(venns)[i],
margin = 0.5, sub.pos = c(0.5, 0.78), sub.col="blue")
# plot the venn diagram on the viewport
grid.draw(temp)
# done with this viewport
popViewport()
}
Any idea? Maybe by increasing the margins between viewports without changing the parameters within wiewport?
I met this problem before and my solution is to create more grids.
gl <- grid.layout(nrow=3, ncol=3, widths = c(1, 0.2, 1), heights = c(1, 0.2, 1))
grid.show.layout(gl)
Then you can plot your venn diagram on the corner grids.

Resources