Arrange Microscopic images in table for publication - r

I wonder if there is an automated way to align several microscopic images together in R to appear in such form like this image (source: doi: https://doi.org/10.1101/2019.12.11.873471). I am not looking for image analysis, only grid the images and add labels. I tried with magick in which I was able to write labels on the images and with gridExtra to align them and add the labels outside. but still not satisfied because I wasn't able to control the distance between the blocks and add labels in boxes like the image above.
can anybody recommend a package to do something like this ( maybe in R or Python, not sure if Latex can do something like that also?).
below is my reproducible code and what I get.
Many thanks for your help.
library(magick)
library(grid)
library(gridExtra)
names_of_images <- LETTERS[1:16]
pic_url <- "https://imagehost.imageupload.net/2020/04/30/EXAMPLE.jpg"
pic_cat <- tempfile()
download.file(pic_url,pic_cat,mode="wb")
pic <- image_read(pic_cat)
## write labels on images
image_listall <- list()
for ( i in names_of_images ) {
im_read_bor <- image_border(pic , 'white' ,geometry = "10x10")
im_read_bor_anno <- image_annotate(im_read_bor, paste(i),
size = 100, color = "white" , location = "+50+40" )
image_listall[[i]] = im_read_bor_anno
}
## arrange images in rows
row1 <- image_append(c(image_listall$A, image_listall$B ,image_listall$C,image_listall$D ), stack = F)
row2 <- image_append(c(image_listall$E, image_listall$F ,image_listall$G,image_listall$H ), stack = F)
row3 <- image_append(c(image_listall$I, image_listall$J,image_listall$K,image_listall$L) ,stack = F)
row4 <- image_append(c(image_listall$M, image_listall$N,image_listall$O, image_listall$P) ,stack = F)
## now add row labels and title
r1 <- grid.arrange(rasterGrob(row1) , top = textGrob(
"First Block",just = "center",
gp = gpar(fontface = 'bold', fontsize = 18)) ,
left = textGrob(
"(A)",
gp = gpar(fontface = 'bold', fontsize = 15)))
r2 <- grid.arrange(rasterGrob(row2) ,
left = textGrob(
"(B)",
gp = gpar(fontface = 'bold', fontsize = 15)))
r3 <- grid.arrange(rasterGrob(row3) , top = textGrob(
"Second Block", just = "center",
gp = gpar(fontface = 'bold', fontsize = 18)) ,
left = textGrob(
"(C)",
gp = gpar(fontface = 'bold', fontsize = 15)))
r4 <- grid.arrange(rasterGrob(row4) ,
left = textGrob(
"(D)",
gp = gpar(fontface = 'bold', fontsize = 15)))
## draw all together
grid.draw(rbind(r1, r2,r3,r4, size = "last"))
it looks like this:

Maybe not the best and more elegant solution, but a possible way will be to use the function facet_grid from ggplot2 to draw multipanel plot. The advantage of using ggplot2 will be that you can benefit for a large panel of tools pretty easy to use to customize the location, the font, the color of all your labels.
So, you can prepare a fake dataframe with your text for each vertical and horizontal label:
verticallabel <- c("Control", "cKO")
horizontallabel <- c("Text1","Text2","Text3")
text <- expand.grid(verticallabel,horizontallabel)
text <- as.data.frame(text)
Var1 Var2
1 Control Text1
2 cKO Text1
3 Control Text2
4 cKO Text2
5 Control Text3
6 cKO Text3
Then, you can create an empty plot with 6 panels with both horizontal and vertical labeling as follow:
library(ggplot2)
g <- ggplot(text, aes(x = Var1, y = Var2))+geom_point(color = NA)+
facet_grid(Var1~Var2, switch = "y")+ labs(tag = "A")+
theme(strip.background = element_blank(),
strip.text = element_blank(),
axis.text = element_blank(),
axis.title = element_blank(),
panel.background = element_blank(),
axis.ticks = element_blank(),
plot.margin = margin(1,1,1,1, unit = "cm"),
plot.tag.position = c(-0.015, 1.05))+
coord_cartesian(clip = "off", ylim = c(0,5), xlim = c(0,2))+
geom_text(data = subset(text, Var1 == "Control"), aes(label = Var2, x = 1, y = 6.3, color = Var2), show.legend = FALSE,vjust = 0.5)+
geom_text(data = subset(text, Var2 == "Text1"), aes(label = Var1, x = -0.8, y = 2.5, angle = 90, vjust = 0))+
geom_rect(data = subset(text, Var1 == "Control"), aes(xmin = -Inf, xmax = Inf, ymax = 6.8, ymin = 5.8), fill = NA, color = "black")+
geom_rect(data = subset(text, Var2 == "Text1"), aes(ymin = -Inf, ymax = Inf, xmin = -1, xmax = -0.7), fill = NA, color = "black")
Then, if you want to add image on each panel, you can use the excellent solution proposed by #EdgarSantos in this post: Adding custom images to ggplot facets
annotation_custom2 <-
function (grob, xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = Inf, data){ layer(data = data, stat = StatIdentity, position = PositionIdentity,
geom = ggplot2:::GeomCustomAnn,
inherit.aes = TRUE, params = list(grob = grob,
xmin = xmin, xmax = xmax,
ymin = ymin, ymax = ymax))}
And here with some example images (some are downloaded on my computer, so you need to adapt path for your images):
library(png)
img1 = readPNG(getURLContent('https://cdn2.iconfinder.com/data/icons/animals/48/Turtle.png'))
img2 = readPNG(getURLContent('https://cdn2.iconfinder.com/data/icons/animals/48/Elephant.png'))
img3 = readPNG(getURLContent('https://cdn2.iconfinder.com/data/icons/animals/48/Hippopotamus.png'))
rlogo <- readPNG("../external-content.duckduckgo.com.png")
rstudio <- readPNG("../rstudio.png")
g +
annotation_custom2(rasterGrob(img1, width =unit(1,"npc"), height = unit(1,"npc")),
xmin = -Inf, xmax =Inf, ymin = -Inf, ymax = Inf, data = text[1,])+
annotation_custom2(rasterGrob(img2, width =unit(1,"npc"), height = unit(1,"npc")),
xmin = -Inf, xmax =Inf, ymin = -Inf, ymax = Inf, data = text[2,])+
annotation_custom2(rasterGrob(img3, width =unit(1,"npc"), height = unit(1,"npc")),
xmin = -Inf, xmax =Inf, ymin = -Inf, ymax = Inf, data = text[3,])+
annotation_custom2(rasterGrob(rlogo, width =unit(1,"npc"), height = unit(1,"npc")),
xmin = -Inf, xmax =Inf, ymin = -Inf, ymax = Inf, data = text[4,])+
annotation_custom2(rasterGrob(rstudio, width =unit(1,"npc"), height = unit(1,"npc")),
xmin = -Inf, xmax =Inf, ymin = -Inf, ymax = Inf, data = text[5,])
Does it answer your question ?

Related

How to remove border colour of geom_rect_pattern from {ggpattern}?

I want to plot two rectangles with internal gradients beside each other using ggpattern::geom_rect_pattern(pattern = "gradient") without a border around each rectangle.
Example:
library(tidyverse)
library(ggpattern)
tibble(
id = c("a", "b"),
xmin = c(-1, -1),
xmax = c(1, 1),
ymin = c(-1, 0),
ymax = c(0, 1)
) |>
ggplot() +
geom_rect_pattern(
aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, pattern_fill2 = id),
pattern_fill = "white", pattern = "gradient", pattern_orientation = "horizontal"
) +
theme_classic() +
coord_fixed(xlim = c(-1.1,1.1), ylim = c(-1.1,1.1), ratio = 1)
Which produces:
My issue is how do I remove the border around the rectangles?
Setting colour = "white" in geom_rect_pattern() will work to remove the outer border, but will introduce an internal border which is undesirable for my figure:
Setting colour = NA and/or pattern_colour = NA produces the same plot as the first
.
Is there an aesthetic I am missing here?
There seems to be a gray-filled rectGrob under the gradient fill on the finished plot, and you can just see the edges of it. If you set fill = NA this disappears.
library(tidyverse)
library(ggpattern)
tibble(
id = c("a", "b"),
xmin = c(-1, -1),
xmax = c(1, 1),
ymin = c(-1, 0),
ymax = c(0, 1)
) |>
ggplot() +
geom_rect_pattern(
aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, pattern_fill2 = id),
pattern_fill = "white", pattern = "gradient", fill = NA,
pattern_orientation = "horizontal",
) +
theme_classic() +
coord_fixed(xlim = c(-1.1,1.1), ylim = c(-1.1,1.1), ratio = 1)

