scales 1.4.0

  scales

  Teun van den Brand

We’re stoked to announce the release of scales 1.4.0. scales is a package that provides much of the scaling logic that is used in ggplot2 to a general framework, along with utility functions for e.g. formatting labels or creating colour palettes.

You can install it from CRAN with:

install.packages("scales")

This blog post will give an overview of the 1.4.0 release, which has some nifty upgrades for working with colours and labels.

You can see a full list of changes in the release notes

Colour manipulation

The alpha() and muted() functions have been part of scales for a long time. Back in the 1.1.0 release we swapped to farver to power these functions. We felt it was appropriate to use this package for other common colour tasks, and so col_shift(), col_lighter(), col_darker(), col_saturate() and col_mix() were born.

my_colours <- c("red", "green", "blue")

m <- rbind(
  original = my_colours,
  shift    = col_shift(my_colours, 90),
  lighter  = col_lighter(my_colours, 20),
  darker   = col_darker(my_colours, 20),
  duller   = col_saturate(my_colours, -50),
  mixed    = col_mix(my_colours, "orchid")
)

show_col(t(m), ncol = ncol(m))
text(x = ncol(m) + 0.25, y = -(1:nrow(m)) + 0.5, rownames(m), adj = 0)

Palettes

Palettes have also been reworked in this release to include some useful properties. Palettes now come in one of two classes: ‘pal_discrete’ or ‘pal_continuous’.

my_palette <- manual_pal(c("palegreen", "deepskyblue", "magenta"))
class(my_palette)
#> [1] "pal_discrete" "scales_pal"   "function"

Having palettes as a class rather than as plain functions, allows us to store useful metadata about the palette which can be used downstream. In addition, most colour palette functions also allow the aforementioned colour manipulation functions to work on the palette output.

palette_type(my_palette)
#> [1] "colour"

palette_nlevels(my_palette)
#> [1] 3

col_shift(my_palette, 180)(3)
#> [1] "#FFC3FF" "#E4A735" "#00B100"

With the new setup it is now possible to expand discrete palettes to continuous palettes with as_continuous_pal() or vise versa to chop up continuous palettes into discrete palettes with as_discrete_pal().

plot(as_continuous_pal(my_palette))

Another quality of life improvement for palettes, is that the ‘scales’ package now keeps track of some named palettes. By default, the collection of ‘known’ palettes is pre-populated with colour palettes from the grDevices, RColorBrewer and viridisLite packages.

as_discrete_pal("Okabe-Ito")(8)
#> [1] "#000000" "#E69F00" "#56B4E9" "#009E73" "#F0E442" "#0072B2" "#D55E00"
#> [8] "#CC79A7"

Providing palettes as package

For those that are interested in developing R packages with palettes, there are a few recommendations we make in ?palette-recommendations to smoothly interface with the scales package.

If your palettes are vectors of colour values, we recommend simply exporting the naked vector.

#' @export
aurora <- c("palegreen", "deepskyblue", "magenta")

That way, they can easily be accessed and used in as_discrete_pal() and as_continuous_pal().

Alternatively, if you have functions that generate colours that is not predefined, we recommend wrapping the function in new_discrete_palette() and new_continuous_palette(). For predefined palettes, you can also use pal_manual() or pal_gradient_n().

pal_random <- function() {
  fun <- function(n) {
    sample(colours(distinct = TRUE), size = n, replace = TRUE)
  }
  new_discrete_palette(fun, type = "colour", nlevels = length(colours()))
}

Populating the metadata in new_discrete_palette()/ new_continuous_palette() helps to make converting between palette types less painful.

as_continuous_pal(pal_random())
as_discrete_pal(pal_random())

Labels

This release also provides improvements to labelling in the form of two new labelling functions and two new convenience functions for labels. In contrast to most of scales’ label functions, these label functions are great for discrete input. First up is label_glue(), which uses the string interpolation from the glue package to format your labels.

label_glue("The {x} penguin")(c("Gentoo", "Chinstrap", "Adelie"))
#> The Gentoo penguin
#> The Chinstrap penguin
#> The Adelie penguin

The other labelling function, label_dictionary(), is convenient when some variable you use consists of short-codes or abbreviations. You can provide label_dictionary() with a named vector that translates the values to prettier labels. If one or more of your values doesn’t exist in the dictionary, they stay as-is by default.

dict <- c(
  diy = "Do it yourself", eta = "Estimated time of arrival",
  asap = "As soon as possible", tldr = "Too long; didn't read"
)
label_dictionary(dict)(c("diy", "tldr", "bff"))
#> [1] "Do it yourself"        "Too long; didn't read" "bff"

compose_label() is a useful convenience function we’ve added which will help you to create custom labelling behaviour without needing to write a labelling function from scratch. Similar to compose_trans(), it allows you to chain together different labelling functions.

screaming_flowers <- compose_label(label_glue("The {x} flower"), toupper)
screaming_flowers(c("daffodil", "orchid", "tulip"))
#> THE DAFFODIL FLOWER
#> THE ORCHID FLOWER
#> THE TULIP FLOWER

Lastly, we haven’t completely forgotton about numeric labels either. We have introduced the number_options() functions to globally populate defaults for functions such as label_number() and label_currency(). This can be convenient if you produce statistical reports in non-English languages.

number_options(
  decimal.mark = ",",
  big.mark = ".",
  style_negative = "minus",
  currency.prefix = "",
  currency.suffix = "€",
  currency.decimal.mark = ",",
  currency.big.mark = " ",
  ordinal.rules = ordinal_french()
)

label_currency(accuracy = 0.01)(c(0.1, 10, 1000000, -1000))
#> [1] "0,10€"         "10,00€"        "1 000 000,00€" "-1 000,00€"

label_ordinal()(1:4)
#> [1] "1er" "2e"  "3e"  "4e"

Acknowledgements

We’d like to thank all people who have contributed in some way, whether it was filing issues, participating in discussion or contributing to code and documentation:

@Aariq, @Aehmlo, @Ali-Hudson, @cb12991, @colindouglas, @d-morrison, @davidhodge931, @EricMarcon, @kellijohnson-NOAA, @kmcd39, @lz1nwm, @mine-cetinkaya-rundel, @mjskay, @Moohan, @muschellij2, @ppreshant, @rawktheuniversemon, @rogiersbart, @SchmidtPaul, @teunbrand, and @thomasp85.