R/util_color.R

Defines functions get_col_names get_pal_key parse_pal col_distinct col_distance col_asif_alpha_mix col_asif_alpha is_col_pal is_col is_hex_col col2hex rgb2hex get_alpha

Documented in get_col_names

## util_color.R  |  unikn
## spds | uni.kn | 2023 06 06
## ---------------------------

# Color-related utility functions: 
# Converting and evaluating colors and accessing and plotting color palettes. 


# (A) Color conversion and evaluation functions: -------

# - col2rgb(): (in grDevices) ------ 

## Check: 
# col2rgb("black", alpha = FALSE)  # Note: alpha is Boolean argument.
# col2rgb("black", alpha = TRUE)
# col2rgb("black")

# col2rgb("white", alpha = FALSE)
# col2rgb("white", alpha = TRUE)
# col2rgb("#FFFFFF")



# - get_alpha(): Get color transparency / alpha values ------

get_alpha <- function(pal){
  
  grDevices::col2rgb(pal, alpha = TRUE)["alpha", ] 
  
} # get_alpha().

## Check:
# get_alpha("red")
# get_alpha(shades_of(10, "black"))  # all same alpha
# get_alpha(ac(col = "black", alpha = seq(0, 1, by = 0.10)))  # 11 different alphas



# - rgb2hex(): Color conversion function ------ 

rgb2hex <- function(R, G, B) {
  
  rgb(R, G, B, maxColorValue = 255)
  
} # rgb2hex().

## Check:
# rgb2hex(255, 255, 255)
# rgb2hex(0, 0, 0)



# - col2hex(): Color conversion function ------ 

col2hex <- function(col, alpha = NA, use_alpha = FALSE) {
  
  if (use_alpha){
    
    if (is.na(alpha)){ # Only get alpha values of col:
      
      rgb(t(col2rgb(col)), alpha = get_alpha(col), maxColorValue = 255)
      
    } else { # Use the alpha value(s) provided:
      
      # ToDo: verify that alpha is numeric and in [0, 1]
      
      rgb(t(col2rgb(col)), alpha = alpha * 255, maxColorValue = 255)
      
    }
    
  } else { # default: 
    
    rgb(t(col2rgb(col)), maxColorValue = 255)
    
  }
  
} # col2hex().

## Check: 
# hex1 <- col2hex("black", alpha = 1/2, use_alpha = TRUE)
# hex2 <- col2hex("white", alpha = 2/3, use_alpha = TRUE)
# hex3 <- col2hex("gold", alpha = 1/3, use_alpha = TRUE)
# hex4 <- col2hex("steelblue", alpha = 0, use_alpha = FALSE)
# seecol(pal = c(hex1, hex2, hex3, hex4), n = "all")
# 
# # Note 2 limitations: 
# 1. As col2hex assumes a SCALAR alpha value,
#    the following does currently NOT work:
# col2hex("black", alpha = c(1/4, 1/2, 3/4))
# # ToDo: Vectorize col2hex arguments?
#
# # 2. By default, col2hex does not detect/use transparency differences: 
# tblack <- ac("black", alpha = seq(0, 1, by = 0.25))
# # seecol(tblack)
# col2hex(tblack)  # ignores transparency!
# # Use color transparency in col2hex:
# col2hex(tblack, use_alpha = TRUE) # uses alpha values without setting them
# # seecol(col2hex(tblack, use_alpha = TRUE))



# - is_hex_col(): Checking for HEX-colors ------ 

is_hex_col <- function(color) {
  
  return(grepl(pattern = "^#[0-9A-Fa-f]{6,}", color))
  
} # is_hex_col().

## Check:
# is_hex_col("black")
# is_hex_col(col2hex("black"))
# is_hex_col(rgb2hex(0, 0, 0))
# 
# # with color transparency:
# my_col <- rgb(1, 1, 1, alpha = 1/2, names = "50% black")
# is_hex_col(my_col)


# - is_col(): Verify color status (of an individual color) ------ 

# Assumes that input color is an individual character string. 

is_col <- function(color) {
  
  return( is_hex_col(color) | color %in% colors() )
  
} # is_col(). 

