#' @title Plotting 2D-landscape images for the PathwaySpace package
#'
#' @description \code{plotPathwaySpace} is a wrapper function to 
#' create dedicated ggplot graphics for PathwaySpace-class objects.
#'
#' @param ps A \linkS4class{PathwaySpace} class object.
#' @param colors A vector of colors.
#' @param bg.color A single color for background.
#' @param si.color A single color for silhouette.
#' (see \code{\link{silhouetteMapping}}).
#' @param si.alpha A transparency level in [0, 1], used to adjust the 
#' opacity of the silhouette. This parameter is useful for improving the 
#' perception of a background image, when one is available.
#' @param theme Name of a custom PathwaySpace theme. These themes 
#' (from 'th0' to 'th3') consist mainly of preconfigured ggplot settings, 
#' which the user can subsequently refine using \code{\link[ggplot2]{ggplot2}}.
#' @param title A string for the title.
#' @param xlab The title for the 'x' axis of a 2D-image space.
#' @param ylab The title for the 'y' axis of a 2D-image space.
#' @param zlab The title for the 'z' axis of the image signal.
#' @param font.size A single numeric value passed to plot annotations.
#' @param font.color A single color passed to plot annotations.
#' @param zlim The 'z' limits of the plot (a numeric vector with two numbers).
#' If NULL, limits are determined from the range of the input values.
#' @param slices A single positive integer value used to split 
#' the image signal into equally-spaced intervals.
#' @param add.grid A logical value indicating whether to add gridlines to 
#' the image space. However, gridlines will only appear when the image 
#' is decorated with graph silhouettes (see \code{\link{silhouetteMapping}}).
#' @param grid.color A color passed to \code{\link[ggplot2]{geom_point}}.
#' @param add.summits A logical value indicating whether to add contour 
#' lines to 'summits' (when summits are available; 
#' see \code{\link{summitMapping}}).
#' @param label.summits A logical value indicating whether to label summits.
#' @param summit.color A color passed to 'summits'.
#' @param add.marks A logical value indicating whether to plot vertex labels.
#' @param marks A vector of vertex names to be highlighted in the 
#' image space. This argument overrides 'add.labels'.
#' @param mark.size A size argument passed to \code{\link[ggplot2]{geom_text}}.
#' @param mark.color A color passed to \code{\link[ggplot2]{geom_text}}.
#' @param mark.padding A box padding argument passed to 
#' \code{\link[ggrepel]{geom_text_repel}}.
#' @param mark.line.width A line width argument passed to 
#' \code{\link[ggrepel]{geom_text_repel}}.
#' @param use.dotmark A logical value indicating whether "marks" should be 
#' represented as dots.
#' @param add.image A logical value indicating whether to add a background 
#' image, when one is available (see \code{\link[RGraphSpace]{GraphSpace}}).
#' @return A ggplot-class object.
#' @author Sysbiolab Team, Mauro Castro.
#' @seealso \code{\link{circularProjection}}
#' @examples
#' # Load a demo igraph
#' data('gtoy1', package = 'RGraphSpace')
#'
#' # # Check graph validity
#' gs <- GraphSpace(gtoy1)
#' 
#' gs <- normalizeGraphSpace(gs)
#' 
#' # Create a PathwaySpace object
#' ps <- buildPathwaySpace(gs, nrc = 300)
#' # note: adjust 'nrc' to increase image resolution
#'
#' # Set '1s' as vertex signal
#' vertexSignal(ps) <- 1
#'
#' # Create a 2D-landscape image
#' ps <- circularProjection(ps, k = 2,
#'    decay.fun = weibullDecay(pdist = 0.4))
#'
#' # Plot a 2D-landscape image
#' plotPathwaySpace(ps)
#' 
#' @import methods
#' @docType methods
#' @importFrom ggplot2 ggplot annotate element_text theme theme_bw theme_gray
#' @importFrom ggplot2 scale_x_continuous scale_y_continuous expansion
#' @importFrom ggplot2 aes scale_fill_gradientn annotation_raster
#' @importFrom ggplot2 coord_fixed geom_raster labs
#' @importFrom ggplot2 margin element_blank element_rect element_line
#' @importFrom grid unit
#' @importFrom ggrepel geom_text_repel
#' @importFrom grDevices convertColor col2rgb rgb as.raster
#' @importFrom grDevices adjustcolor colorRampPalette
#' @importFrom stats runif
#' @importFrom RGraphSpace plotGraphSpace theme_gspace_coords
#' @importFrom patchwork wrap_plots
#' @rdname plotPathwaySpace-methods
#' @aliases plotPathwaySpace
#' @export
#'
setMethod("plotPathwaySpace", "PathwaySpace", 
  function(ps, colors = pspace.cols(), bg.color = "grey95", 
    si.color = "grey85", si.alpha = 1,
    theme = c("th0", "th1", "th2", "th3"),
    title = "PathwaySpace", 
    xlab = "Pathway coordinates 1", 
    ylab = "Pathway coordinates 2", 
    zlab = "Density", 
    font.size = 1, font.color = "white",
    zlim = NULL, slices = 25, 
    add.grid = TRUE, grid.color = "white", 
    add.summits = TRUE, label.summits = TRUE, summit.color = "white",
    add.marks = FALSE, marks = NULL, mark.size = 3, mark.color = "white", 
    mark.padding = 0.5, mark.line.width = 0.5, use.dotmark = FALSE, 
    add.image = FALSE) {
    
    #--- validate the ps object and args
    if (!.checkStatus(ps, "Projection") && !.checkStatus(ps, "Silhouette")) {
      stop("NOTE: 'ps' needs to be evaluated by a 'projection' method!",
        call. = FALSE)
    }
    .validate.ps.args("singleNumber", "si.alpha", si.alpha)
    .validate.ps.args("singleString", "title", title)
    .validate.ps.args("singleString", "xlab", xlab)
    .validate.ps.args("singleString", "ylab", ylab)
    .validate.ps.args("singleString", "zlab", zlab)
    .validate.ps.args("singleNumber", "font.size", font.size)
    .validate.ps.args("singleInteger", "slices", slices)
    .validate.ps.args("singleLogical", "add.grid", add.grid)
    .validate.ps.args("singleLogical", "add.summits", add.summits)
    .validate.ps.args("singleLogical", "label.summits", label.summits)
    .validate.ps.args("singleLogical", "add.marks", add.marks)
    .validate.ps.args("singleNumber", "mark.size", mark.size)
    .validate.ps.args("singleNumber", "mark.padding", mark.padding)
    .validate.ps.args("singleNumber","mark.line.width", mark.line.width)
    .validate.ps.args("singleLogical", "use.dotmark", use.dotmark)
    .validate.ps.args("singleLogical", "add.image", add.image)
    .validate.colors("singleColor", "mark.color", mark.color)
    .validate.colors("allColors","colors", colors)
    .validate.colors("singleColor", "si.color", si.color)
    .validate.colors("singleColor", "font.color", font.color)
    .validate.colors("singleColor", "grid.color", grid.color)
    .validate.colors("singleColor", "summit.color", summit.color)
    .validate.psplot.args("marks", marks)
    if(!is.na(bg.color) ){
      .validate.colors("singleColor", "bg.color", bg.color)
    }
    theme <- match.arg(theme)
    if(!is.null(zlim)) {
      .validate.ps.args("numeric_vec", "zlim", zlim)
      if(length(zlim)!=2) 
        stop("'zlim' should be a numeric vector of lenght 2.", call. = FALSE)
    }
    if (si.alpha < 0 || si.alpha > 1) {
      stop("'si.alpha' should be in [0,1]", call. = FALSE)
    }
    #--- get slots from ps
    summits <- getPathwaySpace(ps, "summits")
    cset <- getPathwaySpace(ps, "summit_contour")
    silstatus <- .checkStatus(ps, "Silhouette")
    gxy <- getPathwaySpace(ps, "projections")$gxy
    gxyz <- getPathwaySpace(ps, "projections")$gxyz
    pars <- getPathwaySpace(ps, "projections")$pars
    
    #--- set colors
    colors <- colorRampPalette(colors)(25)
    if(pars$ps$configs$scale.type=="negpos"){
      slices <- ceiling(slices/2) * 2
    }
    
    # set scales
    if(is.null(zlim)){
      zlim <- pars$ps$configs$zlim
    } else {
      gxyz[gxyz < zlim[1]] <- zlim[1]
      gxyz[gxyz > zlim[2]] <- zlim[2]
    }
    if (all(zlim == 0)) zlim[2] <- 1
    
    #--- set gspace theme
    gs_theme <- theme_gspace_coords(theme = theme, 
      is_norm = TRUE, xlab = xlab, ylab = ylab, 
      txt_size = font.size, leg_size = font.size, 
      bg_color = "grey95")
    
    gs_pars <- attributes(gs_theme)$gspace_pars
    
    #--- trim colors and set zlim args
    cl <- .trimcols(colors, bg.color, zlim, pars)
    cl <- .set_theme_zlim(cl, zlim)
    bks <- seq(zlim[1], zlim[2], length.out = slices)
    gxyz[, ] <- bks[cut(as.numeric(gxyz), breaks = sort(unique(bks)),
      include.lowest = TRUE)]
    
    #--- get grid
    gridln <- .getGrid(gxyz, gs_pars$axis.ticks)
    gridln <- as.numeric(gridln)
    gridln <- data.frame(
      arrayInd(seq_along(gridln), .dim = dim(gxyz)), gridln)
    colnames(gridln) <- c("Y", "X", "L")
    
    #--- set main input for ggplot
    gxyz <- data.frame(arrayInd(seq_along(gxyz), .dim = dim(gxyz)),
      as.numeric(gxyz))
    colnames(gxyz) <- c("Y", "X", "Z")
    
    #--- scale coordinates to plot space
    gxyz$X <- scales::rescale(gxyz$X)
    gxyz$Y <- scales::rescale(gxyz$Y)
    gxyz$L <- gridln$L
    
    #--- set a bg color effect, scaling alpha to z
    if(si.alpha < 1){
      si.color <- adjustcolor(si.color, si.alpha)
      gz.alpha <- .scale_alpha(si.alpha, gxyz, zlim, pars)
    } else {
      gz.alpha <- 1
    }
    
    #--- initialize ggplot
    ggp <- .set_pspace(gxyz, zlab, cl, si.color)
    
    #--- adjust gs_theme
    ggp <- ggp + gs_theme
    if(theme == "th2"){
      ggp <- ggp + ggplot2::theme(panel.grid = element_blank())
    } else if(theme == "th3"){
      ggp <- ggp + ggplot2::theme(panel.grid = element_blank(),
        legend.position = "bottom")
    }
    #--- add image
    if(pars$image.layer){
      img <- getPathwaySpace(ps, "image")
      if(add.image){
        ggp <- .add_image(ggp, img)
      } else {
        ggi <- .add_image(ggp, img)
        if(add.grid) ggi <- .add_grid(ggi, gxyz, grid.color)
      }
    }
    ggp <- ggp + ggplot2::labs(fill = zlab)
      
    #--- add main projection
    ggp <- ggp + ggplot2::geom_raster(interpolate = FALSE, 
      na.rm=TRUE, alpha = gz.alpha)
    
    #--- add a grid
    if(add.grid) ggp <- .add_grid(ggp, gxyz, grid.color)
    
    #--- add contour lines if available
    bl <- add.summits || label.summits
    if (bl && !is.null(summits) && length(summits) > 0 && sum(cset) > 0) {
      ggp <- .add_contour(ggp, gxyz, summits, cset, 
        summit.color, mark.size, add.summits, label.summits)
    }
    
    #--- add marks if available
    if (!is.null(marks)) {
      ggp <- .add_marks(ggp, gxy, pars, marks, mark.size,
        mark.color, mark.padding, mark.line.width, use.dotmark)
    } else if(add.marks){
      ggp <- .add_marks(ggp, gxy, pars, marks=rownames(gxy), 
        mark.size, mark.color, mark.padding, mark.line.width, use.dotmark)
    }
    
    #--- add annotations
    if(.checkStatus(ps, "Projection")){
      ggp <- .custom_annotations(ggp, title, pars, font.size, 
        font.color, silstatus, si.color)
    }
    
    if(pars$image.layer && !add.image){
      ggl <- list(graph = ggp, image = ggi)
      return(ggl)
    } else {
      return(ggp)
    }
    
  }
)

