R/methods_visualise.R

Defines functions visualise.virtual4C_discovery visualise.RCP_discovery visualise.DI_discovery visualise.IS_discovery visualise.CS_discovery visualise.ARA_discovery visualise.ATA_discovery visualise.PESCAn_discovery visualise.CSCAn_discovery visualise.APA_discovery visualise.ARMLA visualise.default visualise

Documented in visualise visualise.APA_discovery visualise.ARA_discovery visualise.ATA_discovery visualise.CSCAn_discovery visualise.CS_discovery visualise.default visualise.DI_discovery visualise.IS_discovery visualise.PESCAn_discovery visualise.RCP_discovery visualise.virtual4C_discovery

# Documentation -----------------------------------------------------------

#' @name visualise
#' @aliases visualise visualize
#' @title Visualise discoveries
#'
#' @description Plot the results of \code{discovery} objects. By default
#'   contrasts one sample with the others.
#'
#' @param discovery A \code{discovery} object as returned by GENOVA analysis
#'   functions.
#'
#' @param contrast An \code{integer} or \code{character} matching an experiment
#'   (name) of length 1, specifying which sample should be used to make a
#'   contrast with all other samples. Alternatively, set to \code{NULL} to not
#'   plot contrast panels. See also the \code{show_single_contrast} argument.
#'
#' @param colour_lim,colour_lim_contrast
#'   Indication of limits for the primary and secondary continuous colour scales 
#'   respectively. One of: \itemize{ 
#'   \item \code{NULL} to have an educated quess for the scale. 
#'   \item A \code{numeric} vector of length two providing the limits of the 
#'   scale. Use \code{NA} to refer to existing minima or maxima. 
#'   \item A \code{function} that accepts the existing (automatic) limits and 
#'   returns new limits.}
#'
#' @param raw A \code{logical} of length 1: should a bare bones plot be
#'   returned?
#'
#' @param title add a title
#' @param ... Further arguments specific to the discovery class. See the section 
#' extended arguments below.
#'
#' @details The \code{"diff"} \code{metric} value creates contrast panels by
#'   subtracting the values of each sample by the values of the sample indicated
#'   by the '\code{contrast}' argument. The \code{"lfc"} \code{metric} value
#'   creates contrast panels by dividing every samples' values by the sample
#'   indicated by the '\code{contrast}' argument and then taking the base 2
#'   logarithm of that division.
#'
#'   The '\code{raw = TRUE}' argument allows custimisation of the plots. The
#'   returned plot will not have position- or fill-scales and no theme settings,
#'   which can be set freely afterwards to match personal aesthetic tastes. When
#'   '\code{raw = TRUE}' and '\code{subtract}' is not \code{NULL}, the fill
#'   scale of the contrast panels can be manipulated by setting the
#'   '\code{aesthetics = "altfill"}' inside ggplot2's fill scale functions.
#'   
#' @section Extended arguments:
#'
#' @note For \code{ATA_discovery} objects which include the matrix's diagonal,
#'   the upper limit for the contacts fill scale is set to the 95th percentile
#'   of the data to increase the dynamic range of colours. The same is true for
#'   \code{ARA_discovery}, when '\code{mode = "signal"}'.
#'   
#' @export
#'
#' @examples
#' \dontrun{
#' # APA
#' apa <- APA(list(WT = WT_40kb, KO = KO_40kb), loops)
#' visualise(apa)
#'
#' # PE-SCAn
#' pescan <- PESCAn(list(WT = WT_40kb, KO = KO_40kb), super_enhancers)
#' visualise(pescan)
#'
#' # To plot PE-SCAn without background correction
#' visualise(pescan, mode = "signal")
#'
#' # ATA
#' ata <- APA(list(WT = WT_10kb, KO = KO_10kb), tads)
#' visualise(ata)
#'
#' # ARA
#' ara <- ARA(list(WT = WT_20kb, KO = KO_20kb), ctcf_sites)
#' visualise(ara)
#'
#' # Compartment score
#' cs <- compartment_score(list(WT = WT_100kb, KO = KO_100kb), H3K4me1_peaks)
#' visualise(cs, chr = "chr1")
#'
#' # Saddle function
#' sadl <- saddle(list(WT = WT_100kb, KO = KO_100kb), cs) # see example above
#' visualise(sadl)
#'
#' # Handling 'raw' plots
#' visualise(pescan, raw = TRUE) +
#'   ggplot2::scale_fill_gradient(aesthetics = "altfill")
#' }
visualise <- function(discovery, ...) {
  UseMethod("visualise", discovery)
}



# Default -----------------------------------------------------------------

# If you fail, it is best to fail graciously

#' @export
#' @rdname visualise
visualise.default <- function(discovery, contrast, raw, title, 
                              colour_lim, colour_lim_contrast, ...) {
  stop("No visualise method for class '", class(discovery),
       "' has been implemented.", call. = FALSE)
}

# Common elements ---------------------------------------------------------

# Common ancestor for aggregate repeated matrix lookup analysis plots
visualise.ARMLA <- function(discovery, contrast = 1,
                            metric = c("diff", "lfc"),
                            raw = FALSE, altfillscale, 
                            show_single_contrast = FALSE,
                            ...) {
  metric <- match.arg(metric)
  mats <- discovery$signal

  # Format dimnames if missing
  dims <- dim(mats)
  names_dims <- dimnames(mats)
  names_dims <- lapply(seq_along(dims), function(i) {
    if (is.null(names_dims[[i]])) {
      return(seq_len(dims[i]))
    } else {
      return(names_dims[[i]])
    }
  })

  # Setup coordinates
  coords <- data.frame(
    x = as.numeric(names_dims[[2]][as.vector(col(mats[,,1]))]),
    y = as.numeric(names_dims[[1]][as.vector(row(mats[,,1]))])
  )

  # Fill coordinates with experiment data
  df <- lapply(seq_len(dims[3]), function(i) {
    cbind.data.frame(coords,
                     name = names_dims[[3]][i],
                     mode = "Individual",
                     value = as.vector(mats[,,i]))
  })
  df <- do.call(rbind, df)

  # Calculate metric if contrast is supplied
  showcontrast <- !is.null(contrast) && 
    (dims[3] > 1L || literalTRUE(show_single_contrast))
  
  if (showcontrast) {
    contrast <- as.vector(mats[,,contrast])
    contrast <- switch(
      metric,
      "diff" = as.vector(mats) - rep(contrast, dims[3]),
      "lfc"  = log2(as.vector(mats) / rep(contrast, dims[3]))
    )
    dim(contrast) <- dim(mats)
    contrast_name <- switch(metric,
                            "diff" = "Difference",
                            "lfc" = "Change")
    contrast <- lapply(seq_len(dims[3]), function(i) {
      cbind.data.frame(coords,
                       name = names_dims[[3]][i],
                       mode = contrast_name,
                       value = as.vector(contrast[,,i]))
    })
    contrast <- do.call(rbind, contrast)
    df <- rbind(df, contrast)
  }

  # Setup base of plot
  g <- ggplot2::ggplot(df, ggplot2::aes(x, y))

  if (showcontrast) {
    # Setup basics of the diff plots
    # Warnings are supressed because ggplot doesn't recognise
    # the altfill aesthetic (YET!)
    suppressWarnings(
      g <- g + ggplot2::geom_raster(
        data = datafilter(mode == contrast_name),
        ggplot2::aes(altfill = value)
      ) +
        ggplot2::facet_grid(mode ~ name, switch = "y")
    )

    if (!raw) {
      g <- g + altfillscale
      # Hack for scale
      g$scales$scales[[1]]$guide <- ggplot2::guide_colourbar()
      g$scales$scales[[1]]$guide$available_aes[[3]] <- "altfill"
      g$scales$scales[[1]]$guide$order <- 2
    } else {
      g <- g + ggplot2::guides(
        "altfill" = ggplot2::guide_colourbar(available_aes = "altfill")
      )
    }

    # Good old ggnomics-style hack for different fill scales
    old_geom <- g$layers[[1]]$geom
    old_nahandle <- old_geom$handle_na
    new_nahandle <- function(self, data, params) {
      colnames(data)[colnames(data) %in% "altfill"] <- "fill"
      old_nahandle(data, params)
    }
    new_geom <- ggplot2::ggproto(paste0(sample(1e6, 1), class(old_geom)),
                                 old_geom,
                                 handle_na = new_nahandle
    )
    names(new_geom$default_aes)[1] <- "altfill"
    new_geom$non_missing_aes <- "altfill"
    g$layers[[1]]$geom <- new_geom
  } else {
    # No special treatment needed for just one colourscale
    g <- g + ggplot2::facet_grid(~ name)
  }

  # Add the non-diff plots
  g <- g + ggplot2::geom_raster(
    data = datafilter(mode == "Individual"),
    ggplot2::aes(fill = value)
  )

  if (raw) {
    return(g)
  } else {
    g <- g + ggplot2::theme(
      aspect.ratio = 1,
      strip.placement = "outside",
      strip.background = ggplot2::element_blank(),
      panel.border = ggplot2::element_rect(fill = NA, colour = "grey30")
    )
    return(g)
  }
}