## Check:
# is_col("white")
# is_col(col2hex("black", alpha = 255/2))
# is_col(NA)
# is_col("bumblebee")

# BUT note: 
# is_col(col2rgb("white"))  # => FALSE FALSE FALSE



# - is_col_pal(): Verify an existing color palette ------ 

is_col_pal <- function(x) {
  
  # if ( !typeof(x) %in% c("vector", "list") ) {  # BUG: uni-pals are of type "character"!
  if ( !is.vector(x) & !is.list(x) ) { # most palettes are vectors or lists:
    
    is_color_ix <- FALSE
    
    # ToDo: usecol() returns an object for which attr(cols, which = "comment") == "custom"
    #       => is.vector() is FALSE for such objects!
    
  } else { # verify that all elements of x:
    
    # is_color_ix <- is_hex_col(color = x)  # Why only check for HEX colors?
    is_color_ix <- is_col(color = x)
    
  }
  
  return(all(is_color_ix))  # TRUE iff ALL elements are colors
  
} # is_col_pal(). 

## Check: 
# # TRUE for 
# is_col_pal(c("black", "grey", "white"))
# is_col_pal(pal_unikn)
# is_col_pal(uni_freiburg_br)
# is_col_pal(newpal(col = c("black", "white"), as_df = FALSE))
# is_col_pal(RColorBrewer::brewer.pal(n = 5, name = "Set2"))
# # but FALSE for palettes modified by usecol():
# is_col_pal(usecol(c("black", "white"), n = 3))
# is_col_pal(usecol(pal_unikn, n = 3))



# - col_asif_alpha(): Color corresponding to the hue of a transparent color ------ 

# Which non-transparent color values match the hue of a transparent color?
# Task: Get the non-transparent color corresponding to the hue of a transparent color (with 0 < alpha < 1)
# Source: https://stackoverflow.com/questions/12228548/finding-equivalent-color-with-opacity

# Solution 1: Convert into RGB and correct manually: 

col_asif_alpha <- function(col, alpha = NA, col_bg = "white"){
  
  # Assume: 
  # 1. col is a color
  # 2. 0 < alpha < 1
  # 3. col_bg is a color
  
  # Prepare: ----
  
  # Handle alpha value:
  
  # 1. Get alpha from col or alpha argument:
  
  # (ad b) from col:
  col_rgb <- grDevices::col2rgb(col, alpha = TRUE)
  col_rgb_alpha <- col_rgb["alpha", ]
  
  if (is.na(alpha) == FALSE){ # (a) from alpha argument:
    
    # print(paste0("User set alpha = ", alpha))  # 4debugging
    
    if (col_rgb_alpha < 255){
      print(paste0("Ignoring col_rgb_alpha = ", col_rgb_alpha))  # 4debugging 
    }
    
  } else if (col_rgb_alpha < 255){ # (b) from col:
    
    alpha <- round(col_rgb_alpha/255, 3)
    
    # print(paste0("Transparent RGB col alpha = ", alpha))  # 4debugging    
    
  } else { # (c) not yet set: use default 
    
    alpha <- 1  # default
    
    # print(paste0("Using default alpha = ", alpha))  # 4debugging    
    
  }
  
  # print(paste0("Uncorrected alpha = ", alpha))  # 4debugging
  
  # 2. Correct alpha value:
  correct_alpha <- TRUE  # Boolean: Correct alpha value? 
  
  if (correct_alpha){
    
    # Reduce alpha, to get a brighter/lighter result:
    
    if (alpha > .01 & alpha <= .05){
      
      alpha <- alpha - .01
      
    } else if (alpha > .05 & alpha <= .10){
      
      alpha <- alpha - .02
      
    } else if (alpha > .10 & alpha <= .15){
      
      alpha <- alpha - .03
      
    } else if (alpha > .15 & alpha <= .25){
      
      alpha <- alpha - .04
      
    } else if (alpha > .25 & alpha <= .35){
      
      alpha <- alpha - .05
      
    } else if (alpha > .35 & alpha <= .85){
      
      alpha <- alpha - .07
      
    } else if (alpha > .85 & alpha <= .95){
      
      alpha <- alpha - .03
      
    } 
    
    # print(paste0("Corrected alpha = ", alpha))  # 4debugging
    
  }
  
  # Main: ----
  
  if (alpha < 1){ # find color corresponding to alpha:
    
    col_rgb_rgb <- col_rgb[1:3, ]
    col_bg_rgb <- grDevices::col2rgb(col_bg, alpha = FALSE)[1:3, ]
    
    # (1) Know: RGBA1 (transparent front) over RGB2 (back) 
    #     Want: RBG3 (effective visual result)
    r1 <- col_rgb_rgb 
    a1 <- alpha
    r2 <- col_bg_rgb
    
    r3 = r2 + (r1 - r2) * a1
    
    col_out <- r3
    
    # # (2) Know: RGB3 (the final desired color), RGB2 (the background color), and A1 (how much opacity you want), 
    # #     Want: RGB1
    # r3 <- col_rgb_rgb 
    # a1 <- alpha
    # r2 <- col_bg_rgb
    # 
    # r1 = (r3 - r2 + r2 * a1) / a1
    # 
    # col_out <- r1
    
  } else {
    
    col_out <- col_rgb[1:3, ]
    
  }
  
  # Correct extreme values:
  col_out[col_out < 0]   <- 0
  col_out[col_out > 255] <- 255
  
  # print(t(col_out))  # 4debugging
  
  # Output: ---- 
  
  rgb(t(col_out), maxColorValue = 255)
  
} # col_asif_alpha(). 