#-------------------------------------------------------------------------------
.scale_alpha <- function(si.alpha, gxyz, zlim, pars){
  az <- gxyz$Z
  mxz <- max(abs(zlim))
  if(pars$ps$configs$scale.type == "negpos") {
    slim <- 0.5 * (1 - si.alpha)
    slim <- slim * mxz
    az[az < 0 & az < -slim] <- mxz
    az[az > 0 & az > slim] <- mxz
    az <- abs(az)
  } else if(pars$ps$configs$scale.type == "neg") {
    az[az < zlim[2]] <- zlim[1]
  } else {
    az[az > zlim[1]] <- zlim[2]
  }
  pars$ps$configs$zlim
  az <- az/mxz
  alpha <- az^10 + si.alpha
  return(alpha)
}

#-------------------------------------------------------------------------------
.custom_annotations <- function(ggp, title, pars, font.size, 
  font.color, silstatus, si.color){
  if(silstatus){
    if(si.color=="grey85"){
      fcol <- "grey20"
    } else {
      fcol <- font.color
    }
    sep <- "; "
    xlab <- 0.01
    hjust <- 0
  } else {
    fcol <- font.color
    sep <- "\n"
    xlab <- 0.99
    hjust <- 1
  }
  ggp <- ggp + ggplot2::annotate("text", label = title,
    colour = fcol, size = font.size*4, x = 0, y = 0.99, 
    hjust = 0, vjust = 1)
  dfun <- pars$ps$decay$fun
  if(!is.null(dfun)){
    if(dfun == "weibullDecay"){
      dfun <- "Weibull decay"
    } else if(dfun == "expDecay"){
      dfun <- "Exponential decay"
    } else if(dfun == "linearDecay"){
      dfun <- "Linear decay"
    } else {
      dfun <- "Custom decay"
    }
    pars$ps$dfun <- dfun
  } else {
    pars$ps$dfun <- "Custom decay"
  }
  if(pars$ps$projection=="Polar"){
    annot <- pars$ps[c("projection", "dfun", "k", "beta")]
    annot$k <- paste0("k = ", annot$k, "; ")
    annot$beta <- paste0("beta = ", pars$ps$beta)
  } else {
    annot <- pars$ps[c("projection", "dfun", "k")]
    annot$k <- paste0("k = ", annot$k)
  }
  annot$projection <- paste0(annot$projection, " projection", sep)
  annot$dfun <- paste0(annot$dfun, sep)
  annot <- paste(unlist(annot), collapse = "")
  ggp <- ggp + ggplot2::annotate("text", label = annot,
    colour = fcol, size = font.size*3, x = xlab, 
    y = 0.01, hjust = hjust, vjust = 0)
  return(ggp)
}

