R/annotated_spectrum_plot.R

Defines functions annotated_spectrum_plot

Documented in annotated_spectrum_plot

#' 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)}

    }

  }

}
EMSL-Computing/pspecterlib documentation built on Jan. 28, 2024, 8:13 p.m.