How to remove border from quadrant lines in geom_point plot (ggplot2) after adding "size"

How to remove border from quadrant lines in geom_point plot (ggplot2) after adding "size"?
ggplot(
DurablesSIZE,
aes(
x = DurablesSIZE$`GDP LQ`,
y = DurablesSIZE$Slope,
color = DurablesSIZE$Sector,
size = DurablesSIZE$`2019 GDP`
)
) +
geom_point() +
geom_hline(yintercept = 0) +
geom_vline(xintercept = 1) +
xlim(0, 5.5) +
ylim(-0.26, 0.26) +
geom_rect(aes(
xmin = 1,
xmax = Inf,
ymin = 0,
ymax = Inf
),
fill = "green",
alpha = 0.03) +
geom_rect(aes(
xmin = -Inf,
xmax = 1,
ymin = -Inf,
ymax = 0
),
fill = "red",
alpha = 0.03) +
geom_rect(aes(
xmin = -Inf,
xmax = 1,
ymin = 0,
ymax = Inf
),
fill = "yellow",
alpha = 0.03) +
geom_rect(aes(
xmin = 1,
xmax = Inf,
ymin = -Inf,
ymax = 0
),
fill = "yellow",
alpha = 0.03) +
labs(y = "Slope of GDP LQ (5Y)",
x = "2019 GDP LQ",
color = "Sector",
size = "2019 GDP") +
ggtitle("Oregon Durable Manufacturing \nTargeting Potential (GDP)") +
geom_text(
aes(label = ifelse(Slope > 0 & LQ > 1, as.character(Sector), '')),
hjust = 0,
vjust = 0,
size = 2.5,
nudge_x = -0.07,
nudge_y = 0.013
) +
theme(legend.key = element_rect(colour = NA, fill = NA),
legend.box.background = element_blank())
After adding size to my points, there is a weird border around the quadrant line weird border.
The size aesthetic is being applied globally, so it is creating a thick border around each geom_rect -- it controls border width for that geom.
To remove it, take size out of the global aes mapping and use geom_point(aes(size = '2019 GDP')) + to apply it to that layer alone.
Another note: if you use geom_rect for annotation purposes, it will be plotted once for each relevant line of your data, leading to massive overplotting and minimal control of alpha. It will be better to use annotate("rect" ...) for those, or to create a separate summary table which those layers can refer to so they only plot once.
Here's some fake data I made up so that I could run your code. Please include something like this in your questions.
DurablesSIZE <- tibble(
`GDP LQ` = 0.5*(1:10),
LQ = 10:1,
Slope = 0.05*(-4:5),
Sector = rep(LETTERS[1:5], 2),
`2019 GDP` = 1:10
)
Result with original code:
Revision with size aesthetic only applied locally:

