|
a |
|
b/R/visualization.R |
|
|
1 |
#' @importFrom ggplot2 ggproto |
|
|
2 |
NULL |
|
|
3 |
|
|
|
4 |
#### |
|
|
5 |
# Spatial plots #### |
|
|
6 |
#### |
|
|
7 |
|
|
|
8 |
#### |
|
|
9 |
## Spatial Identity Plot #### |
|
|
10 |
#### |
|
|
11 |
|
|
|
12 |
#' vrSpatialPlot |
|
|
13 |
#' |
|
|
14 |
#' Plotting identification of spatially resolved cells, spots, and ROI on associated images from multiple assays in a VoltRon object. |
|
|
15 |
#' |
|
|
16 |
#' @param object a VoltRon object |
|
|
17 |
#' @param group.by a column of metadata from \link{Metadata} used as grouping label for the spatial entities |
|
|
18 |
#' @param plot.segments plot segments from \link{vrSegments} instead of points |
|
|
19 |
#' @param group.ids a subset of categories defined in metadata column from \code{group.by} |
|
|
20 |
#' @param colors the color set for group.by. Should be of the same size of group.id (if specified) or unique elements in group.by |
|
|
21 |
#' @param n.tile should points be aggregated into tiles before visualization (see \link{geom_tile}). Applicable only for cells and molecules |
|
|
22 |
#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. |
|
|
23 |
#' if NULL, the default assay will be used, see \link{vrMainAssay}. |
|
|
24 |
#' @param graph.name if not NULL, the spatial graph is with name \code{graph.name} is visualized as well, see \link{vrGraphNames} |
|
|
25 |
#' @param graph.edge.color the colors of the graph edges, if \code{graph.name} is not NULL. |
|
|
26 |
#' @param reduction used by \code{vrSpatialPlotVitessce} to visualize an embedding alongside with the spatial plot. |
|
|
27 |
#' @param ncol column wise number of plots, for \link{ggarrange} |
|
|
28 |
#' @param nrow row wise number of plots, for \link{ggarrange} |
|
|
29 |
#' @param font.size font size |
|
|
30 |
#' @param pt.size point size |
|
|
31 |
#' @param cell.shape the shape of the points representing cells, see \link{geom_point} |
|
|
32 |
#' @param alpha alpha level of colors of visualized points and segments |
|
|
33 |
#' @param label if TRUE, the labels of the ROI assays will be visualized |
|
|
34 |
#' @param spatial the name of the main spatial system |
|
|
35 |
#' @param channel the name of the channel associated with the image |
|
|
36 |
#' @param background.color the color of plot background if a channel is not specified, or the spatial coord system doesnt have an image. |
|
|
37 |
#' @param background (DEPRECATED) the background of the plot. Either an image name, see \link{vrImageNames} or a vector of length two with image name |
|
|
38 |
#' and a channel name, see \link{vrImageChannelNames}. Type "black" or "white" for black or white backgrounds. if NULL, the main image (\link{vrMainSpatial}) |
|
|
39 |
#' and main channel (\link{vrMainChannel}) will be in the background. Otherwise the background will be grey. |
|
|
40 |
#' @param reg TRUE if registered coordinates of the main image (\link{vrMainSpatial}) is requested |
|
|
41 |
#' @param crop whether to crop an image of a spot assay to the extend of spots |
|
|
42 |
#' @param legend.pt.size the size of points at the legend |
|
|
43 |
#' @param legend.text.size the size of the text at the legend |
|
|
44 |
#' @param scale.image if TRUE, background image will be scaled down to a low resolution (width: 1000px) |
|
|
45 |
#' @param legend.loc the location of the legend, default is "right" |
|
|
46 |
#' @param common.legend whether to use a common legend for all plots, see \link{ggarrange} |
|
|
47 |
#' @param collapse.plots whether to combine all ggplots |
|
|
48 |
#' @param interactive if TRUE, run interactive plot |
|
|
49 |
#' @param shiny.options a list of shiny options (browser, host, port etc.) passed \code{options} arguement of \link{shinyApp}. For more information, see \link{runApp} |
|
|
50 |
#' |
|
|
51 |
#' @import ggplot2 |
|
|
52 |
#' @importFrom ggpubr ggarrange |
|
|
53 |
#' |
|
|
54 |
#' @export |
|
|
55 |
vrSpatialPlot <- function(object, group.by = "Sample", plot.segments = FALSE, group.ids = NULL, colors = NULL, n.tile = 0, |
|
|
56 |
assay = NULL, graph.name = NULL, graph.edge.color = "orange", reduction = NULL, ncol = 2, nrow = NULL, font.size = 2, pt.size = 2, |
|
|
57 |
cell.shape = 21, alpha = 1, label = FALSE, spatial = NULL, channel = NULL, background.color = NULL, |
|
|
58 |
background = NULL, reg = FALSE, crop = FALSE, legend.pt.size = 2, legend.text.size = 14, |
|
|
59 |
scale.image = TRUE, legend.loc = "right", common.legend = TRUE, collapse.plots = TRUE, interactive = FALSE, |
|
|
60 |
shiny.options = list()) { |
|
|
61 |
|
|
|
62 |
# check object for zarr |
|
|
63 |
if(is.character(object)){ |
|
|
64 |
if(grepl(".zarr$", object)){ |
|
|
65 |
return(vrSpatialPlotVitessce(zarr.file = object, group.by = group.by, reduction = reduction, |
|
|
66 |
shiny.options = shiny.options)) |
|
|
67 |
} |
|
|
68 |
} |
|
|
69 |
|
|
|
70 |
# check object |
|
|
71 |
if(!inherits(object, "VoltRon")) |
|
|
72 |
stop("Please provide a VoltRon object!") |
|
|
73 |
|
|
|
74 |
# sample metadata |
|
|
75 |
sample.metadata <- SampleMetadata(object) |
|
|
76 |
|
|
|
77 |
# get assay names |
|
|
78 |
assay_names <- vrAssayNames(object, assay = assay) |
|
|
79 |
|
|
|
80 |
# interactive plotting |
|
|
81 |
if(interactive){ |
|
|
82 |
if(length(assay_names) > 1){ |
|
|
83 |
stop("Only one assay can be visualized with the interactive plot at a time.") |
|
|
84 |
} else{ |
|
|
85 |
gg <- vrSpatialPlot(object, group.by = group.by, plot.segments = plot.segments, group.ids = group.ids, colors = colors, n.tile = n.tile, |
|
|
86 |
assay = assay, graph.name = graph.name, reduction = reduction, ncol = ncol, nrow = nrow, font.size = font.size, |
|
|
87 |
pt.size = pt.size, cell.shape = cell.shape, alpha = alpha, label = label, spatial = spatial, channel = channel, |
|
|
88 |
background.color = background.color, background = background, reg = reg, crop = crop, legend.pt.size = legend.pt.size, |
|
|
89 |
legend.text.size = legend.text.size, scale.image = FALSE, legend.loc = legend.loc, common.legend = common.legend, |
|
|
90 |
collapse.plots = collapse.plots, interactive = FALSE) |
|
|
91 |
return(vrSpatialPlotInteractive(plot_g = gg, shiny.options = shiny.options)) |
|
|
92 |
} |
|
|
93 |
} |
|
|
94 |
|
|
|
95 |
# get entity type and metadata |
|
|
96 |
metadata <- Metadata(object, assay = assay) |
|
|
97 |
|
|
|
98 |
# configure titles |
|
|
99 |
plot_title <- as.list(apply(sample.metadata[assay_names,], 1, function(x) paste(x["Sample"], x["Layer"], sep = ", "))) |
|
|
100 |
names(plot_title) <- assay_names |
|
|
101 |
|
|
|
102 |
# adjust group.ids |
|
|
103 |
if(!group.by %in% colnames(metadata)) |
|
|
104 |
stop("The column '", group.by, "' was not found in the metadata!") |
|
|
105 |
if(is.null(group.ids)){ |
|
|
106 |
group.ids <- unique(metadata[[group.by]]) |
|
|
107 |
} |
|
|
108 |
|
|
|
109 |
# check group.id |
|
|
110 |
levels_group.ids <- as.character(group.ids) |
|
|
111 |
if(all(!is.na(suppressWarnings(as.numeric(levels_group.ids))))){ |
|
|
112 |
levels_group.ids <- sort(as.numeric(levels_group.ids)) |
|
|
113 |
} |
|
|
114 |
group.ids <- factor(group.ids, levels = levels_group.ids) |
|
|
115 |
|
|
|
116 |
# check colors |
|
|
117 |
if(!is.null(colors)){ |
|
|
118 |
if(length(colors) != length(levels_group.ids)){ |
|
|
119 |
stop("Please provide colors whose length is equal to group.ids (if specified) or unique elements of group.by") |
|
|
120 |
} |
|
|
121 |
if(is.null(names(colors))){ |
|
|
122 |
stop("Name of each color has to be specified given as names(colors)") |
|
|
123 |
} |
|
|
124 |
} else{ |
|
|
125 |
colors <- hue_pal(length(group.ids)) |
|
|
126 |
names(colors) <- group.ids |
|
|
127 |
} |
|
|
128 |
|
|
|
129 |
# for each assay |
|
|
130 |
i <- 1 |
|
|
131 |
gg <- list() |
|
|
132 |
for(assy in assay_names){ |
|
|
133 |
|
|
|
134 |
# get assay |
|
|
135 |
cur_assay <- object[[assy]] |
|
|
136 |
cur_metadata <- subset_metadata(metadata, assays = assy) |
|
|
137 |
|
|
|
138 |
# get graph |
|
|
139 |
if(!is.null(graph.name)){ |
|
|
140 |
if(graph.name %in% vrGraphNames(object)){ |
|
|
141 |
graph <- vrGraph(object, assay = assy, graph.type = graph.name) |
|
|
142 |
} else { |
|
|
143 |
stop("the graph with named '", graph.name, "' was not found in the graph list!") |
|
|
144 |
} |
|
|
145 |
} else { |
|
|
146 |
graph <- NULL |
|
|
147 |
} |
|
|
148 |
|
|
|
149 |
# check group.by |
|
|
150 |
levels_group.by <- as.character(unique(metadata[[group.by]][!is.na(metadata[[group.by]])])) |
|
|
151 |
if(all(!is.na(suppressWarnings(as.numeric(levels_group.by))))){ |
|
|
152 |
levels_group.by <- sort(as.numeric(levels_group.by)) |
|
|
153 |
} |
|
|
154 |
cur_metadata[[group.by]] <- factor(cur_metadata[[group.by]], levels = levels_group.by) |
|
|
155 |
|
|
|
156 |
# visualize |
|
|
157 |
p_title <- plot_title[[assy]] |
|
|
158 |
gg[[i]] <- vrSpatialPlotSingle(assay = cur_assay, metadata = cur_metadata, |
|
|
159 |
group.by = group.by, plot.segments = plot.segments, group.ids = group.ids, colors = colors, n.tile = n.tile, graph = graph, graph.edge.color = graph.edge.color, font.size = font.size, |
|
|
160 |
pt.size = pt.size, alpha = alpha, cell.shape = cell.shape, plot_title = p_title, background = background, spatial = spatial, channel = channel, background.color = background.color, reg = reg, |
|
|
161 |
crop = crop, legend.pt.size = legend.pt.size, legend.text.size = legend.text.size, scale.image = scale.image) |
|
|
162 |
i <- i + 1 |
|
|
163 |
} |
|
|
164 |
|
|
|
165 |
# return a list of plots or a single one |
|
|
166 |
if(collapse.plots){ |
|
|
167 |
if(length(assay_names) > 1){ |
|
|
168 |
if(length(gg) < ncol) ncol <- length(gg) |
|
|
169 |
return(ggpubr::ggarrange(plotlist = gg, ncol = ncol, nrow = ceiling(length(gg)/ncol), common.legend = common.legend, legend = legend.loc)) |
|
|
170 |
} else { |
|
|
171 |
return(gg[[1]] + theme(legend.position=legend.loc)) |
|
|
172 |
} |
|
|
173 |
} else { |
|
|
174 |
for(i in seq_len(length(assay_names))){ |
|
|
175 |
gg[[i]] <- gg[[i]] + theme(legend.position=legend.loc) |
|
|
176 |
} |
|
|
177 |
return(gg) |
|
|
178 |
} |
|
|
179 |
} |
|
|
180 |
|
|
|
181 |
#' vrSpatialPlotSingle |
|
|
182 |
#' |
|
|
183 |
#' Plotting a single assay from a VoltRon object. We plot the identification of spatially resolved cells, spots, and ROI on associated images. |
|
|
184 |
#' |
|
|
185 |
#' @param assay vrAssay object |
|
|
186 |
#' @param metadata the metadata associated with the assay |
|
|
187 |
#' @param group.by a column of metadata from \link{Metadata} used as grouping label for the spatial entities |
|
|
188 |
#' @param plot.segments plot segments from \link{vrSegments} instead of points |
|
|
189 |
#' @param group.ids a subset of categories defined in metadata column from \code{group.by} |
|
|
190 |
#' @param colors the color set for group.by. Should be of the same size of group.id (if specified) or unique elements in group.by |
|
|
191 |
#' @param n.tile should points be aggregated into tiles before visualization (see \link{geom_tile}). Applicable only for cells and molecules |
|
|
192 |
#' @param graph if not NULL, the graph is added to the plot |
|
|
193 |
#' @param graph.edge.color the color of graph edges, if \code{graph} is not NULL. |
|
|
194 |
#' @param font.size font sizes |
|
|
195 |
#' @param pt.size point size |
|
|
196 |
#' @param cell.shape the shape of the points representing cells, see \link{geom_point} |
|
|
197 |
#' @param alpha alpha level of colors of visualized points and segments |
|
|
198 |
#' @param plot_title the title of the single plot |
|
|
199 |
#' @param spatial the name of the main spatial system |
|
|
200 |
#' @param channel the name of the channel associated with the image |
|
|
201 |
#' @param background.color the color of plot background if a channel is not specified, or the spatial coord system doesnt have an image. |
|
|
202 |
#' @param background (DEPRECATED) the background of the plot. Either an image name, see \link{vrImageNames} or a vector of length two with image name |
|
|
203 |
#' and a channel name, see \link{vrImageChannelNames}. Type "black" or "white" for black or white backgrounds. if NULL, the main image (\link{vrMainSpatial}) |
|
|
204 |
#' and main channel (\link{vrMainChannel}) will be in the background. Otherwise the background will be grey. |
|
|
205 |
#' @param reg TRUE if registered coordinates of the main image (\link{vrMainSpatial}) is requested |
|
|
206 |
#' @param crop whether to crop an image of a spot assay to the extend of spots |
|
|
207 |
#' @param legend.pt.size the size of points at the legend |
|
|
208 |
#' @param legend.text.size the size of the text at the legend |
|
|
209 |
#' @param scale.image if TRUE, background image will be scaled down to a low resolution (width: 1000px) |
|
|
210 |
#' |
|
|
211 |
#' @import ggplot2 |
|
|
212 |
#' @importFrom igraph get.data.frame |
|
|
213 |
#' @importFrom magick image_info image_read |
|
|
214 |
#' |
|
|
215 |
#' @noRd |
|
|
216 |
vrSpatialPlotSingle <- function(assay, metadata, group.by = "Sample", plot.segments = FALSE, group.ids = NULL, colors = NULL, n.tile = 0, |
|
|
217 |
graph = NULL, graph.edge.color = "orange", font.size = 2, pt.size = 2, cell.shape = 16, alpha = 1, plot_title = NULL, spatial = NULL, |
|
|
218 |
channel = NULL, background.color = NULL, background = NULL, reg = FALSE, crop = FALSE, legend.pt.size = 2, |
|
|
219 |
legend.text.size = 14, scale.image = TRUE){ |
|
|
220 |
|
|
|
221 |
# plot |
|
|
222 |
g <- ggplot() |
|
|
223 |
|
|
|
224 |
# add assay to ggplot |
|
|
225 |
g$voltron_params <- list() |
|
|
226 |
g$voltron_params$assay <- vrAssayNames(assay) |
|
|
227 |
|
|
|
228 |
# add image and background |
|
|
229 |
image <- vrSpatialPlotImage(g, assay, background, scale.image, spatial = spatial, |
|
|
230 |
channel = channel, background.color = background.color) |
|
|
231 |
g <- image$plot |
|
|
232 |
info <- image$info |
|
|
233 |
background.color <- image$background.color |
|
|
234 |
g$voltron_params$spatial_name <- spatial_name <- image$spatial |
|
|
235 |
g$voltron_params$scale_factors <- scale_factors <- image$scale_factors |
|
|
236 |
|
|
|
237 |
# coords |
|
|
238 |
coords <- vrCoordinates(assay, spatial_name = spatial_name, reg = reg) |
|
|
239 |
if(!inherits(coords, "IterableMatrix")){ |
|
|
240 |
coords <- as.data.frame(coords) |
|
|
241 |
} |
|
|
242 |
coords <- coords/scale_factors |
|
|
243 |
segments <- vrSegments(assay, spatial_name = spatial_name) |
|
|
244 |
|
|
|
245 |
# plotting features |
|
|
246 |
if(!group.by %in% colnames(metadata)) |
|
|
247 |
stop("The column '", group.by, "' was not found in the metadata!") |
|
|
248 |
if(inherits(metadata, "data.table")){ |
|
|
249 |
cur_group.by <- metadata[,get(names(metadata)[which(colnames(metadata) == group.by)])] |
|
|
250 |
names(cur_group.by) <- metadata$id |
|
|
251 |
} else { |
|
|
252 |
cur_group.by <- metadata[,group.by] |
|
|
253 |
if(!is.null(rownames(metadata))){ |
|
|
254 |
names(cur_group.by) <- rownames(metadata) |
|
|
255 |
} else { |
|
|
256 |
names(cur_group.by) <- as.vector(metadata$id) |
|
|
257 |
} |
|
|
258 |
} |
|
|
259 |
|
|
|
260 |
# group ids |
|
|
261 |
if(!is.null(group.ids)){ |
|
|
262 |
len_set_diff <- length(setdiff(group.ids, cur_group.by)) |
|
|
263 |
if(len_set_diff > 0){ |
|
|
264 |
# warning("Some groups defined in group.ids does not exist in group.by!") |
|
|
265 |
} else if(len_set_diff == length(group.ids)){ |
|
|
266 |
stop("None of the groups defined in group.ids exist in group.by!") |
|
|
267 |
} else { |
|
|
268 |
segments <- segments[cur_group.by %in% group.ids] |
|
|
269 |
} |
|
|
270 |
cur_group.by <- droplevels(cur_group.by[cur_group.by %in% group.ids]) |
|
|
271 |
coords <- coords[names(cur_group.by),] |
|
|
272 |
} |
|
|
273 |
|
|
|
274 |
# change levels of groups |
|
|
275 |
cur_group.by <- factor(cur_group.by, levels = group.ids) |
|
|
276 |
|
|
|
277 |
# add groups |
|
|
278 |
coords <- as.matrix(coords) |
|
|
279 |
coords <- data.frame(coords, cur_group.by[rownames(coords), drop = FALSE]) |
|
|
280 |
colnames(coords)[length(colnames(coords))] <- group.by |
|
|
281 |
|
|
|
282 |
# set up the limits |
|
|
283 |
g <- vrSpatialExtent(g, assay, coords, crop, info) |
|
|
284 |
|
|
|
285 |
# set colors |
|
|
286 |
names_colors <- factor(names(colors)) |
|
|
287 |
|
|
|
288 |
# visualize based on points type |
|
|
289 |
if(vrAssayTypes(assay) == "ROI"){ |
|
|
290 |
polygon_data <- NULL |
|
|
291 |
circle_data <- NULL |
|
|
292 |
for(i in seq_len(length(segments))){ |
|
|
293 |
if(nrow(segments[[i]]) > 1){ |
|
|
294 |
cur_segment <- segments[[i]][,c("x","y")] |
|
|
295 |
suppressWarnings({ |
|
|
296 |
cur_data <- as.data.frame(cbind(cur_segment, names(segments)[i], cur_group.by[i])) |
|
|
297 |
}) |
|
|
298 |
colnames(cur_data) <- c("x", "y", "segment", "group.by") |
|
|
299 |
cur_data[,c("x", "y")] <- cur_data[,c("x", "y")]/scale_factors |
|
|
300 |
polygon_data <- as.data.frame(rbind(polygon_data, cur_data)) |
|
|
301 |
} else { |
|
|
302 |
cur_segment <- segments[[i]][,c("x","y", "rx", "ry")] |
|
|
303 |
suppressWarnings({ |
|
|
304 |
cur_data <- as.data.frame(cbind(cur_segment, names(segments)[i], cur_group.by[i])) |
|
|
305 |
}) |
|
|
306 |
colnames(cur_data) <- c("x", "y", "rx", "ry", "segment", "group.by") |
|
|
307 |
cur_data[,c("x", "y","rx", "ry")] <- cur_data[,c("x", "y","rx", "ry")]/scale_factors |
|
|
308 |
circle_data <- as.data.frame(rbind(circle_data, cur_data)) |
|
|
309 |
} |
|
|
310 |
} |
|
|
311 |
if(!is.null(polygon_data)){ |
|
|
312 |
g <- g + |
|
|
313 |
geom_polygon(aes(x = x, y = y, fill = group.by, group = segment), data = polygon_data, alpha = alpha, show.legend = TRUE) |
|
|
314 |
} |
|
|
315 |
if(!is.null(circle_data)){ |
|
|
316 |
if(!requireNamespace('ggforce')) |
|
|
317 |
stop("Please install ggforce package!: install.packages('ggforce')") |
|
|
318 |
g <- g + |
|
|
319 |
ggforce::geom_ellipse(aes(x0 = as.numeric(x), y0 = as.numeric(y), a = as.numeric(rx), b = as.numeric(ry), angle = 0, |
|
|
320 |
fill = group.by, group = segment), data = circle_data, lwd = 0, alpha = alpha, show.legend = TRUE) |
|
|
321 |
} |
|
|
322 |
g <- g + |
|
|
323 |
scale_fill_manual(values = colors, labels = names_colors, drop = FALSE, limits = names_colors, |
|
|
324 |
guide = guide_legend(override.aes = list(alpha = 1))) + |
|
|
325 |
guides(fill = guide_legend(title = group.by)) |
|
|
326 |
|
|
|
327 |
# spot visualization |
|
|
328 |
} else if(vrAssayTypes(assay) == "spot"){ |
|
|
329 |
|
|
|
330 |
# rasterize if requested or needed |
|
|
331 |
if(n.tile > 0 || nrow(coords) > 50000){ |
|
|
332 |
if(n.tile == 0) |
|
|
333 |
n.tile <- 1000 |
|
|
334 |
g <- vrGroupPlotTiling(g = g, data = coords, group.by = group.by, n.tile = n.tile, alpha = alpha) |
|
|
335 |
} else { |
|
|
336 |
spot.type <- vrAssayParams(assay, param = "spot.type") |
|
|
337 |
spot.type <- ifelse(is.null(spot.type), "circle", spot.type) |
|
|
338 |
g <- g + |
|
|
339 |
geom_spot(mapping = aes(x = .data[["x"]], y = .data[["y"]], fill = .data[[group.by]]), coords, shape = 21, alpha = alpha, show.legend = TRUE, |
|
|
340 |
spot.radius = vrAssayParams(assay, param = "vis.spot.radius")/scale_factors, |
|
|
341 |
spot.type = spot.type) |
|
|
342 |
|
|
|
343 |
# add if a graph exists |
|
|
344 |
if(!is.null(graph)){ |
|
|
345 |
g <- g + addGraph(graph = graph, coords = coords, background = graph.edge.color) |
|
|
346 |
} |
|
|
347 |
} |
|
|
348 |
|
|
|
349 |
# style, color and text |
|
|
350 |
g <- g + |
|
|
351 |
scale_fill_manual(values = colors, labels = names_colors, drop = FALSE, limits = names_colors) + |
|
|
352 |
guides(fill = guide_legend(override.aes=list(shape = 21, size = 4, lwd = 0.1))) |
|
|
353 |
|
|
|
354 |
# cell visualization |
|
|
355 |
} else if(vrAssayTypes(assay) %in% c("cell", "tile")) { |
|
|
356 |
|
|
|
357 |
if(plot.segments){ |
|
|
358 |
|
|
|
359 |
if(length(segments) == 0) { |
|
|
360 |
stop("No Segments are available in this assay!") |
|
|
361 |
} else { |
|
|
362 |
polygon_data <- do.call(rbind,segments) |
|
|
363 |
polygon_data[,c("x", "y")] <- polygon_data[,c("x", "y")]/scale_factors |
|
|
364 |
len_segments <- vapply(segments, nrow, numeric(1)) |
|
|
365 |
polygon_data <- data.frame(polygon_data, segment = rep(names(segments), len_segments), group.by = rep(cur_group.by, len_segments)) |
|
|
366 |
g <- g + |
|
|
367 |
geom_polygon(aes(x = .data[["x"]], y = .data[["y"]], fill = .data[["group.by"]], group = segment), data = polygon_data, alpha = alpha, show.legend = TRUE) + |
|
|
368 |
scale_fill_manual(values = colors, labels = names_colors, drop = FALSE, limits = names_colors, name = group.by, guide = guide_legend(order = 1)) |
|
|
369 |
|
|
|
370 |
} |
|
|
371 |
} else { |
|
|
372 |
|
|
|
373 |
# rasterize if requested or needed |
|
|
374 |
if(n.tile > 0 || nrow(coords) > 50000){ |
|
|
375 |
if(n.tile == 0) |
|
|
376 |
n.tile <- 1000 |
|
|
377 |
g <- vrGroupPlotTiling(g = g, data = coords, group.by = group.by, n.tile = n.tile, alpha = alpha) |
|
|
378 |
} else { |
|
|
379 |
g <- g + |
|
|
380 |
geom_point(mapping = aes(x = .data[["x"]], y = .data[["y"]], fill = .data[[group.by]], color = .data[[group.by]]), |
|
|
381 |
coords, shape = cell.shape, size = rel(pt.size), alpha = alpha, show.legend = TRUE) |
|
|
382 |
} |
|
|
383 |
|
|
|
384 |
# style, color and text |
|
|
385 |
g <- g + |
|
|
386 |
scale_fill_manual(values = colors, labels = names_colors, drop = FALSE, limits = names_colors, name = group.by, guide = guide_legend(order = 1)) + |
|
|
387 |
scale_color_manual(values = colors, labels = names_colors, drop = FALSE, limits = names_colors, name = group.by, guide = guide_legend(order = 1)) + |
|
|
388 |
theme(legend.text=element_text(size=legend.text.size), legend.title=element_text(size=legend.text.size)) |
|
|
389 |
|
|
|
390 |
# add if a graph exists |
|
|
391 |
if(!is.null(graph)){ |
|
|
392 |
g <- g + addGraph(graph = graph, coords = coords, background = graph.edge.color) |
|
|
393 |
} |
|
|
394 |
|
|
|
395 |
} |
|
|
396 |
} else if(vrAssayTypes(assay) == "molecule") { |
|
|
397 |
|
|
|
398 |
# rasterize if requested or needed |
|
|
399 |
if(n.tile > 0 || nrow(coords) > 50000){ |
|
|
400 |
if(n.tile == 0) |
|
|
401 |
n.tile <- 1000 |
|
|
402 |
g <- vrGroupPlotTiling(g = g, data = coords, group.by = group.by, n.tile = n.tile, alpha = alpha) |
|
|
403 |
} else { |
|
|
404 |
g <- g + |
|
|
405 |
geom_point(mapping = aes(x = .data[["x"]], y = .data[["y"]], fill = .data[[group.by]], color = .data[[group.by]]), |
|
|
406 |
coords, shape = cell.shape, size = rel(pt.size), alpha = alpha, show.legend = TRUE) |
|
|
407 |
} |
|
|
408 |
g <- g + |
|
|
409 |
scale_fill_manual(values = colors, labels = names_colors, drop = FALSE, limits = names_colors, name = group.by, guide = guide_legend(order = 1)) + |
|
|
410 |
scale_color_manual(values = colors, labels = names_colors, drop = FALSE, limits = names_colors, name = group.by, guide = guide_legend(order = 1)) + |
|
|
411 |
theme(legend.text=element_text(size=legend.text.size), legend.title=element_text(size=legend.text.size)) |
|
|
412 |
|
|
|
413 |
} else { |
|
|
414 |
stop("Only ROIs, spots, cells, molecules and tiles can be visualized with vrSpatialPlot!") |
|
|
415 |
} |
|
|
416 |
|
|
|
417 |
# more visualization parameters |
|
|
418 |
g <- g + |
|
|
419 |
ggtitle(plot_title) + theme(plot.title = element_text(hjust = 0.5, margin=margin(0,0,0,0), size = legend.text.size), |
|
|
420 |
panel.grid.minor = element_blank(), panel.grid.major = element_blank(), |
|
|
421 |
axis.line=element_blank(),axis.text.x=element_blank(), axis.text.y=element_blank(), |
|
|
422 |
axis.ticks=element_blank(), axis.title.x=element_blank(), axis.title.y=element_blank(), |
|
|
423 |
legend.margin = margin(0,0,0,0)) |
|
|
424 |
|
|
|
425 |
# return data |
|
|
426 |
return(g) |
|
|
427 |
} |
|
|
428 |
|
|
|
429 |
#' addSpatialLayer |
|
|
430 |
#' |
|
|
431 |
#' adding additional layers of spatial plots to an existing \link{vrSpatialPlot}. |
|
|
432 |
#' |
|
|
433 |
#' @param g ggplot object |
|
|
434 |
#' @param object a VoltRon object |
|
|
435 |
#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. |
|
|
436 |
#' if NULL, the default assay will be used, see \link{vrMainAssay}. |
|
|
437 |
#' @param group.by a column of metadata from \link{Metadata} used as grouping label for the spatial entities |
|
|
438 |
#' @param plot.segments plot segments from \link{vrSegments} instead of points |
|
|
439 |
#' @param group.ids a subset of categories defined in metadata column from \code{group.by} |
|
|
440 |
#' @param reg TRUE if registered coordinates of the main image (\link{vrMainSpatial}) is requested |
|
|
441 |
#' @param colors the color set for group.by. Should be of the same size of group.id (if specified) or unique elements in group.by |
|
|
442 |
#' @param alpha alpha level of colors of visualized points and segments |
|
|
443 |
#' @param n.tile should points be aggregated into tiles before visualization (see \link{geom_tile}). Applicable only for cells and molecules |
|
|
444 |
#' @param pt.size point size |
|
|
445 |
#' @param cell.shape the shape of the points representing cells, see \link{geom_point} |
|
|
446 |
#' @param graph if not NULL, the graph is added to the plot |
|
|
447 |
#' @param graph.edge.color the color of graph edges, if \code{graph} is not NULL. |
|
|
448 |
#' @param spatial the name of the main spatial system |
|
|
449 |
#' |
|
|
450 |
#' @import ggplot2 |
|
|
451 |
#' |
|
|
452 |
#' @export |
|
|
453 |
addSpatialLayer <- function(g, object, assay, group.by = "Sample", plot.segments = FALSE, group.ids = NULL, reg = FALSE, colors = NULL, alpha = 1, |
|
|
454 |
n.tile = 0, pt.size = 2, cell.shape = 21, graph = NULL, graph.edge.color = "orange", spatial = NULL){ |
|
|
455 |
|
|
|
456 |
# check package |
|
|
457 |
if(!requireNamespace("ggnewscale")){ |
|
|
458 |
stop("Please install ggnewscale package for using multilayer visualization!: install_packages('ggnewscale')") |
|
|
459 |
} |
|
|
460 |
|
|
|
461 |
# sample metadata |
|
|
462 |
sample.metadata <- SampleMetadata(object) |
|
|
463 |
spatial.names <- vrSpatialNames(object, assay = "all") |
|
|
464 |
|
|
|
465 |
# get assay names |
|
|
466 |
assay_names <- vrAssayNames(object, assay = assay) |
|
|
467 |
|
|
|
468 |
# objects and parameters |
|
|
469 |
assay <- object[[assay_names]] |
|
|
470 |
adj_assay <- g$voltron_params$assay |
|
|
471 |
spatial_name <- if(is.null(spatial)) g$voltron_params$spatial_name else spatial |
|
|
472 |
scale_factors <- g$voltron_params$scale_factors |
|
|
473 |
|
|
|
474 |
# check adjacency |
|
|
475 |
adj_sample <- unique(sample.metadata[c(assay_names, adj_assay),"Sample"]) |
|
|
476 |
adj_spatial <- unique(spatial.names[c(assay_names, adj_assay),"Main"]) |
|
|
477 |
if(length(adj_sample) != 1 || length(adj_sample) != 1){ |
|
|
478 |
message("Assays in addSpatialLayer() are not adjacent to vrSpatialPlot()!") |
|
|
479 |
return(g) |
|
|
480 |
} |
|
|
481 |
|
|
|
482 |
# get entity type and metadata |
|
|
483 |
metadata <- Metadata(object, assay = assay_names) |
|
|
484 |
|
|
|
485 |
# coords |
|
|
486 |
coords <- vrCoordinates(assay, spatial_name = spatial_name, reg = reg) |
|
|
487 |
if(!inherits(coords, "IterableMatrix")){ |
|
|
488 |
coords <- as.data.frame(coords) |
|
|
489 |
} |
|
|
490 |
coords <- coords/scale_factors |
|
|
491 |
segments <- vrSegments(assay, spatial_name = spatial_name) |
|
|
492 |
|
|
|
493 |
# adjust group.ids |
|
|
494 |
if(!group.by %in% colnames(metadata)) |
|
|
495 |
stop("The column '", group.by, "' was not found in the metadata!") |
|
|
496 |
if(is.null(group.ids)){ |
|
|
497 |
group.ids <- unique(metadata[[group.by]]) |
|
|
498 |
} |
|
|
499 |
|
|
|
500 |
# check group.id |
|
|
501 |
levels_group.ids <- as.character(group.ids) |
|
|
502 |
if(all(!is.na(suppressWarnings(as.numeric(levels_group.ids))))){ |
|
|
503 |
levels_group.ids <- sort(as.numeric(levels_group.ids)) |
|
|
504 |
} |
|
|
505 |
group.ids <- factor(group.ids, levels = levels_group.ids) |
|
|
506 |
|
|
|
507 |
# check colors |
|
|
508 |
if(!is.null(colors)){ |
|
|
509 |
if(length(colors) != length(levels_group.ids)){ |
|
|
510 |
stop("Please provide colors whose length is equal to group.ids (if specified) or unique elements of group.by") |
|
|
511 |
} |
|
|
512 |
if(is.null(names(colors))){ |
|
|
513 |
stop("Name of each color has to be specified given as names(colors)") |
|
|
514 |
} |
|
|
515 |
} else{ |
|
|
516 |
colors <- hue_pal(length(group.ids)) |
|
|
517 |
names(colors) <- group.ids |
|
|
518 |
} |
|
|
519 |
|
|
|
520 |
# check group.by |
|
|
521 |
levels_group.by <- as.character(unique(metadata[[group.by]][!is.na(metadata[[group.by]])])) |
|
|
522 |
if(all(!is.na(suppressWarnings(as.numeric(levels_group.by))))){ |
|
|
523 |
levels_group.by <- sort(as.numeric(levels_group.by)) |
|
|
524 |
} |
|
|
525 |
metadata[[group.by]] <- factor(metadata[[group.by]], levels = levels_group.by) |
|
|
526 |
|
|
|
527 |
# plotting features |
|
|
528 |
if(!group.by %in% colnames(metadata)) |
|
|
529 |
stop("The column '", group.by, "' was not found in the metadata!") |
|
|
530 |
if(inherits(metadata, "data.table")){ |
|
|
531 |
cur_group.by <- metadata[,get(names(metadata)[which(colnames(metadata) == group.by)])] |
|
|
532 |
names(cur_group.by) <- metadata$id |
|
|
533 |
} else { |
|
|
534 |
cur_group.by <- metadata[,group.by] |
|
|
535 |
if(!is.null(rownames(metadata))){ |
|
|
536 |
names(cur_group.by) <- rownames(metadata) |
|
|
537 |
} else { |
|
|
538 |
names(cur_group.by) <- as.vector(metadata$id) |
|
|
539 |
} |
|
|
540 |
} |
|
|
541 |
|
|
|
542 |
# group ids |
|
|
543 |
if(!is.null(group.ids)){ |
|
|
544 |
len_set_diff <- length(setdiff(group.ids, cur_group.by)) |
|
|
545 |
if(len_set_diff > 0){ |
|
|
546 |
# warning("Some groups defined in group.ids does not exist in group.by!") |
|
|
547 |
} else if(len_set_diff == length(group.ids)){ |
|
|
548 |
stop("None of the groups defined in group.ids exist in group.by!") |
|
|
549 |
} else { |
|
|
550 |
segments <- segments[cur_group.by %in% group.ids] |
|
|
551 |
} |
|
|
552 |
cur_group.by <- droplevels(cur_group.by[cur_group.by %in% group.ids]) |
|
|
553 |
coords <- coords[names(cur_group.by),] |
|
|
554 |
} |
|
|
555 |
|
|
|
556 |
# change levels of groups |
|
|
557 |
cur_group.by <- factor(cur_group.by, levels = group.ids) |
|
|
558 |
|
|
|
559 |
# add groups |
|
|
560 |
coords <- as.matrix(coords) |
|
|
561 |
coords <- data.frame(coords, cur_group.by[rownames(coords), drop = FALSE]) |
|
|
562 |
colnames(coords)[length(colnames(coords))] <- group.by |
|
|
563 |
|
|
|
564 |
# set colors |
|
|
565 |
names_colors <- factor(names(colors)) |
|
|
566 |
|
|
|
567 |
# set new scales |
|
|
568 |
g <- g + ggnewscale::new_scale_color() |
|
|
569 |
g <- g + ggnewscale::new_scale_fill() |
|
|
570 |
|
|
|
571 |
# visualize based on points type |
|
|
572 |
if(vrAssayTypes(assay) == "ROI"){ |
|
|
573 |
polygon_data <- NULL |
|
|
574 |
circle_data <- NULL |
|
|
575 |
for(i in seq_len(length(segments))){ |
|
|
576 |
if(nrow(segments[[i]]) > 1){ |
|
|
577 |
cur_segment <- segments[[i]][,c("x","y")] |
|
|
578 |
suppressWarnings({ |
|
|
579 |
cur_data <- as.data.frame(cbind(cur_segment, names(segments)[i], cur_group.by[i])) |
|
|
580 |
}) |
|
|
581 |
colnames(cur_data) <- c("x", "y", "segment", "group.by") |
|
|
582 |
cur_data[,c("x", "y")] <- cur_data[,c("x", "y")]/scale_factors |
|
|
583 |
polygon_data <- as.data.frame(rbind(polygon_data, cur_data)) |
|
|
584 |
} else { |
|
|
585 |
cur_segment <- segments[[i]][,c("x","y", "rx", "ry")] |
|
|
586 |
suppressWarnings({ |
|
|
587 |
cur_data <- as.data.frame(cbind(cur_segment, names(segments)[i], cur_group.by[i])) |
|
|
588 |
}) |
|
|
589 |
colnames(cur_data) <- c("x", "y", "rx", "ry", "segment", "group.by") |
|
|
590 |
cur_data[,c("x", "y","rx", "ry")] <- cur_data[,c("x", "y","rx", "ry")]/scale_factors |
|
|
591 |
circle_data <- as.data.frame(rbind(circle_data, cur_data)) |
|
|
592 |
} |
|
|
593 |
} |
|
|
594 |
if(!is.null(polygon_data)){ |
|
|
595 |
g <- g + |
|
|
596 |
geom_polygon(aes(x = .data$x, y = .data$y, fill = .data[["group.by"]], group = .data[["segment"]]), data = polygon_data, alpha = alpha, col = "grey") |
|
|
597 |
} |
|
|
598 |
if(!is.null(circle_data)){ |
|
|
599 |
if(!requireNamespace('ggforce')) |
|
|
600 |
stop("Please install ggforce package!: install.packages('ggforce')") |
|
|
601 |
g <- g + |
|
|
602 |
ggforce::geom_ellipse(aes(x0 = as.numeric(x), y0 = as.numeric(y), a = as.numeric(rx), b = as.numeric(ry), angle = 0, |
|
|
603 |
fill = group.by, color = "grey", group = segment), data = circle_data, lwd = 0, alpha = alpha) |
|
|
604 |
} |
|
|
605 |
g <- g + |
|
|
606 |
scale_fill_manual(values = colors, labels = names_colors, drop = FALSE, limits = names_colors, name = group.by, |
|
|
607 |
guide = guide_legend(override.aes = list(alpha = 1), order = 2)) + |
|
|
608 |
scale_color_manual(values = colors, labels = names_colors, drop = FALSE, limits = names_colors, name = group.by, |
|
|
609 |
guide = guide_legend(override.aes = list(alpha = 1), order = 2)) |
|
|
610 |
|
|
|
611 |
} else if(vrAssayTypes(assay) %in% c("cell", "tile")) { |
|
|
612 |
|
|
|
613 |
if(plot.segments){ |
|
|
614 |
|
|
|
615 |
if(length(segments) == 0) { |
|
|
616 |
stop("No Segments are available in this assay!") |
|
|
617 |
} else { |
|
|
618 |
polygon_data <- do.call(rbind,segments) |
|
|
619 |
polygon_data[,c("x", "y")] <- polygon_data[,c("x", "y")]/scale_factors |
|
|
620 |
len_segments <- vapply(segments, nrow, numeric(1)) |
|
|
621 |
polygon_data <- data.frame(polygon_data, segment = rep(names(segments), len_segments), group.by = rep(cur_group.by, len_segments)) |
|
|
622 |
g <- g + |
|
|
623 |
geom_polygon(aes(x = .data[["x"]], y = .data[["y"]], fill = .data[["group.by"]], group = segment), data = polygon_data, alpha = alpha) + |
|
|
624 |
scale_fill_manual(values = colors, labels = names_colors, drop = FALSE, limits = names_colors, name = group.by, guide = guide_legend(order = 2)) |
|
|
625 |
} |
|
|
626 |
} else { |
|
|
627 |
|
|
|
628 |
# rasterize if requested or needed |
|
|
629 |
if(n.tile > 0 || nrow(coords) > 50000){ |
|
|
630 |
if(n.tile == 0) |
|
|
631 |
n.tile <- 1000 |
|
|
632 |
g <- vrGroupPlotTiling(g = g, data = coords, group.by = group.by, n.tile = n.tile, alpha = alpha) |
|
|
633 |
} else { |
|
|
634 |
g <- g + |
|
|
635 |
geom_point(mapping = aes(x = .data[["x"]], y = .data[["y"]], fill = .data[[group.by]], color = .data[[group.by]]), |
|
|
636 |
coords, shape = cell.shape, size = rel(pt.size), alpha = alpha) |
|
|
637 |
} |
|
|
638 |
|
|
|
639 |
# style, color and text |
|
|
640 |
g <- g + |
|
|
641 |
scale_fill_manual(values = colors, labels = names_colors, drop = FALSE, limits = names_colors, name = group.by, guide = guide_legend(order = 2)) + |
|
|
642 |
scale_color_manual(values = colors, labels = names_colors, drop = FALSE, limits = names_colors, name = group.by, guide = guide_legend(order = 2)) |
|
|
643 |
|
|
|
644 |
# add if a graph exists |
|
|
645 |
if(!is.null(graph)){ |
|
|
646 |
g <- g + addGraph(graph = graph, coords = coords, background = graph.edge.color) |
|
|
647 |
} |
|
|
648 |
|
|
|
649 |
} |
|
|
650 |
} else if(vrAssayTypes(assay) == "molecule") { |
|
|
651 |
|
|
|
652 |
# rasterize if requested or needed |
|
|
653 |
if(n.tile > 0 || nrow(coords) > 50000){ |
|
|
654 |
if(n.tile == 0) |
|
|
655 |
n.tile <- 1000 |
|
|
656 |
g <- vrGroupPlotTiling(g = g, data = coords, group.by = group.by, n.tile = n.tile, alpha = alpha) |
|
|
657 |
} else { |
|
|
658 |
g <- g + |
|
|
659 |
geom_point(mapping = aes(x = .data[["x"]], y = .data[["y"]], fill = .data[[group.by]], color = .data[[group.by]]), |
|
|
660 |
coords, shape = cell.shape, size = rel(pt.size), alpha = alpha) |
|
|
661 |
} |
|
|
662 |
g <- g + |
|
|
663 |
scale_fill_manual(values = colors, labels = names_colors, drop = FALSE, limits = names_colors, name = group.by, |
|
|
664 |
guide = guide_legend(override.aes = list(alpha = 1), order = 2)) + |
|
|
665 |
scale_color_manual(values = colors, labels = names_colors, drop = FALSE, limits = names_colors, name = group.by, |
|
|
666 |
guide = guide_legend(override.aes = list(alpha = 1), order = 2)) |
|
|
667 |
|
|
|
668 |
} else { |
|
|
669 |
stop("Only ROIs, spots, cells, molecules and tiles can be visualized with vrSpatialPlot!") |
|
|
670 |
} |
|
|
671 |
|
|
|
672 |
return(g) |
|
|
673 |
} |
|
|
674 |
|
|
|
675 |
#### |
|
|
676 |
## Spatial Feature Plot #### |
|
|
677 |
#### |
|
|
678 |
|
|
|
679 |
#' vrSpatialFeaturePlot |
|
|
680 |
#' |
|
|
681 |
#' Plotting single/multiple features of spatially resolved cells, spots, and ROI on associated images from multiple assays in a VoltRon object. |
|
|
682 |
#' |
|
|
683 |
#' @param object a VoltRon object |
|
|
684 |
#' @param features a set of features to be visualized, either from \link{vrFeatures} of raw or normalized data or columns of the \link{Metadata}. |
|
|
685 |
#' @param combine.features whether to combine all features in one plot |
|
|
686 |
#' @param group.by a column of metadata from \link{Metadata} used as grouping label for the spatial entities |
|
|
687 |
#' @param plot.segments plot segments from \link{vrSegments} instead of points |
|
|
688 |
#' @param n.tile should points be aggregated into tiles before visualization (see \link{geom_tile}). Applicable only for cells and molecules |
|
|
689 |
#' @param norm if TRUE, the normalized data is used |
|
|
690 |
#' @param log if TRUE, data features (excluding metadata features) will be log transformed |
|
|
691 |
#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. |
|
|
692 |
#' if NULL, the default assay will be used, see \link{vrMainAssay}. |
|
|
693 |
#' @param graph.name if not NULL, the spatial graph is with name \code{graph.name} is visualized as well, see \link{vrGraphNames} |
|
|
694 |
#' @param ncol column wise number of plots, for \link{ggarrange} |
|
|
695 |
#' @param nrow row wise number of plots, for \link{ggarrange} |
|
|
696 |
#' @param font.size font size |
|
|
697 |
#' @param pt.size point size |
|
|
698 |
#' @param cell.shape the shape of the points representing cells, see \link{geom_point} |
|
|
699 |
#' @param title.size title size of legend and plot |
|
|
700 |
#' @param alpha alpha level of colors of visualized points and segments |
|
|
701 |
#' @param keep.scale whether unify all scales for all features or not |
|
|
702 |
#' @param label if TRUE, labels of ROIs will be visualized too |
|
|
703 |
#' @param spatial the name of the main spatial system |
|
|
704 |
#' @param channel the name of the channel associated with the image |
|
|
705 |
#' @param background.color the color of plot background if a channel is not specified, or the spatial coord system doesnt have an image. |
|
|
706 |
#' @param background (DEPRECATED) the background of the plot. Either an image name, see \link{vrImageNames} or a vector of length two with image name |
|
|
707 |
#' and a channel name, see \link{vrImageChannelNames}. Type "black" or "white" for black or white backgrounds. if NULL, the main image (\link{vrMainSpatial}) |
|
|
708 |
#' and main channel (\link{vrMainChannel}) will be in the background. Otherwise the background will be grey. |
|
|
709 |
#' @param reg TRUE if registered coordinates of the main image (\link{vrMainSpatial}) is requested |
|
|
710 |
#' @param crop whether to crop an image of a spot assay to the extend of spots |
|
|
711 |
#' @param scale.image if TRUE, background image will be scaled down to a low resolution (width: 1000px) |
|
|
712 |
#' @param common.legend whether to use a common legend for all plots, see \link{ggarrange} |
|
|
713 |
#' @param legend.loc the location of the legend, default is "right" |
|
|
714 |
#' @param collapse.plots whether to combine all ggplots |
|
|
715 |
#' |
|
|
716 |
#' @import ggplot2 |
|
|
717 |
#' @importFrom ggpubr ggarrange |
|
|
718 |
#' |
|
|
719 |
#' @export |
|
|
720 |
#' |
|
|
721 |
vrSpatialFeaturePlot <- function(object, features, combine.features = FALSE, group.by = "label", plot.segments = FALSE, n.tile = 0, |
|
|
722 |
norm = TRUE, log = FALSE, assay = NULL, graph.name = NULL, ncol = 2, nrow = NULL, |
|
|
723 |
font.size = 2, pt.size = 2, cell.shape = 16, title.size = 10, alpha = 0.6, keep.scale = "feature", label = FALSE, |
|
|
724 |
spatial = NULL, channel = NULL, background.color = NULL, background = NULL, reg = FALSE, |
|
|
725 |
crop = FALSE, scale.image = TRUE, common.legend = FALSE, legend.loc = "right", collapse.plots = TRUE) { |
|
|
726 |
|
|
|
727 |
# check object |
|
|
728 |
if(!inherits(object, "VoltRon")) |
|
|
729 |
stop("Please provide a VoltRon object!") |
|
|
730 |
|
|
|
731 |
# sample metadata |
|
|
732 |
sample.metadata <- SampleMetadata(object) |
|
|
733 |
|
|
|
734 |
# get assay names |
|
|
735 |
assay_names <- vrAssayNames(object, assay = assay) |
|
|
736 |
|
|
|
737 |
# get entity type and metadata |
|
|
738 |
metadata <- Metadata(object, assay = assay) |
|
|
739 |
|
|
|
740 |
# calculate limits for plotting, all for making one scale, feature for making multiple |
|
|
741 |
limits <- Map(function(feat){ |
|
|
742 |
range_feat <- Map(function(assy){ |
|
|
743 |
spatialpoints <- vrSpatialPoints(object[[assy]]) |
|
|
744 |
if(feat %in% vrFeatures(object, assay = assy)){ |
|
|
745 |
data <- vrData(object[[assy]], features = feat, norm = norm) |
|
|
746 |
if(log) |
|
|
747 |
data <- log1p(data) |
|
|
748 |
# return(getRange(data, na.rm = TRUE, finite = TRUE)) |
|
|
749 |
return(getRange(data, na.rm = TRUE)) |
|
|
750 |
} else { |
|
|
751 |
metadata <- Metadata(object, assay = assy) |
|
|
752 |
if(feat %in% colnames(metadata)){ |
|
|
753 |
# return(getRange(metadata[,feat], na.rm = TRUE, finite = TRUE)) |
|
|
754 |
return(getRange(metadata[,feat], na.rm = TRUE)) |
|
|
755 |
} else { |
|
|
756 |
stop("Feature '", feat, "' cannot be found in data or metadata!") |
|
|
757 |
} |
|
|
758 |
} |
|
|
759 |
}, assay_names) |
|
|
760 |
if(keep.scale == "all"){ |
|
|
761 |
range_feat_all <- c(do.call(min, range_feat), do.call(max, range_feat)) |
|
|
762 |
range_feat <- Map(function(assy) return(range_feat_all), assay_names) |
|
|
763 |
} |
|
|
764 |
if(!keep.scale %in% c("all", "feature")){ |
|
|
765 |
stop("keep.scale should be either 'all' or 'feature', check help(vrSpatialFeaturePlot)") |
|
|
766 |
} |
|
|
767 |
return(range_feat) |
|
|
768 |
}, features) |
|
|
769 |
|
|
|
770 |
# configure titles |
|
|
771 |
assay_title <- as.list(apply(sample.metadata[assay_names,], 1, function(x) paste(x["Sample"], x["Layer"], sep = ", "))) |
|
|
772 |
names(assay_title) <- assay_names |
|
|
773 |
feature_title <- as.list(features) |
|
|
774 |
names(feature_title) <- features |
|
|
775 |
plot_title <- assay_title |
|
|
776 |
legend_title <- feature_title |
|
|
777 |
|
|
|
778 |
# for each assay |
|
|
779 |
i <- 1 |
|
|
780 |
gg <- list() |
|
|
781 |
for(assy in assay_names){ |
|
|
782 |
|
|
|
783 |
# get assay |
|
|
784 |
cur_assay <- object[[assy]] |
|
|
785 |
cur_metadata <- subset_metadata(metadata, assays = assy) |
|
|
786 |
|
|
|
787 |
p_title <- plot_title[[assy]] |
|
|
788 |
|
|
|
789 |
# get graph |
|
|
790 |
if(!is.null(graph.name)){ |
|
|
791 |
if(graph.name %in% vrGraphNames(object)){ |
|
|
792 |
graph <- vrGraph(object, assay = assy, graph.type = graph.name) |
|
|
793 |
} else { |
|
|
794 |
stop("the graph with named '", graph.name, "' was not found in the graph list!") |
|
|
795 |
} |
|
|
796 |
} else { |
|
|
797 |
graph <- NULL |
|
|
798 |
} |
|
|
799 |
|
|
|
800 |
# make one plot by combining features |
|
|
801 |
if(combine.features){ |
|
|
802 |
|
|
|
803 |
if(vrAssayTypes(cur_assay) != "cell"){ |
|
|
804 |
stop("Currently, combining features can only be used for cell based assays") |
|
|
805 |
} else { |
|
|
806 |
l_title <- "" |
|
|
807 |
gg[[i]] <- vrSpatialFeaturePlotCombined(assay = cur_assay, metadata = cur_metadata, features = features, plot.segments = plot.segments, |
|
|
808 |
n.tile = n.tile, graph = graph, group.by = group.by, norm = norm, log = log, |
|
|
809 |
font.size = font.size, pt.size = pt.size, title.size = title.size, alpha = alpha, cell.shape = cell.shape, |
|
|
810 |
label = label, plot_title = p_title, legend_title = l_title, background = background, reg = reg, crop = crop) |
|
|
811 |
} |
|
|
812 |
|
|
|
813 |
# make individual feature plots |
|
|
814 |
} else { |
|
|
815 |
|
|
|
816 |
# for each feature |
|
|
817 |
for(feat in features){ |
|
|
818 |
l_title <- legend_title[[feat]] |
|
|
819 |
gg[[i]] <- vrSpatialFeaturePlotSingle(assay = cur_assay, metadata = cur_metadata, feature = feat, plot.segments = plot.segments, |
|
|
820 |
n.tile = n.tile, graph = graph, limits = limits[[feat]][[assy]], group.by = group.by, norm = norm, log = log, |
|
|
821 |
font.size = font.size, pt.size = pt.size, title.size = title.size, alpha = alpha, cell.shape = cell.shape, |
|
|
822 |
label = label, plot_title = p_title, legend_title = l_title, spatial = spatial, channel = channel, |
|
|
823 |
background.color = background.color, background = background, reg = reg, crop = crop) |
|
|
824 |
i <- i + 1 |
|
|
825 |
} |
|
|
826 |
} |
|
|
827 |
} |
|
|
828 |
|
|
|
829 |
if(collapse.plots){ |
|
|
830 |
# return a list of plots or a single one |
|
|
831 |
if(length(features) > 1 && length(assay_names) > 1){ |
|
|
832 |
return(suppressWarnings(ggpubr::ggarrange(plotlist = gg, ncol = length(features), nrow = length(assay_names)))) |
|
|
833 |
} else if(length(features) > 1 && length(assay_names) == 1){ |
|
|
834 |
if(length(gg) < ncol) ncol <- length(gg) |
|
|
835 |
return(suppressWarnings(ggpubr::ggarrange(plotlist = gg, ncol = ncol, nrow = ceiling(length(gg)/ncol)))) |
|
|
836 |
} else if(length(features) == 1 && length(assay_names) > 1){ |
|
|
837 |
if(length(gg) < ncol) ncol <- length(gg) |
|
|
838 |
return(suppressWarnings(ggpubr::ggarrange(plotlist = gg, ncol = ncol, nrow = ceiling(length(gg)/ncol), |
|
|
839 |
common.legend = common.legend, legend = legend.loc))) |
|
|
840 |
} else { |
|
|
841 |
return(suppressWarnings(gg[[1]] + theme(legend.position=legend.loc))) |
|
|
842 |
} |
|
|
843 |
} else { |
|
|
844 |
for(i in seq_len(length(features)*length(assay_names))){ |
|
|
845 |
gg[[i]] <- gg[[i]] + theme(legend.position=legend.loc) |
|
|
846 |
} |
|
|
847 |
return(suppressWarnings(gg)) |
|
|
848 |
} |
|
|
849 |
} |
|
|
850 |
|
|
|
851 |
#' vrSpatialFeaturePlotSingle |
|
|
852 |
#' |
|
|
853 |
#' A single Spatial Feature plot of VoltRon object |
|
|
854 |
#' |
|
|
855 |
#' @param assay vrAssay object |
|
|
856 |
#' @param metadata the metadata associated with the assay |
|
|
857 |
#' @param feature a feature, either from the rows of rawdata, normdata or columns of the metadata |
|
|
858 |
#' @param plot.segments plot segments from \link{vrSegments} instead of points |
|
|
859 |
#' @param n.tile should points be aggregated into tiles before visualization (see \link{geom_tile}). Applicable only for cells and molecules |
|
|
860 |
#' @param graph if not NULL, the graph is added to the plot |
|
|
861 |
#' @param limits limits of the legend of the plot |
|
|
862 |
#' @param group.by a column of metadata from \link{Metadata} used as grouping label for the spatial entities |
|
|
863 |
#' @param norm if TRUE, the normalized data is used |
|
|
864 |
#' @param log if TRUE, data features (excluding metadata features) will be log transformed |
|
|
865 |
#' @param font.size font sizes |
|
|
866 |
#' @param pt.size point size |
|
|
867 |
#' @param cell.shape the shape of the points representing cells, see \link{geom_point} |
|
|
868 |
#' @param title.size title size of legend and plot |
|
|
869 |
#' @param alpha alpha level of colors of visualized points and segments |
|
|
870 |
#' @param label if TRUE, labels of ROIs will be visualized too |
|
|
871 |
#' @param plot_title the main title of the single plot |
|
|
872 |
#' @param legend_title the legend title of the single plot |
|
|
873 |
#' @param spatial the name of the main spatial system |
|
|
874 |
#' @param channel the name of the channel associated with the image |
|
|
875 |
#' @param background.color the color of plot background if a channel is not specified, or the spatial coord system doesnt have an image. |
|
|
876 |
#' @param background (DEPRECATED) the background of the plot. Either an image name, see \link{vrImageNames} or a vector of length two with image name |
|
|
877 |
#' and a channel name, see \link{vrImageChannelNames}. Type "black" or "white" for black or white backgrounds. if NULL, the main image (\link{vrMainSpatial}) |
|
|
878 |
#' and main channel (\link{vrMainChannel}) will be in the background. Otherwise the background will be grey. |
|
|
879 |
#' @param reg TRUE if registered coordinates of the main image (\link{vrMainSpatial}) is requested |
|
|
880 |
#' @param crop whether to crop an image of a spot assay to the extend of spots |
|
|
881 |
#' @param scale.image if TRUE, background image will be scaled down to a low resolution (width: 1000px) |
|
|
882 |
#' |
|
|
883 |
#' @import ggplot2 |
|
|
884 |
#' @importFrom ggrepel geom_label_repel |
|
|
885 |
#' @importFrom igraph get.data.frame |
|
|
886 |
#' @importFrom dplyr arrange |
|
|
887 |
#' |
|
|
888 |
#' @noRd |
|
|
889 |
vrSpatialFeaturePlotSingle <- function(assay, metadata, feature, plot.segments = FALSE, n.tile = 0, graph = NULL, limits, |
|
|
890 |
group.by = "label", norm = TRUE, log = FALSE, font.size = 2, pt.size = 2, cell.shape = 16, |
|
|
891 |
title.size = 10, alpha = 0.6, label = FALSE, plot_title = NULL, legend_title = NULL, |
|
|
892 |
spatial = NULL, channel = NULL, background.color = NULL, background = NULL, reg = FALSE, |
|
|
893 |
crop = FALSE, scale.image = TRUE){ |
|
|
894 |
|
|
|
895 |
# get image information and plotting features |
|
|
896 |
midpoint <- sum(limits)/2 |
|
|
897 |
|
|
|
898 |
# get data |
|
|
899 |
data_features <- feature[feature %in% vrFeatures(assay)] |
|
|
900 |
if(length(data_features) > 0){ |
|
|
901 |
normdata <- vrData(assay, features = feature, norm = norm) |
|
|
902 |
if(log) |
|
|
903 |
normdata <- log1p(normdata) |
|
|
904 |
} |
|
|
905 |
if(feature %in% data_features){ |
|
|
906 |
if(inherits(normdata, "IterableMatrix")){ |
|
|
907 |
cur_score <- as.matrix(normdata[feature,])[1,] |
|
|
908 |
} else { |
|
|
909 |
cur_score <- normdata[feature,] |
|
|
910 |
names(cur_score) <- colnames(normdata) |
|
|
911 |
} |
|
|
912 |
} else { |
|
|
913 |
cur_score <- metadata[,feature] |
|
|
914 |
if(!is.null(rownames(metadata))){ |
|
|
915 |
names(cur_score) <- rownames(metadata) |
|
|
916 |
} else { |
|
|
917 |
names(cur_score) <- as.vector(metadata$id) |
|
|
918 |
} |
|
|
919 |
} |
|
|
920 |
|
|
|
921 |
# plot |
|
|
922 |
g <- ggplot() |
|
|
923 |
|
|
|
924 |
# add image and background |
|
|
925 |
image <- vrSpatialPlotImage(g, assay, background, scale.image, spatial = spatial, |
|
|
926 |
channel = channel, background.color = background.color) |
|
|
927 |
g <- image$plot |
|
|
928 |
info <- image$info |
|
|
929 |
background.color <- image$background.color |
|
|
930 |
spatial_name <- image$spatial |
|
|
931 |
scale_factors <- image$scale_factors |
|
|
932 |
|
|
|
933 |
# coords |
|
|
934 |
coords <- vrCoordinates(assay, spatial_name = spatial_name, reg = reg) |
|
|
935 |
if(!inherits(coords, "IterableMatrix")){ |
|
|
936 |
coords <- as.data.frame(coords) |
|
|
937 |
} |
|
|
938 |
coords <- coords/scale_factors |
|
|
939 |
segments <- vrSegments(assay, spatial_name = spatial_name) |
|
|
940 |
|
|
|
941 |
# add score |
|
|
942 |
coords <- as.matrix(coords) |
|
|
943 |
coords <- data.frame(coords, cur_score[rownames(coords), drop = FALSE]) |
|
|
944 |
colnames(coords)[length(colnames(coords))] <- "score" |
|
|
945 |
|
|
|
946 |
# set up the limits |
|
|
947 |
g <- vrSpatialExtent(g, assay, coords, crop, info) |
|
|
948 |
|
|
|
949 |
# add points or segments |
|
|
950 |
if(vrAssayTypes(assay) == "ROI" && !is.null(segments)){ |
|
|
951 |
polygon_data <- NULL |
|
|
952 |
circle_data <- NULL |
|
|
953 |
for(i in seq_len(length(segments))){ |
|
|
954 |
cur_data <- as.data.frame(cbind(segments[[i]], names(segments)[i], coords$score[i])) |
|
|
955 |
if(nrow(segments[[i]]) > 1){ |
|
|
956 |
colnames(cur_data) <- c("id", "x", "y", "segment", "score") |
|
|
957 |
cur_data[,c("x", "y")] <- cur_data[,c("x", "y")]/scale_factors |
|
|
958 |
polygon_data <- as.data.frame(rbind(polygon_data, cur_data)) |
|
|
959 |
} else { |
|
|
960 |
colnames(cur_data) <- c("id", "x", "y", "rx", "ry", "segment", "score") |
|
|
961 |
cur_data[,c("x", "y","rx", "ry")] <- cur_data[,c("x", "y","rx", "ry")]/scale_factors |
|
|
962 |
circle_data <- as.data.frame(rbind(circle_data, cur_data)) |
|
|
963 |
} |
|
|
964 |
} |
|
|
965 |
if(!is.null(geom_polygon)){ |
|
|
966 |
g <- g + |
|
|
967 |
geom_polygon(aes(x = x, y = y, fill = score, group = segment), data = polygon_data, alpha = alpha) |
|
|
968 |
} |
|
|
969 |
if(!is.null(circle_data)){ |
|
|
970 |
if(!requireNamespace('ggforce')) |
|
|
971 |
stop("Please install ggforce package!: install.packages('ggforce')") |
|
|
972 |
g <- g + |
|
|
973 |
ggforce::geom_ellipse(aes(x0 = as.numeric(x), y0 = as.numeric(y), a = as.numeric(rx), b = as.numeric(ry), angle = 0, |
|
|
974 |
fill = score, group = segment), data = circle_data, lwd = 0, alpha = alpha) |
|
|
975 |
} |
|
|
976 |
g <- g + |
|
|
977 |
scale_fill_gradientn(name = legend_title, |
|
|
978 |
colors=c("dodgerblue2", "white", "yellow3"), |
|
|
979 |
values=rescale_numeric(c(limits[1], midpoint, limits[2])), limits = limits) |
|
|
980 |
} else if(vrAssayTypes(assay) == "spot"){ |
|
|
981 |
|
|
|
982 |
# rasterize if requested or needed |
|
|
983 |
if(n.tile > 0 || nrow(coords) > 50000){ |
|
|
984 |
if(n.tile == 0) |
|
|
985 |
n.tile <- 1000 |
|
|
986 |
g <- vrFeaturePlotTiling(g = g, data = coords, legend_title = legend_title, n.tile = n.tile, alpha = alpha, type = "spatial") |
|
|
987 |
} else { |
|
|
988 |
spot.type <- vrAssayParams(assay, param = "spot.type") |
|
|
989 |
spot.type <- ifelse(is.null(spot.type), "circle", spot.type) |
|
|
990 |
g <- g + |
|
|
991 |
geom_spot(mapping = aes(x = x, y = y, fill = score), coords, shape = 21, alpha = alpha, |
|
|
992 |
spot.radius = vrAssayParams(assay, param = "vis.spot.radius")/scale_factors, |
|
|
993 |
spot.type = spot.type) + |
|
|
994 |
scale_fill_gradientn(name = legend_title, |
|
|
995 |
colors=c("dodgerblue3", "yellow", "red"), |
|
|
996 |
values=rescale_numeric(c(limits[1], midpoint, limits[2])), limits = limits) |
|
|
997 |
} |
|
|
998 |
|
|
|
999 |
} else if(vrAssayTypes(assay) %in% c("cell", "tile")) { |
|
|
1000 |
|
|
|
1001 |
if(plot.segments){ |
|
|
1002 |
|
|
|
1003 |
if(length(segments) == 0) { |
|
|
1004 |
stop("No Segments are available in this assay!") |
|
|
1005 |
} else { |
|
|
1006 |
polygon_data <- do.call(rbind,segments) |
|
|
1007 |
polygon_data[,c("x", "y")] <- polygon_data[,c("x", "y")]/scale_factors |
|
|
1008 |
# len_segments <- sapply(segments, nrow, simplify = TRUE) |
|
|
1009 |
len_segments <- vapply(segments, nrow, numeric(1)) |
|
|
1010 |
polygon_data <- data.frame(polygon_data, segment = rep(names(segments), len_segments), score = rep(coords$score, len_segments)) |
|
|
1011 |
g <- g + |
|
|
1012 |
geom_polygon(aes(x = x, y = y, fill = score, group = segment), data = polygon_data, alpha = alpha) |
|
|
1013 |
} |
|
|
1014 |
g <- g + |
|
|
1015 |
scale_fill_gradientn(name = legend_title, |
|
|
1016 |
colors=c("dodgerblue2", "white", "yellow3"), |
|
|
1017 |
values=rescale_numeric(c(limits[1], midpoint, limits[2])), limits = limits) |
|
|
1018 |
} else { |
|
|
1019 |
|
|
|
1020 |
# rasterize if requested or needed |
|
|
1021 |
if(n.tile > 0 || nrow(coords) > 50000){ |
|
|
1022 |
if(n.tile == 0) |
|
|
1023 |
n.tile <- 1000 |
|
|
1024 |
g <- vrFeaturePlotTiling(g = g, data = coords, legend_title = legend_title, n.tile = n.tile, alpha = alpha, type = "spatial") |
|
|
1025 |
} else { |
|
|
1026 |
g <- g + |
|
|
1027 |
geom_point(mapping = aes(x = x, y = y, fill = score, color = score), dplyr::arrange(coords, score), shape = cell.shape, size = rel(pt.size), alpha = alpha) + |
|
|
1028 |
scale_colour_gradientn(name = legend_title, |
|
|
1029 |
colors=c("dodgerblue2", "white", "yellow3"), |
|
|
1030 |
values=rescale_numeric(c(limits[1], midpoint, limits[2])), limits = limits, aesthetics = c("fill", "colour")) |
|
|
1031 |
} |
|
|
1032 |
|
|
|
1033 |
# add if a graph exists |
|
|
1034 |
if(!is.null(graph)){ |
|
|
1035 |
graph.df <- igraph::get.data.frame(graph) |
|
|
1036 |
graph.df$from.x <- coords$x[match(graph.df$from, rownames(coords))] |
|
|
1037 |
graph.df$from.y <- coords$y[match(graph.df$from, rownames(coords))] |
|
|
1038 |
graph.df$to.x <- coords$x[match(graph.df$to, rownames(coords))] |
|
|
1039 |
graph.df$to.y <- coords$y[match(graph.df$to, rownames(coords))] |
|
|
1040 |
g <- g + |
|
|
1041 |
geom_segment(data = graph.df, mapping = aes(x=from.x,xend = to.x, y=from.y,yend = to.y), alpha = 0.5, color = ifelse(background == "black", "grey", "black")) |
|
|
1042 |
} |
|
|
1043 |
} |
|
|
1044 |
} else { |
|
|
1045 |
stop("Feature plotting is not possible for molecule assays!") |
|
|
1046 |
} |
|
|
1047 |
|
|
|
1048 |
# more visualization parameters |
|
|
1049 |
g <- g + |
|
|
1050 |
ggtitle(plot_title) + theme(plot.title = element_text(hjust = 0.5, margin=margin(0,0,0,0), size = title.size), |
|
|
1051 |
panel.grid.minor = element_blank(), panel.grid.major = element_blank(), |
|
|
1052 |
axis.line=element_blank(),axis.text.x=element_blank(), axis.text.y=element_blank(), |
|
|
1053 |
axis.ticks=element_blank(), axis.title.x=element_blank(), axis.title.y=element_blank(), |
|
|
1054 |
legend.key.size = unit(title.size, "points"), legend.title = element_text(size=title.size), |
|
|
1055 |
legend.margin = margin(0,0,0,0)) |
|
|
1056 |
|
|
|
1057 |
# visualize labels |
|
|
1058 |
if(label && vrAssayTypes(assay) == "ROI"){ |
|
|
1059 |
if(group.by %in% colnames(metadata)){ |
|
|
1060 |
coords[[group.by]] <- as.vector(metadata[,group.by]) |
|
|
1061 |
} else { |
|
|
1062 |
stop("The column ", group.by, " was not found in the metadata!") |
|
|
1063 |
} |
|
|
1064 |
g <- g + ggrepel::geom_label_repel(mapping = aes(x = .data[["x"]], y = .data[["y"]], label = .data[[group.by]]), coords, |
|
|
1065 |
box.padding = 0.5, size = font.size, direction = "both", seed = 1) |
|
|
1066 |
} |
|
|
1067 |
|
|
|
1068 |
# return data |
|
|
1069 |
return(g) |
|
|
1070 |
} |
|
|
1071 |
|
|
|
1072 |
#' vrSpatialFeaturePlotSingle |
|
|
1073 |
#' |
|
|
1074 |
#' A single Spatial Feature plot of VoltRon object |
|
|
1075 |
#' |
|
|
1076 |
#' @param assay vrAssay object |
|
|
1077 |
#' @param metadata the metadata associated with the assay |
|
|
1078 |
#' @param features features, either from the rows of rawdata, normdata or columns of the metadata |
|
|
1079 |
#' @param plot.segments plot segments from \link{vrSegments} instead of points |
|
|
1080 |
#' @param n.tile should points be aggregated into tiles before visualization (see \link{geom_tile}). Applicable only for cells and molecules |
|
|
1081 |
#' @param graph if not NULL, the graph is added to the plot |
|
|
1082 |
#' @param limits limits of the legend of the plot |
|
|
1083 |
#' @param group.by a column of metadata from \link{Metadata} used as grouping label for the spatial entities |
|
|
1084 |
#' @param norm if TRUE, the normalized data is used |
|
|
1085 |
#' @param log if TRUE, data features (excluding metadata features) will be log transformed |
|
|
1086 |
#' @param font.size font sizes |
|
|
1087 |
#' @param pt.size point size |
|
|
1088 |
#' @param cell.shape the shape of the points representing cells, see \link{geom_point} |
|
|
1089 |
#' @param title.size title size of legend and plot |
|
|
1090 |
#' @param alpha alpha level of colors of visualized points and segments |
|
|
1091 |
#' @param label if TRUE, labels of ROIs will be visualized too |
|
|
1092 |
#' @param plot_title the main title of the single plot |
|
|
1093 |
#' @param legend_title the legend title of the single plot |
|
|
1094 |
#' and a channel name, see \link{vrImageChannelNames}. Type "black" or "white" for black or white backgrounds. if NULL, the main image (\link{vrMainSpatial}) |
|
|
1095 |
#' and main channel (\link{vrMainChannel}) will be in the background. Otherwise the background will be grey. |
|
|
1096 |
#' @param background the background of the plot. Either an image name, see \link{vrImageNames} or a vector of length two with image name |
|
|
1097 |
#' and a channel name, see \link{vrImageChannelNames}. Type "black" or "white" for black or white backgrounds. if NULL, the main image (\link{vrMainSpatial}) |
|
|
1098 |
#' and main channel (\link{vrMainChannel}) will be in the background. Otherwise the background will be grey. |
|
|
1099 |
#' @param reg TRUE if registered coordinates of the main image (\link{vrMainSpatial}) is requested |
|
|
1100 |
#' @param crop whether to crop an image of a spot assay to the extend of spots |
|
|
1101 |
#' @param scale.image if TRUE, background image will be scaled down to a low resolution (width: 1000px) |
|
|
1102 |
#' |
|
|
1103 |
#' @import ggplot2 |
|
|
1104 |
#' @importFrom ggrepel geom_label_repel |
|
|
1105 |
#' @importFrom igraph get.data.frame |
|
|
1106 |
#' @importFrom dplyr arrange |
|
|
1107 |
#' |
|
|
1108 |
#' @noRd |
|
|
1109 |
vrSpatialFeaturePlotCombined <- function(assay, metadata, features, plot.segments = FALSE, n.tile = 0, graph = NULL, group.by = "label", norm = TRUE, log = FALSE, |
|
|
1110 |
font.size = 2, pt.size = 2, cell.shape = 16, title.size = 10, alpha = 0.6, label = FALSE, plot_title = NULL, |
|
|
1111 |
legend_title = NULL, background = NULL, reg = FALSE, crop = FALSE, scale.image = TRUE){ |
|
|
1112 |
|
|
|
1113 |
# plot |
|
|
1114 |
g <- ggplot() |
|
|
1115 |
scale_factors <- 1 |
|
|
1116 |
|
|
|
1117 |
# add image |
|
|
1118 |
info <- NULL |
|
|
1119 |
spatial_name <- vrMainSpatial(assay) |
|
|
1120 |
|
|
|
1121 |
# data |
|
|
1122 |
coords <- as.data.frame(vrCoordinates(assay, spatial_name = spatial_name, reg = reg)) |
|
|
1123 |
coords <- coords/scale_factors |
|
|
1124 |
segments <- vrSegments(assay, spatial_name = spatial_name) |
|
|
1125 |
data_features <- features[features %in% vrFeatures(assay)] |
|
|
1126 |
if(length(data_features) > 0){ |
|
|
1127 |
normdata <- vrData(assay, features = data_features, norm = norm) |
|
|
1128 |
if(log) |
|
|
1129 |
normdata <- log1p(normdata) |
|
|
1130 |
} |
|
|
1131 |
|
|
|
1132 |
# calculate limits for plotting, all for making one scale, feature for making multiple |
|
|
1133 |
limits <- Map(function(feat){ |
|
|
1134 |
if(feat %in% vrFeatures(assay)){ |
|
|
1135 |
return(range(normdata[feat, ])) |
|
|
1136 |
} else { |
|
|
1137 |
if(feat %in% colnames(metadata)){ |
|
|
1138 |
return(range(metadata[,feat])) |
|
|
1139 |
} else { |
|
|
1140 |
stop("Feature '", feat, "' cannot be found in data or metadata!") |
|
|
1141 |
} |
|
|
1142 |
} |
|
|
1143 |
}, features) |
|
|
1144 |
|
|
|
1145 |
# set up the limits |
|
|
1146 |
g <- vrSpatialExtent(g, assay, coords, crop, info) |
|
|
1147 |
|
|
|
1148 |
# configure titles |
|
|
1149 |
feature_title <- as.list(features) |
|
|
1150 |
names(feature_title) <- features |
|
|
1151 |
legend_title <- feature_title |
|
|
1152 |
|
|
|
1153 |
# for each feature |
|
|
1154 |
i <- 1 |
|
|
1155 |
gg <- list() |
|
|
1156 |
all_data <- NULL |
|
|
1157 |
colors <- get_rasterization_colors(length(features)) |
|
|
1158 |
for(feat in features){ |
|
|
1159 |
|
|
|
1160 |
# get data |
|
|
1161 |
if(feat %in% data_features){ |
|
|
1162 |
coords$score <- normdata[feat,] |
|
|
1163 |
} else { |
|
|
1164 |
coords$score <- metadata[,feat] |
|
|
1165 |
} |
|
|
1166 |
|
|
|
1167 |
# get image information and plotting features |
|
|
1168 |
midpoint <- sum(limits[[feat]])/2 |
|
|
1169 |
|
|
|
1170 |
# add points, rasterize if requested or needed |
|
|
1171 |
if(n.tile > 0 || nrow(coords) > 50000){ |
|
|
1172 |
if(n.tile == 0) |
|
|
1173 |
n.tile <- 1000 |
|
|
1174 |
g_single <- ggplot() + |
|
|
1175 |
stat_summary_2d(mapping = aes(x = x, y = y, z = score), fun = mean, data = coords, geom = "tile", bins = n.tile, drop = TRUE) + |
|
|
1176 |
scale_fill_gradientn(name = legend_title[[feat]], |
|
|
1177 |
colors=c("grey97", colors[i]), |
|
|
1178 |
values=rescale_numeric(c(limits[[feat]][1], limits[[feat]][2])), limits = limits[[feat]]) |
|
|
1179 |
all_data <- rbind(all_data, |
|
|
1180 |
data.frame(layer_data(g_single), color_group = colors[i])) |
|
|
1181 |
} else { |
|
|
1182 |
g_single <- ggplot() + |
|
|
1183 |
geom_point(mapping = aes(x = x, y = y, color = score), coords, shape = 16, size = pt.size) + |
|
|
1184 |
scale_color_gradientn(name = legend_title[[feat]], |
|
|
1185 |
colors=c("grey97", colors[i]), |
|
|
1186 |
values=rescale_numeric(c(limits[[feat]][1], limits[[feat]][2])), limits = limits[[feat]]) |
|
|
1187 |
all_data <- rbind(all_data, |
|
|
1188 |
data.frame(layer_data(g_single), value = coords$score, color_group = colors[i])) |
|
|
1189 |
} |
|
|
1190 |
|
|
|
1191 |
# add graph to list |
|
|
1192 |
gg[[i]] <- g_single |
|
|
1193 |
i <- i + 1 |
|
|
1194 |
} |
|
|
1195 |
|
|
|
1196 |
# combine feature plots |
|
|
1197 |
g <- vrSpatialFeatureCombinePlot(g, all_data, n.tile, coords, features) |
|
|
1198 |
|
|
|
1199 |
# add if a graph exists |
|
|
1200 |
if(!is.null(graph)){ |
|
|
1201 |
graph.df <- igraph::get.data.frame(graph) |
|
|
1202 |
graph.df$from.x <- coords$x[match(graph.df$from, rownames(coords))] |
|
|
1203 |
graph.df$from.y <- coords$y[match(graph.df$from, rownames(coords))] |
|
|
1204 |
graph.df$to.x <- coords$x[match(graph.df$to, rownames(coords))] |
|
|
1205 |
graph.df$to.y <- coords$y[match(graph.df$to, rownames(coords))] |
|
|
1206 |
g <- g + |
|
|
1207 |
geom_segment(data = graph.df, mapping = aes(x=from.x,xend = to.x, y=from.y,yend = to.y), alpha = 0.5, color = ifelse(background == "black", "grey", "black")) |
|
|
1208 |
} |
|
|
1209 |
|
|
|
1210 |
# more visualization parameters |
|
|
1211 |
g <- g + |
|
|
1212 |
ggtitle(plot_title) + theme(plot.title = element_text(hjust = 0.5, margin=margin(0,0,0,0), size = title.size), |
|
|
1213 |
panel.grid.minor = element_blank(), panel.grid.major = element_blank(), |
|
|
1214 |
axis.line=element_blank(),axis.text.x=element_blank(), axis.text.y=element_blank(), |
|
|
1215 |
axis.ticks=element_blank(), axis.title.x=element_blank(), axis.title.y=element_blank(), |
|
|
1216 |
legend.key.size = unit(title.size, "points"), legend.title = element_text(size=title.size), |
|
|
1217 |
legend.margin = margin(0,0,0,0)) |
|
|
1218 |
|
|
|
1219 |
# background |
|
|
1220 |
g <- g + |
|
|
1221 |
theme(panel.background = element_rect(fill = "grey97", colour = "grey97", linewidth = 0.5, linetype = "solid")) |
|
|
1222 |
|
|
|
1223 |
# return plot |
|
|
1224 |
return(g) |
|
|
1225 |
} |
|
|
1226 |
|
|
|
1227 |
#' vrSpatialFeatureCombinePlot |
|
|
1228 |
#' |
|
|
1229 |
#' A different plotting setting for vrSpatialFeaturePlot when combine.feature=TRUE |
|
|
1230 |
#' |
|
|
1231 |
#' @param g ggplot object |
|
|
1232 |
#' @param all_data summary data |
|
|
1233 |
#' @param n.tile should points be aggregated into tiles before visualization (see \link{geom_tile}). Applicable only for cells and molecules |
|
|
1234 |
#' @param datax original plotting data |
|
|
1235 |
#' @param features features |
|
|
1236 |
#' |
|
|
1237 |
#' @import ggplot2 |
|
|
1238 |
#' |
|
|
1239 |
#' @noRd |
|
|
1240 |
vrSpatialFeatureCombinePlot <- function(g, all_data, n.tile, coords, features){ |
|
|
1241 |
|
|
|
1242 |
# tiling or not |
|
|
1243 |
if(n.tile > 0 || nrow(coords) > 50000){ |
|
|
1244 |
if(n.tile == 0) |
|
|
1245 |
n.tile <- 1000 |
|
|
1246 |
all_data <- all_data %>% group_by(x,y) %>% |
|
|
1247 |
summarize(fill = fill[which.max(value)], |
|
|
1248 |
group = color_group[which.max(value)], |
|
|
1249 |
value = value[which.max(value)]) |
|
|
1250 |
key_table <- all_data[,c("fill", "group", "value")] %>% |
|
|
1251 |
dplyr::group_by(group) %>% |
|
|
1252 |
dplyr::summarise(fill = fill[which.max(value)], value = max(value)) |
|
|
1253 |
g.combined <- g + |
|
|
1254 |
ggplot2::geom_tile(data = as.data.frame(all_data), aes(x = x, y = y, fill = fill)) + |
|
|
1255 |
ggplot2::scale_fill_identity("", labels = features, breaks = key_table$fill, guide = "legend") |
|
|
1256 |
} else { |
|
|
1257 |
all_data <- all_data %>% group_by(x,y) %>% |
|
|
1258 |
dplyr::summarise(color = colour[which.max(value)], |
|
|
1259 |
group = color_group[which.max(value)], |
|
|
1260 |
value = value[which.max(value)]) |
|
|
1261 |
key_table <- all_data[,c("color", "group", "value")] %>% |
|
|
1262 |
dplyr::group_by(group) %>% |
|
|
1263 |
dplyr::summarise(color = color[which.max(value)], value = max(value)) |
|
|
1264 |
g.combined <- g + |
|
|
1265 |
ggplot2::geom_point(data = as.data.frame(all_data), aes(x = x, y = y, color = color)) + |
|
|
1266 |
ggplot2::scale_color_identity("", labels = features, breaks = key_table$color, guide = "legend") |
|
|
1267 |
} |
|
|
1268 |
|
|
|
1269 |
g.combined |
|
|
1270 |
} |
|
|
1271 |
|
|
|
1272 |
#### |
|
|
1273 |
## Spatial Auxiliary #### |
|
|
1274 |
#### |
|
|
1275 |
|
|
|
1276 |
#' vrSpatialPlotImage |
|
|
1277 |
#' |
|
|
1278 |
#' setting background with or without the image |
|
|
1279 |
#' |
|
|
1280 |
#' @param g a ggplot object |
|
|
1281 |
#' @param assay vrAssay object |
|
|
1282 |
#' @param spatial the name of the main spatial system |
|
|
1283 |
#' @param channel the name of the channel associated with the image |
|
|
1284 |
#' @param background.color the color of plot background if a channel is not specified, or the spatial coord system doesnt have an image. |
|
|
1285 |
#' @param background (DEPRECATED) the background of the plot. Either an image name, see \link{vrImageNames} or a vector of length two with image name |
|
|
1286 |
#' and a channel name, see \link{vrImageChannelNames}. Type "black" or "white" for black or white backgrounds. if NULL, the main image (\link{vrMainSpatial}) |
|
|
1287 |
#' and main channel (\link{vrMainChannel}) will be in the background. Otherwise the background will be grey. |
|
|
1288 |
#' @param scale.image if TRUE, background image will be scaled down to a low resolution (width: 1000px) |
|
|
1289 |
#' |
|
|
1290 |
#' @import ggplot2 |
|
|
1291 |
#' @importFrom magick image_read |
|
|
1292 |
#' |
|
|
1293 |
#' @noRd |
|
|
1294 |
vrSpatialPlotImage <- function(g, assay, background, scale.image, spatial = NULL, channel = NULL, background.color = NULL){ |
|
|
1295 |
|
|
|
1296 |
# parameters |
|
|
1297 |
scale_factors <- 1 |
|
|
1298 |
|
|
|
1299 |
# check background, spatial and channel |
|
|
1300 |
if(!is.null(background)){ |
|
|
1301 |
warning("The use of 'background' is deprecated, please use 'spatial' to call spatial coordinate systems and 'channel' to get channels of associated images.") |
|
|
1302 |
warning("Please use 'background.color' to draw spatial plots with custom colors in the background.") |
|
|
1303 |
if(length(background) == 2) { |
|
|
1304 |
channel <- background[2] |
|
|
1305 |
} else { |
|
|
1306 |
channel <- NULL |
|
|
1307 |
} |
|
|
1308 |
spatial <- background[1] |
|
|
1309 |
} else { |
|
|
1310 |
if(is.null(spatial)){ |
|
|
1311 |
spatial <- vrMainSpatial(assay) |
|
|
1312 |
} |
|
|
1313 |
} |
|
|
1314 |
|
|
|
1315 |
# get image and adjust size |
|
|
1316 |
if(spatial %in% vrSpatialNames(assay) && is.null(background.color)){ |
|
|
1317 |
|
|
|
1318 |
# get image |
|
|
1319 |
image <- suppressWarnings({vrImages(assay, name = spatial, channel = channel, as.raster = TRUE)}) |
|
|
1320 |
if(!is.null(image) & !inherits(image, "Image_Array")){ |
|
|
1321 |
image <- magick::image_read(image) |
|
|
1322 |
} |
|
|
1323 |
|
|
|
1324 |
if(!is.null(image)){ |
|
|
1325 |
info <- getImageInfo(image) |
|
|
1326 |
if(info$width > 1000 && scale.image){ |
|
|
1327 |
image <- resize_Image(image, geometry = "1000x") |
|
|
1328 |
scale_factors <- info$width/1000 |
|
|
1329 |
info <- getImageInfo(image) |
|
|
1330 |
} |
|
|
1331 |
g <- g + |
|
|
1332 |
ggplot2::annotation_raster(image, 0, info$width, info$height, 0, interpolate = FALSE) |
|
|
1333 |
} else { |
|
|
1334 |
info <- NULL |
|
|
1335 |
} |
|
|
1336 |
} else { |
|
|
1337 |
info <- NULL |
|
|
1338 |
} |
|
|
1339 |
|
|
|
1340 |
# background color |
|
|
1341 |
if(!is.null(background.color)){ |
|
|
1342 |
g <- g + |
|
|
1343 |
theme(panel.background = element_rect(fill = background.color, colour = background.color, linewidth = 0.5, linetype = "solid")) |
|
|
1344 |
} else{ |
|
|
1345 |
if(is.null(info)){ |
|
|
1346 |
g <- g + |
|
|
1347 |
theme(panel.background = element_rect(fill = "grey97", colour = "grey97", linewidth = 0.5, linetype = "solid")) |
|
|
1348 |
} else{ |
|
|
1349 |
g <- g + |
|
|
1350 |
theme(panel.background = element_blank()) |
|
|
1351 |
} |
|
|
1352 |
} |
|
|
1353 |
list(plot = g, info = info, background.color = background.color, spatial = spatial, scale_factors = scale_factors) |
|
|
1354 |
} |
|
|
1355 |
|
|
|
1356 |
#' vrSpatialExtent |
|
|
1357 |
#' |
|
|
1358 |
#' setting extent for spatial plots |
|
|
1359 |
#' |
|
|
1360 |
#' @param g a ggplot object |
|
|
1361 |
#' @param assay vrAssay object |
|
|
1362 |
#' @param coords coordinates |
|
|
1363 |
#' @param crop whether to crop an image of a spot assay to the extend of spots |
|
|
1364 |
#' @param image info |
|
|
1365 |
#' |
|
|
1366 |
#' @importFrom ggplot2 xlim ylim coord_fixed |
|
|
1367 |
#' |
|
|
1368 |
#' @noRd |
|
|
1369 |
vrSpatialExtent <- function(g, assay, coords, crop, info){ |
|
|
1370 |
if(vrAssayTypes(assay) == "spot"){ |
|
|
1371 |
if(crop){ |
|
|
1372 |
g <- g + |
|
|
1373 |
ggplot2::coord_fixed(xlim = range(coords$x), ylim = range(coords$y)) |
|
|
1374 |
} else { |
|
|
1375 |
if(!is.null(info)){ |
|
|
1376 |
g <- g + |
|
|
1377 |
ggplot2::coord_fixed(xlim = c(0,info$width), ylim = c(0,info$height)) |
|
|
1378 |
} |
|
|
1379 |
} |
|
|
1380 |
} else { |
|
|
1381 |
if(crop){ |
|
|
1382 |
g <- g + |
|
|
1383 |
ggplot2::coord_fixed(xlim = range(coords$x), ylim = range(coords$y)) |
|
|
1384 |
} else { |
|
|
1385 |
if(!is.null(info)){ |
|
|
1386 |
g <- g + |
|
|
1387 |
ggplot2::xlim(0,info$width) + ggplot2::ylim(0, info$height) |
|
|
1388 |
} |
|
|
1389 |
} |
|
|
1390 |
} |
|
|
1391 |
g |
|
|
1392 |
} |
|
|
1393 |
|
|
|
1394 |
#' @import ggplot2 |
|
|
1395 |
#' @importFrom grid pointsGrob unit gpar |
|
|
1396 |
#' @importFrom rlang list2 |
|
|
1397 |
#' |
|
|
1398 |
#' @noRd |
|
|
1399 |
NULL |
|
|
1400 |
|
|
|
1401 |
#' geom_spot |
|
|
1402 |
#' |
|
|
1403 |
#' @import ggplot2 |
|
|
1404 |
#' @importFrom grid pointsGrob unit gpar |
|
|
1405 |
#' @importFrom rlang list2 |
|
|
1406 |
#' |
|
|
1407 |
#' @noRd |
|
|
1408 |
geom_spot <- function (mapping = NULL, data = NULL, stat = "identity", position = "identity", |
|
|
1409 |
..., na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) { |
|
|
1410 |
ggplot2::layer(data = data, mapping = mapping, stat = stat, geom = GeomSpot, |
|
|
1411 |
position = position, show.legend = show.legend, inherit.aes = inherit.aes, |
|
|
1412 |
params = rlang::list2(na.rm = na.rm, ...)) |
|
|
1413 |
} |
|
|
1414 |
|
|
|
1415 |
#' GeomSpot (ggproto class) |
|
|
1416 |
#' |
|
|
1417 |
#' @import ggplot2 |
|
|
1418 |
#' @importFrom grid pointsGrob unit gpar |
|
|
1419 |
#' |
|
|
1420 |
#' @noRd |
|
|
1421 |
GeomSpot <- ggplot2::ggproto("GeomSpot", |
|
|
1422 |
Geom, |
|
|
1423 |
required_aes = c("x", "y"), |
|
|
1424 |
non_missing_aes = c("size", "shape", "colour"), |
|
|
1425 |
default_aes = aes( |
|
|
1426 |
shape = 21, |
|
|
1427 |
colour = "black", |
|
|
1428 |
size = 1.5, |
|
|
1429 |
fill = NA, |
|
|
1430 |
spot.radius = 1, |
|
|
1431 |
spot.type = "circle", |
|
|
1432 |
alpha = NA, |
|
|
1433 |
stroke = 0.5 |
|
|
1434 |
), |
|
|
1435 |
draw_panel = function(self, data, panel_params, coord, na.rm = FALSE) { |
|
|
1436 |
if (is.character(data$shape)) { |
|
|
1437 |
data$shape <- translate_shape_string(data$shape) |
|
|
1438 |
} |
|
|
1439 |
if(!is.null(data$spot.type)){ |
|
|
1440 |
if(all(data$spot.type == "square")){ |
|
|
1441 |
data$shape <- 15 |
|
|
1442 |
data$spot.radius <- data$spot.radius/2 |
|
|
1443 |
} |
|
|
1444 |
} |
|
|
1445 |
coords <- coord$transform(data, panel_params) |
|
|
1446 |
stroke_size <- coords$stroke |
|
|
1447 |
stroke_size[is.na(stroke_size)] <- 0 |
|
|
1448 |
xrange <- panel_params$x.range[2] - panel_params$x.range[1] |
|
|
1449 |
yrange <- panel_params$y.range[2] - panel_params$y.range[1] |
|
|
1450 |
mainrange <- min(xrange, yrange) |
|
|
1451 |
spot.radius <- data$spot.radius/mainrange |
|
|
1452 |
ggname("geom_spot", |
|
|
1453 |
grid::pointsGrob( |
|
|
1454 |
coords$x, coords$y, |
|
|
1455 |
pch = coords$shape, |
|
|
1456 |
size = grid::unit(spot.radius, "npc"), |
|
|
1457 |
gp = grid::gpar( |
|
|
1458 |
col = ggplot2::alpha(coords$colour, coords$alpha), |
|
|
1459 |
fill = ggplot2::alpha(coords$fill, coords$alpha), |
|
|
1460 |
fontsize = coords$size * .pt + stroke_size * .stroke / 2, |
|
|
1461 |
lwd = coords$stroke * .stroke / 2 |
|
|
1462 |
) |
|
|
1463 |
) |
|
|
1464 |
) |
|
|
1465 |
}, |
|
|
1466 |
|
|
|
1467 |
draw_key = draw_key_point |
|
|
1468 |
) |
|
|
1469 |
|
|
|
1470 |
#' addGraph |
|
|
1471 |
#' |
|
|
1472 |
#' add graph to a spatial plot |
|
|
1473 |
#' |
|
|
1474 |
#' @param coords coordinates data |
|
|
1475 |
#' @param graph if not NULL, the graph is added to the plot |
|
|
1476 |
#' @param background the background of the plot. Either an image name, see \link{vrImageNames} or a vector of length two with image name |
|
|
1477 |
#' |
|
|
1478 |
#' @import ggplot2 |
|
|
1479 |
#' @importFrom igraph as_data_frame |
|
|
1480 |
#' |
|
|
1481 |
#' @noRd |
|
|
1482 |
addGraph <- function(graph, coords, background){ |
|
|
1483 |
# graph.df <- igraph::get.data.frame(graph) |
|
|
1484 |
graph.df <- igraph::as_data_frame(graph) |
|
|
1485 |
graph.df$from.x <- coords$x[match(graph.df$from, rownames(coords))] |
|
|
1486 |
graph.df$from.y <- coords$y[match(graph.df$from, rownames(coords))] |
|
|
1487 |
graph.df$to.x <- coords$x[match(graph.df$to, rownames(coords))] |
|
|
1488 |
graph.df$to.y <- coords$y[match(graph.df$to, rownames(coords))] |
|
|
1489 |
geom_segment(data = graph.df, mapping = aes(x=from.x,xend = to.x, y=from.y,yend = to.y), |
|
|
1490 |
alpha = 0.5, color = background) |
|
|
1491 |
} |
|
|
1492 |
|
|
|
1493 |
#### |
|
|
1494 |
## Spatial Neighborhood Plot #### |
|
|
1495 |
#### |
|
|
1496 |
|
|
|
1497 |
#' vrNeighbourhoodEnrichmentPlot |
|
|
1498 |
#' |
|
|
1499 |
#' Plotting results of \link{vrNeighbourhoodEnrichment} |
|
|
1500 |
#' |
|
|
1501 |
#' @param results The results from \link{vrNeighbourhoodEnrichment} |
|
|
1502 |
#' @param assay assay name (exp: Assay1), see \link{SampleMetadata}. |
|
|
1503 |
#' @param type the type of spatial test. Either "assoc" for association test or "segreg" for segregation test |
|
|
1504 |
#' |
|
|
1505 |
#' @export |
|
|
1506 |
#' |
|
|
1507 |
vrNeighbourhoodEnrichmentPlot <- function(results, assay = NULL, type = c("assoc", "segreg")){ |
|
|
1508 |
|
|
|
1509 |
# check Seurat package |
|
|
1510 |
if(!requireNamespace('circlize')) |
|
|
1511 |
stop("Please install circlize package for coloring the Heatmap!: install.packages('circlize')") |
|
|
1512 |
|
|
|
1513 |
# get assay names |
|
|
1514 |
if(!is.null(assay)){ |
|
|
1515 |
if(length(assay) > 1){ |
|
|
1516 |
stop("Spatial enrichment plots of only one Assay can be visualized at a time") |
|
|
1517 |
} |
|
|
1518 |
} else { |
|
|
1519 |
stop("assay should be defined, e.g. assay == 'Assay1'") |
|
|
1520 |
} |
|
|
1521 |
results <- results[results$AssayID == assay,] |
|
|
1522 |
|
|
|
1523 |
# get type |
|
|
1524 |
if(length(type) > 1) |
|
|
1525 |
type <- "assoc" |
|
|
1526 |
|
|
|
1527 |
# make vis matrices |
|
|
1528 |
results$p_adj <- ifelse(results$value > 0, results$p_assoc, results$p_segreg) |
|
|
1529 |
results$cell1 <- results$from_value |
|
|
1530 |
results$cell2 <- results$to_value |
|
|
1531 |
entities <- unique(c(results$cell1,results$cell2)) |
|
|
1532 |
mat <- matrix(nrow = length(entities), ncol = length(entities)) |
|
|
1533 |
rownames(mat) <- colnames(mat) <- entities |
|
|
1534 |
mat_padj <- mat |
|
|
1535 |
for(i in seq_len(nrow(results))){ |
|
|
1536 |
mat_padj[which(results$cell1[i]==entities), which(results$cell2[i]==entities)] <- |
|
|
1537 |
mat_padj[which(results$cell2[i]==entities), which(results$cell1[i]==entities)] <- ifelse(type == "assoc", results$p_assoc_adj[i], results$p_segreg_adj[i]) |
|
|
1538 |
mat[which(results$cell1[i]==entities), which(results$cell2[i]==entities)] <- |
|
|
1539 |
mat[which(results$cell2[i]==entities), which(results$cell1[i]==entities)] <- ifelse(type == "assoc", results$value[i], -1*results$value[i]) |
|
|
1540 |
} |
|
|
1541 |
|
|
|
1542 |
# color fun |
|
|
1543 |
limits <- c(0,round(quantile(mat[mat > 0], probs = 0.99),2)) |
|
|
1544 |
enrich_col_fun = circlize::colorRamp2(limits, c("white", "red")) |
|
|
1545 |
|
|
|
1546 |
# make heatmap |
|
|
1547 |
ht <- ComplexHeatmap::Heatmap(mat_padj, |
|
|
1548 |
cluster_rows = TRUE, show_row_dend = FALSE, |
|
|
1549 |
cluster_columns = TRUE, show_column_dend = TRUE, |
|
|
1550 |
column_names_rot = 45, |
|
|
1551 |
col = enrich_col_fun, |
|
|
1552 |
heatmap_legend_param = list( |
|
|
1553 |
at = limits, |
|
|
1554 |
title = "Score"), |
|
|
1555 |
cell_fun = function(j, i, x, y, width, height, fill) { |
|
|
1556 |
grid::grid.rect(x = x, y = y, width = width, height = height, |
|
|
1557 |
gp = gpar(col = "black", fill = "gray75")) |
|
|
1558 |
grid::grid.circle(x = x, y = y, r = (1-mat_padj[i, j])/2 * (min(grid::unit.c(width, height))*0.90), |
|
|
1559 |
gp = gpar(fill = enrich_col_fun(mat[i, j]), col = "black")) |
|
|
1560 |
}) |
|
|
1561 |
|
|
|
1562 |
# extra heatmap legend |
|
|
1563 |
lgd <- ComplexHeatmap::Legend(labels = c("1.00", "0.75", "0.50", "0.25"), title = "p.adj", type = "points", pch = 16, |
|
|
1564 |
size = unit(seq_len(5), 'mm'), direction = 'vertical', legend_gp = gpar(col = "black"), background = 'white') |
|
|
1565 |
|
|
|
1566 |
# draw heatmap |
|
|
1567 |
ComplexHeatmap::draw(ht, annotation_legend_list = lgd) |
|
|
1568 |
} |
|
|
1569 |
|
|
|
1570 |
#### |
|
|
1571 |
# Embeddings plots #### |
|
|
1572 |
#### |
|
|
1573 |
|
|
|
1574 |
#### |
|
|
1575 |
## Embedding Identity Plot #### |
|
|
1576 |
#### |
|
|
1577 |
|
|
|
1578 |
#' vrEmbeddingPlot |
|
|
1579 |
#' |
|
|
1580 |
#' Plotting embeddings of cells and spots on associated images from multiple assays in a VoltRon object. |
|
|
1581 |
#' |
|
|
1582 |
#' @param object a VoltRon object |
|
|
1583 |
#' @param embedding the embedding type, i.e. pca, umap etc. |
|
|
1584 |
#' @param group.by a column of metadata from \link{Metadata} used as grouping label for the spatial entities |
|
|
1585 |
#' @param group.ids a subset of categories defined in metadata column from \code{group.by} |
|
|
1586 |
#' @param split.by a column of metadata from \link{Metadata} used as splitting spatial entities into ggplot panels, see \link{facet_wrap} |
|
|
1587 |
#' @param colors the color set for group.by. Should be of the same size of group.id (if specified) or unique elements in group.by |
|
|
1588 |
#' @param n.tile should points be aggregated into tiles before visualization (see \link{geom_tile}). Applicable only for cells and molecules |
|
|
1589 |
#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. |
|
|
1590 |
#' if NULL, the default assay will be used, see \link{vrMainAssay}. |
|
|
1591 |
#' @param ncol column wise number of plots, for \link{facet_wrap} |
|
|
1592 |
#' @param nrow row wise number of plots, for \link{facet_wrap} |
|
|
1593 |
#' @param font.size font size of labels, if label is TRUE |
|
|
1594 |
#' @param pt.size point size |
|
|
1595 |
#' @param label if TRUE, the labels of the ROI assays will be visualized |
|
|
1596 |
#' @param common.legend whether to use a common legend for all plots, see \link{ggarrange} |
|
|
1597 |
#' @param collapse.plots whether to combine all ggplots |
|
|
1598 |
#' |
|
|
1599 |
#' @import ggplot2 |
|
|
1600 |
#' @importFrom stats aggregate |
|
|
1601 |
#' |
|
|
1602 |
#' @export |
|
|
1603 |
#' |
|
|
1604 |
vrEmbeddingPlot <- function(object, embedding = "pca", group.by = "Sample", group.ids = NULL, split.by = NULL, colors = NULL, n.tile = 0, assay = NULL, |
|
|
1605 |
ncol = 2, nrow = NULL, font.size = 5, pt.size = 1, label = FALSE, common.legend = TRUE, collapse.plots = TRUE) { |
|
|
1606 |
|
|
|
1607 |
# check object |
|
|
1608 |
if(!inherits(object, "VoltRon")) |
|
|
1609 |
stop("Please provide a VoltRon object!") |
|
|
1610 |
|
|
|
1611 |
# sample metadata |
|
|
1612 |
sample.metadata <- SampleMetadata(object) |
|
|
1613 |
|
|
|
1614 |
# get assay names |
|
|
1615 |
assay_names <- vrAssayNames(object, assay = assay) |
|
|
1616 |
|
|
|
1617 |
# get entity type and metadata |
|
|
1618 |
metadata <- Metadata(object, assay = assay) |
|
|
1619 |
|
|
|
1620 |
# grep assays from metadata |
|
|
1621 |
metadata <- subset_metadata(metadata, assays = assay_names) |
|
|
1622 |
|
|
|
1623 |
# check group.by |
|
|
1624 |
if(!group.by %in% colnames(metadata)){ |
|
|
1625 |
stop("Column ", group.by, " is not found in metadata!") |
|
|
1626 |
} |
|
|
1627 |
|
|
|
1628 |
# adjust group.ids |
|
|
1629 |
if(is.null(group.ids)){ |
|
|
1630 |
group.ids <- unique(metadata[[group.by]]) |
|
|
1631 |
} |
|
|
1632 |
|
|
|
1633 |
# check group.id |
|
|
1634 |
levels_group.ids <- as.character(group.ids) |
|
|
1635 |
if(all(!is.na(suppressWarnings(as.numeric(levels_group.ids))))){ |
|
|
1636 |
levels_group.ids <- sort(as.numeric(levels_group.ids)) |
|
|
1637 |
} |
|
|
1638 |
group.ids <- factor(group.ids, levels = levels_group.ids) |
|
|
1639 |
|
|
|
1640 |
# check colors |
|
|
1641 |
if(!is.null(colors)){ |
|
|
1642 |
if(length(colors) != length(levels_group.ids)){ |
|
|
1643 |
stop("Please provide colors whose length is equal to group.ids (if specified) or unique elements of group.by") |
|
|
1644 |
} |
|
|
1645 |
if(is.null(names(colors))){ |
|
|
1646 |
stop("Name of each color has to be specified given as names(colors)") |
|
|
1647 |
} |
|
|
1648 |
} else{ |
|
|
1649 |
colors <- hue_pal(length(group.ids)) |
|
|
1650 |
names(colors) <- group.ids |
|
|
1651 |
} |
|
|
1652 |
|
|
|
1653 |
# plotting features |
|
|
1654 |
datax <- data.frame(vrEmbeddings(object, assay = assay_names, type = embedding)) |
|
|
1655 |
datax <- datax[,seq_len(2)] |
|
|
1656 |
colnames(datax) <- c("x", "y") |
|
|
1657 |
if(group.by %in% colnames(metadata)){ |
|
|
1658 |
if(inherits(metadata, "data.table")){ |
|
|
1659 |
datax[[group.by]] <- metadata[,get(names(metadata)[which(colnames(metadata) == group.by)])] |
|
|
1660 |
} else { |
|
|
1661 |
if(!is.null(rownames(metadata))){ |
|
|
1662 |
datax[[group.by]] <- as.factor(metadata[rownames(datax),group.by]) |
|
|
1663 |
} else{ |
|
|
1664 |
datax[[group.by]] <- as.factor(as.vector(metadata[match(rownames(datax), as.vector(metadata$id)),group.by])) |
|
|
1665 |
} |
|
|
1666 |
} |
|
|
1667 |
} else { |
|
|
1668 |
stop("Column ", group.by, " cannot be found in metadata!") |
|
|
1669 |
} |
|
|
1670 |
|
|
|
1671 |
# subset group.by using group.id |
|
|
1672 |
datax <- droplevels(datax[datax[[group.by]] %in% group.ids,]) |
|
|
1673 |
datax[[group.by]] <- factor(datax[[group.by]], levels = group.ids) |
|
|
1674 |
|
|
|
1675 |
# get split.by |
|
|
1676 |
if(!is.null(split.by)){ |
|
|
1677 |
if(split.by %in% colnames(metadata)){ |
|
|
1678 |
if(inherits(metadata, "data.table")){ |
|
|
1679 |
datax[[split.by]] <- metadata[,get(names(metadata)[which(colnames(metadata) == split.by)])] |
|
|
1680 |
} else { |
|
|
1681 |
datax[[split.by]] <- as.factor(metadata[rownames(datax),split.by]) |
|
|
1682 |
} |
|
|
1683 |
} else { |
|
|
1684 |
stop("Column ", split.by, " cannot be found in metadata!") |
|
|
1685 |
} |
|
|
1686 |
} |
|
|
1687 |
|
|
|
1688 |
# plot |
|
|
1689 |
g <- ggplot() |
|
|
1690 |
|
|
|
1691 |
# add points, rasterize if requested or needed |
|
|
1692 |
if(n.tile > 0 || nrow(datax) > 50000){ |
|
|
1693 |
if(n.tile == 0) |
|
|
1694 |
n.tile <- 1000 |
|
|
1695 |
g <- vrGroupPlotTiling(g = g, data = datax, group.by = group.by, n.tile = n.tile) |
|
|
1696 |
} else { |
|
|
1697 |
# g <- g + |
|
|
1698 |
# geom_point(mapping = aes_string(x = "x", y = "y", color = group.by), datax, shape = 16, size = pt.size) |
|
|
1699 |
g <- g + |
|
|
1700 |
geom_point(mapping = aes(x = .data[["x"]], y = .data[["y"]], |
|
|
1701 |
color = .data[[group.by]], fill = .data[[group.by]]), datax, shape = 16, size = pt.size) |
|
|
1702 |
} |
|
|
1703 |
g <- g + |
|
|
1704 |
scale_color_manual(values = colors, labels = names(colors), drop = FALSE, limits = names(colors)) + |
|
|
1705 |
scale_fill_manual(values = colors, labels = names(colors), drop = FALSE, limits = names(colors)) + |
|
|
1706 |
guides(color = guide_legend(override.aes=list(size = 2))) |
|
|
1707 |
|
|
|
1708 |
# more visualization parameters |
|
|
1709 |
g <- g + |
|
|
1710 |
theme_classic() + |
|
|
1711 |
xlab(paste0(toupper(embedding), "_1")) + ylab(paste0(toupper(embedding), "_2")) + |
|
|
1712 |
theme(plot.title = element_text(hjust = 0.5, margin=margin(0,0,0,0)), |
|
|
1713 |
panel.grid.minor = element_blank(), panel.grid.major = element_blank(), |
|
|
1714 |
legend.margin = margin(0,0,0,0), panel.background = element_blank()) |
|
|
1715 |
|
|
|
1716 |
# labels |
|
|
1717 |
if(label){ |
|
|
1718 |
datax_group <- stats::aggregate(datax[,c("x","y")], list(datax[[group.by]]),mean) |
|
|
1719 |
colnames(datax_group) <- c(group.by, "x", "y") |
|
|
1720 |
g <- g + |
|
|
1721 |
geom_text(mapping = aes(x = .data[["x"]], y = .data[["y"]], label = .data[[group.by]]), datax_group, size = font.size) |
|
|
1722 |
} |
|
|
1723 |
|
|
|
1724 |
# splitting |
|
|
1725 |
if(!is.null(split.by)){ |
|
|
1726 |
if(is.null(nrow)){ |
|
|
1727 |
unique_split <- unique(datax[[split.by]]) |
|
|
1728 |
nrow <- ceiling(length(unique_split)/ncol) |
|
|
1729 |
} |
|
|
1730 |
g <- g + |
|
|
1731 |
facet_wrap(as.formula(paste("~", split.by)), nrow = nrow, ncol = ncol) |
|
|
1732 |
} |
|
|
1733 |
|
|
|
1734 |
# return data |
|
|
1735 |
return(g) |
|
|
1736 |
} |
|
|
1737 |
|
|
|
1738 |
#### |
|
|
1739 |
## Embedding Feature Plot #### |
|
|
1740 |
#### |
|
|
1741 |
|
|
|
1742 |
#' vrEmbeddingFeaturePlot |
|
|
1743 |
#' |
|
|
1744 |
#' Plotting features of spatially resolved cells and spots on embeddings from multiple assays in a VoltRon object. |
|
|
1745 |
#' |
|
|
1746 |
#' @param object a VoltRon object |
|
|
1747 |
#' @param embedding the embedding type, i.e. pca, umap etc. |
|
|
1748 |
#' @param features a set of features to be visualized, either from \link{vrFeatures} of raw or normalized data or columns of the \link{Metadata}. |
|
|
1749 |
#' @param combine.features whether to combine all features in one plot |
|
|
1750 |
#' @param n.tile should points be aggregated into tiles before visualization (see \link{geom_tile}). Applicable only for cells and molecules |
|
|
1751 |
#' @param norm if TRUE, the normalized data is used |
|
|
1752 |
#' @param log if TRUE, data features (excluding metadata features) will be log transformed |
|
|
1753 |
#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. |
|
|
1754 |
#' if NULL, the default assay will be used, see \link{vrMainAssay}. |
|
|
1755 |
#' @param ncol column wise number of plots, for \link{ggarrange} |
|
|
1756 |
#' @param nrow row wise number of plots, for \link{ggarrange} |
|
|
1757 |
#' @param font.size font size |
|
|
1758 |
#' @param pt.size point size |
|
|
1759 |
#' @param keep.scale whether unify all scales for all features or not |
|
|
1760 |
#' @param common.legend whether to use a common legend for all plots, see \link{ggarrange} |
|
|
1761 |
#' @param collapse.plots whether to combine all ggplots |
|
|
1762 |
#' |
|
|
1763 |
#' @import ggplot2 |
|
|
1764 |
#' @importFrom dplyr arrange desc |
|
|
1765 |
#' |
|
|
1766 |
#' @export |
|
|
1767 |
vrEmbeddingFeaturePlot <- function(object, embedding = "pca", features = NULL, combine.features = FALSE, n.tile = 0, norm = TRUE, log = FALSE, assay = NULL, ncol = 2, nrow = NULL, |
|
|
1768 |
font.size = 2, pt.size = 1, keep.scale = "feature", common.legend = TRUE, collapse.plots = TRUE) { |
|
|
1769 |
|
|
|
1770 |
# check object |
|
|
1771 |
if(!inherits(object, "VoltRon")) |
|
|
1772 |
stop("Please provide a VoltRon object!") |
|
|
1773 |
|
|
|
1774 |
# features |
|
|
1775 |
if(is.null(features)) |
|
|
1776 |
stop("You have to define at least one feature") |
|
|
1777 |
|
|
|
1778 |
# sample metadata |
|
|
1779 |
sample.metadata <- SampleMetadata(object) |
|
|
1780 |
|
|
|
1781 |
# get assay names |
|
|
1782 |
assay_names <- vrAssayNames(object, assay = assay) |
|
|
1783 |
|
|
|
1784 |
# get entity type and metadata |
|
|
1785 |
metadata <- Metadata(object, assay = assay) |
|
|
1786 |
|
|
|
1787 |
# get data |
|
|
1788 |
data_features <- features[features %in% vrFeatures(object, assay = assay)] |
|
|
1789 |
if(length(data_features) > 0){ |
|
|
1790 |
normdata <- vrData(object, features = data_features, assay = assay_names, norm = norm) |
|
|
1791 |
if(log) |
|
|
1792 |
normdata <- log1p(normdata) |
|
|
1793 |
} |
|
|
1794 |
|
|
|
1795 |
# get embedding |
|
|
1796 |
datax <- data.frame(vrEmbeddings(object, assay = assay_names, type = embedding)) |
|
|
1797 |
datax <- datax[,seq_len(2)] |
|
|
1798 |
colnames(datax) <- c("x", "y") |
|
|
1799 |
|
|
|
1800 |
# calculate limits for plotting, all for making one scale, feature for making multiple |
|
|
1801 |
limits <- Map(function(feat){ |
|
|
1802 |
if(feat %in% vrFeatures(object, assay = assay)){ |
|
|
1803 |
return(getRange(normdata[feat, ])) |
|
|
1804 |
} else { |
|
|
1805 |
if(feat %in% colnames(metadata)){ |
|
|
1806 |
return(getRange(metadata[, feat])) |
|
|
1807 |
} else { |
|
|
1808 |
stop("Feature '", feat, "' cannot be found in data or metadata!") |
|
|
1809 |
} |
|
|
1810 |
} |
|
|
1811 |
}, features) |
|
|
1812 |
|
|
|
1813 |
# configure titles |
|
|
1814 |
feature_title <- as.list(features) |
|
|
1815 |
names(feature_title) <- features |
|
|
1816 |
legend_title <- feature_title |
|
|
1817 |
|
|
|
1818 |
# for each feature |
|
|
1819 |
i <- 1 |
|
|
1820 |
gg <- list() |
|
|
1821 |
g <- ggplot() |
|
|
1822 |
all_data <- NULL |
|
|
1823 |
colors <- get_rasterization_colors(length(features)) |
|
|
1824 |
for(feat in features){ |
|
|
1825 |
|
|
|
1826 |
# get data |
|
|
1827 |
if(feat %in% vrFeatures(object, assay = assay)){ |
|
|
1828 |
if(inherits(normdata, "IterableMatrix")){ |
|
|
1829 |
datax$score <- as.matrix(normdata[feat, rownames(datax)])[1,] |
|
|
1830 |
} else { |
|
|
1831 |
datax$score <- normdata[feat, rownames(datax)] |
|
|
1832 |
} |
|
|
1833 |
} else { |
|
|
1834 |
datax$score <- metadata[rownames(datax),feat] |
|
|
1835 |
} |
|
|
1836 |
|
|
|
1837 |
# get image information and plotting features |
|
|
1838 |
midpoint <- sum(limits[[feat]])/2 |
|
|
1839 |
|
|
|
1840 |
# make one plot by combining features |
|
|
1841 |
if(combine.features){ |
|
|
1842 |
|
|
|
1843 |
# plot |
|
|
1844 |
g <- ggplot() |
|
|
1845 |
|
|
|
1846 |
# add points, rasterize if requested or needed |
|
|
1847 |
if(n.tile > 0 || nrow(datax) > 50000){ |
|
|
1848 |
if(n.tile == 0) |
|
|
1849 |
n.tile <- 1000 |
|
|
1850 |
g <- g + |
|
|
1851 |
stat_summary_2d(mapping = aes(x = x, y = y, z = score), fun = mean, data = datax, geom = "tile", bins = n.tile, drop = TRUE) + |
|
|
1852 |
scale_fill_gradientn(name = legend_title[[feat]], |
|
|
1853 |
colors=c("grey97", colors[i]), |
|
|
1854 |
values=rescale_numeric(c(limits[[feat]][1], limits[[feat]][2])), limits = limits[[feat]]) |
|
|
1855 |
all_data <- rbind(all_data, |
|
|
1856 |
data.frame(layer_data(g), color_group = colors[i])) |
|
|
1857 |
} else { |
|
|
1858 |
g <- g + |
|
|
1859 |
geom_point(mapping = aes(x = x, y = y, color = score), datax, shape = 16, size = pt.size) + |
|
|
1860 |
scale_color_gradientn(name = legend_title[[feat]], |
|
|
1861 |
colors=c("grey97", colors[i]), |
|
|
1862 |
values=rescale_numeric(c(limits[[feat]][1], limits[[feat]][2])), limits = limits[[feat]]) |
|
|
1863 |
all_data <- rbind(all_data, |
|
|
1864 |
data.frame(layer_data(g), value = datax$score, color_group = colors[i])) |
|
|
1865 |
} |
|
|
1866 |
|
|
|
1867 |
# make individual feature plots |
|
|
1868 |
} else { |
|
|
1869 |
|
|
|
1870 |
# plot |
|
|
1871 |
g <- ggplot() |
|
|
1872 |
|
|
|
1873 |
# add points, rasterize if requested or needed |
|
|
1874 |
if(n.tile > 0 || nrow(datax) > 50000){ |
|
|
1875 |
if(n.tile == 0) |
|
|
1876 |
n.tile <- 1000 |
|
|
1877 |
g <- vrFeaturePlotTiling(g = g, data = datax, legend_title = legend_title[[feat]], n.tile = n.tile, type = "embedding", limits = limits[[feat]]) |
|
|
1878 |
} else { |
|
|
1879 |
g <- g + |
|
|
1880 |
geom_point(mapping = aes(x = x, y = y, color = score), dplyr::arrange(datax,score), shape = 16, size = pt.size) + |
|
|
1881 |
scale_color_gradientn(name = legend_title[[feat]], |
|
|
1882 |
colors=c("grey97", "blue"), |
|
|
1883 |
values=rescale_numeric(c(limits[[feat]][1], limits[[feat]][2])), limits = limits[[feat]]) |
|
|
1884 |
} |
|
|
1885 |
} |
|
|
1886 |
|
|
|
1887 |
# more visualization parameters |
|
|
1888 |
# g <- g + addEmbeddingPlotStyle(embedding) |
|
|
1889 |
g <- g + |
|
|
1890 |
theme_classic() + |
|
|
1891 |
xlab(paste0(toupper(embedding), "_1")) + ylab(paste0(toupper(embedding), "_2")) + |
|
|
1892 |
theme(plot.title = element_text(hjust = 0.5, margin=margin(0,0,0,0)), |
|
|
1893 |
panel.grid.minor = element_blank(), panel.grid.major = element_blank(), |
|
|
1894 |
legend.margin = margin(0,0,0,0), panel.background = element_blank()) |
|
|
1895 |
|
|
|
1896 |
# add graph to list |
|
|
1897 |
gg[[i]] <- g |
|
|
1898 |
i <- i + 1 |
|
|
1899 |
} |
|
|
1900 |
|
|
|
1901 |
# collapse plots |
|
|
1902 |
if(collapse.plots){ |
|
|
1903 |
|
|
|
1904 |
# return a list of plots or a single one |
|
|
1905 |
if(length(features) > 1){ |
|
|
1906 |
|
|
|
1907 |
# combine features |
|
|
1908 |
if(combine.features){ |
|
|
1909 |
g.combined <- vrEmbeddingFeatureCombinePlot(all_data, n.tile, datax, features, embedding) |
|
|
1910 |
return(g.combined) |
|
|
1911 |
} else { |
|
|
1912 |
if(length(gg) < ncol) ncol <- length(gg) |
|
|
1913 |
return(ggpubr::ggarrange(plotlist = gg, ncol = ncol, nrow = ceiling(length(gg)/ncol))) |
|
|
1914 |
} |
|
|
1915 |
} else { |
|
|
1916 |
return(gg[[1]]) |
|
|
1917 |
} |
|
|
1918 |
} else { |
|
|
1919 |
return(gg) |
|
|
1920 |
} |
|
|
1921 |
} |
|
|
1922 |
|
|
|
1923 |
#' vrEmbeddingFeatureCombinePlot |
|
|
1924 |
#' |
|
|
1925 |
#' A different plotting setting for vrEmbeddingFeaturePlot when combine.feature=TRUE |
|
|
1926 |
#' |
|
|
1927 |
#' @param all_data summary data |
|
|
1928 |
#' @param n.tile should points be aggregated into tiles before visualization (see \link{geom_tile}). Applicable only for cells and molecules |
|
|
1929 |
#' @param datax original plotting data |
|
|
1930 |
#' @param features features |
|
|
1931 |
#' @param embedding embedding |
|
|
1932 |
#' |
|
|
1933 |
#' @import ggplot2 |
|
|
1934 |
#' |
|
|
1935 |
#' @noRd |
|
|
1936 |
vrEmbeddingFeatureCombinePlot <- function(all_data, n.tile, datax, features, embedding){ |
|
|
1937 |
|
|
|
1938 |
# ggplot |
|
|
1939 |
g.combined <- ggplot() |
|
|
1940 |
|
|
|
1941 |
# tiling or not |
|
|
1942 |
if(n.tile > 0 || nrow(datax) > 50000){ |
|
|
1943 |
if(n.tile == 0) |
|
|
1944 |
n.tile <- 1000 |
|
|
1945 |
all_data <- all_data %>% group_by(x,y) %>% |
|
|
1946 |
summarize(fill = fill[which.max(value)], |
|
|
1947 |
group = color_group[which.max(value)], |
|
|
1948 |
value = value[which.max(value)]) |
|
|
1949 |
key_table <- all_data[,c("fill", "group", "value")] %>% |
|
|
1950 |
dplyr::group_by(group) %>% |
|
|
1951 |
dplyr::summarise(fill = fill[which.max(value)], value = max(value)) |
|
|
1952 |
g.combined <- g.combined + |
|
|
1953 |
ggplot2::geom_tile(data = as.data.frame(all_data), aes(x = x, y = y, fill = fill)) + |
|
|
1954 |
ggplot2::scale_fill_identity("", labels = features, breaks = key_table$fill, guide = "legend") |
|
|
1955 |
} else { |
|
|
1956 |
all_data <- all_data %>% group_by(x,y) %>% |
|
|
1957 |
dplyr::summarise(color = colour[which.max(value)], |
|
|
1958 |
group = color_group[which.max(value)], |
|
|
1959 |
value = value[which.max(value)]) |
|
|
1960 |
key_table <- all_data[,c("color", "group", "value")] %>% |
|
|
1961 |
dplyr::group_by(group) %>% |
|
|
1962 |
dplyr::summarise(color = color[which.max(value)], value = max(value)) |
|
|
1963 |
g.combined <- g.combined + |
|
|
1964 |
ggplot2::geom_point(data = as.data.frame(all_data), aes(x = x, y = y, color = color)) + |
|
|
1965 |
ggplot2::scale_color_identity("", labels = features, breaks = key_table$color, guide = "legend") |
|
|
1966 |
} |
|
|
1967 |
|
|
|
1968 |
# style |
|
|
1969 |
g.combined <- g.combined + |
|
|
1970 |
addEmbeddingPlotStyle(embedding) + |
|
|
1971 |
xlab(paste0(toupper(embedding), "_1")) + |
|
|
1972 |
ylab(paste0(toupper(embedding), "_2")) |
|
|
1973 |
|
|
|
1974 |
g.combined |
|
|
1975 |
} |
|
|
1976 |
|
|
|
1977 |
#### |
|
|
1978 |
# Scatter Plot #### |
|
|
1979 |
#### |
|
|
1980 |
|
|
|
1981 |
#' vrScatterPlot |
|
|
1982 |
#' |
|
|
1983 |
#' get a scatter plot between two features |
|
|
1984 |
#' |
|
|
1985 |
#' @param object a VoltRon object |
|
|
1986 |
#' @param feature.1 first feature |
|
|
1987 |
#' @param feature.2 second feature |
|
|
1988 |
#' @param norm if TRUE, the normalize data will be used |
|
|
1989 |
#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. |
|
|
1990 |
#' if NULL, the default assay will be used, see \link{vrMainAssay}. |
|
|
1991 |
#' @param pt.size point size |
|
|
1992 |
#' @param font.size font size |
|
|
1993 |
#' @param group.by a column of metadata from \link{Metadata} used as grouping label for the spatial entities |
|
|
1994 |
#' @param label whether labels are visualized or not |
|
|
1995 |
#' @param trend inserting a trend line two the scatter plot |
|
|
1996 |
#' |
|
|
1997 |
#' @import ggplot2 |
|
|
1998 |
#' @importFrom ggrepel geom_label_repel |
|
|
1999 |
#' |
|
|
2000 |
#' @export |
|
|
2001 |
#' |
|
|
2002 |
vrScatterPlot <- function(object, feature.1, feature.2, norm = TRUE, assay = NULL, |
|
|
2003 |
pt.size = 2, font.size = 2, group.by = "label", label = FALSE, trend = FALSE){ |
|
|
2004 |
|
|
|
2005 |
# check object |
|
|
2006 |
if(!inherits(object, "VoltRon")) |
|
|
2007 |
stop("Please provide a VoltRon object!") |
|
|
2008 |
|
|
|
2009 |
# check the number of features |
|
|
2010 |
if(is.null(feature.1) | is.null(feature.2)) |
|
|
2011 |
stop("Please provide both 'feature.1' and 'feature.2'") |
|
|
2012 |
|
|
|
2013 |
# check the number of features |
|
|
2014 |
if((length(feature.1) != 1 | length(feature.2) != 1)) |
|
|
2015 |
stop("Both 'feature.1' and 'feature.2' should be of length 1.") |
|
|
2016 |
|
|
|
2017 |
# data |
|
|
2018 |
normdata <- vrData(object, assay = assay, norm = norm) |
|
|
2019 |
|
|
|
2020 |
# sample metadata |
|
|
2021 |
sample.metadata <- SampleMetadata(object) |
|
|
2022 |
|
|
|
2023 |
# get assay names |
|
|
2024 |
assay_names <- vrAssayNames(object, assay = assay) |
|
|
2025 |
|
|
|
2026 |
# get entity type and metadata |
|
|
2027 |
metadata <- Metadata(object, assay = assay) |
|
|
2028 |
|
|
|
2029 |
# get data |
|
|
2030 |
# data_feature <- sapply(c(feature.1, feature.2), function(feat){ |
|
|
2031 |
data_feature <- vapply(c(feature.1, feature.2), function(feat){ |
|
|
2032 |
if(feat %in% rownames(normdata)){ |
|
|
2033 |
return(normdata[feat,]) |
|
|
2034 |
} else { |
|
|
2035 |
return(metadata[,feat]) |
|
|
2036 |
} |
|
|
2037 |
}, numeric(nrow(metadata))) |
|
|
2038 |
data_feature <- as.data.frame(data_feature) |
|
|
2039 |
|
|
|
2040 |
# plot |
|
|
2041 |
g <- ggplot() |
|
|
2042 |
|
|
|
2043 |
# plot scatter |
|
|
2044 |
g <- g + |
|
|
2045 |
geom_point(mapping = aes(x = .data[[feature.1]], y = .data[[feature.2]]), data = data_feature, size = pt.size) |
|
|
2046 |
|
|
|
2047 |
# visualize labels |
|
|
2048 |
if(label){ |
|
|
2049 |
data_feature[[group.by]] <- metadata[,group.by] |
|
|
2050 |
g <- g + ggrepel::geom_label_repel(mapping = aes(x = .data[[feature.1]], y = .data[[feature.2]], label = .data[[group.by]]), data_feature, |
|
|
2051 |
box.padding = 0.5, size = font.size, direction = "both", seed = 1) |
|
|
2052 |
} |
|
|
2053 |
|
|
|
2054 |
# visualize trend |
|
|
2055 |
if(trend){ |
|
|
2056 |
g <- g + geom_smooth() |
|
|
2057 |
} |
|
|
2058 |
|
|
|
2059 |
g |
|
|
2060 |
} |
|
|
2061 |
|
|
|
2062 |
#### |
|
|
2063 |
# Heatmap Plot #### |
|
|
2064 |
#### |
|
|
2065 |
|
|
|
2066 |
#' HeatmapPlot |
|
|
2067 |
#' |
|
|
2068 |
#' @param object a VoltRon object |
|
|
2069 |
#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. |
|
|
2070 |
#' if NULL, the default assay will be used, see \link{vrMainAssay}. |
|
|
2071 |
#' @param features a set of features to be visualized from \link{vrFeatures} of raw or normalized data. |
|
|
2072 |
#' @param group.by a column of metadata from \link{Metadata} used as grouping label for the spatial entities |
|
|
2073 |
#' @param norm if TRUE, the normalized data is used |
|
|
2074 |
#' @param scaled if TRUE, the data will be scaled before visualization |
|
|
2075 |
#' @param show_row_names if TRUE, row names of the heatmap will be shown |
|
|
2076 |
#' @param cluster_rows if TRUE, the rows of the heatmap will be clustered |
|
|
2077 |
#' @param show_heatmap_legend if TRUE, the heatmap legend is shown |
|
|
2078 |
#' @param outlier.quantile quantile for detecting outliers whose values are set to the quantile, change to lower values to adjust large number of outliers, default: 0.99 |
|
|
2079 |
#' @param highlight.some if TRUE, some rows will be showed at random, reproducible by \code{seed} argument |
|
|
2080 |
#' @param n_highlight the number of row labels shown, if \code{show_row_names} is TRUE |
|
|
2081 |
#' @param font.size font size |
|
|
2082 |
#' @param ... additional parameters passed to \link{getVariableFeatures} |
|
|
2083 |
#' |
|
|
2084 |
#' @importFrom stats quantile |
|
|
2085 |
#' |
|
|
2086 |
#' @export |
|
|
2087 |
vrHeatmapPlot <- function(object, assay = NULL, features = NULL, group.by = "clusters", |
|
|
2088 |
norm = TRUE, scaled = TRUE, show_row_names = NULL, cluster_rows = TRUE, show_heatmap_legend = FALSE, |
|
|
2089 |
outlier.quantile = 0.99, highlight.some = FALSE, n_highlight = 30, font.size = 13.2, ...){ |
|
|
2090 |
|
|
|
2091 |
if (!requireNamespace('ComplexHeatmap')) |
|
|
2092 |
stop("Please install ComplexHeatmap package to use the Heatmap function!: BiocManager::install('ComplexHeatmap')") |
|
|
2093 |
if (!requireNamespace('viridisLite')) |
|
|
2094 |
stop("Please install viridisLite package to use the Heatmap function!: install.packages('viridisLite')") |
|
|
2095 |
|
|
|
2096 |
# check object |
|
|
2097 |
if(!inherits(object, "VoltRon")) |
|
|
2098 |
stop("Please provide a VoltRon object!") |
|
|
2099 |
|
|
|
2100 |
# data |
|
|
2101 |
heatmapdata <- vrData(object, assay = assay, norm = norm) |
|
|
2102 |
|
|
|
2103 |
# features |
|
|
2104 |
if(is.null(features)){ |
|
|
2105 |
if(nrow(vrFeatureData(object, assay = assay)) > 0){ |
|
|
2106 |
features <- getVariableFeatures(object, assay = assay, ...) |
|
|
2107 |
} else { |
|
|
2108 |
features <- vrFeatures(object, assay = assay) |
|
|
2109 |
} |
|
|
2110 |
} else { |
|
|
2111 |
nonmatching_features <- setdiff(features, vrFeatures(object, assay = assay)) |
|
|
2112 |
features <- features[features %in% vrFeatures(object, assay = assay)] |
|
|
2113 |
if(length(features) == 0){ |
|
|
2114 |
stop("None of the provided features are found in the assay") |
|
|
2115 |
} |
|
|
2116 |
if(length(nonmatching_features)) |
|
|
2117 |
message("the following features are not found in the assay: ", paste(nonmatching_features, collapse = ", ")) |
|
|
2118 |
} |
|
|
2119 |
heatmapdata <- heatmapdata[features, ] |
|
|
2120 |
|
|
|
2121 |
# get entity type and metadata |
|
|
2122 |
metadata <- Metadata(object, assay = assay) |
|
|
2123 |
if(!is.null(rownames(metadata))){ |
|
|
2124 |
metadata <- metadata[colnames(heatmapdata),] |
|
|
2125 |
} else { |
|
|
2126 |
metadata <- metadata[match(colnames(heatmapdata), as.vector(metadata$id)),] |
|
|
2127 |
} |
|
|
2128 |
|
|
|
2129 |
# scaling, optional |
|
|
2130 |
if(scaled){ |
|
|
2131 |
heatmapdata <- apply(heatmapdata, 1, scale) |
|
|
2132 |
heatmapdata <- t(heatmapdata) |
|
|
2133 |
legend_title <- "Scaled \n Exp." |
|
|
2134 |
} else { |
|
|
2135 |
legend_title <- "Norm. \n Exp." |
|
|
2136 |
} |
|
|
2137 |
|
|
|
2138 |
# check for nan |
|
|
2139 |
heatmapdata[is.nan(heatmapdata)] <- 0 |
|
|
2140 |
|
|
|
2141 |
# manage data for plotting |
|
|
2142 |
if(group.by %in% colnames(metadata)){ |
|
|
2143 |
heatmapdata <- heatmapdata[,order(metadata[[group.by]], decreasing = FALSE)] |
|
|
2144 |
labels_ordered <- metadata[[group.by]][order(metadata[[group.by]], decreasing = FALSE)] |
|
|
2145 |
labels_ordered_table <- table(labels_ordered) |
|
|
2146 |
col_split = factor(labels_ordered, levels = names(labels_ordered_table)) |
|
|
2147 |
} else { |
|
|
2148 |
stop("Column '", group.by, "' cannot be found in metadata!") |
|
|
2149 |
} |
|
|
2150 |
|
|
|
2151 |
# update limits |
|
|
2152 |
limits <- stats::quantile(as.vector(heatmapdata), probs = c(1-outlier.quantile, outlier.quantile)) |
|
|
2153 |
heatmapdata[heatmapdata > limits[2]] <- limits[2] |
|
|
2154 |
heatmapdata[heatmapdata < limits[1]] <- limits[1] |
|
|
2155 |
|
|
|
2156 |
# highlight some rows |
|
|
2157 |
if(highlight.some){ |
|
|
2158 |
ind <- sample(seq_len(nrow(heatmapdata)), n_highlight, replace = FALSE) |
|
|
2159 |
ha <- ComplexHeatmap::rowAnnotation(foo = ComplexHeatmap::anno_mark(at = ind, |
|
|
2160 |
labels = rownames(heatmapdata)[ind], |
|
|
2161 |
padding = 1, |
|
|
2162 |
labels_gp = gpar(fontsize = font.size))) |
|
|
2163 |
} else{ |
|
|
2164 |
ha <- NULL |
|
|
2165 |
} |
|
|
2166 |
|
|
|
2167 |
# visualize |
|
|
2168 |
if(is.null(show_row_names)) |
|
|
2169 |
show_row_names <- (nrow(heatmapdata) < 30) |
|
|
2170 |
legend_at <- seq(min(heatmapdata), max(heatmapdata), (max(heatmapdata)-min(heatmapdata))/5) |
|
|
2171 |
legend_label <- round(legend_at, 2) |
|
|
2172 |
ComplexHeatmap::Heatmap(heatmapdata, |
|
|
2173 |
show_row_names = show_row_names, show_row_dend = FALSE, row_names_gp = gpar(fontsize = font.size), |
|
|
2174 |
show_column_names = FALSE, column_title_rot = 45, column_title_gp = gpar(fontsize = font.size), |
|
|
2175 |
column_split = col_split, cluster_columns = FALSE, cluster_rows = cluster_rows, |
|
|
2176 |
show_heatmap_legend = show_heatmap_legend, |
|
|
2177 |
heatmap_legend_param = list(title = legend_title, at = legend_at, labels = legend_label), |
|
|
2178 |
right_annotation = ha, |
|
|
2179 |
col = viridisLite::viridis(100)) |
|
|
2180 |
} |
|
|
2181 |
|
|
|
2182 |
#### |
|
|
2183 |
# Violin Plot #### |
|
|
2184 |
#### |
|
|
2185 |
|
|
|
2186 |
#' vrViolinPlot |
|
|
2187 |
#' |
|
|
2188 |
#' @param object a VoltRon object |
|
|
2189 |
#' @param features a set of features to be visualized, either from \link{vrFeatures} of raw or normalized data or columns of the \link{Metadata}. |
|
|
2190 |
#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. |
|
|
2191 |
#' if NULL, the default assay will be used, see \link{vrMainAssay}. |
|
|
2192 |
#' @param group.by a column of metadata from \link{Metadata} used as grouping label for the spatial entities |
|
|
2193 |
#' @param norm if TRUE, the normalized data is used |
|
|
2194 |
#' @param pt.size point size |
|
|
2195 |
#' @param plot.points if TRUE, measures are visualized as points as well. |
|
|
2196 |
#' @param ncol column wise number of plots, for \link{ggarrange} |
|
|
2197 |
#' @param nrow row wise number of plots, for \link{ggarrange} |
|
|
2198 |
#' |
|
|
2199 |
#' @import ggplot2 |
|
|
2200 |
#' @importFrom data.table data.table melt |
|
|
2201 |
#' |
|
|
2202 |
#' @export |
|
|
2203 |
#' |
|
|
2204 |
vrViolinPlot <- function(object, features = NULL, assay = NULL, group.by = "Sample", |
|
|
2205 |
norm = TRUE, pt.size = 0.5, plot.points = TRUE, ncol = 2, nrow = NULL){ |
|
|
2206 |
|
|
|
2207 |
# check object |
|
|
2208 |
if(!inherits(object, "VoltRon")) |
|
|
2209 |
stop("Please provide a VoltRon object!") |
|
|
2210 |
|
|
|
2211 |
# features |
|
|
2212 |
if(is.null(features)) |
|
|
2213 |
stop("You have to define at least one feature") |
|
|
2214 |
|
|
|
2215 |
# sample metadata |
|
|
2216 |
sample.metadata <- SampleMetadata(object) |
|
|
2217 |
|
|
|
2218 |
# get entity type and metadata |
|
|
2219 |
metadata <- Metadata(object, assay = assay) |
|
|
2220 |
|
|
|
2221 |
# get data |
|
|
2222 |
if(any(features %in% vrFeatures(object, assay = assay))){ |
|
|
2223 |
selected_features <- features[features %in% vrFeatures(object, assay = assay)] |
|
|
2224 |
violindata <- vrData(object, features = selected_features, assay = assay, norm = norm) |
|
|
2225 |
} |
|
|
2226 |
|
|
|
2227 |
# get feature data |
|
|
2228 |
datax <- lapply(features, function(x){ |
|
|
2229 |
if(x %in% vrFeatures(object, assay = assay)){ |
|
|
2230 |
return(violindata[x,]) |
|
|
2231 |
} else if(x %in% colnames(metadata)){ |
|
|
2232 |
return(metadata[,x]) |
|
|
2233 |
} else { |
|
|
2234 |
stop("Please provide feature names which reside in either the data or metadata slots!") |
|
|
2235 |
} |
|
|
2236 |
}) |
|
|
2237 |
datax <- do.call(cbind, datax) |
|
|
2238 |
colnames(datax) <- features |
|
|
2239 |
|
|
|
2240 |
# assays |
|
|
2241 |
assays <- stringr::str_extract(rownames(metadata), "Assay[0-9]+$") |
|
|
2242 |
assay_title <- apply(sample.metadata[assays,], 1, function(x) paste(x["Sample"], x["Layer"], x["Assay"], sep = "|")) |
|
|
2243 |
|
|
|
2244 |
# violin plot |
|
|
2245 |
ggplotdatax <- data.frame(datax, |
|
|
2246 |
group.by = as.factor(metadata[[group.by]]), |
|
|
2247 |
assay_title = assay_title, |
|
|
2248 |
spatialpoints = rownames(metadata)) |
|
|
2249 |
# ggplotdatax <- reshape2::melt(ggplotdatax, id.var = c("group.by", "assay_title", "spatialpoints")) |
|
|
2250 |
ggplotdatax <- data.table::melt(data.table::data.table(ggplotdatax), id.var = c("group.by", "assay_title", "spatialpoints")) |
|
|
2251 |
|
|
|
2252 |
# visualize points on violin |
|
|
2253 |
if(plot.points){ |
|
|
2254 |
gg <- ggplot(ggplotdatax, aes(x = group.by, y = value, color = group.by)) + |
|
|
2255 |
geom_violin() + |
|
|
2256 |
geom_point(size = pt.size, position = position_jitter()) + |
|
|
2257 |
guides(fill = guide_legend(show = FALSE), color = guide_legend(title = group.by, override.aes=list(size = 2))) |
|
|
2258 |
} else { |
|
|
2259 |
gg <- ggplot(ggplotdatax, aes(x = group.by, y = value, color = group.by, fill = group.by)) + |
|
|
2260 |
geom_violin() + |
|
|
2261 |
guides(color = guide_legend(title = group.by, override.aes=list(size = 2)), fill = guide_legend(title = group.by, override.aes=list(size = 2))) |
|
|
2262 |
} |
|
|
2263 |
|
|
|
2264 |
# theme |
|
|
2265 |
gg <- gg + |
|
|
2266 |
theme(axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1)) + |
|
|
2267 |
ylab("") + xlab(group.by) |
|
|
2268 |
|
|
|
2269 |
if(length(features) > 1){ |
|
|
2270 |
if(length(gg) < ncol) ncol <- length(gg) |
|
|
2271 |
gg <- gg + facet_wrap(.~variable, ncol = ncol, nrow = ceiling(length(gg)/ncol), scales = "free_y") |
|
|
2272 |
} else { |
|
|
2273 |
gg <- gg + labs(title = features) |
|
|
2274 |
} |
|
|
2275 |
|
|
|
2276 |
return(gg) |
|
|
2277 |
} |
|
|
2278 |
|
|
|
2279 |
#### |
|
|
2280 |
# ROI Plots #### |
|
|
2281 |
#### |
|
|
2282 |
|
|
|
2283 |
#' vrBarPlot |
|
|
2284 |
#' |
|
|
2285 |
#' @param object a VoltRon object |
|
|
2286 |
#' @param features a set of features to be visualized, either from \link{vrFeatures} of raw or normalized data or columns of the \link{Metadata}. |
|
|
2287 |
#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. |
|
|
2288 |
#' if NULL, the default assay will be used, see \link{vrMainAssay}. |
|
|
2289 |
#' @param x.label labels of the x axis |
|
|
2290 |
#' @param group.by a column of metadata from \link{Metadata} used as grouping label for the spatial entities |
|
|
2291 |
#' @param split.by the column to split the barplots by |
|
|
2292 |
#' @param norm if TRUE, the normalized data is used |
|
|
2293 |
#' @param log if TRUE, data features (excluding metadata features) will be log transformed |
|
|
2294 |
#' @param ncol column wise number of plots, for \link{ggarrange} |
|
|
2295 |
#' @param nrow row wise number of plots, for \link{ggarrange} |
|
|
2296 |
#' |
|
|
2297 |
#' @import ggplot2 |
|
|
2298 |
#' @importFrom data.table data.table melt |
|
|
2299 |
#' |
|
|
2300 |
#' @export |
|
|
2301 |
vrBarPlot <- function(object, features = NULL, assay = NULL, x.label = NULL, group.by = "Sample", |
|
|
2302 |
split.by = NULL, norm = TRUE, log = FALSE, ncol = 2, nrow = NULL){ |
|
|
2303 |
|
|
|
2304 |
# check object |
|
|
2305 |
if(!inherits(object, "VoltRon")) |
|
|
2306 |
stop("Please provide a VoltRon object!") |
|
|
2307 |
|
|
|
2308 |
# features |
|
|
2309 |
if(is.null(features)) |
|
|
2310 |
stop("You have to define at least one feature") |
|
|
2311 |
|
|
|
2312 |
# sample metadata |
|
|
2313 |
sample.metadata <- SampleMetadata(object) |
|
|
2314 |
|
|
|
2315 |
# get assay names |
|
|
2316 |
assay_names <- vrAssayNames(object, assay = assay) |
|
|
2317 |
|
|
|
2318 |
# get data |
|
|
2319 |
barplotdata <- vrData(object, assay = assay, norm = norm) |
|
|
2320 |
if(log) |
|
|
2321 |
barplotdata <- log1p(barplotdata) |
|
|
2322 |
|
|
|
2323 |
# get entity type and metadata |
|
|
2324 |
assay_types <- vrAssayTypes(object, assay = assay) |
|
|
2325 |
if(unique(assay_types) %in% c("spot","cell")){ |
|
|
2326 |
stop("vrBarPlot can only be used for ROI assays") |
|
|
2327 |
} else { |
|
|
2328 |
metadata <- Metadata(object, type = "ROI") |
|
|
2329 |
metadata <- subset_metadata(metadata, assays = assay_names) |
|
|
2330 |
} |
|
|
2331 |
|
|
|
2332 |
# get feature data |
|
|
2333 |
datax <- lapply(features, function(x){ |
|
|
2334 |
if(x %in% rownames(barplotdata)){ |
|
|
2335 |
return(as.vector(as(barplotdata[x,,drop = FALSE], "dgCMatrix"))) |
|
|
2336 |
} else if(x %in% colnames(metadata)){ |
|
|
2337 |
return(as.vector(metadata[,x])) |
|
|
2338 |
} else{ |
|
|
2339 |
stop("Feature '", x, "' cannot be found in data or metadata!") |
|
|
2340 |
} |
|
|
2341 |
}) |
|
|
2342 |
datax <- as.data.frame(do.call(cbind, datax)) |
|
|
2343 |
colnames(datax) <- features |
|
|
2344 |
|
|
|
2345 |
# get assay titles |
|
|
2346 |
assays <- stringr::str_extract(vrSpatialPoints(object, assay = assay_names), "Assay[0-9]+$") |
|
|
2347 |
assay_title <- apply(sample.metadata[assays,], 1, function(x) paste(x["Sample"], x["Layer"], x["Assay"], sep = "|")) |
|
|
2348 |
|
|
|
2349 |
# labels and groups |
|
|
2350 |
if(is.null(x.label)) { |
|
|
2351 |
if(is.null(rownames(metadata))){ |
|
|
2352 |
x.labels <- factor(metadata$id) |
|
|
2353 |
} else { |
|
|
2354 |
x.labels <- factor(rownames(metadata)) |
|
|
2355 |
} |
|
|
2356 |
} else { |
|
|
2357 |
if(x.label %in% colnames(metadata)){ |
|
|
2358 |
x.labels <- factor(as.vector(metadata[[x.label]])) |
|
|
2359 |
} else { |
|
|
2360 |
stop("Column '", x.label, "' cannot be found in metadata!") |
|
|
2361 |
} |
|
|
2362 |
} |
|
|
2363 |
if(group.by %in% colnames(metadata)){ |
|
|
2364 |
group.by.col <- factor(as.vector(metadata[[group.by]])) |
|
|
2365 |
} else { |
|
|
2366 |
stop("Column '", group.by, "' cannot be found in metadata!") |
|
|
2367 |
} |
|
|
2368 |
|
|
|
2369 |
# plotting data |
|
|
2370 |
if(!is.null(rownames(metadata))){ |
|
|
2371 |
spatialpoints <- rownames(metadata) |
|
|
2372 |
} else { |
|
|
2373 |
spatialpoints <- metadata$id |
|
|
2374 |
} |
|
|
2375 |
if(is.null(split.by)){ |
|
|
2376 |
ggplotdatax <- data.frame(datax, |
|
|
2377 |
x.label = x.labels, |
|
|
2378 |
group.by = group.by.col, |
|
|
2379 |
assay_title = assay_title, |
|
|
2380 |
spatialpoints = spatialpoints) |
|
|
2381 |
ggplotdatax <- data.table::melt(data.table::data.table(ggplotdatax), id.var = c("x.label", "assay_title", "group.by", "spatialpoints")) |
|
|
2382 |
gg <- ggplot(ggplotdatax, aes(x = x.label, y = value, |
|
|
2383 |
fill = factor(group.by, levels = unique(group.by)))) + |
|
|
2384 |
geom_bar(stat = "identity") + |
|
|
2385 |
theme(axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1)) + |
|
|
2386 |
ylab("") + xlab(x.label) + |
|
|
2387 |
guides(fill = guide_legend(title = group.by)) |
|
|
2388 |
|
|
|
2389 |
if(length(features) > 1){ |
|
|
2390 |
if(length(gg) < ncol) ncol <- length(gg) |
|
|
2391 |
gg <- gg + facet_wrap(.~variable, ncol = ncol, nrow = ceiling(length(features)/ncol), scales = "free") |
|
|
2392 |
return(gg) |
|
|
2393 |
} else { |
|
|
2394 |
gg <- gg + labs(title = features) |
|
|
2395 |
return(gg) |
|
|
2396 |
} |
|
|
2397 |
} else { |
|
|
2398 |
|
|
|
2399 |
# check split.by column name |
|
|
2400 |
if(split.by %in% colnames(metadata)){ |
|
|
2401 |
split.by.col <- factor(as.vector(metadata[[split.by]])) |
|
|
2402 |
} else { |
|
|
2403 |
stop("Column '", split.by, "' cannot be found in metadata!") |
|
|
2404 |
} |
|
|
2405 |
|
|
|
2406 |
# make ggplot |
|
|
2407 |
ggplotdatax <- data.frame(datax, |
|
|
2408 |
x.label = x.labels, |
|
|
2409 |
group.by = group.by.col, |
|
|
2410 |
split.by = split.by.col, |
|
|
2411 |
assay_title = assay_title, |
|
|
2412 |
spatialpoints = spatialpoints) |
|
|
2413 |
ggplotdatax <- data.table::melt(data.table::data.table(ggplotdatax), id.var = c("x.label", "assay_title", "group.by", "split.by", "spatialpoints")) |
|
|
2414 |
gg <- ggplot(ggplotdatax, aes(x = x.label, y = value, |
|
|
2415 |
fill = factor(group.by, levels = unique(group.by)))) + |
|
|
2416 |
geom_bar(stat = "identity") + |
|
|
2417 |
theme(axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1)) + |
|
|
2418 |
ylab("") + xlab(x.label) + |
|
|
2419 |
guides(fill = guide_legend(title = group.by)) |
|
|
2420 |
|
|
|
2421 |
if(length(features) > 1){ |
|
|
2422 |
gg <- gg + facet_grid(variable~split.by, scales = "free", space = "free_x") |
|
|
2423 |
return(gg) |
|
|
2424 |
} else { |
|
|
2425 |
if(length(gg) < ncol) ncol <- length(gg) |
|
|
2426 |
gg <- gg + facet_wrap(.~split.by, ncol = ncol, nrow = ceiling(length(unique(split.by.col))/ncol), scales = "free_x") |
|
|
2427 |
return(gg) |
|
|
2428 |
} |
|
|
2429 |
} |
|
|
2430 |
} |
|
|
2431 |
|
|
|
2432 |
#' vrPercentagePlot |
|
|
2433 |
#' |
|
|
2434 |
#' @param object a VoltRon object |
|
|
2435 |
#' @param assay assay name (exp: Assay1) or assay class (exp: Visium, Xenium), see \link{SampleMetadata}. |
|
|
2436 |
#' if NULL, the default assay will be used, see \link{vrMainAssay}. |
|
|
2437 |
#' @param x.label labels of the x axis |
|
|
2438 |
#' @param split.by the column to split the barplots by |
|
|
2439 |
#' @param split.method either facet_grid or facet_wrap, not affected if \code{split.by} is \code{NULL} |
|
|
2440 |
#' @param ncol column wise number of plots, for \link{ggarrange} |
|
|
2441 |
#' @param nrow row wise number of plots, for \link{ggarrange} |
|
|
2442 |
#' |
|
|
2443 |
#' @import ggplot2 |
|
|
2444 |
#' @importFrom data.table data.table melt |
|
|
2445 |
#' |
|
|
2446 |
#' @export |
|
|
2447 |
#' |
|
|
2448 |
vrProportionPlot <- function(object, assay = NULL, x.label = NULL, |
|
|
2449 |
split.by = NULL, split.method = "facet_wrap", ncol = 2, nrow = NULL){ |
|
|
2450 |
|
|
|
2451 |
# check object |
|
|
2452 |
if(!inherits(object, "VoltRon")) |
|
|
2453 |
stop("Please provide a VoltRon object!") |
|
|
2454 |
|
|
|
2455 |
# sample metadata |
|
|
2456 |
sample.metadata <- SampleMetadata(object) |
|
|
2457 |
|
|
|
2458 |
# get assay names |
|
|
2459 |
assay_names <- vrAssayNames(object, assay = assay) |
|
|
2460 |
|
|
|
2461 |
# get data |
|
|
2462 |
barplotdata <- vrData(object, assay = assay, norm = FALSE) |
|
|
2463 |
|
|
|
2464 |
# get entity type and metadata |
|
|
2465 |
assay_types <- vrAssayTypes(object, assay = assay) |
|
|
2466 |
if(unique(assay_types) %in% c("spot","cell")){ |
|
|
2467 |
stop("vrProportionPlot can only be used for ROI assays") |
|
|
2468 |
} else { |
|
|
2469 |
metadata <- Metadata(object, type = "ROI") |
|
|
2470 |
metadata <- subset_metadata(metadata, assays = assay_names) |
|
|
2471 |
} |
|
|
2472 |
|
|
|
2473 |
# violin plot |
|
|
2474 |
assays <- stringr::str_extract(vrSpatialPoints(object, assay = assay_names), "Assay[0-9]+$") |
|
|
2475 |
assay_title <- apply(sample.metadata[assays,], 1, function(x) paste(x["Sample"], x["Layer"], x["Assay"], sep = "|")) |
|
|
2476 |
|
|
|
2477 |
# labels and groups |
|
|
2478 |
if(is.null(x.label)) { |
|
|
2479 |
if(is.null(rownames(metadata))){ |
|
|
2480 |
x.labels <- factor(metadata$id) |
|
|
2481 |
} else { |
|
|
2482 |
x.labels <- factor(rownames(metadata)) |
|
|
2483 |
} |
|
|
2484 |
} else { |
|
|
2485 |
if(x.label %in% colnames(metadata)){ |
|
|
2486 |
x.labels <- factor(as.vector(metadata[[x.label]])) |
|
|
2487 |
} else { |
|
|
2488 |
stop("Column '", x.label, "' cannot be found in metadata!") |
|
|
2489 |
} |
|
|
2490 |
} |
|
|
2491 |
|
|
|
2492 |
# plotting data |
|
|
2493 |
if(!is.null(rownames(metadata))){ |
|
|
2494 |
spatialpoints <- rownames(metadata) |
|
|
2495 |
} else { |
|
|
2496 |
spatialpoints <- metadata$id |
|
|
2497 |
} |
|
|
2498 |
ggplotdatax <- data.frame(t(barplotdata), |
|
|
2499 |
x.label = x.label, |
|
|
2500 |
assay_title = assay_title, |
|
|
2501 |
spatialpoints = spatialpoints) |
|
|
2502 |
ggplotdatax <- data.table::melt(data.table::data.table(ggplotdatax), id.var = c("x.label", "assay_title", "spatialpoints")) |
|
|
2503 |
ggplotdatax <- ggplotdatax[ggplotdatax$value > 0,] |
|
|
2504 |
gg <- ggplot(ggplotdatax, aes(x = x.label, y = value, fill = variable)) + |
|
|
2505 |
geom_bar(stat = "identity") + |
|
|
2506 |
theme(axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1)) + |
|
|
2507 |
ylab("") + xlab("") + |
|
|
2508 |
guides(fill = guide_legend(title = "")) |
|
|
2509 |
|
|
|
2510 |
if(is.null(split.by)){ |
|
|
2511 |
ggplotdatax <- data.frame(t(barplotdata), |
|
|
2512 |
x.label = x.label, |
|
|
2513 |
assay_title = assay_title, |
|
|
2514 |
spatialpoints = rownames(metadata)) |
|
|
2515 |
ggplotdatax <- data.table::melt(data.table::data.table(ggplotdatax), id.var = c("x.label", "assay_title", "spatialpoints")) |
|
|
2516 |
ggplotdatax <- ggplotdatax[ggplotdatax$value > 0,] |
|
|
2517 |
gg <- ggplot(ggplotdatax, aes(x = x.label, y = value, fill = variable)) + |
|
|
2518 |
geom_bar(stat = "identity") + |
|
|
2519 |
theme(axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1)) + |
|
|
2520 |
ylab("") + xlab("") + |
|
|
2521 |
guides(fill = guide_legend(title = "")) |
|
|
2522 |
} else { |
|
|
2523 |
|
|
|
2524 |
# check split.by column name |
|
|
2525 |
if(split.by %in% colnames(metadata)){ |
|
|
2526 |
split.by.col <- factor(metadata[[split.by]]) |
|
|
2527 |
} else { |
|
|
2528 |
stop("Column '", split.by, "' cannot be found in metadata!") |
|
|
2529 |
} |
|
|
2530 |
ggplotdatax <- data.frame(t(barplotdata), |
|
|
2531 |
x.label = x.label, |
|
|
2532 |
assay_title = assay_title, |
|
|
2533 |
split.by = split.by.col, |
|
|
2534 |
spatialpoints = spatialpoints) |
|
|
2535 |
ggplotdatax <- data.table::melt(data.table::data.table(ggplotdatax), id.var = c("x.label", "assay_title", "split.by", "spatialpoints")) |
|
|
2536 |
ggplotdatax <- ggplotdatax[ggplotdatax$value > 0,] |
|
|
2537 |
gg <- ggplot(ggplotdatax, aes(x = x.label, y = value, fill = variable)) + |
|
|
2538 |
geom_bar(stat = "identity") + |
|
|
2539 |
theme(axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1)) + |
|
|
2540 |
ylab("") + xlab("") + |
|
|
2541 |
guides(fill = guide_legend(title = "")) |
|
|
2542 |
|
|
|
2543 |
# split |
|
|
2544 |
if(length(gg) < ncol) ncol <- length(gg) |
|
|
2545 |
if(split.method == "facet_wrap"){ |
|
|
2546 |
gg <- gg + facet_wrap(.~split.by, ncol = ncol, nrow = ceiling(length(unique(split.by.col))/ncol), scales = "free_x") |
|
|
2547 |
} else { |
|
|
2548 |
gg <- gg + facet_grid(.~split.by, scales = "free_x", space = "free_x", ) |
|
|
2549 |
} |
|
|
2550 |
} |
|
|
2551 |
return(gg) |
|
|
2552 |
} |
|
|
2553 |
|
|
|
2554 |
#### |
|
|
2555 |
# Rasterization #### |
|
|
2556 |
#### |
|
|
2557 |
|
|
|
2558 |
#' vrGroupPlotTiling |
|
|
2559 |
#' |
|
|
2560 |
#' Plotting a tiled version of the vrEmbeddingPlot and vrSpatialPlot |
|
|
2561 |
#' |
|
|
2562 |
#' @param g the ggplot figure |
|
|
2563 |
#' @param data the data frame with coordinates and group identities |
|
|
2564 |
#' @param group.by a column of metadata from \link{Metadata} used as grouping label for the spatial entities |
|
|
2565 |
#' @param n.tile should points be aggregated into tiles before visualization (see \link{geom_tile}). Applicable only for cells and molecules |
|
|
2566 |
#' @param alpha alpha level of colors of visualized points and segments |
|
|
2567 |
#' |
|
|
2568 |
#' @import ggplot2 |
|
|
2569 |
#' |
|
|
2570 |
#' @noRd |
|
|
2571 |
vrGroupPlotTiling <- function(g, data, group.by, n.tile, alpha = 1){ |
|
|
2572 |
g + stat_bin_2d(mapping = aes(x = .data[["x"]], y = .data[["y"]], fill = .data[[group.by]], color = .data[[group.by]]), |
|
|
2573 |
data = data, bins = n.tile, drop = TRUE, alpha = alpha) |
|
|
2574 |
} |
|
|
2575 |
|
|
|
2576 |
#' vrFeaturePlotTiling |
|
|
2577 |
#' |
|
|
2578 |
#' Plotting a tiled version of the vrEmbeddingFeaturePlot and vrSpatialFeaturePlot |
|
|
2579 |
#' |
|
|
2580 |
#' @param g the ggplot figure |
|
|
2581 |
#' @param data the data frame with coordinates and group identities |
|
|
2582 |
#' @param legend_title the legend title of the single plot |
|
|
2583 |
#' @param n.tile should points be aggregated into tiles before visualization (see \link{geom_tile}). Applicable only for cells and molecules |
|
|
2584 |
#' @param alpha alpha level of colors of visualized points and segments |
|
|
2585 |
#' @param type embedding or spatial, changes the color scheme |
|
|
2586 |
#' |
|
|
2587 |
#' @import ggplot2 |
|
|
2588 |
#' |
|
|
2589 |
#' @noRd |
|
|
2590 |
vrFeaturePlotTiling <- function(g, data, legend_title, n.tile, alpha = 1, limits = NULL, type = "embedding"){ |
|
|
2591 |
|
|
|
2592 |
# get summary per title |
|
|
2593 |
gplot <- g + stat_summary_2d(mapping = aes(x = x, y = y, z = score), fun = mean, |
|
|
2594 |
data = data, geom = "tile", bins = n.tile, drop = TRUE, alpha = alpha) |
|
|
2595 |
|
|
|
2596 |
# check if limits are defined, typically coming from vrEmbeddingFeaturePlot |
|
|
2597 |
if(is.null(limits)){ |
|
|
2598 |
hex_count_data <- suppressWarnings(ggplot_build(gplot)$data) |
|
|
2599 |
hex_count_data <- hex_count_data[[length(hex_count_data)]] |
|
|
2600 |
midpoint <- max(hex_count_data$value)/2 |
|
|
2601 |
} |
|
|
2602 |
|
|
|
2603 |
# color scheme for either spatial or embedding feature plot |
|
|
2604 |
if(type == "spatial"){ |
|
|
2605 |
gplot <- gplot + |
|
|
2606 |
scale_fill_gradientn(name = legend_title, |
|
|
2607 |
colors=c("dodgerblue2", "white", "yellow3"), |
|
|
2608 |
values=rescale_numeric(c(min(hex_count_data$value), midpoint, max(hex_count_data$value)), |
|
|
2609 |
limits = c(min(hex_count_data$value), max(hex_count_data$value)))) |
|
|
2610 |
} else{ |
|
|
2611 |
gplot <- gplot + |
|
|
2612 |
scale_fill_gradientn(name = legend_title, |
|
|
2613 |
colors=c("grey97", "blue"), |
|
|
2614 |
values=rescale_numeric(c(limits[1], limits[2])), limits = limits) |
|
|
2615 |
} |
|
|
2616 |
|
|
|
2617 |
# return |
|
|
2618 |
gplot |
|
|
2619 |
} |
|
|
2620 |
|
|
|
2621 |
#' @param n number of colors |
|
|
2622 |
#' |
|
|
2623 |
#' @noRd |
|
|
2624 |
get_rasterization_colors <- function(n){ |
|
|
2625 |
colors <- c("darkblue",'darkred', "yellow2", "darkgreen", "darkorange", 'darkmagenta', "brown") |
|
|
2626 |
colors[seq_len(n)] |
|
|
2627 |
} |
|
|
2628 |
|
|
|
2629 |
#### |
|
|
2630 |
# Plotting style |
|
|
2631 |
#### |
|
|
2632 |
|
|
|
2633 |
# this function is deprecated |
|
|
2634 |
addEmbeddingPlotStyle <- function(embedding){ |
|
|
2635 |
suppressWarnings( |
|
|
2636 |
theme_classic() + |
|
|
2637 |
xlab(paste0(toupper(embedding), "_1")) + ylab(paste0(toupper(embedding), "_2")) + |
|
|
2638 |
theme(plot.title = element_text(hjust = 0.5, margin=margin(0,0,0,0)), |
|
|
2639 |
panel.grid.minor = element_blank(), panel.grid.major = element_blank(), |
|
|
2640 |
legend.margin = margin(0,0,0,0), panel.background = element_blank()) |
|
|
2641 |
) |
|
|
2642 |
} |