# Discovery objects -------------------------------------------------------

# Aggregate matrices ------------------------------------------------------

#' @rdname visualise
#' @section Extended arguments:\subsection{APA, PE-SCAn, ATA, ARA & C-SCAn}{
#' \describe{
#'  \item{\code{metric}}{A \code{character} of length one: \code{"diff"} for 
#'   difference by subtraction or \code{"lfc"} for 
#'   \ifelse{html}{\out{log<sub>2</sub>}}{\eqn{log_2}} fold changes.}
#'  \item{\code{colour_lim}, \code{colour_lim_contrast}}{
#'   Indication of limits for the primary and secondary colour scale 
#'   respectively. One of: \itemize{ 
#'   \item \code{NULL} to have an educated quess for the scale. 
#'   \item A \code{numeric} vector of length two providing the limits of the 
#'   scale. Use \code{NA} to refer to existing minima or maxima. 
#'   \item A \code{function} that accepts the existing (automatic) limits and 
#'   returns new limits.}}
#'  \item{\code{mode}}{A \code{character} of length one indicating what type of
#'  result to plot. Either \code{"signal"} or \code{"obsexp"}, referring to the
#'  slot in the discovery object. Applicatble to PE-SCAn, C-SCAn and ARA.}
#'  \item{\code{show_single_contrast}}{A \code{logical} of length 1; 
#'   if \code{FALSE} (default), does not show contrasts when \code{discovery} 
#'   describes one experiment. If \code{TRUE}, plots empty panel.}
#' }
#' }
#' @export
#' @usage NULL
visualise.APA_discovery <- function(discovery, contrast = 1,
                                    metric = c("lfc", "diff"),
                                    raw = FALSE, title = NULL,
                                    colour_lim = NULL,
                                    colour_lim_contrast = NULL, 
                                    show_single_contrast = FALSE,
                                    ...) {
  metric <- match.arg(metric)
  
  # Decide on limits
  if (is.null(colour_lim)) {
    colour_lim <- c(NA, NA)
  }

  altfillscale <- scale_fill_GENOVA_div(
    aesthetics = "altfill",
    name = switch (metric,
      "diff" = "Difference",
      "lfc" = expression(atop("Log"[2]*" Fold", "Change"))
    ),
    limits = colour_lim_contrast,
    oob = scales::squish,
    midpoint = 0
  )

  # Get a default plot
  g <- visualise.ARMLA(
    discovery = discovery,
    contrast = contrast,
    metric = metric, raw = raw,
    altfillscale = altfillscale,
    show_single_contrast = show_single_contrast
  )
  if (raw) {
    return(g)
  }

  g <- g + scale_fill_GENOVA(
    guide = ggplot2::guide_colourbar(order = 1),
    name = expression(mu*" Contacts"),
    limits = colour_lim,
    oob = scales::squish
  ) +
  ggplot2::scale_x_continuous(
    name = "",
    expand = c(0, 0),
    breaks = breaks_trim_outer(),
    labels = label_kilobase_relative("3'")
  ) +
    ggplot2::scale_y_continuous(
      name = "",
      expand = c(0, 0),
      breaks = breaks_trim_outer(),
      labels = label_kilobase_relative("5'")
    )

  if(!is.null(title)){
    g = g + ggplot2::ggtitle(title)
  }
  g
}

#' @rdname visualise
#' @export
#' @usage NULL
visualise.CSCAn_discovery <- function(discovery, mode = c("obsexp", "signal"),
                                      raw = FALSE, title = NULL, 
                                      colour_lim = NULL,
                                      ...) {
  mode <- match.arg(mode)
  hasobsexp <- "obsexp" %in% names(discovery)
  if (mode == "obsexp" & !hasobsexp) {
    warning("Mode was set to 'obsexp' but no such result was found")
  }
  hasobsexp <- hasobsexp && mode == "obsexp"
  res <- if (hasobsexp) {
    setNames(discovery[names(discovery) %in% "obsexp"], "signal")
  } else {
    discovery
  }
  
  df <- c(lapply(seq_along(dim(res$signal)), function(i) {
    dnm <- dimnames(res$signal)[[i]]
    if (is.null(dnm)) {
      return(as.vector(slice.index(res$signal, i)))
    } else {
      return(dnm[as.vector(slice.index(res$signal, i))])
    }
  }), list(as.vector(res$signal)))
  df <- as.data.frame(df, stringsAsFactors = FALSE)
  df <- setNames(df, c(paste0("Var", seq_along(dim(res$signal))), "value"))
  if (ncol(df) == 5) {
    groups <- strsplit(as.character(df$Var3), "-")
    df$left  <- vapply(groups, `[`, character(1), 1)
    df$right <- vapply(groups, `[`, character(1), 2)
  }
  df$Var1 <- as.integer(df$Var1)
  df$Var2 <- as.integer(df$Var2)
  
  g <- ggplot2::ggplot(df, ggplot2::aes(Var1, Var2, fill = value)) +
    ggplot2::geom_raster()
  
  if (all(c("left", "right") %in% names(df))) {
    g <- g + ggplot2::facet_grid(left ~ Var4 + right, switch = "y")
  } else if ("Var4" %in% names(df)) {
    g <- g + ggplot2::facet_grid(~ Var4, switch = "y")
  }
  
  if (raw) {
    return(g)
  }

  panel_spac <- c(rep(5.5, pmax(length(unique(df$right)) - 1L, 0)))
  panel_spac <- c(panel_spac, rep(c(11, panel_spac), 
                                  pmax(length(unique(df$Var4)) - 1, 0)))
  panel_spac <- if (length(panel_spac)) panel_spac else 5.5
  panel_spac <- ggplot2::unit(panel_spac, "points")
  
  fillscale <- if (hasobsexp) {
    scale_fill_GENOVA_div(
      guide = ggplot2::guide_colourbar(order = 1),
      name = expression(frac("Observed", "Expected")),
      oob = scales::squish,
      midpoint = 1,
      limits = colour_lim
    )
  } else {
    scale_fill_GENOVA(
      guide = ggplot2::guide_colourbar(order = 1),
      name = expression(mu*" Contacts"),
      oob = scales::squish,
      limits = colour_lim
    )
  }

  g <- g + fillscale + 
    ggplot2::scale_x_continuous(breaks = breaks_trim_outer(), 
                                labels = label_kilobase_relative("3'"), 
                                expand = c(0,0),
                                name = "") +
    ggplot2::scale_y_continuous(breaks = breaks_trim_outer(),
                                labels = label_kilobase_relative("5'"), 
                                expand = c(0,0), name = "") +
    GENOVA_THEME() +
    ggplot2::theme(
      aspect.ratio = 1,
      strip.placement = "outside",
      panel.spacing.x = panel_spac
    )
  
  if (!is.null(title)) {
    g <- g + ggplot2::ggtitle(title)
  }
  
  g
}