annotate ggplot above plot

I tried lately to annotate a graph with boxes above a ggplot.
Here is what I want:
I found a way using grid, but I find it too complicated, and I am quite sure there is a better way to do it, more ggplot2 friendly. Here is the example and my solution:
the data:
y2 <- 350
mesure_pol <- data.frame(x1 = c(1,4,7),
x2 = c(4,7,10),
politiquecat = c(1:3),
politique = c("Phase 1\n","Phase 2\n","Phase 3\n"),
y = c(y2,y2,y2)
)
mesure_pol$x_median <- (mesure_pol$x1 + mesure_pol$x2)/2
colorpal <- viridis::inferno(n=3,direction = -1)
plot
the main plot
p <- ggplot(data = mesure_pol) +
geom_rect(aes(xmin = x1,
xmax = x2,
ymin = 0,
ymax = 300,
fill = as.factor(politiquecat)),
fill = colorpal,
color = "black",
size = 0.3,
alpha = 0.2)+
theme(plot.margin=unit(c(60, 5.5, 5.5, 5.5), "points"))+
coord_cartesian(clip = 'off')
the annotation part
Here is the part I am not happy with:
for (i in 1:dim(mesure_pol)[1]) {
text <- textGrob(label = mesure_pol[i,"politique"], gp = gpar(fontsize=7,fontface="bold"),hjust = 0.5)
rg <- rectGrob(x = text$x, y = text$y, width = stringWidth(text$label) - unit(3,"mm") ,
height = stringHeight(text$label) ,gp = gpar(fill=colorpal[i],alpha = 0.3))
p <- p + annotation_custom(
grob = rg,
ymin = mesure_pol[i,"y"], # Vertical position of the textGrob
ymax = mesure_pol[i,"y"],
xmin = mesure_pol[i,"x_median"], # Note: The grobs are positioned outside the plot area
xmax = mesure_pol[i,"x_median"]) +
annotation_custom(
grob = text,
ymin = mesure_pol[i,"y"], # Vertical position of the textGrob
ymax = mesure_pol[i,"y"],
xmin = mesure_pol[i,"x_median"], # Note: The grobs are positioned outside the plot area
xmax = mesure_pol[i,"x_median"])
}
Is there a simplier/nicer way to obtain similar result ? I tried with annotate, label but without any luck.
An alternative approach to achieve the desired result would be to make the annotations via a second ggplot which could be glued to the main plot via e.g. patchwork.
For the annotation plot I basically used your code for the main plot, added a geom_text layer, get rid of the axix, etc. via theme_void and set the limits in line with main plot. Main difference is that I restrict the y-axis to a 0 to 1 scale. Besides that I shifted the xmin, xmax, ymin and ymax values to add some space around the rectangels (therefore it is important to set the limits).
library(ggplot2)
library(patchwork)
y2 <- 350
mesure_pol <- data.frame(x1 = c(1,4,7),
x2 = c(4,7,10),
politiquecat = c(1:3),
politique = c("Phase 1\n","Phase 2\n","Phase 3\n"),
y = c(y2,y2,y2)
)
mesure_pol$x_median <- (mesure_pol$x1 + mesure_pol$x2)/2
colorpal <- viridis::inferno(n=3,direction = -1)
p <- ggplot(data = mesure_pol) +
geom_rect(aes(xmin = x1,
xmax = x2,
ymin = 0,
ymax = 300,
fill = as.factor(politiquecat)),
fill = colorpal,
color = "black",
size = 0.3,
alpha = 0.2)
ann <- ggplot(data = mesure_pol) +
geom_rect(aes(xmin = x1 + 1,
xmax = x2 - 1,
ymin = 0.2,
ymax = 0.8,
fill = as.factor(politiquecat)),
fill = colorpal,
color = "black",
size = 0.3,
alpha = 0.2) +
geom_text(aes(x = x_median, y = .5, label = politique), vjust = .8, fontface = "bold", color = "black") +
coord_cartesian(xlim = c(1, 10), ylim = c(0, 1)) +
theme_void()
ann / p +
plot_layout(heights = c(1, 4))
By setting a second x-axis and filling the background of the new axis labels with element_markdown from the ggtext package. You may achieve this:
Here is the code:
library(ggtext)
y2 <- 350
mesure_pol <- data.frame(x1 = c(1,4,7),
x2 = c(4,7,10),
politiquecat = c(1:3),
politique = c("Phase 1\n","Phase 2\n","Phase 3\n"),
y = c(y2,y2,y2)
)
mesure_pol$x_median <- (mesure_pol$x1 + mesure_pol$x2)/2
p <- ggplot(data = mesure_pol) +
geom_rect(aes(xmin = x1,
xmax = x2,
ymin = 0,
ymax = 300,
fill = as.factor(politiquecat)),
fill = c("yellow", "red", "black"),
color = "black",
size = 0.3,
alpha = 0.2) +
scale_x_continuous(sec.axis = dup_axis(name = "",
breaks = c(2.5, 5.5, 8.5),
labels = c("Phase 1", "Phase 2", "Phase 3"))) +
theme(plot.margin=unit(c(60, 5.5, 5.5, 5.5), "points"),
axis.ticks.x.top = element_blank(),
axis.text.x.top = element_markdown(face = "bold",
size = 12,
fill = adjustcolor(c("yellow", "red", "black"),
alpha.f = .2)))+
coord_cartesian(clip = 'off')