#-------------------------------------------------------------------------------
.set_pspace <- function(gxyz, zlab, cl, si.color){
  X <- Y <- Z <- NULL
  ggp <- ggplot2::ggplot(gxyz, ggplot2::aes(X, Y, fill = Z)) +
    ggplot2::scale_fill_gradientn(limits = cl$zlim,
      breaks = cl$breaks, labels = names(cl$breaks),
      colours = cl$pal, aesthetics = "fill", na.value = si.color) +
    ggplot2::coord_fixed()
  return(ggp)
}

#-------------------------------------------------------------------------------
.add_grid <- function(ggp, gxyz, grid.color){
  dt <- gxyz[gxyz$L == 1, c("X", "Y")]
  ggp <- ggp + ggplot2::annotate(geom = "point", x = dt$X, 
    y = dt$Y, color = grid.color, size = 0.2, pch = 15)
  return(ggp)
}

#-------------------------------------------------------------------------------
.add_image <- function(ggp, image){
  ggp <- ggp + annotation_raster(image, xmin = 0, xmax = 1, ymin = 0, ymax = 1)
  return(ggp)
}

#-------------------------------------------------------------------------------
.add_contour <- function(ggp, gxyz, summits, cset, summit.color, 
  mark.size, add.summits, label.summits) {
  setnames <- names(summits)
  if (is.null(setnames)) {
    setnames <- seq_along(setnames)
  }
  cset <- data.frame(which(!is.na(cset) | is.na(cset), arr.ind = TRUE),
    as.numeric(cset))
  colnames(cset) <- c("Y", "X", "C")
  gxyz <- cbind(gxyz, C = cset$C)
  xy.tx <- NULL
  concav <- sort(unique(gxyz$C))[-1]
  for (i in seq_along(concav)) {
    xy.cv <- gxyz[gxyz$C == i, c("X", "Y")]
    xy.tx <- rbind(xy.tx, colMeans(xy.cv))
    if(add.summits){
      ggp <- ggp + ggplot2::annotate(geom = "tile", x = xy.cv[, 1], 
        y = xy.cv[, 2], color = summit.color,
        fill = summit.color, linewidth = 0.2)
    }
  }
  rownames(xy.tx) <- setnames
  xy.tx <- as.data.frame(xy.tx)
  if(label.summits){
    if(add.summits){
      ggp <- ggp + ggplot2::annotate(geom = "text", x = xy.tx[, 1],
        y = xy.tx[, 2], label = rownames(xy.tx), vjust = 0.5, hjust = 0.5,
        color = summit.color, size = mark.size,
        fontface = "bold")
    } else {
      nudgex <- sign(xy.tx$X - 0.5) * xy.tx$X
      nudgey <- sign(xy.tx$Y - 0.5) * xy.tx$Y
      ggp <- ggp + ggrepel::geom_text_repel(
        mapping = aes(label = rownames(xy.tx),segment.size = 0.4), 
        xlim = c(0.1, 0.9), ylim = c(0.1, 0.9),
        data = xy.tx, min.segment.length = 0.1,
        fontface = "bold", force = 3, segment.linetype = 1,
        max.overlaps = nrow(xy.tx) + 5, point.padding = 0, seed = 123,
        max.iter = 20000, max.time = 30, nudge_x = nudgex * 0.075,
        nudge_y = nudgey * 0.075, size = mark.size, colour = summit.color,
        segment.colour = summit.color)
    }
  }
  return(ggp)
}