#' @rdname visualise
#' @export
#' @usage NULL
visualise.PESCAn_discovery <- function(discovery, contrast = 1,
                                       metric = c("diff", "lfc"),
                                       mode = c("obsexp", "signal"),
                                       raw = FALSE, title = NULL,
                                       colour_lim = NULL,
                                       colour_lim_contrast = NULL, 
                                       show_single_contrast = FALSE,
                                       ...) {
  metric <- match.arg(metric)
  # Handle mode settings
  mode <- match.arg(mode)
  hasobsexp <- "obsexp" %in% names(discovery)
  if (mode == "obsexp" & !hasobsexp) {
    warning("Mode was set to 'obsexp' but no such result was found")
  }
  hasobsexp <- hasobsexp && mode == "obsexp"
  res <- if (hasobsexp) {
    setNames(discovery[names(discovery) %in% "obsexp"], "signal")
  } else {
    discovery
  }
  
  # Decide on limits
  # if (is.null(colour_lim)) {
  if (hasobsexp) {
    midpoint <- 1
  } else {
    midpoint <- NA
  }
  # }

  altcols <- if (hasobsexp) {
    "greenpink"
  } else {
    "divergent"
  }
  altfillscale <- scale_fill_GENOVA_div(
    palette = altcols,
    aesthetics = "altfill",
    name = switch (metric,
                   "diff" = "Difference",
                   "lfc" = expression(atop("Log"[2]*" Fold", "Change"))
    ),
    midpoint = 0,
    limits = colour_lim_contrast,
    oob = scales::squish
  )

  # Get a default plot
  g <- visualise.ARMLA(
    discovery = res,
    contrast = contrast,
    metric = metric, raw = raw,
    altfillscale = altfillscale,
    show_single_contrast = show_single_contrast
  )
  if (raw) {
    return(g)
  }

  fillscale <- if (hasobsexp) {
    scale_fill_GENOVA_div(
      palette = "divergent",
      guide = ggplot2::guide_colourbar(order = 1),
      name = expression(frac("Observed", "Expected")),
      oob = scales::squish,
      limits = colour_lim,
      midpoint = 1
    )
  } else {
    scale_fill_GENOVA(
      guide = ggplot2::guide_colourbar(order = 1),
      name = expression(mu*" Contacts"),
      oob = scales::squish,
      limits = colour_lim
    )
  }

  g <- g + fillscale +
    ggplot2::scale_x_continuous(
      name = "",
      expand = c(0, 0),
      breaks = breaks_trim_outer(),
      labels = label_kilobase_relative("3'")
    ) +
    ggplot2::scale_y_continuous(
      name = "",
      expand = c(0, 0),
      breaks = breaks_trim_outer(),
      labels = label_kilobase_relative("5'")
    )

  if(!is.null(title)){
    g = g + ggplot2::ggtitle(title)
  }
  
  g
}

#' @rdname visualise
#' @export
#' @usage NULL
visualise.ATA_discovery <- function(discovery, contrast = 1,
                                    metric = c("lfc", "diff"),
                                    raw = FALSE, title = NULL,
                                    colour_lim = NULL,
                                    colour_lim_contrast = NULL, 
                                    show_single_contrast = FALSE,
                                    ...) {
  metric <- match.arg(metric)
  
  # Decide on limits
  # if (is.null(colour_lim_contrast)) {
  #   colour_lim_contrast <- centered_limits()
  # }

  altfillscale <- scale_fill_GENOVA_div(
    aesthetics = "altfill",
    name = switch (metric,
                   "diff" = "Difference",
                   "lfc" = expression(atop("Log"[2]*" Fold", "Change"))
    ),
    oob = scales::squish,
    midpoint = 0,
    limits = colour_lim_contrast
  )

  # Get a default plot
  g <- visualise.ARMLA(
    discovery = discovery,
    contrast = contrast,
    metric = metric, raw = raw,
    altfillscale = altfillscale,
    show_single_contrast = show_single_contrast
  )
  if (raw) {
    return(g)
  }

  haspad <- "padding" %in% names(attributes(discovery))
  if (haspad) {
    padding <- attr(discovery, "padding")
    pos_breaks <- function(x) {
      cumsum(c(padding - 0.5, 1)) / (2 * padding) * diff(x) + min(x)
    }
  } else {
    pos_breaks <- function(x) {
      seq(x[1], x[2], length.out = 5)[c(2,4)]
    }
  }
  
  if (is.null(colour_lim)) {
    colour_lim <- c(NA, quantile(discovery$signal, 0.95))
  }

  g <- g +
    scale_fill_GENOVA(
      guide = ggplot2::guide_colourbar(order = 1),
      name = expression(mu*" Contacts"),
      limits = colour_lim,
      oob = scales::squish
    ) +
    ggplot2::scale_x_continuous(
      name = "",
      expand = c(0, 0),
      breaks = pos_breaks,
      labels = c("5' border", "3' border")
    ) +
    ggplot2::scale_y_continuous(
      name = "",
      expand = c(0, 0),
      breaks = pos_breaks,
      labels = c("3' border", "5' border")
    )

  if(!is.null(title)){
    g = g + ggplot2::ggtitle(title)
  }
  
  g
}

#' @rdname visualise
#' @export
#' @usage NULL
visualise.ARA_discovery <- function(discovery, contrast = 1,
                                    metric = c("diff", "lfc"),
                                    mode = c("obsexp", "signal"),
                                    raw = FALSE, title = NULL,
                                    colour_lim = NULL,
                                    colour_lim_contrast = NULL, 
                                    show_single_contrast = FALSE,
                                    ...) {
  metric <- match.arg(metric)
  # Handle mode settings
  mode <- match.arg(mode)
  hasobsexp <- "obsexp" %in% names(discovery)
  if (mode == "obsexp" & !hasobsexp) {
    warning("Mode was set to 'obsexp' but no such result was found")
  }
  hasobsexp <- hasobsexp && mode == "obsexp"
  res <- if (hasobsexp) {
    setNames(discovery[names(discovery) %in% "obsexp"], "signal")
  } else {
    discovery
  }

  altcols <- if (hasobsexp) {
    pal <- "greenpink"
  } else {
    pal <- "divergent"
  }
  altfillscale <- scale_fill_GENOVA_div(
    palette = pal,
    aesthetics = "altfill",
    name = switch (metric,
                   "diff" = "Difference",
                   "lfc" = expression(atop("Log"[2]*" Fold", "Change"))
    ),
    limits = colour_lim_contrast,
    midpoint = 0,
    oob = scales::squish
  )

  # Get a default plot
  g <- visualise.ARMLA(
    discovery = res,
    contrast = contrast,
    metric = metric, raw = raw,
    altfillscale = altfillscale,
    show_single_contrast = show_single_contrast
  )
  if (raw) {
    return(g)
  }

  # pos_breaks <- function(x) {
  #   x <- scales::extended_breaks()(x)
  #   if (length(x) > 3) head(tail(x, -1), -1) else x
  # }
  
  # Decide on limits
  if (is.null(colour_lim)) {
    if (!hasobsexp) {
      colour_lim <- c(NA, quantile(discovery$signal, 0.95))
    }
  }

  fillscale <- if (hasobsexp) {
    scale_fill_GENOVA_div(
      palette = "divergent",
      guide = ggplot2::guide_colourbar(order = 1),
      name = expression(frac("Observed", "Expected")),
      oob = scales::squish,
      limits = colour_lim,
      midpoint = 1
    )
  } else {
    scale_fill_GENOVA(
      guide = ggplot2::guide_colourbar(order = 1),
      name = expression(mu*" Contacts"),
      limits = colour_lim,
      oob = scales::squish
    )
  }

  g <- g + fillscale +
    ggplot2::scale_x_continuous(
      name = "",
      expand = c(0, 0),
      breaks = breaks_trim_outer(),
      labels = scales::label_number(scale = 1e-3, suffix = " kb")
    ) +
    ggplot2::scale_y_continuous(
      name = "",
      expand = c(0, 0),
      breaks = breaks_trim_outer(),
      labels = scales::label_number(scale = 1e-3, suffix = " kb")
    )

  if(!is.null(title)){
    g <- g + ggplot2::ggtitle(title)
  }
  
  g
}

# Genome wide scores ------------------------------------------------------

