├── .Rbuildignore ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .github ├── .gitignore └── workflows │ └── R-CMD-check.yaml ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── easings.R └── seq-color.R ├── README.Rmd ├── README.md └── man ├── figures ├── .gitignore ├── README-example-1.png ├── README-example-2.png ├── README-example-3.png ├── README-unnamed-chunk-2-1.png ├── README-unnamed-chunk-3-1.png ├── README-unnamed-chunk-4-1.png └── anim3.gif ├── seq_color.Rd └── seq_ease.Rd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^README.Rmd$ 4 | ^README.md$ 5 | ^LICENSE.md$ 6 | ^.devcontainer$ 7 | ^man/figures$ 8 | ^\.github$ 9 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # Pre-built Dev Container Image for R. More info: https://github.com/rocker-org/devcontainer-images/pkgs/container/devcontainer%2Ftidyverse 2 | # Available R version: 4, 4.1, 4.0 3 | ARG VARIANT="4.2" 4 | FROM ghcr.io/rocker-org/devcontainer/tidyverse:${VARIANT} 5 | 6 | RUN install2.r --error --skipinstalled -n -1 \ 7 | statip \ 8 | patchwork \ 9 | paletteer \ 10 | here \ 11 | doParallel \ 12 | janitor \ 13 | vip \ 14 | ranger \ 15 | palmerpenguins \ 16 | skimr \ 17 | nnet \ 18 | kernlab \ 19 | plotly \ 20 | factoextra \ 21 | cluster \ 22 | tidymodels \ 23 | markdown \ 24 | ottr \ 25 | && rm -rf /tmp/downloaded_packages \ 26 | && R -q -e 'remotes::install_github("https://github.com/dcomtois/summarytools/tree/0-8-9")' 27 | 28 | # Install Python packages 29 | COPY requirements.txt /tmp/pip-tmp/ 30 | RUN python3 -m pip --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt 31 | 32 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "R Data Science Environment", 3 | "build": { 4 | "dockerfile": "Dockerfile", 5 | // Update VARIANT to pick a specific R version: 4, 4.1, 4.0 6 | // More info: https://github.com/rocker-org/devcontainer-images/pkgs/container/devcontainer%2Ftidyverse 7 | "args": { "VARIANT": "4" } 8 | }, 9 | 10 | // Install Dev Container Features. More info: https://containers.dev/features 11 | "features": { 12 | "ghcr.io/rocker-org/devcontainer-features/quarto-cli:1": {}, 13 | // Install JupyterLab and IRkernel. 14 | // More info: https://github.com/rocker-org/devcontainer-templates/tree/main/src/r-ver 15 | "ghcr.io/rocker-org/devcontainer-features/r-rig:1": { 16 | "version": "none", 17 | "installJupyterlab": true 18 | } 19 | }, 20 | 21 | "customizations": { 22 | "vscode": { 23 | "extensions": [ 24 | // Add Jupyter and Python vscode extensions 25 | "ms-toolsai.jupyter", 26 | "ms-toolsai.jupyter-renderers", 27 | "ms-python.python", 28 | "ms-python.vscode-pylance", 29 | "vsls-contrib.codetour", 30 | "GitHub.copilot" 31 | ] 32 | } 33 | }, 34 | 35 | // Forward Jupyter and RStudio ports 36 | "forwardPorts": [8787, 8888], 37 | "portsAttributes": { 38 | "8787": { 39 | "label": "Rstudio", 40 | "requireLocalPort": true, 41 | "onAutoForward": "ignore" 42 | }, 43 | "8888": { 44 | "label": "Jupyter", 45 | "requireLocalPort": true, 46 | "onAutoForward": "ignore" 47 | } 48 | }, 49 | 50 | // Use 'postAttachCommand' to run commands after the container is started. 51 | "postAttachCommand": "sudo rstudio-server start" 52 | 53 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root 54 | // "remoteUser": "root" 55 | } 56 | 57 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | 8 | name: R-CMD-check.yaml 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | R-CMD-check: 14 | runs-on: ${{ matrix.config.os }} 15 | 16 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | config: 22 | - {os: macos-latest, r: 'release'} 23 | - {os: windows-latest, r: 'release'} 24 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 25 | - {os: ubuntu-latest, r: 'release'} 26 | - {os: ubuntu-latest, r: 'oldrel-1'} 27 | 28 | env: 29 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 30 | R_KEEP_PKG_SOURCE: yes 31 | 32 | steps: 33 | - uses: actions/checkout@v4 34 | 35 | - uses: r-lib/actions/setup-pandoc@v2 36 | 37 | - uses: r-lib/actions/setup-r@v2 38 | with: 39 | r-version: ${{ matrix.config.r }} 40 | http-user-agent: ${{ matrix.config.http-user-agent }} 41 | use-public-rspm: true 42 | 43 | - uses: r-lib/actions/setup-r-dependencies@v2 44 | with: 45 | extra-packages: any::rcmdcheck 46 | needs: check 47 | 48 | - uses: r-lib/actions/check-r-package@v2 49 | with: 50 | upload-snapshots: true 51 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .Rhistory 3 | *.Rproj 4 | .Rproj.user 5 | *.swp 6 | *.txt 7 | inst/doc 8 | doc 9 | Meta 10 | working 11 | pkgdown 12 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: displease 2 | Type: Package 3 | Title: Numeric and Color Sequences with Non-Linear Interpolation 4 | Version: 1.0.0 5 | Authors@R: c( 6 | person("Mike", "Cheng", role = c("aut", "cre", 'cph'), 7 | email = "mikefc@coolbutuseless.com")) 8 | Maintainer: Mike Cheng 9 | Description: When visualising changes between two values over time, a strict 10 | linear interpolation can look jarring and unnatural. By applying a non-linear 11 | easing to the transition, the motion between values can appear smoother and 12 | more natural. This package includes functions for applying such non-linear 13 | easings to colors and numeric values, and is useful where smooth animated movement 14 | and transitions are desired. 15 | License: MIT + file LICENSE 16 | Encoding: UTF-8 17 | RoxygenNote: 7.3.2 18 | URL: https://github.com/coolbutuseless/displease 19 | BugReports: https://github.com/coolbutuseless/displease/issues 20 | Suggests: 21 | farver 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2022-2024 2 | COPYRIGHT HOLDER: Mike Cheng 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 mikefc@coolbutuseless.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(seq_color) 4 | export(seq_ease) 5 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | 2 | # displease 1.0.0 3 | 4 | * Initial public release 5 | -------------------------------------------------------------------------------- /R/easings.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | #' Create a sequence interpolating between two values with the specified 5 | #' non-linear easing. 6 | #' 7 | #' @param x1,x2 The start and end values of the sequence. Default: 0, 1 8 | #' @param n Number of steps for the transition (including the endpoints) 9 | #' @param type Type of motion easing. Default: 'cubic'. Valid values are 10 | #' are 'sine', 'quad', 'cubic', 'quart', 'quint', 'exp', 'circle', 'back', 11 | #' 'elastic', 'linear'. 12 | #' @param direction When should the easing apply? Default: "in-out". 13 | #' Valid values are 'in', 'out', in-out'. Default: 'in-out' 14 | #' 15 | #' @return Numeric vector of length \code{n} 16 | #' @examples 17 | #' x <- seq_ease(x1 = 0, x2 = 1, n = 20, type = 'cubic', direction = 'in-out') 18 | #' x 19 | #' plot(x) 20 | #' @export 21 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 22 | seq_ease <- function(x1 = 0, x2 = 1, n = 100, type = 'cubic', direction = 'in-out') { 23 | loc <- seq(0, 1, length.out = n) 24 | 25 | if (type == 'linear') { 26 | return(loc) 27 | } 28 | 29 | stopifnot(direction %in% c('in', 'out', 'in-out')) 30 | type <- paste0(type, '-', direction) 31 | 32 | fac <- switch( 33 | type, 34 | 35 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 36 | # Sine 37 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 38 | `sine-in` = 1 - cos((loc * pi) / 2), 39 | `sine-out` = sin((loc * pi) / 2), 40 | `sine-in-out` = -(cos(loc * pi) - 1) / 2, 41 | 42 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 43 | # Quad 44 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 45 | `quad-in` = loc^2, 46 | `quad-out` = 1 - (1 - loc)^2, 47 | `quad-in-out` = ifelse(loc < 0.5, 48 | 2 * loc^2, 49 | 1 - 0.5 * (-2 * loc + 2)^2), 50 | 51 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 52 | # Cubic 53 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 54 | `cubic-in` = loc^3, 55 | `cubic-out` = 1 - (1 - loc)^3, 56 | `cubic-in-out` = ifelse(loc < 0.5, 57 | 4 * loc^3, 58 | 1 - 0.5 * (-2 * loc + 2)^3), 59 | 60 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 61 | # Quart 62 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 63 | `quart-in` = loc^4, 64 | `quart-out` = 1 - (1 - loc)^4, 65 | `quart-in-out` = ifelse(loc < 0.5, 66 | 8 * loc^4, 67 | 1 - 0.5 * (-2 * loc + 2)^4), 68 | 69 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 70 | # Quint 71 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 72 | `quint-in` = loc^5, 73 | `quint-out` = 1 - (1 - loc)^5, 74 | `quint-in-out` = ifelse(loc < 0.5, 75 | 16 * loc^5, 76 | 1 - 0.5 * (-2 * loc + 2)^5), 77 | 78 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 79 | # Exp 80 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 81 | `exp-in` = 2^(10 * loc - 10), 82 | `exp-out` = 1 - 2^(-10 * loc), 83 | `exp-in-out` = ifelse(loc == 0, 0, 84 | ifelse(loc == 1, 1, 85 | ifelse(loc < 0.5, 86 | 2 ^ (20 * loc - 10)/2, 87 | (2 - 2^(-20 * loc + 10)) / 2 88 | ) 89 | ) 90 | ), 91 | 92 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 93 | # Circle 94 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 95 | `circle-in` = 1 - sqrt(1 - loc^2), 96 | `circle-out` = sqrt(1 - (loc - 1)^2), 97 | `circle-in-out` = suppressWarnings({ 98 | ifelse(loc < 0.5, 99 | (1 - sqrt(1 - (2 * loc)^2)) / 2, 100 | 0.5 * (sqrt(1 - (-2 * loc + 2)^2) + 1)) 101 | }), 102 | 103 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 104 | # Back 105 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 106 | `back-in` = 2.70158 * loc^3 - 1.70158 * loc^2, 107 | `back-out` = 1 + 2.70158 * (loc - 1)^3 + 1.70158 * (loc - 1)^2, 108 | `back-in-out` = { 109 | c1 <- 1.70158 110 | c2 <- c1 * 1.525 111 | ifelse(loc < 0.5, 112 | (2*loc)^2 * ((c2 + 1) * 2 * loc - c2) / 2, 113 | ((2*loc-2)^2 * ((c2 + 1) * (loc * 2 - 2) + c2) + 2) / 2 114 | ) 115 | }, 116 | 117 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 118 | # Elastic 119 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 120 | `elastic-in` = ifelse( 121 | loc == 0, 0, 122 | ifelse(loc == 1, 123 | 1, 124 | -(2 ^ (10*loc-10)) * sin((loc * 10 - 10.75) * 2 * pi / 3) 125 | ) 126 | ), 127 | `elastic-out` = ifelse( 128 | loc == 0, 0, 129 | ifelse(loc == 1, 130 | 1, 131 | 2^(-10*loc) * sin((loc * 10 - 0.75) * 2 * pi / 3) + 1) 132 | ), 133 | `elastic-in-out` = ifelse( 134 | loc == 0, 0, 135 | ifelse(loc == 1, 1, 136 | ifelse(loc < 0.5, 137 | -(2^( 20*loc - 10) * sin((20 * loc - 11.125) * 2 * pi/4.5)) / 2, 138 | (2^(-20*loc + 10) * sin((20 * loc - 11.125) * 2 * pi/4.5)) / 2 + 1 139 | )) 140 | ), 141 | 142 | 143 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 144 | # Ooops! 145 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 146 | stop("No such ease type: ", type) 147 | ) 148 | 149 | x1 + fac * (x2 - x1) 150 | } 151 | -------------------------------------------------------------------------------- /R/seq-color.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | #' Interpolate (non-linearly) between two colors 5 | #' 6 | #' @inheritParams seq_ease 7 | #' @param col1,col2 the two colors to interpolate between 8 | #' @param colorspace Color space in which to do the interpolation. Default: 'hcl' 9 | #' Can be any colorspace understood by the \code{farver} package i.e. 10 | #' "cmy", "cmyk", "hsl", "hsb", "hsv", "lab" (CIE L*ab), "hunterlab" 11 | #' (Hunter Lab), "oklab", "lch" (CIE Lch(ab) / polarLAB), "luv", "rgb" 12 | #' (sRGB), "xyz", "yxy" (CIE xyY), "hcl" (CIE Lch(uv) / polarLuv), 13 | #' or "oklch" (Polar form of oklab). 14 | #' Note: Not all color spaces make sense for interpolation. 15 | #' @return character vector containing a color sequence 16 | #' @examplesIf requireNamespace('farver', quietly = TRUE) 17 | #' n <- 20 18 | #' cols <- seq_color('red', 'blue', n = n, direction = 'in-out', colorspace = 'hcl') 19 | #' cols 20 | #' grid::grid.rect(x = seq(0, 0.95, length.out = n), width = 0.1, 21 | #' gp = grid::gpar(fill = cols, col = NA)) 22 | #' @export 23 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 24 | seq_color <- function(col1, col2, n = 100, type = 'cubic', direction = 'in-out', 25 | colorspace = 'hcl') { 26 | 27 | if (requireNamespace('farver', quietly = TRUE)) { 28 | # Original RGB color space 29 | rgb <- farver::decode_colour(c(col1, col2)) 30 | 31 | # Converted color space 32 | cs <- farver::convert_colour(rgb, from = 'rgb', to = colorspace) 33 | 34 | # Interpolate between color 1 and 2 35 | columns <- lapply( 36 | seq_len(ncol(cs)), 37 | function(idx) { 38 | seq_ease(cs[1, idx], cs[2, idx], n = n, type = type, direction = direction) 39 | } 40 | ) 41 | 42 | # Re-assemble interpolated colors in Converted color space 43 | cs_new <- do.call(cbind, columns) 44 | 45 | # Convert colors back to hex strings for R 46 | rgb_new <- farver::convert_colour(cs_new, from = colorspace, to = 'rgb') 47 | rgb_new <- farver::encode_colour(rgb_new) 48 | 49 | 50 | rgb_new 51 | } else { 52 | stop("Color interpolation requires the 'farver' package") 53 | } 54 | } 55 | 56 | 57 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | 15 | library(displease) 16 | ``` 17 | 18 | # displease 19 | 20 | 21 | ![](https://img.shields.io/badge/cool-useless-green.svg) 22 | [![R-CMD-check](https://github.com/coolbutuseless/displease/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/coolbutuseless/displease/actions/workflows/R-CMD-check.yaml) 23 | [![CRAN status](https://www.r-pkg.org/badges/version/displease)](https://CRAN.R-project.org/package=displease) 24 | 25 | 26 | The `displease` package provides non-linear interpolation between numeric values. 27 | 28 | It is a replacement for `seq()` for when you want a sequence between two values which is **not** equally spaced. 29 | 30 | Easing can look much better than strict linear interpolation when applied to 31 | objects moving spatially. 32 | 33 | `displease` = **displ** + **ease**. The results are often used for 34 | animating visual **displacement**, and the non-linear interpolation is 35 | performed by applying an **easing** function. 36 | 37 | 38 | ## Installation 39 | 40 | This package can be installed from CRAN 41 | 42 | ``` r 43 | install.packages('displease') 44 | ``` 45 | 46 | You can install the latest development version from 47 | [GitHub](https://github.com/coolbutuseless/displease) with: 48 | 49 | ``` r 50 | # install.package('remotes') 51 | remotes::install_github('coolbutuseless/displease') 52 | ``` 53 | 54 | Pre-built source/binary versions can also be installed from 55 | [R-universe](https://r-universe.dev) 56 | 57 | ``` r 58 | install.packages('displease', repos = c('https://coolbutuseless.r-universe.dev', 'https://cloud.r-project.org')) 59 | ``` 60 | 61 | ## What's in the box? 62 | 63 | * `seq_ease(x1, x2, n, type, direction)` - create a sequence of length `n` 64 | between `x1` and `x2`. Apply the easing function given by `type`. 65 | * `type` is one of: `sine`, `quad`, `cubic`, `quart`, `quint`, `exp`, 66 | `circle`, `back`, `elastic` 67 | * `direction` is one of: `in`, `out`, `in-out` 68 | * `seq_color(col1, col2, ...)` non-linear interpolation between two colors. 69 | 70 | The following graph shows the `seq_ease()` output for `n = 100` for 71 | all the different easing functions with given extents of `x1 = 0` and `x2 = 1` 72 | 73 | ## Displacement Easing 74 | 75 | 76 | ```{r example} 77 | library(displease) 78 | 79 | # normal linear interpolation using seq() 80 | (y_linear <- seq(1, 10, length.out = 10)) 81 | 82 | # Non-linear interpolation using {displease} 83 | (y_cubic_in <- seq_ease(1, 10, n=10, type = 'cubic', direction = 'in')) 84 | (y_cubic_in_out <- seq_ease(1, 10, n=10, type = 'cubic', direction = 'in-out')) 85 | 86 | plot(1:10, y_linear , type = 'b') 87 | plot(1:10, y_cubic_in , type = 'b') 88 | plot(1:10, y_cubic_in_out, type = 'b') 89 | ``` 90 | 91 | ### Illustration of supported easings 92 | 93 | ```{r echo=FALSE, fig.height = 10} 94 | suppressPackageStartupMessages({ 95 | library(dplyr) 96 | library(ggplot2) 97 | library(purrr) 98 | }) 99 | 100 | 101 | type <- c('sine', 'quad', 'cubic', 'quart', 'quint', 'exp', 'circle', 'back', 'elastic') 102 | direction <- c('in', 'out', 'in-out') 103 | 104 | df <- as_tibble(expand.grid(type = type, direction = direction)) 105 | 106 | df <- purrr::map2_dfr(df$type, df$direction, 107 | ~tibble( 108 | type = .x, 109 | direction = .y, 110 | x = 1:100, 111 | y = seq_ease(type = .x, direction = .y))) 112 | 113 | 114 | 115 | ggplot(df) + 116 | geom_line(aes(x, y)) + 117 | facet_grid(type ~ direction) + 118 | theme_bw() + 119 | scale_y_continuous(name = NULL, breaks = c(0, 1)) + 120 | scale_x_continuous(name = NULL, breaks = c(0, 100)) + 121 | theme(axis.title = element_blank(), axis.text.x = element_blank()) + 122 | coord_fixed(50) 123 | 124 | ``` 125 | 126 | 127 | ## Easing between two colors 128 | 129 | When easing between two colors, the `colorspace` where the interpolation takes 130 | place will have a large effect on the final result. 131 | 132 | The examples below show the interpolation from 'red' to 'blue'. The only difference 133 | between the examples is the colorspace where the interpolation occurs. 134 | 135 | ```{r fig.height = 1} 136 | n <- 100 137 | cols <- seq_color('red', 'blue', n = n, direction = 'in-out', type = 'cubic', 138 | colorspace = 'hcl') 139 | head(cols) 140 | grid::grid.rect(x = seq(0, 0.95, length.out = n), width = 0.1, 141 | gp = grid::gpar(fill = cols, col = NA)) 142 | ``` 143 | 144 | 145 | 146 | 147 | ```{r fig.height = 1} 148 | n <- 100 149 | cols <- seq_color('red', 'blue', n = n, direction = 'in-out', type = 'cubic', 150 | colorspace = 'lab') 151 | head(cols) 152 | grid::grid.rect(x = seq(0, 0.95, length.out = n), width = 0.1, 153 | gp = grid::gpar(fill = cols, col = NA)) 154 | ``` 155 | 156 | 157 | ## Animated example 158 | 159 | This animation shows each of the easing types. 160 | 161 | ```{r eval = FALSE, echo = FALSE} 162 | library(dplyr) 163 | library(ggplot2) 164 | 165 | N <- 50 166 | linear = seq(0, 1, length.out = N) 167 | sine = seq_ease(0, 1, n=N, type = 'sine' , direction = 'in-out') 168 | quad = seq_ease(0, 1, n=N, type = 'quad' , direction = 'in-out') 169 | cubic = seq_ease(0, 1, n=N, type = 'cubic' , direction = 'in-out') 170 | quart = seq_ease(0, 1, n=N, type = 'quart' , direction = 'in-out') 171 | quint = seq_ease(0, 1, n=N, type = 'quint' , direction = 'in-out') 172 | exp = seq_ease(0, 1, n=N, type = 'exp' , direction = 'in-out') 173 | circle = seq_ease(0, 1, n=N, type = 'circle' , direction = 'in-out') 174 | back = seq_ease(0, 1, n=N, type = 'back' , direction = 'in-out') 175 | elastic = seq_ease(0, 1, n=N, type = 'elastic', direction = 'in-out') 176 | 177 | df <- data.frame( 178 | idx = seq_len(N * 2), 179 | linear = c(linear , rev(linear )), 180 | sine = c(sine , rev(sine )), 181 | quad = c(quad , rev(quad )), 182 | cubic = c(cubic , rev(cubic )), 183 | quart = c(quart , rev(quart )), 184 | quint = c(quint , rev(quint )), 185 | exp = c(exp , rev(exp )), 186 | circle = c(circle , rev(circle )), 187 | back = c(back , rev(back )), 188 | elastic = c(elastic, rev(elastic)) 189 | ) 190 | 191 | mdf <- tidyr::gather(df, type, value, -idx) 192 | 193 | dir.create('man/figures/anim/', showWarnings = FALSE) 194 | zz <- list.files('man/figures/anim/', full.names = TRUE) 195 | unlink(zz) 196 | 197 | alpha <- c( 198 | '0' = 1, 199 | '1' = 0.15, 200 | '2' = 0.1, 201 | '3' = 0.07, 202 | '4' = 0.03, 203 | '5' = 0.02, 204 | '6' = 0.01 205 | ) 206 | 207 | create_plot <- function(this_idx) { 208 | plot_df <- mdf %>% 209 | filter(idx <= this_idx, this_idx - idx <= 5) %>% 210 | mutate(lag = as.character(this_idx - idx)) 211 | p <- ggplot(plot_df) + 212 | geom_point(aes(value, type, alpha = lag), size = 3, stroke = NA) + 213 | theme_void() + 214 | xlim(-0.2, 1.2) + 215 | theme( 216 | axis.text.y = element_text(), 217 | legend.position = 'none' 218 | ) + 219 | scale_alpha_manual(values = alpha) 220 | 221 | filename <- sprintf("man/figures/anim/%03i.png", this_idx) 222 | ggsave(filename, p, width = 6, height = 5) 223 | p 224 | } 225 | 226 | seq_len(N*2) %>% purrr::walk(create_plot) 227 | 228 | 229 | # system.time({ system("convert man/figures/anim/*.png man/figures/anim.gif") }) 230 | # system.time({ system("gifski --fast --fps 10 man/figures/anim/*.png --output man/figures/anim2.gif")}) 231 | system.time({ system("gifski --fps 10 man/figures/anim/*.png --output man/figures/anim2.gif")}) 232 | system("gifsicle -k 16 -O99 man/figures/anim2.gif -o man/figures/anim3.gif") 233 | ``` 234 | 235 | 236 | 237 | 238 | 239 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # displease 5 | 6 | 7 | 8 | ![](https://img.shields.io/badge/cool-useless-green.svg) 9 | [![R-CMD-check](https://github.com/coolbutuseless/displease/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/coolbutuseless/displease/actions/workflows/R-CMD-check.yaml) 10 | [![CRAN 11 | status](https://www.r-pkg.org/badges/version/displease)](https://CRAN.R-project.org/package=displease) 12 | 13 | 14 | The `displease` package provides non-linear interpolation between 15 | numeric values. 16 | 17 | It is a replacement for `seq()` for when you want a sequence between two 18 | values which is **not** equally spaced. 19 | 20 | Easing can look much better than strict linear interpolation when 21 | applied to objects moving spatially. 22 | 23 | `displease` = **displ** + **ease**. The results are often used for 24 | animating visual **displacement**, and the non-linear interpolation is 25 | performed by applying an **easing** function. 26 | 27 | ## Installation 28 | 29 | This package can be installed from CRAN 30 | 31 | ``` r 32 | install.packages('displease') 33 | ``` 34 | 35 | You can install the latest development version from 36 | [GitHub](https://github.com/coolbutuseless/displease) with: 37 | 38 | ``` r 39 | # install.package('remotes') 40 | remotes::install_github('coolbutuseless/displease') 41 | ``` 42 | 43 | Pre-built source/binary versions can also be installed from 44 | [R-universe](https://r-universe.dev) 45 | 46 | ``` r 47 | install.packages('displease', repos = c('https://coolbutuseless.r-universe.dev', 'https://cloud.r-project.org')) 48 | ``` 49 | 50 | ## What’s in the box? 51 | 52 | - `seq_ease(x1, x2, n, type, direction)` - create a sequence of length 53 | `n` between `x1` and `x2`. Apply the easing function given by `type`. 54 | - `type` is one of: `sine`, `quad`, `cubic`, `quart`, `quint`, `exp`, 55 | `circle`, `back`, `elastic` 56 | - `direction` is one of: `in`, `out`, `in-out` 57 | - `seq_color(col1, col2, ...)` non-linear interpolation between two 58 | colors. 59 | 60 | The following graph shows the `seq_ease()` output for `n = 100` for all 61 | the different easing functions with given extents of `x1 = 0` and 62 | `x2 = 1` 63 | 64 | ## Displacement Easing 65 | 66 | ``` r 67 | library(displease) 68 | 69 | # normal linear interpolation using seq() 70 | (y_linear <- seq(1, 10, length.out = 10)) 71 | #> [1] 1 2 3 4 5 6 7 8 9 10 72 | 73 | # Non-linear interpolation using {displease} 74 | (y_cubic_in <- seq_ease(1, 10, n=10, type = 'cubic', direction = 'in')) 75 | #> [1] 1.000000 1.012346 1.098765 1.333333 1.790123 2.543210 3.666667 76 | #> [8] 5.234568 7.320988 10.000000 77 | (y_cubic_in_out <- seq_ease(1, 10, n=10, type = 'cubic', direction = 'in-out')) 78 | #> [1] 1.000000 1.049383 1.395062 2.333333 4.160494 6.839506 8.666667 79 | #> [8] 9.604938 9.950617 10.000000 80 | 81 | plot(1:10, y_linear , type = 'b') 82 | ``` 83 | 84 | 85 | 86 | ``` r 87 | plot(1:10, y_cubic_in , type = 'b') 88 | ``` 89 | 90 | 91 | 92 | ``` r 93 | plot(1:10, y_cubic_in_out, type = 'b') 94 | ``` 95 | 96 | 97 | 98 | ### Illustration of supported easings 99 | 100 | 101 | 102 | ## Easing between two colors 103 | 104 | When easing between two colors, the `colorspace` where the interpolation 105 | takes place will have a large effect on the final result. 106 | 107 | The examples below show the interpolation from ‘red’ to ‘blue’. The only 108 | difference between the examples is the colorspace where the 109 | interpolation occurs. 110 | 111 | ``` r 112 | n <- 100 113 | cols <- seq_color('red', 'blue', n = n, direction = 'in-out', type = 'cubic', 114 | colorspace = 'hcl') 115 | head(cols) 116 | #> [1] "#FF0000" "#FF0000" "#FF0000" "#FF0000" "#FF0100" "#FF0200" 117 | grid::grid.rect(x = seq(0, 0.95, length.out = n), width = 0.1, 118 | gp = grid::gpar(fill = cols, col = NA)) 119 | ``` 120 | 121 | 122 | 123 | ``` r 124 | n <- 100 125 | cols <- seq_color('red', 'blue', n = n, direction = 'in-out', type = 'cubic', 126 | colorspace = 'lab') 127 | head(cols) 128 | #> [1] "#FF0000" "#FF0000" "#FF0000" "#FF0000" "#FF0000" "#FF0000" 129 | grid::grid.rect(x = seq(0, 0.95, length.out = n), width = 0.1, 130 | gp = grid::gpar(fill = cols, col = NA)) 131 | ``` 132 | 133 | 134 | 135 | ## Animated example 136 | 137 | This animation shows each of the easing types. 138 | 139 | 140 | -------------------------------------------------------------------------------- /man/figures/.gitignore: -------------------------------------------------------------------------------- 1 | anim 2 | -------------------------------------------------------------------------------- /man/figures/README-example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/displease/88ec3586a75c2c93c1d56b47630cb12099d7c9a3/man/figures/README-example-1.png -------------------------------------------------------------------------------- /man/figures/README-example-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/displease/88ec3586a75c2c93c1d56b47630cb12099d7c9a3/man/figures/README-example-2.png -------------------------------------------------------------------------------- /man/figures/README-example-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/displease/88ec3586a75c2c93c1d56b47630cb12099d7c9a3/man/figures/README-example-3.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/displease/88ec3586a75c2c93c1d56b47630cb12099d7c9a3/man/figures/README-unnamed-chunk-2-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/displease/88ec3586a75c2c93c1d56b47630cb12099d7c9a3/man/figures/README-unnamed-chunk-3-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/displease/88ec3586a75c2c93c1d56b47630cb12099d7c9a3/man/figures/README-unnamed-chunk-4-1.png -------------------------------------------------------------------------------- /man/figures/anim3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/displease/88ec3586a75c2c93c1d56b47630cb12099d7c9a3/man/figures/anim3.gif -------------------------------------------------------------------------------- /man/seq_color.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/seq-color.R 3 | \name{seq_color} 4 | \alias{seq_color} 5 | \title{Interpolate (non-linearly) between two colors} 6 | \usage{ 7 | seq_color( 8 | col1, 9 | col2, 10 | n = 100, 11 | type = "cubic", 12 | direction = "in-out", 13 | colorspace = "hcl" 14 | ) 15 | } 16 | \arguments{ 17 | \item{col1, col2}{the two colors to interpolate between} 18 | 19 | \item{n}{Number of steps for the transition (including the endpoints)} 20 | 21 | \item{type}{Type of motion easing. Default: 'cubic'. Valid values are 22 | are 'sine', 'quad', 'cubic', 'quart', 'quint', 'exp', 'circle', 'back', 23 | 'elastic', 'linear'.} 24 | 25 | \item{direction}{When should the easing apply? Default: "in-out". 26 | Valid values are 'in', 'out', in-out'. Default: 'in-out'} 27 | 28 | \item{colorspace}{Color space in which to do the interpolation. Default: 'hcl' 29 | Can be any colorspace understood by the \code{farver} package i.e. 30 | "cmy", "cmyk", "hsl", "hsb", "hsv", "lab" (CIE L*ab), "hunterlab" 31 | (Hunter Lab), "oklab", "lch" (CIE Lch(ab) / polarLAB), "luv", "rgb" 32 | (sRGB), "xyz", "yxy" (CIE xyY), "hcl" (CIE Lch(uv) / polarLuv), 33 | or "oklch" (Polar form of oklab). 34 | Note: Not all color spaces make sense for interpolation.} 35 | } 36 | \value{ 37 | character vector containing a color sequence 38 | } 39 | \description{ 40 | Interpolate (non-linearly) between two colors 41 | } 42 | \examples{ 43 | \dontshow{if (requireNamespace('farver', quietly = TRUE)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 44 | n <- 20 45 | cols <- seq_color('red', 'blue', n = n, direction = 'in-out', colorspace = 'hcl') 46 | cols 47 | grid::grid.rect(x = seq(0, 0.95, length.out = n), width = 0.1, 48 | gp = grid::gpar(fill = cols, col = NA)) 49 | \dontshow{\}) # examplesIf} 50 | } 51 | -------------------------------------------------------------------------------- /man/seq_ease.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/easings.R 3 | \name{seq_ease} 4 | \alias{seq_ease} 5 | \title{Create a sequence interpolating between two values with the specified 6 | non-linear easing.} 7 | \usage{ 8 | seq_ease(x1 = 0, x2 = 1, n = 100, type = "cubic", direction = "in-out") 9 | } 10 | \arguments{ 11 | \item{x1, x2}{The start and end values of the sequence. Default: 0, 1} 12 | 13 | \item{n}{Number of steps for the transition (including the endpoints)} 14 | 15 | \item{type}{Type of motion easing. Default: 'cubic'. Valid values are 16 | are 'sine', 'quad', 'cubic', 'quart', 'quint', 'exp', 'circle', 'back', 17 | 'elastic', 'linear'.} 18 | 19 | \item{direction}{When should the easing apply? Default: "in-out". 20 | Valid values are 'in', 'out', in-out'. Default: 'in-out'} 21 | } 22 | \value{ 23 | Numeric vector of length \code{n} 24 | } 25 | \description{ 26 | Create a sequence interpolating between two values with the specified 27 | non-linear easing. 28 | } 29 | \examples{ 30 | x <- seq_ease(x1 = 0, x2 = 1, n = 20, type = 'cubic', direction = 'in-out') 31 | x 32 | plot(x) 33 | } 34 | --------------------------------------------------------------------------------