Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manual palette colours #325

Merged
merged 5 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ New features:
- `tinyplot(..., file = "*.pdf")` will now default to using `cairo_pdf()` if
cairo graphics are supported on the user's machine. This should help to ensure
better fidelity of (non-standard) fonts in PDFs. (#311 @grantcdermott)
- The palette argument now accepts a vector or list of manual colours, e.g.
`tinyplot(..., palette = c("cyan4", "hotpink, "purple4"))`, or
`tinytheme("clean", palette = c("cyan4", "hotpink, "purple4"))` (#325 @grantmcdermott)

Bugs fixes:

Expand Down
88 changes: 68 additions & 20 deletions R/by_aesthetics.R
Original file line number Diff line number Diff line change
Expand Up @@ -69,39 +69,87 @@ by_col = function(ngrps = 1L, col = NULL, palette = NULL, gradient = NULL, order
}
} else {
if (is.character(palette)) {
fx = function(x) tolower(gsub("[-, _, \\,, (, ), \\ , \\.]", "", x))
pal_match = charmatch(fx(palette), fx(palette.pals()))
if (!is.na(pal_match)) {
if (pal_match < 1L) stop("'palette' is ambiguous")
palette_fun = palette.colors
if (isTRUE(gradient)) {
palette_fun2 = function(n, palette, alpha) colorRampPalette(palette.colors(palette = palette, alpha = alpha))(n)
palette_fun = palette_fun2
# special case: if vector of character strings, we assume that the user
# must have passed a vector of colours (e.g., c("red", "blue")) rather
# than a known/named colour palette (e.g. "Harmonic")
if (length(palette) > 1) {
palette_fun = "c"
if (!is.null(alpha)) palette = adjustcolor(palette, alpha.f = alpha)
args = as.list(palette)
if (length(args) < ngrps && length(args) != 1) {
# if manual colours < ngrps, either (1) interpolate for gradient
# colors, or (2) recycle for discrete colours
if (gradient) {
args = list(colorRampPalette(args, alpha = TRUE)(ngrps))
} else {
ncolsstr = paste0("(", length(args), ")")
ngrpsstr = paste0("(", ngrps, ")")
warning(
"\nFewer colours ", ncolsstr, " provided than than there are groups ",
ngrpsstr, ". Recycling to make up the shortfall."
)
args = rep(args, length.out = ngrps)
}
}
} else {
pal_match = charmatch(fx(palette), fx(hcl.pals()))
fx = function(x) tolower(gsub("[-, _, \\,, (, ), \\ , \\.]", "", x))
pal_match = charmatch(fx(palette), fx(palette.pals()))
if (!is.na(pal_match)) {
if (pal_match < 1L) stop("'palette' is ambiguous")
palette_fun = hcl.colors
palette_fun = palette.colors
if (isTRUE(gradient)) {
palette_fun2 = function(n, palette, alpha) colorRampPalette(palette.colors(palette = palette, alpha = alpha))(n)
palette_fun = palette_fun2
}
} else {
stop(
"\nPalette string not recogized. Must be a value produced by either",
"`palette.pals()` or `hcl.pals()`.\n",
call. = FALSE
)
pal_match = charmatch(fx(palette), fx(hcl.pals()))
if (!is.na(pal_match)) {
if (pal_match < 1L) stop("'palette' is ambiguous")
palette_fun = hcl.colors
} else {
stop(
"\nPalette string not recogized. Must be a value produced by either",
"`palette.pals()` or `hcl.pals()`.\n",
call. = FALSE
)
}
}
args = list(n = ngrps, palette = palette, alpha = alpha)
}
args = list(n = ngrps, palette = palette, alpha = alpha)
} else if (class(palette) %in% c("call", "name")) {
args = as.list(palette)
palette_fun = paste(args[[1]])
args[[1]] = NULL
args[["n"]] = ngrps
# remove unnamed arguments to prevent unintentional argument sliding
if (any(names(args) == "")) args[[which(names(args) == "")]] = NULL
# catch for direct vector or list
if (palette_fun %in% c("c", "list")) {
if (palette_fun == "list") palette_fun = "c"
if (!is.null(alpha)) args = lapply(args, function(a) adjustcolor(a, alpha.f = alpha))
if (length(args) < ngrps && length(args) != 1) {
# if manual colours < ngrps, either (1) interpolate for gradient
# colors, or (2) recycle for discrete colours
if (gradient) {
args = list(colorRampPalette(args, alpha = TRUE)(ngrps))
} else {
ncolsstr = paste0("(", length(args), ")")
ngrpsstr = paste0("(", ngrps, ")")
warning(
"\nFewer colours ", ncolsstr, " provided than than there are groups ",
ngrpsstr, ". Recycling to make up the shortfall."
)
args = rep(args, length.out = ngrps)
}
}
} else {
args[["n"]] = ngrps
# remove unnamed arguments to prevent unintentional argument sliding
if (any(names(args) == "")) args[[which(names(args) == "")]] = NULL
}
} else if (inherits(palette, "function")) {
args = list()
palette_fun = palette
} else {
stop(
"\nInvalid palette argument. Must be a recognized keyword, or a",
"\nInvalid palette argument. Must be a recognized keyword, or a ",
"palette-generating function with named arguments.\n"
)
}
Expand Down
4 changes: 4 additions & 0 deletions R/tinyplot.R
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@
#' unnamed arguments will be ignored and the key `n` argument, denoting the
#' number of colours, will automatically be spliced in as the number of
#' groups.
#' - A vector or list of colours, e.g. `c("darkorange", "purple", "cyan4")`.
#' If too few colours are provided for a discrete (qualitative) set of
#' groups, then the colours will be recycled with a warning. For continuous
#' (sequential) groups, a gradient palette will be interpolated.
#' @param legend one of the following options:
#' - NULL (default), in which case the legend will be determined by the
#' grouping variable. If there is no group variable (i.e., `by` is NULL) then
Expand Down
Loading