annotation_custom size in ggplot2

Hello I used this code in order to get a ggplot figure :
ggplot(mod_mat_constraint, aes(x=Categorie, y=label)) + scale_fill_manual(values = c("#86d65e","#404040","#86d65e","#40c5e8","#e84a4a","#86d65e","#404040","#e2e2e2"), breaks=label_text) +
theme_tree2() + geom_tile(aes(fill = Value), colour = "black") + facet_wrap(~ facet) +
scale_y_discrete(limits=rev(list_tax_order))+
coord_cartesian(clip='off') +
annotation_custom(valigned,xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = Inf)+
theme(legend.position="none",plot.margin = unit(c(0, 12, 0, 0), "cm"))
so it just display a heatmap with a table next to it.
In order to add the the table I use then : annotation_custom(valigned,xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = Inf)
so I can move the table everywhere I want but I do not know how I can deal with the size of the df ?
I previously create the table with :
g1<-tableGrob(Resricted_df_heatmap3[1],rows = NULL)
g2<-tableGrob(Resricted_df_heatmap3[2],rows = NULL)
valigned <- gtable_combine(g1,g2, along=2)
valigned<- gtable_add_grob(valigned ,
grobs = rectGrob(gp = gpar(fill = NA, lwd = 3)),
t = 1, b = nrow(valigned), l = 1, r = ncol(valigned))

