Diff of /R/auxiliary.R [000000] .. [413088]

Switch to unified view

a b/R/auxiliary.R
1
####
2
# Matrix Operations ####
3
####
4
5
getMin <- function(data, ...){
6
  if(inherits(data, "IterableMatrix")){
7
    data <- as(data, "dgCMatrix")
8
  } 
9
  return(min(data, ...))
10
}
11
12
getMax <- function(data, ...){
13
  if(inherits(data, "IterableMatrix")){
14
    data <- as(data, "dgCMatrix")
15
  } 
16
  return(max(data, ...))
17
}
18
19
getRange <- function(data, ...){
20
  return(c(getMin(data, ...), getMax(data, ...)))
21
}
22
23
getColQuantiles <- function(data, desiredQuantile){
24
  if(inherits(data, "IterableMatrix")){
25
    return(BPCells::colQuantiles(data, probs = desiredQuantile))
26
  } else {
27
    return(apply(data, 2, function(x) stats::quantile(x, desiredQuantile)))
28
  }
29
}
30
31
32
####
33
# Nanostring Auxiliary tools ####
34
####
35
36
generate_pkc_lookup <- function (jsons_vec)
37
{
38
  lookup_df <- data.frame(RTS_ID = character(), Target = character(),
39
                          Module = character(), CodeClass = character(), ProbeID = character(),
40
                          GeneID = character(), SystematicName = character(), stringsAsFactors = FALSE)
41
  for (curr_idx in seq_len(length(jsons_vec))) {
42
    curr_module <- names(jsons_vec)[curr_idx]
43
    curr_json <- jsons_vec[[curr_idx]]
44
    for (targ in curr_json[["Targets"]]) {
45
      curr_targ <- targ[["DisplayName"]]
46
      curr_code_class <- gsub("\\d+$", "", targ[["CodeClass"]])
47
      for (prb in targ[["Probes"]]) {
48
        if (curr_json[["AnalyteType"]] == "Protein") {
49
          curr_RTS_ID <- targ$RTS_ID
50
        }
51
        else {
52
          curr_RTS_ID <- prb$RTS_ID
53
        }
54
        curr_probe_ID <- prb$ProbeID
55
        curr_gene_ID <- paste(prb$GeneID, collapse = ", ")
56
        if (length(prb$GeneID) < 1) {
57
          curr_gene_ID <- NA
58
        }
59
        curr_syst_name <- paste(prb$SystematicName, collapse = ", ")
60
        lookup_df[nrow(lookup_df) + 1, ] <- list(curr_RTS_ID,
61
                                                 curr_targ, curr_module, curr_code_class, curr_probe_ID,
62
                                                 curr_gene_ID, curr_syst_name)
63
      }
64
    }
65
  }
66
  return(lookup_df)
67
}
68
69
.dccMetadata <-
70
  list(schema =
71
         list("Header" =
72
                data.frame(labelDescription =
73
                             c("The version of the file",
74
                               "The version of the software used to create the file",
75
                               "The date of the sample"),
76
                           minVersion = numeric_version(c("0.01", "0.01", "0.01")),
77
                           row.names =
78
                             c("FileVersion", "SoftwareVersion", "Date"),
79
                           stringsAsFactors = FALSE),
80
              "Scan_Attributes" =
81
                data.frame(labelDescription =
82
                             c("The sample ID",
83
                               "The plate ID",
84
                               "The well ID"),
85
                           row.names =
86
                             c("ID", "Plate_ID", "Well"),
87
                           minVersion = numeric_version(c(rep("0.01", 3L))),
88
                           stringsAsFactors = FALSE),
89
              "NGS_Processing_Attributes" =
90
                data.frame(labelDescription =
91
                             c(NA_character_,
92
                               NA_integer_,
93
                               NA_integer_,
94
                               NA_integer_,
95
                               NA_integer_,
96
                               NA_real_,
97
                               NA_real_),
98
                           minVersion = numeric_version(c(rep("0.01", 7L))),
99
                           row.names =
100
                             c("SeqSetId", "Raw", "Trimmed",
101
                               "Stitched", "Aligned", "umiQ30", "rtsQ30"),
102
                           stringsAsFactors = FALSE),
103
              "Code_Summary" =
104
                data.frame(labelDescription =
105
                             c(NA_character_, NA_integer_),
106
                           minVersion = numeric_version(c(rep("0.01", 2L))),
107
                           row.names = c("RTS_ID", "Count"),
108
                           stringsAsFactors = FALSE)
109
         )
110
  )
111
112
113
.dccMetadata[["protocolData"]] <-
114
  do.call(rbind,
115
          unname(head(.dccMetadata[["schema"]], 3L)))[, "labelDescription",
116
                                                      drop = FALSE]
117
118
rownames(.dccMetadata[["protocolData"]])[rownames(.dccMetadata[["protocolData"]]) == "ID"] <- "SampleID"
119
120
121
.codeClassMetadata <-
122
  c("CodeClass,IsControl,Analyte",
123
    "Endogenous,FALSE,gx|cnv|fusion",
124
    "Housekeeping,TRUE,gx|fusion",
125
    "Positive,TRUE,general",
126
    "Negative,TRUE,general",
127
    "Binding,TRUE,general",
128
    "Purification,TRUE,general",
129
    "Reserved,TRUE,general",
130
    "SNV_INPUT_CTL,TRUE,SNV",
131
    "SNV_NEG,TRUE,SNV",
132
    "SNV_POS,TRUE,SNV",
133
    "SNV_UDG_CTL,TRUE,SNV",
134
    "SNV_PCR_CTL,TRUE,SNV",
135
    "SNV_REF,FALSE,SNV",
136
    "SNV_VAR,FALSE,SNV",
137
    "PROTEIN,FALSE,protein",
138
    "PROTEIN_NEG,TRUE,protein",
139
    "PROTEIN_CELL_NORM,TRUE,protein",
140
    "Restriction Site,TRUE,CNV",
141
    "Invariant,TRUE,CNV")
142
.codeClassMetadata <-
143
  utils::read.csv(textConnection(paste0(.codeClassMetadata, collapse = "\n")),
144
                  colClasses = c("character", "logical", "character"),
145
                  stringsAsFactors = FALSE)
146
147
148
.validDccSchema <-
149
  function(x, fileVersion,
150
           section = c("Header", "Scan_Attributes", "NGS_Processing_Attributes", "Code_Summary"))
151
  {
152
    section <- match.arg(section)
153
    schema <- .dccMetadata[["schema"]][[section]]
154
    expectedNames <- row.names(schema)[schema[,"minVersion"] <= fileVersion]
155
    if (all(expectedNames %in% colnames(x))) {
156
      TRUE
157
    } else {
158
      sprintf("<%s> section must contain %s", section,
159
              paste0("\"", expectedNames, "\"", collapse = ", "))
160
    }
161
  }