#' @rdname visualise
#' @section Extended arguments:\subsection{Compartment-, Insulation score & 
#' Directionality Index}{
#' \describe{
#'  \item{\code{chr}}{A \code{character} of length one with the chromosome 
#'  name. Defaults to \code{"chr1"}.}
#'  \item{\code{start}, \code{end}}{An \code{integer} of length one setting the 
#'  start or end of the region to plot. If \code{NULL} (default), is set to 
#'  \code{-Inf} and \code{Inf} respectively}
#' }
#' }
#' @export
#' @usage NULL
visualise.CS_discovery <- function(discovery, contrast = NULL,
                                   chr = "chr1", start = NULL, end = NULL,
                                   raw = FALSE, show_single_contrast = FALSE,
                                   ...) {
  start <- if (is.null(start)) -Inf else start
  end <- if (is.null(end)) Inf else end
  loc <- standardise_location(chr, start, end, singular = TRUE)
  df <- as.data.table(discovery$compart_scores)
  ii <- which(df[["chrom"]] == loc$chrom & df[["start"]] >= loc$start & 
                df[["end"]] <= loc$end)
  df <- df[ii,]
  expnames <- colnames(df)[5:ncol(df)]
  
  df <- data.frame(mid = (df[["start"]] + df[["end"]])/2,
                   df[, ..expnames])
  
 showcontrast <- !is.null(contrast) && (length(expnames) > 1L || literalTRUE(show_single_contrast))
  if (showcontrast) {
    cdf <- as.matrix(df[,expnames])
    cdf <- cdf - cdf[, contrast]
    cdf <- cbind.data.frame(mid = df$mid, cdf)
  } else {
    cdf <- NULL
  }
  
  yname <- if (attr(discovery, "signed")) "Compartment Score" else "Score"
  
  # Melt df
  df <- data.frame(mid = rep(df$mid, length(expnames)),
                   score = unlist(df[2:ncol(df)]),
                   exp = rep(expnames, each = nrow(df)))
  
  if (showcontrast) {
    # Melt cdf
    cdf <- data.frame(mid = rep(cdf$mid, length(expnames)),
                      score = unlist(cdf[2:ncol(cdf)]),
                      exp = rep(expnames, each = nrow(cdf)),
                      panel = "Difference")
    df$panel <- yname
  }
  
  
  rownames(df) <- NULL
  
  g <- ggplot2::ggplot(df, ggplot2::aes(mid, score, colour = exp)) +
    ggplot2::geom_line()
  
  if (!is.null(cdf)) {
    g <- g + ggplot2::geom_line(data = cdf) +
      ggplot2::facet_grid(panel ~ ., scales = "free_y", switch = "y")
  }
  
  if (raw) {
    return(g)
  }
  cols <- attr(discovery, "colours")
  g <- g + ggplot2::scale_x_continuous(
    name = paste0("Location ", chr),
    expand = c(0.01,0),
    labels = scales::label_number(scale = 1e-6, suffix = "Mb")
    ) +
    ggplot2::scale_y_continuous(
      name = yname,
      # limits = c(-1.25, 1.25),
      oob = scales::squish,
      limits = centered_limits()
    ) +
    ggplot2::scale_colour_manual(
      name = "Sample",
      breaks = expnames,
      limits = expnames,
      values = cols
    )
  g <- g + ggplot2::theme(
    panel.background = ggplot2::element_blank(),
    axis.line = ggplot2::element_line(colour = "black"),
    legend.key = ggplot2::element_blank(),
    aspect.ratio = 2 / (1 + sqrt(5)),
    text = ggplot2::element_text(color = 'black'),
    axis.text = ggplot2::element_text(color = 'black'),
  )
  if (!is.null(cdf)) {
    g <- g + ggplot2::theme(
      strip.placement = "outside",
      axis.title.y = ggplot2::element_blank(),
      strip.background = ggplot2::element_blank(),
      strip.text = ggplot2::element_text(size = ggplot2::rel(1))
    )
  }
  g
}

#' @rdname visualise
#' @export
#' @usage NULL
visualise.IS_discovery <- function(discovery, contrast = NULL, chr = "chr1",
                                   start = NULL, end = NULL, raw = FALSE, 
                                   show_single_contrast = FALSE,
                                   ...) {
  start <- if (is.null(start)) -Inf else start
  end <- if (is.null(end)) Inf else end
  loc <- standardise_location(chr, start, end, singular = TRUE)
  df <- as.data.table(discovery$insula_score)
  ii <- which(df[["chrom"]] == loc$chrom & df[["start"]] >= loc$start & 
                df[["end"]] <= loc$end)
  df <- df[ii,]
  expnames <- colnames(df)[5:ncol(df)]
  
  df <- data.table(mid = (df[["start"]] + df[["end"]])/2,
                   df[, ..expnames])

  showcontrast <- !is.null(contrast) && 
    (length(expnames) > 1L || literalTRUE(show_single_contrast))
  
  if (showcontrast) {
    cdf <- as.matrix(df[,..expnames])
    cdf <- cdf - cdf[, contrast]
    cdf <- cbind.data.frame(mid = df$mid, cdf)
  } else {
    cdf <- NULL
  }
  
  yname <- "Insulation Score"
  
  # Melt df
  df <- data.frame(mid = rep(df$mid, length(expnames)),
                   score = unlist(as.list(df)[2:ncol(df)]),
                   exp = rep(expnames, each = nrow(df)))
  
  if (showcontrast) {
    # Melt cdf
    cdf <- data.frame(mid = rep(cdf$mid, length(expnames)),
                      score = unlist(cdf[2:ncol(cdf)]),
                      exp = rep(expnames, each = nrow(cdf)),
                      panel = "Difference")
    df$panel <- yname
  }
  
  
  rownames(df) <- NULL
  
  g <- ggplot2::ggplot(df, ggplot2::aes(mid, score, colour = exp)) +
    ggplot2::geom_line()
  
  if (!is.null(cdf)) {
    g <- g + ggplot2::geom_line(data = cdf) +
      ggplot2::facet_grid(panel ~ ., scales = "free_y", switch = "y")
  }
  
  if (raw) {
    return(g)
  }
  cols <- attr(discovery, "colours")
  g <- g + ggplot2::scale_x_continuous(
    name = paste0("Location ", chr),
    expand = c(0.01,0),
    labels = scales::label_number(scale = 1e-6, suffix = "Mb")
  ) +
    ggplot2::scale_y_continuous(
      name = yname,
      oob = scales::squish,
      limits = symmetric_quantiles(df$score, c(0.05, 0.95))
    ) +
    ggplot2::scale_colour_manual(
      name = "Sample",
      breaks = expnames,
      limits = expnames,
      values = cols
    )
  g <- g + ggplot2::theme(
    panel.background = ggplot2::element_blank(),
    axis.line = ggplot2::element_line(colour = "black"),
    legend.key = ggplot2::element_blank(),
    aspect.ratio = 2 / (1 + sqrt(5)),
    text = ggplot2::element_text(color = 'black'),
    axis.text = ggplot2::element_text(color = 'black'),
  )
  if (!is.null(cdf)) {
    g <- g + ggplot2::theme(
      strip.placement = "outside",
      axis.title.y = ggplot2::element_blank(),
      strip.background = ggplot2::element_blank(),
      strip.text = ggplot2::element_text(size = ggplot2::rel(1))
    )
  }
  g
}

#' @rdname visualise
#' @export
#' @usage NULL
visualise.DI_discovery <-  function(discovery, contrast = NULL, chr = "chr1",
                                    start = NULL, end = NULL, raw = FALSE, 
                                    show_single_contrast = FALSE,
                                    ...) {
  start <- if (is.null(start)) -Inf else start
  end <- if (is.null(end)) Inf else end
  loc <- standardise_location(chr, start, end, singular = TRUE)
  df <- discovery$DI
  ii <- which(df[["chrom"]] == loc$chrom & df[["start"]] >= loc$start & 
                df[["end"]] <= loc$end)
  df <- df[ii,]
  expnames <- tail(colnames(df), -4)
  
  df <- cbind.data.frame(mid = (df[["start"]] + df[["end"]]/2),
                         df[, expnames])
  
  yname <- "Directionality Index"
  
  showcontrast <- !is.null(contrast) && (length(expnames) > 1L || literalTRUE(show_single_contrast))
  
  if (showcontrast) {
    
    cdf <- do.call(cbind.data.frame, c(list(mid = df$mid), lapply(df[, -1], function(x){
      x - df[[contrast + 1]]
    })))
    setDT(cdf)
    cdf <- melt.data.table(cdf, value.name = "dir_index", id.vars = "mid")
    cdf[, panel := factor("Difference", levels = c(yname, "Difference"))]
    setDT(df)
    df <- melt.data.table(df, value.name = "dir_index", id.vars = "mid")
    df$panel <- factor(yname, levels = c(yname, "Difference"))
  } else {
    setDT(df)
    df <- melt.data.table(df, value.name = "dir_index", id.vars = "mid")
    cdf <- NULL
  }

  rownames(df) <- NULL
  
  g <- ggplot2::ggplot(df, ggplot2::aes(mid, dir_index, colour = variable)) +
    ggplot2::geom_line()
  
  if (!is.null(cdf)) {
    g <- g + ggplot2::geom_line(data = cdf) +
      ggplot2::facet_grid(panel ~ ., scales = "free_y", switch = "y")
  }
  
  if (raw) {
    return(g)
  }
  cols <- attr(discovery, "colours")
  g <- g + ggplot2::scale_x_continuous(
    name = paste0("Location ", chr),
    expand = c(0.01,0),
    labels = scales::label_number(scale = 1e-6, suffix = "Mb")
  ) +
    ggplot2::scale_y_continuous(
      name = yname,
      # limits = c(-1.25, 1.25),
      oob = scales::squish,
      limits = symmetric_quantiles(df$dir_index, c(0.005, 0.995))
    ) +
    ggplot2::scale_colour_manual(
      name = "Sample",
      breaks = expnames,
      limits = expnames,
      values = cols
    )
  g <- g + ggplot2::theme(
    panel.background = ggplot2::element_blank(),
    axis.line = ggplot2::element_line(colour = "black"),
    legend.key = ggplot2::element_blank(),
    aspect.ratio = 2 / (1 + sqrt(5)),
    text = ggplot2::element_text(color = 'black'),
    axis.text = ggplot2::element_text(color = 'black'),
  )
  if (!is.null(cdf)) {
    g <- g + ggplot2::theme(
      strip.placement = "outside",
      axis.title.y = ggplot2::element_blank(),
      strip.background = ggplot2::element_blank(),
      strip.text = ggplot2::element_text(size = ggplot2::rel(1))
    )
  }
  g
}

