#' Annotate a Spectrum with Fragments
#'
#' @description Generates an experimental spectrum with calculated peptide fragments.
#' Plot can be interactive or not.
#'
#' @param PeakData Object of the peak_data class from get_peak_data.
#' @param MatchedPeaks Object of the matched_peaks class from get_matched_peaks. Default is NULL.
#' @param IncludeIsotopes A logical to indicate whether isotopes should be included in the plot. Default is TRUE.
#' @param IncludeLabels A logical to indicate whether labels should be included in the plot. Default is TRUE.
#' @param LabelSize A numeric indicating the size of the label in ggplot dimensions. Default is 4.
#' @param LabelDistance A numeric indicating the distance from each peak the label should be. Default is 0.5 M/Z.
#' @param Interactive A logical to determine whether the plot should be interactive or not. For plots
#' with more than 10,000 peaks, it is advantageous to set to FALSE. Default is TRUE.
#'
#' @examples
#' \dontrun{
#'
#' # Test bottom up data
#' BU_Peak <- get_peak_data(ScanMetadata = BU_ScanMetadata, ScanNumber = 31728)
#' BU_Match <- get_matched_peaks(ScanMetadata = BU_ScanMetadata, PeakData = BU_Peak)
#'
#' annotated_spectrum_plot(PeakData = BU_Peak, Interactive = TRUE)
#' annotated_spectrum_plot(PeakData = BU_Peak, MatchedPeaks = BU_Match, IncludeLabels = FALSE, Interactive = TRUE)
#' annotated_spectrum_plot(PeakData = BU_Peak, MatchedPeaks = BU_Match, IncludeLabels = TRUE)
#' }
#'
#' @export
annotated_spectrum_plot <- function(PeakData,
MatchedPeaks = NULL,
IncludeIsotopes = TRUE,
IncludeLabels = TRUE,
LabelSize = 4,
LabelDistance = 0.5,
Interactive = FALSE) {
##################
## CHECK INPUTS ##
##################
# Assert that Peak Data is of the correct type
if ("peak_data" %in% class(PeakData) == FALSE) {
stop("PeakData must be of the class peak_data generated by get_peak_data.")
}
# Assert that Matched Peaks is of the correct type
if (is.null(MatchedPeaks) == FALSE && "matched_peaks" %in% class(MatchedPeaks) == FALSE) {
stop("MatchedPeaks must be of the class matched_peaks generated by get_matched_peaks.")
}
# Assert that Include Isotopes is a single logical
if (is.na(IncludeIsotopes) || is.logical(IncludeIsotopes) == FALSE || length(IncludeIsotopes) > 1) {
stop("IncludeIsotopes must be a single logical value TRUE or FALSE.")
}
# Assert that Include Labels is a single logical
if (is.na(IncludeLabels) || is.logical(IncludeLabels) == FALSE || length(IncludeLabels) > 1) {
stop("IncludeLabels must be a single logical value TRUE or FALSE.")
}
# Assert that Label Size is a numeric
if (is.numeric(LabelSize) == FALSE) {
stop("LabelSize must be a number.")
}
# Ensure that Label Size is a positive number
LabelSize <- abs(LabelSize)
# Assert that Label Distance is a positive number
if (is.numeric(LabelDistance) == FALSE) {
stop("LabelDistance must be a number.")
}
# Ensure that Label Distance is a positive number
LabelDistance <- abs(LabelDistance)
# Assert that Interactive is a single logical
if (is.na(Interactive) || is.logical(Interactive) == FALSE || length(Interactive) > 1) {
stop("Interactive must be a single logical value TRUE or FALSE.")
}
###############
## MAKE PLOT ##
###############
# Set color vector
ColorVector <- c("a" = "forestgreen", "a+" = "forestgreen", "a++" = "forestgreen",
"a-" = "forestgreen", "a--" = "forestgreen", "a^" = "forestgreen", "a^^" = "forestgreen",
"b" = "steelblue", "b+" = "steelblue", "b++" = "steelblue",
"b-" = "steelblue", "b--" = "steelblue", "b^" = "steelblue", "b^^" = "steelblue",
"c" = "darkviolet", "c+" = "darkviolet", "c++" = "darkviolet",
"c-" = "darkviolet", "c--" = "darkviolet", "c^" = "darkviolet", "c^^" = "darkviolet",
"x" = "pink3", "x+" = "pink3", "x++" = "pink3",
"x-" = "pink3", "x--" = "pink3", "x^" = "pink3", "x^^" = "pink3",
"y" = "red", "y+" = "red", "y++" = "red",
"y-" = "red", "y--" = "red", "y^" = "red", "y^^" = "red",
"z" = "darkorange", "z+" = "darkorange", "z++" = "darkorange",
"z-" = "darkorange", "z--" = "darkorange", "z^" = "darkorange", "z^^" = "darkorange",
"Spectrum" = "black")
# Return just the spectrum if no fragments identified
if (is.null(MatchedPeaks)) {
# Put zeros on either side of the peaks (zero center)
Peaks <- PeakData
# Generate zero center
Peaks0 <- data.table::data.table("M/Z" = c(Peaks$`M/Z` - 1e-12, Peaks$`M/Z` + 1e-12),
"Intensity" = 0, "Abundance" = 0)
# Bind and order
Peaks <- rbind(Peaks0, Peaks)
Peaks <- Peaks[order(Peaks$`M/Z`),]
# Generate plot
Spectrum <- ggplot2::ggplot(Peaks, ggplot2::aes(x = `M/Z`, y = Intensity)) +
ggplot2::theme_bw() + ggplot2::geom_line(size = 1) +
ggplot2::theme(legend.title = ggplot2::element_blank(), plot.title = ggplot2::element_text(hjust = 0.5))
# Return interactive if requested
if (Interactive) {
return(Spectrum %>% plotly::ggplotly())
} else {
return(Spectrum)
}
} else {
# Subset out Fragment Dataframe
FragmentTable <- MatchedPeaks
# Remove isotopes if indicated
if (IncludeIsotopes == FALSE) {
FragmentTable <- FragmentTable[FragmentTable$Isotope == "M",]
}
# Pull peak data
Peaks <- PeakData
# Rename Peak first column
colnames(Peaks)[1] <- "M/Z Experimental"
# Merge identified fragments and peak data
Peaks <- merge(Peaks, FragmentTable, by = "M/Z Experimental", all = TRUE)
# Set general type to a string
Peaks$Type[is.na(Peaks$Type)] <- "Spectrum"
# Remove ion at 0 peaks
Peaks[Peaks$Intensity == 0, "Ion"] <- NA
# Ensure the order is correct
Peaks <- Peaks[order(Peaks$`M/Z Experimental`),]
# Zero center peaks
PeaksPrevious <- PeaksNext <- Peaks
PeaksPrevious$Intensity <- PeaksNext$Intensity <- 0
PeaksPrevious$`M/Z Experimental` <- PeaksPrevious$`M/Z Experimental` - 1e-12
PeaksNext$`M/Z Experimental` <- PeaksNext$`M/Z Experimental` + 1e-12
# Now zero center peaks, and order
Peaks <- rbind(PeaksPrevious, Peaks, PeaksNext)
Peaks <- Peaks[order(Peaks$`M/Z Experimental`),]
# Remove Ions for Inensity 0
Peaks[Peaks$Intensity == 0, c("Ion")] <- NA
# Make a ggplot if interactive is false
if (Interactive == FALSE) {
# Set the base spectrum
BaseSpectrum <- ggplot2::ggplot(Peaks, ggplot2::aes(x = `M/Z Experimental`,
y = Intensity, color = Type, label = Ion)) +
ggplot2::theme_bw() + ggplot2::geom_line(linewidth = 1) +
ggplot2::scale_color_manual(values = ColorVector) + ggplot2::xlab(bquote(italic(.("M/Z")))) +
ggplot2::theme(legend.title = ggplot2::element_blank(), plot.title = ggplot2::element_text(hjust = 0.5))
# If scan number is 0, then remove title
if (attr(PeakData, "pspecter")$ScanNumber != 0) {
BaseSpectrum <- BaseSpectrum + ggplot2::ggtitle(paste("Scan Number:", attr(PeakData, "pspecter")$ScanNumber))
}
# Add labels if True
if (IncludeLabels) {
BaseSpectrum <- BaseSpectrum + ggplot2::geom_text(size = LabelSize, hjust = LabelDistance, show.legend = FALSE)
}
return(BaseSpectrum)
} else {
# Generate a blank plotly
p <- plotly::plot_ly()
# Set fragment type order
FragOrder <- Peaks$Type[Peaks$Type != "Spectrum"] %>% unique() %>% sort()
FragOrder <- c("Spectrum", FragOrder)
for (FragType in FragOrder) {
# Subset Peak Data frame
PeakSub <- Peaks[Peaks$Type == FragType,]
# Create a separate "Add Trace" for Spectrum
if (FragType == "Spectrum") {
# Add spectrum trace
p <- plotly::add_trace(p, x = PeakSub$`M/Z Experimental`, y = PeakSub$Intensity,
type = "scatter", mode = "lines+markers", line = list(color = ColorVector[FragType]),
marker = list(color = ColorVector[FragType], opacity = 0),
name = FragType, hoverinfo = "text",
hovertext = paste("M/Z:", round(PeakSub$`M/Z Experimental`, 3), "<br>Intensity:",
round(PeakSub$Intensity)))
} else {
# Add ion trace
p <- plotly::add_trace(p, x = PeakSub$`M/Z Experimental`, y = PeakSub$Intensity,
type = "scatter", mode = "lines+markers", line = list(color = ColorVector[FragType]),
marker = list(color = ColorVector[FragType], opacity = 0),
name = FragType, hoverinfo = "text",
hovertext = paste("M/Z:", round(PeakSub$`M/Z Experimental`, 3), "<br>Intensity:",
round(PeakSub$Intensity), "<br>Ion:", paste0(PeakSub$Ion, "<sup>", PeakSub$Z, "</sup> ",
PeakSub$Isotope, sep = "")))
}
}
# Add title and axes if scan number is 0
if (is.null(attr(PeakData, "pspecter")$ScanNumber) || attr(PeakData, "pspecter")$ScanNumber == 0) {
# Add title and axes
p <- p %>% plotly::layout(xaxis = list(title = '<i>m/z</i> (Mass to Charge)'),
yaxis = list(title = "Intensity"),
legend = list(orientation = "h"))
} else {
# Add title and axes
p <- p %>% plotly::layout(xaxis = list(title = '<i>m/z</i> (Mass to Charge)'),
yaxis = list(title = "Intensity"),
title = paste("Scan Number:", attr(PeakData, "pspecter")$ScanNumber),
legend = list(orientation = "h"))
}
# Add labels if applicable
if (IncludeLabels) {
# Use the fragment table to label the spectrum
for (row in 1:nrow(FragmentTable)) {
Text <- list(
x = FragmentTable$`M/Z Experimental`[row] + LabelDistance,
y = FragmentTable$`Intensity Experimental`[row],
text = htmltools::HTML(paste('<span style="color: ', ColorVector[FragmentTable$Type[row]],
'; font-size: ', LabelSize, 'pt;"> ', FragmentTable$Ion[row], "<sup>",
FragmentTable$Z[row], "</sup>, ", FragmentTable$Isotope[row], "</span>", sep = "")),
xref = "x", yref = "y", showarrow = FALSE
)
p <- p %>% plotly::layout(annotations = Text)
}
return(p)
} else {return(p)}
}
}
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.