# Check:

# # 1. baseline:
# col_asif_alpha("black")
# col_asif_alpha("white")
# seecol(c("steelblue", col_asif_alpha("steelblue")))

# # 2. non-transparent color input and explicit alpha value:
# col <- "blue4"
# af <- 0.35
# seecol(c(adjustcolor(col, alpha.f = af), col_asif_alpha(col, alpha = af)))

# # 3. transparent color input:
# cols_t <- ac("darkblue", alpha = seq(1, 0, by = -.10))
# col_1 <- cols_t[9]
# col_2 <- col_asif_alpha(col_1, alpha = NA)
# seecol(c(col_1, col_2))


# - col_asif_alpha_mix(): Color corresponding to the hue of a transparent color ------ 

# Solution 2: Mix a gradient with col_bg = "white" and then select by alpha value: 

col_asif_alpha_mix <- function(col, alpha = 1, col_bg = "white"){
  
  n_bins <- 101  # granularity of gradient
  alpha_bin <- n_bins - round(alpha * (n_bins - 1), 0)
  # print(alpha_bin)  # 4debugging 
  
  # Create gradient and select bin:
  usecol(c(col, col_bg), n = n_bins)[alpha_bin]
  
} # col_asif_alpha_mix(). 

# # Check:
# # 1. baseline:
# col_asif_alpha_mix("black")
# col_asif_alpha_mix("white")
# seecol(c("steelblue", col_asif_alpha_mix("steelblue")))
# 
# # 2. non-transparent color input and explicit alpha value:
# col <- "blue4"
# af <- 0.50
# seecol(c(adjustcolor(col, alpha.f = af), col_asif_alpha_mix(col, alpha = af)))
# # Problem: Simulated alpha color appears darker than actual transparent color.
# 
# # +++ here now +++
# 
# # + More:
# col_asif_alpha_mix("black", alpha = 0, col_bg = "#AABBCCFF")  # should yield col_bg (non-transparent)



# - v_col_asif_alpha(): Vectorized version of col_asif_alpha(): ----

v_col_asif_alpha <- Vectorize(col_asif_alpha, vectorize.args = c("col", "alpha"))