# Miscellaneous discoveries ----------------------------------------------------

#' @rdname visualise
#' @section Extended arguments:\subsection{Relative Contact Probability}{
#' \describe{
#'  \item{\code{metric}}{A \code{character} of length one. The choices are:
#'  \describe{
#'   \item{\code{"smooth"}}{A 
#'   \ifelse{html}{\out{log<sub>10</sub>}}{\eqn{log_10}}-smoothed line 
#'   (default)}
#'   \item{\code{"both"}}{Like \code{"smooth"}, but also adds the raw binned 
#'   distances as points.}
#'   \item{\code{"lfc"}}{Displays 
#'   \ifelse{html}{\out{log<sub>2</sub>}}{\eqn{log_2}} fold change compared to 
#'   the sample specified in the \code{contrast} argument.}
#'  }}
#'  \item{\code{flipFacet}}{A \code{logical} of length one. If the 
#'  \code{bedlist} argument was provided to \code{RCP()}, combine all regions 
#'   in one panel? Defaults to \code{FALSE}.}
#' }
#' }
#' @export
#' @usage NULL
visualise.RCP_discovery = function(discovery, contrast = 1, 
                                   metric = c("smooth","both","lfc"), raw = FALSE, 
                                   title = NULL, flipFacet = FALSE, ...){
  
  metric <- match.arg(metric)
  
  nregion = length(unique(discovery$smooth$region))
  
  # colours
  smplCols = unique(discovery$smooth[,c('samplename', 'colour')])
  
  smplCols = lapply(split(smplCols,smplCols$colour), function(x){
    NL = nrow(x)
    if(NL > 1){
      
      cols = (col2rgb(unique(x$colour))+1)/255
      cols = sapply(1:NL, function(i){
        
        factor = ((1/(NL+2))*i)
        CF = cols+factor
        
        if(any(CF > 1)){
          factor = ((1/(NL+2))*i)
          CF = cols-factor
        }
        
        rgb(t(CF), maxColorValue = 1)
        
      })
      x$colour =  cols
    } else {
      x$colour =  rgb(t(col2rgb(unique(x$colour))/255))
    }
    x
  })
  
  smplCols = data.table::rbindlist(smplCols)
  smplCols = setNames(smplCols$colour,smplCols$samplename)
  
  D3cols <-c("#1F77B4", "#FF7F0E", "#2CA02C", "#D62728", "#9467BD", "#8C564B", 
             "#E377C2", "#7F7F7F", "#BCBD22", "#17BECF", "#AEC7E8", "#FFBB78", 
             "#98DF8A", "#FF9896", "#C5B0D5", "#C49C94", "#F7B6D2", "#C7C7C7", 
             "#DBDB8D", "#9EDAE5")
  regCols = D3cols[1:nregion]
  
  
  ####################################################################### LFC
  
  if(metric == 'lfc'){
    smplevels = levels(discovery$smooth$samplename)
    if(is.numeric(contrast)){
      contrast = smplevels[contrast]
    } else if(!contrast %in% smplevels){
      stop('The contrast given is not found as samplename.')
    }
    
    # breaks
    logRange = round(log10(unique(discovery$smooth$distance)))
    logRange = range(logRange[is.finite(logRange)])
    logRange[2] = logRange[2] +1
    
    breaks <- 10**seq(from = logRange[1],
                      to =  logRange[2], length.out = 101)
    breaks = c(0,breaks)
    
    
    lfcDT = lapply(unique(discovery$smooth$region), function(REGION){
      
      tmp = RCPlfc(discovery$raw[discovery$raw$region == REGION,], contrast, breaks )
      tmp$region = REGION
      tmp
    })
    lfcDT = rbindlist(lfcDT)
    
    
    
    
    GG = NULL
    if(nregion == 1){
      GG = ggplot2::ggplot(lfcDT, ggplot2::aes(x = log10(distance), y = P ,col = samplename)) +
        ggplot2::labs(x = 'distance (Mb)', col = 'sample', y = expression("log2(P"[sample]*"/P"[contrast]*")")) +
        ggplot2::scale_color_manual(values = smplCols)
    } else if(flipFacet){
      GG =   ggplot2::ggplot(lfcDT, ggplot2::aes(x = log10(distance), y = P ,col = region)) +
        ggplot2::facet_grid(. ~ samplename)+
        ggplot2::labs(x = 'distance (Mb)', col = 'region', y = expression("log2(P"[sample]*"/P"[contrast]*")")) +
        ggplot2::scale_color_manual(values = regCols)
    } else {
      GG =ggplot2::ggplot(lfcDT, ggplot2::aes(x = log10(distance), y = P ,col = samplename)) +
        ggplot2::facet_grid(. ~ region)+
        ggplot2::labs(x = 'distance (Mb)', col = 'sample', y = expression("log2(P"[sample]*"/P"[contrast]*")"))+
        ggplot2::scale_color_manual(values = smplCols)
    }
    RAUW = GG +   
      ggplot2::geom_line() 
    
    # GG: misc
    breaks = unique(round(log10(unique(discovery$smooth$distance))))
    breaks = breaks[is.finite(breaks)]
    
    GG = GG + ggplot2::theme_classic() +
      ggplot2::scale_x_continuous(breaks = breaks, labels = paste0((10**breaks)/1e6)) + 
      ggplot2::geom_line() +
      ggplot2::coord_fixed(ylim = range(lfcDT$P))
    
    GG = GG +  ggplot2::theme(panel.background = ggplot2::element_blank(),
                              aspect.ratio = 1,
                              strip.background = ggplot2::element_rect(fill = NA, colour = NA),
                              panel.border = ggplot2::element_rect(fill = NA, colour = 'black'),
                              text = ggplot2::element_text(color = 'black'),
                              axis.line = ggplot2::element_blank(),
                              axis.text = ggplot2::element_text(colour = 'black'),
                              strip.text = ggplot2::element_text(colour = 'black') )
    
    if(!is.null(title)){
      GG = GG + ggplot2::ggtitle(title)
    }
    
    if(raw){
      suppressWarnings(RAUW)
    } else {
      suppressWarnings(GG)
    }
  } else {
    
    ####################################################################### \ LFC
    # GG: in
    GG = NULL
    if(nregion == 1){
      GG = ggplot2::ggplot(discovery$smooth, ggplot2::aes(x = log10(distance), y = P ,col = samplename)) +
        ggplot2::labs(x = 'distance (Mb)', col = 'sample') +
        ggplot2::scale_color_manual(values = smplCols)
    } else if(flipFacet){
      GG =ggplot2::ggplot(discovery$smooth, ggplot2::aes(x = log10(distance), y = P ,col = region)) +
        ggplot2::facet_grid(. ~ samplename)+
        ggplot2::labs(x = 'distance (Mb)', col = 'region') +
        ggplot2::scale_color_manual(values = regCols)
    } else {
      GG =ggplot2::ggplot(discovery$smooth, ggplot2::aes(x = log10(distance), y = P ,col = samplename)) +
        ggplot2::facet_grid(. ~ region)+
        ggplot2::labs(x = 'distance (Mb)', col = 'sample')+
        ggplot2::scale_color_manual(values = smplCols)
    }
    RAUW = GG +   
      ggplot2::scale_y_log10() + 
      ggplot2::geom_line() 
    
    # GG: cloud
    if(metric == 'both'){
      GG = GG + ggplot2::geom_point(data = discovery$raw, pch = '.', cex = 1, alpha = 0.01) 
      RAUW = RAUW + ggplot2::geom_point(data = discovery$raw, pch = '.', cex = 1, alpha = 0.01) 
    }
    
    # GG: misc
    breaks = unique(round(log10(unique(discovery$smooth$distance))))
    breaks = breaks[is.finite(breaks)]
    
    GG = GG + ggplot2::theme_classic() +
      ggplot2::scale_x_continuous(breaks = breaks, labels = paste0((10**breaks)/1e6)) + 
      ggplot2::scale_y_log10() + 
      ggplot2::geom_line() +
      ggplot2::coord_fixed(ylim = range(discovery$smooth$P))
    
    GG = GG +  ggplot2::theme(panel.background = ggplot2::element_blank(),
                              aspect.ratio = 1,
                              strip.background = ggplot2::element_rect(fill = NA, colour = NA),
                              panel.border = ggplot2::element_rect(fill = NA, colour = 'black'),
                              text = ggplot2::element_text(color = 'black'),
                              axis.line = ggplot2::element_blank(),
                              axis.text = ggplot2::element_text(colour = 'black'),
                              strip.text = ggplot2::element_text(colour = 'black') )
    
    if(!is.null(title)){
      GG = GG + ggplot2::ggtitle(title)
    }
    
    if(raw){
      suppressMessages(RAUW)
    } else {
      suppressWarnings(GG)
    }
    
  }
}