162
163
164
.allNA <- function(x) {
165
  all(is.na(x))
166
}
167
168
.allTRUE <- function(x) {
169
  is.logical(x) && !anyNA(x) && all(x)
170
}
171
172
.allFALSE <- function(x) {
173
  is.logical(x) && !anyNA(x) && !any(x)
174
}
175
176
.allZero <- function(x) {
177
  is.numeric(x) && !anyNA(x) && identical(range(x), c(0, 0))
178
}
179
180
.validNonNegativeInteger <- function(x) {
181
  is.integer(x) && !anyNA(x) && min(x) >= 0L
182
}
183
184
.validNonNegativeNumber <- function(x) {
185
  is.numeric(x) && !anyNA(x) && min(x) >= 0
186
}
187
188
.validPositiveNumber <- function(x) {
189
  is.numeric(x) && !anyNA(x) && min(x) > 0
190
}
191
192
193
####
194
# Basilisk Environment ####
195
####
196
197
#' get the Python Basilisk environment
198
#'
199
#' Defines a conda environment via Basilisk, which is used to convert R objects to Zarr stores.
200
#'
201
#' @export
202
getBasilisk <- function(){
203
 
204
  if(!requireNamespace('basilisk'))
205
    stop("Please install basilisk package!: BiocManager::install('basilisk')")
206
  
207
  basilisk.packages=c(
208
    "numpy==1.*",
209
    "pandas==1.*",
210
    "anndata==0.8.*",
211
    "h5py==3.*",
212
    "hdf5==1.*",
213
    "natsort==7.*",
214
    "packaging==20.*",
215
    "scipy==1.*",
216
    "sqlite==3.*",
217
    "zarr==2.*",
218
    "numcodecs==0.*",
219
    "tifffile==2024.2.12"
220
  )
221
  basilisk.pip=c(
222
    "ome-zarr==0.2.1"
223
  )
224
  
225
  py_env <- basilisk::BasiliskEnvironment(
226
    envname="VoltRon_basilisk_env",
227
    pkgname="VoltRon",
228
    packages=basilisk.packages,
229
    pip=basilisk.pip
230
  )
231
  
232
  py_env
233
}
234
235
####
236
# Other Auxiliary tools ####
237
####
238
239
fill.na <- function(x, i = 5) {
240
  if (is.na(x)[i]) {
241
    return(round(mean(x, na.rm = TRUE), 0))
242
  }
243
  else {
244
    return(round(x[i], 0))
245
  }
246
}
247
248
#' slotApply
249
#'
250
#' apply to slots
251
#'
252
#' @param x object
253
#' @param FUN function
254
#' @param ... arguments passed to \code{FUN}
255
#'
256
#' @importFrom methods slot slotNames
257
#'
258
slotApply <- function(x,FUN,...){
259
  cl <- class(x)
260
  result <- list()
261
  for(i in methods::slotNames(cl)){
262
    result[[i]] <- FUN(methods::slot(x,i),...)
263
  }
264
  result
265
}
266
267
#' slotToList
268
#'
269
#' slot to list
270
#'
271
#' @param x object
272
#'
273
#' @importFrom methods slot slotNames
274
#'
275
slotToList <- function(x){
276
  returnlist <- list()
277
  namesslot <- methods::slotNames(x)
278
  for(cur_slot in namesslot)
279
    returnlist[[cur_slot]] <- methods::slot(x, name = cur_slot)
280
  returnlist
281
}
282
283
ggname <- function(prefix, grob) {
284
  grob$name <- grid::grobName(grob, prefix)
285
  grob
286
}
287
288
quiet <- function(x) {
289
  sink(tempfile())
290
  on.exit(sink())
291
  invisible(force(x))
292
}
293
294
stopQuietly <- function(...) {
295
  blankMsg <- sprintf("\r%s\r", paste(rep(" ", getOption("width")-1L), collapse=" "));
296
  stop(simpleError(blankMsg));
297
}
298
299
#' make_css
300
#'
301
#' make_css from \code{tableHTML} package
302
#'
303
#' @param ... css style definitions. Each object you provide must be a list of three elements. The first element will be a vector of the selectors to be styled (e.g. table, th, an id or html class). If the first element is a vector of length greater than one then the selectors will be comma separated in the css. The second element will be a vector of the css definitions and the third element will a vector of the values of those definitions.
304
#' @param file Character sting. If a file name is provided then the css code will be printed into that file. If the argument is NULL (default) then a string will be returned.
305
#'
306
#' @importFrom shiny HTML
307
#'
308
#' @noRd
309
make_css <- function (..., file = NULL)
310
{
311
  css_defs <- list(...)
312
  for (x in css_defs) {
313
    if ((!is.list(x)) | (length(x) != 3L)) {
314
      stop("Each element in ... needs to be a list of three elements")
315
    }
316
    if (length(x[[2]]) != length(x[[3]])) {
317
      stop("The second and third elements of each list need to have the same length")
318
    }
319
  }
320
  all_css <- vapply(css_defs, function(x) {
321
    css_comp <- paste0(x[[2]], ": ", x[[3]], ";")
322
    style <- paste(css_comp, collapse = "\n  ")
323
    to_be_styled <- paste(x[[1]], collapse = ",\n")
324
    paste0(to_be_styled, " {\n  ", style, "\n}\n")
325
  }, FUN.VALUE = character(1))
326
  css_string <- shiny::HTML(paste(all_css, collapse = "\n"))
327
  if (is.null(file)) {
328
    css_string
329
  }
330
  else {
331
    message(css_string, file = file)
332
    invisible(NULL)
333
  }
334
}
335
336
#' Fast creation of dummy variables
337
#'
338
#' Quickly create dummy (binary) columns from character and
339
#' factor type columns in the inputted data (and numeric columns if specified.)
340
#' This function is useful for statistical analysis when you want binary
341
#' columns rather than character columns. Adapted from the \code{fastDummies} package (https://jacobkap.github.io/fastDummies/)
342
#'
343
#' @param .data
344
#' An object with the data set you want to make dummy columns from.
345
#' @param select_columns
346
#' Vector of column names that you want to create dummy variables from.
347
#' If NULL (default), uses all character and factor columns.
348
#' @param remove_first_dummy
349
#' Removes the first dummy of every variable such that only n-1 dummies remain.
350
#' This avoids multicollinearity issues in models.
351
#' @param remove_most_frequent_dummy
352
#' Removes the most frequently observed category such that only n-1 dummies
353
#' remain. If there is a tie for most frequent, will remove the first
354
#' (by alphabetical order) category that is tied for most frequent.
355
#' @param ignore_na
356
#' If TRUE, ignores any NA values in the column. If FALSE (default), then it
357
#' will make a dummy column for value_NA and give a 1 in any row which has a
358
#' NA value.
359
#' @param split
360
#' A string to split a column when multiple categories are in the cell. For
361
#' example, if a variable is Pets and the rows are "cat", "dog", and "turtle",
362
#' each of these pets would become its own dummy column. If one row is "cat, dog",
363
#' then a split value of "," this row would have a value of 1 for both the cat
364
#' and dog dummy columns.
365
#' @param remove_selected_columns
366
#' If TRUE (not default), removes the columns used to generate the dummy columns.
367
#' @param omit_colname_prefix
368
#' If TRUE (not default) and `length(select_columns) == 1`, omit pre-pending the
369
#' name of `select_columns` to the names of the newly generated dummy columns
370
#'
371
#' @return
372
#' A data.frame (or tibble or data.table, depending on input data type) with
373
#' same number of rows as inputted data and original columns plus the newly
374
#' created dummy columns.
375
#'
376
#' @importFrom data.table as.data.table is.data.table chmatch alloc.col set
377
#' @importFrom stringr str_sort str_order
378
#'
379
dummy_cols <- function(.data, select_columns = NULL, remove_first_dummy = FALSE,
380
          remove_most_frequent_dummy = FALSE, ignore_na = FALSE, split = NULL,
381
          remove_selected_columns = FALSE, omit_colname_prefix = FALSE)
