#' The ReducedDimensionPlot panel
#'
#' The ReducedDimensionPlot is a panel class for creating a \linkS4class{ColumnDotPlot} where the coordinates of each column/sample are taken from the \code{\link{reducedDims}} of a \linkS4class{SingleCellExperiment} object.
#' It provides slots and methods to specify which dimensionality reduction result to use and to create the data.frame with the coordinates of the specified results for plotting.
#'
#' @section ReducedDimensionPlot slot overview:
#' The following slots control the dimensionality reduction result that is used:
#' \itemize{
#' \item \code{Type}, a string specifying the name of the dimensionality reduction result.
#' If \code{NA}, defaults to the first entry of \code{\link{reducedDims}}.
#' \item \code{XAxis}, integer scalar specifying the dimension to plot on the x-axis.
#' Defaults to 1.
#' \item \code{YAxis}, integer scalar specifying the dimension to plot on the y-axis.
#' Defaults to 2.
#' }
#'
#' In addition, this class inherits all slots from its parent \linkS4class{ColumnDotPlot}, \linkS4class{DotPlot} and \linkS4class{Panel} classes.
#'
#' @section Constructor:
#' \code{ReducedDimensionPlot(...)} creates an instance of a ReducedDimensionPlot class, where any slot and its value can be passed to \code{...} as a named argument.
#'
#' @section Supported methods:
#' In the following code snippets, \code{x} is an instance of a \linkS4class{ReducedDimensionPlot} class.
#' Refer to the documentation for each method for more details on the remaining arguments.
#'
#' For setting up data values:
#' \itemize{
#' \item \code{\link{.cacheCommonInfo}(x)} adds a \code{"ReducedDimensionPlot"} entry containing \code{valid.reducedDim.names}, a character vector of names of valid dimensionality reduction results (i.e., at least one dimension).
#' This will also call the equivalent \linkS4class{ColumnDotPlot} method.
#' \item \code{\link{.refineParameters}(x, se)} replaces \code{NA} values in \code{RedDimType} with the first valid dimensionality reduction result name in \code{se}.
#' This will also call the equivalent \linkS4class{ColumnDotPlot} method for further refinements to \code{x}.
#' If no dimensionality reduction results are available, \code{NULL} is returned instead.
#' }
#'
#' For defining the interface:
#' \itemize{
#' \item \code{\link{.defineDataInterface}(x, se, select_info)} returns a list of interface elements for manipulating all slots described above.
#' \item \code{\link{.panelColor}(x)} will return the specified default color for this panel class.
#' }
#'
#' For monitoring reactive expressions:
#' \itemize{
#' \item \code{\link{.createObservers}(x, se, input, session, pObjects, rObjects)} sets up observers for all slots described above and in the parent classes.
#' This will also call the equivalent \linkS4class{ColumnDotPlot} method.
#' }
#'
#' For defining the panel name:
#' \itemize{
#' \item \code{\link{.fullName}(x)} will return \code{"Reduced dimension plot"}.
#' }
#'
#' For creating the plot:
#' \itemize{
#' \item \code{\link{.generateDotPlotData}(x, envir)} will create a data.frame of reduced dimension coordinates in \code{envir}.
#' It will return the commands required to do so as well as a list of labels.
#' }
#'
#' For documentation:
#' \itemize{
#' \item \code{\link{.definePanelTour}(x)} returns an data.frame containing a panel-specific tour.
#' }
#'
#' Subclasses do not have to provide any methods, as this is a concrete class.
#'
#' @author Aaron Lun
#'
#' @seealso
#' \linkS4class{ColumnDotPlot}, for the immediate parent class.
#'
#' @examples
#' #################
#' # For end-users #
#' #################
#'
#' x <- ReducedDimensionPlot()
#' x[["Type"]]
#' x[["Type"]] <- "TSNE"
#'
#' ##################
#' # For developers #
#' ##################
#'
#' library(scater)
#' sce <- mockSCE()
#' sce <- logNormCounts(sce)
#'
#' # Spits out a NULL and a warning if no reducedDims are available.
#' sce0 <- .cacheCommonInfo(x, sce)
#' .refineParameters(x, sce0)
#'
#' # Replaces the default with something sensible.
#' sce <- runPCA(sce)
#' sce0 <- .cacheCommonInfo(x, sce)
#' .refineParameters(x, sce0)
#'
#' @docType methods
#' @aliases ReducedDimensionPlot ReducedDimensionPlot-class
#' initialize,ReducedDimensionPlot-method
#' .refineParameters,ReducedDimensionPlot-method
#' .cacheCommonInfo,ReducedDimensionPlot-method
#' .defineDataInterface,ReducedDimensionPlot-method
#' .createObservers,ReducedDimensionPlot-method
#' .fullName,ReducedDimensionPlot-method
#' .panelColor,ReducedDimensionPlot-method
#' .generateDotPlotData,ReducedDimensionPlot-method
#' .definePanelTour,ReducedDimensionPlot-method
#'
#' @name ReducedDimensionPlot-class
NULL
#' @export
ReducedDimensionPlot <- function(...) {
new("ReducedDimensionPlot", ...)
}
#' @export
#' @importFrom methods callNextMethod
setMethod("initialize", "ReducedDimensionPlot", function(.Object, ...) {
args <- list(...)
args <- .emptyDefault(args, .redDimType, NA_character_)
args <- .emptyDefault(args, .redDimXAxis, 1L)
args <- .emptyDefault(args, .redDimYAxis, 2L)
do.call(callNextMethod, c(list(.Object), args))
})
#' @export
#' @importFrom SingleCellExperiment reducedDimNames reducedDim
#' @importClassesFrom SingleCellExperiment SingleCellExperiment
#' @importFrom methods callNextMethod
setMethod(".cacheCommonInfo", "ReducedDimensionPlot", function(x, se) {
if (!is.null(.getCachedCommonInfo(se, "ReducedDimensionPlot"))) {
return(se)
}
se <- callNextMethod()
if (is(se, "SingleCellExperiment")) {
available <- reducedDimNames(se)
for (y in seq_along(available)) {
if (ncol(reducedDim(se, y))==0L) {
available[y] <- NA_character_
}
}
available <- available[!is.na(available)]
} else {
available <- character(0)
}
.setCachedCommonInfo(se, "ReducedDimensionPlot",
valid.reducedDim.names=available)
})
#' @export
#' @importFrom SingleCellExperiment reducedDim
#' @importFrom methods callNextMethod
setMethod(".refineParameters", "ReducedDimensionPlot", function(x, se) {
x <- callNextMethod()
if (is.null(x)) {
return(NULL)
}
available <- .getCachedCommonInfo(se, "ReducedDimensionPlot")$valid.reducedDim.names
if (!is.na(chosen <- slot(x, .redDimType)) &&
chosen %in% available &&
slot(x, .redDimXAxis) <= ncol(reducedDim(se, chosen)) &&
slot(x, .redDimYAxis) <= ncol(reducedDim(se, chosen)))
{
# All is well, nothing needs to be done here.
} else {
if (length(available)==0L) {
warning(sprintf("no 'reducedDims' with non-zero dimensions for '%s'", class(x)[1]))
return(NULL)
}
y <- available[1]
slot(x, .redDimType) <- y
slot(x, .redDimXAxis) <- 1L
slot(x, .redDimYAxis) <- min(ncol(reducedDim(se, y)), 2L)
}
x
})
#' @importFrom S4Vectors setValidity2
setValidity2("ReducedDimensionPlot", function(object) {
msg <- character(0)
msg <- .singleStringError(msg, object, .redDimType)
for (field in c(.redDimXAxis, .redDimYAxis)) {
if (length(val <- object[[field]])!=1 || is.na(val) || val <= 0L) {
msg <- c(msg, sprintf("'%s' must be a single positive integer", field))
}
}
if (length(msg)>0) {
return(msg)
}
TRUE
})
#' @export
#' @importFrom SingleCellExperiment reducedDim reducedDimNames
#' @importFrom shiny selectInput
#' @importFrom methods callNextMethod
setMethod(".defineDataInterface", "ReducedDimensionPlot", function(x, se, select_info) {
cur_reddim <- slot(x, .redDimType)
max_dim <- ncol(reducedDim(se, cur_reddim))
choices <- seq_len(max_dim)
.addSpecificTour(class(x)[1], .redDimType, function(plot_name) {
data.frame(
rbind(
c(
element=paste0("#", plot_name, "_", .redDimType, " + .selectize-control"),
intro="Here, we can select the type of dimensionality reduction result to show.
The choices are extracted from the <code>reducedDims</code> of a <code>SingleCellExperiment</code> object.
These results should be loaded into the object prior to calling <strong>iSEE</strong> - they are not computed on the fly."
)
)
)
})
.addSpecificTour(class(x)[1], .redDimXAxis, function(plot_name) {
data.frame(
rbind(
c(
element=paste0("#", plot_name, "_", .redDimXAxis, " + .selectize-control"),
intro="Given a particular <code>reducedDim</code> entry to visualize, this field specifies the dimension to show on the x-axis."
)
)
)
})
.addSpecificTour(class(x)[1], .redDimYAxis, function(plot_name) {
data.frame(
rbind(
c(
element=paste0("#", plot_name, "_", .redDimYAxis, " + .selectize-control"),
intro="Given a particular <code>reducedDim</code> entry to visualize, this field specifies the dimension to show on the y-axis."
)
)
)
})
list(
.selectInput.iSEE(x, .redDimType,
label="Type:",
choices=.getCachedCommonInfo(se, "ReducedDimensionPlot")$valid.reducedDim.names,
selected=cur_reddim),
.selectInput.iSEE(x, .redDimXAxis,
label="Dimension 1:",
choices=choices,
selected=slot(x, .redDimXAxis)),
.selectInput.iSEE(x, .redDimYAxis,
label="Dimension 2:",
choices=choices,
selected=slot(x, .redDimYAxis))
)
})
#' @export
#' @importFrom SingleCellExperiment reducedDim
#' @importFrom shiny observeEvent updateSelectInput
#' @importFrom methods callNextMethod
setMethod(".createObservers", "ReducedDimensionPlot", function(x, se, input, session, pObjects, rObjects) {
callNextMethod()
plot_name <- .getEncodedName(x)
.createProtectedParameterObservers(plot_name,
fields=c(.redDimXAxis, .redDimYAxis),
input=input, pObjects=pObjects, rObjects=rObjects)
cur_field <- paste0(plot_name, "_", .redDimType)
dim_fieldX <- paste0(plot_name, "_", .redDimXAxis)
dim_fieldY <- paste0(plot_name, "_", .redDimYAxis)
# nocov start
observeEvent(input[[cur_field]], {
matched_input <- as(input[[cur_field]], typeof(pObjects$memory[[plot_name]][[.redDimType]]))
if (identical(matched_input, pObjects$memory[[plot_name]][[.redDimType]])) {
return(NULL)
}
pObjects$memory[[plot_name]][[.redDimType]] <- matched_input
# Updating the selectInputs as well. This should not trigger re-plotting as the identical() check in the
# corresponding observers should stop the replotting flag from being set.
new_max <- ncol(reducedDim(se, matched_input))
capped_X <- pmin(new_max, pObjects$memory[[plot_name]][[.redDimXAxis]])
capped_Y <- pmin(new_max, pObjects$memory[[plot_name]][[.redDimYAxis]])
pObjects$memory[[plot_name]][[.redDimXAxis]] <- capped_X
pObjects$memory[[plot_name]][[.redDimYAxis]] <- capped_Y
new_choices <- seq_len(new_max)
updateSelectInput(session, dim_fieldX, choices=new_choices, selected=capped_X)
updateSelectInput(session, dim_fieldY, choices=new_choices, selected=capped_Y)
.requestCleanUpdate(plot_name, pObjects, rObjects)
}, ignoreInit=TRUE)
# nocov end
invisible(NULL)
})
#' @export
setMethod(".fullName", "ReducedDimensionPlot", function(x) "Reduced dimension plot")
#' @export
setMethod(".panelColor", "ReducedDimensionPlot", function(x) "#3565AA")
#' @export
setMethod(".generateDotPlotData", "ReducedDimensionPlot", function(x, envir) {
data_cmds <- list()
data_cmds[["reducedDim"]] <- sprintf(
"red.dim <- reducedDim(se, %s);", deparse(slot(x, .redDimType)))
data_cmds[["xy"]] <- sprintf(
"plot.data <- data.frame(X=red.dim[, %i], Y=red.dim[, %i], row.names=colnames(se));",
slot(x, .redDimXAxis), slot(x, .redDimYAxis))
plot_title <- slot(x, .redDimType)
x_lab <- sprintf("Dimension %s", slot(x, .redDimXAxis))
y_lab <- sprintf("Dimension %s", slot(x, .redDimYAxis))
data_cmds <- unlist(data_cmds)
.textEval(data_cmds, envir)
list(commands=data_cmds, labels=list(title=plot_title, X=x_lab, Y=y_lab))
})
#' @export
setMethod(".definePanelTour", "ReducedDimensionPlot", function(x) {
collated <- character(0)
collated <- rbind(
c(paste0("#", .getEncodedName(x)), sprintf("The <font color=\"%s\">Reduced dimension plot</font> panel shows reduced dimensions from a <code>SingleCellExperiment</code> object or one of its subclasses. Here, each point corresponds to a column (usually a cell) of the <code>SingleCellExperiment</code> object.", .getPanelColor(x))),
.addTourStep(x, .dataParamBoxOpen, "The <i>Data parameters</i> box shows the available parameters that can be tweaked in this plot.<br/><br/><strong>Action:</strong> click on this box to open up available options.")
)
rbind(
data.frame(element=collated[,1], intro=collated[,2], stringsAsFactors=FALSE),
callNextMethod()
)
})
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.