#-------------------------------------------------------------------------------
.add_marks <- function(ggp, gxy, pars, marks, mark.size,
  mark.color, mark.padding, mark.line.width, use.dotmark) {
  
  # scale coordinates to plot space
  gxy_df <- as.data.frame(gxy)
  gxy_df <- gxy_df[, c("X", "Y")]
  gxy_df$X <- scales::rescale(gxy_df$X, from = c(1, pars$ps$nrc))
  gxy_df$Y <- scales::rescale(gxy_df$Y, from = c(1, pars$ps$nrc))
  
  # match  marks
  marks <- marks[!duplicated(marks)]
  if (is.null(names(marks))) names(marks) <- marks
  names(marks) <- ifelse(is.na(names(marks)), marks, names(marks))
  names(marks) <- ifelse(names(marks) == "", marks, names(marks))
  idx_df <- .get_mark_idx(marks, gxy_df)
  if(any(is.na(idx_df$idx))){
    stop("All 'marks' should be annotated in the 'PathwaySpace' object.",
      call. = FALSE)
  }
  
  # set df
  gxy_df <- gxy_df[idx_df$idx, , drop = FALSE]
  gxy_df$ID <- idx_df$label
  ID <- NULL
  
  # call to ggplot
  if (use.dotmark) {
    ggp <- ggp + ggplot2::annotate("point", shape = 5, 
      colour = mark.color, size = mark.size * 0.4, stroke = 0.4, 
      x = gxy_df$X, y = gxy_df$Y)
  }
  
  nudgex <- sign(gxy_df$X - 0.5) * gxy_df$X * 0.1
  nudgey <- sign(gxy_df$Y - 0.5) * gxy_df$Y * 0.1
  nudgex <- pmax(nudgex,-0.1)
  nudgey <- pmax(nudgey,-0.1)
  nudgex <- pmin(nudgex, 0.1)
  nudgey <- pmin(nudgey, 0.1)
  
  ggp <- ggp + ggrepel::geom_text_repel(
    mapping = aes(label = ID,segment.size = mark.line.width), 
    xlim = c(0.1, 0.9), ylim = c(0.1, 0.9),
    data = gxy_df, min.segment.length = 0.1,
    fontface = "bold", force = 3, segment.linetype = "2121", 
    max.overlaps = nrow(gxy_df) + 5, point.padding = 0, seed = 123, 
    max.iter = 20000, max.time = 30, nudge_x = nudgex, 
    nudge_y = nudgey, size = mark.size, colour = mark.color, 
    segment.colour = mark.color, box.padding = mark.padding)
  
  return(ggp)
}
.get_mark_idx <- function(marks, gxy_df){
  idx_df <- data.frame(name=marks, label=names(marks))
  idx_df$idx1 <- match(marks, rownames(gxy_df))
  idx_df$idx2 <- match(names(marks), rownames(gxy_df))
  idx_df$idx <- sapply(seq_len(nrow(idx_df)), function(i){
    idx <- idx_df[i, c(3,4)]
    idx[!is.na(idx)][1]
  })
  idx_df <- idx_df[,c(1,2,5)]
  return(idx_df)
}