#' @rdname visualise
#' @section Extended arguments: \subsection{Virtual 4C}{
#' \describe{
#'  \item{\code{bins}}{An \code{integer} of length 1 with the number of bins to
#'   aggregate signal in. If \code{NULL} (the default), this is set to the 
#'   number of Hi-C bins in the viewpoint's chromosome.}
#'  \item{\code{bedlist}}{Either a BED-formatted \code{data.frame} or a 
#'  \code{list} thereof, indicating genomic intervals to annotate in the bottom 
#'  margin of the plot.}
#'  \item{\code{extend_viewpoint}}{An \code{integer} of length one in basepairs
#'  indicating by how much to widen the viewpoint censor-box. Affects the 
#'  scaling of the y-axis.}
#' }
#' }
#' @export
#' @usage NULL
visualise.virtual4C_discovery <- function(discovery, bins = NULL, 
                                          bedlist = NULL, bed_colours = "black", 
                                          extend_viewpoint = NULL, ...){
  data <- as.data.table(discovery$data)
  VP   <- attr(discovery,"viewpoint")
  data <- data[chromosome == VP[1, 1]]

  expnames <- tail(colnames(data), -2)
  
  if(!is.null(extend_viewpoint)){
    VP[, 2] <- VP[, 2] - extend_viewpoint
    VP[, 3] <- VP[, 3] + extend_viewpoint
  }
  
  if( is.null(bins) ) {
    bins = nrow(data[!duplicated(mid)])
  }
  
  VP_mid <- rowMeans(attr(discovery, 'viewpoint')[, 2:3])
  
  blackout_up   <- VP[, 2]
  blackout_down <- VP[, 3]
  
  data <- melt.data.table(data, id.vars = c("chromosome", "mid"))
  
  data_blackout <- data
  for (i in seq_len(nrow(VP))) {
    if (nrow(VP) > 1) {
      expcheck1 <- data_blackout$variable != expnames[i]
      expcheck2 <- data$variable != expnames[i]
    } else {
      expcheck1 <- expcheck2 <- FALSE
    }
    data_blackout <- data_blackout[(mid >= blackout_up[i] & 
                                      mid <= blackout_down[i]) |
                                     expcheck1]
    data <- data[!(mid >= blackout_up[i] & mid <= blackout_down[i]) |
                   expcheck2]
  }

  if( !is.null(bedlist)) {
    if (inherits(bedlist, "list")) {
      bedlist <- lapply(seq_along(bedlist), function(i) {
        bed <- bedlist[[i]]
        if (!inherits(bed, "data.frame") | is.data.table(bed)) {
          bed <- as.data.frame(bed)
        }
        bed <- bed[bed[,1] == VP[1, 1], 2:3]
        colnames(bed) <- c("start", "end")
        bed$entry <- i
        bed
      })
      bed <- do.call(rbind, bedlist)
    } else {
      if (!inherits(bedlist, "data.frame") | is.data.table(bedlist)) {
        bedlist <- as.data.frame(bedlist)
      }
      bed <- bedlist
      bed <- bed[bed[,1] == VP[1, 1], 2:3]
      colnames(bed) <- c("start", "end")
      bed$entry <- 1
    }
  } else {
    bed <- NULL
  }

  breaks   <- seq(min(data$mid), max(data$mid), length.out = bins)
  bin_size <- median(diff(breaks))
  data <- data[is.finite(value)]
  smooth   <- data[, mean(value), 
                   by = list(findInterval(data$mid, breaks), variable)]
  smooth$mid = breaks[smooth[[1]]] + (bin_size/2)
  smooth[,1] = NULL
  colnames(smooth) = c("variable", "value","mid")
  
  smooth$experiment <- factor(smooth$experiment, levels = expnames)
  data$experiment <- factor(data$experiment, levels = expnames)
  p = ggplot2::ggplot(data, ggplot2::aes(x= mid, y = value)) +
    ggplot2::geom_col(data = smooth, fill = 'black', width = bin_size,
                      colour = NA) +
    ggplot2::theme_classic() +
    ggplot2::scale_x_continuous(name = paste0("Location ", VP[1, 1], " (Mb)"),
                                labels = scales::label_number(scale = 1e-6)) +
    ggplot2::scale_y_continuous(name = "Signal",
                                breaks = function(x) {
                                  scales::extended_breaks()(pmax(x, 0))
                                }) +
    ggplot2::facet_grid(variable ~ .)
  
  # draw_blackout ===+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  ymax <- ceiling(max(smooth$value))
  data_blackout$value <- ymax

  if (nrow(data_blackout) > 0) {
    blackout <- data_blackout[, list(min = min(mid), max = max(mid)), 
                              by = "variable"]
    blackout[, "mid" := (min + max) / 2]
    p <- p + ggplot2::geom_rect(
      data = blackout, fill = "#D8D8D8", colour = "#D8D8D8",
      ggplot2::aes(xmin = min, xmax = max, ymin = 0, ymax = ymax),
      inherit.aes = FALSE
    ) +
      ggplot2::geom_text(
        data = blackout, label = "\u2693", vjust = 1,
        ggplot2::aes(mid, ymax * 0.9)
      )
  }
  bed_size <- ymax/50
  bed_padding <- ymax/200
  
  if( !is.null(bed) ){
    bed$colour <- bed_colours[pmin(bed$entry, length(bed_colours))]
    bed$ymin <- -1 * bed_size * bed$entry - bed_padding * (bed$entry)
    bed$ymax <- bed$ymin + bed_size
    n_entries <- length(unique(bed$entry))
    
    p <- p + ggplot2::geom_rect(
      data = bed, inherit.aes = FALSE,
      ggplot2::aes(xmin = start, xmax = end, 
                   ymin = ymin,
                   ymax = ymax),
      fill = rep(bed$colour, length(expnames))
    ) +
      ggplot2::coord_cartesian(
        expand = F,
        ylim = c(-1 * (bed_size * n_entries) - bed_padding * (n_entries + 1),
                 ymax)
      )
  } else {
    p = p + ggplot2::coord_cartesian(expand = F, 
                                     ylim = c(0, ymax))
  }

  p <- p + ggplot2::theme(axis.line = ggplot2::element_line(colour = 'black'),
                          axis.text = ggplot2::element_text(colour = 'black'),
                          strip.background = ggplot2::element_blank())
  suppressWarnings(p)
}

