#' Generate alevin/alevin-fry summary shiny app
#'
#' Generate a shiny app summarizing the main aspects of an alevin/alevin-fry
#' quantification run. The app generation assumes that alevin has been run
#' with the --dumpFeatures flag to generate the necessary output files.
#'
#' @param baseDir (Only used for alevin output) Path to the output directory
#' from the alevin run (should be the directory containing the
#' \code{alevin} directory).
#' @param mapDir (Only used for alevin-fry output) Path to the output directory
#' from the salmon alevin run (should be the directory containing the
#' \code{map.rad} file).
#' @param permitDir (Only used for alevin-fry output) Path to the output
#' directory from the permit list generation step (should be
#' the directory containing the \code{all_freq.tsv} file).
#' @param quantDir (Only used for alevin-fry output) Path to the output
#' directory from the alevin-fry quantification step (should be
#' the directory containing the \code{alevin} directory).
#' @param simpleafQuantDir (Only used for simpleaf output) Path to the output
#' directory from the simpleaf run (should be the directory containing the
#' \code{af_map} and \code{af_quant} directories).
#' @param sampleId Sample ID, will be used set the title for the app.
#' @param customCBList Named list with custom set(s) of barcodes to provide
#' summary statistics/plots for, in addition to the whitelists generated by
#' alevin.
#' @param addStopButton Logical scalar. If \code{TRUE} (default), will add a
#' dropdown menu with a button to stop the app (by calling
#' \code{shiny::stopApp}) and return a list with the information displayed
#' in the app.
#'
#' @author Charlotte Soneson
#'
#' @name qcShiny
#'
#' @import dplyr
#' @importFrom shiny fluidRow plotOutput renderPlot shinyApp actionButton icon
#' stopApp observeEvent
#' @importFrom shinydashboard dashboardPage dashboardHeader dashboardSidebar
#' box dropdownMenu notificationItem dropdownMenuOutput renderMenu
#' @importFrom DT dataTableOutput datatable renderDataTable
#' @importFrom utils packageVersion
#'
#' @return A shiny app.
#'
#' @examples
#' app <- alevinQCShiny(
#' baseDir = system.file("extdata/alevin_example_v0.14",
#' package = "alevinQC"),
#' sampleId = "example")
#' if (interactive()) {
#' shiny::runApp(app)
#' }
#'
#' app <- alevinFryQCShiny(
#' mapDir = system.file("extdata/alevinfry_example_v0.5.0/map",
#' package = "alevinQC"),
#' permitDir = system.file("extdata/alevinfry_example_v0.5.0/permit",
#' package = "alevinQC"),
#' quantDir = system.file("extdata/alevinfry_example_v0.5.0/quant",
#' package = "alevinQC"),
#' sampleId = "example")
#' if (interactive()) {
#' shiny::runApp(app)
#' }
#'
#' app <- simpleafQCShiny(
#' simpleafQuantDir = system.file("extdata/alevinfry_example_piscem_v0.6.0",
#' package = "alevinQC"),
#' sampleId = "example")
#' if (interactive()) {
#' shiny::runApp(app)
#' }
#'
NULL
#' @rdname qcShiny
#' @export
alevinQCShiny <- function(baseDir, sampleId, customCBList = list(),
addStopButton = TRUE) {
.alevinQCShiny(baseDir = baseDir, mapDir = NULL, permitDir = NULL,
quantDir = NULL, quantMethod = "alevin", sampleId = sampleId,
customCBList = customCBList, addStopButton = addStopButton)
}
#' @rdname qcShiny
#' @export
alevinFryQCShiny <- function(mapDir, permitDir, quantDir, sampleId,
addStopButton = TRUE) {
.alevinQCShiny(baseDir = NULL, mapDir = mapDir, permitDir = permitDir,
quantDir = quantDir, quantMethod = "alevin-fry",
sampleId = sampleId, customCBList = list(),
addStopButton = addStopButton)
}
#' @rdname qcShiny
#' @export
simpleafQCShiny <- function(simpleafQuantDir, sampleId, addStopButton = TRUE) {
mapDir <- file.path(simpleafQuantDir, "af_map")
permitDir <- file.path(simpleafQuantDir, "af_quant")
quantDir <- file.path(simpleafQuantDir, "af_quant")
if (!dir.exists(mapDir)) {
stop("The provided simpleaf quant output directory doesn't contain ",
"the `af_map` folder; Cannot proceed. Please run ",
"alevinFryQCReport() and provide mapDir, permitDir, quantDir ",
"explicitly. If produced by calling `simpleaf quant`, the ",
"permitDir and `quantDir` is the same and is named as `af_quant` ",
"under the simpleaf quant output directory.")
}
if (!dir.exists(quantDir)) {
stop("The provided simpleaf quant output directory doesn't contain ",
"the `af_quant` folder; Cannot proceed. Please run ",
"alevinFryQCReport() and provide mapDir, permitDir, quantDir ",
"explicitly. If produced by calling `simpleaf quant`, the ",
"permitDir and `quantDir` is the same and is named as `af_quant` ",
"under the simpleaf quant output directory.")
}
.alevinQCShiny(baseDir = NULL, mapDir = mapDir, permitDir = permitDir,
quantDir = quantDir, quantMethod = "alevin-fry",
sampleId = sampleId, customCBList = list(),
addStopButton = addStopButton)
}
#' @keywords internal
#' @noRd
.alevinQCShiny <- function(baseDir, mapDir, permitDir, quantDir,
quantMethod, sampleId, customCBList = list(),
addStopButton) {
if (quantMethod == "alevin") {
checkAlevinInputFiles(baseDir)
alevin <- readAlevinQC(baseDir = baseDir, customCBList = customCBList)
firstSelColName <- "inFirstWhiteList"
wlName <- "initial whitelist"
countCol <- "collapsedFreq"
} else if (quantMethod == "alevin-fry") {
checkAlevinFryInputFiles(mapDir = mapDir, permitDir = permitDir,
quantDir = quantDir)
alevin <- readAlevinFryQC(mapDir = mapDir, permitDir = permitDir,
quantDir = quantDir)
firstSelColName <- "inPermitList"
wlName <- "permitlist"
countCol <- "nbrMappedUMI"
}
pLayout <- shinydashboard::dashboardPage(
skin = "red",
shinydashboard::dashboardHeader(
title = paste0("alevinQC (v",
utils::packageVersion("alevinQC"), "), ",
sampleId),
titleWidth = (10 + nchar(sampleId)) * 20,
shinydashboard::dropdownMenuOutput("stopAppMenu")
),
shinydashboard::dashboardSidebar(disable = TRUE),
shinydashboard::dashboardBody(
shiny::fluidRow(
shinydashboard::box(
width = 6,
title = paste0("Version info, ", quantMethod, " run"),
DT::dataTableOutput("versionTable")
),
shinydashboard::box(
width = 6,
title = "Summary tables",
if (quantMethod == "alevin") DT::dataTableOutput("summaryTableFull"),
if (quantMethod == "alevin") DT::dataTableOutput("summaryTableInitialWl"),
if (quantMethod == "alevin") DT::dataTableOutput("summaryTableFinalWl"),
if (quantMethod == "alevin-fry") DT::dataTableOutput("summaryTablePl"),
shiny::uiOutput("summaryTablesCustomCBs")
)
),
shiny::fluidRow(
shinydashboard::box(
width = 4,
title = "Knee plot, initial whitelist determination",
shiny::plotOutput("rawCBKneePlot")
),
shinydashboard::box(
width = 4,
title = "Barcode collapsing",
shiny::plotOutput("barcodeCollapsePlot")
),
shinydashboard::box(
width = 4,
title = "Knee plot, number of genes per cell",
shiny::plotOutput("nbrGenesKneePlot")
)
),
shiny::fluidRow(
shinydashboard::box(
width = 12,
title = paste0("Quantification summary (", wlName, ")"),
shiny::plotOutput("quantPlot"),
shiny::uiOutput("quantPlotsCustomCBs")
)
),
shiny::fluidRow(
shinydashboard::box(
width = 12,
title = paste0("Selected summary distributions (",
wlName, ")"),
shiny::plotOutput("histPlot"),
shiny::uiOutput("histPlotsCustomCBs")
)
)
)
)
server_function <- function(input, output, session) { # nocov start
## ----------------------------------------------------------------- ##
## Version table
## ----------------------------------------------------------------- ##
output$versionTable <- DT::renderDataTable(
DT::datatable(
alevin$versionTable,
colnames = "",
options = list(scrollX = TRUE)
)
)
## ----------------------------------------------------------------- ##
## Standard summary tables
## ----------------------------------------------------------------- ##
output$summaryTableFull <- DT::renderDataTable(
DT::datatable(
alevin$summaryTables$fullDataset,
colnames = "",
options = list(scrollX = TRUE)
)
)
output$summaryTableInitialWl <- DT::renderDataTable(
DT::datatable(
alevin$summaryTables$initialWhitelist,
colnames = "",
options = list(scrollX = TRUE)
)
)
output$summaryTableFinalWl <- DT::renderDataTable(
DT::datatable(
alevin$summaryTables$finalWhitelist,
colnames = "",
options = list(scrollX = TRUE)
)
)
output$summaryTablePl <- DT::renderDataTable(
DT::datatable(
alevin$summaryTables$permitlist,
colnames = "",
options = list(scrollX = TRUE)
)
)
## ----------------------------------------------------------------- ##
## Custom CB summary tables
## ----------------------------------------------------------------- ##
lapply(seq_along(customCBList), function(i) {
id <- paste0("dt", i)
output[[id]] <- DT::renderDataTable(
DT::datatable(
alevin$summaryTables[[paste0("customCB__",
names(customCBList)[i])]],
colnames = "",
options = list(scrollX = TRUE)
)
)
})
output$summaryTablesCustomCBs <- shiny::renderUI({
lapply(as.list(seq_along(customCBList)), function(i) {
id <- paste0("dt", i)
DT::dataTableOutput(id)
})
})
## ----------------------------------------------------------------- ##
## Standard plots
## ----------------------------------------------------------------- ##
output$rawCBKneePlot <- shiny::renderPlot(
plotAlevinKneeRaw(alevin$cbTable, firstSelColName = firstSelColName)
)
output$barcodeCollapsePlot <- shiny::renderPlot(
plotAlevinBarcodeCollapse(alevin$cbTable,
firstSelColName = firstSelColName,
countCol = countCol)
)
output$nbrGenesKneePlot <- shiny::renderPlot(
plotAlevinKneeNbrGenes(alevin$cbTable,
firstSelColName = firstSelColName)
)
## ----------------------------------------------------------------- ##
## Standard quant plots
## ----------------------------------------------------------------- ##
output$quantPlot <- shiny::renderPlot(
if (quantMethod == "alevin") {
plotAlevinQuant(alevin$cbTable, colName = "inFinalWhiteList",
cbName = "final whitelist",
firstSelColName = firstSelColName)
} else if (quantMethod == "alevin-fry") {
plotAlevinQuant(alevin$cbTable, colName = "inPermitList",
cbName = "permitlist",
firstSelColName = firstSelColName)
}
)
## ----------------------------------------------------------------- ##
## Custom CB quant plots
## ----------------------------------------------------------------- ##
lapply(seq_along(customCBList), function(i) {
id <- paste0("qpl", i)
output[[id]] <- shiny::renderPlot(
plotAlevinQuant(alevin$cbTable,
colName = paste0("customCB__",
names(customCBList)[i]),
cbName = names(customCBList)[i],
firstSelColName = firstSelColName)
)
})
output$quantPlotsCustomCBs <- shiny::renderUI({
lapply(as.list(seq_along(customCBList)), function(i) {
id <- paste0("qpl", i)
shiny::plotOutput(id)
})
})
## ----------------------------------------------------------------- ##
## Standard distribution plots
## ----------------------------------------------------------------- ##
output$histPlot <- shiny::renderPlot(
if (quantMethod == "alevin") {
cowplot::plot_grid(
plotAlevinHistogram(alevin$cbTable, plotVar = "dedupRate",
axisLabel = "Deduplication rate",
colName = "inFinalWhiteList",
cbName = "final whitelist",
firstSelColName = firstSelColName),
plotAlevinHistogram(alevin$cbTable, plotVar = "mappingRate",
axisLabel = "Mapping rate",
colName = "inFinalWhiteList",
cbName = "final whitelist",
firstSelColName = firstSelColName)
)
} else if (quantMethod == "alevin-fry") {
cowplot::plot_grid(
plotAlevinHistogram(alevin$cbTable, plotVar = "dedupRate",
axisLabel = "Deduplication rate",
colName = firstSelColName,
cbName = "permitlist",
firstSelColName = firstSelColName),
plotAlevinHistogram(alevin$cbTable, plotVar = "mappingRate",
axisLabel = "Mapping rate",
colName = firstSelColName,
cbName = "permitlist",
firstSelColName = firstSelColName)
)
}
)
## ----------------------------------------------------------------- ##
## Custom CB distribution plots
## ----------------------------------------------------------------- ##
lapply(seq_along(customCBList), function(i) {
id <- paste0("hpl", i)
output[[id]] <- shiny::renderPlot(
cowplot::plot_grid(
plotAlevinHistogram(
alevin$cbTable, plotVar = "dedupRate",
axisLabel = "Deduplication rate",
colName = paste0("customCB__",
names(customCBList)[i]),
cbName = names(customCBList)[i]),
plotAlevinHistogram(
alevin$cbTable, plotVar = "mappingRate",
axisLabel = "Mapping rate",
colName = paste0("customCB__",
names(customCBList)[i]),
cbName = names(customCBList)[i])
)
)
})
output$histPlotsCustomCBs <- shiny::renderUI({
lapply(as.list(seq_along(customCBList)), function(i) {
id <- paste0("hpl", i)
shiny::plotOutput(id)
})
})
## ----------------------------------------------------------------- ##
## Close and return button
## ----------------------------------------------------------------- ##
output$stopAppMenu <- shinydashboard::renderMenu(
if (addStopButton) {
shinydashboard::dropdownMenu(
type = "tasks",
icon = shiny::icon("circle-xmark"),
badgeStatus = NULL,
headerText = "",
shinydashboard::notificationItem(
text = shiny::actionButton(
inputId = "close_app", label = "Close app",
icon = shiny::icon("circle-xmark")
),
icon = shiny::icon(""), # tricking it to not have additional icon
status = "primary"
)
)
} else {
NULL
}
)
shiny::observeEvent(input$close_app, {
shiny::stopApp(returnValue = list(alevin = alevin,
firstSelColName = firstSelColName,
wlName = wlName,
countCol = countCol))
})
} # nocov end
shiny::shinyApp(ui = pLayout, server = server_function)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.