#-------------------------------------------------------------------------------
.set_theme_zlim <- function(cl, zlim){
  # adjust labels for z-axis midle and tips
  bks_names <- cl$breaks
  bks_names <- format(bks_names,  trim = TRUE)
  n <- length(bks_names)
  bks_names[!seq_len(n) %in% c(1, ceiling(n/2), n)] <- ""
  # bks_names <- format(bks_names, justify=cl$justify)
  names(cl$breaks) <- bks_names
  # expand 'zlim' and palette tips
  expand <- TRUE
  if(expand){
    tips <- (zlim[2] - zlim[1]) * 0.1
    cl$zlim <- c(zlim[1] - tips, zlim[2] + tips)
    cl$pal <- c(cl$pal[1], cl$pal, cl$pal[length(cl$pal)])
  } else {
    cl$zlim <- zlim
  }
  return(cl)
}
#-------------------------------------------------------------------------------
#--- grid lines
.getGrid <- function(gxyz, ticks = c(0.2, 0.4, 0.6, 0.8), ndots = 100) {
  ticks <- ticks[ticks>0 & ticks <1]
  nc <- ncol(gxyz)
  nr <- nrow(gxyz)
  by <- ceiling(nc/ndots)
  ic <- ceiling(ticks * nc)
  ir <- ceiling(ticks * nr)
  grid1 <- grid2 <- array(0, dim = dim(gxyz))
  grid1[ir, seq(1, ncol(grid1), by = by)] <- 1L
  grid2[seq(1, nrow(grid2), by = by), ic] <- 1L
  grid1 <- (grid1 + grid2) > 0
  grid1[grid1] <- 1
  grid1[!is.na(gxyz)] <- 0
  return(grid1)
}