#' @rdname visualise
#' @section Extended arguments:\subsection{Saddle}{
#' \describe{
#'  \item{\code{chr}}{A \code{character} of length one with the chromosome 
#'   name. Defaults to \code{"all"}.}
#'  \item{\code{show_single_contrast}}{A \code{logical} of length 1; 
#'   if \code{FALSE} (default), does not show contrasts when \code{discovery} 
#'   describes one experiment. If \code{TRUE}, plots empty panel.}
#' }
#' }
#' @export
#' @usage NULL
visualise.saddle_discovery <- function(discovery, contrast = 1,
                                       chr = "all",
                                       raw = FALSE, title = NULL,
                                       colour_lim = NULL,
                                       colour_lim_contrast = NULL, 
                                       show_single_contrast = FALSE,
                                       ...) {
  df <- discovery$saddle
  df <- df[!is.na(mean) & !is.na(q1),]
  df$exp <- factor(df$exp, levels = unique(df$exp))
  
  if (chr != "all") {
    if (endsWith(chr, "p") || endsWith(chr, "q")) {
      chromo <- chr
      df <- df[chr == chromo,]
    } else {
      chromo <- df[["chr"]]
      chromo <- substr(chromo, 1, nchar(chromo) - 1)
      keep <- chromo == chr
      df <- df[keep,]
    }
  }
  
  expnames <- df[, unique(exp)]
  
  # Aggregate across chromosomes
  df <- df[, mean(mean), by = list(exp, q1, q2)]
  setnames(df, 4, "obsexp")
  
  # Mirror along diagonal
  comp <- df[q1 != q2, ]
  setnames(comp, 2:3, names(comp)[3:2])
  df <- rbindlist(list(df, comp), use.names = TRUE)

  showcontrast <- !is.null(contrast) && (length(expnames) > 1L || show_single_contrast)
  if (showcontrast) {
    contrast <- df[exp == expnames[contrast],]
    m <- matrix(NA_real_, max(contrast$q1), max(contrast$q2))
    i <- as.matrix(contrast[,list(q1, q2)])
    m[i] <- contrast[["obsexp"]]
    
    contrast <- lapply(expnames, function(j) {
      xx <- df[exp == j]
      mm <- m
      mm[as.matrix(xx[,list(q1, q2)])] <- xx[["obsexp"]]
      mm <- mm - m
      data.table(exp = j, q1 = row(mm)[TRUE], q2 = col(mm)[TRUE], 
                 diff = mm[TRUE])
    })
    contrast <- rbindlist(contrast)
    contrast <- as.data.frame(contrast)
    contrast$panel <- factor("Difference", 
                             levels = c("Individual", "Difference"))
    contrast$q1 <- contrast$q1 / max(contrast$q1)
    contrast$q2 <- contrast$q2 / max(contrast$q2)
  }
  
  df$panel <- factor("Individual",
                     levels = c("Individual", "Difference"))
  df <- as.data.frame(df)
  df$q1 <- df$q1 / max(df$q1)
  df$q2 <- df$q2 / max(df$q2)

  g <- ggplot2::ggplot(df, ggplot2::aes(q1, q2)) +
    ggplot2::geom_raster(hjust = 0, vjust = 0, ggplot2::aes(fill = obsexp))
  
  if (!is.null(title)) {
    g <- g + ggplot2::ggtitle(title)
  }
  
  if (showcontrast) {
    
    suppressWarnings(
      g <- g + ggplot2::geom_raster(data = contrast, 
                                    ggplot2::aes(q1, q2, altfill = diff),
                                    inherit.aes = FALSE,
                                    hjust = 0, vjust = 0) +
        ggplot2::facet_grid(panel ~ exp, switch = "y")
    )
    
    if (!raw) {
      g <- g + scale_fill_GENOVA_div(
        palette = "greenpink",
        aesthetics = "altfill",
        oob = scales::squish,
        name = "Difference",
        limits = colour_lim_contrast,
        midpoint = 0
      )
      g$scales$scales[[1]]$guide <- ggplot2::guide_colourbar()
      g$scales$scales[[1]]$guide$available_aes[[3]] <- "altfill"
      g$scales$scales[[1]]$guide$order <- 2
    } else {
      g <- g + ggplot2::guides(
        "altfill" = ggplot2::guide_colourbar(available_aes = "altfill")
      )
    }
    
    # ggnomics style hack
    old_geom <- g$layers[[2]]$geom
    old_nahandle <- old_geom$handle_na
    new_nahandle <- function(self, data, params) {
      colnames(data)[colnames(data) %in% "altfill"] <- "fill"
      old_nahandle(data, params)
    }
    new_geom <- ggplot2::ggproto(paste0(sample(1e6, 1), class(old_geom)),
                                 old_geom,
                                 handle_na = new_nahandle)
    names(new_geom$default_aes)[1] <- "altfill"
    new_geom$non_missing_aes <- "altfill"
    g$layers[[2]]$geom <- new_geom
  } else if (length(unique(df$exp)) > 1) {
    g <- g + ggplot2::facet_grid(~ exp)
  }
  
  if (raw) {
    return(g)
  } else {
    g <- g + ggplot2::theme(
      aspect.ratio = 1,
      strip.placement = "outside",
      strip.background = ggplot2::element_blank(),
      panel.border = ggplot2::element_rect(fill = NA, colour = "black",
                                           size = 0.25),
      axis.text = ggplot2::element_text(colour = "black"),
      axis.ticks = ggplot2::element_line(colour = "black", size = 0.25),
      strip.text = ggplot2::element_text(colour = "black"),
      panel.spacing.x = grid::unit(0.8 * 0.5, "strwidth", data = c("1.00.0")),
      panel.spacing.y = grid::unit(0.8 * 1.5, "strheight", data = c("1.00.0"))
    ) +
      ggplot2::scale_x_continuous(name = "Quantile", expand = c(0,0),
                                  breaks = c(0, 1), labels = c("B", "A")) +
      ggplot2::scale_y_continuous(name = "Quantile", expand = c(0,0),
                                  breaks = c(0, 1), labels = c("B", "A")) +
      scale_fill_GENOVA_div(
        palette = "divergent",
        midpoint = 1,
        limits = colour_lim,
        oob = scales::squish,
        name = expression(frac("Observed", "Expected")),
        guide = ggplot2::guide_colourbar(order = 1)) +
      ggplot2::coord_cartesian(clip = "off")
  }
  return(g)
}

#' @rdname visualise
#' @usage NULL
#' @export
visualise.domainogram_discovery <- function(discovery, 
                                            colour_lim = c(-1, 1),
                                            title = NULL,
                                            raw = FALSE, ...) {
  df <- discovery$scores
  df <- as.data.table(df)
  df <- melt.data.table(df, id.vars = c("window", "position"), 
                        value.name = "insulation")
  setnames(df, 3, "experiment")
  
  g <- ggplot2::ggplot(df, ggplot2::aes(position, window, fill = insulation)) +
    ggplot2::geom_raster() +
    ggplot2::facet_grid(experiment ~ .)
  
  if (!is.null(title)) {
    g <- g + ggplot2::ggtitle(title)
  }
  
  if (!raw) {
    g <- g + 
      ggplot2::scale_x_continuous(name = paste0("Position ", 
                                                attr(df, "chrom"), " (Mb)"),
                                  labels = scales::label_number(scale = 1e-6),
                                  expand = c(0,0)) +
      ggplot2::scale_y_continuous(name = "Window Size", expand = c(0, 0)) +
      scale_fill_GENOVA_div(midpoint = 0,
                            trans = "reverse",
                            limits = rev(colour_lim), 
                            oob = scales::squish,
                            name = "Insulation\nScore") +
      ggplot2::theme(axis.text = ggplot2::element_text(colour = "black"),
                     strip.background = ggplot2::element_blank(),
                     axis.ticks = ggplot2::element_line(colour = "black"),
                     axis.line  = ggplot2::element_line(colour = "black"),
                     panel.grid = ggplot2::element_blank(),
                     panel.background = ggplot2::element_blank())
  }
  
  g
}