# # Check: 
# # (a) individual colors:
# seecol(v_col_asif_alpha(col = "black", alpha = c(.25, .75), col_bg = "white"))       # works
# seecol(v_col_asif_alpha(col = "black", alpha = c(.25, .50, .75), col_bg = "white"))  # fails!
# 
# # (b) Color palettes:
# alf <- .25
# c_1 <- usecol(uni_freiburg_info, alpha = alf)  # transparent
# c_2 <- v_col_asif_alpha(uni_freiburg_info, alpha = c(alf))  # non-transparent
# 
# c_1 <- usecol(pal_unikn_pref, alpha = alf, use_names = TRUE)  # transparent
# c_2 <- v_col_asif_alpha(pal_unikn_pref, alpha = c(alf))  # non-transparent
# 
# my_pal <- rep(NA, 2 * length(c_1))
# 
# for (i in 1:length(c_1)){ # re-sort pairs:
#   my_pal[i * 2 - 1] <- c_1[i]
#   my_pal[i * 2 + 0] <- c_2[i]  
# }
# 
# # Compare:
# seecol(my_pal)


# - col_distance(): Color distance (in RGB space) ------

col_distance <- function(col_1, col_2){
  
  # Vectorize (if needed):
  len_1 <- length(col_1)
  len_2 <- length(col_2)
  
  if (len_1 > len_2){ # extend col_2:
    fct <- ceiling(len_1/len_2)
    col_2 <- rep(col_2, fct)[1:len_1]
  }
  
  if (len_2 > len_1){ # extend col_1:
    fct <- ceiling(len_2/len_1)
    col_1 <- rep(col_1, fct)[1:len_2]
  }
  
  # Convert to RGB:
  rgb_1 <- grDevices::col2rgb(col_1)
  rgb_2 <- grDevices::col2rgb(col_2)
  
  # Output (as matrix): 
  abs(rgb_1 - rgb_2)
  
} # col_distance().

## Check: 
# # (a) individual colors: 
# col_distance("red", "red")
# col_distance("black", "white")
# 
# # (b) Color palette: 
# # col2rgb(palette())
# pal <- palette()
# names(pal) <- palette()
# col_distance("black", pal)  # No names vs.
# col_distance(pal, "black")  # Note names



# - col_distinct(): A unique() function for color values (using HEX codes) ------

# Goal: Remove visual duplicate colors (using HEX values to judge the identiy of colors, 
#       rather than color names). 
#
# Notes: 
# - The function aims to detect visual duplicates (i.e., colors that look the same, 
#   irrespective of their names/values).
# - Color transparency is only considered when use_alpha = TRUE.
# - grDevices::colors() has a 'distinct = TRUE' argument to remove visual duplicates.
# - By default, visually distinct colors (i.e., with different color values) but same NAME are retained, 
#   but setting use_names = TRUE removes colors with duplicate names (even if their color values differ).

col_distinct <- function(pal, use_hex = TRUE, use_alpha = FALSE, use_names = FALSE){
  
  # Prepare: ------ 
  
  if (any(is_col(pal) == FALSE)){
    stop("pal contains non-colors")
  }
  
  # Initialize:
  hex_dupes <- FALSE   # default: all different HEX values
  alpha_dupes <- TRUE  # default: all same alpha values
  name_dupes <- FALSE  # default: no names/all different names
  
  out_pal <- NA
  
  
  # Main: ------
  
  # 1. Color HEX values: ---- 
  
  if (use_hex){
    
    pal_hex <- col2hex(pal)  # ignores transparency!
    hex_dupes <- duplicated(pal_hex)  # logical ix of duplicated hex values
    
  } 
  
  
  # 2. alpha values: ----
  
  if (use_alpha){
    
    # Get transparency values:
    pal_alpha <- get_alpha(pal)
    
    alpha_dupes <- duplicated(pal_alpha)  # logical ix of duplicate alpha values
    
  } 
  
  # Apply indices 1+2: ----
  # print(hex_dupes)   # 4debugging
  # print(alpha_dupes) # 4debugging
  
  cur_pal <- pal[(hex_dupes & alpha_dupes) == FALSE]
  
  
  # 3. Color names (for cur_pal only): ---- 
  
  if ((is.null(names(cur_pal)) == FALSE) & use_names){
    
    pal_names <- names(cur_pal)
    name_dupes <- duplicated(pal_names)
    
  }
  
  # Apply index 3: ---- 
  # print(name_dupes) # 4debugging
  
  if (use_names){
    out_pal <- cur_pal[name_dupes == FALSE]
  } else {
    out_pal <- cur_pal
  }
  
  
  # Output: ------
  
  return(out_pal)
  
} # col_distinct().


