####
# Matrix Operations ####
####
getMin <- function(data, ...){
if(inherits(data, "IterableMatrix")){
data <- as(data, "dgCMatrix")
}
return(min(data, ...))
}
getMax <- function(data, ...){
if(inherits(data, "IterableMatrix")){
data <- as(data, "dgCMatrix")
}
return(max(data, ...))
}
getRange <- function(data, ...){
return(c(getMin(data, ...), getMax(data, ...)))
}
getColQuantiles <- function(data, desiredQuantile){
if(inherits(data, "IterableMatrix")){
return(BPCells::colQuantiles(data, probs = desiredQuantile))
} else {
return(apply(data, 2, function(x) stats::quantile(x, desiredQuantile)))
}
}
####
# Nanostring Auxiliary tools ####
####
generate_pkc_lookup <- function (jsons_vec)
{
lookup_df <- data.frame(RTS_ID = character(), Target = character(),
Module = character(), CodeClass = character(), ProbeID = character(),
GeneID = character(), SystematicName = character(), stringsAsFactors = FALSE)
for (curr_idx in seq_len(length(jsons_vec))) {
curr_module <- names(jsons_vec)[curr_idx]
curr_json <- jsons_vec[[curr_idx]]
for (targ in curr_json[["Targets"]]) {
curr_targ <- targ[["DisplayName"]]
curr_code_class <- gsub("\\d+$", "", targ[["CodeClass"]])
for (prb in targ[["Probes"]]) {
if (curr_json[["AnalyteType"]] == "Protein") {
curr_RTS_ID <- targ$RTS_ID
}
else {
curr_RTS_ID <- prb$RTS_ID
}
curr_probe_ID <- prb$ProbeID
curr_gene_ID <- paste(prb$GeneID, collapse = ", ")
if (length(prb$GeneID) < 1) {
curr_gene_ID <- NA
}
curr_syst_name <- paste(prb$SystematicName, collapse = ", ")
lookup_df[nrow(lookup_df) + 1, ] <- list(curr_RTS_ID,
curr_targ, curr_module, curr_code_class, curr_probe_ID,
curr_gene_ID, curr_syst_name)
}
}
}
return(lookup_df)
}
.dccMetadata <-
list(schema =
list("Header" =
data.frame(labelDescription =
c("The version of the file",
"The version of the software used to create the file",
"The date of the sample"),
minVersion = numeric_version(c("0.01", "0.01", "0.01")),
row.names =
c("FileVersion", "SoftwareVersion", "Date"),
stringsAsFactors = FALSE),
"Scan_Attributes" =
data.frame(labelDescription =
c("The sample ID",
"The plate ID",
"The well ID"),
row.names =
c("ID", "Plate_ID", "Well"),
minVersion = numeric_version(c(rep("0.01", 3L))),
stringsAsFactors = FALSE),
"NGS_Processing_Attributes" =
data.frame(labelDescription =
c(NA_character_,
NA_integer_,
NA_integer_,
NA_integer_,
NA_integer_,
NA_real_,
NA_real_),
minVersion = numeric_version(c(rep("0.01", 7L))),
row.names =
c("SeqSetId", "Raw", "Trimmed",
"Stitched", "Aligned", "umiQ30", "rtsQ30"),
stringsAsFactors = FALSE),
"Code_Summary" =
data.frame(labelDescription =
c(NA_character_, NA_integer_),
minVersion = numeric_version(c(rep("0.01", 2L))),
row.names = c("RTS_ID", "Count"),
stringsAsFactors = FALSE)
)
)
.dccMetadata[["protocolData"]] <-
do.call(rbind,
unname(head(.dccMetadata[["schema"]], 3L)))[, "labelDescription",
drop = FALSE]
rownames(.dccMetadata[["protocolData"]])[rownames(.dccMetadata[["protocolData"]]) == "ID"] <- "SampleID"
.codeClassMetadata <-
c("CodeClass,IsControl,Analyte",
"Endogenous,FALSE,gx|cnv|fusion",
"Housekeeping,TRUE,gx|fusion",
"Positive,TRUE,general",
"Negative,TRUE,general",
"Binding,TRUE,general",
"Purification,TRUE,general",
"Reserved,TRUE,general",
"SNV_INPUT_CTL,TRUE,SNV",
"SNV_NEG,TRUE,SNV",
"SNV_POS,TRUE,SNV",
"SNV_UDG_CTL,TRUE,SNV",
"SNV_PCR_CTL,TRUE,SNV",
"SNV_REF,FALSE,SNV",
"SNV_VAR,FALSE,SNV",
"PROTEIN,FALSE,protein",
"PROTEIN_NEG,TRUE,protein",
"PROTEIN_CELL_NORM,TRUE,protein",
"Restriction Site,TRUE,CNV",
"Invariant,TRUE,CNV")
.codeClassMetadata <-
utils::read.csv(textConnection(paste0(.codeClassMetadata, collapse = "\n")),
colClasses = c("character", "logical", "character"),
stringsAsFactors = FALSE)
.validDccSchema <-
function(x, fileVersion,
section = c("Header", "Scan_Attributes", "NGS_Processing_Attributes", "Code_Summary"))
{
section <- match.arg(section)
schema <- .dccMetadata[["schema"]][[section]]
expectedNames <- row.names(schema)[schema[,"minVersion"] <= fileVersion]
if (all(expectedNames %in% colnames(x))) {
TRUE
} else {
sprintf("<%s> section must contain %s", section,
paste0("\"", expectedNames, "\"", collapse = ", "))
}
}
.allNA <- function(x) {
all(is.na(x))
}
.allTRUE <- function(x) {
is.logical(x) && !anyNA(x) && all(x)
}
.allFALSE <- function(x) {
is.logical(x) && !anyNA(x) && !any(x)
}
.allZero <- function(x) {
is.numeric(x) && !anyNA(x) && identical(range(x), c(0, 0))
}
.validNonNegativeInteger <- function(x) {
is.integer(x) && !anyNA(x) && min(x) >= 0L
}
.validNonNegativeNumber <- function(x) {
is.numeric(x) && !anyNA(x) && min(x) >= 0
}
.validPositiveNumber <- function(x) {
is.numeric(x) && !anyNA(x) && min(x) > 0
}
####
# Basilisk Environment ####
####
#' get the Python Basilisk environment
#'
#' Defines a conda environment via Basilisk, which is used to convert R objects to Zarr stores.
#'
#' @export
getBasilisk <- function(){
if(!requireNamespace('basilisk'))
stop("Please install basilisk package!: BiocManager::install('basilisk')")
basilisk.packages=c(
"numpy==1.*",
"pandas==1.*",
"anndata==0.8.*",
"h5py==3.*",
"hdf5==1.*",
"natsort==7.*",
"packaging==20.*",
"scipy==1.*",
"sqlite==3.*",
"zarr==2.*",
"numcodecs==0.*",
"tifffile==2024.2.12"
)
basilisk.pip=c(
"ome-zarr==0.2.1"
)
py_env <- basilisk::BasiliskEnvironment(
envname="VoltRon_basilisk_env",
pkgname="VoltRon",
packages=basilisk.packages,
pip=basilisk.pip
)
py_env
}
####
# Other Auxiliary tools ####
####
fill.na <- function(x, i = 5) {
if (is.na(x)[i]) {
return(round(mean(x, na.rm = TRUE), 0))
}
else {
return(round(x[i], 0))
}
}
#' slotApply
#'
#' apply to slots
#'
#' @param x object
#' @param FUN function
#' @param ... arguments passed to \code{FUN}
#'
#' @importFrom methods slot slotNames
#'
slotApply <- function(x,FUN,...){
cl <- class(x)
result <- list()
for(i in methods::slotNames(cl)){
result[[i]] <- FUN(methods::slot(x,i),...)
}
result
}
#' slotToList
#'
#' slot to list
#'
#' @param x object
#'
#' @importFrom methods slot slotNames
#'
slotToList <- function(x){
returnlist <- list()
namesslot <- methods::slotNames(x)
for(cur_slot in namesslot)
returnlist[[cur_slot]] <- methods::slot(x, name = cur_slot)
returnlist
}
ggname <- function(prefix, grob) {
grob$name <- grid::grobName(grob, prefix)
grob
}
quiet <- function(x) {
sink(tempfile())
on.exit(sink())
invisible(force(x))
}
stopQuietly <- function(...) {
blankMsg <- sprintf("\r%s\r", paste(rep(" ", getOption("width")-1L), collapse=" "));
stop(simpleError(blankMsg));
}
#' make_css
#'
#' make_css from \code{tableHTML} package
#'
#' @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.
#' @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.
#'
#' @importFrom shiny HTML
#'
#' @noRd
make_css <- function (..., file = NULL)
{
css_defs <- list(...)
for (x in css_defs) {
if ((!is.list(x)) | (length(x) != 3L)) {
stop("Each element in ... needs to be a list of three elements")
}
if (length(x[[2]]) != length(x[[3]])) {
stop("The second and third elements of each list need to have the same length")
}
}
all_css <- vapply(css_defs, function(x) {
css_comp <- paste0(x[[2]], ": ", x[[3]], ";")
style <- paste(css_comp, collapse = "\n ")
to_be_styled <- paste(x[[1]], collapse = ",\n")
paste0(to_be_styled, " {\n ", style, "\n}\n")
}, FUN.VALUE = character(1))
css_string <- shiny::HTML(paste(all_css, collapse = "\n"))
if (is.null(file)) {
css_string
}
else {
message(css_string, file = file)
invisible(NULL)
}
}
#' Fast creation of dummy variables
#'
#' Quickly create dummy (binary) columns from character and
#' factor type columns in the inputted data (and numeric columns if specified.)
#' This function is useful for statistical analysis when you want binary
#' columns rather than character columns. Adapted from the \code{fastDummies} package (https://jacobkap.github.io/fastDummies/)
#'
#' @param .data
#' An object with the data set you want to make dummy columns from.
#' @param select_columns
#' Vector of column names that you want to create dummy variables from.
#' If NULL (default), uses all character and factor columns.
#' @param remove_first_dummy
#' Removes the first dummy of every variable such that only n-1 dummies remain.
#' This avoids multicollinearity issues in models.
#' @param remove_most_frequent_dummy
#' Removes the most frequently observed category such that only n-1 dummies
#' remain. If there is a tie for most frequent, will remove the first
#' (by alphabetical order) category that is tied for most frequent.
#' @param ignore_na
#' If TRUE, ignores any NA values in the column. If FALSE (default), then it
#' will make a dummy column for value_NA and give a 1 in any row which has a
#' NA value.
#' @param split
#' A string to split a column when multiple categories are in the cell. For
#' example, if a variable is Pets and the rows are "cat", "dog", and "turtle",
#' each of these pets would become its own dummy column. If one row is "cat, dog",
#' then a split value of "," this row would have a value of 1 for both the cat
#' and dog dummy columns.
#' @param remove_selected_columns
#' If TRUE (not default), removes the columns used to generate the dummy columns.
#' @param omit_colname_prefix
#' If TRUE (not default) and `length(select_columns) == 1`, omit pre-pending the
#' name of `select_columns` to the names of the newly generated dummy columns
#'
#' @return
#' A data.frame (or tibble or data.table, depending on input data type) with
#' same number of rows as inputted data and original columns plus the newly
#' created dummy columns.
#'
#' @importFrom data.table as.data.table is.data.table chmatch alloc.col set
#' @importFrom stringr str_sort str_order
#'
dummy_cols <- function(.data, select_columns = NULL, remove_first_dummy = FALSE,
remove_most_frequent_dummy = FALSE, ignore_na = FALSE, split = NULL,
remove_selected_columns = FALSE, omit_colname_prefix = FALSE)
{
stopifnot(is.null(select_columns) || is.character(select_columns),
select_columns != "", is.logical(remove_first_dummy),
length(remove_first_dummy) == 1, is.logical(remove_selected_columns))
if (remove_first_dummy == TRUE & remove_most_frequent_dummy ==
TRUE) {
stop("Select either 'remove_first_dummy' or 'remove_most_frequent_dummy'\n to proceed.")
}
if (is.vector(.data)) {
.data <- data.frame(.data = .data, stringsAsFactors = FALSE)
}
data_type <- check_type(.data)
if (!data.table::is.data.table(.data)) {
.data <- data.table::as.data.table(.data)
}
if (!is.null(select_columns)) {
char_cols <- select_columns
cols_not_in_data <- char_cols[!char_cols %in% names(.data)]
char_cols <- char_cols[!char_cols %in% cols_not_in_data]
if (length(char_cols) == 0) {
stop("select_columns is/are not in data. Please check data and spelling.")
}
}
else if (ncol(.data) == 1) {
char_cols <- names(.data)
}
else {
char_cols <- sapply(.data, class)
char_cols <- char_cols[char_cols %in% c("factor", "character")]
char_cols <- names(char_cols)
}
if (length(char_cols) == 0 && is.null(select_columns)) {
stop("No character or factor columns found. ",
"Please use select_columns to choose columns.")
}
if (!is.null(select_columns) && length(cols_not_in_data) >
0) {
warning("NOTE: The following select_columns input(s) ",
"is not a column in data:\n", names(cols_not_in_data), "\t")
}
for (col_name in char_cols) {
if (is.factor(.data[[col_name]])) {
unique_vals <- levels(.data[[col_name]])
if (any(is.na(.data[[col_name]]))) {
unique_vals <- c(unique_vals, NA)
}
}
else {
unique_vals <- unique(.data[[col_name]])
unique_vals <- stringr::str_sort(unique_vals, na_last = TRUE,
locale = "en_US", numeric = TRUE)
}
unique_vals <- as.character(unique_vals)
if (!is.null(split)) {
unique_vals <- unique(trimws(unlist(strsplit(unique_vals,
split = split))))
}
if (ignore_na) {
unique_vals <- unique_vals[!is.na(unique_vals)]
}
if (remove_most_frequent_dummy) {
vals <- as.character(.data[[col_name]])
vals <- data.frame(sort(table(vals), decreasing = TRUE),
stringsAsFactors = FALSE)
top_vals <- vals[vals$Freq %in% max(vals$Freq), ]
other_vals <- vals$vals[!vals$Freq %in% max(vals$Freq)]
other_vals <- as.character(other_vals)
top_vals <- top_vals[stringr::str_order(top_vals$vals,
na_last = TRUE, locale = "en_US", numeric = TRUE),
]
if (nrow(top_vals) == 1) {
top_vals <- NULL
}
else {
top_vals <- as.character(top_vals$vals[2:nrow(top_vals)])
}
unique_vals <- c(top_vals, other_vals)
unique_vals <- stringr::str_sort(unique_vals, na_last = TRUE,
locale = "en_US", numeric = TRUE)
}
if (remove_first_dummy) {
unique_vals <- unique_vals[-1]
}
data.table::alloc.col(.data, ncol(.data) + length(unique_vals))
.data[, paste0(col_name, "_", unique_vals)] <- 0L
for (unique_value in unique_vals) {
data.table::set(.data, i = which(data.table::chmatch(as.character(.data[[col_name]]),
unique_value, nomatch = 0) == 1L), j = paste0(col_name,
"_", unique_value), value = 1L)
if (!is.na(unique_value)) {
data.table::set(.data, i = which(is.na(.data[[col_name]])),
j = paste0(col_name, "_", unique_value), value = NA)
}
if (!is.null(split)) {
max_split_length <- max(sapply(strsplit(as.character(.data[[col_name]]),
split = split), length))
for (split_length in seq_len(max_split_length)) {
data.table::set(.data, i = which(data.table::chmatch(as.character(trimws(sapply(strsplit(as.character(.data[[col_name]]),
split = split), `[`, split_length))), unique_value,
nomatch = 0) == 1L), j = paste0(col_name,
"_", unique_value), value = 1L)
}
if (is.na(unique_value)) {
.data[[paste0(col_name, "_", unique_value)]][which(!is.na(.data[[col_name]]))] <- 0
}
}
}
}
if (remove_selected_columns) {
.data <- .data[-which(names(.data) %in% char_cols)]
}
.data <- fix_data_type(.data, data_type)
if (omit_colname_prefix) {
if (length(select_columns) == 1) {
new_col_index <- as.logical(rowSums(sapply(unique_vals,
function(x) grepl(paste0(select_columns, "_",
x), names(.data)))))
names(.data)[new_col_index] <- gsub(paste0(select_columns,
"_"), "", names(.data)[new_col_index])
}
else {
message("Can't omit the colname prefix when recoding more than one column.")
message("Returning prefixed dummy columns.")
}
}
return(.data)
}
#' @importFrom data.table is.data.table
#' @noRd
check_type <- function(.data) {
if (data.table::is.data.table(.data)) {
data_type <- "is_data_table"
} else if (inherits(.data, "tbl_df")) {
data_type <- "is_tibble"
} else {
data_type <- "is_data_frame"
}
return(data_type)
}
#' @importFrom dplyr as_tibble
#' @noRd
fix_data_type <- function(.data, data_type) {
if (data_type == "is_data_frame") {
.data <- as.data.frame(.data, stringsAsFactors = FALSE)
} else if (data_type == "is_tibble") {
.data <- dplyr::as_tibble(.data)
}
return(.data)
}
#' CSS string helper
#'
#' Convenience function for building CSS style declarations (i.e. the string
#' that goes into a style attribute, or the parts that go inside curly braces in
#' a full stylesheet).
#'
#' CSS uses `'-'` (minus) as a separator character in property names, but
#' this is an inconvenient character to use in an R function argument name.
#' Instead, you can use `'.'` (period) and/or `'_'` (underscore) as
#' separator characters. For example, `css(font.size = "12px")` yields
#' `"font-size:12px;"`.
#'
#' To mark a property as `!important`, add a `'!'` character to the end
#' of the property name. (Since `'!'` is not normally a character that can be
#' used in an identifier in R, you'll need to put the name in double quotes or
#' backticks.)
#'
#' Argument values will be converted to strings using
#' `paste(collapse = " ")`. Any property with a value of `NULL` or
#' `""` (after paste) will be dropped.
#'
#' @param ... Named style properties, where the name is the property name and
#' the argument is the property value. See Details for conversion rules.
#' @param collapse_ (Note that the parameter name has a trailing underscore
#' character.) Character to use to collapse properties into a single string;
#' likely `""` (the default) for style attributes, and either `"\n"`
#' or `NULL` for style blocks.
#'
#' @importFrom rlang dots_list
#'
#' @noRd
css <- function(..., collapse_ = "") {
props <- rlang::dots_list(...)
if (length(props) == 0) {
return(NULL)
}
if (is.null(names(props)) || any(names(props) == "")) {
stop("cssList expects all arguments to be named")
}
# Necessary to make factors show up as level names, not numbers
props[] <- lapply(props, paste, collapse = " ")
# Drop null args
props <- props[!sapply(props, empty)]
if (length(props) == 0) {
return(NULL)
}
# Translate camelCase, snake_case, and dot.case to kebab-case
# For standard CSS properties only, not CSS variables
is_css_var <- grepl("^--", names(props))
names(props)[!is_css_var] <- standardize_property_names(names(props)[!is_css_var])
# Create "!important" suffix for each property whose name ends with !, then
# remove the ! from the property name
important <- ifelse(grepl("!$", names(props), perl = TRUE), " !important", "")
names(props) <- sub("!$", "", names(props), perl = TRUE)
paste0(names(props), ":", props, important, ";", collapse = collapse_)
}
empty <- function(x) {
length(x) == 0 || (is.character(x) && !any(nzchar(x)))
}
standardize_property_names <- function(x) {
# camelCase to kebab-case
x <- gsub("([A-Z])", "-\\1", x)
x <- tolower(x)
# snake_case and dot.case to kebab-case
gsub("[._]", "-", x)
}
#' @importFrom grDevices hcl
hue_pal <- function(n, h = c(0, 360) + 15, c = 100, l = 65, h.start = 0, direction = 1)
{
if (n == 0) {
stop("Must request at least one colour from a hue palette.")
}
if ((diff(h)%%360) < 1) {
h[2] <- h[2] - 360/n
}
hues <- seq(h[1], h[2], length.out = n)
hues <- (hues + h.start)%%360
hcl <- cbind(hues, c, l)
pal <- apply(hcl, 1, function(x){
grDevices::hcl(x[1], x[2], x[3])
})
if (direction == -1) {
rev(pal)
}
else {
pal
}
}
rescale_numeric <- function(x, to = c(0, 1), from = range(x, na.rm = TRUE, finite = TRUE), ...) {
(x - from[1]) / diff(from) * diff(to) + to[1]
}
as.raster_array <- function (x, max = 1, ...)
{
if (!is.numeric(x)) {
if (is.raw(x)) {
storage.mode(x) <- "integer"
max <- 255L
}
else stop("a raster array must be numeric")
}
if (length(d <- dim(x)) != 3L)
stop("a raster array must have exactly 3 dimensions")
r <- array(if (d[3L] == 3L)
rgb(t(x[, , 1L]), t(x[, , 2L]), t(x[, , 3L]), maxColorValue = max)
else if (d[3L] == 4L)
rgb(t(x[, , 1L]), t(x[, , 2L]), t(x[, , 3L]), t(x[, , 4L]), maxColorValue = max)
else if (d[3L] == 1L)
rgb(t(x[, , 1L]), t(x[, , 1L]), t(x[, , 1L]), maxColorValue = max)
else stop("a raster array must have exactly 1, 3 or 4 planes"),
dim = d[seq_len(2)])
class(r) <- "raster"
r
}
avgHexColor <- function(colors){
rgb(t(Reduce(`+`, lapply(colors, col2rgb))/3), maxColorValue=255)
}
####
## ggedit tools ####
## See https://github.com/yonicd/ggedit/tree/master/R
####
#' @title ggplot2 layer proto extraction
#' @description Extract geom, stat and position protos from a ggplot2 layer
#' @param l ggproto
#' @noRd
proto_features <- function(l) {
a <- sapply(c("position", "geom", "stat"), function(x) {
class(l[[x]])[1]
})
data.frame(t(a), stringsAsFactors = FALSE)
}
# forked from https://github.com/yihui/knitr/blob/master/R/defaults.R
#' @importFrom stats setNames
#' @noRd
new_defaults <- function(value = list()) {
defaults <- value
get <- function(name, default = FALSE, drop = TRUE, regex=FALSE, ...) {
if (default) defaults <- value # this is only a local version
if (missing(name)) {
defaults
} else {
if (drop && length(name) == 1) {
if (regex) {
name_grep <- grep(name, names(defaults), value = TRUE, ...)
stats::setNames(defaults[name_grep], name_grep)
} else {
defaults[[name]]
}
} else {
stats::setNames(defaults[name], name)
}
}
}
set <- function(...) {
dots <- list(...)
if (length(dots) == 0) return()
if (is.null(names(dots)) && length(dots) == 1 && is.list(dots[[1]])) {
if (length(dots <- dots[[1]]) == 0) {
return()
}
}
defaults <<- merge(dots)
invisible(NULL)
}
# merge <- function(values) merge_list(defaults, values)
restore <- function(target = value) defaults <<- target
append <- function(...) {
dots <- list(...)
if (length(dots) == 0) return()
if (is.null(names(dots)) && length(dots) == 1 && is.list(dots[[1]])) {
if (length(dots <- dots[[1]]) == 0) {
return()
}
}
dots <- sapply(names(dots), function(x) dots[[x]] <- c(defaults[[x]], dots[[x]]), simplify = FALSE)
defaults <<- merge(dots)
invisible(NULL)
}
list(get = get, set = set, append = append, merge = merge, restore = restore)
}
#' @title Creates an independent copy of a ggplot layer object
#' @description Creates copies of ggplot layers from within ggplot objects that
#' are independent of the parent object.
#' @details ggplot objects are comprimsed of layer objects. Once compiled they
#' are part of the plot object environment and if they are changed internally
#' regardless of where they are in the (ie different environment) it will change
#' the original plot. This function allows to create replicates of the plot layers
#' and edit them independent of the original plot. When setting verbose to TRUE
#' function returns the ggplot2 call as a string to paste in regular ggplot script
#' to generate the layer.
#' @param l ggplot2 object layer
#' @param verbose toggle to control if the output is ggproto object (verbose==FALSE,default) or string of layer call (verbose==TRUE)
#' @param showDefaults toggle to control if the verbose output shows all the input arguments passed to the proto object (if verbose==FALSE then ignored)
#' @return ggproto or string object (conditional on verbose)
#'
#' @importFrom utils capture.output
#' @importFrom rlang sym '!!'
#' @noRd
cloneLayer <- function (l, verbose = FALSE, showDefaults = TRUE)
{
geom_opts <- ggedit_opts$get("session_geoms")
parent.layer <- dplyr::left_join(proto_features(l), dplyr::filter(geom_opts,
!grepl("^stat", !!rlang::sym("fn"))), by = c("position",
"geom", "stat"))
if (is.na(parent.layer$fn))
parent.layer$fn <- paste0(tolower(strsplit(parent.layer$stat,
"(?<=Stat)", perl = TRUE)[[1]]), collapse = "_")
layer.names <- c("mapping", "data", "geom", "position", "stat",
"show.legend", "inherit.aes", "aes_params", "geom_params",
"stat_params")
x <- sapply(layer.names, function(y) {
b <- l[[y]]
if ("waiver" %in% class(b))
b <- NULL
if (y == "geom")
b <- eval(parse(text = parent.layer$geom))
if (y == "position")
b <- gsub(y, "", tolower(class(b)[1]))
if (y == "stat")
b <- eval(parse(text = parent.layer$stat))
b
})
x$params <- append(x$stat_params, x$geom_params)
x$params <- append(x$params, x$aes_params)
x$params <- x$params[!duplicated(names(x$params))]
x$geom_params <- x$aes_params <- x$stat_params <- NULL
if (verbose) {
nm <- names(x)
nm <- nm[!sapply(x, typeof) %in% c("environment", "closure",
"list")]
geom_aes <- list(geom = parent.layer$fn, mapping = sapply(names(x$mapping),
build_map, y = x$mapping), params = sapply(names(x$params),
build_map, y = x$params), layer = sapply(rev(nm),
build_map, y = x[rev(nm)]), data = paste0("data = ",
paste0(capture.output(dput(x$data)), collapse = "\n")))
strRet <- sprintf("%s(mapping=aes(%s),%s,%s)", paste0(geom_aes$geom,
collapse = ","), paste0(geom_aes$mapping, collapse = ","),
paste0(geom_aes$params, collapse = ","), paste0(geom_aes$layer,
collapse = ","))
if (!showDefaults) {
geom_proto <- cloneProto(eval(parse(text = paste0(geom_aes$geom,
"()"))))
geom_diff <- sapply(names(geom_aes)[-1], function(x) geom_aes[[x]][!geom_aes[[x]] %in%
geom_proto[[x]]])
strRet <- sprintf("%s(aes(%s),%s,%s,%s)", paste0(geom_aes$geom,
collapse = ","), paste0(geom_diff$mapping, collapse = ","),
paste0(geom_diff$params, collapse = ","), paste0(geom_diff$layer,
collapse = ","), geom_aes$data)
}
strRet <- gsub("aes()", "", strRet, fixed = T)
strRet <- gsub("[,]{2,}", ",", strRet)
strRet <- gsub("data=NULL", "", strRet)
strRet <- gsub(",)", ")", strRet)
strRet <- gsub("\\(,", "(", strRet)
strRet
}
else {
do.call(layer, x)
}
}
#' @importFrom dplyr filter left_join
#' @importFrom rlang sym '!!'
#' @noRd
cloneProto <- function(l) {
geom_opts <- ggedit_opts$get("session_geoms")
parent.layer <- proto_features(l) |>
dplyr::left_join(
geom_opts |> dplyr::filter(!grepl("^stat", !!rlang::sym('fn'))),
by = c("position", "geom", "stat")
)
if (is.na(parent.layer$fn)) {
parent.layer$fn <- paste0(tolower(strsplit(parent.layer$stat, "(?<=Stat)", perl = TRUE)[[1]]), collapse = "_")
}
layer.names <- c("mapping", "data", "geom", "position", "stat", "show.legend", "inherit.aes", "aes_params", "geom_params", "stat_params")
x <- sapply(layer.names, function(y) {
b <- l[[y]]
if ("waiver" %in% class(b)) {
b = NULL
}
if (y == "geom") {
b <- eval(parse(text = parent.layer$geom))
}
if (y == "position") {
b <- gsub(y, "", tolower(class(b)[1]))
}
if (y == "stat") {
b <- eval(parse(text = parent.layer$stat))
}
b
})
x$params <- append(x$stat_params, x$geom_params)
x$params <- append(x$params, x$aes_params)
x$params <- x$params[!duplicated(names(x$params))]
x$geom_params <- x$aes_params <- x$stat_params <- NULL
fn <- parent.layer$fn
g <- paste0(fn, "()")
g <- eval(parse(text = g))
nm <- names(x)
nm <- nm[!sapply(x, typeof) %in% c("environment", "closure", "list")]
geom_aes <- list(
geom = fn,
mapping = sapply(names(x$mapping), build_map,y = x$mapping),
params = sapply(names(x$params), build_map, y = x$params),
layer = sapply(rev(nm), build_map, y = x[rev(nm)])
)
nDF <- cbind(names(g$geom$default_aes), paste(g$geom$default_aes))
nDF[grep("colour|fill|color", nDF[, 1]), 2] <- paste0("'", col2hcl(nDF[grep("colour|fill|color", nDF[, 1]), 2], alpha = NULL), "'")
geom_aes$default <- paste0(apply(nDF, 1, function(x) paste0(x, collapse = "=")))
geom_aes
}
#' @title Default and current ggedit options
#'
#' @description Options for functions in the ggedit package. When running R code, the object \code{ggedit_opts}
#' (default options) is not modified by chunk headers (local chunk options are
#' merged with default options), whereas \code{ggedit_opts_current} (current options)
#' changes with different chunk headers and it always reflects the options for
#' the current chunk.
#'
#' Normally we set up the global options once in the first code chunk in a
#' document using \code{ggedit_opts$set()}, so that all \emph{latter} chunks will
#' use these options. Note the global options set in one chunk will not affect
#' the options in this chunk itself, and that is why we often need to set global
#' options in a separate chunk.
#'
#' @note \code{ggedit_opts_current} is read-only in the sense that it does nothing if
#' you call \code{ggedit_opts_current$set()}; you can only query the options via
#' \code{ggedit_opts_current$get()}.
#' @rdname ggeditOpts
#' @noRd
ggedit_opts <- new_defaults(list(
fontDefaults = c(
"sans",
"Canonical",
"mono",
"Courier",
"Helvetica",
"serif",
"Times",
"AvantGarde",
"Bookman",
"Helvetica-Narrow",
"NewCenturySchoolbook",
"Palatino",
"URWGothic",
"URWBookman",
"NimbusMon",
"URWHelvetica",
"NimbusSan",
"NimbusSanCond",
"CenturySch",
"URWPalladio",
"URWTimes",
"NimbusRom"
),
slideDefaults = list(
alpha = c(min = 0, max = 1),
size = c(min = 0, max = 10),
shape = c(min = 1, max = 25),
stroke = c(min = 0, max = 10),
weight = c(min = 0, max = 10),
linetype = c(min = 1, max = 5),
width = c(min = 0, max = 1),
angle = c(min = 0, max = 360),
hjust = c(min = -10, max = 10),
vjust = c(min = -10, max = 10),
stroke = c(min = 0, max = 10),
lineheight = c(min = 0, max = 10),
linewidth = c(min = 0, max = 5),
fontface = c(min = 1, max = 4),
rel_min_height = c(min = 0, max = 1),
scale = c(min = 0, max = 100)
),
themeTips = list(
element_rect = list(
fill = "fill colour",
colour = "border colour",
size = "border size (in pts)",
linetype = paste0(
paste(
seq(0, 6),
c(
"blank", "solid", "dashed", "dotted", "dotdash",
"longdash", "twodash"
), sep = ": "
),
collapse = ", "
)
),
element_line = list(
colour = "line colour",
size = "numeric (in pts) or \n relative to global size rel(numeric)",
linetype = paste0(
paste(
seq(0, 6),
c(
"blank", "solid", "dashed", "dotted", "dotdash",
"longdash", "twodash"
), sep = ": "
),
collapse = ", "
),
lineend = c("butt(default),round,square")
),
element_text = list(
family = shiny::HTML('<a href="http://www.cookbook-r.com/Graphs/Fonts/" target="_blank">font family</a>'),
face = 'font face ("plain", "italic", "bold", "bold.italic")',
colour = "text colour",
size = "text size (in pts)",
hjust = "horizontal justification (in [0, 1])",
vjust = "vertical justification (in [0, 1])",
angle = "angle (in [0, 360])",
lineheight = "numeric line height"
),
justification = list(justification = 'anchor point for positioning legend inside plot <br/> "center" or two-element numeric vector'),
position = list(position = 'the position of legends. <br/> "left", "right", "bottom", "top", or two-element numeric vector')
),
ThemeDefaultClass =
data.frame(
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"),
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
),
session_geoms =
data.frame(
fn = c("annotation_custom", "annotation_logticks",
"annotation_map", "annotation_raster", "geom_abline", "geom_area",
"geom_bar", "geom_bin2d", "geom_blank", "geom_boxplot", "geom_col",
"geom_contour", "geom_count", "geom_crossbar", "geom_curve",
"geom_density", "geom_density_2d", "geom_density2d", "geom_dotplot",
"geom_errorbar", "geom_errorbarh", "geom_freqpoly", "geom_hex",
"geom_histogram", "geom_hline", "geom_jitter", "geom_label",
"geom_line", "geom_linerange", "geom_map", "geom_path", "geom_point",
"geom_pointrange", "geom_polygon", "geom_qq", "geom_qq_line",
"geom_quantile", "geom_raster", "geom_rect", "geom_ribbon", "geom_rug",
"geom_segment", "geom_sf", "geom_smooth", "geom_spoke", "geom_step",
"geom_text", "geom_tile", "geom_violin", "geom_vline", "stat_bin",
"stat_bin_2d", "stat_bin_hex", "stat_bin2d", "stat_binhex", "stat_boxplot",
"stat_contour", "stat_count", "stat_density", "stat_density_2d",
"stat_density2d", "stat_ecdf", "stat_ellipse", "stat_function",
"stat_identity", "stat_qq", "stat_qq_line", "stat_quantile",
"stat_sf", "stat_smooth", "stat_sum", "stat_summary", "stat_summary_2d",
"stat_summary_bin", "stat_summary_hex", "stat_unique", "stat_ydensity"),
geom = c("GeomCustomAnn", "GeomLogticks", "GeomAnnotationMap",
"GeomRasterAnn", "GeomAbline", "GeomArea", "GeomBar", "GeomTile",
"GeomBlank", "GeomBoxplot", "GeomCol", "GeomContour", "GeomPoint",
"GeomCrossbar", "GeomCurve", "GeomDensity", "GeomDensity2d",
"GeomDensity2d", "GeomDotplot", "GeomErrorbar", "GeomErrorbarh",
"GeomPath", "GeomHex", "GeomBar", "GeomHline", "GeomPoint", "GeomLabel",
"GeomLine", "GeomLinerange", "GeomMap", "GeomPath", "GeomPoint",
"GeomPointrange", "GeomPolygon", "GeomPoint", "GeomPath", "GeomQuantile",
"GeomRaster", "GeomRect", "GeomRibbon", "GeomRug", "GeomSegment",
"GeomSf", "GeomSmooth", "GeomSpoke", "GeomStep", "GeomText",
"GeomTile", "GeomViolin", "GeomVline", "GeomBar", "GeomTile",
"GeomHex", "GeomTile", "GeomHex", "GeomBoxplot", "GeomContour",
"GeomBar", "GeomArea", "GeomDensity2d", "GeomDensity2d", "GeomStep",
"GeomPath", "GeomPath", "GeomPoint", "GeomPoint", "GeomPath",
"GeomQuantile", "GeomRect", "GeomSmooth", "GeomPoint", "GeomPointrange",
"GeomTile", "GeomPointrange", "GeomHex", "GeomPoint", "GeomViolin"),
position = c("PositionIdentity", "PositionIdentity", "PositionIdentity",
"PositionIdentity", "PositionIdentity", "PositionStack", "PositionStack",
"PositionIdentity", "PositionIdentity", "PositionDodge2", "PositionStack",
"PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity",
"PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity",
"PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity",
"PositionStack", "PositionIdentity", "PositionJitter", "PositionIdentity",
"PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity",
"PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity",
"PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity",
"PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity",
"PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity",
"PositionIdentity", "PositionDodge", "PositionIdentity", "PositionStack",
"PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity",
"PositionDodge2", "PositionIdentity", "PositionStack", "PositionStack",
"PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity",
"PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity",
"PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity",
"PositionIdentity", "PositionIdentity", "PositionIdentity", "PositionIdentity",
"PositionIdentity", "PositionDodge"),
stat = c("StatIdentity", "StatIdentity", "StatIdentity", "StatIdentity", "StatIdentity",
"StatIdentity", "StatCount", "StatBin2d", "StatIdentity", "StatBoxplot",
"StatIdentity", "StatContour", "StatSum", "StatIdentity", "StatIdentity",
"StatDensity", "StatDensity2d", "StatDensity2d", "StatBindot",
"StatIdentity", "StatIdentity", "StatBin", "StatBinhex", "StatBin",
"StatIdentity", "StatIdentity", "StatIdentity", "StatIdentity",
"StatIdentity", "StatIdentity", "StatIdentity", "StatIdentity",
"StatIdentity", "StatIdentity", "StatQq", "StatQqLine", "StatQuantile",
"StatIdentity", "StatIdentity", "StatIdentity", "StatIdentity",
"StatIdentity", "StatSf", "StatSmooth", "StatIdentity", "StatIdentity",
"StatIdentity", "StatIdentity", "StatYdensity", "StatIdentity",
"StatBin", "StatBin2d", "StatBinhex", "StatBin2d", "StatBinhex",
"StatBoxplot", "StatContour", "StatCount", "StatDensity", "StatDensity2d",
"StatDensity2d", "StatEcdf", "StatEllipse", "StatFunction", "StatIdentity",
"StatQq", "StatQqLine", "StatQuantile", "StatSf", "StatSmooth",
"StatSum", "StatSummary", "StatSummary2d", "StatSummaryBin",
"StatSummaryHex", "StatUnique", "StatYdensity"),
pkg = rep("ggplot2", 77),stringsAsFactors = FALSE)
))
# Function to convert color to HCL and adjust components
#' @importFrom grDevices col2rgb rgb2hsv hcl rgb
col2hcl <- function(colour, h = NULL, c = NULL, l = NULL, alpha = NULL) {
# Convert color to RGB
rgb_col <- grDevices::col2rgb(colour) / 255
# Convert RGB to HSV
hsv_col <- grDevices::rgb2hsv(rgb_col)
# Convert HSV to HCL
hue <- hsv_col[1] * 360 # Convert hue to degrees (0-360)
chroma <- hsv_col[2] * 100 # Chroma is similar to saturation
luminance <- hsv_col[3] * 100 # Luminance is related to value
# Allow user to override H, C, or L values
if (!is.null(h)) hue <- h
if (!is.null(c)) chroma <- c
if (!is.null(l)) luminance <- l
# Create the HCL color with potentially modified components
hcl_col <- grDevices::hcl(h = hue, c = chroma, l = luminance)
# Convert HCL back to RGB to apply alpha
rgb_col_with_alpha <- grDevices::col2rgb(hcl_col) / 255
# Add alpha transparency if provided
if (!is.null(alpha)) {
rgba_col <- grDevices::rgb(rgb_col_with_alpha[1], rgb_col_with_alpha[2], rgb_col_with_alpha[3], alpha = alpha)
} else {
rgba_col <- grDevices::rgb(rgb_col_with_alpha[1], rgb_col_with_alpha[2], rgb_col_with_alpha[3])
}
# Return the final RGBA color
return(rgba_col)
}
#' @importFrom stats as.formula
#' @importFrom rlang quo_name
#' @noRd
build_map <- function(item,y) {
y <- y[[item]]
if (inherits(y,'quosure')){
return(sprintf('%s = %s',item,rlang::quo_name(y)))
}
if (inherits(y,'character')){
return(sprintf("%s = '%s'",item,y))
}
if (inherits(y, "formula")){
return(sprintf("formula=stats::as.formula('%s')",
paste0(as.character(y)[-1], collapse = "~")))
}
if (inherits(y,'NULL')) {
return(sprintf('%s = NULL',item))
}
if (inherits(y, c("function", "call", "ggproto"))) {
return(sprintf("%s = %s",
item,
paste(capture.output(
dput(y)),
collapse = "\n")
))
}
if (inherits(y, c("data.frame"))) {
return(paste0("=", paste(capture.output(dput(y)), collapse = "\n")))
}
return(sprintf('%s = %s',item, y))
}
#' @noRd
capture.output <- function (..., file = NULL, append = FALSE, type = c("output", "message"), split = FALSE)
{
type <- match.arg(type)
rval <- NULL
closeit <- TRUE
if (is.null(file))
file <- textConnection("rval", "w", local = TRUE)
else if (is.character(file))
file <- file(file, if (append)
"a"
else "w")
else if (inherits(file, "connection")) {
if (!isOpen(file))
open(file, if (append)
"a"
else "w")
else closeit <- FALSE
}
else stop("'file' must be NULL, a character string or a connection")
sink(file, type = type, split = split)
on.exit({
sink(type = type, split = split)
if (closeit) close(file)
})
for (i in seq_len(...length())) {
out <- withVisible(...elt(i))
if (out$visible)
print(out$value)
}
on.exit()
sink(type = type, split = split)
if (closeit)
close(file)
if (is.null(rval))
invisible(NULL)
else rval
}
avgHexColor <- function(colors, ctrlcolor){
colors <- lapply(colors, col2rgb)
rgb(t(Reduce(`+`, colors)/length(colors)), maxColorValue=255)
}
fill_na_with_preceding <- function(x) {
if (all(is.na(x))) return(x)
for (i in 2:length(x)) {
if (is.na(x[i])) {
x[i] <- x[i - 1]
}
}
return(x)
}