#-------------------------------------------------------------------------------
.trimcols <- function(colors, bg.color, zlim, pars) {
  if(pars$ps$configs$scale.type == "negpos") {
    
    if (is.na(bg.color)) {
      if (length(colors) %% 2 == 1){
        bg.color <- colors[(length(colors)+1)/2]
      } else {
        bg.color <- colors[(length(colors)/2)+1]
      } 
    } else {
      if (length(colors) %% 2 == 1){
        colors[(length(colors)+1)/2] <- bg.color
      } else {
        n <- length(colors)/2
        colors <- c(colors[seq_len(n)], bg.color, 
          colors[(n+1):(n*2)])
      } 
    }
    cols <- colorRampPalette(colors)(17)
    bg <- cols[9]
    
  } else if(pars$ps$configs$scale.type=="neg") {
    
    if (is.na(bg.color)) {
      bg.color <- colors[length(colors)]
      colors <- colors[-length(colors)]
    }
    cols <- colorRampPalette(c(colors,bg.color))(16)
    bg <- cols[length(cols)]
    cols <- cols[-length(cols)]
    
  } else {
    
    if (is.na(bg.color)) {
      bg.color <- colors[1]
      colors <- colors[-1]
    }
    cols <- colorRampPalette(c(bg.color, colors))(16)
    bg <- cols[1]
    cols <- cols[-1]
    
  }
  
  bkIn <- seq(zlim[1], zlim[2], length.out = length(cols))
  bkOut <- pretty(zlim, n = 11)
  pal <- .trimRamp(bkIn, bkOut, cols)
  
  return(list(breaks = bkOut, pal = pal, bg = bg))
}
.trimRamp <- function(bkIn, bkOut, cols) {
  cols <- t(col2rgb(cols)/255)
  bkOut <- ifelse(bkOut < bkIn[1], bkIn[1], bkOut)
  bkOut <- ifelse(bkOut > bkIn[length(bkIn)], bkIn[length(bkIn)], bkOut)
  bnout <- .bincode(bkOut, bkIn, right = TRUE, include.lowest = TRUE)
  rcol <- lapply(unique(bnout), function(i){
    j <- bnout == i
    .getcolor(bkOut[j], bkIn[i], bkIn[i+1], cols[i, ], cols[i+1, ])
  })
  rcol <- unlist(rcol)
  return(rcol)
}
.getcolor <- function (x, bk1, bk2, c1, c2) {
  c1 <- grDevices::convertColor(c1, "sRGB", "Lab")
  c2 <- grDevices::convertColor(c2, "sRGB", "Lab")
  rcol <- matrix(ncol = 3, nrow = length(x))
  for (i in seq_len(3)) {
    xx <- (x - bk2) * (c2[i] - c1[i]) / (bk2 - bk1) + c2[i]
    rcol[, i] <- xx
  }
  rcol <- grDevices::convertColor(rcol, "Lab", "sRGB")
  .xlim <- function(x) {
    x[x < 0] <- 0; x[x > 1] <- 1
    return(x)
  }
  rcol[, ] <- .xlim(as.numeric(rcol))
  .rgb2hex <- function(r, g, b){
    grDevices::rgb(r, g, b, maxColorValue = 1)
  }
  rcol <- .rgb2hex(rcol[, 1], rcol[, 2], rcol[, 3])
  return(rcol)
}