#' @rdname visualise
#' @section Extended arguments:\subsection{Intra-inter TAD}{
#' \describe{
#'  \item{\code{geom}}{One of the following length one \code{character}s:
#'    \describe{
#'      \item{\code{"boxplot"}}{Display as boxplots.}
#'      \item{\code{"violin"}}{Display as violin plots.}
#'      \item{\code{"jitter"}}{Display as jittered points plot.}
#'    }
#'  }
#'  \item{\code{censor_contrast}}{A \code{logical} of length 1 deciding whether 
#'  the contrasting experiment itself should be censored (\code{TRUE}) or
#'  included (\code{FALSE}).}
#' }
#' }
#' @export
#' @usage NULL
visualise.IIT_discovery <- function(discovery, contrast = 1, raw = FALSE,
                                    geom = c("boxplot", "violin", "jitter"),
                                    censor_contrast = TRUE, title = NULL, 
                                    show_single_contrast = FALSE,
                                    ...) {
  geom <- match.arg(geom)
  dat <- as.data.table(discovery$results)
  cols <- attr(discovery, "colours")
  
  expnames <- tail(colnames(dat), -2)
  
  if (!is.null(contrast) & length(expnames) < 2) {
    if (show_single_contrast) {
      message("Cannot compute a contrast for one sample. Reverting to ",
              "visualising plain values.")
    }
    contrast <- NULL
  } else if (!is.null(contrast)){
    contrast <- expnames[contrast]
    
    trans <- lapply(setNames(expnames, expnames), function(i) {
      log2(dat[[i]] / dat[[contrast]])
    })
    
    for (i in expnames) {
      dat[, as.character(i) := trans[[i]]]
    }
    
    if (censor_contrast & !is.null(contrast)) {
      dat <- dat[, -..contrast]
      cols <- cols[which(expnames != contrast)]
    }
  }
  
  df <- melt.data.table(
    dat, id.vars = c("x", "y"), 
    measure.vars = intersect(colnames(dat), expnames)
  )
  
  df$diff <- as.factor(df$y - df$x)

  g <- ggplot2::ggplot(df, ggplot2::aes(diff, value))
  
  if (!is.null(title)) {
    g <- g + ggplot2::ggtitle(title)
  }
  
  if (geom == "boxplot") {
    g <- g + ggplot2::geom_boxplot(ggplot2::aes(fill = variable),
                                   key_glyph = "polygon")
  } else if (geom == "violin") {
    g <- g + ggplot2::geom_violin(ggplot2::aes(fill = variable))
  } else if (geom == "jitter") {
    g <- g + ggplot2::geom_point(ggplot2::aes(colour = variable),
                                 position = ggplot2::position_jitterdodge(),
                                 size = 1, alpha = 0.3, shape = 16)
  }
  
  if (raw) {
    return(g)
  }
  
  if ("fill" %in% names(g$layers[[1]]$mapping)) {
    g <- g + ggplot2::scale_fill_manual(
      values = cols, name = "Experiment"
    )
  } else {
    g <- g + ggplot2::scale_colour_manual(
      values = cols, name = "Experiment",
      guide = ggplot2::guide_legend(
        override.aes = list(size = 2, alpha = 1)
      )
    )
  }
  
  g <- g + ggplot2::scale_x_discrete(
    name = "TAD Distance",
    labels = scales::math_format(n + .x)
    # labels = scales::label_number(prefix = "n +")
  )
  
  if (is.null(contrast)) {
    g <- g + ggplot2::scale_y_continuous(
      name = "Contacts",
      trans = "log10",
      labels = scales::math_format(format = log10)
    )
  } else {
    ytitle <- bquote("Log"[2]~" (Experiment contacts /" ~ 
                       paste(.(contrast)) ~ "contacts)")
    g <- g + ggplot2::scale_y_continuous(
      name = ytitle,
    )
  }
  
  g <- g + ggplot2::theme(
    text = ggplot2::element_text(colour = "black"),
    axis.text = ggplot2::element_text(colour = "black"),
    axis.line = ggplot2::element_line(colour = "black"),
    panel.background = ggplot2::element_blank(),
    panel.grid = ggplot2::element_blank(),
    panel.grid.major = ggplot2::element_blank(),
    legend.key = ggplot2::element_blank()
  )
  
  return(g)
}

#' @rdname visualise
#' @usage NULL
#' @export
visualise.chrommat_discovery <- function(discovery, raw = FALSE, title = NULL,
                                         colour_lim = NULL, ...) {
  obsexp <- discovery$obs
  dim <- dim(obsexp)
  if (attr(discovery, "mode") %in% c("trans", "regress")) {
    tmp <- obsexp
    tmp[slice.index(obsexp, 1) == slice.index(obsexp, 2)] <- 0
  } else {
    tmp <- obsexp
  }
  sums <- apply(tmp, 3, sum)
  obsexp[] <- log2((obsexp / rep(sums, each = prod(dim[1:2]))) / discovery$exp)
  
  df <- data.frame(
    row = as.vector(slice.index(obsexp, 1)),
    col = as.vector(slice.index(obsexp, 2)),
    exp = as.vector(slice.index(obsexp, 3))
  )
  df[] <- mapply(function(x, i){x[i]}, x = dimnames(obsexp), i = df)
  df$value <- as.vector(obsexp)
  
  if (is.null(colour_lim)) {
    colour_lim <- range(with(df, value[row != col]))
    colour_lim[1] <- min(colour_lim[1], -1)
    colour_lim[2] <- max(colour_lim[2], 1) 
  }

  
  if (utils::packageVersion("ggplot2") > "3.2.1") {
    guide_x <- ggplot2::guide_axis(check.overlap = TRUE, angle = 90)
    guide_y <- ggplot2::guide_axis(check.overlap = TRUE)
  } else {
    guide_x <- guide_y <- ggplot2::waiver()
  }
  
  g <- ggplot2::ggplot(df, ggplot2::aes(row, col, fill = value)) +
    ggplot2::geom_raster() +
    ggplot2::facet_grid(~ exp) +
    ggplot2::scale_x_discrete(limits = dimnames(obsexp)[[1]], guide = guide_x,
                              expand = c(0,0), name = "") +
    ggplot2::scale_y_discrete(limits = dimnames(obsexp)[[2]], guide = guide_y,
                              expand = c(0,0), name = "")
  if (!is.null(title)) {
    g <- g + ggplot2::ggtitle(title)
  }
  if (raw) {
    return(g)
  }
  
  g <- g + scale_fill_GENOVA_div(
    name = expression(Log[2]*frac("Observed", "Expected")),
    limits = colour_lim, oob = scales::squish,
    midpoint = 0
  ) +
    ggplot2::coord_equal() +
    GENOVA_THEME()
  g
}

# Utilities ---------------------------------------------------------------

#' scale_altfill_continuous Makes sure no errors are returned when
#' visualise(..., raw = TRUE) Unfortunately has to be exported, but that is
#' better than throwing errors anytime an attempt is mode to print a raw plot.
#'
#' @param ... Arguments to \code{\link[ggplot2]{scale_fill_gradient2}}.
#'
#' @export
scale_altfill_continuous <- function(...) {
  guide <- ggplot2::guide_colourbar()
  guide$available_aes <- "altfill"
  ggplot2::scale_fill_gradient2(..., guide = guide,
                                aesthetics = "altfill")
}

# Function factory for centering limits around a value
centered_limits <- function(around = 0) {
  function(input) {
    c(-1, 1) * max(abs(input - around)) + around
  }
}

symmetric_quantiles <- function(x, q = c(0.05, 0.95)) {
  c(-1, 1) * diff(quantile(x[is.finite(x)], q))
}

# Function factory labelling
label_kilobase_relative <- function(zero = "0") {
  fun <- scales::label_number(scale = 1e-3, suffix = " kb")
  function(x) {
    ifelse(x == 0, zero, fun(x))
  }
}

# Break function factory
breaks_trim_outer <- function(min = 3) {
  function(x) {
    x <- scales::extended_breaks()(x)
    if (length(x) > min) head(tail(x, -1), -1) else x
  }
}

# Convenience function for filtering layer data when inheriting from
# main ggplot2 call
datafilter <- function(expr = NULL) {
  expr <- substitute(expr)
  if (is.null(expr)) {
    expr <- substitute(TRUE)
  }
  f <- function(x) subset.data.frame(x, eval(expr))
  parent.env(environment(f)) <- parent.frame()
  f
}
robinweide/GENOVA documentation built on March 14, 2024, 11:16 p.m.