382
{
383
  stopifnot(is.null(select_columns) || is.character(select_columns),
384
            select_columns != "", is.logical(remove_first_dummy),
385
            length(remove_first_dummy) == 1, is.logical(remove_selected_columns))
386
  if (remove_first_dummy == TRUE & remove_most_frequent_dummy ==
387
      TRUE) {
388
    stop("Select either 'remove_first_dummy' or 'remove_most_frequent_dummy'\n         to proceed.")
389
  }
390
  if (is.vector(.data)) {
391
    .data <- data.frame(.data = .data, stringsAsFactors = FALSE)
392
  }
393
  data_type <- check_type(.data)
394
  if (!data.table::is.data.table(.data)) {
395
    .data <- data.table::as.data.table(.data)
396
  }
397
  if (!is.null(select_columns)) {
398
    char_cols <- select_columns
399
    cols_not_in_data <- char_cols[!char_cols %in% names(.data)]
400
    char_cols <- char_cols[!char_cols %in% cols_not_in_data]
401
    if (length(char_cols) == 0) {
402
      stop("select_columns is/are not in data. Please check data and spelling.")
403
    }
404
  }
405
  else if (ncol(.data) == 1) {
406
    char_cols <- names(.data)
407
  }
408
  else {
409
    char_cols <- sapply(.data, class)
410
    char_cols <- char_cols[char_cols %in% c("factor", "character")]
411
    char_cols <- names(char_cols)
412
  }
413
  if (length(char_cols) == 0 && is.null(select_columns)) {
414
    stop("No character or factor columns found. ",
415
         "Please use select_columns to choose columns.")
416
  }
417
  if (!is.null(select_columns) && length(cols_not_in_data) >
418
      0) {
419
    warning("NOTE: The following select_columns input(s) ",
420
                   "is not a column in data:\n", names(cols_not_in_data), "\t")
421
  }
422
  for (col_name in char_cols) {
423
    if (is.factor(.data[[col_name]])) {
424
      unique_vals <- levels(.data[[col_name]])
425
      if (any(is.na(.data[[col_name]]))) {
426
        unique_vals <- c(unique_vals, NA)
427
      }
428
    }
429
    else {
430
      unique_vals <- unique(.data[[col_name]])
431
      unique_vals <- stringr::str_sort(unique_vals, na_last = TRUE,
432
                                       locale = "en_US", numeric = TRUE)
433
    }
434
    unique_vals <- as.character(unique_vals)
435
    if (!is.null(split)) {
436
      unique_vals <- unique(trimws(unlist(strsplit(unique_vals,
437
                                                   split = split))))
438
    }
439
    if (ignore_na) {
440
      unique_vals <- unique_vals[!is.na(unique_vals)]
441
    }
442
    if (remove_most_frequent_dummy) {
443
      vals <- as.character(.data[[col_name]])
444
      vals <- data.frame(sort(table(vals), decreasing = TRUE),
445
                         stringsAsFactors = FALSE)
446
      top_vals <- vals[vals$Freq %in% max(vals$Freq), ]
447
      other_vals <- vals$vals[!vals$Freq %in% max(vals$Freq)]
448
      other_vals <- as.character(other_vals)
449
      top_vals <- top_vals[stringr::str_order(top_vals$vals,
450
                                              na_last = TRUE, locale = "en_US", numeric = TRUE),
451
      ]
452
      if (nrow(top_vals) == 1) {
453
        top_vals <- NULL
454
      }
455
      else {
456
        top_vals <- as.character(top_vals$vals[2:nrow(top_vals)])
457
      }
458
      unique_vals <- c(top_vals, other_vals)
459
      unique_vals <- stringr::str_sort(unique_vals, na_last = TRUE,
460
                                       locale = "en_US", numeric = TRUE)
461
    }
462
    if (remove_first_dummy) {
463
      unique_vals <- unique_vals[-1]
464
    }
465
    data.table::alloc.col(.data, ncol(.data) + length(unique_vals))
466
    .data[, paste0(col_name, "_", unique_vals)] <- 0L
467
    for (unique_value in unique_vals) {
468
      data.table::set(.data, i = which(data.table::chmatch(as.character(.data[[col_name]]),
469
                                                           unique_value, nomatch = 0) == 1L), j = paste0(col_name,
470
                                                                                                         "_", unique_value), value = 1L)
471
      if (!is.na(unique_value)) {
472
        data.table::set(.data, i = which(is.na(.data[[col_name]])),
473
                        j = paste0(col_name, "_", unique_value), value = NA)
474
      }
475
      if (!is.null(split)) {
476
        max_split_length <- max(sapply(strsplit(as.character(.data[[col_name]]),
477
                                                split = split), length))
478
        for (split_length in seq_len(max_split_length)) {
479
          data.table::set(.data, i = which(data.table::chmatch(as.character(trimws(sapply(strsplit(as.character(.data[[col_name]]),
480
                                                                                                   split = split), `[`, split_length))), unique_value,
481
                                                               nomatch = 0) == 1L), j = paste0(col_name,
482
                                                                                               "_", unique_value), value = 1L)
483
        }
484
        if (is.na(unique_value)) {
485
          .data[[paste0(col_name, "_", unique_value)]][which(!is.na(.data[[col_name]]))] <- 0
486
        }
487
      }
488
    }
489
  }
490
  if (remove_selected_columns) {
491
    .data <- .data[-which(names(.data) %in% char_cols)]
492
  }
493
  .data <- fix_data_type(.data, data_type)
494
  if (omit_colname_prefix) {
495
    if (length(select_columns) == 1) {
496
      new_col_index <- as.logical(rowSums(sapply(unique_vals,
497
                                                 function(x) grepl(paste0(select_columns, "_",
498
                                                                          x), names(.data)))))
499
      names(.data)[new_col_index] <- gsub(paste0(select_columns,
500
                                                 "_"), "", names(.data)[new_col_index])
501
    }
502
    else {
503
      message("Can't omit the colname prefix when recoding more than one column.")
504
      message("Returning prefixed dummy columns.")
505
    }
506
  }
507
  return(.data)
508
}
509
510
#' @importFrom data.table is.data.table
511
#' @noRd
512
check_type <- function(.data) {
513
  if (data.table::is.data.table(.data)) {
514
    data_type <- "is_data_table"
515
  } else if (inherits(.data, "tbl_df")) {
516
    data_type <- "is_tibble"
517
  } else {
518
    data_type <- "is_data_frame"
519
  }
520
521
  return(data_type)
522
}
523
524
#' @importFrom dplyr as_tibble
525
#' @noRd
526
fix_data_type <- function(.data, data_type) {
527
  if (data_type == "is_data_frame") {
528
    .data <- as.data.frame(.data, stringsAsFactors = FALSE)
529
  } else if (data_type == "is_tibble") {
530
    .data <- dplyr::as_tibble(.data)
531
  }
532
533
  return(.data)
534
}
535
536
#' CSS string helper
537
#'
538
#' Convenience function for building CSS style declarations (i.e. the string
539
#' that goes into a style attribute, or the parts that go inside curly braces in
540
#' a full stylesheet).
541
#'
542
#' CSS uses `'-'` (minus) as a separator character in property names, but
543
#' this is an inconvenient character to use in an R function argument name.
544
#' Instead, you can use `'.'` (period) and/or `'_'` (underscore) as
545
#' separator characters. For example, `css(font.size = "12px")` yields
546
#' `"font-size:12px;"`.
547
#'
548
#' To mark a property as `!important`, add a `'!'` character to the end
549
#' of the property name. (Since `'!'` is not normally a character that can be
550
#' used in an identifier in R, you'll need to put the name in double quotes or
551
#' backticks.)
552
#'
553
#' Argument values will be converted to strings using
554
#' `paste(collapse = " ")`. Any property with a value of `NULL` or
555
#' `""` (after paste) will be dropped.
556
#'
557
#' @param ... Named style properties, where the name is the property name and
558
#'   the argument is the property value. See Details for conversion rules.
559
#' @param collapse_ (Note that the parameter name has a trailing underscore
560
#'   character.) Character to use to collapse properties into a single string;
561
#'   likely `""` (the default) for style attributes, and either `"\n"`
562
#'   or `NULL` for style blocks.
563
#'
564
#' @importFrom rlang dots_list
565
#' 
566
#' @noRd
567
css <- function(..., collapse_ = "") {
568
  props <- rlang::dots_list(...)
569
  if (length(props) == 0) {
570
    return(NULL)
571
  }
572
  
573
  if (is.null(names(props)) || any(names(props) == "")) {
574
    stop("cssList expects all arguments to be named")
575
  }
576
  
577
  # Necessary to make factors show up as level names, not numbers
578
  props[] <- lapply(props, paste, collapse = " ")
579
  
580
  # Drop null args
581
  props <- props[!sapply(props, empty)]
582
  if (length(props) == 0) {
583
    return(NULL)
584
  }
585
  
586
  # Translate camelCase, snake_case, and dot.case to kebab-case
587
  # For standard CSS properties only, not CSS variables
588
  is_css_var <- grepl("^--", names(props))
589
  names(props)[!is_css_var] <- standardize_property_names(names(props)[!is_css_var])
590
  
591
  # Create "!important" suffix for each property whose name ends with !, then
592
  # remove the ! from the property name
593
  important <- ifelse(grepl("!$", names(props), perl = TRUE), " !important", "")
594
  names(props) <- sub("!$", "", names(props), perl = TRUE)
595
  
596
  paste0(names(props), ":", props, important, ";", collapse = collapse_)
597
}
598
599
empty <- function(x) {
600
  length(x) == 0 || (is.character(x) && !any(nzchar(x)))
601
}
602
603
standardize_property_names <- function(x) {
604
  # camelCase to kebab-case
605
  x <- gsub("([A-Z])", "-\\1", x)
606
  x <- tolower(x)
607
  # snake_case and dot.case to kebab-case
608
  gsub("[._]", "-", x)
609
}
610
611
#' @importFrom grDevices hcl
612
hue_pal <- function(n, h = c(0, 360) + 15, c = 100, l = 65, h.start = 0, direction = 1) 
613
{
614
  if (n == 0) {
615
    stop("Must request at least one colour from a hue palette.")
616
  }
617
  if ((diff(h)%%360) < 1) {
618
    h[2] <- h[2] - 360/n
619
  }
620
  hues <- seq(h[1], h[2], length.out = n)
621
  hues <- (hues + h.start)%%360
622
  hcl <- cbind(hues, c, l)
623
  pal <- apply(hcl, 1, function(x){
624
    grDevices::hcl(x[1], x[2], x[3])
625
  })
626
  if (direction == -1) {
627
    rev(pal)
628
  }
629
  else {
630
    pal
631
  }
632
}
633
634
rescale_numeric <- function(x, to = c(0, 1), from = range(x, na.rm = TRUE, finite = TRUE), ...) {
635
  (x - from[1]) / diff(from) * diff(to) + to[1]
636
}
637
638
as.raster_array <- function (x, max = 1, ...) 
639
{
640
  if (!is.numeric(x)) {
641
    if (is.raw(x)) {
642
      storage.mode(x) <- "integer"
643
      max <- 255L
644
    }
645
    else stop("a raster array must be numeric")
646
  }
647
  if (length(d <- dim(x)) != 3L) 
648
    stop("a raster array must have exactly 3 dimensions")
649
  r <- array(if (d[3L] == 3L) 
650
    rgb(t(x[, , 1L]), t(x[, , 2L]), t(x[, , 3L]), maxColorValue = max)
651
    else if (d[3L] == 4L) 
652
      rgb(t(x[, , 1L]), t(x[, , 2L]), t(x[, , 3L]), t(x[, , 4L]), maxColorValue = max)
653
    else if (d[3L] == 1L) 
654
      rgb(t(x[, , 1L]), t(x[, , 1L]), t(x[, , 1L]), maxColorValue = max)
655
    else stop("a raster array must have exactly 1, 3 or 4 planes"), 
656
    dim = d[seq_len(2)])
657
  class(r) <- "raster"
658
  r
659
}
660
661
avgHexColor <- function(colors){
662
  rgb(t(Reduce(`+`, lapply(colors, col2rgb))/3), maxColorValue=255)
663
}
664
665
####
666
## ggedit tools ####
667
## See https://github.com/yonicd/ggedit/tree/master/R
668
####
669
670
#' @title ggplot2 layer proto extraction
671
#' @description Extract geom, stat and position protos from a ggplot2 layer
672
#' @param l ggproto
673
#' @noRd
674
proto_features <- function(l) {
675
  a <- sapply(c("position", "geom", "stat"), function(x) {
676
    class(l[[x]])[1]
677
  })
678
  
679
  data.frame(t(a), stringsAsFactors = FALSE)
680
}
681
682
# forked from https://github.com/yihui/knitr/blob/master/R/defaults.R
683
#' @importFrom stats setNames
684
#' @noRd
685
new_defaults <- function(value = list()) {
686
  defaults <- value
687
  
688
  get <- function(name, default = FALSE, drop = TRUE, regex=FALSE, ...) {
689
    if (default) defaults <- value # this is only a local version
690
    if (missing(name)) {
691
      defaults
692
    } else {
693
      if (drop && length(name) == 1) {
694
        if (regex) {
695
          name_grep <- grep(name, names(defaults), value = TRUE, ...)
696
          stats::setNames(defaults[name_grep], name_grep)
697
        } else {
698
          defaults[[name]]
699
        }
700
      } else {
701
        stats::setNames(defaults[name], name)
702
      }
703
    }
704
  }
705
  
706
  set <- function(...) {
707
    dots <- list(...)
708
    if (length(dots) == 0) return()
709
    if (is.null(names(dots)) && length(dots) == 1 && is.list(dots[[1]])) {
710
      if (length(dots <- dots[[1]]) == 0) {
711
        return()
712
      }
713
    }
714
    defaults <<- merge(dots)
715
    invisible(NULL)
716
  }
717
  
718
  # merge <- function(values) merge_list(defaults, values)
719
  
720
  restore <- function(target = value) defaults <<- target
721
  
722
  append <- function(...) {
723
    dots <- list(...)
724
    if (length(dots) == 0) return()
725
    if (is.null(names(dots)) && length(dots) == 1 && is.list(dots[[1]])) {
726
      if (length(dots <- dots[[1]]) == 0) {
727
        return()
728
      }
729
    }
730
    dots <- sapply(names(dots), function(x) dots[[x]] <- c(defaults[[x]], dots[[x]]), simplify = FALSE)
731
    defaults <<- merge(dots)
732
    invisible(NULL)
733
  }
734
  
735
  list(get = get, set = set, append = append, merge = merge, restore = restore)
736
}
737
738
#' @title Creates an independent copy of a ggplot layer object
739
#' @description Creates copies of ggplot layers from within ggplot objects that
740
#' are independent of the parent object.
741
#' @details ggplot objects are comprimsed of layer objects. Once compiled they
742
#' are part of the plot object environment and if they are changed internally
743
#' regardless of where they are in the (ie different environment) it will change
744
#' the original plot. This function allows to create replicates of the plot layers
745
#' and edit them independent of the original plot. When setting verbose to TRUE
746
#' function returns the ggplot2 call as a string to paste in regular ggplot script
747
#' to generate the layer.
748
#' @param l ggplot2 object layer
749
#' @param verbose toggle to control if the output is ggproto object (verbose==FALSE,default) or string of layer call (verbose==TRUE)
750
#' @param showDefaults toggle to control if the verbose output shows all the input arguments passed to the proto object (if verbose==FALSE then ignored)
751
#' @return ggproto or string object (conditional on verbose)
752
#'
753
#' @importFrom utils capture.output
754
#' @importFrom rlang sym '!!'
755
#' @noRd
756
cloneLayer <- function (l, verbose = FALSE, showDefaults = TRUE) 
757
{
758
  geom_opts <- ggedit_opts$get("session_geoms")
759
  parent.layer <- dplyr::left_join(proto_features(l), dplyr::filter(geom_opts, 
760
                                                                    !grepl("^stat", !!rlang::sym("fn"))), by = c("position", 
761
                                                                                                                 "geom", "stat"))
762
  if (is.na(parent.layer$fn)) 
763
    parent.layer$fn <- paste0(tolower(strsplit(parent.layer$stat, 
764
                                               "(?<=Stat)", perl = TRUE)[[1]]), collapse = "_")
765
  layer.names <- c("mapping", "data", "geom", "position", "stat", 
766
                   "show.legend", "inherit.aes", "aes_params", "geom_params", 
767
                   "stat_params")
768
  x <- sapply(layer.names, function(y) {
769
    b <- l[[y]]
770
    if ("waiver" %in% class(b)) 
771
      b <- NULL
772
    if (y == "geom") 
773
      b <- eval(parse(text = parent.layer$geom))
774
    if (y == "position") 
775
      b <- gsub(y, "", tolower(class(b)[1]))
776
    if (y == "stat") 
777
      b <- eval(parse(text = parent.layer$stat))
778
    b
779
  })
780
  x$params <- append(x$stat_params, x$geom_params)
781
  x$params <- append(x$params, x$aes_params)
782
  x$params <- x$params[!duplicated(names(x$params))]
783
  x$geom_params <- x$aes_params <- x$stat_params <- NULL
784
  if (verbose) {
785
    nm <- names(x)
786
    nm <- nm[!sapply(x, typeof) %in% c("environment", "closure", 
787
                                       "list")]
788
    geom_aes <- list(geom = parent.layer$fn, mapping = sapply(names(x$mapping), 
789
                                                              build_map, y = x$mapping), params = sapply(names(x$params), 
790
                                                                                                         build_map, y = x$params), layer = sapply(rev(nm), 
791
                                                                                                                                                  build_map, y = x[rev(nm)]), data = paste0("data = ", 
792
                                                                                                                                                                                            paste0(capture.output(dput(x$data)), collapse = "\n")))
793
    strRet <- sprintf("%s(mapping=aes(%s),%s,%s)", paste0(geom_aes$geom, 
794
                                                          collapse = ","), paste0(geom_aes$mapping, collapse = ","), 
795
                      paste0(geom_aes$params, collapse = ","), paste0(geom_aes$layer, 
796
                                                                      collapse = ","))
797
    if (!showDefaults) {
798
      geom_proto <- cloneProto(eval(parse(text = paste0(geom_aes$geom, 
799
                                                        "()"))))
800
      geom_diff <- sapply(names(geom_aes)[-1], function(x) geom_aes[[x]][!geom_aes[[x]] %in% 
801
                                                                           geom_proto[[x]]])
802
      strRet <- sprintf("%s(aes(%s),%s,%s,%s)", paste0(geom_aes$geom, 
803
                                                       collapse = ","), paste0(geom_diff$mapping, collapse = ","), 
804
                        paste0(geom_diff$params, collapse = ","), paste0(geom_diff$layer, 
805
                                                                         collapse = ","), geom_aes$data)
806
    }
807
    strRet <- gsub("aes()", "", strRet, fixed = T)
808
    strRet <- gsub("[,]{2,}", ",", strRet)
809
    strRet <- gsub("data=NULL", "", strRet)
810
    strRet <- gsub(",)", ")", strRet)
811
    strRet <- gsub("\\(,", "(", strRet)
812
    strRet
813
  }
814
  else {
815
    do.call(layer, x)
816
  }
817
}
818
819
#' @importFrom dplyr filter left_join
820
#' @importFrom rlang sym '!!'
821
#' @noRd
822
cloneProto <- function(l) {
823
  
824
  geom_opts <- ggedit_opts$get("session_geoms")
825
  
826
  parent.layer <- proto_features(l)  |> 
827
    dplyr::left_join(
828
      geom_opts  |>  dplyr::filter(!grepl("^stat", !!rlang::sym('fn'))),
829
      by = c("position", "geom", "stat")
830
    )
831
  
832
  if (is.na(parent.layer$fn)) {
833
    parent.layer$fn <- paste0(tolower(strsplit(parent.layer$stat, "(?<=Stat)", perl = TRUE)[[1]]), collapse = "_")
834
  }
835
  
836
  layer.names <- c("mapping", "data", "geom", "position", "stat", "show.legend", "inherit.aes", "aes_params", "geom_params", "stat_params")
837
  
838
  x <- sapply(layer.names, function(y) {
839
    b <- l[[y]]
840
    
841
    if ("waiver" %in% class(b)) {
842
      b = NULL
843
    }
844
    
845
    if (y == "geom") {
846
      b <- eval(parse(text = parent.layer$geom))
847
    }
848
    
849
    if (y == "position") {
850
      b <- gsub(y, "", tolower(class(b)[1]))
851
    }
852
    
853
    if (y == "stat") {
854
      b <- eval(parse(text = parent.layer$stat))
855
    }
856
    
857
    b
858
  })
859
  
860
  x$params <- append(x$stat_params, x$geom_params)
861
  x$params <- append(x$params, x$aes_params)
862
  x$params <- x$params[!duplicated(names(x$params))]
863
  
864
  x$geom_params <- x$aes_params <- x$stat_params <- NULL
865
  
866
  fn <- parent.layer$fn
867
  
868
  g <- paste0(fn, "()")
869
  g <- eval(parse(text = g))
870
  nm <- names(x)
871
  
872
  nm <- nm[!sapply(x, typeof) %in% c("environment", "closure", "list")]
873
  
874
  geom_aes <- list(
875
    geom = fn,
876
    mapping = sapply(names(x$mapping), build_map,y = x$mapping),
877
    params = sapply(names(x$params), build_map, y = x$params),
878
    layer = sapply(rev(nm), build_map, y = x[rev(nm)])
879
  )
880
  
881
  nDF <- cbind(names(g$geom$default_aes), paste(g$geom$default_aes))
882
  nDF[grep("colour|fill|color", nDF[, 1]), 2] <- paste0("'", col2hcl(nDF[grep("colour|fill|color", nDF[, 1]), 2], alpha = NULL), "'")
883
  
884
  geom_aes$default <- paste0(apply(nDF, 1, function(x) paste0(x, collapse = "=")))
885
  
886
  geom_aes
887
}
888
889
#' @title Default and current ggedit options
890
#'
891
#' @description Options for functions in the ggedit package. When running R code, the object \code{ggedit_opts}
892
#' (default options) is not modified by chunk headers (local chunk options are
893
#' merged with default options), whereas \code{ggedit_opts_current} (current options)
894
#' changes with different chunk headers and it always reflects the options for
895
#' the current chunk.
896
#'
897
#' Normally we set up the global options once in the first code chunk in a
898
#' document using \code{ggedit_opts$set()}, so that all \emph{latter} chunks will
899
#' use these options. Note the global options set in one chunk will not affect
900
#' the options in this chunk itself, and that is why we often need to set global
901
#' options in a separate chunk.
902
#' 
903
#' @note \code{ggedit_opts_current} is read-only in the sense that it does nothing if
904
#'   you call \code{ggedit_opts_current$set()}; you can only query the options via
905
#'   \code{ggedit_opts_current$get()}.
906
#' @rdname ggeditOpts
907
#' @noRd
908
ggedit_opts <- new_defaults(list(
909
  fontDefaults = c(
910
    "sans",
911
    "Canonical",
912
    "mono",
913
    "Courier",
914
    "Helvetica",
915
    "serif",
916
    "Times",
917
    "AvantGarde",
918
    "Bookman",
919
    "Helvetica-Narrow",
920
    "NewCenturySchoolbook",
921
    "Palatino",
922
    "URWGothic",
923
    "URWBookman",
924
    "NimbusMon",
925
    "URWHelvetica",
926
    "NimbusSan",
927
    "NimbusSanCond",
928
    "CenturySch",
929
    "URWPalladio",
930
    "URWTimes",
931
    "NimbusRom"
932
  ),
933
  slideDefaults = list(
934
    alpha = c(min = 0, max = 1),
935
    size = c(min = 0, max = 10),
936
    shape = c(min = 1, max = 25),
937
    stroke = c(min = 0, max = 10),
938
    weight = c(min = 0, max = 10),
939
    linetype = c(min = 1, max = 5),
940
    width = c(min = 0, max = 1),
941
    angle = c(min = 0, max = 360),
942
    hjust = c(min = -10, max = 10),
943
    vjust = c(min = -10, max = 10),
944
    stroke = c(min = 0, max = 10),
945
    lineheight = c(min = 0, max = 10),
946
    linewidth = c(min = 0, max = 5),
947
    fontface = c(min = 1, max = 4),
948
    rel_min_height = c(min = 0, max = 1),
949
    scale = c(min = 0, max = 100)
950
  ),
951
  themeTips = list(
952
    element_rect = list(
953
      fill = "fill colour",
954
      colour = "border colour",
955
      size = "border size (in pts)",
956
      linetype = paste0(
957
        paste(
958
          seq(0, 6),
959
          c(
960
            "blank", "solid", "dashed", "dotted", "dotdash",
961
            "longdash", "twodash"
962
          ), sep = ": "
963
        ),
964
        collapse = ", "
965
      )
966
    ),
967
    element_line = list(
968
      colour = "line colour",
969
      size = "numeric (in pts) or \n relative to global size rel(numeric)",
970
      linetype = paste0(
971
        paste(
972
          seq(0, 6),
973
          c(
974
            "blank", "solid", "dashed", "dotted", "dotdash",
975
            "longdash", "twodash"
976
          ), sep = ": "
977
        ),
978
        collapse = ", "
979
      ),
980
      lineend = c("butt(default),round,square")
981
    ),
982
    element_text = list(
983
      family = shiny::HTML('<a href="http://www.cookbook-r.com/Graphs/Fonts/" target="_blank">font family</a>'),
984
      face = 'font face ("plain", "italic", "bold", "bold.italic")',
985
      colour = "text colour",
986
      size = "text size (in pts)",
987
      hjust = "horizontal justification (in [0, 1])",
988
      vjust = "vertical justification (in [0, 1])",
989
      angle = "angle (in [0, 360])",
990
      lineheight = "numeric line height"
991
    ),
992
    justification = list(justification = 'anchor point for positioning legend inside plot <br/> "center" or two-element numeric vector'),
993
    position = list(position = 'the position of legends. <br/> "left", "right", "bottom", "top", or two-element numeric vector')
994
  ),
995
  
996
  ThemeDefaultClass =
997
    data.frame(
998
      item = c("angle", "background", "caption", "colour", "face", "family", "fill", "grid.major", "grid.minor", "hjust", "justification", "key", "key.size", "line", "lineheight", "linetype", "margin", "ontop", "position", "size", "subtitle", "switch.pad.grid", "switch.pad.wrap", "text", "text.x", "text.y", "ticks", "ticks.length", "title", "title.x", "title.y", "vjust", "placement"),
999
      class = c("numeric", "character", "character", "character", "character", "character", "character", "character", "character", "numeric", "character", "character", "character", "character", "numeric", "numeric", "numeric", "character", "character", "numeric", "character", "character", "character", "character", "character", "character", "numeric", "numeric", "character", "character", "character", "numeric", "character"), stringsAsFactors = FALSE
1000
    ),
1001
  session_geoms = 
1002
    data.frame(
1003
      fn = c("annotation_custom", "annotation_logticks", 
1004
             "annotation_map", "annotation_raster", "geom_abline", "geom_area", 
1005
             "geom_bar", "geom_bin2d", "geom_blank", "geom_boxplot", "geom_col", 
1006
             "geom_contour", "geom_count", "geom_crossbar", "geom_curve", 
1007
             "geom_density", "geom_density_2d", "geom_density2d", "geom_dotplot", 
1008
             "geom_errorbar", "geom_errorbarh", "geom_freqpoly", "geom_hex", 
1009
             "geom_histogram", "geom_hline", "geom_jitter", "geom_label", 
1010
             "geom_line", "geom_linerange", "geom_map", "geom_path", "geom_point", 
1011
             "geom_pointrange", "geom_polygon", "geom_qq", "geom_qq_line", 
1012
             "geom_quantile", "geom_raster", "geom_rect", "geom_ribbon", "geom_rug", 
1013
             "geom_segment", "geom_sf", "geom_smooth", "geom_spoke", "geom_step", 
1014
             "geom_text", "geom_tile", "geom_violin", "geom_vline", "stat_bin", 
1015
             "stat_bin_2d", "stat_bin_hex", "stat_bin2d", "stat_binhex", "stat_boxplot", 
1016
             "stat_contour", "stat_count", "stat_density", "stat_density_2d", 
1017
             "stat_density2d", "stat_ecdf", "stat_ellipse", "stat_function", 
1018
             "stat_identity", "stat_qq", "stat_qq_line", "stat_quantile", 
1019
             "stat_sf", "stat_smooth", "stat_sum", "stat_summary", "stat_summary_2d", 
1020
             "stat_summary_bin", "stat_summary_hex", "stat_unique", "stat_ydensity"),
1021
      geom = c("GeomCustomAnn", "GeomLogticks", "GeomAnnotationMap", 
1022
               "GeomRasterAnn", "GeomAbline", "GeomArea", "GeomBar", "GeomTile", 
1023
               "GeomBlank", "GeomBoxplot", "GeomCol", "GeomContour", "GeomPoint", 
1024
               "GeomCrossbar", "GeomCurve", "GeomDensity", "GeomDensity2d", 
1025
               "GeomDensity2d", "GeomDotplot", "GeomErrorbar", "GeomErrorbarh", 
1026
               "GeomPath", "GeomHex", "GeomBar", "GeomHline", "GeomPoint", "GeomLabel", 
1027
               "GeomLine", "GeomLinerange", "GeomMap", "GeomPath", "GeomPoint", 
1028
               "GeomPointrange", "GeomPolygon", "GeomPoint", "GeomPath", "GeomQuantile", 
1029
               "GeomRaster", "GeomRect", "GeomRibbon", "GeomRug", "GeomSegment", 
1030
               "GeomSf", "GeomSmooth", "GeomSpoke", "GeomStep", "GeomText", 
1031
               "GeomTile", "GeomViolin", "GeomVline", "GeomBar", "GeomTile", 
1032
               "GeomHex", "GeomTile", "GeomHex", "GeomBoxplot", "GeomContour", 
1033
               "GeomBar", "GeomArea", "GeomDensity2d", "GeomDensity2d", "GeomStep", 
1034
               "GeomPath", "GeomPath", "GeomPoint", "GeomPoint", "GeomPath", 
1035
               "GeomQuantile", "GeomRect", "GeomSmooth", "GeomPoint", "GeomPointrange", 
1036
               "GeomTile", "GeomPointrange", "GeomHex", "GeomPoint", "GeomViolin"), 
1037
      position = c("PositionIdentity", "PositionIdentity", "PositionIdentity", 
1038
                   "PositionIdentity", "PositionIdentity", "PositionStack", "PositionStack", 
1039
                   "PositionIdentity", "PositionIdentity", "PositionDodge2", "PositionStack", 
1040
                   "PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity", 
1041
                   "PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity", 
1042
                   "PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity", 
1043
                   "PositionStack", "PositionIdentity", "PositionJitter", "PositionIdentity", 
1044
                   "PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity", 
1045
                   "PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity", 
1046
                   "PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity", 
1047
                   "PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity", 
1048
                   "PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity", 
1049
                   "PositionIdentity", "PositionDodge", "PositionIdentity", "PositionStack", 
1050
                   "PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity", 
1051
                   "PositionDodge2", "PositionIdentity", "PositionStack", "PositionStack", 
1052
                   "PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity", 
1053
                   "PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity", 
1054
                   "PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity", 
1055
                   "PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity", 
1056
                   "PositionIdentity", "PositionDodge"), 
1057
      stat = c("StatIdentity", "StatIdentity", "StatIdentity", "StatIdentity", "StatIdentity", 
1058
               "StatIdentity", "StatCount", "StatBin2d", "StatIdentity", "StatBoxplot", 
1059
               "StatIdentity", "StatContour", "StatSum", "StatIdentity", "StatIdentity", 
1060
               "StatDensity", "StatDensity2d", "StatDensity2d", "StatBindot", 
1061
               "StatIdentity", "StatIdentity", "StatBin", "StatBinhex", "StatBin", 
1062
               "StatIdentity", "StatIdentity", "StatIdentity", "StatIdentity", 
1063
               "StatIdentity", "StatIdentity", "StatIdentity", "StatIdentity", 
1064
               "StatIdentity", "StatIdentity", "StatQq", "StatQqLine", "StatQuantile", 
1065
               "StatIdentity", "StatIdentity", "StatIdentity", "StatIdentity", 
1066
               "StatIdentity", "StatSf", "StatSmooth", "StatIdentity", "StatIdentity", 
1067
               "StatIdentity", "StatIdentity", "StatYdensity", "StatIdentity", 
1068
               "StatBin", "StatBin2d", "StatBinhex", "StatBin2d", "StatBinhex", 
1069
               "StatBoxplot", "StatContour", "StatCount", "StatDensity", "StatDensity2d", 
1070
               "StatDensity2d", "StatEcdf", "StatEllipse", "StatFunction", "StatIdentity", 
1071
               "StatQq", "StatQqLine", "StatQuantile", "StatSf", "StatSmooth", 
1072
               "StatSum", "StatSummary", "StatSummary2d", "StatSummaryBin", 
1073
               "StatSummaryHex", "StatUnique", "StatYdensity"), 
1074
      pkg = rep("ggplot2",  77),stringsAsFactors = FALSE)
1075
))
1076
1077
# Function to convert color to HCL and adjust components
1078
#' @importFrom grDevices col2rgb rgb2hsv hcl rgb
1079
col2hcl <- function(colour, h = NULL, c = NULL, l = NULL, alpha = NULL) {
1080
  # Convert color to RGB
1081
  rgb_col <- grDevices::col2rgb(colour) / 255
1082
  
1083
  # Convert RGB to HSV
1084
  hsv_col <- grDevices::rgb2hsv(rgb_col)
1085
  
1086
  # Convert HSV to HCL
1087
  hue <- hsv_col[1] * 360  # Convert hue to degrees (0-360)
1088
  chroma <- hsv_col[2] * 100  # Chroma is similar to saturation
1089
  luminance <- hsv_col[3] * 100  # Luminance is related to value
1090
  
1091
  # Allow user to override H, C, or L values
1092
  if (!is.null(h)) hue <- h
1093
  if (!is.null(c)) chroma <- c
1094
  if (!is.null(l)) luminance <- l
1095
  
1096
  # Create the HCL color with potentially modified components
1097
  hcl_col <- grDevices::hcl(h = hue, c = chroma, l = luminance)
1098
  
1099
  # Convert HCL back to RGB to apply alpha
1100
  rgb_col_with_alpha <- grDevices::col2rgb(hcl_col) / 255
1101
  
1102
  # Add alpha transparency if provided
1103
  if (!is.null(alpha)) {
1104
    rgba_col <- grDevices::rgb(rgb_col_with_alpha[1], rgb_col_with_alpha[2], rgb_col_with_alpha[3], alpha = alpha)
1105
  } else {
1106
    rgba_col <- grDevices::rgb(rgb_col_with_alpha[1], rgb_col_with_alpha[2], rgb_col_with_alpha[3])
1107
  }
1108
  
1109
  # Return the final RGBA color
1110
  return(rgba_col)
1111
}
1112
1113
#' @importFrom stats as.formula
1114
#' @importFrom rlang quo_name
1115
#' @noRd
1116
build_map <- function(item,y) {
1117
  
1118
  y <- y[[item]]
1119
  
1120
  if (inherits(y,'quosure')){
1121
    return(sprintf('%s = %s',item,rlang::quo_name(y)))
1122
  }
1123
  
1124
  if (inherits(y,'character')){
1125
    return(sprintf("%s = '%s'",item,y))
1126
  }
1127
  
1128
  if (inherits(y, "formula")){
1129
    return(sprintf("formula=stats::as.formula('%s')",
1130
                   paste0(as.character(y)[-1], collapse = "~")))
1131
  }
1132
  
1133
  
1134
  if (inherits(y,'NULL')) {
1135
    return(sprintf('%s = NULL',item))
1136
  }
1137
  
1138
  
1139
  if (inherits(y, c("function", "call", "ggproto"))) {
1140
    return(sprintf("%s = %s",
1141
                   item,
1142
                   paste(capture.output(
1143
                     dput(y)),
1144
                     collapse = "\n")
1145
    ))
1146
  }
1147
  
1148
  if (inherits(y, c("data.frame"))) {
1149
    return(paste0("=", paste(capture.output(dput(y)), collapse = "\n")))
1150
  }
1151
  
1152
1153
  return(sprintf('%s = %s',item, y))
1154
  
1155
}
1156
1157
#' @noRd
1158
capture.output <- function (..., file = NULL, append = FALSE, type = c("output", "message"), split = FALSE) 
1159
{
1160
  type <- match.arg(type)
1161
  rval <- NULL
1162
  closeit <- TRUE
1163
  if (is.null(file)) 
1164
    file <- textConnection("rval", "w", local = TRUE)
1165
  else if (is.character(file)) 
1166
    file <- file(file, if (append) 
1167
      "a"
1168
      else "w")
1169
  else if (inherits(file, "connection")) {
1170
    if (!isOpen(file)) 
1171
      open(file, if (append) 
1172
        "a"
1173
        else "w")
1174
    else closeit <- FALSE
1175
  }
1176
  else stop("'file' must be NULL, a character string or a connection")
1177
  sink(file, type = type, split = split)
1178
  on.exit({
1179
    sink(type = type, split = split)
1180
    if (closeit) close(file)
1181
  })
1182
  for (i in seq_len(...length())) {
1183
    out <- withVisible(...elt(i))
1184
    if (out$visible) 
1185
      print(out$value)
1186
  }
1187
  on.exit()
1188
  sink(type = type, split = split)
1189
  if (closeit) 
1190
    close(file)
1191
  if (is.null(rval)) 
1192
    invisible(NULL)
1193
  else rval
1194
}
1195
1196
avgHexColor <- function(colors, ctrlcolor){
1197
  colors <- lapply(colors, col2rgb)
1198
  rgb(t(Reduce(`+`, colors)/length(colors)), maxColorValue=255)
1199
}
1200
1201
fill_na_with_preceding <- function(x) {
1202
  if (all(is.na(x))) return(x)
1203
  for (i in 2:length(x)) {
1204
    if (is.na(x[i])) {
1205
      x[i] <- x[i - 1]
1206
    }
1207
  }
1208
  return(x)
1209
}