# ## Check:
# (p1 <- c("gray", "grey", "black", "grey0", rgb(0, 0, 0), "red", "red1", rgb(1, 0, 0), "red2", "red3"))
# seecol(p1)
# col_distinct(p1)
# col_distinct(p1, use_hex = FALSE, use_alpha = TRUE, use_names = TRUE)
# 
# # with repeated color values (in custom pals):
# (p2 <- usecol(c("black", pal_unikn, pal_seeblau, pal_grau, "white")))
# col_distinct(p2)
# seecol(col_distinct(p2))
# 
# # use_names:
# (p3 <- newpal(col = c("black", "gray", "grey", "white"), names = c("extreme", "med", "med", "extreme")))
# col_distinct(p3)  # removes duplicate color values, but keeps colors with duplicate names
# col_distinct(p3, use_names = TRUE)  # removes duplicate names (despite distinct colors)
# col_distinct(p3, use_hex = FALSE, use_names = TRUE)
# 
# # use_alpha: By default, color transparency is being ignored:
# tblack <- ac("black", alpha = seq(0, 1, by = 0.25))
# seecol(tblack)        # Shows 5 shades, but
# col_distinct(tblack)  # all share same basic color!
# col_distinct(tblack, use_alpha = TRUE)  # USES color transparency (to keep visual different shades of same color)
# col_distinct(tblack, use_hex = FALSE, use_names = TRUE)
# 
# # Invalid inputs:
# col_distinct(c("black", "NO color", "white"))




# (B) Color and color palette retrieval functions: ------

# - parse_pal(): Parse color palette input ------ 

parse_pal <- function(pal) {
  
  parenv <- parent.frame()  # get the calling environment. 
  
  # Check if pal is legible (already a color palette): 
  vector_input <- tryCatch(
    {
      all(sapply(pal, is_col))
    },
    
    error = function(e) {
      
      return(FALSE)  # return FALSE if not all are colors. 
      
    },
    silent = TRUE
  )
  
  
  if ( vector_input ) {  # if the input is a color vector (or list).
    
    out <- pal
    
  } else { # otherwise:
    
    # Deparse argument: 
    if ( identical(parenv , globalenv()) ) {  # if the calling environment is the global env:
      
      tmp <- noquote(deparse(substitute(pal)))  # get the palette. 
      
    } else {  # if the calling environment is another function:
      
      tmp <- noquote(deparse(substitute(expr = pal, 
                                        env = parent.frame())))  # get input from function.
      
      tmp <- noquote(tmp)  # unquote input. 
      
    }
    
    # Split input string; getting everything within the parentheses:
    
    if ( grepl("\\(", tmp) ) {  # if a parenthesis exists: 
      
      tmp <- sub(".*?\\(+(.*)\\).*", "\\1", tmp, perl = TRUE)
      # .\*?   matches anything but stops at the first match of what follows
      # \\s+   matches one or more blank spaces
      # (.\*)  matches any number of characters, because it is in parentheses
      # it becomes a capture group and is stored in the variable \1
      # \\s    waits for another blank, this time, the last one
      # .*     matches anything after the last blank
      
    }
    
    elem <- gsub(" |\"", "", unlist(strsplit(tmp, split = ",")))  
    # Split get elements of the input at ',' and remove whitespace and quotes.
    
    # Check if any element is warpped in one or more functions: 
    parens <- grepl("\\(", elem)   # are there any parentheses left?
    funs <- rep(NA, length(elem))  # initialize vector. 
    funs[parens] <- gsub(" *\\(.*", "", elem[parens])  # get any functions.
    
    # Remove functions: 
    elem <- sub(".*?\\(+(.*)\\).*", "\\1", elem, perl = TRUE)
    
    
    # Existence checks: ----- 
    
    ## Check existence of every element:
    elemex <- sapply(elem, function(x) exists(x) & x != "pal")
    # also ask, whether the element is named pal, to prevent name conflicts!
    # Was: elemex <- sapply(elem, exists)
    
    if ( any(!elemex) ) { # only if not all inputs have been resolved
      
      # Those which are still unknown: Are those colors? 
      elemex[!elemex] <- sapply(elem[!elemex], is_col)
      
    }
    
    # Prefix those which do not exist with "pal_": 
    if ( any(!elemex) ) {  # only if not all inputs have been resolved
      
      elem[!elemex] <- paste0("pal_", elem[!elemex])
      elemex[!elemex] <- sapply(elem[!elemex], exists)
      
    }
    
    # Handle undefined palettes: ---- 
    
    if (!all(elemex)) {
      
      nex <- gsub("pal_", "", elem[!elemex])  # remove any "pal_" string parts. 
      
      if ( length(nex) > 1) {
        
        errmsg <- paste0("Inputs ", paste0("\"", nex, "\"", collapse = ", "), " do not exist")
        
      } else {
        
        errmsg <- paste0("Input \"", nex, "\" does not exist")
        
      }
      
      stop(errmsg)
      
    }
    
    
    # Get all palettes: ---- 
    out <- lapply(elem, function(x) if (is_col(x)) {x} else {get(x)} )
    
    
    # Apply any previously detected functions: ----  
    if ( any(!is.na(funs)) ) {
      
      out[!is.na(funs)] <- apply(rbind(out, funs), MARGIN = 2, FUN = function(x) {
        if(!is.na(x$funs)) eval(call(x$funs, x$out)) # apply function to all non-NA elements. 
      })[!is.na(funs)] 
      
    }
    
    # Create the output: ---- 
    out <- unname(out)  # finish the palette by removing upper level (palette) names.
    
  }
  
  # Output: ---- 
  
  out <- unlist(out)
  
  # Provide missing names, by using the color:
  ix_nameless <- is.null(names(out)) | names(out) == ""
  names(out)[ix_nameless] <- out[ix_nameless]
  
  return(out)
  
} # parse_pal().



