Diff of /R/visualization.R [000000] .. [413088]

Switch to unified view

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
}