R ggplot2 Bar Chart with Round Corners on Top of Bar

I would like to create a ggplot2 bar chart with round corners at the top of the bars. Consider the following example data:
data <- data.frame(x = letters[1:3],
y = c(5, 1, 4))
Based on the ggchicklet package, I can draw a ggplot2 bar chart with rounded corners:
library("ggplot2")
library("ggchicklet")
ggplot(data, aes(x, y)) +
geom_chicklet(radius = grid::unit(10, 'mm'))
However, as you can see in the image, the corners are round on both sides of the bars. How could I create a ggplot2 bar chart, where only the top of the bars are round?
As #GregorThomas suggests, you probably need a bit of a hacky fix. Here's my effort:
ggplot(data, aes(x, y + 2)) +
geom_chicklet(radius = grid::unit(10, 'mm')) +
scale_y_continuous(breaks = 0:8, labels = (-2):6) +
coord_cartesian(ylim = c(2, 8)) +
geom_rect(aes(xmin = 0.5, xmax = 3.5, ymin = 0, ymax = 1.95), fill = "gray95") +
labs(y = "y")
This allows fills and outlines to be preserved:
ggplot(data, aes(x, y + 2, fill = x)) +
geom_chicklet(radius = grid::unit(10, 'mm'), colour = "black") +
scale_y_continuous(breaks = 0:8, labels = (-2):6) +
coord_cartesian(ylim = c(2, 8)) +
geom_rect(aes(xmin = 0.5, xmax = 3.5, ymin = 0, ymax = 1.95), fill = "gray95") +
labs(y = "y")
Another Hack (also described by #Gregor Thomas in the comments)
Here is another hack that avoids manipulating the scale by painting a rect over the bottom rounded corners. It works with fill (but fails on outlines):
ggplot(data, aes(x, y, fill = x)) +
geom_chicklet(radius = grid::unit(10, 'mm'), colour = NA) +
geom_col(aes(y = y / 2))
polyclip solution
Based on the hack, I cobbled together a solution that will also work with outlines. The following code creates two new geoms, geom_top_rounded_rect and geom_top_rounded_col. A top rounded rect is created by uniting the polygons from a roundrectGrob and a rectGrob with half the size (like in the hack) using gridGeometry::polyclipGrob().
geom_top_rounded_rect <- function(mapping = NULL, data = NULL,
stat = "identity", position = "identity",
radius = grid::unit(6, "pt"),
...,
na.rm = FALSE,
show.legend = NA,
inherit.aes = TRUE) {
layer(
data = data,
mapping = mapping,
stat = stat,
geom = GeomTopRoundedRect,
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list(
radius = radius,
na.rm = na.rm,
...
)
)
}
GeomTopRoundedRect <- ggplot2::ggproto(
"GeomTopRoundedRect", ggplot2::Geom,
default_aes = ggplot2::aes(
colour = NA, fill = "grey35", size = 0.5, linetype = 1, alpha = NA
),
required_aes = c("xmin", "xmax", "ymin", "ymax"),
draw_panel = function(self, data, panel_params, coord,
radius = grid::unit(6, "pt")) {
coords <- coord$transform(data, panel_params)
grobs <- lapply(1:length(coords$xmin), function(i) {
gridGeometry::polyclipGrob(
grid::roundrectGrob(
coords$xmin[i], coords$ymax[i],
width = (coords$xmax[i] - coords$xmin[i]),
height = (coords$ymax[i] - coords$ymin[i]),
r = radius,
default.units = "native",
just = c("left", "top")
),
grid::rectGrob(
coords$xmin[i], coords$ymax[i] - (coords$ymax[i] - coords$ymin[i]) / 2,
width = (coords$xmax[i] - coords$xmin[i]),
height = (coords$ymax[i] - coords$ymin[i]) / 2,
default.units = "native",
just = c("left", "top")
),
op = "union",
gp = grid::gpar(
col = coords$colour[i],
fill = alpha(coords$fill[i], coords$alpha[i]),
lwd = coords$size[i] * .pt,
lty = coords$linetype[i],
lineend = "butt"
)
)
})
grobs <- do.call(grid::gList, grobs)
ggplot2:::ggname("geom_top_rounded_rect", grid::grobTree(children = grobs))
},
draw_key = ggplot2::draw_key_polygon
)
geom_top_rounded_col <- function(mapping = NULL, data = NULL,
position = ggplot2::position_stack(reverse = TRUE),
radius = grid::unit(3, "pt"), ..., width = NULL,
na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) {
layer(
data = data, mapping = mapping, stat = "identity",
geom = GeomTopRoundedCol, position = position, show.legend = show.legend,
inherit.aes = inherit.aes, params = list(
width = width, radius = radius, na.rm = na.rm, ...
)
)
}
GeomTopRoundedCol <- ggproto(
"GeomTopRoundedCol", GeomTopRoundedRect,
required_aes = c("x", "y"),
setup_params = function(data, params) {
params$flipped_aes <- has_flipped_aes(data, params)
params
},
non_missing_aes = c("xmin", "xmax", "ymin", "ymax"),
setup_data = function(data, params) {
data$width <- data$width %||%
params$width %||% (resolution(data$x, FALSE) * 0.9)
transform(data,
ymin = pmin(y, 0), ymax = pmax(y, 0),
xmin = x - width / 2, xmax = x + width / 2, width = NULL
)
},
draw_panel = function(self, data, panel_params, coord, width = NULL, radius = grid::unit(3, "pt")) {
ggproto_parent(GeomTopRoundedRect, self)$draw_panel(data, panel_params, coord, radius = radius)
}
)
Usage:
ggplot(data, aes(x, y, fill = x)) +
geom_top_rounded_col(radius = grid::unit(10, 'mm'))
With outlines:
ggplot(data, aes(x, y, fill = x)) +
geom_top_rounded_col(radius = grid::unit(10, 'mm'), color = "black")

Resources