# - get_pal_key(): Get a color palette or list of palettes by keyword -------

get_pal_key <- function(pal = "all", n = "all", alpha = NA) {
  
  # Process the 'pal' argument: ----- 
  
  # Getting palettes by keyword: ----  
  
  keys <- c(#
    # (a) all palettes (of the unikn package):
    "all",                            # 1. all palettes (of the unikn package)
    # (b) local/uni.kn palettes:
    "unikn_all", "all_unikn",         # 2:3. all local/uni.kn palettes
    "basic", "unikn_basic", "basic_unikn", # 4:6. basic palettes 
    "pair", "pair_all", "all_pair",   # 7:9. paired palettes 
    "pref", "pref_all", "all_pref",   # 10:12. preferred palettes and gradients 
    "grad", "grad_all", "all_grad",   # 13:15. gradients
    # (c) added/contributed palettes: 
    "add", "uni"                      # 16:17. additional/contributed palettes
  )
  
  # Throw an error, if no valid keyword is specified:
  if ( !(pal %in% keys) ) {
    stop('Invalid keyword specified. Allowed keywords are 
         c("all", "unikn_all", "all_unikn", "pref_all", "all_pref", "grad_all", "all_grad", "add")')
  } else {
    
    # (a) all palettes (of the unikn package):
    if ( pal %in% keys[1] )     key <- "all"
    # (b) local/uni.kn palettes:
    if ( pal %in% keys[2:3] )   key <- "all_kn"    
    if ( pal %in% keys[4:6] )   key <- "basic"
    if ( pal %in% keys[7:9] )   key <- "pair"
    if ( pal %in% keys[10:12] ) key <- "pref"
    if ( pal %in% keys[13:15] ) key <- "grad"
    # (c) added/contributed palettes: 
    if ( pal %in% keys[16:17] ) key <- "add" 
    
  }
  
  # Get all color palettes with the prefix "pal_" from the environment: ----- 
  
  # Distinguish 7 cases:
  pal_names <- switch(
    key,
    # (a) all palettes (of the unikn package):
    all = all_pals,
    # (b) local/uni.kn palettes:
    all_kn = all_palkn,
    basic = all_palkn_basic,
    pair = all_palkn_pair,
    pref = all_palkn_pref,
    grad = all_palkn_grad,
    # (c) added/contributed palettes:
    add = add_pals
  )
  
  # Get list of palettes specified by keyword:
  lst_pal <- sapply(pal_names, get)
  # print(lst_pal)  # 4debugging
  
  # Check which lst_pal elements are pre-existing color palettes:
  is_pal <- lapply(X = lst_pal,
                   FUN = is_col_pal)  # using utility function (see above)
  # Note: Palettes modified by usecol() are NOT pre-existing color palettes.
  
  # print(is_pal)  # 4debugging
  
  # Remove non-colors:
  col_pals <- lst_pal[unlist(is_pal)]
  # print(col_pals) # 4debugging
  
  # Check if palette is non-empty:
  if (length(col_pals) == 0) {
    stop("No color palettes found in the current environment.")
  }
  
  # If only color subsets should be displayed:
  if (n != "all" ) {
    
    # Get the subset of each palette , as defined in usecol():
    out <- lapply(col_pals, FUN = usecol, n = n, alpha = alpha, use_names = TRUE)
    
  } else {
    
    if ( !is.na(alpha) ) {
      
      out <- lapply(col_pals, FUN = adjustcolor, alpha.f = alpha)  # adjust alpha
      
    } else { # if n is un-specified: 
      
      out <- col_pals  # return list as is
      
    }
    
  }
  
  pal_nm <- names(out)  # get palette names from listnames # ToDo: Used/needed anywhere???
  
  return(out)
  
} # get_pal_key(). 



# - get_col_names(): Get custom and default color names ------

#' Get color names
#'
#' \code{get_col_names} gets color names from color palettes.
#' 
#' @param col A vector of colors.
#' @param custom_pals A vector of color palettes.
#' 
#' @return A vector of color names.
#' 
#' @family utility functions

get_col_names <- function(col, custom_pals = all_pals){
  
  # 0. Prepare:
  require(unikn, quietly = TRUE)  # ensure that unikn is attached (when calling from other pkgs)
  
  # 1. Customized names from custom color palettes:
  cus_pals  <- lapply(X = custom_pals, FUN = get)
  cus_names <- names(unlist(cus_pals))[match(col, unlist(cus_pals))]
  
  # 2. Default names: Predefined color names (in grDevices):
  def_names <- grDevices::colors()[match(
    grDevices::rgb(t(grDevices::col2rgb(col)), maxColorValue = 255), 
    c(grDevices::rgb(t(grDevices::col2rgb(grDevices::colors())), maxColorValue = 255))
  )]
  
  # Replace NA values by "": 
  def_names[is.na(def_names)] <- ""
  cus_names[is.na(cus_names)] <- ""
  
  # Combine both name vectors (to avoid duplicates): 
  def_names[def_names == cus_names] <- ""  # remove duplicates 
  def_names[!def_names == "" & !cus_names == ""] <- 
    paste0("/", def_names[!def_names == "" & !cus_names == ""]) # distinguish different names for the same color
  
  col_names <- paste0(cus_names, def_names)
  
  return(col_names)
  
} # get_col_names(). 

# ## Check:
# get_col_names(c("black", "white"))  # colors()
# get_col_names(pal_unikn_pref)  # unikn color palettes
# get_col_names(pal_unikn)  # colors with multiple names (prioritizing custom names)
# get_col_names(c("black", Grau, "white", Seeblau))  # combinations
# get_col_names(usecol(c("black", "white"), n = 5))  # derivations (AFTER evaluating usecol())


## ToDo: ------

# - In `get_pal_key()`: Define FUN in `lapply()`` as a separate function `is_col_pal()`.

# - Consider exporting utility functions `get_alpha()`, `col_distance()` and `col_distinct()`.

# - `col2hex()`: Improve by vectorizing arguments and considering transparency/alpha values (see 2 ToDos above). 
# - `plot_col()`: Add option for scaling multiple color palettes to fixed width rectangles. 
# - Add functions for translating to/from HCL values (see `HCL_color_exploration.Rmd`). 

## eof. ----------

Try the unikn package in your browser

Any scripts or data that you put into this service are public.

unikn documentation built on Aug. 10, 2023, 5:08 p.m.