|
| 1 | +--- |
| 2 | +output: hugodown::hugo_document |
| 3 | + |
| 4 | +slug: scales-1-4-0 |
| 5 | +title: scales 1.4.0 |
| 6 | +date: 2025-04-23 |
| 7 | +author: Teun van den Brand |
| 8 | +description: > |
| 9 | + The new 1.4.0 release of the scales package adds some colourful updates. |
| 10 | + Read about colour manipulation, palettes and new label functions. |
| 11 | +
|
| 12 | +photo: |
| 13 | + url: https://unsplash.com/photos/a-close-up-of-a-person-holding-a-paintbrush-Xrelr7cTYm4 |
| 14 | + author: Jennie Razumnaya |
| 15 | + |
| 16 | +# one of: "deep-dive", "learn", "package", "programming", "roundup", or "other" |
| 17 | +categories: [package] |
| 18 | +tags: [scales] |
| 19 | +--- |
| 20 | + |
| 21 | +<!-- |
| 22 | +TODO: |
| 23 | +* [x] Look over / edit the post's title in the yaml |
| 24 | +* [x] Edit (or delete) the description; note this appears in the Twitter card |
| 25 | +* [x] Pick category and tags (see existing with `hugodown::tidy_show_meta()`) |
| 26 | +* [x] Find photo & update yaml metadata |
| 27 | +* [x] Create `thumbnail-sq.jpg`; height and width should be equal |
| 28 | +* [x] Create `thumbnail-wd.jpg`; width should be >5x height |
| 29 | +* [x] `hugodown::use_tidy_thumbnails()` |
| 30 | +* [x] Add intro sentence, e.g. the standard tagline for the package |
| 31 | +* [x] `usethis::use_tidy_thanks()` |
| 32 | +--> |
| 33 | + |
| 34 | +We're stoked to announce the release of [scales]({https://scales.r-lib.org/}) 1.4.0. |
| 35 | +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. |
| 36 | + |
| 37 | +You can install it from CRAN with: |
| 38 | + |
| 39 | +```{r, eval = FALSE} |
| 40 | +install.packages("scales") |
| 41 | +``` |
| 42 | + |
| 43 | +This blog post will give an overview of the 1.4.0 release, which has some nifty upgrades for working with colours and labels. |
| 44 | + |
| 45 | +You can see a full list of changes in the [release notes](https://scales.r-lib.org/news/index.html) |
| 46 | + |
| 47 | +```{r setup} |
| 48 | +library(scales) |
| 49 | +``` |
| 50 | + |
| 51 | +```{r ragg, include=FALSE} |
| 52 | +knitr::opts_chunk$set(dev = "ragg_png") |
| 53 | +``` |
| 54 | + |
| 55 | +## Colour manipulation |
| 56 | + |
| 57 | +The `alpha()` and `muted()` functions have been part of scales for a long time. |
| 58 | +Back in the 1.1.0 release we swapped to [farver](https://farver.data-imaginist.com/) to power these functions. |
| 59 | +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. |
| 60 | + |
| 61 | +```{r} |
| 62 | +my_colours <- c("red", "green", "blue") |
| 63 | +
|
| 64 | +m <- rbind( |
| 65 | + original = my_colours, |
| 66 | + shift = col_shift(my_colours, 90), |
| 67 | + lighter = col_lighter(my_colours, 20), |
| 68 | + darker = col_darker(my_colours, 20), |
| 69 | + duller = col_saturate(my_colours, -50), |
| 70 | + mixed = col_mix(my_colours, "orchid") |
| 71 | +) |
| 72 | +
|
| 73 | +show_col(t(m), ncol = ncol(m)) |
| 74 | +text(x = ncol(m) + 0.25, y = -(1:nrow(m)) + 0.5, rownames(m), adj = 0) |
| 75 | +``` |
| 76 | + |
| 77 | +## Palettes |
| 78 | + |
| 79 | +Palettes have also been reworked in this release to include some useful properties. |
| 80 | +Palettes now come in one of two classes: 'pal_discrete' or 'pal_continuous'. |
| 81 | + |
| 82 | +```{r} |
| 83 | +my_palette <- manual_pal(c("palegreen", "deepskyblue", "magenta")) |
| 84 | +class(my_palette) |
| 85 | +``` |
| 86 | + |
| 87 | +Having palettes as a class rather than as plain functions, allows us to store useful metadata about the palette which can be used downstream. |
| 88 | +In addition, most colour palette functions also allow the aforementioned colour manipulation functions to work on the palette output. |
| 89 | + |
| 90 | +```{r} |
| 91 | +palette_type(my_palette) |
| 92 | +
|
| 93 | +palette_nlevels(my_palette) |
| 94 | +
|
| 95 | +col_shift(my_palette, 180)(3) |
| 96 | +``` |
| 97 | + |
| 98 | +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()`. |
| 99 | + |
| 100 | +```{r} |
| 101 | +plot(as_continuous_pal(my_palette)) |
| 102 | +``` |
| 103 | + |
| 104 | +Another quality of life improvement for palettes, is that the 'scales' package now keeps track of some named palettes. |
| 105 | +By default, the collection of 'known' palettes is pre-populated with colour palettes from the grDevices, RColorBrewer and viridisLite packages. |
| 106 | + |
| 107 | + |
| 108 | +```{r} |
| 109 | +as_discrete_pal("Okabe-Ito")(8) |
| 110 | +``` |
| 111 | + |
| 112 | +### Providing palettes as package |
| 113 | + |
| 114 | +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. |
| 115 | + |
| 116 | +If your palettes are vectors of colour values, we recommend simply exporting the naked vector. |
| 117 | + |
| 118 | +```{r} |
| 119 | +#' @export |
| 120 | +aurora <- c("palegreen", "deepskyblue", "magenta") |
| 121 | +``` |
| 122 | + |
| 123 | +That way, they can easily be accessed and used in `as_discrete_pal()` and `as_continuous_pal()`. |
| 124 | + |
| 125 | +```{r, results='hide'} |
| 126 | +as_continuous_pal(aurora) |
| 127 | +as_discrete_pal(aurora) |
| 128 | +``` |
| 129 | + |
| 130 | +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()`. |
| 131 | + |
| 132 | +```{r} |
| 133 | +pal_random <- function() { |
| 134 | + fun <- function(n) { |
| 135 | + sample(colours(distinct = TRUE), size = n, replace = TRUE) |
| 136 | + } |
| 137 | + new_discrete_palette(fun, type = "colour", nlevels = length(colours())) |
| 138 | +} |
| 139 | +``` |
| 140 | + |
| 141 | +Populating the metadata in `new_discrete_palette()`/`new_continuous_palette()` helps to make converting between palette types less painful. |
| 142 | + |
| 143 | +```{r, results='hide'} |
| 144 | +as_continuous_pal(pal_random()) |
| 145 | +as_discrete_pal(pal_random()) |
| 146 | +``` |
| 147 | + |
| 148 | + |
| 149 | + |
| 150 | +## Labels |
| 151 | + |
| 152 | +This release also provides improvements to labelling in the form of two new labelling functions and two new convenience functions for labels. |
| 153 | +In contrast to most of scales' label functions, these label functions are great for discrete input. |
| 154 | +First up is `label_glue()`, which uses the string interpolation from the glue package to format your labels. |
| 155 | + |
| 156 | +```{r} |
| 157 | +label_glue("The {x} penguin")(c("Gentoo", "Chinstrap", "Adelie")) |
| 158 | +``` |
| 159 | + |
| 160 | +The other labelling function, `label_dictionary()`, is convenient when some variable you use consists of short-codes or abbreviations. |
| 161 | +You can provide `label_dictionary()` with a named vector that translates the values to prettier labels. |
| 162 | +If one or more of your values doesn't exist in the dictionary, they stay as-is by default. |
| 163 | + |
| 164 | +```{r} |
| 165 | +dict <- c( |
| 166 | + diy = "Do it yourself", eta = "Estimated time of arrival", |
| 167 | + asap = "As soon as possible", tldr = "Too long; didn't read" |
| 168 | +) |
| 169 | +label_dictionary(dict)(c("diy", "tldr", "bff")) |
| 170 | +``` |
| 171 | + |
| 172 | +`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. |
| 173 | +Similar to `compose_trans()`, it allows you to chain together different labelling functions. |
| 174 | + |
| 175 | +```{r} |
| 176 | +screaming_flowers <- compose_label(label_glue("The {x} flower"), toupper) |
| 177 | +screaming_flowers(c("daffodil", "orchid", "tulip")) |
| 178 | +``` |
| 179 | +Lastly, we haven't completely forgotton about numeric labels either. |
| 180 | +We have introduced the `number_options()` functions to globally populate defaults for functions such as `label_number()` and `label_currency()`. |
| 181 | +This can be convenient if you produce statistical reports in non-English languages. |
| 182 | + |
| 183 | +```{r} |
| 184 | +number_options( |
| 185 | + decimal.mark = ",", |
| 186 | + big.mark = ".", |
| 187 | + style_negative = "minus", |
| 188 | + currency.prefix = "", |
| 189 | + currency.suffix = "€", |
| 190 | + currency.decimal.mark = ",", |
| 191 | + currency.big.mark = " ", |
| 192 | + ordinal.rules = ordinal_french() |
| 193 | +) |
| 194 | +
|
| 195 | +label_currency(accuracy = 0.01)(c(0.1, 10, 1000000, -1000)) |
| 196 | +
|
| 197 | +label_ordinal()(1:4) |
| 198 | +``` |
| 199 | + |
| 200 | + |
| 201 | + |
| 202 | +## Acknowledgements |
| 203 | + |
| 204 | +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: |
| 205 | + |
| 206 | +[@Aariq](https://github.com/Aariq), [@Aehmlo](https://github.com/Aehmlo), [@Ali-Hudson](https://github.com/Ali-Hudson), [@cb12991](https://github.com/cb12991), [@colindouglas](https://github.com/colindouglas), [@d-morrison](https://github.com/d-morrison), [@davidhodge931](https://github.com/davidhodge931), [@EricMarcon](https://github.com/EricMarcon), [@kellijohnson-NOAA](https://github.com/kellijohnson-NOAA), [@kmcd39](https://github.com/kmcd39), [@lz1nwm](https://github.com/lz1nwm), [@mine-cetinkaya-rundel](https://github.com/mine-cetinkaya-rundel), [@mjskay](https://github.com/mjskay), [@Moohan](https://github.com/Moohan), [@muschellij2](https://github.com/muschellij2), [@ppreshant](https://github.com/ppreshant), [@rawktheuniversemon](https://github.com/rawktheuniversemon), [@rogiersbart](https://github.com/rogiersbart), [@SchmidtPaul](https://github.com/SchmidtPaul), [@teunbrand](https://github.com/teunbrand), and [@thomasp85](https://github.com/thomasp85). |
| 207 | + |
| 208 | + |
0 commit comments