ColorBar.R 12.5 KB
Newer Older
ColorBar <- function(brks = NULL, cols = NULL, vertical = TRUE, subsampleg = 1,
                     bar_limits = NULL, var_limits = NULL, triangle_ends = NULL,
                     color_fun = clim.colors, plot = TRUE, draw_ticks = TRUE, 
                     draw_separators = FALSE, extra_labels = NULL, units = NULL,
  # Required checks
  if (!is.null(brks)) {
    if (!is.numeric(brks)) {
      stop("Parameter 'brks' must be numeric if specified.")
    } else if (length(brks) > 1) {
      reorder <- sort(brks, index.return = TRUE)
      if (!is.null(cols)) {
        if (length(cols) > length(brks)) {
          cols <- cols[sort(c(brks[1] - ((length(cols) - length(brks)):1) * (brks[2] - brks[1]), brks), 
                            index.return = TRUE)$ix]
        } else {
          cols <- cols[reorder$ix[which(reorder$ix <= length(cols))]]
        }
      }
      brks <- reorder$x
    }
  }
  if ((is.null(brks) || length(brks) < 2) && is.null(var_limits)) {
    stop("At least one of 'brks' with the desired breaks or 'var_limits' must be provided to generate the colour bar.")
  }

  # Check var_limits
  if (is.null(var_limits)) {
    var_limits <- c(head(brks, 1), tail(brks, 1))
  } else if (!is.numeric(var_limits) || length(var_limits) != 2) {
    stop("Parameter 'var_limits' must be a numeric vector with two elements.")
  } else if (any(is.na(var_limits))) {
    stop("Parameter 'var_limits' must not contain NA values.")
  }

  # Check bar_limits
  if (is.null(bar_limits)) {
    bar_limits <- var_limits
  } else if ((!is.numeric(bar_limits) && !all(is.na(bar_limits))) || length(bar_limits) != 2) {
    stop("Parameter 'bar_limits' must be a numeric vector with two elements.")
  } else {
    bar_limits[which(is.na(bar_limits))] <- var_limits[which(is.na(bar_limits))]
  }

  # Check color_fun
  if (!is.function(color_fun)) {
    stop("Parameter 'color_fun' must be a colour-generator function.")
  }
    
  # Check triangle_ends
  if (is.null(triangle_ends)) {
    if (bar_limits[1] > var_limits[1] && bar_limits[2] < var_limits[2]) {
      triangle_ends <- 'both'
    } else if (bar_limits[1] <= var_limits[1] && bar_limits[2] >= var_limits[2]) {
      triangle_ends <- 'none'
    } else if (bar_limits[1] <= var_limits[1]) {
      triangle_ends <- 'max'
    } else {
      triangle_ends <- 'min'
    }
  } else {
    if (is.logical(triangle_ends)) {
      if (triangle_ends) {
        triangle_ends <- 'both'
      } else {
        triangle_ends <- 'none'
      }
    }
    if (!is.character(triangle_ends)) {
      stop("Parameter 'triangle_ends' must be a character string or a logical value.")
    } else if (!(triangle_ends %in% c('none', 'min', 'max', 'both'))) {
      stop("Parameter 'triangle_ends' must take the values 'none', FALSE, 'min', 'max', 'both' or TRUE.")
    }
  }

  # Check brks and cols
  if (!is.null(cols)) {
    if (!is.character(cols)) {
      stop("Parameter 'cols' must be a character vector.")
    } else if (any(!sapply(cols, .IsColor))) {
      stop("Parameter 'cols' must contain valid colour identifiers.")
    }
  }
  if (is.null(brks)) {
    if (!is.null(cols)) {
      brks <- length(cols)
      if (triangle_ends == 'both') {
        brks <- brks - 1
      } else if (triangle_ends == 'none') {
        brks <- brks + 1
      }
    } else {
      brks <- 21
    }
  }
  if (is.numeric(brks)) {
    if (length(brks) == 1) {
      brks <- seq(bar_limits[1], bar_limits[2], length.out = brks)
    }
    if (is.null(cols)) {
      if (triangle_ends == 'both') {
        cols <- color_fun(length(brks) + 1)
      } else if (triangle_ends == 'none') {
        cols <- color_fun(length(brks) - 1)
      } else {
        cols <- color_fun(length(brks))
      }
    } else {
      if ((triangle_ends == 'none') && (length(cols) != (length(brks) - 1))) {
        stop("Incorrect number of 'brks' and 'cols'. There must be one more break than the number of colours.")
      }
      if ((triangle_ends == 'both') && (length(cols) != (length(brks) + 1))) {
        stop("Incorrect number of 'brks' and 'cols'. 'var' contains values beyond both the minimum and maximum value in the colour bar. There must be one more colour than the number of breaks in order to colour the values beyond.")
      }
      if ((triangle_ends %in% c('min', 'max') && length(cols) != length(brks))) {
        stop("Incorrect number of 'brks' and 'cols'. 'var' contains values beyond either the minimum or the maximum value in the colour bar. There must be the same number of colours as the number of breaks in order to colour the values beyond.")
      }
    }
  } else {
    stop("Parameter 'brks' must be a numeric vector.")
  }

  # Check vertical
  if (!is.logical(vertical)) {
    stop("Parameter 'vertical' must be TRUE or FALSE.")
  }

  # Check extra_labels
  if (is.null(extra_labels)) {
    extra_labels <- numeric(0)
  }
  if (!is.numeric(extra_labels)) {
    stop("Parameter 'extra_labels' must be numeric.")
  } else {
    if (any(extra_labels > bar_limits[2]) || any(extra_labels < bar_limits[1])) {
      stop("Parameter 'extra_labels' must not contain ticks beyond the color bar limits.")
    }
  }
  extra_labels <- sort(extra_labels)

  # Check subsampleg
  primes <- function(x) {
    # Courtesy of Chase. See http://stackoverflow.com/questions/6424856/r-function-for-returning-all-factors
    x <- as.integer(x)
    div <- seq_len(abs(x))
    factors <- div[x %% div == 0L]
    factors <- list(neg = -factors, pos = factors)
    return(factors)
  }
  added_final_tick <- FALSE
  if (is.null(subsampleg)) {
    subsampleg <- 1
    while (length(brks) / subsampleg > 15 - 1) {
      next_factor <- primes((length(brks) - 1) / subsampleg)$pos
      next_factor <- next_factor[length(next_factor) - ifelse(length(next_factor) > 2, 1, 0)]
      subsampleg <- subsampleg * next_factor
    }
    if (subsampleg > (length(brks) - 1) / 4) {
      subsampleg <- max(1, round(length(brks) / 4))
      extra_labels <- c(extra_labels, signif(bar_limits[2], 4))
      added_final_tick <- TRUE
    }
  } else if (!is.numeric(subsampleg)) {
    stop("Parameter 'subsampleg' must be numeric.")
  }
  subsampleg <- round(subsampleg)

  # Check plot
  if (!is.logical(plot)) {
    stop("Parameter 'plot' must be logical.")
  }

  # Check draw_separators
  if (!is.logical(draw_separators)) {
    stop("Parameter 'draw_separators' must be logical.")
  }

  # Check draw_ticks
  if (!is.logical(draw_ticks)) {
    stop("Parameter 'draw_ticks' must be logical.")
  }

  # Check units
  if (is.null(units)) {
    units <- ''
  }
  if (!is.character(units)) {
    stop("Parameter 'units' must be a character string.")
  }

  # Check cex
  if (!is.numeric(cex)) {
    stop("Parameter 'cex' must be numeric.")
  }

  # Process the user graphical parameters that may be passed in the call
  ## Graphical parameters to exclude
  excludedArgs <- c("cex", "cex.axis", "col", "lab", "las", "mar", "mgp", "new", "ps")
  userArgs <- .FilterUserGraphicArgs(excludedArgs, ...)
  #
  #  Plotting colorbar
  # ~~~~~~~~~~~~~~~~~~~
  #
  if (plot) {
    # Load the user parameters
    par(userArgs)
    # Set up color bar plot region
    col <- cols
    col_inf <- NULL
    col_sup <- NULL
    dev_size <- dev.size()
    char_size <- par('cin')
    ch <- char_size[2]
#    margins <- c((0.3 + cex), 1, 1.8, 1)
margins <- c(0, 0, 0 ,0)
    bar_extra_margins <- c(0, 0, 0, 0)
    margins <- margins + bar_extra_margins
    margins <- margins * ch
    margins[1] <- margins[1] + dev_size[2] / 10 / 2
    margins[3] <- margins[3] + dev_size[2] / 10 / 1.5
    if (triangle_ends %in% c('min', 'both')) {
       col_inf <- col[1]
       col <- col[-1]
print(margins[2])
print(dev_size[2])
       margins[2] <- margins[2] + dev_size[1] / 8
    }
#      col[1] <- 'white'
#      # e is the distance of the left-most break (or bottom if vertical = TRUE) 
#      # from the left border of the colour bar, in plotting units
#      e <- e + 1
#    }
    if (triangle_ends %in% c('max', 'both')) {
      col_sup <- tail(col, 1)
      col <- col[-length(col)]
      margins[4] <- margins[4] + dev_size[1] / 8
    }
#      # f is the distance of the right-most break (or top if vertical = TRUE) from
#      # the right border of the colour bar, in plotting units
#      f <- f + 1
#    }
#    e <- 0.5
#    f <- -0.5
    ncols <- length(col)
    # Set up the points of triangles
    left_triangle <- list(x = c(1, 0, 1) - 0.5,
                          y = c(1.4, 1, 0.6))
    right_triangle <- list(x = c(ncols, ncols + 1, ncols) + 0.5,
                           y = c(1.4, 1, 0.6))
    # Draw the color squares and title
    if (vertical) {
      par(mai = c(margins[2:4], margins[1]), mgp = c(1, 1, 0), 
          las = 1, cex = 1.2)
      d <- 4  
      image(1, 1:ncols, t(1:ncols), axes = FALSE, col = col, xlab = '', ylab = '')
      title(ylab = units, line = 0.5, cex.lab = 0.9)
      # Draw top and bottom border lines    
      lines(c(0.6, 0.6), c(1 - 0.5, ncols + 0.5))
      lines(c(1.4, 1.4), c(1 - 0.5, ncols + 0.5))
      # Rotate triangles
      names(left_triangle) <- rev(names(left_triangle))
      names(right_triangle) <- rev(names(right_triangle))
    } else {
      par(mai = margins, mgp = c(1.5, max(c(0.3, 0.8 * (cex - 0.625))), 0),
          las = 1, cex = 1.2)
      d <- 1
      image(1:ncols, 1, t(t(1:ncols)), axes = FALSE, col = col, xlab = '', ylab = '')
      title(units, line = 0.5, cex.main = 0.9)
      # Draw top and bottom border lines    
      lines(c(1 - 0.5, ncols + 0.5), c(0.6, 0.6))
      lines(c(1 - 0.5, ncols + 0.5), c(1.4, 1.4))
    }
    # Draw the triangles
    par(xpd = TRUE)
    if (triangle_ends %in% c('min', 'both')) {
#      col[1] <- 'white'
#      # e is the distance of the left-most break (or bottom if vertical = TRUE) 
#      # from the left border of the colour bar, in plotting units
#      e <- e + 1
      # Draw left triangle
      polygon(left_triangle$x, left_triangle$y, col = col_inf, border = NA)
      lines(left_triangle$x, left_triangle$y)       
    }
    if (triangle_ends %in% c('max', 'both')) {
#      col[length(col)] <- 'white'
#      # f is the distance of the right-most break (or top if vertical = TRUE) from
#      # the right border of the colour bar, in plotting units
#      f <- f + 1
      # Draw right triangle
      polygon(right_triangle$x, right_triangle$y, col = col_sup, border = NA)
      lines(right_triangle$x, right_triangle$y)
    }
    par(xpd = FALSE)
#    # Draw the bar

    lines(c(1, 0), c(0.6, 0.6), xpd = TRUE)
#    # Draw the triangles
#    left_triangle <- list(x = c(1, 0, 1) + 0.5,
#                          y = c(1.4, 1, 0.6))
#    right_triangle <- list(x = c(ncols, ncols + 1, ncols) - 0.5,
#                           y = c(1.4, 1, 0.6))
#    if (vertical) {
#      # Bar left border line
#      lines(c(0.6, 0.6), c(e, ncols + f))
#      # Bar right border line
#      lines(c(1.4, 1.4), c(e, ncols + f))
#      names(left_triangle) <- rev(names(left_triangle))
#      names(right_triangle) <- rev(names(right_triangle))
#    } else {
#      # Bar bottom border line
#      lines(c(e, (ncols - f)), c(0.6, 0.6))
#      # Bar top border line
#      lines(c(e, (ncols - f)), c(1.4, 1.4))
#    }
#    if (triangle_ends %in% c('min', 'both')) {
#      # Draw left triangle
#      polygon(left_triangle$x, left_triangle$y, col = cols[1], border = NA)
#      lines(left_triangle$x, left_triangle$y)
#    } else {
#      lines(c(e, e), c(0.6, 0.4))
#    }
#    if (triangle_ends %in% c('max', 'both')) {
#      # Draw right triangle
#      polygon(right_triangle$x, right_triangle$y, col = cols[length(cols)], border = NA)
#      lines(right_triangle$x, right_triangle$y)
#    } else {
#      lines(length(cols) + f, )
#    }
#    # Put the separators
#    if (draw_separators && (ncols - f) >= e) {
#      for (i in e:(ncols - f) + 0.5) {
#        lines(c(i, i) - 0.5, c(0, 2))
#      }
#    }
#    # Put the ticks
#    plot_range <- length(brks) + f - e
#    var_range <- tail(brks, 1) - head(brks, 1)
#    extra_labels_at <- ((extra_labels - head(brks, 1)) / var_range) * plot_range + e
#    at <- seq(e, length(brks) + f, subsampleg)
#    labels <- signif(brks[seq(1, length(brks), subsampleg)], 4)
#    # Getting rid of next-to-last tick if too close to last one
#    if (added_final_tick) {
#      at <- at[-length(at)]
#      labels <- labels[-length(labels)]
#    }
#    at <- c(at, extra_labels_at)
#    labels <- c(labels, extra_labels)
#    tick_reorder <- sort(at, index.return = TRUE)
#    at <- tick_reorder$x
#    labels <- labels[tick_reorder$ix]
#    axis(d, at = at, tick = draw_ticks, labels = labels, cex.axis = cex)
  invisible(list(brks = brks, cols = cols, triangle_ends = triangle_ends))