New upstream version 1.0.1
Joost van Baal-Ilić
4 years ago
0 | Package: tweenr | |
1 | Type: Package | |
2 | Title: Interpolate Data for Smooth Animations | |
3 | Version: 1.0.1 | |
4 | Date: 2018-12-14 | |
5 | Authors@R: c( | |
6 | person('Thomas Lin', 'Pedersen', , 'thomasp85@gmail.com', c('aut', 'cre')) | |
7 | ) | |
8 | Maintainer: Thomas Lin Pedersen <thomasp85@gmail.com> | |
9 | Description: In order to create smooth animation between states of data, | |
10 | tweening is necessary. This package provides a range of functions for | |
11 | creating tweened data that can be used as basis for animation. Furthermore | |
12 | it adds a number of vectorized interpolaters for common R data | |
13 | types such as numeric, date and colour. | |
14 | URL: https://github.com/thomasp85/tweenr | |
15 | BugReports: https://github.com/thomasp85/tweenr/issues | |
16 | License: MIT + file LICENSE | |
17 | Encoding: UTF-8 | |
18 | LazyData: TRUE | |
19 | Depends: R (>= 3.2.0) | |
20 | Imports: Rcpp (>= 0.12.3), grDevices, farver, magrittr, rlang | |
21 | LinkingTo: Rcpp | |
22 | RoxygenNote: 6.1.1 | |
23 | Suggests: testthat, covr | |
24 | NeedsCompilation: yes | |
25 | Packaged: 2018-12-14 13:16:06 UTC; thomas | |
26 | Author: Thomas Lin Pedersen [aut, cre] | |
27 | Repository: CRAN | |
28 | Date/Publication: 2018-12-14 13:40:03 UTC |
0 | 6e6fd070981b4da1e4ff6d549960fd66 *DESCRIPTION | |
1 | 928d886d4e8454f3db823a37d4de465f *LICENSE | |
2 | 1ce9ebcadbe312056258a8f41326b685 *NAMESPACE | |
3 | 313d3adca091ef0a5997a0bf74c05e61 *R/RcppExports.R | |
4 | 1bdc1df668054061a499fd86dbeef5ab *R/aaa.R | |
5 | 16501add0944b61f30d83e263b8e8d1d *R/display_ease.R | |
6 | 96179175a1b2714715bd51272b167f8b *R/interpolate_along.R | |
7 | 210b74ecfb81b7e59ad62d60411065fe *R/interpolate_at.R | |
8 | 0a8e1bd6c209027fa880190dcb0801d3 *R/interpolate_element.R | |
9 | cc10f6bda97ee4a5b9482fadfe58c7e0 *R/interpolate_fill.R | |
10 | fa58c8bac6ab8f305f19c81a75fc0730 *R/interpolate_state.R | |
11 | 689f558411e69e41e4e75f5369aeaf60 *R/tween.R | |
12 | ac339e3835e902f17f662884605697e6 *R/tween_along.R | |
13 | 2d43ff320e0f18cae6f4d62fce5d9c94 *R/tween_appear.R | |
14 | 641fea63107e6850430abe0e1de68b73 *R/tween_at.R | |
15 | a0fb7bf5931f79cdc9b3fe258a1b7af5 *R/tween_colour.R | |
16 | 9721b0743f541b5c29e1e03e150fd861 *R/tween_components.R | |
17 | e2bf81312be495522d36feace8ca4291 *R/tween_constant.R | |
18 | 81d553e0dabc78640045286414615d9c *R/tween_date.R | |
19 | c043ef5f53d196435d92b7502733ed98 *R/tween_datetime.R | |
20 | c3d9bd610ec0ebf6843938b52ba9a238 *R/tween_elements.R | |
21 | ead890774e462247a42cff161142c73d *R/tween_events.R | |
22 | 8d2d40175112d9250bbf643846105c5f *R/tween_fill.R | |
23 | 3fbebb9b93bfc70f98c8d4da699a7517 *R/tween_numeric.R | |
24 | 391009a63a320512bafdfc940d0aa50e *R/tween_state.R | |
25 | 812405ce92dfc8a9e81a5a823894cc10 *R/tween_states.R | |
26 | 96d0cc4763f6e260723404181527692d *R/tweenr_package.R | |
27 | 7c25a1760eb4d2c1eaad187a63f8792a *README.md | |
28 | a7d5791f172a3bec357efeb97420bb15 *man/display_ease.Rd | |
29 | 34366927919391bd1a708125f2175dd4 *man/dot-complete_states.Rd | |
30 | 4c6856bef70b38658bdb9adf438ddbf8 *man/dot-get_last_frame.Rd | |
31 | 822defaec9293e5bce6728c6bfcecddc *man/dot-max_id.Rd | |
32 | 690fc06d8dff1acdfe0bd31a3475408c *man/figures/README-unnamed-chunk-3.gif | |
33 | 2dadfaa78bd7bc38097a1be9fbd75780 *man/figures/README-unnamed-chunk-4-1.png | |
34 | a43cef038c756170afdfda6598b4886c *man/figures/logo.png | |
35 | 4bfb05de15f5ea7ad46e96577a2bedcc *man/reexports.Rd | |
36 | d8719148815e87dbba9859db35580d4c *man/tween.Rd | |
37 | bc68775c38b034cc4d24deae25a6ecac *man/tween_along.Rd | |
38 | f3ac36b46eda0ed8ebdb82a6f3856c2d *man/tween_appear.Rd | |
39 | 5ae8335a6fb227f5334ca7e2d863aa99 *man/tween_at.Rd | |
40 | bad8b26b955f205ab8f06dd0aaf6fb73 *man/tween_components.Rd | |
41 | 9a85615c0e8118222f300eeacc37bda0 *man/tween_elements.Rd | |
42 | ce4b2a862ea15359ed9b0ef3bc606e05 *man/tween_events.Rd | |
43 | 27b0ed281713d21cae636526fe08fb06 *man/tween_fill.Rd | |
44 | 45a6c662bde72d3ca31f6f128ce0c17f *man/tween_state.Rd | |
45 | a78da1ea3e528145730e1e63624b919a *man/tween_states.Rd | |
46 | 8655be1ce7f753485c94e2e4cdee6066 *man/tweenr-package.Rd | |
47 | ec84e616c2be105b238e96a615188688 *src/RcppExports.cpp | |
48 | bef46ca72aef6dffb9ae1fc728aef864 *src/easing.c | |
49 | 20db4b5dfde1a8274bafe699cb5b5cc4 *src/easing.h | |
50 | 6e674ad562cd298af44d1e801ea6b510 *src/interpolators.cpp | |
51 | aa895b44ba533f702387d9ec557e501a *tests/testthat.R | |
52 | 642f1ef1e7a912267fc9891a21216cc4 *tests/testthat/test-along.R | |
53 | ee87f10b41a70e3ca3ef3e90f5e1a505 *tests/testthat/test-at.R | |
54 | 925c0539a4fdf0c24b2344cd0cadb0ab *tests/testthat/test-components.R | |
55 | 7d3b896a697948cda844ebdc6f0188ef *tests/testthat/test-events.R | |
56 | 6159b10fcca398bb4731d00bfa6b6342 *tests/testthat/test-fill.R | |
57 | 8801c912dc4fa55e6074f6e0eee83f2d *tests/testthat/test-state.R |
0 | # Generated by roxygen2: do not edit by hand | |
1 | ||
2 | export("%>%") | |
3 | export(.complete_states) | |
4 | export(.get_first_frame) | |
5 | export(.get_last_frame) | |
6 | export(.has_frames) | |
7 | export(.max_id) | |
8 | export(.with_later_frames) | |
9 | export(.with_prior_frames) | |
10 | export(close_state) | |
11 | export(display_ease) | |
12 | export(keep_state) | |
13 | export(open_state) | |
14 | export(tween) | |
15 | export(tween_along) | |
16 | export(tween_appear) | |
17 | export(tween_at) | |
18 | export(tween_color) | |
19 | export(tween_color_t) | |
20 | export(tween_colour) | |
21 | export(tween_colour_t) | |
22 | export(tween_components) | |
23 | export(tween_constant) | |
24 | export(tween_constant_t) | |
25 | export(tween_date) | |
26 | export(tween_date_t) | |
27 | export(tween_datetime) | |
28 | export(tween_datetime_t) | |
29 | export(tween_elements) | |
30 | export(tween_events) | |
31 | export(tween_fill) | |
32 | export(tween_numeric) | |
33 | export(tween_numeric_t) | |
34 | export(tween_state) | |
35 | export(tween_states) | |
36 | export(tween_t) | |
37 | importFrom(Rcpp,sourceCpp) | |
38 | importFrom(farver,convert_colour) | |
39 | importFrom(grDevices,col2rgb) | |
40 | importFrom(grDevices,rgb) | |
41 | importFrom(graphics,plot) | |
42 | importFrom(magrittr,"%>%") | |
43 | importFrom(rlang,"%||%") | |
44 | importFrom(rlang,enquo) | |
45 | importFrom(rlang,eval_tidy) | |
46 | importFrom(rlang,quo_is_missing) | |
47 | importFrom(rlang,quo_is_null) | |
48 | importFrom(utils,head) | |
49 | useDynLib(tweenr) |
0 | # Generated by using Rcpp::compileAttributes() -> do not edit by hand | |
1 | # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 | |
2 | ||
3 | numeric_state_interpolator <- function(data, states) { | |
4 | .Call('_tweenr_numeric_state_interpolator', PACKAGE = 'tweenr', data, states) | |
5 | } | |
6 | ||
7 | colour_state_interpolator <- function(data, states) { | |
8 | .Call('_tweenr_colour_state_interpolator', PACKAGE = 'tweenr', data, states) | |
9 | } | |
10 | ||
11 | constant_state_interpolator <- function(data, states) { | |
12 | .Call('_tweenr_constant_state_interpolator', PACKAGE = 'tweenr', data, states) | |
13 | } | |
14 | ||
15 | list_state_interpolator <- function(data, states) { | |
16 | .Call('_tweenr_list_state_interpolator', PACKAGE = 'tweenr', data, states) | |
17 | } | |
18 | ||
19 | numlist_state_interpolator <- function(data, states) { | |
20 | .Call('_tweenr_numlist_state_interpolator', PACKAGE = 'tweenr', data, states) | |
21 | } | |
22 | ||
23 | phase_state_interpolator <- function(data, states) { | |
24 | .Call('_tweenr_phase_state_interpolator', PACKAGE = 'tweenr', data, states) | |
25 | } | |
26 | ||
27 | numeric_element_interpolator <- function(data, group, frame, ease) { | |
28 | .Call('_tweenr_numeric_element_interpolator', PACKAGE = 'tweenr', data, group, frame, ease) | |
29 | } | |
30 | ||
31 | colour_element_interpolator <- function(data, group, frame, ease) { | |
32 | .Call('_tweenr_colour_element_interpolator', PACKAGE = 'tweenr', data, group, frame, ease) | |
33 | } | |
34 | ||
35 | constant_element_interpolator <- function(data, group, frame, ease) { | |
36 | .Call('_tweenr_constant_element_interpolator', PACKAGE = 'tweenr', data, group, frame, ease) | |
37 | } | |
38 | ||
39 | list_element_interpolator <- function(data, group, frame, ease) { | |
40 | .Call('_tweenr_list_element_interpolator', PACKAGE = 'tweenr', data, group, frame, ease) | |
41 | } | |
42 | ||
43 | numlist_element_interpolator <- function(data, group, frame, ease) { | |
44 | .Call('_tweenr_numlist_element_interpolator', PACKAGE = 'tweenr', data, group, frame, ease) | |
45 | } | |
46 | ||
47 | phase_element_interpolator <- function(data, group, frame, ease) { | |
48 | .Call('_tweenr_phase_element_interpolator', PACKAGE = 'tweenr', data, group, frame, ease) | |
49 | } | |
50 | ||
51 | numeric_along_interpolator <- function(data, group, time, history, keep_last, nframes, ease) { | |
52 | .Call('_tweenr_numeric_along_interpolator', PACKAGE = 'tweenr', data, group, time, history, keep_last, nframes, ease) | |
53 | } | |
54 | ||
55 | colour_along_interpolator <- function(data, group, time, history, keep_last, nframes, ease) { | |
56 | .Call('_tweenr_colour_along_interpolator', PACKAGE = 'tweenr', data, group, time, history, keep_last, nframes, ease) | |
57 | } | |
58 | ||
59 | constant_along_interpolator <- function(data, group, time, history, keep_last, nframes, ease) { | |
60 | .Call('_tweenr_constant_along_interpolator', PACKAGE = 'tweenr', data, group, time, history, keep_last, nframes, ease) | |
61 | } | |
62 | ||
63 | list_along_interpolator <- function(data, group, time, history, keep_last, nframes, ease) { | |
64 | .Call('_tweenr_list_along_interpolator', PACKAGE = 'tweenr', data, group, time, history, keep_last, nframes, ease) | |
65 | } | |
66 | ||
67 | numlist_along_interpolator <- function(data, group, time, history, keep_last, nframes, ease) { | |
68 | .Call('_tweenr_numlist_along_interpolator', PACKAGE = 'tweenr', data, group, time, history, keep_last, nframes, ease) | |
69 | } | |
70 | ||
71 | phase_along_interpolator <- function(group, time, history, keep_last, nframes) { | |
72 | .Call('_tweenr_phase_along_interpolator', PACKAGE = 'tweenr', group, time, history, keep_last, nframes) | |
73 | } | |
74 | ||
75 | numeric_at_interpolator <- function(from, to, at, ease) { | |
76 | .Call('_tweenr_numeric_at_interpolator', PACKAGE = 'tweenr', from, to, at, ease) | |
77 | } | |
78 | ||
79 | colour_at_interpolator <- function(from, to, at, ease) { | |
80 | .Call('_tweenr_colour_at_interpolator', PACKAGE = 'tweenr', from, to, at, ease) | |
81 | } | |
82 | ||
83 | constant_at_interpolator <- function(from, to, at, ease) { | |
84 | .Call('_tweenr_constant_at_interpolator', PACKAGE = 'tweenr', from, to, at, ease) | |
85 | } | |
86 | ||
87 | list_at_interpolator <- function(from, to, at, ease) { | |
88 | .Call('_tweenr_list_at_interpolator', PACKAGE = 'tweenr', from, to, at, ease) | |
89 | } | |
90 | ||
91 | numlist_at_interpolator <- function(from, to, at, ease) { | |
92 | .Call('_tweenr_numlist_at_interpolator', PACKAGE = 'tweenr', from, to, at, ease) | |
93 | } | |
94 | ||
95 | numeric_fill_interpolator <- function(data, ease) { | |
96 | .Call('_tweenr_numeric_fill_interpolator', PACKAGE = 'tweenr', data, ease) | |
97 | } | |
98 | ||
99 | colour_fill_interpolator <- function(data, ease) { | |
100 | .Call('_tweenr_colour_fill_interpolator', PACKAGE = 'tweenr', data, ease) | |
101 | } | |
102 | ||
103 | constant_fill_interpolator <- function(data, ease) { | |
104 | .Call('_tweenr_constant_fill_interpolator', PACKAGE = 'tweenr', data, ease) | |
105 | } | |
106 | ||
107 | list_fill_interpolator <- function(data, ease) { | |
108 | .Call('_tweenr_list_fill_interpolator', PACKAGE = 'tweenr', data, ease) | |
109 | } | |
110 | ||
111 | numlist_fill_interpolator <- function(data, ease) { | |
112 | .Call('_tweenr_numlist_fill_interpolator', PACKAGE = 'tweenr', data, ease) | |
113 | } | |
114 |
0 | BASEDATE <- Sys.Date() - as.numeric(Sys.Date()) | |
1 | BASEDATETIME <- Sys.time() - as.numeric(Sys.time()) | |
2 | ||
3 | validEase <- c( | |
4 | "linear", | |
5 | "quadratic-in", | |
6 | "quadratic-out", | |
7 | "quadratic-in-out", | |
8 | "cubic-in", | |
9 | "cubic-out", | |
10 | "cubic-in-out", | |
11 | "quartic-in", | |
12 | "quartic-out", | |
13 | "quartic-in-out", | |
14 | "quintic-in", | |
15 | "quintic-out", | |
16 | "quintic-in-out", | |
17 | "sine-in", | |
18 | "sine-out", | |
19 | "sine-in-out", | |
20 | "circular-in", | |
21 | "circular-out", | |
22 | "circular-in-out", | |
23 | "exponential-in", | |
24 | "exponential-out", | |
25 | "exponential-in-out", | |
26 | "elastic-in", | |
27 | "elastic-out", | |
28 | "elastic-in-out", | |
29 | "back-in", | |
30 | "back-out", | |
31 | "back-in-out", | |
32 | "bounce-in", | |
33 | "bounce-out", | |
34 | "bounce-in-out" | |
35 | ) | |
36 | ||
37 | #' @importFrom magrittr %>% | |
38 | #' @export | |
39 | magrittr::`%>%` | |
40 | ||
41 | col_classes <- function(data) { | |
42 | classes <- sapply(data, function(d) { | |
43 | if (is.numeric(d)) { | |
44 | 'numeric' | |
45 | } else if (is.logical(d)) { | |
46 | 'logical' | |
47 | } else if (is.factor(d)) { | |
48 | 'factor' | |
49 | } else if (is.character(d)) { | |
50 | colour <- try(suppressWarnings(col2rgb(d)), silent = TRUE) | |
51 | if (all(is.na(d)) || inherits(colour, 'try-error') || anyNA(colour) || all(grepl('^(\\d|\\.)+$', d))) { | |
52 | 'character' | |
53 | } else { | |
54 | 'colour' | |
55 | } | |
56 | } else if (inherits(d, 'Date')) { | |
57 | 'date' | |
58 | } else if (inherits(d, 'POSIXt')) { | |
59 | 'datetime' | |
60 | } else if (is.list(d)) { | |
61 | if (all(vapply(d, is.numeric, logical(1)))) 'numlist' | |
62 | else 'list' | |
63 | } else { | |
64 | 'constant' | |
65 | } | |
66 | }) | |
67 | names(classes) <- names(data) | |
68 | classes[names(classes) == '.phase'] <- 'phase' | |
69 | classes | |
70 | } | |
71 | ||
72 | prepareTween <- function(data, n, ease) { | |
73 | if (!is.list(data)) { | |
74 | data <- as.list(data) | |
75 | } | |
76 | if (length(unique(lengths(data))) != 1) { | |
77 | stop('All elements in data must have the same length') | |
78 | } | |
79 | if (!all(ease %in% validEase)) { | |
80 | stop('ease must be the name of a valid easing function') | |
81 | } | |
82 | n <- c(rep(n, length.out = length(data) - 1) - 1, 1) | |
83 | ease <- c(rep(ease, length.out = length(data) - 1), 'constant') | |
84 | states <- data.frame( | |
85 | state = seq_along(data) - 1, | |
86 | nframes = n, | |
87 | ease = ease, | |
88 | stringsAsFactors = FALSE | |
89 | ) | |
90 | list( | |
91 | data = data, | |
92 | states = states | |
93 | ) | |
94 | } | |
95 | ||
96 | prepareTweenTranspose <- function(data, n, ease) { | |
97 | if (!is.list(data)) { | |
98 | data <- list(data) | |
99 | } | |
100 | if (!all(ease %in% validEase)) { | |
101 | stop('ease must be the name of a valid easing function') | |
102 | } | |
103 | n <- rep(n, length.out = length(data)) | |
104 | n <- Map(function(n, l) { | |
105 | s <- floor(n / l) | |
106 | s <- rep(s, l) | |
107 | overhead <- n - sum(s) | |
108 | if (overhead) { | |
109 | s <- s + rep(floor(overhead / l), l) | |
110 | addInd <- sample(length(s), overhead %% l) | |
111 | s[addInd] <- s[addInd] + 1 | |
112 | } | |
113 | c(s, 1) | |
114 | }, n = n - 1, l = lengths(data) - 1) | |
115 | n <- unlist(n) | |
116 | ease <- rep(ease, length.out = length(data)) | |
117 | ease <- rep(ease, lengths(data) - 1) | |
118 | easeSplit <- split(ease, rep(seq_along(data), lengths(data) - 1)) | |
119 | ease <- unlist(lapply(easeSplit, append, values = 'constant')) | |
120 | data <- as.list(unlist(data)) | |
121 | states <- data.frame( | |
122 | state = seq_along(data) - 1, | |
123 | nframes = n, | |
124 | ease = ease, | |
125 | stringsAsFactors = FALSE | |
126 | ) | |
127 | list( | |
128 | data = data, | |
129 | states = states | |
130 | ) | |
131 | } |
0 | #' Display an easing function | |
1 | #' | |
2 | #' This simple helper lets you explore how the different easing functions govern | |
3 | #' the interpolation of data. | |
4 | #' | |
5 | #' @details | |
6 | #' How transitions proceed between states are defined by an easing function. The | |
7 | #' easing function converts the parameterized progression from one state to the | |
8 | #' next to a new number between 0 and 1. `linear` easing is equivalent to | |
9 | #' an identity function that returns the input unchanged. In addition there are | |
10 | #' a range of additional easers available, each with three modifiers. | |
11 | #' | |
12 | #' \strong{Easing modifiers:} | |
13 | #' \describe{ | |
14 | #' \item{-in}{The easing function is applied as-is} | |
15 | #' \item{-out}{The easing function is applied in reverse} | |
16 | #' \item{-in-out}{The first half of the transition it is applied as-is, while | |
17 | #' in the last half it is reversed} | |
18 | #' } | |
19 | #' | |
20 | #' \strong{Easing functions} | |
21 | #' \describe{ | |
22 | #' \item{quadratic}{Models a power-of-2 function} | |
23 | #' \item{cubic}{Models a power-of-3 function} | |
24 | #' \item{quartic}{Models a power-of-4 function} | |
25 | #' \item{quintic}{Models a power-of-5 function} | |
26 | #' \item{sine}{Models a sine function} | |
27 | #' \item{circular}{Models a pi/2 circle arc} | |
28 | #' \item{exponential}{Models an exponential function} | |
29 | #' \item{elastic}{Models an elastic release of energy} | |
30 | #' \item{back}{Models a pullback and relase} | |
31 | #' \item{bounce}{Models the bouncing of a ball} | |
32 | #' } | |
33 | #' | |
34 | #' In addition to this function a good animated explanation can be found | |
35 | #' [here](http://easings.net). | |
36 | #' | |
37 | #' @param ease The name of the easing function to display (see details) | |
38 | #' | |
39 | #' @return This function is called for its side effects | |
40 | #' | |
41 | #' @examples | |
42 | #' # The default - identity | |
43 | #' display_ease('linear') | |
44 | #' | |
45 | #' # A more fancy easer | |
46 | #' display_ease('elastic-in') | |
47 | #' | |
48 | #' @importFrom graphics plot | |
49 | #' @export | |
50 | #' | |
51 | display_ease <- function(ease) { | |
52 | easepoints <- tween_numeric(c(0, 1), 100, ease)[[1]] | |
53 | progress <- seq(0, 1, length.out = 100) | |
54 | plot(progress, easepoints, type = 'l', main = ease, xlab = 'In', | |
55 | ylab = 'Out', bty = 'n') | |
56 | } |
0 | interpolate_numeric_along <- function(data, group, frame, nframes, ease, history, keep_last) { | |
1 | numeric_along_interpolator(data, group, frame, history, keep_last, nframes, ease) | |
2 | } | |
3 | interpolate_logical_along <- function(data, group, frame, nframes, ease, history, keep_last) { | |
4 | res <- numeric_along_interpolator(data, group, frame, history, keep_last, nframes, ease) | |
5 | res[['data']] <- as.logical(round(res[['data']])) | |
6 | res | |
7 | } | |
8 | #' @importFrom grDevices col2rgb rgb | |
9 | #' @importFrom farver convert_colour | |
10 | interpolate_colour_along <- function(data, group, frame, nframes, ease, history, keep_last) { | |
11 | col <- t(col2rgb(data, alpha = TRUE)) | |
12 | data <- convert_colour(col[,1:3, drop = FALSE], from = 'rgb', to = 'lab') | |
13 | int_col <- colour_along_interpolator(cbind(data, col[,4]), group, frame, history, keep_last, nframes, ease) | |
14 | int_col_convert <- convert_colour(as.matrix(int_col[, c('data1', 'data2', 'data3')]), from = 'lab', to = 'rgb') | |
15 | int_col_convert[int_col_convert > 255] <- 255 | |
16 | int_col_convert[int_col_convert < 0] <- 0 | |
17 | int_col$data4[int_col$data4 < 0] <- 0 | |
18 | data.frame( | |
19 | data = rgb(int_col_convert[, 1], int_col_convert[, 2], int_col_convert[, 3], int_col$data4, maxColorValue = 255), | |
20 | group = int_col$group, | |
21 | frame = int_col$frame, | |
22 | stringsAsFactors = FALSE | |
23 | ) | |
24 | } | |
25 | interpolate_constant_along <- function(data, group, frame, nframes, ease, history, keep_last) { | |
26 | constant_along_interpolator(data, group, frame, history, keep_last, nframes, ease) | |
27 | } | |
28 | interpolate_character_along <- function(data, group, frame, nframes, ease, history, keep_last) { | |
29 | constant_along_interpolator(data, group, frame, history, keep_last, nframes, ease) | |
30 | } | |
31 | interpolate_date_along <- function(data, group, frame, nframes, ease, history, keep_last) { | |
32 | data <- as.numeric(data) | |
33 | res <- interpolate_numeric_along(data, group, frame, nframes, ease, history, keep_last) | |
34 | res[['data']] <- as.Date(res[['data']], origin = BASEDATE) | |
35 | res | |
36 | } | |
37 | interpolate_datetime_along <- function(data, group, frame, nframes, ease, history, keep_last) { | |
38 | if (inherits(data, 'POSIXlt')) { | |
39 | warning("POSIXlt converted to POSIXct") | |
40 | } | |
41 | data <- as.numeric(data) | |
42 | res <- interpolate_numeric_along(data, group, frame, nframes, ease, history, keep_last) | |
43 | res[['data']] <- as.POSIXct(res[['data']], origin = BASEDATETIME) | |
44 | res | |
45 | } | |
46 | interpolate_factor_along <- function(data, group, frame, nframes, ease, history, keep_last) { | |
47 | all_levels <- levels(data) | |
48 | data <- as.character(data) | |
49 | res <- interpolate_character_along(data, group, frame, nframes, ease, history, keep_last) | |
50 | res[['data']] <- factor(res[['data']], all_levels) | |
51 | res | |
52 | } | |
53 | interpolate_list_along <- function(data, group, frame, nframes, ease, history, keep_last) { | |
54 | new_data <- list_along_interpolator(data, group, frame, history, keep_last, nframes, ease) | |
55 | attributes(new_data$data) <- attributes(data) | |
56 | new_data | |
57 | } | |
58 | interpolate_numlist_along <- function(data, group, frame, nframes, ease, history, keep_last) { | |
59 | new_data <- numlist_along_interpolator(data, group, frame, history, keep_last, nframes, ease) | |
60 | attributes(new_data$data) <- attributes(data) | |
61 | new_data | |
62 | } | |
63 | get_phase_along <- function(group, frame, nframes, history, keep_last) { | |
64 | phase_along_interpolator(group, frame, history, keep_last, nframes) | |
65 | } |
0 | interpolate_numeric_at <- function(from, to, at, ease) { | |
1 | numeric_at_interpolator(from, to, at, ease) | |
2 | } | |
3 | ||
4 | interpolate_logical_at <- function(from, to, at, ease) { | |
5 | as.logical(round(numeric_at_interpolator(from, to, at, ease))) | |
6 | } | |
7 | ||
8 | interpolate_colour_at <- function(from, to, at, ease) { | |
9 | from <- t(col2rgb(from, alpha = TRUE)) | |
10 | from[, 1:3] <- convert_colour(from[, 1:3, drop = FALSE], from = 'rgb', to = 'lab') | |
11 | to <- t(col2rgb(to, alpha = TRUE)) | |
12 | to[, 1:3] <- convert_colour(to[, 1:3, drop = FALSE], from = 'rgb', to = 'lab') | |
13 | data <- colour_at_interpolator(from, to, at, ease) | |
14 | data[, 1:3] <- convert_colour(data[, 1:3, drop = FALSE], from = 'lab', to = 'rgb') | |
15 | data[data > 255] <- 255 | |
16 | data[data < 0] <- 0 | |
17 | rgb(data, alpha = data[, 4], maxColorValue = 255L) | |
18 | } | |
19 | ||
20 | interpolate_character_at <- function(from, to, at, ease) { | |
21 | constant_at_interpolator(from, to, at, ease) | |
22 | } | |
23 | ||
24 | interpolate_constant_at <- function(from, to, at, ease) { | |
25 | constant_at_interpolator(from, to, at, ease) | |
26 | } | |
27 | ||
28 | interpolate_date_at <- function(from, to, at, ease) { | |
29 | data <- numeric_at_interpolator(as.numeric(from), as.numeric(to), at, ease) | |
30 | as.Date(data, origin = BASEDATE) | |
31 | } | |
32 | ||
33 | interpolate_datetime_at <- function(from, to, at, ease) { | |
34 | if (inherits(from, 'POSIXlt')) { | |
35 | warning("POSIXlt converted to POSIXct") | |
36 | } | |
37 | data <- numeric_at_interpolator(as.numeric(from), as.numeric(to), at, ease) | |
38 | as.POSIXct(data, origin = BASEDATETIME) | |
39 | } | |
40 | ||
41 | interpolate_factor_at <- function(from, to, at, ease) { | |
42 | all_levels <- unique(c(levels(from), levels(to))) | |
43 | data <- constant_at_interpolator(as.character(from), as.character(to), at, ease) | |
44 | factor(data, all_levels) | |
45 | } | |
46 | ||
47 | interpolate_list_at <- function(from, to, at, ease) { | |
48 | data <- list_at_interpolator(from, to, at, ease) | |
49 | attributes(data) <- attributes(from) | |
50 | data | |
51 | } | |
52 | ||
53 | interpolate_numlist_at <- function(from, to, at, ease) { | |
54 | data <- numlist_at_interpolator(from, to, at, ease) | |
55 | attributes(data) <- attributes(from) | |
56 | data | |
57 | } |
0 | interpolate_numeric_element <- function(data, group, frame, ease) { | |
1 | numeric_element_interpolator(data, group, frame, ease) | |
2 | } | |
3 | interpolate_logical_element <- function(data, group, frame, ease) { | |
4 | res <- numeric_element_interpolator(as.numeric(data), group, frame, ease) | |
5 | res[['data']] <- as.logical(round(res[['data']])) | |
6 | res | |
7 | } | |
8 | #' @importFrom grDevices col2rgb rgb | |
9 | #' @importFrom farver convert_colour | |
10 | interpolate_colour_element <- function(data, group, frame, ease) { | |
11 | col <- t(col2rgb(data, alpha = TRUE)) | |
12 | data <- convert_colour(col[,1:3, drop = FALSE], from = 'rgb', to = 'lab') | |
13 | int_col <- colour_element_interpolator(cbind(data, col[,4]), group, frame, ease) | |
14 | int_col_convert <- convert_colour(as.matrix(int_col[, c('data1', 'data2', 'data3')]), from = 'lab', to = 'rgb') | |
15 | int_col_convert[int_col_convert > 255] <- 255 | |
16 | int_col_convert[int_col_convert < 0] <- 0 | |
17 | int_col$data4[int_col$data4 < 0] <- 0 | |
18 | data.frame( | |
19 | data = rgb(int_col_convert[, 1], int_col_convert[, 2], int_col_convert[, 3], int_col$data4, maxColorValue = 255), | |
20 | group = int_col$group, | |
21 | frame = int_col$frame, | |
22 | stringsAsFactors = FALSE | |
23 | ) | |
24 | } | |
25 | interpolate_constant_element <- function(data, group, frame, ease) { | |
26 | constant_element_interpolator(data, group, frame, ease) | |
27 | } | |
28 | interpolate_character_element <- function(data, group, frame, ease) { | |
29 | interpolate_constant_element(data, group, frame, ease) | |
30 | } | |
31 | interpolate_date_element <- function(data, group, frame, ease) { | |
32 | data <- as.numeric(data) | |
33 | res <- interpolate_numeric_element(data, group, frame, ease) | |
34 | res[['data']] <- as.Date(res[['data']], origin = BASEDATE) | |
35 | res | |
36 | } | |
37 | interpolate_datetime_element <- function(data, group, frame, ease) { | |
38 | if (inherits(data, 'POSIXlt')) { | |
39 | warning("POSIXlt converted to POSIXct") | |
40 | } | |
41 | data <- as.numeric(data) | |
42 | res <- interpolate_numeric_element(data, group, frame, ease) | |
43 | res[['data']] <- as.POSIXct(res[['data']], origin = BASEDATETIME) | |
44 | res | |
45 | } | |
46 | interpolate_factor_element <- function(data, group, frame, ease) { | |
47 | all_levels <- levels(data) | |
48 | data <- as.character(data) | |
49 | res <- interpolate_character_element(data, group, frame, ease) | |
50 | res[['data']] <- factor(res[['data']], all_levels) | |
51 | res | |
52 | } | |
53 | interpolate_list_element <- function(data, group, frame, ease) { | |
54 | new_data <- list_element_interpolator(data, group, frame, ease) | |
55 | attributes(new_data$data) <- attributes(data) | |
56 | new_data | |
57 | } | |
58 | interpolate_numlist_element <- function(data, group, frame, ease) { | |
59 | new_data <- numlist_element_interpolator(data, group, frame, ease) | |
60 | attributes(new_data$data) <- attributes(data) | |
61 | new_data | |
62 | } | |
63 | get_phase_element <- function(data, group, frame, ease) { | |
64 | phase_element_interpolator(data, group, frame, ease) | |
65 | } |
0 | interpolate_numeric_fill <- function(data, ease) { | |
1 | numeric_fill_interpolator(data, ease) | |
2 | } | |
3 | interpolate_logical_fill <- function(data, ease) { | |
4 | as.logical(round(numeric_fill_interpolator(as.numeric(data), ease))) | |
5 | } | |
6 | #' @importFrom grDevices col2rgb rgb | |
7 | #' @importFrom farver convert_colour | |
8 | interpolate_colour_fill <- function(data, ease) { | |
9 | NA_col <- is.na(data) | |
10 | data <- t(col2rgb(data, alpha = TRUE)) | |
11 | data[, 1:3] <- convert_colour(data[, 1:3, drop = FALSE], from = 'rgb', to = 'lab') | |
12 | data[NA_col,] <- NA | |
13 | data <- colour_fill_interpolator(data, ease) | |
14 | NA_col <- is.na(data[, 1]) | |
15 | data[NA_col, 1:3] <- convert_colour(data[NA_col, 1:3, drop = FALSE], from = 'lab', to = 'rgb') | |
16 | data[data > 255] <- 255 | |
17 | data[data < 0] <- 0 | |
18 | all_data <- rep(NA, nrow(data)) | |
19 | all_data[!NA_col] <- rgb(data[!NA_col, , drop = FALSE], alpha = data[!NA_col, 4], maxColorValue = 255L) | |
20 | all_data | |
21 | } | |
22 | interpolate_constant_fill <- function(data, ease) { | |
23 | constant_fill_interpolator(data, ease) | |
24 | } | |
25 | interpolate_character_fill <- function(data, ease) { | |
26 | interpolate_constant_fill(data, ease) | |
27 | } | |
28 | interpolate_date_fill <- function(data, ease) { | |
29 | data <- lapply(data, as.numeric) | |
30 | as.Date(interpolate_numeric_fill(data, ease), origin = BASEDATE) | |
31 | } | |
32 | interpolate_datetime_fill <- function(data, ease) { | |
33 | if (inherits(data, 'POSIXlt')) { | |
34 | warning("POSIXlt converted to POSIXct") | |
35 | } | |
36 | as.POSIXct(interpolate_numeric_fill(as.numeric(data), ease), origin = BASEDATETIME) | |
37 | } | |
38 | interpolate_factor_fill <- function(data, ease) { | |
39 | all_levels <- levels(data) | |
40 | factor(interpolate_character_fill(as.character(data), ease), all_levels) | |
41 | } | |
42 | interpolate_list_fill <- function(data, ease) { | |
43 | new_data <- list_fill_interpolator(data, ease) | |
44 | attributes(new_data) <- attributes(data) | |
45 | new_data | |
46 | } | |
47 | interpolate_numlist_fill <- function(data, ease) { | |
48 | new_data <- numlist_fill_interpolator(lapply(data, as.numeric), ease) | |
49 | attributes(new_data) <- attributes(data) | |
50 | new_data | |
51 | } |
0 | interpolate_numeric_state <- function(data, states) { | |
1 | numeric_state_interpolator(data, states) | |
2 | } | |
3 | interpolate_logical_state <- function(data, states) { | |
4 | as.logical(round(numeric_state_interpolator(lapply(data, as.numeric), states))) | |
5 | } | |
6 | #' @importFrom grDevices col2rgb rgb | |
7 | #' @importFrom farver convert_colour | |
8 | interpolate_colour_state <- function(data, states) { | |
9 | data <- lapply(data, function(d){ | |
10 | col <- t(col2rgb(d, alpha = TRUE)) | |
11 | col_conv <- convert_colour(col[,1:3, drop = FALSE], from = 'rgb', to = 'lab') | |
12 | cbind(col_conv, col[,4]) | |
13 | }) | |
14 | int_col <- colour_state_interpolator(data, states) | |
15 | alpha <- int_col[,4] | |
16 | alpha[alpha > 255] <- 255 | |
17 | alpha[alpha < 0] <- 0 | |
18 | int_col <- convert_colour(int_col[, 1:3, drop = FALSE], from = 'lab', to = 'rgb') | |
19 | int_col[int_col > 255] <- 255 | |
20 | int_col[int_col < 0] <- 0 | |
21 | rgb(int_col[, 1], int_col[, 2], int_col[, 3], alpha, maxColorValue = 255) | |
22 | } | |
23 | interpolate_constant_state <- function(data, states) { | |
24 | constant_state_interpolator(data, states) | |
25 | } | |
26 | interpolate_character_state <- function(data, states) { | |
27 | interpolate_constant_state(data, states) | |
28 | } | |
29 | interpolate_date_state <- function(data, states) { | |
30 | data <- lapply(data, as.numeric) | |
31 | as.Date(interpolate_numeric_state(data, states), origin = BASEDATE) | |
32 | } | |
33 | interpolate_datetime_state <- function(data, states) { | |
34 | if (inherits(data[[1]], 'POSIXlt')) { | |
35 | warning("POSIXlt converted to POSIXct") | |
36 | } | |
37 | data <- lapply(data, as.numeric) | |
38 | as.POSIXct(interpolate_numeric_state(data, states), origin = BASEDATETIME) | |
39 | } | |
40 | interpolate_factor_state <- function(data, states) { | |
41 | all_levels <- Reduce(union, lapply(data, levels)) | |
42 | data <- lapply(data, as.character) | |
43 | factor(interpolate_character_state(data, states), all_levels) | |
44 | } | |
45 | interpolate_list_state <- function(data, states) { | |
46 | new_data <- list_state_interpolator(data, states) | |
47 | attributes(new_data) <- attributes(data) | |
48 | new_data | |
49 | } | |
50 | interpolate_numlist_state <- function(data, states) { | |
51 | new_data <- numlist_state_interpolator(lapply(data, lapply, as.numeric), states) | |
52 | attributes(new_data) <- attributes(data) | |
53 | new_data | |
54 | } | |
55 | ||
56 | get_phase_state <- function(data, states) { | |
57 | phase_state_interpolator(data, states) | |
58 | } |
0 | #' Create simple tweens | |
1 | #' | |
2 | #' This set of functions can be used to interpolate between single data types, | |
3 | #' i.e. data not part of data.frames but stored in vectors. All functions come | |
4 | #' in two flavours: the standard and a *_t version. The standard reads the data | |
5 | #' as a list of states, each tween matched element-wise from state to state. The | |
6 | #' *_t version uses the transposed representation where each element is a vector | |
7 | #' of states. The standard approach can be used when each tween has the same | |
8 | #' number of states and you want to control the number of point in each state | |
9 | #' transition. The latter is useful when each tween consists of different | |
10 | #' numbers of states and/or you want to specify the total number of points for | |
11 | #' each tween. | |
12 | #' | |
13 | #' @section Difference Between `tween_numeric` and `approx()`: | |
14 | #' `tween_numeric` (and `tween_numeric_t`) is superficially equivalent to | |
15 | #' [stats::approx()], but there are differences. | |
16 | #' [stats::approx()] will create evenly spaced points, at the expense | |
17 | #' of not including the actual points in the input, while the reverse is true | |
18 | #' for `tween_numeric`. Apart from that `tween_numeric` of course supports easing | |
19 | #' functions and is vectorized. | |
20 | #' | |
21 | #' @details | |
22 | #' `tween` and `tween_t` are wrappers around the other functions that tries to guess | |
23 | #' the type of input data and choose the appropriate tween function. Unless you | |
24 | #' have data that could be understood as a colour but is in fact a character | |
25 | #' vector it should be safe to use these wrappers. It is probably safer and more | |
26 | #' verbose to use the explicit functions within package code as they circumvent | |
27 | #' the type inference and checks whether the input data matches the tween | |
28 | #' function. | |
29 | #' | |
30 | #' `tween_numeric` will provide a linear interpolation between the points based on | |
31 | #' the sequence returned by the easing function. `tween_date` and `tween_datetime` | |
32 | #' converts to numeric, produces the tweening, and converts back again. | |
33 | #' `tween_colour` converts colours into Lab and does the interpolation there, | |
34 | #' converting back to sRGB after the tweening is done. `tween_constant` is a | |
35 | #' catchall that converts the input into character and interpolates by switching | |
36 | #' between states halfway through the transition. | |
37 | #' | |
38 | #' The meaning of the `n` and `ease` arguments differs somewhat | |
39 | #' between the standard and *_t versions of the functions. In the standard | |
40 | #' function `n` and `ease` refers to the length and easing function of | |
41 | #' each transition, being recycled if necessary to `length(data) - 1`. In | |
42 | #' the *_t functions `n` and `ease` refers to the total length of each | |
43 | #' tween and the easing function to be applied to all transition for each tween. | |
44 | #' The will both be recycled to `length(data)`. | |
45 | #' | |
46 | #' @param data A list of vectors or a single vector. In the standard functions | |
47 | #' each element in the list must be of equal length; for the *_t functions | |
48 | #' lengths can differ. If a single vector is used it will be eqivalent to using | |
49 | #' `as.list(data)` for the standard functions and `list(data)` for the | |
50 | #' *_t functions. | |
51 | #' | |
52 | #' @param n The number of elements per transition or tween. See details | |
53 | #' | |
54 | #' @param ease The easing function to use for each transition or tween. See | |
55 | #' details. Defaults to `'linear'` | |
56 | #' | |
57 | #' @return A list with an element for each tween. That means that the length of | |
58 | #' the return is equal to the length of the elements in `data` for the | |
59 | #' standard functions and equal to the length of `data` for the *_t | |
60 | #' functions. | |
61 | #' | |
62 | #' @examples | |
63 | #' tween_numeric(list(1:3, 10:8, c(20, 60, 30)), 10) | |
64 | #' | |
65 | #' tween_colour_t(list(colours()[1:4], colours()[1:2], colours()[25:100]), 100) | |
66 | #' | |
67 | #' @export | |
68 | #' | |
69 | tween <- function(data, n, ease = 'linear') { | |
70 | type <- guessType(data) | |
71 | switch( | |
72 | type, | |
73 | numeric = tween_numeric(data, n, ease), | |
74 | date = tween_date(data, n, ease), | |
75 | datetime = tween_datetime(data, n, ease), | |
76 | colour = tween_colour(data, n, ease), | |
77 | tween_constant_t(data, n, ease) | |
78 | ) | |
79 | } | |
80 | #' @rdname tween | |
81 | #' @export | |
82 | tween_t <- function(data, n, ease = 'linear') { | |
83 | type <- guessType(data) | |
84 | switch( | |
85 | type, | |
86 | numeric = tween_numeric_t(data, n, ease), | |
87 | date = tween_date_t(data, n, ease), | |
88 | datetime = tween_datetime_t(data, n, ease), | |
89 | colour = tween_colour_t(data, n, ease), | |
90 | tween_constant_t(data, n, ease) | |
91 | ) | |
92 | } | |
93 | #' @importFrom utils head | |
94 | guessType <- function(data) { | |
95 | data <- unlist(data) | |
96 | if (is.character(data)) { | |
97 | convert <- try(suppressWarnings(col2rgb(head(data, 100))), | |
98 | silent = TRUE) | |
99 | if (!inherits(convert, 'try-error')) { | |
100 | if (!anyNA(convert)) { | |
101 | return('colour') | |
102 | } | |
103 | } | |
104 | } | |
105 | if (inherits(data, 'Date')) { | |
106 | return('date') | |
107 | } | |
108 | if (inherits(data, 'POSIXt')) { | |
109 | return('datetime') | |
110 | } | |
111 | if (is.numeric(data)) { | |
112 | return('numeric') | |
113 | } | |
114 | 'unknown' | |
115 | } |
0 | #' Interpolate data along a given dimension | |
1 | #' | |
2 | #' This tween takes groups of rows along with the time for each row and | |
3 | #' calculates the exact value at each at each frame. Further it allows for | |
4 | #' keeping the subsequent raw data from previous frame as well as letting the | |
5 | #' final row linger beyond its time. It especially useful for data that should | |
6 | #' be visualised as lines that are drawn along the x-axis, but can of course | |
7 | #' also be used for other dimensions as well (even dimensions not corresponding | |
8 | #' to any axis). | |
9 | #' | |
10 | #' @inheritParams tween_components | |
11 | #' @param along The "time" point for each row | |
12 | #' @param history Should earlier datapoints be kept in subsequent frames | |
13 | #' @param keep_last Should the last point of each id be kept beyond its time | |
14 | #' | |
15 | #' @return A data.frame with the same columns as `.data` along with `.id` giving | |
16 | #' the component id, `.phase` giving the state of each component in each frame, | |
17 | #' and `.frame` giving the frame membership of each row. | |
18 | #' | |
19 | #' @family data.frame tween | |
20 | #' | |
21 | #' @importFrom rlang enquo quo_is_null eval_tidy | |
22 | #' @export | |
23 | tween_along <- function(.data, ease, nframes, along, id = NULL, range = NULL, history = TRUE, keep_last = FALSE) { | |
24 | along <- enquo(along) | |
25 | along <- as.numeric(eval_tidy(along, .data)) | |
26 | id <- enquo(id) | |
27 | id <- if (quo_is_null(id)) rep(1, nrow(.data)) else eval_tidy(id, .data) | |
28 | .data <- .complete_along(.data, along, id) | |
29 | ||
30 | if (length(ease) == 1) ease <- rep(ease, ncol(.data) - 3) | |
31 | if (length(ease) == ncol(.data) - 3) { | |
32 | ease <- c(ease, 'linear', 'linear', 'linear') # To account for .phase and .id columns | |
33 | } else { | |
34 | stop('Ease must be either a single string or one for each column', call. = FALSE) | |
35 | } | |
36 | stopifnot(length(nframes) == 1 && is.numeric(nframes) && nframes %% 1 == 0) | |
37 | ||
38 | timerange <- if (is.null(range)) range(.data$.time) else range | |
39 | timerange <- as.numeric(timerange) | |
40 | if (diff(timerange) == 0) stop('range must have a length', call. = FALSE) | |
41 | framelength <- diff(timerange) / (nframes - 1) | |
42 | frame <- 1 + (nframes - 1) * (.data$.time - timerange[1]) / diff(timerange) | |
43 | colClasses <- col_classes(.data) | |
44 | tweendata <- lapply(seq_along(.data), function(i) { | |
45 | d <- .data[[i]] | |
46 | e <- ease[i] | |
47 | switch( | |
48 | colClasses[i], | |
49 | numeric = interpolate_numeric_along(d, .data$.id, frame, nframes, e, history, keep_last), | |
50 | logical = interpolate_logical_along(d, .data$.id, frame, nframes, e, history, keep_last), | |
51 | factor = interpolate_factor_along(d, .data$.id, frame, nframes, e, history, keep_last), | |
52 | character = interpolate_character_along(d, .data$.id, frame, nframes, e, history, keep_last), | |
53 | colour = interpolate_colour_along(d, .data$.id, frame, nframes, e, history, keep_last), | |
54 | date = interpolate_date_along(d, .data$.id, frame, nframes, e, history, keep_last), | |
55 | datetime = interpolate_datetime_along(d, .data$.id, frame, nframes, e, history, keep_last), | |
56 | constant = interpolate_constant_along(d, .data$.id, frame, nframes, e, history, keep_last), | |
57 | numlist = interpolate_numlist_along(d, .data$.id, frame, nframes, e, history, keep_last), | |
58 | list = interpolate_list_along(d, .data$.id, frame, nframes, e, history, keep_last), | |
59 | phase = get_phase_along(.data$.id, frame, nframes, history, keep_last) | |
60 | ) | |
61 | }) | |
62 | tweenInfo <- tweendata[[1]][, c('group', 'frame')] | |
63 | tweendata <- lapply(tweendata, `[[`, i = 'data') | |
64 | tweendata <- structure(tweendata, names = names(.data), row.names = seq_along(tweendata[[1]]), class = 'data.frame') | |
65 | tweendata$.frame <- tweenInfo$frame | |
66 | tweendata$.id <- tweenInfo$group | |
67 | attr(tweendata, 'framelength') <- framelength | |
68 | tweendata[order(tweendata$.frame, tweendata$.id), , drop = FALSE] | |
69 | } | |
70 | ||
71 | .complete_along <- function(data, along, id) { | |
72 | if (length(along) != nrow(data) || length(id) != nrow(data)) { | |
73 | stop('along and id must be the same length as the number of rows in data', call. = FALSE) | |
74 | } | |
75 | data <- data[order(id), , drop = FALSE] | |
76 | along <- along[order(id)] | |
77 | id <- sort(id) | |
78 | data$.id <- id | |
79 | data$.phase <- 'raw' | |
80 | data$.time <- along | |
81 | data | |
82 | } |
0 | #' Tween a data.frame of appearances | |
1 | #' | |
2 | #' This function is intended for use when you have a data.frame of events at | |
3 | #' different time points. This could be the appearance of an observation for | |
4 | #' example. This function replicates your data `nframes` times and | |
5 | #' calculates the duration of each frame. At each frame each row is | |
6 | #' assigned an age based on the progression of frames and the entry point of in | |
7 | #' time for that row. A negative age means that the row has not appeared yet. | |
8 | #' | |
9 | #' @param data A data.frame to tween | |
10 | #' | |
11 | #' @param time The name of the column that holds the time dimension. This does | |
12 | #' not need to hold time data in the strictest sence - any numerical type will | |
13 | #' do | |
14 | #' | |
15 | #' @param timerange The range of time to create the tween for. If missing it | |
16 | #' will defaults to the range of the time column | |
17 | #' | |
18 | #' @param nframes The number of frames to create for the tween. If missing it | |
19 | #' will create a frame for each full unit in `timerange` (e.g. | |
20 | #' `timerange = c(1, 10)` will give `nframes = 10`) | |
21 | #' | |
22 | #' @return A data.frame as `data` but repeated `nframes` times and | |
23 | #' with the additional columns `.age` and `.frame` | |
24 | #' | |
25 | #' @family data.frame tween | |
26 | #' | |
27 | #' @examples | |
28 | #' data <- data.frame( | |
29 | #' x = rnorm(100), | |
30 | #' y = rnorm(100), | |
31 | #' time = sample(50, 100, replace = TRUE) | |
32 | #' ) | |
33 | #' | |
34 | #' data <- tween_appear(data, 'time', nframes = 200) | |
35 | #' | |
36 | #' @export | |
37 | #' | |
38 | tween_appear <- function(data, time, timerange, nframes) { | |
39 | if (missing(timerange) || is.null(timerange)) { | |
40 | timerange <- range(data[[time]]) | |
41 | } | |
42 | if (missing(nframes) || is.null(nframes)) { | |
43 | nframes <- ceiling(diff(timerange) + 1) | |
44 | } | |
45 | framelength <- diff(timerange) / nframes | |
46 | ||
47 | tweendata <- lapply(seq_len(nframes) - 1, function(f) { | |
48 | timepoint <- f * framelength | |
49 | data$.age <- timepoint - data[[time]] | |
50 | data$.frame <- f | |
51 | data | |
52 | }) | |
53 | tweendata <- do.call(rbind, tweendata) | |
54 | attr(tweendata, 'framelength') <- framelength | |
55 | tweendata | |
56 | } |
0 | #' Get a specific position between two states | |
1 | #' | |
2 | #' This tween allows you to query a specific postion between two states rather | |
3 | #' than generate evenly spaced states. It can work with either data.frames or | |
4 | #' single vectors and each row/element can have its own position and easing. | |
5 | #' | |
6 | #' @param from,to A data.frame or vector of the same type. If either is of | |
7 | #' length/nrow 1 it will get repeated to match the length of the other | |
8 | #' @param at A numeric between 0 and 1 recycled to match the nrow/length of | |
9 | #' `from` | |
10 | #' @param ease A character vector giving valid easing functions. Recycled to | |
11 | #' match the ncol of `from` | |
12 | #' | |
13 | #' @return If `from`/`to` is a data.frame then a data.frame with the same | |
14 | #' columns. If `from`/`to` is a vector then a vector. | |
15 | #' | |
16 | #' @export | |
17 | #' | |
18 | #' @examples | |
19 | #' tween_at(mtcars[1:6, ], mtcars[6:1, ], runif(6), 'cubic-in-out') | |
20 | #' | |
21 | tween_at <- function(from, to, at, ease) { | |
22 | single_vec <- !is.data.frame(from) | |
23 | if (single_vec) { | |
24 | if (length(from) == 0 || length(to) == 0) return(to[integer()]) | |
25 | from_df <- data.frame(data = rep(NA, length(from))) | |
26 | to_df <- data.frame(data = rep(NA, length(to))) | |
27 | from_df$data <- from | |
28 | to_df$data <- to | |
29 | from <- from_df | |
30 | to <- to_df | |
31 | } else { | |
32 | if (nrow(from) == 0 || nrow(to) == 0) return(to[integer(), ]) | |
33 | } | |
34 | if (length(at) == 0) stop('at must have length > 0', call. = FALSE) | |
35 | if (nrow(from) == 1) from <- from[rep(1, nrow(to)), , drop = FALSE] | |
36 | if (nrow(to) == 1) to <- to[rep(1, nrow(from)), , drop = FALSE] | |
37 | if (nrow(from) != nrow(to)) { | |
38 | stop('from and to must be same length', call. = FALSE) | |
39 | } | |
40 | stopifnot(names(from) == names(to)) | |
41 | at <- rep(at, length.out = nrow(from)) | |
42 | ease <- rep(ease, length.out = ncol(from)) | |
43 | classes <- col_classes(from) | |
44 | stopifnot(identical(classes, col_classes(to))) | |
45 | tweendata <- lapply(seq_along(classes), function(i) { | |
46 | switch( | |
47 | classes[i], | |
48 | numeric = interpolate_numeric_at(from[[i]], to[[i]], at, ease[i]), | |
49 | logical = interpolate_logical_at(from[[i]], to[[i]], at, ease[i]), | |
50 | factor = interpolate_factor_at(from[[i]], to[[i]], at, ease[i]), | |
51 | character = interpolate_character_at(from[[i]], to[[i]], at, ease[i]), | |
52 | colour = interpolate_colour_at(from[[i]], to[[i]], at, ease[i]), | |
53 | date = interpolate_date_at(from[[i]], to[[i]], at, ease[i]), | |
54 | datetime = interpolate_datetime_at(from[[i]], to[[i]], at, ease[i]), | |
55 | constant = interpolate_constant_at(from[[i]], to[[i]], at, ease[i]), | |
56 | numlist = interpolate_numlist_at(from[[i]], to[[i]], at, ease[i]), | |
57 | list = interpolate_list_at(from[[i]], to[[i]], at, ease[i]) | |
58 | ) | |
59 | }) | |
60 | if (single_vec) return(tweendata[[1]]) | |
61 | ||
62 | structure(tweendata, names = names(from), row.names = seq_along(tweendata[[1]]), class = 'data.frame') | |
63 | } |
0 | #' @rdname tween | |
1 | #' | |
2 | #' @export | |
3 | tween_colour <- function(data, n, ease = 'linear') { | |
4 | data <- as.list(data) | |
5 | prepData <- prepareTween(data, n, ease) | |
6 | if (anyNA(suppressWarnings(col2rgb(unlist(prepData$data))))) { | |
7 | stop('all elements in data must be convertible to colour') | |
8 | } | |
9 | tweendata <- do.call(interpolate_colour_state, prepData) | |
10 | unname(split(tweendata, | |
11 | rep(seq_along(data[[1]]), length.out = length(tweendata)))) | |
12 | } | |
13 | #' @rdname tween | |
14 | #' | |
15 | #' @export | |
16 | tween_color <- tween_colour | |
17 | ||
18 | #' @rdname tween | |
19 | #' | |
20 | #' @export | |
21 | tween_colour_t <- function(data, n, ease = 'linear') { | |
22 | if (!is.list(data)) { | |
23 | data <- list(data) | |
24 | } | |
25 | prepData <- prepareTweenTranspose(data, n, ease) | |
26 | if (anyNA(suppressWarnings(col2rgb(unlist(prepData$data))))) { | |
27 | stop('all elements in data must be convertible to colour') | |
28 | } | |
29 | tweendata <- do.call(interpolate_colour_state, prepData) | |
30 | unname(split(tweendata, | |
31 | rep(seq_along(data), rep(n, length.out = length(data))))) | |
32 | } | |
33 | #' @rdname tween | |
34 | #' | |
35 | #' @export | |
36 | tween_color_t <- tween_colour_t |
0 | #' Interpolate individual component | |
1 | #' | |
2 | #' This function is much like [tween_elements()] but with a slightly different | |
3 | #' syntax and support for many of the newer features such as enter/exits and | |
4 | #' tween phase identification. Furthermore it uses tidy evaluation for time and | |
5 | #' id, making it easier to change these on the fly. The biggest change in terms | |
6 | #' of functionality compared to `tween_elements()` is that the easing function | |
7 | #' is now given per column and not per row. If different easing functions are | |
8 | #' needed for each transition then `tween_elements()` is needed. | |
9 | #' | |
10 | #' @inheritParams tween_state | |
11 | #' | |
12 | #' @param .data A data.frame with components at different stages | |
13 | #' | |
14 | #' @param time An unquoted expression giving the timepoint for the different | |
15 | #' stages of the components. Will be evaluated in the context of `.data` so can | |
16 | #' refer to a column from that | |
17 | #' | |
18 | #' @param id An unquoted expression giving the component id for each row. Will | |
19 | #' be evaluated in the context of `.data` so can refer to a column from that | |
20 | #' | |
21 | #' @param range The range of time points to include in the tween. If `NULL` it | |
22 | #' will use the range of `time` | |
23 | #' | |
24 | #' @param enter_length,exit_length The lenght of the opening and closing | |
25 | #' transitions if `enter` and/or `exit` is given. Measured in the same units as | |
26 | #' `time` | |
27 | #' | |
28 | #' @return A data.frame with the same columns as `.data` along with `.id` giving | |
29 | #' the component id, `.phase` giving the state of each component in each frame, | |
30 | #' and `.frame` giving the frame membership of each row. | |
31 | #' | |
32 | #' @family data.frame tween | |
33 | #' | |
34 | #' @examples | |
35 | #' | |
36 | #' from_zero <- function(x) {x$x <- 0; x} | |
37 | #' | |
38 | #' data <- data.frame( | |
39 | #' x = c(1, 2, 2, 1, 2, 2), | |
40 | #' y = c(1, 2, 2, 2, 1, 1), | |
41 | #' time = c(1, 4, 10, 4, 8, 10), | |
42 | #' id = c(1, 1, 1, 2, 2, 2) | |
43 | #' ) | |
44 | #' | |
45 | #' data <- tween_components(data, 'cubic-in-out', nframes = 100, time = time, | |
46 | #' id = id, enter = from_zero, enter_length = 4) | |
47 | #' | |
48 | #' @export | |
49 | #' @importFrom rlang enquo eval_tidy | |
50 | #' | |
51 | tween_components <- function(.data, ease, nframes, time, id = NULL, range = NULL, enter = NULL, exit = NULL, enter_length = 0, exit_length = 0) { | |
52 | time <- enquo(time) | |
53 | time <- eval_tidy(time, .data) | |
54 | id <- enquo(id) | |
55 | id <- if (quo_is_null(id)) rep(1, nrow(.data)) else eval_tidy(id, .data) | |
56 | if (is.null(enter_length)) enter_length <- 0 | |
57 | if (is.null(exit_length)) exit_length <- 0 | |
58 | .data <- .complete_components(.data, time, id, enter, exit, enter_length, exit_length) | |
59 | ||
60 | .tween_individuals(.data, ease, nframes, range) | |
61 | } | |
62 | ||
63 | .tween_individuals <- function(.data, ease, nframes, range) { | |
64 | if (nframes == 0) return(.data[integer(), , drop = FALSE]) | |
65 | if (nrow(.data) == 0) return(.data) | |
66 | if (length(ease) == 1) ease <- rep(ease, ncol(.data) - 3) | |
67 | if (length(ease) == ncol(.data) - 3) { | |
68 | ease <- c(ease, 'linear', 'linear', 'linear') # To account for .phase and .id columns | |
69 | } else { | |
70 | stop('Ease must be either a single string or one for each column', call. = FALSE) | |
71 | } | |
72 | stopifnot(length(nframes) == 1 && is.numeric(nframes) && nframes %% 1 == 0) | |
73 | ||
74 | timerange <- if (is.null(range)) range(.data$.time) else range | |
75 | if (diff(timerange) == 0) stop('range must have a length', call. = FALSE) | |
76 | framelength <- diff(timerange) / (nframes - 1) | |
77 | .data <- .data[order(.data$.id, .data$.time), , drop = FALSE] | |
78 | frame <- round((.data$.time - min(timerange[1])) / framelength) + 1 | |
79 | .data$.time <- NULL | |
80 | colClasses <- col_classes(.data) | |
81 | tweendata <- lapply(seq_along(.data), function(i) { | |
82 | d <- .data[[i]] | |
83 | e <- rep(ease[i], length(d)) | |
84 | switch( | |
85 | colClasses[i], | |
86 | numeric = interpolate_numeric_element(d, .data$.id, frame, e), | |
87 | logical = interpolate_logical_element(d, .data$.id, frame, e), | |
88 | factor = interpolate_factor_element(d, .data$.id, frame, e), | |
89 | character = interpolate_character_element(d, .data$.id, frame, e), | |
90 | colour = interpolate_colour_element(d, .data$.id, frame, e), | |
91 | date = interpolate_date_element(d, .data$.id, frame, e), | |
92 | datetime = interpolate_datetime_element(d, .data$.id, frame, e), | |
93 | constant = interpolate_constant_element(d, .data$.id, frame, e), | |
94 | numlist = interpolate_numlist_element(d, .data$.id, frame, e), | |
95 | list = interpolate_list_element(d, .data$.id, frame, e), | |
96 | phase = get_phase_element(d, .data$.id, frame, e) | |
97 | ) | |
98 | }) | |
99 | tweenInfo <- tweendata[[1]][, c('group', 'frame')] | |
100 | tweendata <- lapply(tweendata, `[[`, i = 'data') | |
101 | tweendata <- structure(tweendata, names = names(.data), row.names = seq_along(tweendata[[1]]), class = 'data.frame') | |
102 | tweendata$.frame <- tweenInfo$frame | |
103 | tweendata$.id <- tweenInfo$group | |
104 | tweendata <- tweendata[tweendata$.frame >= 1 & tweendata$.frame <= nframes, , drop = FALSE] | |
105 | attr(tweendata, 'framelength') <- framelength | |
106 | tweendata[order(tweendata$.frame, tweendata$.id), , drop = FALSE] | |
107 | } | |
108 | ||
109 | .complete_components <- function(data, time, id, enter, exit, enter_length, exit_length) { | |
110 | if (length(id) != nrow(data) || length(time) != nrow(data)) { | |
111 | stop('id and time must have the same length as the number of rows in data', call. = FALSE) | |
112 | } | |
113 | data$.id <- id | |
114 | data$.phase <- rep('raw', nrow(data)) | |
115 | data$.time <- time | |
116 | if (any(!is.null(enter), !is.null(exit))) { | |
117 | time_ord <- order(time) | |
118 | if (!is.null(enter)) { | |
119 | enter_data <- enter(data[time_ord[!duplicated(id[time_ord])], , drop = FALSE]) | |
120 | enter_data$.phase <- 'enter' | |
121 | enter_data$.time <- enter_data$.time - enter_length | |
122 | } else { | |
123 | enter_data <- data[0, , drop = FALSE] | |
124 | } | |
125 | if (!is.null(exit)) { | |
126 | exit_data <- exit(data[time_ord[!duplicated(id[time_ord], fromLast = TRUE)], , drop = FALSE]) | |
127 | exit_data$.phase <- 'exit' | |
128 | exit_data$.time <- exit_data$.time + exit_length | |
129 | } else { | |
130 | exit_data <- data[0, , drop = FALSE] | |
131 | } | |
132 | data <- rbind(enter_data, data, exit_data) | |
133 | } | |
134 | data | |
135 | } |
0 | #' @rdname tween | |
1 | #' | |
2 | #' @export | |
3 | tween_constant <- function(data, n, ease = 'linear') { | |
4 | data <- as.list(data) | |
5 | data <- lapply(data, as.character) | |
6 | prepData <- prepareTween(data, n, ease) | |
7 | tweendata <- do.call(interpolate_character_state, prepData) | |
8 | unname(split(tweendata, | |
9 | rep(seq_along(data[[1]]), length.out = length(tweendata)))) | |
10 | } | |
11 | ||
12 | #' @rdname tween | |
13 | #' | |
14 | #' @export | |
15 | tween_constant_t <- function(data, n, ease = 'linear') { | |
16 | if (!is.list(data)) { | |
17 | data <- list(data) | |
18 | } | |
19 | data <- lapply(data, as.character) | |
20 | prepData <- prepareTweenTranspose(data, n, ease) | |
21 | tweendata <- do.call(interpolate_character_state, prepData) | |
22 | unname(split(tweendata, | |
23 | rep(seq_along(data), rep(n, length.out = length(data))))) | |
24 | } |
0 | #' @rdname tween | |
1 | #' | |
2 | #' @export | |
3 | tween_date <- function(data, n, ease = 'linear') { | |
4 | data <- as.list(data) | |
5 | prepData <- prepareTween(data, n, ease) | |
6 | if (!all(sapply(prepData$data, inherits, what = 'Date'))) { | |
7 | stop('data must consist of Date elements') | |
8 | } | |
9 | tweendata <- do.call(interpolate_date_state, prepData) | |
10 | unname(split(tweendata, | |
11 | rep(seq_along(data[[1]]), length.out = length(tweendata)))) | |
12 | } | |
13 | ||
14 | #' @rdname tween | |
15 | #' | |
16 | #' @export | |
17 | tween_date_t <- function(data, n, ease = 'linear') { | |
18 | if (!is.list(data)) { | |
19 | data <- list(data) | |
20 | } | |
21 | prepData <- prepareTweenTranspose(data, n, ease) | |
22 | if (!all(sapply(prepData$data, inherits, what = 'Date'))) { | |
23 | stop('data must consist of Date elements') | |
24 | } | |
25 | tweendata <- do.call(interpolate_date_state, prepData) | |
26 | unname(split(tweendata, | |
27 | rep(seq_along(data), rep(n, length.out = length(data))))) | |
28 | } |
0 | #' @rdname tween | |
1 | #' | |
2 | #' @export | |
3 | tween_datetime <- function(data, n, ease = 'linear') { | |
4 | data <- as.list(data) | |
5 | prepData <- prepareTween(data, n, ease) | |
6 | if (!all(sapply(prepData$data, inherits, what = 'POSIXt'))) { | |
7 | stop('data must consist of POSIXt elements') | |
8 | } | |
9 | tweendata <- do.call(interpolate_datetime_state, prepData) | |
10 | unname(split(tweendata, | |
11 | rep(seq_along(data[[1]]), length.out = length(tweendata)))) | |
12 | } | |
13 | ||
14 | #' @rdname tween | |
15 | #' | |
16 | #' @export | |
17 | tween_datetime_t <- function(data, n, ease = 'linear') { | |
18 | if (!is.list(data)) { | |
19 | data <- list(data) | |
20 | } | |
21 | prepData <- prepareTweenTranspose(data, n, ease) | |
22 | if (!all(sapply(prepData$data, inherits, what = 'POSIXt'))) { | |
23 | stop('data must consist of POSIXt elements') | |
24 | } | |
25 | tweendata <- do.call(interpolate_datetime_state, prepData) | |
26 | unname(split(tweendata, | |
27 | rep(seq_along(data), rep(n, length.out = length(data))))) | |
28 | } |
0 | #' Create frames based on individual element states | |
1 | #' | |
2 | #' This function creates tweens for each observation individually, in cases | |
3 | #' where the data doesn't pass through collective states but consists of fully | |
4 | #' independent transitions. Each observation is identified by an id and each | |
5 | #' state must have a time associated with it. | |
6 | #' | |
7 | #' @param data A data.frame consisting at least of a column giving the | |
8 | #' observation id, a column giving timepoints for each state and a column giving | |
9 | #' the easing to apply when transitioning away from the state. | |
10 | #' | |
11 | #' @param time The name of the column holding timepoints | |
12 | #' | |
13 | #' @param group The name of the column holding the observation id | |
14 | #' | |
15 | #' @param ease The name of the column holding the easing function name | |
16 | #' | |
17 | #' @param timerange The range of time to span. If missing it will default to | |
18 | #' \code{range(data[[time]])} | |
19 | #' | |
20 | #' @param nframes The number of frames to generate. If missing it will default | |
21 | #' to `ceiling(diff(timerange) + 1)` (At least one frame for each | |
22 | #' individual timepoint) | |
23 | #' | |
24 | #' @return A data.frame with the same columns as `data` except for the | |
25 | #' group and ease columns, but replicated `nframes` times. Two additional | |
26 | #' columns called `.frame` and `.group` will be added giving the frame | |
27 | #' number and observation id for each row. | |
28 | #' | |
29 | #' @family data.frame tween | |
30 | #' | |
31 | #' @examples | |
32 | #' data <- data.frame( | |
33 | #' x = c(1, 2, 2, 1, 2, 2), | |
34 | #' y = c(1, 2, 2, 2, 1, 1), | |
35 | #' time = c(1, 4, 10, 4, 8, 10), | |
36 | #' group = c(1, 1, 1, 2, 2, 2), | |
37 | #' ease = rep('cubic-in-out', 6) | |
38 | #' ) | |
39 | #' | |
40 | #' data <- tween_elements(data, 'time', 'group', 'ease', nframes = 100) | |
41 | #' | |
42 | #' @export | |
43 | #' | |
44 | tween_elements <- function(data, time, group, ease, timerange, nframes) { | |
45 | if (!all(data[[ease]] %in% validEase)) { | |
46 | stop("All names given in the easing column must be valid easers") | |
47 | } | |
48 | ||
49 | if (missing(timerange) || is.null(timerange)) { | |
50 | timerange <- range(data[[time]]) | |
51 | } | |
52 | if (missing(nframes) || is.null(nframes)) { | |
53 | nframes <- ceiling(diff(timerange) + 1) | |
54 | } | |
55 | framelength <- diff(timerange) / nframes | |
56 | specialCols <- c(group, ease) | |
57 | data <- data[order(data[[group]], data[[time]]), ] | |
58 | group <- as.character(data[[group]]) | |
59 | frame <- round((data[[time]] - timerange[1]) / framelength) | |
60 | ease <- as.character(data[[ease]]) | |
61 | data <- data[, !names(data) %in% specialCols, drop = FALSE] | |
62 | ||
63 | colClasses <- col_classes(data) | |
64 | tweendata <- lapply(seq_along(data), function(i) { | |
65 | d <- data[[i]] | |
66 | switch( | |
67 | colClasses[i], | |
68 | numeric = interpolate_numeric_element(d, group, frame, ease), | |
69 | logical = interpolate_logical_element(d, group, frame, ease), | |
70 | factor = interpolate_factor_element(d, group, frame, ease), | |
71 | character = interpolate_character_element(d, group, frame, ease), | |
72 | colour = interpolate_colour_element(d, group, frame, ease), | |
73 | date = interpolate_date_element(d, group, frame, ease), | |
74 | datetime = interpolate_datetime_element(d, group, frame, ease), | |
75 | constant = interpolate_constant_element(d, group, frame, ease), | |
76 | numlist = interpolate_numlist_element(d, group, frame, ease), | |
77 | list = interpolate_list_element(d, group, frame, ease) | |
78 | ) | |
79 | }) | |
80 | tweenInfo <- tweendata[[1]][, c('group', 'frame')] | |
81 | tweendata <- as.data.frame(lapply(tweendata, `[[`, i = 'data')) | |
82 | names(tweendata) <- names(data) | |
83 | tweendata$.frame <- tweenInfo$frame | |
84 | tweendata$.group <- tweenInfo$group | |
85 | attr(tweendata, 'framelength') <- framelength | |
86 | tweendata[order(tweendata$.frame, tweendata$.group), ] | |
87 | } |
0 | #' Transition in and out of events | |
1 | #' | |
2 | #' This tweening function is a more powerful version of [tween_appear()], with | |
3 | #' support for newer features such as enter/exits and tween phase | |
4 | #' identification. The tweener treats each row in the data as unique events in | |
5 | #' time, and creates frames with the correct events present at any given time. | |
6 | #' | |
7 | #' @param start,end The start (and potential end) of the event encoded in the | |
8 | #' row, as unquoted expressions. Will be evaluated in the context of `.data` so | |
9 | #' can refer to columns in it. If `end = NULL` the event will be without extend | |
10 | #' and only visible in a single frame, unless `enter` and/or `exit` is given. | |
11 | #' | |
12 | #' @inheritParams tween_components | |
13 | #' | |
14 | #' @return A data.frame with the same columns as `.data` along with `.id` giving | |
15 | #' the component id, `.phase` giving the state of each component in each frame, | |
16 | #' and `.frame` giving the frame membership of each row. | |
17 | #' | |
18 | #' @family data.frame tween | |
19 | #' | |
20 | #' @importFrom rlang enquo quo_is_missing eval_tidy | |
21 | #' @export | |
22 | #' | |
23 | #' @examples | |
24 | #' d <- data.frame( | |
25 | #' x = runif(20), | |
26 | #' y = runif(20), | |
27 | #' time = runif(20), | |
28 | #' duration = runif(20, max = 0.1) | |
29 | #' ) | |
30 | #' from_left <- function(x) { | |
31 | #' x$x <- -0.5 | |
32 | #' x | |
33 | #' } | |
34 | #' to_right <- function(x) { | |
35 | #' x$x <- 1.5 | |
36 | #' x | |
37 | #' } | |
38 | #' | |
39 | #' tween_events(d, 'cubic-in-out', 50, start = time, end = time + duration, | |
40 | #' enter = from_left, exit = to_right, enter_length = 0.1, | |
41 | #' exit_length = 0.05) | |
42 | #' | |
43 | tween_events <- function(.data, ease, nframes, start, end = NULL, range = NULL, enter = NULL, exit = NULL, enter_length = 0, exit_length = 0) { | |
44 | start <- enquo(start) | |
45 | if (quo_is_missing(start)) stop('start must be provided', call. = FALSE) | |
46 | start <- eval_tidy(start, .data) | |
47 | end <- enquo(end) | |
48 | end <- eval_tidy(end, .data) | |
49 | enter_length <- enquo(enter_length) | |
50 | enter_length <- eval_tidy(enter_length, .data) | |
51 | exit_length <- enquo(exit_length) | |
52 | exit_length <- eval_tidy(exit_length, .data) | |
53 | ||
54 | if (is.null(enter_length)) enter_length <- 0 | |
55 | if (is.null(exit_length)) exit_length <- 0 | |
56 | .data <- .complete_events(.data, start, end, enter, exit, enter_length, exit_length) | |
57 | ||
58 | .tween_individuals(.data, ease, nframes, range) | |
59 | } | |
60 | ||
61 | .complete_events <- function(data, start, end, enter, exit, enter_length, exit_length) { | |
62 | data$.id <- seq_len(nrow(data)) | |
63 | data$.phase <- rep("raw", nrow(data)) | |
64 | start <- rep(start, length.out = nrow(data)) | |
65 | if (is.null(end)) { | |
66 | event_end <- data[0, , drop = FALSE] | |
67 | end <- start[0] | |
68 | } else { | |
69 | event_end <- data | |
70 | end <- rep(end, length.out = nrow(data)) | |
71 | data$.phase <- 'static' | |
72 | } | |
73 | if (is.null(enter)) { | |
74 | enter_data <- data[0, , drop = FALSE] | |
75 | enter_time <- start[0] | |
76 | } else { | |
77 | enter_data <- enter(data) | |
78 | enter_data$.phase <- 'enter' | |
79 | enter_time <- start - enter_length | |
80 | } | |
81 | if (is.null(exit)) { | |
82 | exit_data <- data[0, , drop = FALSE] | |
83 | exit_time <- start[0] | |
84 | } else { | |
85 | exit_data <- exit(data) | |
86 | exit_data$.phase <- 'exit' | |
87 | exit_time <- (if (length(end) == 0) start else end) + exit_length | |
88 | } | |
89 | data <- rbind(enter_data, data, event_end, exit_data) | |
90 | time <- c(enter_time, start, end, exit_time) | |
91 | data$.time <- time | |
92 | data | |
93 | } |
0 | #' Fill out missing values by interpolation | |
1 | #' | |
2 | #' This tween fills out `NA` elements (or `NULL` elements if `data` is a list) | |
3 | #' by interpolating between the prior and next non-missing values. | |
4 | #' | |
5 | #' @param data A data.frame or vector. | |
6 | #' @param ease A character vector giving valid easing functions. Recycled to | |
7 | #' match the ncol of `data` | |
8 | #' | |
9 | #' @return If `data` is a data.frame then a data.frame with the same | |
10 | #' columns. If `data` is a vector then a vector. | |
11 | #' | |
12 | #' @export | |
13 | #' | |
14 | #' @examples | |
15 | #' # Single vector | |
16 | #' tween_fill(c(1, NA, NA, NA, NA, NA, 2, 6, NA, NA, NA, -2), 'cubic-in-out') | |
17 | #' | |
18 | #' # Data frame | |
19 | #' tween_fill(mtcars[c(1, NA, NA, NA, NA, 4, NA, NA, NA, 10), ], 'cubic-in') | |
20 | #' | |
21 | tween_fill <- function(data, ease) { | |
22 | single_vec <- !is.data.frame(data) | |
23 | if (single_vec) { | |
24 | if (length(data) == 0) return(data[integer()]) | |
25 | data_df <- data.frame(data = rep(NA, length(data))) | |
26 | data_df$data <- data | |
27 | data <- data_df | |
28 | } else { | |
29 | if (nrow(data) == 0) return(data[integer(), ]) | |
30 | } | |
31 | ease <- rep(ease, length.out = ncol(data)) | |
32 | classes <- col_classes(data) | |
33 | tweendata <- lapply(seq_along(classes), function(i) { | |
34 | switch( | |
35 | classes[i], | |
36 | numeric = interpolate_numeric_fill(data[[i]], ease[i]), | |
37 | logical = interpolate_logical_fill(data[[i]], ease[i]), | |
38 | factor = interpolate_factor_fill(data[[i]], ease[i]), | |
39 | character = interpolate_character_fill(data[[i]], ease[i]), | |
40 | colour = interpolate_colour_fill(data[[i]], ease[i]), | |
41 | date = interpolate_date_fill(data[[i]], ease[i]), | |
42 | datetime = interpolate_datetime_fill(data[[i]], ease[i]), | |
43 | constant = interpolate_constant_fill(data[[i]], ease[i]), | |
44 | numlist = interpolate_numlist_fill(data[[i]], ease[i]), | |
45 | list = interpolate_list_fill(data[[i]], ease[i]) | |
46 | ) | |
47 | }) | |
48 | if (single_vec) return(tweendata[[1]]) | |
49 | ||
50 | structure(tweendata, names = names(data), row.names = seq_along(tweendata[[1]]), class = 'data.frame') | |
51 | } |
0 | #' @rdname tween | |
1 | #' | |
2 | #' @export | |
3 | tween_numeric <- function(data, n, ease = 'linear') { | |
4 | data <- as.list(data) | |
5 | prepData <- prepareTween(data, n, ease) | |
6 | if (!all(sapply(prepData$data, is.numeric))) { | |
7 | stop('data must consist of numeric elements') | |
8 | } | |
9 | tweendata <- do.call(interpolate_numeric_state, prepData) | |
10 | unname(split(tweendata, | |
11 | rep(seq_along(data[[1]]), length.out = length(tweendata)))) | |
12 | } | |
13 | ||
14 | #' @rdname tween | |
15 | #' | |
16 | #' @export | |
17 | tween_numeric_t <- function(data, n, ease = 'linear') { | |
18 | if (!is.list(data)) { | |
19 | data <- list(data) | |
20 | } | |
21 | prepData <- prepareTweenTranspose(data, n, ease) | |
22 | if (!all(sapply(prepData$data, is.numeric))) { | |
23 | stop('data must consist of numeric elements') | |
24 | } | |
25 | tweendata <- do.call(interpolate_numeric_state, prepData) | |
26 | unname(split(tweendata, | |
27 | rep(seq_along(data), rep(n, length.out = length(data))))) | |
28 | } |
0 | #' Compose tweening between states | |
1 | #' | |
2 | #' The `tween_state()` is a counterpart to `tween_states()` that is aimed at | |
3 | #' letting you gradually build up a scene by composing state changes one by one. | |
4 | #' This setup lets you take more control over each state change and allows you | |
5 | #' to work with datasets with uneven number of rows, flexibly specifying what | |
6 | #' should happen with entering and exiting data. `keep_state()` is a simpel | |
7 | #' helper for letting you pause at a state. `open_state()` is a shortcut from | |
8 | #' tweening from an empty dataset with a given `enter()` function while | |
9 | #' `close_state()` is the same but will instead tween into an empty dataset with | |
10 | #' a given `exit()` function. | |
11 | #' | |
12 | #' @param .data A data.frame to start from. If `.data` is the result of a prior | |
13 | #' tween, only the last frame will be used for the tween. The new tween will | |
14 | #' then be added to the prior tween | |
15 | #' | |
16 | #' @param to A data.frame to end at. It must contain the same columns as .data | |
17 | #' (exluding `.frame`) | |
18 | #' | |
19 | #' @param ease The easing function to use. Either a single string or one for | |
20 | #' each column in the data set. | |
21 | #' | |
22 | #' @param nframes The number of frames to calculate for the tween | |
23 | #' | |
24 | #' @param id The column to match observations on. If `NULL` observations will be | |
25 | #' matched by position. See the *Match, Enter, and Exit* section for more | |
26 | #' information. | |
27 | #' | |
28 | #' @param enter,exit functions that calculate a start state for new observations | |
29 | #' that appear in `to` or an end state for observations that are not present in | |
30 | #' `to`. If `NULL` the new/old observations will not be part of the tween. The | |
31 | #' function gets a data.frame with either the start state of the exiting | |
32 | #' observations, or the end state of the entering observations and must return | |
33 | #' a modified version of that data.frame. See the *Match, Enter, and Exit* | |
34 | #' section for more information. | |
35 | #' | |
36 | #' @return A data.frame containing all the intermediary states in the tween, | |
37 | #' each state will be enumerated by the `.frame` column | |
38 | #' | |
39 | #' @section Match, Enter, and Exit: | |
40 | #' When there are discrepancies between the two states to tweeen between you | |
41 | #' need a way to resolve the discrepancy before calculating the intermediary | |
42 | #' states. With discrepancies we mean that some data points are present in the | |
43 | #' start state and not in the end state, and/or some are present in the end | |
44 | #' state but not in the start state. A simple example is that the start state | |
45 | #' contains 100 rows and the end state contains 70. There are 30 missing rows | |
46 | #' that we need to do something about before we can calculate the tween. | |
47 | #' | |
48 | #' **Making pairs** | |
49 | #' The first question to answer is "How do we know which observations are | |
50 | #' disappearing (*exiting*) and/or appearing (*entering*)?". This is done with | |
51 | #' the `id` argument which should give a column name to match rows between the | |
52 | #' two states on. If `id = NULL` the rows will be matched by position (in the | |
53 | #' above example the last 30 rows in the start state will be entering). The `id` | |
54 | #' column must only contain unique values in order to work. | |
55 | #' | |
56 | #' **Making up states** | |
57 | #' Once the rows in each state has been paired you'll end up with three sets of | |
58 | #' data. One containing rows that is present in both the start and end state, | |
59 | #' one containing rows only present in the start state, and one only containing | |
60 | #' rows present in the end state. The first group is easy - here you just tween | |
61 | #' between each rows - but for the other two we'll need some state to start or | |
62 | #' end the tween with. This is really the purpose of the `enter` and `exit` | |
63 | #' functions. They take a data frame containing the subset of data that has not | |
64 | #' been matched and must return a new data frame giving the state that these | |
65 | #' rows must be tweened from/into. A simple example could be an `enter` function | |
66 | #' that sets the variable giving the opacity in the plot to 0 - this will make | |
67 | #' the new points fade into view during the transition. | |
68 | #' | |
69 | #' **Ignoring discrepancies** | |
70 | #' The default values for `enter` and `exit` is `NULL`. This value indicate that | |
71 | #' non-matching rows should simply be ignored for the transition and simply | |
72 | #' appear in the last frame of the tween. This is the default. | |
73 | #' | |
74 | #' @importFrom rlang enquo | |
75 | #' @export | |
76 | #' | |
77 | #' @examples | |
78 | #' data1 <- data.frame( | |
79 | #' x = 1:20, | |
80 | #' y = 0, | |
81 | #' colour = 'forestgreen', | |
82 | #' stringsAsFactors = FALSE | |
83 | #' ) | |
84 | #' data2 <- data1 | |
85 | #' data2$x <- 20:1 | |
86 | #' data2$y <- 1 | |
87 | #' | |
88 | #' data <- data1 %>% | |
89 | #' tween_state(data2, 'linear', 50) %>% | |
90 | #' keep_state(20) %>% | |
91 | #' tween_state(data1, 'bounce-out', 50) | |
92 | #' | |
93 | #' # Using enter and exit (made up numbers) | |
94 | #' df1 <- data.frame( | |
95 | #' country = c('Denmark', 'Sweden', 'Norway'), | |
96 | #' population = c(5e6, 10e6, 3.5e6) | |
97 | #' ) | |
98 | #' df2 <- data.frame( | |
99 | #' country = c('Denmark', 'Sweden', 'Norway', 'Finland'), | |
100 | #' population = c(6e6, 10.5e6, 4e6, 3e6) | |
101 | #' ) | |
102 | #' df3 <- data.frame( | |
103 | #' country = c('Denmark', 'Norway'), | |
104 | #' population = c(10e6, 6e6) | |
105 | #' ) | |
106 | #' to_zero <- function(x) { | |
107 | #' x$population <- 0 | |
108 | #' x | |
109 | #' } | |
110 | #' pop_devel <- df1 %>% | |
111 | #' tween_state(df2, 'cubic-in-out', 50, id = country, enter = to_zero) %>% | |
112 | #' tween_state(df3, 'cubic-in-out', 50, id = country, enter = to_zero, | |
113 | #' exit = to_zero) | |
114 | #' | |
115 | tween_state <- function(.data, to, ease, nframes, id = NULL, enter = NULL, exit = NULL) { | |
116 | from <- .get_last_frame(.data) | |
117 | from$.phase <- rep('raw', length = nrow(from)) | |
118 | to$.phase <- rep('raw', length = nrow(to)) | |
119 | to$.id <- rep(NA_integer_, length = nrow(to)) | |
120 | id <- enquo(id) | |
121 | if (.has_frames(.data)) nframes <- nframes + 1 | |
122 | if (!setequal(names(from), names(to))) { | |
123 | stop('from and to must have identical columns', call. = FALSE) | |
124 | } | |
125 | ||
126 | if (nrow(from) == 0 && nrow(to) == 0) { | |
127 | return(.with_prior_frames(.data, from, nframes)) | |
128 | } | |
129 | ||
130 | to <- to[, match(names(from), names(to)), drop = FALSE] | |
131 | if (length(ease) == 1) ease <- rep(ease, ncol(from) - 2) | |
132 | if (length(ease) == ncol(from) - 2) { | |
133 | ease <- c(ease, 'linear', 'linear') # To account for .phase and .id columns | |
134 | } else { | |
135 | stop('Ease must be either a single string or one for each column', call. = FALSE) | |
136 | } | |
137 | stopifnot(length(nframes) == 1 && is.numeric(nframes) && nframes %% 1 == 0) | |
138 | ||
139 | classes <- if (nrow(from) == 0) col_classes(to) else col_classes(from) | |
140 | if (nrow(from) > 0 && nrow(to) > 0) stopifnot(identical(classes, col_classes(to))) | |
141 | full_set <- .complete_states(from, to, id, enter, exit, .max_id(.data)) | |
142 | to$.id <- full_set$orig_to | |
143 | ||
144 | tweendata <- lapply(seq_along(classes), function(i) { | |
145 | d <- list(full_set$from[[i]], full_set$to[[i]]) | |
146 | state <- simple_state(nframes, ease[i]) | |
147 | switch( | |
148 | classes[i], | |
149 | numeric = interpolate_numeric_state(d, state), | |
150 | logical = interpolate_logical_state(d, state), | |
151 | factor = interpolate_factor_state(d, state), | |
152 | character = interpolate_character_state(d, state), | |
153 | colour = interpolate_colour_state(d, state), | |
154 | date = interpolate_date_state(d, state), | |
155 | datetime = interpolate_datetime_state(d, state), | |
156 | constant = interpolate_constant_state(d, state), | |
157 | numlist = interpolate_numlist_state(d, state), | |
158 | list = interpolate_list_state(d, state), | |
159 | phase = get_phase_state(d, state) | |
160 | ) | |
161 | }) | |
162 | tweendata <- structure(tweendata, names = names(full_set$from), row.names = seq_along(tweendata[[1]]), class = 'data.frame') | |
163 | tweendata$.frame <- rep(seq_len(nframes - 1), each = nrow(full_set$from)) | |
164 | tweendata <- rbind( | |
165 | if (nframes > 1) cbind(from, .frame = rep(1, nrow(from))) else NULL, | |
166 | tweendata[tweendata$.frame != 1, , drop = FALSE], | |
167 | cbind(to, .frame = rep(nframes, nrow(to))) | |
168 | ) | |
169 | .with_prior_frames(.data, tweendata, nframes) | |
170 | } | |
171 | #' @rdname tween_state | |
172 | #' @export | |
173 | keep_state <- function(.data, nframes) { | |
174 | state <- .get_last_frame(.data) | |
175 | state$.phase <- rep('raw', length = nrow(state)) | |
176 | if (.has_frames(.data)) nframes <- nframes + 1 | |
177 | if (nrow(state) == 0) { | |
178 | return(.with_prior_frames(.data, state, nframes)) | |
179 | } | |
180 | states <- state[rep(seq_len(nrow(state)), nframes), , drop = FALSE] | |
181 | states$.phase[seq_len(nrow(state) * (nframes - 1))] <- 'static' | |
182 | states$.frame <- rep(seq_len(nframes), each = nrow(state)) | |
183 | .with_prior_frames(.data, states, nframes) | |
184 | } | |
185 | #' @rdname tween_state | |
186 | #' @export | |
187 | open_state <- function(.data, ease, nframes, enter) { | |
188 | to <- .get_first_frame(.data) | |
189 | if (.has_frames(.data)) nframes <- nframes + 1 | |
190 | tweendata <- tween_state(to[0, , drop = FALSE], to, ease, nframes, enter = enter) | |
191 | .with_later_frames(.data, tweendata, nframes) | |
192 | } | |
193 | #' @rdname tween_state | |
194 | #' @export | |
195 | close_state <- function(.data, ease, nframes, exit) { | |
196 | from <- .get_last_frame(.data) | |
197 | if (.has_frames(.data)) nframes <- nframes + 1 | |
198 | tweendata <- tween_state(from, from[0, , drop = FALSE], ease, nframes, exit = exit) | |
199 | .with_prior_frames(.data, tweendata, nframes) | |
200 | } | |
201 | #' Helpers for working with tweened data | |
202 | #' | |
203 | #' These are internal helpers for extracting and inserting data into a | |
204 | #' data.frame of tweened states. | |
205 | #' | |
206 | #' @param data,prior,later A data.frame. If a `.frame` column exists it will be interpreted | |
207 | #' as a data.frame containing multiple states | |
208 | #' | |
209 | #' @param new_tween The result of a tweening | |
210 | #' | |
211 | #' @return A data.frame | |
212 | #' @keywords internal | |
213 | #' @export | |
214 | #' | |
215 | .get_last_frame <- function(data) { | |
216 | nframes <- attr(data, 'nframes') | |
217 | data <- if (!is.null(nframes)) { | |
218 | data[data$.frame == nframes, names(data) != '.frame', drop = FALSE] | |
219 | } else if ('.frame' %in% names(data)) { | |
220 | data[data$.frame == max(data$.frame), names(data) != '.frame', drop = FALSE] | |
221 | } else { | |
222 | data | |
223 | } | |
224 | if (is.null(data$.id)) { | |
225 | data$.id <- seq_len(nrow(data)) | |
226 | } | |
227 | data | |
228 | } | |
229 | #' @rdname dot-get_last_frame | |
230 | #' @export | |
231 | .get_first_frame <- function(data) { | |
232 | data <- if ('.frame' %in% names(data)) { | |
233 | data[data$.frame == 1, names(data) != '.frame', drop = FALSE] | |
234 | } else { | |
235 | data | |
236 | } | |
237 | if (is.null(data$.id)) { | |
238 | data$.id <- seq_len(nrow(data)) | |
239 | } | |
240 | data | |
241 | } | |
242 | #' @rdname dot-get_last_frame | |
243 | #' @export | |
244 | .with_prior_frames <- function(prior, new_tween, nframes) { | |
245 | nframes_before <- attr(prior, 'nframes') | |
246 | if (is.null(nframes_before) && nrow(prior) > 0 && '.frame' %in% names(prior)) nframes_before <- max(prior$.frame) | |
247 | frames <- if (!is.null(nframes_before)) { | |
248 | prior <- prior[prior$.frame != nframes_before, , drop = FALSE] | |
249 | new_tween$.frame <- new_tween$.frame + nframes_before - 1 | |
250 | rbind(prior, new_tween) | |
251 | } else { | |
252 | nframes_before <- 1 | |
253 | new_tween | |
254 | } | |
255 | attr(frames, 'nframes') <- nframes + nframes_before - 1 | |
256 | attr(frames, 'max_id') <- find_max_id(prior, new_tween) | |
257 | frames | |
258 | } | |
259 | #' @rdname dot-get_last_frame | |
260 | #' @export | |
261 | .with_later_frames <- function(later, new_tween, nframes) { | |
262 | nframes_before <- attr(later, 'nframes') | |
263 | nframes_before <- if (is.null(nframes_before) && nrow(later) > 0 && '.frame' %in% names(later)) max(later$.frame) else 1 | |
264 | frames <- if ('.frame' %in% names(later)) { | |
265 | later <- later[later$.frame != 1, , drop = FALSE] | |
266 | later$.frame <- later$.frame + max(new_tween$.frame) | |
267 | rbind(new_tween, later) | |
268 | } else { | |
269 | new_tween | |
270 | } | |
271 | attr(frames, 'nframes') <- nframes + nframes_before - 1 | |
272 | attr(frames, 'max_id') <- find_max_id(later, new_tween) | |
273 | frames | |
274 | } | |
275 | find_max_id <- function(data, new) { | |
276 | max_new <- if (nrow(new) == 0) 0 else max(new$.id) | |
277 | max(max_new, .max_id(data)) | |
278 | } | |
279 | #' Get the highest id occuring in a dataset | |
280 | #' | |
281 | #' This is helper for `tween_state` related functions to get the currently | |
282 | #' highest `.id` in a frame collection | |
283 | #' | |
284 | #' @param data A data.frame as returned by `tween_state` | |
285 | #' | |
286 | #' @return An integer giving the currently highest id | |
287 | #' | |
288 | #' @keywords internal | |
289 | #' @export | |
290 | .max_id <- function(data) { | |
291 | max_id <- attr(data, 'max_id') | |
292 | if (is.null(max_id) && nrow(data) > 0 && !is.null(data$.id)) max_id <- max(data$.id) | |
293 | else max_id <- nrow(data) | |
294 | max_id | |
295 | } | |
296 | #' Fill in missing rows using enter and exit functions | |
297 | #' | |
298 | #' This function figures out which rows are missing in either state and applies | |
299 | #' the provided `enter` and `exit` functions to fill in the blanks and provide | |
300 | #' a 1-to-1 relation between the rows in `from` and `to`. | |
301 | #' | |
302 | #' @param from,to Data.frames to tween between | |
303 | #' | |
304 | #' @param id The name of the column that holds the matching id | |
305 | #' | |
306 | #' @param enter,exit functions to fill out missing rows in `from` and `to` | |
307 | #' respectively | |
308 | #' | |
309 | #' @return A list with the elements `from` and `to` holding the filled out | |
310 | #' versions of `from` and `to` | |
311 | #' | |
312 | #' @keywords internal | |
313 | #' @importFrom rlang eval_tidy %||% | |
314 | #' @export | |
315 | .complete_states <- function(from, to, id, enter, exit, max_id) { | |
316 | from_id <- eval_tidy(id, from) %||% seq_len(nrow(from)) | |
317 | to_id <- eval_tidy(id, to) %||% seq_len(nrow(to)) | |
318 | if (length(from_id) != nrow(from) || length(to_id) != nrow(to)) { | |
319 | stop('id must match the length of the data', call. = FALSE) | |
320 | } | |
321 | n_to <- nrow(to) | |
322 | if (anyDuplicated(from_id) || anyDuplicated(to_id) || !setequal(from_id, to_id)) { | |
323 | from_id <- paste(from_id, count_occourance(from_id), sep = '_') | |
324 | to_id <- paste(to_id, count_occourance(to_id), sep = '_') | |
325 | entering <- !to_id %in% from_id | |
326 | exiting <- !from_id %in% to_id | |
327 | exits <- from[entering, , drop = FALSE] | |
328 | ||
329 | if (is.null(enter) || sum(entering) == 0) { | |
330 | to <- to[!entering, , drop = FALSE] | |
331 | to_id <- to_id[!entering] | |
332 | enters <- to[0, , drop = FALSE] | |
333 | enter_id <- to_id[0] | |
334 | } else { | |
335 | stopifnot(is.function(enter)) | |
336 | enters <- enter(to[entering, , drop = FALSE]) | |
337 | enters$.phase <- 'enter' | |
338 | enter_id <- to_id[entering] | |
339 | } | |
340 | if (is.null(exit) || sum(exiting) == 0) { | |
341 | from <- from[!exiting, , drop = FALSE] | |
342 | from_id <- from_id[!exiting] | |
343 | exits <- from[0, , drop = FALSE] | |
344 | exit_id <- from_id[0] | |
345 | } else { | |
346 | stopifnot(is.function(exit)) | |
347 | exits <- exit(from[exiting, , drop = FALSE]) | |
348 | exits$.phase <- 'exit' | |
349 | exit_id <- from_id[exiting] | |
350 | } | |
351 | from <- rbind(from, enters) | |
352 | from_id <- c(from_id, enter_id) | |
353 | to <- rbind(to, exits) | |
354 | to_id <- c(to_id, exit_id) | |
355 | } | |
356 | from$.id[is.na(from$.id)] <- seq_len(sum(is.na(from$.id))) + max_id | |
357 | orig_to_id <- from$.id[match(to_id, from_id)][seq_len(n_to)] | |
358 | to <- to[match(from_id, to_id), , drop = FALSE] | |
359 | to$.id <- from$.id | |
360 | ||
361 | list(from = from, to = to, orig_to = orig_to_id) | |
362 | } | |
363 | #' @rdname dot-get_last_frame | |
364 | #' @export | |
365 | .has_frames <- function(data) { | |
366 | !is.null(attr(data, 'nframes')) || !is.null(data$.frame) | |
367 | } | |
368 | simple_state <- function(n, ease) { | |
369 | data.frame(state = c(0, 1), nframes = c(n - 1, 0), ease = c(ease, 'constant'), stringsAsFactors = FALSE) | |
370 | } | |
371 | ||
372 | count_occourance <- function(x) { | |
373 | if (length(x) == 0) return(integer(0)) | |
374 | unsplit(lapply(split(x, x), seq_along), x) | |
375 | } |
0 | #' Tween a list of data.frames representing states | |
1 | #' | |
2 | #' This function is intended to create smooth transitions between states of | |
3 | #' data. States are defined as full data.frames or data.frames containing only | |
4 | #' the columns with change. Each state can have a defined period of pause, the | |
5 | #' transition length between each states can be defined as well as the easing | |
6 | #' function. | |
7 | #' | |
8 | #' @param data A list of data.frames. Each data.frame must contain the same | |
9 | #' number of rows, but only the first data.frame needs to contain all columns. | |
10 | #' Subsequent data.frames need only contain the columns that shows change. | |
11 | #' | |
12 | #' @param tweenlength The lengths of the transitions between each state. | |
13 | #' | |
14 | #' @param statelength The length of the pause at each state. | |
15 | #' | |
16 | #' @param ease The easing functions to use for the transitions. See details. | |
17 | #' | |
18 | #' @param nframes The number of frames to generate. The actual number of frames | |
19 | #' might end up being higher depending on the regularity of `tweenlength` | |
20 | #' and `statelength`. | |
21 | #' | |
22 | #' @return A data.frame with the same columns as the first data.frame in | |
23 | #' `data`, but replicated `nframes` times. An additional column called | |
24 | #' `.frame` will be added giving the frame number. | |
25 | #' | |
26 | #' @family data.frame tween | |
27 | #' | |
28 | #' @examples | |
29 | #' data1 <- data.frame( | |
30 | #' x = 1:20, | |
31 | #' y = 0, | |
32 | #' colour = 'forestgreen', | |
33 | #' stringsAsFactors = FALSE | |
34 | #' ) | |
35 | #' data2 <- data1 | |
36 | #' data2$x <- 20:1 | |
37 | #' data2$y <- 1 | |
38 | #' | |
39 | #' data <- tween_states(list(data1, data2), 3, 1, 'cubic-in-out', 100) | |
40 | #' | |
41 | #' @export | |
42 | #' | |
43 | tween_states <- function(data, tweenlength, statelength, ease, nframes) { | |
44 | if (!(is.list(data) && all(sapply(data, is.data.frame)))) { | |
45 | stop('data must be a list of data.frames') | |
46 | } | |
47 | if (length(data) == 1) { | |
48 | stop('data must contain multiple states') | |
49 | } | |
50 | if (length(unique(sapply(data, nrow))) != 1) { | |
51 | stop('All elements in data must have the same number of rows') | |
52 | } | |
53 | data <- lapply(data, function(d) { | |
54 | d$.phase <- 'raw' | |
55 | d | |
56 | }) | |
57 | origNames <- names(data[[1]]) | |
58 | if (!is.list(ease)) ease <- as.list(ease) | |
59 | allNames <- unlist(lapply(data, names)) | |
60 | if (!all(allNames %in% origNames)) { | |
61 | stop('All columns must be specified in the original data.frame') | |
62 | } | |
63 | nstates <- length(data) | |
64 | tweenlength <- rep(tweenlength, nstates)[seq_len(nstates - 1)] | |
65 | statelength <- rep(statelength, nstates)[seq_len(nstates)] | |
66 | ease <- rep(ease, nstates)[seq_len(nstates - 1)] | |
67 | pauseIndex <- which(rep(c(TRUE, FALSE), length.out = 2*nstates - 1)) | |
68 | tweenIndex <- which(rep(c(FALSE, TRUE), length.out = 2*nstates - 1)) | |
69 | statesOrder <- order(c(pauseIndex, tweenIndex)) | |
70 | states <- data.frame( | |
71 | length = c(statelength, tweenlength)[statesOrder], | |
72 | nframes = NA_integer_, | |
73 | state = NA_integer_, | |
74 | stringsAsFactors = FALSE | |
75 | ) | |
76 | states$state <- rep(seq_len(nstates) - 1, each = 2, length.out = nrow(states)) | |
77 | states$ease <- lapply(c(rep(list('constant'), nstates), ease)[statesOrder], function(e) { | |
78 | structure(rep(e, length.out = length(origNames)), names = origNames) | |
79 | }) | |
80 | fullLength <- sum(states$length) | |
81 | framelength <- fullLength/nframes | |
82 | states$nframes <- round(states$length / framelength) | |
83 | nframes <- sum(states$nframes) | |
84 | framelength <- fullLength/nframes | |
85 | data <- Reduce(function(l, r) { | |
86 | extraCols <- !names(l[[length(l)]]) %in% names(r); | |
87 | append(l, list(cbind(r, l[[length(l)]][, extraCols]))) | |
88 | }, data[-1], data[1]) | |
89 | colClasses <- col_classes(data[[1]]) | |
90 | tweendata <- lapply(names(data[[1]]), function(name) { | |
91 | d <- lapply(data, `[[`, i = name) | |
92 | d_states <- states | |
93 | d_states$ease <- vapply(d_states$ease, `[`, character(1), i = name) | |
94 | switch( | |
95 | colClasses[name], | |
96 | numeric = interpolate_numeric_state(d, d_states), | |
97 | logical = interpolate_logical_state(d, d_states), | |
98 | factor = interpolate_factor_state(d, d_states), | |
99 | character = interpolate_character_state(d, d_states), | |
100 | colour = interpolate_colour_state(d, d_states), | |
101 | date = interpolate_date_state(d, d_states), | |
102 | datetime = interpolate_datetime_state(d, d_states), | |
103 | constant = interpolate_constant_state(d, d_states), | |
104 | numlist = interpolate_numlist_state(d, d_states), | |
105 | list = interpolate_list_state(d, d_states), | |
106 | phase = get_phase_state(d, d_states) | |
107 | ) | |
108 | }) | |
109 | tweendata <- structure(tweendata, names = names(data[[1]]), row.names = seq_along(tweendata[[1]]), class = 'data.frame') | |
110 | tweendata$.id <- rep(seq_len(nrow(data[[1]])), each = nframes) | |
111 | tweendata$.frame <- rep(seq_len(nframes), each = nrow(data[[1]])) | |
112 | attr(tweendata, 'framelength') <- framelength | |
113 | tweendata | |
114 | } |
0 | #' @details | |
1 | #' tweenr is a small collection of functions to help you in creating | |
2 | #' intermediary representations of your data, i.e. interpolating states of data. | |
3 | #' As such it's a great match for packages such as animate and gganimate, since | |
4 | #' it can work directly with data.frames of data, but it also provide fast and | |
5 | #' efficient interpolaters for numeric, date, datetime and colour that are | |
6 | #' vectorized and thus more efficient to use than the build in interpolation | |
7 | #' functions (mainly [stats::approx()] and | |
8 | #' [grDevices::colorRamp()]). | |
9 | #' | |
10 | #' The main functions for data.frames are [tween_states()], | |
11 | #' [tween_elements()] and [tween_appear()], while the | |
12 | #' standard interpolaters can be found at [tween()] | |
13 | #' | |
14 | #' @useDynLib tweenr | |
15 | #' @importFrom Rcpp sourceCpp | |
16 | '_PACKAGE' |
0 | ||
1 | <!-- README.md is generated from README.Rmd. Please edit that file --> | |
2 | tweenr <img src="man/figures/logo.png" align="right" /> | |
3 | ======================================================= | |
4 | ||
5 | [![Travis-CI Build Status](https://travis-ci.org/thomasp85/tweenr.svg?branch=master)](https://travis-ci.org/thomasp85/tweenr) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/thomasp85/tweenr?branch=master&svg=true)](https://ci.appveyor.com/project/thomasp85/tweenr) [![CRAN\_Release\_Badge](http://www.r-pkg.org/badges/version-ago/tweenr)](https://CRAN.R-project.org/package=tweenr) [![CRAN\_Download\_Badge](http://cranlogs.r-pkg.org/badges/tweenr)](https://CRAN.R-project.org/package=tweenr) [![Coverage Status](https://img.shields.io/codecov/c/github/thomasp85/tweenr/master.svg)](https://codecov.io/github/thomasp85/tweenr?branch=master) | |
6 | ||
7 | What is this? | |
8 | ------------- | |
9 | ||
10 | `tweenr` is a package for interpolating data, mainly for animations. It provides a range of functions that take data of different forms and calculate intermediary values. It supports all atomic vector types along with `factor`, `Date`, `POSIXct`, characters representing colours, and `list`. `tweenr` is used extensibly by [`gganimate`](https://github.com/thomasp85/gganimate) to create smooth animations, but can also be used by itself to prepare data for animation in another framework. | |
11 | ||
12 | How do I get it? | |
13 | ---------------- | |
14 | ||
15 | `tweenr` is available on CRAN and can be installed with `install.packages('tweenr')`. In order to get the development version you can install it from github with `devtools` | |
16 | ||
17 | ``` r | |
18 | #install.packages('devtools') | |
19 | devtools::install_github('thomasp85/tweenr') | |
20 | ``` | |
21 | ||
22 | An example | |
23 | ---------- | |
24 | ||
25 | Following is an example of using the pipeable `tween_state()` function with our belowed iris data: | |
26 | ||
27 | ``` r | |
28 | library(tweenr) | |
29 | library(ggplot2) | |
30 | ||
31 | # Prepare the data with some extra columns | |
32 | iris$col <- c('firebrick', 'forestgreen', 'steelblue')[as.integer(iris$Species)] | |
33 | iris$size <- 4 | |
34 | iris$alpha <- 1 | |
35 | iris <- split(iris, iris$Species) | |
36 | ||
37 | # Here comes tweenr | |
38 | iris_tween <- iris$setosa %>% | |
39 | tween_state(iris$versicolor, ease = 'cubic-in-out', nframes = 30) %>% | |
40 | keep_state(10) %>% | |
41 | tween_state(iris$virginica, ease = 'elastic-out', nframes = 30) %>% | |
42 | keep_state(10) %>% | |
43 | tween_state(iris$setosa, ease = 'quadratic-in', nframes = 30) %>% | |
44 | keep_state(10) | |
45 | ||
46 | # Animate it to show the effect | |
47 | p_base <- ggplot() + | |
48 | geom_point(aes(x = Petal.Length, y = Petal.Width, alpha = alpha, colour = col, | |
49 | size = size)) + | |
50 | scale_colour_identity() + | |
51 | scale_alpha_identity() + | |
52 | scale_size_identity() + | |
53 | coord_cartesian(xlim = range(iris_tween$Petal.Length), | |
54 | ylim = range(iris_tween$Petal.Width)) | |
55 | iris_tween <- split(iris_tween, iris_tween$.frame) | |
56 | for (d in iris_tween) { | |
57 | p <- p_base %+% d | |
58 | plot(p) | |
59 | } | |
60 | ``` | |
61 | ||
62 | ![](man/figures/README-unnamed-chunk-3.gif) | |
63 | ||
64 | Other functions | |
65 | --------------- | |
66 | ||
67 | Besides the `tween_state()`/`keep_state()` combo showcased above, there are a slew of other functions meant for data in different formats | |
68 | ||
69 | **`tween_components`** takes a single data.frame, a vector of ids identifying recurrent elements, and a vector of timepoints for each row and interpolate each element between its specified time points. | |
70 | ||
71 | **`tween_events`** takes a single data.frame where each row encodes a single unique event, along with a start, and end time and expands the data across a given number of frames. | |
72 | ||
73 | **`tween_along`** takes a single data.frame along with an id and timepoint vector and calculate evenly spaced intermediary values with the possibility of keeping old values at each frame. | |
74 | ||
75 | **`tween_at`** takes two data.frames or vectors along with a numeric vector giving the interpolation point between the two data.frames to calculate. | |
76 | ||
77 | **`tween_fill`** fills missing values in a vector or data.frame by interpolating between previous and next non-missing elements | |
78 | ||
79 | Easing | |
80 | ------ | |
81 | ||
82 | In order to get smooth transitions you'd often want a non-linear interpolation. This can be achieved by using an easing function to translate the equidistant interpolation points into new ones. `tweenr` has support for a wide range of different easing functions, all of which can be previewed using `display_ease()` as here where the popular *cubic-in-out* is shown: | |
83 | ||
84 | ``` r | |
85 | tweenr::display_ease('cubic-in-out') | |
86 | ``` | |
87 | ||
88 | ![](man/figures/README-unnamed-chunk-4-1.png) | |
89 | ||
90 | Spatial interpolations | |
91 | ---------------------- | |
92 | ||
93 | The purpose of `tweenr` is to interpolate values independently. If paths and polygons needs to be transitioned the [`transformr`](https://github.com/thomasp85/transformr) package should be used as it expands tweenr into the spatial realm |
0 | % Generated by roxygen2: do not edit by hand | |
1 | % Please edit documentation in R/display_ease.R | |
2 | \name{display_ease} | |
3 | \alias{display_ease} | |
4 | \title{Display an easing function} | |
5 | \usage{ | |
6 | display_ease(ease) | |
7 | } | |
8 | \arguments{ | |
9 | \item{ease}{The name of the easing function to display (see details)} | |
10 | } | |
11 | \value{ | |
12 | This function is called for its side effects | |
13 | } | |
14 | \description{ | |
15 | This simple helper lets you explore how the different easing functions govern | |
16 | the interpolation of data. | |
17 | } | |
18 | \details{ | |
19 | How transitions proceed between states are defined by an easing function. The | |
20 | easing function converts the parameterized progression from one state to the | |
21 | next to a new number between 0 and 1. \code{linear} easing is equivalent to | |
22 | an identity function that returns the input unchanged. In addition there are | |
23 | a range of additional easers available, each with three modifiers. | |
24 | ||
25 | \strong{Easing modifiers:} | |
26 | \describe{ | |
27 | \item{-in}{The easing function is applied as-is} | |
28 | \item{-out}{The easing function is applied in reverse} | |
29 | \item{-in-out}{The first half of the transition it is applied as-is, while | |
30 | in the last half it is reversed} | |
31 | } | |
32 | ||
33 | \strong{Easing functions} | |
34 | \describe{ | |
35 | \item{quadratic}{Models a power-of-2 function} | |
36 | \item{cubic}{Models a power-of-3 function} | |
37 | \item{quartic}{Models a power-of-4 function} | |
38 | \item{quintic}{Models a power-of-5 function} | |
39 | \item{sine}{Models a sine function} | |
40 | \item{circular}{Models a pi/2 circle arc} | |
41 | \item{exponential}{Models an exponential function} | |
42 | \item{elastic}{Models an elastic release of energy} | |
43 | \item{back}{Models a pullback and relase} | |
44 | \item{bounce}{Models the bouncing of a ball} | |
45 | } | |
46 | ||
47 | In addition to this function a good animated explanation can be found | |
48 | \href{http://easings.net}{here}. | |
49 | } | |
50 | \examples{ | |
51 | # The default - identity | |
52 | display_ease('linear') | |
53 | ||
54 | # A more fancy easer | |
55 | display_ease('elastic-in') | |
56 | ||
57 | } |
0 | % Generated by roxygen2: do not edit by hand | |
1 | % Please edit documentation in R/tween_state.R | |
2 | \name{.complete_states} | |
3 | \alias{.complete_states} | |
4 | \title{Fill in missing rows using enter and exit functions} | |
5 | \usage{ | |
6 | .complete_states(from, to, id, enter, exit, max_id) | |
7 | } | |
8 | \arguments{ | |
9 | \item{from, to}{Data.frames to tween between} | |
10 | ||
11 | \item{id}{The name of the column that holds the matching id} | |
12 | ||
13 | \item{enter, exit}{functions to fill out missing rows in \code{from} and \code{to} | |
14 | respectively} | |
15 | } | |
16 | \value{ | |
17 | A list with the elements \code{from} and \code{to} holding the filled out | |
18 | versions of \code{from} and \code{to} | |
19 | } | |
20 | \description{ | |
21 | This function figures out which rows are missing in either state and applies | |
22 | the provided \code{enter} and \code{exit} functions to fill in the blanks and provide | |
23 | a 1-to-1 relation between the rows in \code{from} and \code{to}. | |
24 | } | |
25 | \keyword{internal} |
0 | % Generated by roxygen2: do not edit by hand | |
1 | % Please edit documentation in R/tween_state.R | |
2 | \name{.get_last_frame} | |
3 | \alias{.get_last_frame} | |
4 | \alias{.get_first_frame} | |
5 | \alias{.with_prior_frames} | |
6 | \alias{.with_later_frames} | |
7 | \alias{.has_frames} | |
8 | \title{Helpers for working with tweened data} | |
9 | \usage{ | |
10 | .get_last_frame(data) | |
11 | ||
12 | .get_first_frame(data) | |
13 | ||
14 | .with_prior_frames(prior, new_tween, nframes) | |
15 | ||
16 | .with_later_frames(later, new_tween, nframes) | |
17 | ||
18 | .has_frames(data) | |
19 | } | |
20 | \arguments{ | |
21 | \item{data, prior, later}{A data.frame. If a \code{.frame} column exists it will be interpreted | |
22 | as a data.frame containing multiple states} | |
23 | ||
24 | \item{new_tween}{The result of a tweening} | |
25 | } | |
26 | \value{ | |
27 | A data.frame | |
28 | } | |
29 | \description{ | |
30 | These are internal helpers for extracting and inserting data into a | |
31 | data.frame of tweened states. | |
32 | } | |
33 | \keyword{internal} |
0 | % Generated by roxygen2: do not edit by hand | |
1 | % Please edit documentation in R/tween_state.R | |
2 | \name{.max_id} | |
3 | \alias{.max_id} | |
4 | \title{Get the highest id occuring in a dataset} | |
5 | \usage{ | |
6 | .max_id(data) | |
7 | } | |
8 | \arguments{ | |
9 | \item{data}{A data.frame as returned by \code{tween_state}} | |
10 | } | |
11 | \value{ | |
12 | An integer giving the currently highest id | |
13 | } | |
14 | \description{ | |
15 | This is helper for \code{tween_state} related functions to get the currently | |
16 | highest \code{.id} in a frame collection | |
17 | } | |
18 | \keyword{internal} |
Binary diff not shown
Binary diff not shown
Binary diff not shown
0 | % Generated by roxygen2: do not edit by hand | |
1 | % Please edit documentation in R/aaa.R | |
2 | \docType{import} | |
3 | \name{reexports} | |
4 | \alias{reexports} | |
5 | \alias{\%>\%} | |
6 | \title{Objects exported from other packages} | |
7 | \keyword{internal} | |
8 | \description{ | |
9 | These objects are imported from other packages. Follow the links | |
10 | below to see their documentation. | |
11 | ||
12 | \describe{ | |
13 | \item{magrittr}{\code{\link[magrittr]{\%>\%}}} | |
14 | }} | |
15 |
0 | % Generated by roxygen2: do not edit by hand | |
1 | % Please edit documentation in R/tween.R, R/tween_colour.R, R/tween_constant.R, | |
2 | % R/tween_date.R, R/tween_datetime.R, R/tween_numeric.R | |
3 | \name{tween} | |
4 | \alias{tween} | |
5 | \alias{tween_t} | |
6 | \alias{tween_colour} | |
7 | \alias{tween_color} | |
8 | \alias{tween_colour_t} | |
9 | \alias{tween_color_t} | |
10 | \alias{tween_constant} | |
11 | \alias{tween_constant_t} | |
12 | \alias{tween_date} | |
13 | \alias{tween_date_t} | |
14 | \alias{tween_datetime} | |
15 | \alias{tween_datetime_t} | |
16 | \alias{tween_numeric} | |
17 | \alias{tween_numeric_t} | |
18 | \title{Create simple tweens} | |
19 | \usage{ | |
20 | tween(data, n, ease = "linear") | |
21 | ||
22 | tween_t(data, n, ease = "linear") | |
23 | ||
24 | tween_colour(data, n, ease = "linear") | |
25 | ||
26 | tween_color(data, n, ease = "linear") | |
27 | ||
28 | tween_colour_t(data, n, ease = "linear") | |
29 | ||
30 | tween_color_t(data, n, ease = "linear") | |
31 | ||
32 | tween_constant(data, n, ease = "linear") | |
33 | ||
34 | tween_constant_t(data, n, ease = "linear") | |
35 | ||
36 | tween_date(data, n, ease = "linear") | |
37 | ||
38 | tween_date_t(data, n, ease = "linear") | |
39 | ||
40 | tween_datetime(data, n, ease = "linear") | |
41 | ||
42 | tween_datetime_t(data, n, ease = "linear") | |
43 | ||
44 | tween_numeric(data, n, ease = "linear") | |
45 | ||
46 | tween_numeric_t(data, n, ease = "linear") | |
47 | } | |
48 | \arguments{ | |
49 | \item{data}{A list of vectors or a single vector. In the standard functions | |
50 | each element in the list must be of equal length; for the *_t functions | |
51 | lengths can differ. If a single vector is used it will be eqivalent to using | |
52 | \code{as.list(data)} for the standard functions and \code{list(data)} for the | |
53 | *_t functions.} | |
54 | ||
55 | \item{n}{The number of elements per transition or tween. See details} | |
56 | ||
57 | \item{ease}{The easing function to use for each transition or tween. See | |
58 | details. Defaults to \code{'linear'}} | |
59 | } | |
60 | \value{ | |
61 | A list with an element for each tween. That means that the length of | |
62 | the return is equal to the length of the elements in \code{data} for the | |
63 | standard functions and equal to the length of \code{data} for the *_t | |
64 | functions. | |
65 | } | |
66 | \description{ | |
67 | This set of functions can be used to interpolate between single data types, | |
68 | i.e. data not part of data.frames but stored in vectors. All functions come | |
69 | in two flavours: the standard and a *_t version. The standard reads the data | |
70 | as a list of states, each tween matched element-wise from state to state. The | |
71 | *_t version uses the transposed representation where each element is a vector | |
72 | of states. The standard approach can be used when each tween has the same | |
73 | number of states and you want to control the number of point in each state | |
74 | transition. The latter is useful when each tween consists of different | |
75 | numbers of states and/or you want to specify the total number of points for | |
76 | each tween. | |
77 | } | |
78 | \details{ | |
79 | \code{tween} and \code{tween_t} are wrappers around the other functions that tries to guess | |
80 | the type of input data and choose the appropriate tween function. Unless you | |
81 | have data that could be understood as a colour but is in fact a character | |
82 | vector it should be safe to use these wrappers. It is probably safer and more | |
83 | verbose to use the explicit functions within package code as they circumvent | |
84 | the type inference and checks whether the input data matches the tween | |
85 | function. | |
86 | ||
87 | \code{tween_numeric} will provide a linear interpolation between the points based on | |
88 | the sequence returned by the easing function. \code{tween_date} and \code{tween_datetime} | |
89 | converts to numeric, produces the tweening, and converts back again. | |
90 | \code{tween_colour} converts colours into Lab and does the interpolation there, | |
91 | converting back to sRGB after the tweening is done. \code{tween_constant} is a | |
92 | catchall that converts the input into character and interpolates by switching | |
93 | between states halfway through the transition. | |
94 | ||
95 | The meaning of the \code{n} and \code{ease} arguments differs somewhat | |
96 | between the standard and *_t versions of the functions. In the standard | |
97 | function \code{n} and \code{ease} refers to the length and easing function of | |
98 | each transition, being recycled if necessary to \code{length(data) - 1}. In | |
99 | the *_t functions \code{n} and \code{ease} refers to the total length of each | |
100 | tween and the easing function to be applied to all transition for each tween. | |
101 | The will both be recycled to \code{length(data)}. | |
102 | } | |
103 | \section{Difference Between \code{tween_numeric} and \code{approx()}}{ | |
104 | ||
105 | \code{tween_numeric} (and \code{tween_numeric_t}) is superficially equivalent to | |
106 | \code{\link[stats:approx]{stats::approx()}}, but there are differences. | |
107 | \code{\link[stats:approx]{stats::approx()}} will create evenly spaced points, at the expense | |
108 | of not including the actual points in the input, while the reverse is true | |
109 | for \code{tween_numeric}. Apart from that \code{tween_numeric} of course supports easing | |
110 | functions and is vectorized. | |
111 | } | |
112 | ||
113 | \examples{ | |
114 | tween_numeric(list(1:3, 10:8, c(20, 60, 30)), 10) | |
115 | ||
116 | tween_colour_t(list(colours()[1:4], colours()[1:2], colours()[25:100]), 100) | |
117 | ||
118 | } |
0 | % Generated by roxygen2: do not edit by hand | |
1 | % Please edit documentation in R/tween_along.R | |
2 | \name{tween_along} | |
3 | \alias{tween_along} | |
4 | \title{Interpolate data along a given dimension} | |
5 | \usage{ | |
6 | tween_along(.data, ease, nframes, along, id = NULL, range = NULL, | |
7 | history = TRUE, keep_last = FALSE) | |
8 | } | |
9 | \arguments{ | |
10 | \item{.data}{A data.frame with components at different stages} | |
11 | ||
12 | \item{ease}{The easing function to use. Either a single string or one for | |
13 | each column in the data set.} | |
14 | ||
15 | \item{nframes}{The number of frames to calculate for the tween} | |
16 | ||
17 | \item{along}{The "time" point for each row} | |
18 | ||
19 | \item{id}{An unquoted expression giving the component id for each row. Will | |
20 | be evaluated in the context of \code{.data} so can refer to a column from that} | |
21 | ||
22 | \item{range}{The range of time points to include in the tween. If \code{NULL} it | |
23 | will use the range of \code{time}} | |
24 | ||
25 | \item{history}{Should earlier datapoints be kept in subsequent frames} | |
26 | ||
27 | \item{keep_last}{Should the last point of each id be kept beyond its time} | |
28 | } | |
29 | \value{ | |
30 | A data.frame with the same columns as \code{.data} along with \code{.id} giving | |
31 | the component id, \code{.phase} giving the state of each component in each frame, | |
32 | and \code{.frame} giving the frame membership of each row. | |
33 | } | |
34 | \description{ | |
35 | This tween takes groups of rows along with the time for each row and | |
36 | calculates the exact value at each at each frame. Further it allows for | |
37 | keeping the subsequent raw data from previous frame as well as letting the | |
38 | final row linger beyond its time. It especially useful for data that should | |
39 | be visualised as lines that are drawn along the x-axis, but can of course | |
40 | also be used for other dimensions as well (even dimensions not corresponding | |
41 | to any axis). | |
42 | } | |
43 | \seealso{ | |
44 | Other data.frame tween: \code{\link{tween_appear}}, | |
45 | \code{\link{tween_components}}, | |
46 | \code{\link{tween_elements}}, \code{\link{tween_events}}, | |
47 | \code{\link{tween_states}} | |
48 | } | |
49 | \concept{data.frame tween} |
0 | % Generated by roxygen2: do not edit by hand | |
1 | % Please edit documentation in R/tween_appear.R | |
2 | \name{tween_appear} | |
3 | \alias{tween_appear} | |
4 | \title{Tween a data.frame of appearances} | |
5 | \usage{ | |
6 | tween_appear(data, time, timerange, nframes) | |
7 | } | |
8 | \arguments{ | |
9 | \item{data}{A data.frame to tween} | |
10 | ||
11 | \item{time}{The name of the column that holds the time dimension. This does | |
12 | not need to hold time data in the strictest sence - any numerical type will | |
13 | do} | |
14 | ||
15 | \item{timerange}{The range of time to create the tween for. If missing it | |
16 | will defaults to the range of the time column} | |
17 | ||
18 | \item{nframes}{The number of frames to create for the tween. If missing it | |
19 | will create a frame for each full unit in \code{timerange} (e.g. | |
20 | \code{timerange = c(1, 10)} will give \code{nframes = 10})} | |
21 | } | |
22 | \value{ | |
23 | A data.frame as \code{data} but repeated \code{nframes} times and | |
24 | with the additional columns \code{.age} and \code{.frame} | |
25 | } | |
26 | \description{ | |
27 | This function is intended for use when you have a data.frame of events at | |
28 | different time points. This could be the appearance of an observation for | |
29 | example. This function replicates your data \code{nframes} times and | |
30 | calculates the duration of each frame. At each frame each row is | |
31 | assigned an age based on the progression of frames and the entry point of in | |
32 | time for that row. A negative age means that the row has not appeared yet. | |
33 | } | |
34 | \examples{ | |
35 | data <- data.frame( | |
36 | x = rnorm(100), | |
37 | y = rnorm(100), | |
38 | time = sample(50, 100, replace = TRUE) | |
39 | ) | |
40 | ||
41 | data <- tween_appear(data, 'time', nframes = 200) | |
42 | ||
43 | } | |
44 | \seealso{ | |
45 | Other data.frame tween: \code{\link{tween_along}}, | |
46 | \code{\link{tween_components}}, | |
47 | \code{\link{tween_elements}}, \code{\link{tween_events}}, | |
48 | \code{\link{tween_states}} | |
49 | } | |
50 | \concept{data.frame tween} |
0 | % Generated by roxygen2: do not edit by hand | |
1 | % Please edit documentation in R/tween_at.R | |
2 | \name{tween_at} | |
3 | \alias{tween_at} | |
4 | \title{Get a specific position between two states} | |
5 | \usage{ | |
6 | tween_at(from, to, at, ease) | |
7 | } | |
8 | \arguments{ | |
9 | \item{from, to}{A data.frame or vector of the same type. If either is of | |
10 | length/nrow 1 it will get repeated to match the length of the other} | |
11 | ||
12 | \item{at}{A numeric between 0 and 1 recycled to match the nrow/length of | |
13 | \code{from}} | |
14 | ||
15 | \item{ease}{A character vector giving valid easing functions. Recycled to | |
16 | match the ncol of \code{from}} | |
17 | } | |
18 | \value{ | |
19 | If \code{from}/\code{to} is a data.frame then a data.frame with the same | |
20 | columns. If \code{from}/\code{to} is a vector then a vector. | |
21 | } | |
22 | \description{ | |
23 | This tween allows you to query a specific postion between two states rather | |
24 | than generate evenly spaced states. It can work with either data.frames or | |
25 | single vectors and each row/element can have its own position and easing. | |
26 | } | |
27 | \examples{ | |
28 | tween_at(mtcars[1:6, ], mtcars[6:1, ], runif(6), 'cubic-in-out') | |
29 | ||
30 | } |
0 | % Generated by roxygen2: do not edit by hand | |
1 | % Please edit documentation in R/tween_components.R | |
2 | \name{tween_components} | |
3 | \alias{tween_components} | |
4 | \title{Interpolate individual component} | |
5 | \usage{ | |
6 | tween_components(.data, ease, nframes, time, id = NULL, range = NULL, | |
7 | enter = NULL, exit = NULL, enter_length = 0, exit_length = 0) | |
8 | } | |
9 | \arguments{ | |
10 | \item{.data}{A data.frame with components at different stages} | |
11 | ||
12 | \item{ease}{The easing function to use. Either a single string or one for | |
13 | each column in the data set.} | |
14 | ||
15 | \item{nframes}{The number of frames to calculate for the tween} | |
16 | ||
17 | \item{time}{An unquoted expression giving the timepoint for the different | |
18 | stages of the components. Will be evaluated in the context of \code{.data} so can | |
19 | refer to a column from that} | |
20 | ||
21 | \item{id}{An unquoted expression giving the component id for each row. Will | |
22 | be evaluated in the context of \code{.data} so can refer to a column from that} | |
23 | ||
24 | \item{range}{The range of time points to include in the tween. If \code{NULL} it | |
25 | will use the range of \code{time}} | |
26 | ||
27 | \item{enter}{functions that calculate a start state for new observations | |
28 | that appear in \code{to} or an end state for observations that are not present in | |
29 | \code{to}. If \code{NULL} the new/old observations will not be part of the tween. The | |
30 | function gets a data.frame with either the start state of the exiting | |
31 | observations, or the end state of the entering observations and must return | |
32 | a modified version of that data.frame. See the \emph{Match, Enter, and Exit} | |
33 | section for more information.} | |
34 | ||
35 | \item{exit}{functions that calculate a start state for new observations | |
36 | that appear in \code{to} or an end state for observations that are not present in | |
37 | \code{to}. If \code{NULL} the new/old observations will not be part of the tween. The | |
38 | function gets a data.frame with either the start state of the exiting | |
39 | observations, or the end state of the entering observations and must return | |
40 | a modified version of that data.frame. See the \emph{Match, Enter, and Exit} | |
41 | section for more information.} | |
42 | ||
43 | \item{enter_length, exit_length}{The lenght of the opening and closing | |
44 | transitions if \code{enter} and/or \code{exit} is given. Measured in the same units as | |
45 | \code{time}} | |
46 | } | |
47 | \value{ | |
48 | A data.frame with the same columns as \code{.data} along with \code{.id} giving | |
49 | the component id, \code{.phase} giving the state of each component in each frame, | |
50 | and \code{.frame} giving the frame membership of each row. | |
51 | } | |
52 | \description{ | |
53 | This function is much like \code{\link[=tween_elements]{tween_elements()}} but with a slightly different | |
54 | syntax and support for many of the newer features such as enter/exits and | |
55 | tween phase identification. Furthermore it uses tidy evaluation for time and | |
56 | id, making it easier to change these on the fly. The biggest change in terms | |
57 | of functionality compared to \code{tween_elements()} is that the easing function | |
58 | is now given per column and not per row. If different easing functions are | |
59 | needed for each transition then \code{tween_elements()} is needed. | |
60 | } | |
61 | \examples{ | |
62 | ||
63 | from_zero <- function(x) {x$x <- 0; x} | |
64 | ||
65 | data <- data.frame( | |
66 | x = c(1, 2, 2, 1, 2, 2), | |
67 | y = c(1, 2, 2, 2, 1, 1), | |
68 | time = c(1, 4, 10, 4, 8, 10), | |
69 | id = c(1, 1, 1, 2, 2, 2) | |
70 | ) | |
71 | ||
72 | data <- tween_components(data, 'cubic-in-out', nframes = 100, time = time, | |
73 | id = id, enter = from_zero, enter_length = 4) | |
74 | ||
75 | } | |
76 | \seealso{ | |
77 | Other data.frame tween: \code{\link{tween_along}}, | |
78 | \code{\link{tween_appear}}, \code{\link{tween_elements}}, | |
79 | \code{\link{tween_events}}, \code{\link{tween_states}} | |
80 | } | |
81 | \concept{data.frame tween} |
0 | % Generated by roxygen2: do not edit by hand | |
1 | % Please edit documentation in R/tween_elements.R | |
2 | \name{tween_elements} | |
3 | \alias{tween_elements} | |
4 | \title{Create frames based on individual element states} | |
5 | \usage{ | |
6 | tween_elements(data, time, group, ease, timerange, nframes) | |
7 | } | |
8 | \arguments{ | |
9 | \item{data}{A data.frame consisting at least of a column giving the | |
10 | observation id, a column giving timepoints for each state and a column giving | |
11 | the easing to apply when transitioning away from the state.} | |
12 | ||
13 | \item{time}{The name of the column holding timepoints} | |
14 | ||
15 | \item{group}{The name of the column holding the observation id} | |
16 | ||
17 | \item{ease}{The name of the column holding the easing function name} | |
18 | ||
19 | \item{timerange}{The range of time to span. If missing it will default to | |
20 | \code{range(data[[time]])}} | |
21 | ||
22 | \item{nframes}{The number of frames to generate. If missing it will default | |
23 | to \code{ceiling(diff(timerange) + 1)} (At least one frame for each | |
24 | individual timepoint)} | |
25 | } | |
26 | \value{ | |
27 | A data.frame with the same columns as \code{data} except for the | |
28 | group and ease columns, but replicated \code{nframes} times. Two additional | |
29 | columns called \code{.frame} and \code{.group} will be added giving the frame | |
30 | number and observation id for each row. | |
31 | } | |
32 | \description{ | |
33 | This function creates tweens for each observation individually, in cases | |
34 | where the data doesn't pass through collective states but consists of fully | |
35 | independent transitions. Each observation is identified by an id and each | |
36 | state must have a time associated with it. | |
37 | } | |
38 | \examples{ | |
39 | data <- data.frame( | |
40 | x = c(1, 2, 2, 1, 2, 2), | |
41 | y = c(1, 2, 2, 2, 1, 1), | |
42 | time = c(1, 4, 10, 4, 8, 10), | |
43 | group = c(1, 1, 1, 2, 2, 2), | |
44 | ease = rep('cubic-in-out', 6) | |
45 | ) | |
46 | ||
47 | data <- tween_elements(data, 'time', 'group', 'ease', nframes = 100) | |
48 | ||
49 | } | |
50 | \seealso{ | |
51 | Other data.frame tween: \code{\link{tween_along}}, | |
52 | \code{\link{tween_appear}}, | |
53 | \code{\link{tween_components}}, | |
54 | \code{\link{tween_events}}, \code{\link{tween_states}} | |
55 | } | |
56 | \concept{data.frame tween} |
0 | % Generated by roxygen2: do not edit by hand | |
1 | % Please edit documentation in R/tween_events.R | |
2 | \name{tween_events} | |
3 | \alias{tween_events} | |
4 | \title{Transition in and out of events} | |
5 | \usage{ | |
6 | tween_events(.data, ease, nframes, start, end = NULL, range = NULL, | |
7 | enter = NULL, exit = NULL, enter_length = 0, exit_length = 0) | |
8 | } | |
9 | \arguments{ | |
10 | \item{.data}{A data.frame with components at different stages} | |
11 | ||
12 | \item{ease}{The easing function to use. Either a single string or one for | |
13 | each column in the data set.} | |
14 | ||
15 | \item{nframes}{The number of frames to calculate for the tween} | |
16 | ||
17 | \item{start, end}{The start (and potential end) of the event encoded in the | |
18 | row, as unquoted expressions. Will be evaluated in the context of \code{.data} so | |
19 | can refer to columns in it. If \code{end = NULL} the event will be without extend | |
20 | and only visible in a single frame, unless \code{enter} and/or \code{exit} is given.} | |
21 | ||
22 | \item{range}{The range of time points to include in the tween. If \code{NULL} it | |
23 | will use the range of \code{time}} | |
24 | ||
25 | \item{enter}{functions that calculate a start state for new observations | |
26 | that appear in \code{to} or an end state for observations that are not present in | |
27 | \code{to}. If \code{NULL} the new/old observations will not be part of the tween. The | |
28 | function gets a data.frame with either the start state of the exiting | |
29 | observations, or the end state of the entering observations and must return | |
30 | a modified version of that data.frame. See the \emph{Match, Enter, and Exit} | |
31 | section for more information.} | |
32 | ||
33 | \item{exit}{functions that calculate a start state for new observations | |
34 | that appear in \code{to} or an end state for observations that are not present in | |
35 | \code{to}. If \code{NULL} the new/old observations will not be part of the tween. The | |
36 | function gets a data.frame with either the start state of the exiting | |
37 | observations, or the end state of the entering observations and must return | |
38 | a modified version of that data.frame. See the \emph{Match, Enter, and Exit} | |
39 | section for more information.} | |
40 | ||
41 | \item{enter_length}{The lenght of the opening and closing | |
42 | transitions if \code{enter} and/or \code{exit} is given. Measured in the same units as | |
43 | \code{time}} | |
44 | ||
45 | \item{exit_length}{The lenght of the opening and closing | |
46 | transitions if \code{enter} and/or \code{exit} is given. Measured in the same units as | |
47 | \code{time}} | |
48 | } | |
49 | \value{ | |
50 | A data.frame with the same columns as \code{.data} along with \code{.id} giving | |
51 | the component id, \code{.phase} giving the state of each component in each frame, | |
52 | and \code{.frame} giving the frame membership of each row. | |
53 | } | |
54 | \description{ | |
55 | This tweening function is a more powerful version of \code{\link[=tween_appear]{tween_appear()}}, with | |
56 | support for newer features such as enter/exits and tween phase | |
57 | identification. The tweener treats each row in the data as unique events in | |
58 | time, and creates frames with the correct events present at any given time. | |
59 | } | |
60 | \examples{ | |
61 | d <- data.frame( | |
62 | x = runif(20), | |
63 | y = runif(20), | |
64 | time = runif(20), | |
65 | duration = runif(20, max = 0.1) | |
66 | ) | |
67 | from_left <- function(x) { | |
68 | x$x <- -0.5 | |
69 | x | |
70 | } | |
71 | to_right <- function(x) { | |
72 | x$x <- 1.5 | |
73 | x | |
74 | } | |
75 | ||
76 | tween_events(d, 'cubic-in-out', 50, start = time, end = time + duration, | |
77 | enter = from_left, exit = to_right, enter_length = 0.1, | |
78 | exit_length = 0.05) | |
79 | ||
80 | } | |
81 | \seealso{ | |
82 | Other data.frame tween: \code{\link{tween_along}}, | |
83 | \code{\link{tween_appear}}, | |
84 | \code{\link{tween_components}}, | |
85 | \code{\link{tween_elements}}, \code{\link{tween_states}} | |
86 | } | |
87 | \concept{data.frame tween} |
0 | % Generated by roxygen2: do not edit by hand | |
1 | % Please edit documentation in R/tween_fill.R | |
2 | \name{tween_fill} | |
3 | \alias{tween_fill} | |
4 | \title{Fill out missing values by interpolation} | |
5 | \usage{ | |
6 | tween_fill(data, ease) | |
7 | } | |
8 | \arguments{ | |
9 | \item{data}{A data.frame or vector.} | |
10 | ||
11 | \item{ease}{A character vector giving valid easing functions. Recycled to | |
12 | match the ncol of \code{data}} | |
13 | } | |
14 | \value{ | |
15 | If \code{data} is a data.frame then a data.frame with the same | |
16 | columns. If \code{data} is a vector then a vector. | |
17 | } | |
18 | \description{ | |
19 | This tween fills out \code{NA} elements (or \code{NULL} elements if \code{data} is a list) | |
20 | by interpolating between the prior and next non-missing values. | |
21 | } | |
22 | \examples{ | |
23 | # Single vector | |
24 | tween_fill(c(1, NA, NA, NA, NA, NA, 2, 6, NA, NA, NA, -2), 'cubic-in-out') | |
25 | ||
26 | # Data frame | |
27 | tween_fill(mtcars[c(1, NA, NA, NA, NA, 4, NA, NA, NA, 10), ], 'cubic-in') | |
28 | ||
29 | } |
0 | % Generated by roxygen2: do not edit by hand | |
1 | % Please edit documentation in R/tween_state.R | |
2 | \name{tween_state} | |
3 | \alias{tween_state} | |
4 | \alias{keep_state} | |
5 | \alias{open_state} | |
6 | \alias{close_state} | |
7 | \title{Compose tweening between states} | |
8 | \usage{ | |
9 | tween_state(.data, to, ease, nframes, id = NULL, enter = NULL, | |
10 | exit = NULL) | |
11 | ||
12 | keep_state(.data, nframes) | |
13 | ||
14 | open_state(.data, ease, nframes, enter) | |
15 | ||
16 | close_state(.data, ease, nframes, exit) | |
17 | } | |
18 | \arguments{ | |
19 | \item{.data}{A data.frame to start from. If \code{.data} is the result of a prior | |
20 | tween, only the last frame will be used for the tween. The new tween will | |
21 | then be added to the prior tween} | |
22 | ||
23 | \item{to}{A data.frame to end at. It must contain the same columns as .data | |
24 | (exluding \code{.frame})} | |
25 | ||
26 | \item{ease}{The easing function to use. Either a single string or one for | |
27 | each column in the data set.} | |
28 | ||
29 | \item{nframes}{The number of frames to calculate for the tween} | |
30 | ||
31 | \item{id}{The column to match observations on. If \code{NULL} observations will be | |
32 | matched by position. See the \emph{Match, Enter, and Exit} section for more | |
33 | information.} | |
34 | ||
35 | \item{enter, exit}{functions that calculate a start state for new observations | |
36 | that appear in \code{to} or an end state for observations that are not present in | |
37 | \code{to}. If \code{NULL} the new/old observations will not be part of the tween. The | |
38 | function gets a data.frame with either the start state of the exiting | |
39 | observations, or the end state of the entering observations and must return | |
40 | a modified version of that data.frame. See the \emph{Match, Enter, and Exit} | |
41 | section for more information.} | |
42 | } | |
43 | \value{ | |
44 | A data.frame containing all the intermediary states in the tween, | |
45 | each state will be enumerated by the \code{.frame} column | |
46 | } | |
47 | \description{ | |
48 | The \code{tween_state()} is a counterpart to \code{tween_states()} that is aimed at | |
49 | letting you gradually build up a scene by composing state changes one by one. | |
50 | This setup lets you take more control over each state change and allows you | |
51 | to work with datasets with uneven number of rows, flexibly specifying what | |
52 | should happen with entering and exiting data. \code{keep_state()} is a simpel | |
53 | helper for letting you pause at a state. \code{open_state()} is a shortcut from | |
54 | tweening from an empty dataset with a given \code{enter()} function while | |
55 | \code{close_state()} is the same but will instead tween into an empty dataset with | |
56 | a given \code{exit()} function. | |
57 | } | |
58 | \section{Match, Enter, and Exit}{ | |
59 | ||
60 | When there are discrepancies between the two states to tweeen between you | |
61 | need a way to resolve the discrepancy before calculating the intermediary | |
62 | states. With discrepancies we mean that some data points are present in the | |
63 | start state and not in the end state, and/or some are present in the end | |
64 | state but not in the start state. A simple example is that the start state | |
65 | contains 100 rows and the end state contains 70. There are 30 missing rows | |
66 | that we need to do something about before we can calculate the tween. | |
67 | ||
68 | \strong{Making pairs} | |
69 | The first question to answer is "How do we know which observations are | |
70 | disappearing (\emph{exiting}) and/or appearing (\emph{entering})?". This is done with | |
71 | the \code{id} argument which should give a column name to match rows between the | |
72 | two states on. If \code{id = NULL} the rows will be matched by position (in the | |
73 | above example the last 30 rows in the start state will be entering). The \code{id} | |
74 | column must only contain unique values in order to work. | |
75 | ||
76 | \strong{Making up states} | |
77 | Once the rows in each state has been paired you'll end up with three sets of | |
78 | data. One containing rows that is present in both the start and end state, | |
79 | one containing rows only present in the start state, and one only containing | |
80 | rows present in the end state. The first group is easy - here you just tween | |
81 | between each rows - but for the other two we'll need some state to start or | |
82 | end the tween with. This is really the purpose of the \code{enter} and \code{exit} | |
83 | functions. They take a data frame containing the subset of data that has not | |
84 | been matched and must return a new data frame giving the state that these | |
85 | rows must be tweened from/into. A simple example could be an \code{enter} function | |
86 | that sets the variable giving the opacity in the plot to 0 - this will make | |
87 | the new points fade into view during the transition. | |
88 | ||
89 | \strong{Ignoring discrepancies} | |
90 | The default values for \code{enter} and \code{exit} is \code{NULL}. This value indicate that | |
91 | non-matching rows should simply be ignored for the transition and simply | |
92 | appear in the last frame of the tween. This is the default. | |
93 | } | |
94 | ||
95 | \examples{ | |
96 | data1 <- data.frame( | |
97 | x = 1:20, | |
98 | y = 0, | |
99 | colour = 'forestgreen', | |
100 | stringsAsFactors = FALSE | |
101 | ) | |
102 | data2 <- data1 | |
103 | data2$x <- 20:1 | |
104 | data2$y <- 1 | |
105 | ||
106 | data <- data1 \%>\% | |
107 | tween_state(data2, 'linear', 50) \%>\% | |
108 | keep_state(20) \%>\% | |
109 | tween_state(data1, 'bounce-out', 50) | |
110 | ||
111 | # Using enter and exit (made up numbers) | |
112 | df1 <- data.frame( | |
113 | country = c('Denmark', 'Sweden', 'Norway'), | |
114 | population = c(5e6, 10e6, 3.5e6) | |
115 | ) | |
116 | df2 <- data.frame( | |
117 | country = c('Denmark', 'Sweden', 'Norway', 'Finland'), | |
118 | population = c(6e6, 10.5e6, 4e6, 3e6) | |
119 | ) | |
120 | df3 <- data.frame( | |
121 | country = c('Denmark', 'Norway'), | |
122 | population = c(10e6, 6e6) | |
123 | ) | |
124 | to_zero <- function(x) { | |
125 | x$population <- 0 | |
126 | x | |
127 | } | |
128 | pop_devel <- df1 \%>\% | |
129 | tween_state(df2, 'cubic-in-out', 50, id = country, enter = to_zero) \%>\% | |
130 | tween_state(df3, 'cubic-in-out', 50, id = country, enter = to_zero, | |
131 | exit = to_zero) | |
132 | ||
133 | } |
0 | % Generated by roxygen2: do not edit by hand | |
1 | % Please edit documentation in R/tween_states.R | |
2 | \name{tween_states} | |
3 | \alias{tween_states} | |
4 | \title{Tween a list of data.frames representing states} | |
5 | \usage{ | |
6 | tween_states(data, tweenlength, statelength, ease, nframes) | |
7 | } | |
8 | \arguments{ | |
9 | \item{data}{A list of data.frames. Each data.frame must contain the same | |
10 | number of rows, but only the first data.frame needs to contain all columns. | |
11 | Subsequent data.frames need only contain the columns that shows change.} | |
12 | ||
13 | \item{tweenlength}{The lengths of the transitions between each state.} | |
14 | ||
15 | \item{statelength}{The length of the pause at each state.} | |
16 | ||
17 | \item{ease}{The easing functions to use for the transitions. See details.} | |
18 | ||
19 | \item{nframes}{The number of frames to generate. The actual number of frames | |
20 | might end up being higher depending on the regularity of \code{tweenlength} | |
21 | and \code{statelength}.} | |
22 | } | |
23 | \value{ | |
24 | A data.frame with the same columns as the first data.frame in | |
25 | \code{data}, but replicated \code{nframes} times. An additional column called | |
26 | \code{.frame} will be added giving the frame number. | |
27 | } | |
28 | \description{ | |
29 | This function is intended to create smooth transitions between states of | |
30 | data. States are defined as full data.frames or data.frames containing only | |
31 | the columns with change. Each state can have a defined period of pause, the | |
32 | transition length between each states can be defined as well as the easing | |
33 | function. | |
34 | } | |
35 | \examples{ | |
36 | data1 <- data.frame( | |
37 | x = 1:20, | |
38 | y = 0, | |
39 | colour = 'forestgreen', | |
40 | stringsAsFactors = FALSE | |
41 | ) | |
42 | data2 <- data1 | |
43 | data2$x <- 20:1 | |
44 | data2$y <- 1 | |
45 | ||
46 | data <- tween_states(list(data1, data2), 3, 1, 'cubic-in-out', 100) | |
47 | ||
48 | } | |
49 | \seealso{ | |
50 | Other data.frame tween: \code{\link{tween_along}}, | |
51 | \code{\link{tween_appear}}, | |
52 | \code{\link{tween_components}}, | |
53 | \code{\link{tween_elements}}, \code{\link{tween_events}} | |
54 | } | |
55 | \concept{data.frame tween} |
0 | % Generated by roxygen2: do not edit by hand | |
1 | % Please edit documentation in R/tweenr_package.R | |
2 | \docType{package} | |
3 | \name{tweenr-package} | |
4 | \alias{tweenr} | |
5 | \alias{tweenr-package} | |
6 | \title{tweenr: Interpolate Data for Smooth Animations} | |
7 | \description{ | |
8 | \if{html}{\figure{logo.png}{options: align='right'}} | |
9 | ||
10 | In order to create smooth animation between states of data, | |
11 | tweening is necessary. This package provides a range of functions for | |
12 | creating tweened data that can be used as basis for animation. Furthermore | |
13 | it adds a number of vectorized interpolaters for common R data | |
14 | types such as numeric, date and colour. | |
15 | } | |
16 | \details{ | |
17 | tweenr is a small collection of functions to help you in creating | |
18 | intermediary representations of your data, i.e. interpolating states of data. | |
19 | As such it's a great match for packages such as animate and gganimate, since | |
20 | it can work directly with data.frames of data, but it also provide fast and | |
21 | efficient interpolaters for numeric, date, datetime and colour that are | |
22 | vectorized and thus more efficient to use than the build in interpolation | |
23 | functions (mainly \code{\link[stats:approx]{stats::approx()}} and | |
24 | \code{\link[grDevices:colorRamp]{grDevices::colorRamp()}}). | |
25 | ||
26 | The main functions for data.frames are \code{\link[=tween_states]{tween_states()}}, | |
27 | \code{\link[=tween_elements]{tween_elements()}} and \code{\link[=tween_appear]{tween_appear()}}, while the | |
28 | standard interpolaters can be found at \code{\link[=tween]{tween()}} | |
29 | } | |
30 | \seealso{ | |
31 | Useful links: | |
32 | \itemize{ | |
33 | \item \url{https://github.com/thomasp85/tweenr} | |
34 | \item Report bugs at \url{https://github.com/thomasp85/tweenr/issues} | |
35 | } | |
36 | ||
37 | } | |
38 | \author{ | |
39 | \strong{Maintainer}: Thomas Lin Pedersen \email{thomasp85@gmail.com} | |
40 | ||
41 | } |
0 | // Generated by using Rcpp::compileAttributes() -> do not edit by hand | |
1 | // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 | |
2 | ||
3 | #include <Rcpp.h> | |
4 | ||
5 | using namespace Rcpp; | |
6 | ||
7 | // numeric_state_interpolator | |
8 | NumericVector numeric_state_interpolator(List data, DataFrame states); | |
9 | RcppExport SEXP _tweenr_numeric_state_interpolator(SEXP dataSEXP, SEXP statesSEXP) { | |
10 | BEGIN_RCPP | |
11 | Rcpp::RObject rcpp_result_gen; | |
12 | Rcpp::RNGScope rcpp_rngScope_gen; | |
13 | Rcpp::traits::input_parameter< List >::type data(dataSEXP); | |
14 | Rcpp::traits::input_parameter< DataFrame >::type states(statesSEXP); | |
15 | rcpp_result_gen = Rcpp::wrap(numeric_state_interpolator(data, states)); | |
16 | return rcpp_result_gen; | |
17 | END_RCPP | |
18 | } | |
19 | // colour_state_interpolator | |
20 | NumericMatrix colour_state_interpolator(List data, DataFrame states); | |
21 | RcppExport SEXP _tweenr_colour_state_interpolator(SEXP dataSEXP, SEXP statesSEXP) { | |
22 | BEGIN_RCPP | |
23 | Rcpp::RObject rcpp_result_gen; | |
24 | Rcpp::RNGScope rcpp_rngScope_gen; | |
25 | Rcpp::traits::input_parameter< List >::type data(dataSEXP); | |
26 | Rcpp::traits::input_parameter< DataFrame >::type states(statesSEXP); | |
27 | rcpp_result_gen = Rcpp::wrap(colour_state_interpolator(data, states)); | |
28 | return rcpp_result_gen; | |
29 | END_RCPP | |
30 | } | |
31 | // constant_state_interpolator | |
32 | CharacterVector constant_state_interpolator(List data, DataFrame states); | |
33 | RcppExport SEXP _tweenr_constant_state_interpolator(SEXP dataSEXP, SEXP statesSEXP) { | |
34 | BEGIN_RCPP | |
35 | Rcpp::RObject rcpp_result_gen; | |
36 | Rcpp::RNGScope rcpp_rngScope_gen; | |
37 | Rcpp::traits::input_parameter< List >::type data(dataSEXP); | |
38 | Rcpp::traits::input_parameter< DataFrame >::type states(statesSEXP); | |
39 | rcpp_result_gen = Rcpp::wrap(constant_state_interpolator(data, states)); | |
40 | return rcpp_result_gen; | |
41 | END_RCPP | |
42 | } | |
43 | // list_state_interpolator | |
44 | List list_state_interpolator(List data, DataFrame states); | |
45 | RcppExport SEXP _tweenr_list_state_interpolator(SEXP dataSEXP, SEXP statesSEXP) { | |
46 | BEGIN_RCPP | |
47 | Rcpp::RObject rcpp_result_gen; | |
48 | Rcpp::RNGScope rcpp_rngScope_gen; | |
49 | Rcpp::traits::input_parameter< List >::type data(dataSEXP); | |
50 | Rcpp::traits::input_parameter< DataFrame >::type states(statesSEXP); | |
51 | rcpp_result_gen = Rcpp::wrap(list_state_interpolator(data, states)); | |
52 | return rcpp_result_gen; | |
53 | END_RCPP | |
54 | } | |
55 | // numlist_state_interpolator | |
56 | List numlist_state_interpolator(List data, DataFrame states); | |
57 | RcppExport SEXP _tweenr_numlist_state_interpolator(SEXP dataSEXP, SEXP statesSEXP) { | |
58 | BEGIN_RCPP | |
59 | Rcpp::RObject rcpp_result_gen; | |
60 | Rcpp::RNGScope rcpp_rngScope_gen; | |
61 | Rcpp::traits::input_parameter< List >::type data(dataSEXP); | |
62 | Rcpp::traits::input_parameter< DataFrame >::type states(statesSEXP); | |
63 | rcpp_result_gen = Rcpp::wrap(numlist_state_interpolator(data, states)); | |
64 | return rcpp_result_gen; | |
65 | END_RCPP | |
66 | } | |
67 | // phase_state_interpolator | |
68 | CharacterVector phase_state_interpolator(List data, DataFrame states); | |
69 | RcppExport SEXP _tweenr_phase_state_interpolator(SEXP dataSEXP, SEXP statesSEXP) { | |
70 | BEGIN_RCPP | |
71 | Rcpp::RObject rcpp_result_gen; | |
72 | Rcpp::RNGScope rcpp_rngScope_gen; | |
73 | Rcpp::traits::input_parameter< List >::type data(dataSEXP); | |
74 | Rcpp::traits::input_parameter< DataFrame >::type states(statesSEXP); | |
75 | rcpp_result_gen = Rcpp::wrap(phase_state_interpolator(data, states)); | |
76 | return rcpp_result_gen; | |
77 | END_RCPP | |
78 | } | |
79 | // numeric_element_interpolator | |
80 | DataFrame numeric_element_interpolator(NumericVector data, CharacterVector group, IntegerVector frame, CharacterVector ease); | |
81 | RcppExport SEXP _tweenr_numeric_element_interpolator(SEXP dataSEXP, SEXP groupSEXP, SEXP frameSEXP, SEXP easeSEXP) { | |
82 | BEGIN_RCPP | |
83 | Rcpp::RObject rcpp_result_gen; | |
84 | Rcpp::RNGScope rcpp_rngScope_gen; | |
85 | Rcpp::traits::input_parameter< NumericVector >::type data(dataSEXP); | |
86 | Rcpp::traits::input_parameter< CharacterVector >::type group(groupSEXP); | |
87 | Rcpp::traits::input_parameter< IntegerVector >::type frame(frameSEXP); | |
88 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
89 | rcpp_result_gen = Rcpp::wrap(numeric_element_interpolator(data, group, frame, ease)); | |
90 | return rcpp_result_gen; | |
91 | END_RCPP | |
92 | } | |
93 | // colour_element_interpolator | |
94 | DataFrame colour_element_interpolator(NumericMatrix data, CharacterVector group, IntegerVector frame, CharacterVector ease); | |
95 | RcppExport SEXP _tweenr_colour_element_interpolator(SEXP dataSEXP, SEXP groupSEXP, SEXP frameSEXP, SEXP easeSEXP) { | |
96 | BEGIN_RCPP | |
97 | Rcpp::RObject rcpp_result_gen; | |
98 | Rcpp::RNGScope rcpp_rngScope_gen; | |
99 | Rcpp::traits::input_parameter< NumericMatrix >::type data(dataSEXP); | |
100 | Rcpp::traits::input_parameter< CharacterVector >::type group(groupSEXP); | |
101 | Rcpp::traits::input_parameter< IntegerVector >::type frame(frameSEXP); | |
102 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
103 | rcpp_result_gen = Rcpp::wrap(colour_element_interpolator(data, group, frame, ease)); | |
104 | return rcpp_result_gen; | |
105 | END_RCPP | |
106 | } | |
107 | // constant_element_interpolator | |
108 | DataFrame constant_element_interpolator(CharacterVector data, CharacterVector group, IntegerVector frame, CharacterVector ease); | |
109 | RcppExport SEXP _tweenr_constant_element_interpolator(SEXP dataSEXP, SEXP groupSEXP, SEXP frameSEXP, SEXP easeSEXP) { | |
110 | BEGIN_RCPP | |
111 | Rcpp::RObject rcpp_result_gen; | |
112 | Rcpp::RNGScope rcpp_rngScope_gen; | |
113 | Rcpp::traits::input_parameter< CharacterVector >::type data(dataSEXP); | |
114 | Rcpp::traits::input_parameter< CharacterVector >::type group(groupSEXP); | |
115 | Rcpp::traits::input_parameter< IntegerVector >::type frame(frameSEXP); | |
116 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
117 | rcpp_result_gen = Rcpp::wrap(constant_element_interpolator(data, group, frame, ease)); | |
118 | return rcpp_result_gen; | |
119 | END_RCPP | |
120 | } | |
121 | // list_element_interpolator | |
122 | List list_element_interpolator(List data, CharacterVector group, IntegerVector frame, CharacterVector ease); | |
123 | RcppExport SEXP _tweenr_list_element_interpolator(SEXP dataSEXP, SEXP groupSEXP, SEXP frameSEXP, SEXP easeSEXP) { | |
124 | BEGIN_RCPP | |
125 | Rcpp::RObject rcpp_result_gen; | |
126 | Rcpp::RNGScope rcpp_rngScope_gen; | |
127 | Rcpp::traits::input_parameter< List >::type data(dataSEXP); | |
128 | Rcpp::traits::input_parameter< CharacterVector >::type group(groupSEXP); | |
129 | Rcpp::traits::input_parameter< IntegerVector >::type frame(frameSEXP); | |
130 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
131 | rcpp_result_gen = Rcpp::wrap(list_element_interpolator(data, group, frame, ease)); | |
132 | return rcpp_result_gen; | |
133 | END_RCPP | |
134 | } | |
135 | // numlist_element_interpolator | |
136 | List numlist_element_interpolator(List data, CharacterVector group, IntegerVector frame, CharacterVector ease); | |
137 | RcppExport SEXP _tweenr_numlist_element_interpolator(SEXP dataSEXP, SEXP groupSEXP, SEXP frameSEXP, SEXP easeSEXP) { | |
138 | BEGIN_RCPP | |
139 | Rcpp::RObject rcpp_result_gen; | |
140 | Rcpp::RNGScope rcpp_rngScope_gen; | |
141 | Rcpp::traits::input_parameter< List >::type data(dataSEXP); | |
142 | Rcpp::traits::input_parameter< CharacterVector >::type group(groupSEXP); | |
143 | Rcpp::traits::input_parameter< IntegerVector >::type frame(frameSEXP); | |
144 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
145 | rcpp_result_gen = Rcpp::wrap(numlist_element_interpolator(data, group, frame, ease)); | |
146 | return rcpp_result_gen; | |
147 | END_RCPP | |
148 | } | |
149 | // phase_element_interpolator | |
150 | DataFrame phase_element_interpolator(CharacterVector data, CharacterVector group, IntegerVector frame, CharacterVector ease); | |
151 | RcppExport SEXP _tweenr_phase_element_interpolator(SEXP dataSEXP, SEXP groupSEXP, SEXP frameSEXP, SEXP easeSEXP) { | |
152 | BEGIN_RCPP | |
153 | Rcpp::RObject rcpp_result_gen; | |
154 | Rcpp::RNGScope rcpp_rngScope_gen; | |
155 | Rcpp::traits::input_parameter< CharacterVector >::type data(dataSEXP); | |
156 | Rcpp::traits::input_parameter< CharacterVector >::type group(groupSEXP); | |
157 | Rcpp::traits::input_parameter< IntegerVector >::type frame(frameSEXP); | |
158 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
159 | rcpp_result_gen = Rcpp::wrap(phase_element_interpolator(data, group, frame, ease)); | |
160 | return rcpp_result_gen; | |
161 | END_RCPP | |
162 | } | |
163 | // numeric_along_interpolator | |
164 | DataFrame numeric_along_interpolator(NumericVector data, CharacterVector group, NumericVector time, bool history, bool keep_last, int nframes, CharacterVector ease); | |
165 | RcppExport SEXP _tweenr_numeric_along_interpolator(SEXP dataSEXP, SEXP groupSEXP, SEXP timeSEXP, SEXP historySEXP, SEXP keep_lastSEXP, SEXP nframesSEXP, SEXP easeSEXP) { | |
166 | BEGIN_RCPP | |
167 | Rcpp::RObject rcpp_result_gen; | |
168 | Rcpp::RNGScope rcpp_rngScope_gen; | |
169 | Rcpp::traits::input_parameter< NumericVector >::type data(dataSEXP); | |
170 | Rcpp::traits::input_parameter< CharacterVector >::type group(groupSEXP); | |
171 | Rcpp::traits::input_parameter< NumericVector >::type time(timeSEXP); | |
172 | Rcpp::traits::input_parameter< bool >::type history(historySEXP); | |
173 | Rcpp::traits::input_parameter< bool >::type keep_last(keep_lastSEXP); | |
174 | Rcpp::traits::input_parameter< int >::type nframes(nframesSEXP); | |
175 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
176 | rcpp_result_gen = Rcpp::wrap(numeric_along_interpolator(data, group, time, history, keep_last, nframes, ease)); | |
177 | return rcpp_result_gen; | |
178 | END_RCPP | |
179 | } | |
180 | // colour_along_interpolator | |
181 | DataFrame colour_along_interpolator(NumericMatrix data, CharacterVector group, NumericVector time, bool history, bool keep_last, int nframes, CharacterVector ease); | |
182 | RcppExport SEXP _tweenr_colour_along_interpolator(SEXP dataSEXP, SEXP groupSEXP, SEXP timeSEXP, SEXP historySEXP, SEXP keep_lastSEXP, SEXP nframesSEXP, SEXP easeSEXP) { | |
183 | BEGIN_RCPP | |
184 | Rcpp::RObject rcpp_result_gen; | |
185 | Rcpp::RNGScope rcpp_rngScope_gen; | |
186 | Rcpp::traits::input_parameter< NumericMatrix >::type data(dataSEXP); | |
187 | Rcpp::traits::input_parameter< CharacterVector >::type group(groupSEXP); | |
188 | Rcpp::traits::input_parameter< NumericVector >::type time(timeSEXP); | |
189 | Rcpp::traits::input_parameter< bool >::type history(historySEXP); | |
190 | Rcpp::traits::input_parameter< bool >::type keep_last(keep_lastSEXP); | |
191 | Rcpp::traits::input_parameter< int >::type nframes(nframesSEXP); | |
192 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
193 | rcpp_result_gen = Rcpp::wrap(colour_along_interpolator(data, group, time, history, keep_last, nframes, ease)); | |
194 | return rcpp_result_gen; | |
195 | END_RCPP | |
196 | } | |
197 | // constant_along_interpolator | |
198 | DataFrame constant_along_interpolator(CharacterVector data, CharacterVector group, NumericVector time, bool history, bool keep_last, int nframes, CharacterVector ease); | |
199 | RcppExport SEXP _tweenr_constant_along_interpolator(SEXP dataSEXP, SEXP groupSEXP, SEXP timeSEXP, SEXP historySEXP, SEXP keep_lastSEXP, SEXP nframesSEXP, SEXP easeSEXP) { | |
200 | BEGIN_RCPP | |
201 | Rcpp::RObject rcpp_result_gen; | |
202 | Rcpp::RNGScope rcpp_rngScope_gen; | |
203 | Rcpp::traits::input_parameter< CharacterVector >::type data(dataSEXP); | |
204 | Rcpp::traits::input_parameter< CharacterVector >::type group(groupSEXP); | |
205 | Rcpp::traits::input_parameter< NumericVector >::type time(timeSEXP); | |
206 | Rcpp::traits::input_parameter< bool >::type history(historySEXP); | |
207 | Rcpp::traits::input_parameter< bool >::type keep_last(keep_lastSEXP); | |
208 | Rcpp::traits::input_parameter< int >::type nframes(nframesSEXP); | |
209 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
210 | rcpp_result_gen = Rcpp::wrap(constant_along_interpolator(data, group, time, history, keep_last, nframes, ease)); | |
211 | return rcpp_result_gen; | |
212 | END_RCPP | |
213 | } | |
214 | // list_along_interpolator | |
215 | List list_along_interpolator(List data, CharacterVector group, NumericVector time, bool history, bool keep_last, int nframes, CharacterVector ease); | |
216 | RcppExport SEXP _tweenr_list_along_interpolator(SEXP dataSEXP, SEXP groupSEXP, SEXP timeSEXP, SEXP historySEXP, SEXP keep_lastSEXP, SEXP nframesSEXP, SEXP easeSEXP) { | |
217 | BEGIN_RCPP | |
218 | Rcpp::RObject rcpp_result_gen; | |
219 | Rcpp::RNGScope rcpp_rngScope_gen; | |
220 | Rcpp::traits::input_parameter< List >::type data(dataSEXP); | |
221 | Rcpp::traits::input_parameter< CharacterVector >::type group(groupSEXP); | |
222 | Rcpp::traits::input_parameter< NumericVector >::type time(timeSEXP); | |
223 | Rcpp::traits::input_parameter< bool >::type history(historySEXP); | |
224 | Rcpp::traits::input_parameter< bool >::type keep_last(keep_lastSEXP); | |
225 | Rcpp::traits::input_parameter< int >::type nframes(nframesSEXP); | |
226 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
227 | rcpp_result_gen = Rcpp::wrap(list_along_interpolator(data, group, time, history, keep_last, nframes, ease)); | |
228 | return rcpp_result_gen; | |
229 | END_RCPP | |
230 | } | |
231 | // numlist_along_interpolator | |
232 | List numlist_along_interpolator(List data, CharacterVector group, NumericVector time, bool history, bool keep_last, int nframes, CharacterVector ease); | |
233 | RcppExport SEXP _tweenr_numlist_along_interpolator(SEXP dataSEXP, SEXP groupSEXP, SEXP timeSEXP, SEXP historySEXP, SEXP keep_lastSEXP, SEXP nframesSEXP, SEXP easeSEXP) { | |
234 | BEGIN_RCPP | |
235 | Rcpp::RObject rcpp_result_gen; | |
236 | Rcpp::RNGScope rcpp_rngScope_gen; | |
237 | Rcpp::traits::input_parameter< List >::type data(dataSEXP); | |
238 | Rcpp::traits::input_parameter< CharacterVector >::type group(groupSEXP); | |
239 | Rcpp::traits::input_parameter< NumericVector >::type time(timeSEXP); | |
240 | Rcpp::traits::input_parameter< bool >::type history(historySEXP); | |
241 | Rcpp::traits::input_parameter< bool >::type keep_last(keep_lastSEXP); | |
242 | Rcpp::traits::input_parameter< int >::type nframes(nframesSEXP); | |
243 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
244 | rcpp_result_gen = Rcpp::wrap(numlist_along_interpolator(data, group, time, history, keep_last, nframes, ease)); | |
245 | return rcpp_result_gen; | |
246 | END_RCPP | |
247 | } | |
248 | // phase_along_interpolator | |
249 | DataFrame phase_along_interpolator(CharacterVector group, NumericVector time, bool history, bool keep_last, int nframes); | |
250 | RcppExport SEXP _tweenr_phase_along_interpolator(SEXP groupSEXP, SEXP timeSEXP, SEXP historySEXP, SEXP keep_lastSEXP, SEXP nframesSEXP) { | |
251 | BEGIN_RCPP | |
252 | Rcpp::RObject rcpp_result_gen; | |
253 | Rcpp::RNGScope rcpp_rngScope_gen; | |
254 | Rcpp::traits::input_parameter< CharacterVector >::type group(groupSEXP); | |
255 | Rcpp::traits::input_parameter< NumericVector >::type time(timeSEXP); | |
256 | Rcpp::traits::input_parameter< bool >::type history(historySEXP); | |
257 | Rcpp::traits::input_parameter< bool >::type keep_last(keep_lastSEXP); | |
258 | Rcpp::traits::input_parameter< int >::type nframes(nframesSEXP); | |
259 | rcpp_result_gen = Rcpp::wrap(phase_along_interpolator(group, time, history, keep_last, nframes)); | |
260 | return rcpp_result_gen; | |
261 | END_RCPP | |
262 | } | |
263 | // numeric_at_interpolator | |
264 | NumericVector numeric_at_interpolator(NumericVector from, NumericVector to, NumericVector at, CharacterVector ease); | |
265 | RcppExport SEXP _tweenr_numeric_at_interpolator(SEXP fromSEXP, SEXP toSEXP, SEXP atSEXP, SEXP easeSEXP) { | |
266 | BEGIN_RCPP | |
267 | Rcpp::RObject rcpp_result_gen; | |
268 | Rcpp::RNGScope rcpp_rngScope_gen; | |
269 | Rcpp::traits::input_parameter< NumericVector >::type from(fromSEXP); | |
270 | Rcpp::traits::input_parameter< NumericVector >::type to(toSEXP); | |
271 | Rcpp::traits::input_parameter< NumericVector >::type at(atSEXP); | |
272 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
273 | rcpp_result_gen = Rcpp::wrap(numeric_at_interpolator(from, to, at, ease)); | |
274 | return rcpp_result_gen; | |
275 | END_RCPP | |
276 | } | |
277 | // colour_at_interpolator | |
278 | NumericMatrix colour_at_interpolator(NumericMatrix from, NumericMatrix to, NumericVector at, CharacterVector ease); | |
279 | RcppExport SEXP _tweenr_colour_at_interpolator(SEXP fromSEXP, SEXP toSEXP, SEXP atSEXP, SEXP easeSEXP) { | |
280 | BEGIN_RCPP | |
281 | Rcpp::RObject rcpp_result_gen; | |
282 | Rcpp::RNGScope rcpp_rngScope_gen; | |
283 | Rcpp::traits::input_parameter< NumericMatrix >::type from(fromSEXP); | |
284 | Rcpp::traits::input_parameter< NumericMatrix >::type to(toSEXP); | |
285 | Rcpp::traits::input_parameter< NumericVector >::type at(atSEXP); | |
286 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
287 | rcpp_result_gen = Rcpp::wrap(colour_at_interpolator(from, to, at, ease)); | |
288 | return rcpp_result_gen; | |
289 | END_RCPP | |
290 | } | |
291 | // constant_at_interpolator | |
292 | CharacterVector constant_at_interpolator(CharacterVector from, CharacterVector to, NumericVector at, CharacterVector ease); | |
293 | RcppExport SEXP _tweenr_constant_at_interpolator(SEXP fromSEXP, SEXP toSEXP, SEXP atSEXP, SEXP easeSEXP) { | |
294 | BEGIN_RCPP | |
295 | Rcpp::RObject rcpp_result_gen; | |
296 | Rcpp::RNGScope rcpp_rngScope_gen; | |
297 | Rcpp::traits::input_parameter< CharacterVector >::type from(fromSEXP); | |
298 | Rcpp::traits::input_parameter< CharacterVector >::type to(toSEXP); | |
299 | Rcpp::traits::input_parameter< NumericVector >::type at(atSEXP); | |
300 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
301 | rcpp_result_gen = Rcpp::wrap(constant_at_interpolator(from, to, at, ease)); | |
302 | return rcpp_result_gen; | |
303 | END_RCPP | |
304 | } | |
305 | // list_at_interpolator | |
306 | List list_at_interpolator(List from, List to, NumericVector at, CharacterVector ease); | |
307 | RcppExport SEXP _tweenr_list_at_interpolator(SEXP fromSEXP, SEXP toSEXP, SEXP atSEXP, SEXP easeSEXP) { | |
308 | BEGIN_RCPP | |
309 | Rcpp::RObject rcpp_result_gen; | |
310 | Rcpp::RNGScope rcpp_rngScope_gen; | |
311 | Rcpp::traits::input_parameter< List >::type from(fromSEXP); | |
312 | Rcpp::traits::input_parameter< List >::type to(toSEXP); | |
313 | Rcpp::traits::input_parameter< NumericVector >::type at(atSEXP); | |
314 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
315 | rcpp_result_gen = Rcpp::wrap(list_at_interpolator(from, to, at, ease)); | |
316 | return rcpp_result_gen; | |
317 | END_RCPP | |
318 | } | |
319 | // numlist_at_interpolator | |
320 | List numlist_at_interpolator(List from, List to, NumericVector at, CharacterVector ease); | |
321 | RcppExport SEXP _tweenr_numlist_at_interpolator(SEXP fromSEXP, SEXP toSEXP, SEXP atSEXP, SEXP easeSEXP) { | |
322 | BEGIN_RCPP | |
323 | Rcpp::RObject rcpp_result_gen; | |
324 | Rcpp::RNGScope rcpp_rngScope_gen; | |
325 | Rcpp::traits::input_parameter< List >::type from(fromSEXP); | |
326 | Rcpp::traits::input_parameter< List >::type to(toSEXP); | |
327 | Rcpp::traits::input_parameter< NumericVector >::type at(atSEXP); | |
328 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
329 | rcpp_result_gen = Rcpp::wrap(numlist_at_interpolator(from, to, at, ease)); | |
330 | return rcpp_result_gen; | |
331 | END_RCPP | |
332 | } | |
333 | // numeric_fill_interpolator | |
334 | NumericVector numeric_fill_interpolator(NumericVector data, CharacterVector ease); | |
335 | RcppExport SEXP _tweenr_numeric_fill_interpolator(SEXP dataSEXP, SEXP easeSEXP) { | |
336 | BEGIN_RCPP | |
337 | Rcpp::RObject rcpp_result_gen; | |
338 | Rcpp::RNGScope rcpp_rngScope_gen; | |
339 | Rcpp::traits::input_parameter< NumericVector >::type data(dataSEXP); | |
340 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
341 | rcpp_result_gen = Rcpp::wrap(numeric_fill_interpolator(data, ease)); | |
342 | return rcpp_result_gen; | |
343 | END_RCPP | |
344 | } | |
345 | // colour_fill_interpolator | |
346 | NumericMatrix colour_fill_interpolator(NumericMatrix data, CharacterVector ease); | |
347 | RcppExport SEXP _tweenr_colour_fill_interpolator(SEXP dataSEXP, SEXP easeSEXP) { | |
348 | BEGIN_RCPP | |
349 | Rcpp::RObject rcpp_result_gen; | |
350 | Rcpp::RNGScope rcpp_rngScope_gen; | |
351 | Rcpp::traits::input_parameter< NumericMatrix >::type data(dataSEXP); | |
352 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
353 | rcpp_result_gen = Rcpp::wrap(colour_fill_interpolator(data, ease)); | |
354 | return rcpp_result_gen; | |
355 | END_RCPP | |
356 | } | |
357 | // constant_fill_interpolator | |
358 | CharacterVector constant_fill_interpolator(CharacterVector data, CharacterVector ease); | |
359 | RcppExport SEXP _tweenr_constant_fill_interpolator(SEXP dataSEXP, SEXP easeSEXP) { | |
360 | BEGIN_RCPP | |
361 | Rcpp::RObject rcpp_result_gen; | |
362 | Rcpp::RNGScope rcpp_rngScope_gen; | |
363 | Rcpp::traits::input_parameter< CharacterVector >::type data(dataSEXP); | |
364 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
365 | rcpp_result_gen = Rcpp::wrap(constant_fill_interpolator(data, ease)); | |
366 | return rcpp_result_gen; | |
367 | END_RCPP | |
368 | } | |
369 | // list_fill_interpolator | |
370 | List list_fill_interpolator(List data, CharacterVector ease); | |
371 | RcppExport SEXP _tweenr_list_fill_interpolator(SEXP dataSEXP, SEXP easeSEXP) { | |
372 | BEGIN_RCPP | |
373 | Rcpp::RObject rcpp_result_gen; | |
374 | Rcpp::RNGScope rcpp_rngScope_gen; | |
375 | Rcpp::traits::input_parameter< List >::type data(dataSEXP); | |
376 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
377 | rcpp_result_gen = Rcpp::wrap(list_fill_interpolator(data, ease)); | |
378 | return rcpp_result_gen; | |
379 | END_RCPP | |
380 | } | |
381 | // numlist_fill_interpolator | |
382 | List numlist_fill_interpolator(List data, CharacterVector ease); | |
383 | RcppExport SEXP _tweenr_numlist_fill_interpolator(SEXP dataSEXP, SEXP easeSEXP) { | |
384 | BEGIN_RCPP | |
385 | Rcpp::RObject rcpp_result_gen; | |
386 | Rcpp::RNGScope rcpp_rngScope_gen; | |
387 | Rcpp::traits::input_parameter< List >::type data(dataSEXP); | |
388 | Rcpp::traits::input_parameter< CharacterVector >::type ease(easeSEXP); | |
389 | rcpp_result_gen = Rcpp::wrap(numlist_fill_interpolator(data, ease)); | |
390 | return rcpp_result_gen; | |
391 | END_RCPP | |
392 | } | |
393 | ||
394 | static const R_CallMethodDef CallEntries[] = { | |
395 | {"_tweenr_numeric_state_interpolator", (DL_FUNC) &_tweenr_numeric_state_interpolator, 2}, | |
396 | {"_tweenr_colour_state_interpolator", (DL_FUNC) &_tweenr_colour_state_interpolator, 2}, | |
397 | {"_tweenr_constant_state_interpolator", (DL_FUNC) &_tweenr_constant_state_interpolator, 2}, | |
398 | {"_tweenr_list_state_interpolator", (DL_FUNC) &_tweenr_list_state_interpolator, 2}, | |
399 | {"_tweenr_numlist_state_interpolator", (DL_FUNC) &_tweenr_numlist_state_interpolator, 2}, | |
400 | {"_tweenr_phase_state_interpolator", (DL_FUNC) &_tweenr_phase_state_interpolator, 2}, | |
401 | {"_tweenr_numeric_element_interpolator", (DL_FUNC) &_tweenr_numeric_element_interpolator, 4}, | |
402 | {"_tweenr_colour_element_interpolator", (DL_FUNC) &_tweenr_colour_element_interpolator, 4}, | |
403 | {"_tweenr_constant_element_interpolator", (DL_FUNC) &_tweenr_constant_element_interpolator, 4}, | |
404 | {"_tweenr_list_element_interpolator", (DL_FUNC) &_tweenr_list_element_interpolator, 4}, | |
405 | {"_tweenr_numlist_element_interpolator", (DL_FUNC) &_tweenr_numlist_element_interpolator, 4}, | |
406 | {"_tweenr_phase_element_interpolator", (DL_FUNC) &_tweenr_phase_element_interpolator, 4}, | |
407 | {"_tweenr_numeric_along_interpolator", (DL_FUNC) &_tweenr_numeric_along_interpolator, 7}, | |
408 | {"_tweenr_colour_along_interpolator", (DL_FUNC) &_tweenr_colour_along_interpolator, 7}, | |
409 | {"_tweenr_constant_along_interpolator", (DL_FUNC) &_tweenr_constant_along_interpolator, 7}, | |
410 | {"_tweenr_list_along_interpolator", (DL_FUNC) &_tweenr_list_along_interpolator, 7}, | |
411 | {"_tweenr_numlist_along_interpolator", (DL_FUNC) &_tweenr_numlist_along_interpolator, 7}, | |
412 | {"_tweenr_phase_along_interpolator", (DL_FUNC) &_tweenr_phase_along_interpolator, 5}, | |
413 | {"_tweenr_numeric_at_interpolator", (DL_FUNC) &_tweenr_numeric_at_interpolator, 4}, | |
414 | {"_tweenr_colour_at_interpolator", (DL_FUNC) &_tweenr_colour_at_interpolator, 4}, | |
415 | {"_tweenr_constant_at_interpolator", (DL_FUNC) &_tweenr_constant_at_interpolator, 4}, | |
416 | {"_tweenr_list_at_interpolator", (DL_FUNC) &_tweenr_list_at_interpolator, 4}, | |
417 | {"_tweenr_numlist_at_interpolator", (DL_FUNC) &_tweenr_numlist_at_interpolator, 4}, | |
418 | {"_tweenr_numeric_fill_interpolator", (DL_FUNC) &_tweenr_numeric_fill_interpolator, 2}, | |
419 | {"_tweenr_colour_fill_interpolator", (DL_FUNC) &_tweenr_colour_fill_interpolator, 2}, | |
420 | {"_tweenr_constant_fill_interpolator", (DL_FUNC) &_tweenr_constant_fill_interpolator, 2}, | |
421 | {"_tweenr_list_fill_interpolator", (DL_FUNC) &_tweenr_list_fill_interpolator, 2}, | |
422 | {"_tweenr_numlist_fill_interpolator", (DL_FUNC) &_tweenr_numlist_fill_interpolator, 2}, | |
423 | {NULL, NULL, 0} | |
424 | }; | |
425 | ||
426 | RcppExport void R_init_tweenr(DllInfo *dll) { | |
427 | R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); | |
428 | R_useDynamicSymbols(dll, FALSE); | |
429 | } |
0 | // | |
1 | // easing.c | |
2 | // | |
3 | // Copyright (c) 2011, Auerhaus Development, LLC | |
4 | // | |
5 | // This program is free software. It comes without any warranty, to | |
6 | // the extent permitted by applicable law. You can redistribute it | |
7 | // and/or modify it under the terms of the Do What The Fuck You Want | |
8 | // To Public License, Version 2, as published by Sam Hocevar. See | |
9 | // http://sam.zoy.org/wtfpl/COPYING for more details. | |
10 | // | |
11 | ||
12 | #include <math.h> | |
13 | #include "easing.h" | |
14 | ||
15 | // Modeled after the line y = x | |
16 | AHFloat LinearInterpolation(AHFloat p) | |
17 | { | |
18 | return p; | |
19 | } | |
20 | ||
21 | // Modeled after the parabola y = x^2 | |
22 | AHFloat QuadraticEaseIn(AHFloat p) | |
23 | { | |
24 | return p * p; | |
25 | } | |
26 | ||
27 | // Modeled after the parabola y = -x^2 + 2x | |
28 | AHFloat QuadraticEaseOut(AHFloat p) | |
29 | { | |
30 | return -(p * (p - 2)); | |
31 | } | |
32 | ||
33 | // Modeled after the piecewise quadratic | |
34 | // y = (1/2)((2x)^2) ; [0, 0.5) | |
35 | // y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1] | |
36 | AHFloat QuadraticEaseInOut(AHFloat p) | |
37 | { | |
38 | if(p < 0.5) | |
39 | { | |
40 | return 2 * p * p; | |
41 | } | |
42 | else | |
43 | { | |
44 | return (-2 * p * p) + (4 * p) - 1; | |
45 | } | |
46 | } | |
47 | ||
48 | // Modeled after the cubic y = x^3 | |
49 | AHFloat CubicEaseIn(AHFloat p) | |
50 | { | |
51 | return p * p * p; | |
52 | } | |
53 | ||
54 | // Modeled after the cubic y = (x - 1)^3 + 1 | |
55 | AHFloat CubicEaseOut(AHFloat p) | |
56 | { | |
57 | AHFloat f = (p - 1); | |
58 | return f * f * f + 1; | |
59 | } | |
60 | ||
61 | // Modeled after the piecewise cubic | |
62 | // y = (1/2)((2x)^3) ; [0, 0.5) | |
63 | // y = (1/2)((2x-2)^3 + 2) ; [0.5, 1] | |
64 | AHFloat CubicEaseInOut(AHFloat p) | |
65 | { | |
66 | if(p < 0.5) | |
67 | { | |
68 | return 4 * p * p * p; | |
69 | } | |
70 | else | |
71 | { | |
72 | AHFloat f = ((2 * p) - 2); | |
73 | return 0.5 * f * f * f + 1; | |
74 | } | |
75 | } | |
76 | ||
77 | // Modeled after the quartic x^4 | |
78 | AHFloat QuarticEaseIn(AHFloat p) | |
79 | { | |
80 | return p * p * p * p; | |
81 | } | |
82 | ||
83 | // Modeled after the quartic y = 1 - (x - 1)^4 | |
84 | AHFloat QuarticEaseOut(AHFloat p) | |
85 | { | |
86 | AHFloat f = (p - 1); | |
87 | return f * f * f * (1 - p) + 1; | |
88 | } | |
89 | ||
90 | // Modeled after the piecewise quartic | |
91 | // y = (1/2)((2x)^4) ; [0, 0.5) | |
92 | // y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1] | |
93 | AHFloat QuarticEaseInOut(AHFloat p) | |
94 | { | |
95 | if(p < 0.5) | |
96 | { | |
97 | return 8 * p * p * p * p; | |
98 | } | |
99 | else | |
100 | { | |
101 | AHFloat f = (p - 1); | |
102 | return -8 * f * f * f * f + 1; | |
103 | } | |
104 | } | |
105 | ||
106 | // Modeled after the quintic y = x^5 | |
107 | AHFloat QuinticEaseIn(AHFloat p) | |
108 | { | |
109 | return p * p * p * p * p; | |
110 | } | |
111 | ||
112 | // Modeled after the quintic y = (x - 1)^5 + 1 | |
113 | AHFloat QuinticEaseOut(AHFloat p) | |
114 | { | |
115 | AHFloat f = (p - 1); | |
116 | return f * f * f * f * f + 1; | |
117 | } | |
118 | ||
119 | // Modeled after the piecewise quintic | |
120 | // y = (1/2)((2x)^5) ; [0, 0.5) | |
121 | // y = (1/2)((2x-2)^5 + 2) ; [0.5, 1] | |
122 | AHFloat QuinticEaseInOut(AHFloat p) | |
123 | { | |
124 | if(p < 0.5) | |
125 | { | |
126 | return 16 * p * p * p * p * p; | |
127 | } | |
128 | else | |
129 | { | |
130 | AHFloat f = ((2 * p) - 2); | |
131 | return 0.5 * f * f * f * f * f + 1; | |
132 | } | |
133 | } | |
134 | ||
135 | // Modeled after quarter-cycle of sine wave | |
136 | AHFloat SineEaseIn(AHFloat p) | |
137 | { | |
138 | return sin((p - 1) * M_PI_2) + 1; | |
139 | } | |
140 | ||
141 | // Modeled after quarter-cycle of sine wave (different phase) | |
142 | AHFloat SineEaseOut(AHFloat p) | |
143 | { | |
144 | return sin(p * M_PI_2); | |
145 | } | |
146 | ||
147 | // Modeled after half sine wave | |
148 | AHFloat SineEaseInOut(AHFloat p) | |
149 | { | |
150 | return 0.5 * (1 - cos(p * M_PI)); | |
151 | } | |
152 | ||
153 | // Modeled after shifted quadrant IV of unit circle | |
154 | AHFloat CircularEaseIn(AHFloat p) | |
155 | { | |
156 | return 1 - sqrt(1 - (p * p)); | |
157 | } | |
158 | ||
159 | // Modeled after shifted quadrant II of unit circle | |
160 | AHFloat CircularEaseOut(AHFloat p) | |
161 | { | |
162 | return sqrt((2 - p) * p); | |
163 | } | |
164 | ||
165 | // Modeled after the piecewise circular function | |
166 | // y = (1/2)(1 - sqrt(1 - 4x^2)) ; [0, 0.5) | |
167 | // y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1] | |
168 | AHFloat CircularEaseInOut(AHFloat p) | |
169 | { | |
170 | if(p < 0.5) | |
171 | { | |
172 | return 0.5 * (1 - sqrt(1 - 4 * (p * p))); | |
173 | } | |
174 | else | |
175 | { | |
176 | return 0.5 * (sqrt(-((2 * p) - 3) * ((2 * p) - 1)) + 1); | |
177 | } | |
178 | } | |
179 | ||
180 | // Modeled after the exponential function y = 2^(10(x - 1)) | |
181 | AHFloat ExponentialEaseIn(AHFloat p) | |
182 | { | |
183 | return (p == 0.0) ? p : pow(2, 10 * (p - 1)); | |
184 | } | |
185 | ||
186 | // Modeled after the exponential function y = -2^(-10x) + 1 | |
187 | AHFloat ExponentialEaseOut(AHFloat p) | |
188 | { | |
189 | return (p == 1.0) ? p : 1 - pow(2, -10 * p); | |
190 | } | |
191 | ||
192 | // Modeled after the piecewise exponential | |
193 | // y = (1/2)2^(10(2x - 1)) ; [0,0.5) | |
194 | // y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1] | |
195 | AHFloat ExponentialEaseInOut(AHFloat p) | |
196 | { | |
197 | if(p == 0.0 || p == 1.0) return p; | |
198 | ||
199 | if(p < 0.5) | |
200 | { | |
201 | return 0.5 * pow(2, (20 * p) - 10); | |
202 | } | |
203 | else | |
204 | { | |
205 | return -0.5 * pow(2, (-20 * p) + 10) + 1; | |
206 | } | |
207 | } | |
208 | ||
209 | // Modeled after the damped sine wave y = sin(13pi/2*x)*pow(2, 10 * (x - 1)) | |
210 | AHFloat ElasticEaseIn(AHFloat p) | |
211 | { | |
212 | return sin(13 * M_PI_2 * p) * pow(2, 10 * (p - 1)); | |
213 | } | |
214 | ||
215 | // Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*pow(2, -10x) + 1 | |
216 | AHFloat ElasticEaseOut(AHFloat p) | |
217 | { | |
218 | return sin(-13 * M_PI_2 * (p + 1)) * pow(2, -10 * p) + 1; | |
219 | } | |
220 | ||
221 | // Modeled after the piecewise exponentially-damped sine wave: | |
222 | // y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1)) ; [0,0.5) | |
223 | // y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1] | |
224 | AHFloat ElasticEaseInOut(AHFloat p) | |
225 | { | |
226 | if(p < 0.5) | |
227 | { | |
228 | return 0.5 * sin(13 * M_PI_2 * (2 * p)) * pow(2, 10 * ((2 * p) - 1)); | |
229 | } | |
230 | else | |
231 | { | |
232 | return 0.5 * (sin(-13 * M_PI_2 * ((2 * p - 1) + 1)) * pow(2, -10 * (2 * p - 1)) + 2); | |
233 | } | |
234 | } | |
235 | ||
236 | // Modeled after the overshooting cubic y = x^3-x*sin(x*pi) | |
237 | AHFloat BackEaseIn(AHFloat p) | |
238 | { | |
239 | return p * p * p - p * sin(p * M_PI); | |
240 | } | |
241 | ||
242 | // Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi)) | |
243 | AHFloat BackEaseOut(AHFloat p) | |
244 | { | |
245 | AHFloat f = (1 - p); | |
246 | return 1 - (f * f * f - f * sin(f * M_PI)); | |
247 | } | |
248 | ||
249 | // Modeled after the piecewise overshooting cubic function: | |
250 | // y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5) | |
251 | // y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1] | |
252 | AHFloat BackEaseInOut(AHFloat p) | |
253 | { | |
254 | if(p < 0.5) | |
255 | { | |
256 | AHFloat f = 2 * p; | |
257 | return 0.5 * (f * f * f - f * sin(f * M_PI)); | |
258 | } | |
259 | else | |
260 | { | |
261 | AHFloat f = (1 - (2*p - 1)); | |
262 | return 0.5 * (1 - (f * f * f - f * sin(f * M_PI))) + 0.5; | |
263 | } | |
264 | } | |
265 | ||
266 | AHFloat BounceEaseIn(AHFloat p) | |
267 | { | |
268 | return 1 - BounceEaseOut(1 - p); | |
269 | } | |
270 | ||
271 | AHFloat BounceEaseOut(AHFloat p) | |
272 | { | |
273 | if(p < 4/11.0) | |
274 | { | |
275 | return (121 * p * p)/16.0; | |
276 | } | |
277 | else if(p < 8/11.0) | |
278 | { | |
279 | return (363/40.0 * p * p) - (99/10.0 * p) + 17/5.0; | |
280 | } | |
281 | else if(p < 9/10.0) | |
282 | { | |
283 | return (4356/361.0 * p * p) - (35442/1805.0 * p) + 16061/1805.0; | |
284 | } | |
285 | else | |
286 | { | |
287 | return (54/5.0 * p * p) - (513/25.0 * p) + 268/25.0; | |
288 | } | |
289 | } | |
290 | ||
291 | AHFloat BounceEaseInOut(AHFloat p) | |
292 | { | |
293 | if(p < 0.5) | |
294 | { | |
295 | return 0.5 * BounceEaseIn(p*2); | |
296 | } | |
297 | else | |
298 | { | |
299 | return 0.5 * BounceEaseOut(p * 2 - 1) + 0.5; | |
300 | } | |
301 | } |
0 | // | |
1 | // easing.h | |
2 | // | |
3 | // Copyright (c) 2011, Auerhaus Development, LLC | |
4 | // | |
5 | // This program is free software. It comes without any warranty, to | |
6 | // the extent permitted by applicable law. You can redistribute it | |
7 | // and/or modify it under the terms of the Do What The Fuck You Want | |
8 | // To Public License, Version 2, as published by Sam Hocevar. See | |
9 | // http://sam.zoy.org/wtfpl/COPYING for more details. | |
10 | // | |
11 | ||
12 | #ifndef AH_EASING_H | |
13 | #define AH_EASING_H | |
14 | ||
15 | #if defined(__LP64__) && !defined(AH_EASING_USE_DBL_PRECIS) | |
16 | #define AH_EASING_USE_DBL_PRECIS | |
17 | #endif | |
18 | ||
19 | #ifdef AH_EASING_USE_DBL_PRECIS | |
20 | #define AHFloat double | |
21 | #else | |
22 | #define AHFloat float | |
23 | #endif | |
24 | ||
25 | #if defined __cplusplus | |
26 | extern "C" { | |
27 | #endif | |
28 | ||
29 | typedef AHFloat (*AHEasingFunction)(AHFloat); | |
30 | ||
31 | // Linear interpolation (no easing) | |
32 | AHFloat LinearInterpolation(AHFloat p); | |
33 | ||
34 | // Quadratic easing; p^2 | |
35 | AHFloat QuadraticEaseIn(AHFloat p); | |
36 | AHFloat QuadraticEaseOut(AHFloat p); | |
37 | AHFloat QuadraticEaseInOut(AHFloat p); | |
38 | ||
39 | // Cubic easing; p^3 | |
40 | AHFloat CubicEaseIn(AHFloat p); | |
41 | AHFloat CubicEaseOut(AHFloat p); | |
42 | AHFloat CubicEaseInOut(AHFloat p); | |
43 | ||
44 | // Quartic easing; p^4 | |
45 | AHFloat QuarticEaseIn(AHFloat p); | |
46 | AHFloat QuarticEaseOut(AHFloat p); | |
47 | AHFloat QuarticEaseInOut(AHFloat p); | |
48 | ||
49 | // Quintic easing; p^5 | |
50 | AHFloat QuinticEaseIn(AHFloat p); | |
51 | AHFloat QuinticEaseOut(AHFloat p); | |
52 | AHFloat QuinticEaseInOut(AHFloat p); | |
53 | ||
54 | // Sine wave easing; sin(p * PI/2) | |
55 | AHFloat SineEaseIn(AHFloat p); | |
56 | AHFloat SineEaseOut(AHFloat p); | |
57 | AHFloat SineEaseInOut(AHFloat p); | |
58 | ||
59 | // Circular easing; sqrt(1 - p^2) | |
60 | AHFloat CircularEaseIn(AHFloat p); | |
61 | AHFloat CircularEaseOut(AHFloat p); | |
62 | AHFloat CircularEaseInOut(AHFloat p); | |
63 | ||
64 | // Exponential easing, base 2 | |
65 | AHFloat ExponentialEaseIn(AHFloat p); | |
66 | AHFloat ExponentialEaseOut(AHFloat p); | |
67 | AHFloat ExponentialEaseInOut(AHFloat p); | |
68 | ||
69 | // Exponentially-damped sine wave easing | |
70 | AHFloat ElasticEaseIn(AHFloat p); | |
71 | AHFloat ElasticEaseOut(AHFloat p); | |
72 | AHFloat ElasticEaseInOut(AHFloat p); | |
73 | ||
74 | // Overshooting cubic easing; | |
75 | AHFloat BackEaseIn(AHFloat p); | |
76 | AHFloat BackEaseOut(AHFloat p); | |
77 | AHFloat BackEaseInOut(AHFloat p); | |
78 | ||
79 | // Exponentially-decaying bounce easing | |
80 | AHFloat BounceEaseIn(AHFloat p); | |
81 | AHFloat BounceEaseOut(AHFloat p); | |
82 | AHFloat BounceEaseInOut(AHFloat p); | |
83 | ||
84 | #ifdef __cplusplus | |
85 | } | |
86 | #endif | |
87 | ||
88 | #endif |
0 | #include <Rcpp.h> | |
1 | #include "easing.h" | |
2 | ||
3 | using namespace Rcpp; | |
4 | ||
5 | enum easeEnum { | |
6 | linear, | |
7 | quadratic_in, | |
8 | quadratic_out, | |
9 | quadratic_in_out, | |
10 | cubic_in, | |
11 | cubic_out, | |
12 | cubic_in_out, | |
13 | quartic_in, | |
14 | quartic_out, | |
15 | quartic_in_out, | |
16 | quintic_in, | |
17 | quintic_out, | |
18 | quintic_in_out, | |
19 | sine_in, | |
20 | sine_out, | |
21 | sine_in_out, | |
22 | circular_in, | |
23 | circular_out, | |
24 | circular_in_out, | |
25 | exponential_in, | |
26 | exponential_out, | |
27 | exponential_in_out, | |
28 | elastic_in, | |
29 | elastic_out, | |
30 | elastic_in_out, | |
31 | back_in, | |
32 | back_out, | |
33 | back_in_out, | |
34 | bounce_in, | |
35 | bounce_out, | |
36 | bounce_in_out, | |
37 | UNKNOWN | |
38 | }; | |
39 | easeEnum hashEase(std::string ease) { | |
40 | if (ease == "linear") return linear; | |
41 | if (ease == "quadratic-in") return quadratic_in; | |
42 | if (ease == "quadratic-out") return quadratic_out; | |
43 | if (ease == "quadratic-in-out") return quadratic_in_out; | |
44 | if (ease == "cubic-in") return cubic_in; | |
45 | if (ease == "cubic-out") return cubic_out; | |
46 | if (ease == "cubic-in-out") return cubic_in_out; | |
47 | if (ease == "quartic-in") return quartic_in; | |
48 | if (ease == "quartic-out") return quartic_out; | |
49 | if (ease == "quartic-in-out") return quartic_in_out; | |
50 | if (ease == "quintic-in") return quintic_in; | |
51 | if (ease == "quintic-out") return quintic_out; | |
52 | if (ease == "quintic-in-out") return quintic_in_out; | |
53 | if (ease == "sine-in") return sine_in; | |
54 | if (ease == "sine-out") return sine_out; | |
55 | if (ease == "sine-in-out") return sine_in_out; | |
56 | if (ease == "circular-in") return circular_in; | |
57 | if (ease == "circular-out") return circular_out; | |
58 | if (ease == "circular-in-out") return circular_in_out; | |
59 | if (ease == "exponential-in") return exponential_in; | |
60 | if (ease == "exponential-out") return exponential_out; | |
61 | if (ease == "exponential-in-out") return exponential_in_out; | |
62 | if (ease == "elastic-in") return elastic_in; | |
63 | if (ease == "elastic-out") return elastic_out; | |
64 | if (ease == "elastic-in-out") return elastic_in_out; | |
65 | if (ease == "back-in") return back_in; | |
66 | if (ease == "back-out") return back_out; | |
67 | if (ease == "back-in-out") return back_in_out; | |
68 | if (ease == "bounce-in") return bounce_in; | |
69 | if (ease == "bounce-out") return bounce_out; | |
70 | if (ease == "bounce-in-out") return bounce_in_out; | |
71 | return UNKNOWN; | |
72 | } | |
73 | ||
74 | std::vector<double> easeSeq(std::string easer, int length) { | |
75 | std::vector<double> res(length); | |
76 | double p; | |
77 | // Just linear for now | |
78 | for(int i = 0; i < length; ++i) { | |
79 | p = double(i) / length; | |
80 | switch (hashEase(easer)) { | |
81 | case linear: | |
82 | res[i] = LinearInterpolation(p); | |
83 | break; | |
84 | case quadratic_in: | |
85 | res[i] = QuadraticEaseIn(p); | |
86 | break; | |
87 | case quadratic_out: | |
88 | res[i] = QuadraticEaseOut(p); | |
89 | break; | |
90 | case quadratic_in_out: | |
91 | res[i] = QuadraticEaseInOut(p); | |
92 | break; | |
93 | case cubic_in: | |
94 | res[i] = CubicEaseIn(p); | |
95 | break; | |
96 | case cubic_out: | |
97 | res[i] = CubicEaseOut(p); | |
98 | break; | |
99 | case cubic_in_out: | |
100 | res[i] = CubicEaseInOut(p); | |
101 | break; | |
102 | case quartic_in: | |
103 | res[i] = QuarticEaseIn(p); | |
104 | break; | |
105 | case quartic_out: | |
106 | res[i] = QuarticEaseOut(p); | |
107 | break; | |
108 | case quartic_in_out: | |
109 | res[i] = QuarticEaseInOut(p); | |
110 | break; | |
111 | case quintic_in: | |
112 | res[i] = QuinticEaseIn(p); | |
113 | break; | |
114 | case quintic_out: | |
115 | res[i] = QuinticEaseOut(p); | |
116 | break; | |
117 | case quintic_in_out: | |
118 | res[i] = QuinticEaseInOut(p); | |
119 | break; | |
120 | case sine_in: | |
121 | res[i] = SineEaseIn(p); | |
122 | break; | |
123 | case sine_out: | |
124 | res[i] = SineEaseOut(p); | |
125 | break; | |
126 | case sine_in_out: | |
127 | res[i] = SineEaseInOut(p); | |
128 | break; | |
129 | case circular_in: | |
130 | res[i] = CircularEaseIn(p); | |
131 | break; | |
132 | case circular_out: | |
133 | res[i] = CircularEaseOut(p); | |
134 | break; | |
135 | case circular_in_out: | |
136 | res[i] = CircularEaseInOut(p); | |
137 | break; | |
138 | case exponential_in: | |
139 | res[i] = ExponentialEaseIn(p); | |
140 | break; | |
141 | case exponential_out: | |
142 | res[i] = ExponentialEaseOut(p); | |
143 | break; | |
144 | case exponential_in_out: | |
145 | res[i] = ExponentialEaseInOut(p); | |
146 | break; | |
147 | case elastic_in: | |
148 | res[i] = ElasticEaseIn(p); | |
149 | break; | |
150 | case elastic_out: | |
151 | res[i] = ElasticEaseOut(p); | |
152 | break; | |
153 | case elastic_in_out: | |
154 | res[i] = ElasticEaseInOut(p); | |
155 | break; | |
156 | case back_in: | |
157 | res[i] = BackEaseIn(p); | |
158 | break; | |
159 | case back_out: | |
160 | res[i] = BackEaseOut(p); | |
161 | break; | |
162 | case back_in_out: | |
163 | res[i] = BackEaseInOut(p); | |
164 | break; | |
165 | case bounce_in: | |
166 | res[i] = BounceEaseIn(p); | |
167 | break; | |
168 | case bounce_out: | |
169 | res[i] = BounceEaseOut(p); | |
170 | break; | |
171 | case bounce_in_out: | |
172 | res[i] = BounceEaseInOut(p); | |
173 | break; | |
174 | case UNKNOWN: | |
175 | stop("Unknown easing function"); | |
176 | } | |
177 | } | |
178 | return res; | |
179 | } | |
180 | double easePos(double p, std::string easer) { | |
181 | double p_new; | |
182 | switch (hashEase(easer)) { | |
183 | case linear: | |
184 | p_new = LinearInterpolation(p); | |
185 | break; | |
186 | case quadratic_in: | |
187 | p_new = QuadraticEaseIn(p); | |
188 | break; | |
189 | case quadratic_out: | |
190 | p_new = QuadraticEaseOut(p); | |
191 | break; | |
192 | case quadratic_in_out: | |
193 | p_new = QuadraticEaseInOut(p); | |
194 | break; | |
195 | case cubic_in: | |
196 | p_new = CubicEaseIn(p); | |
197 | break; | |
198 | case cubic_out: | |
199 | p_new = CubicEaseOut(p); | |
200 | break; | |
201 | case cubic_in_out: | |
202 | p_new = CubicEaseInOut(p); | |
203 | break; | |
204 | case quartic_in: | |
205 | p_new = QuarticEaseIn(p); | |
206 | break; | |
207 | case quartic_out: | |
208 | p_new = QuarticEaseOut(p); | |
209 | break; | |
210 | case quartic_in_out: | |
211 | p_new = QuarticEaseInOut(p); | |
212 | break; | |
213 | case quintic_in: | |
214 | p_new = QuinticEaseIn(p); | |
215 | break; | |
216 | case quintic_out: | |
217 | p_new = QuinticEaseOut(p); | |
218 | break; | |
219 | case quintic_in_out: | |
220 | p_new = QuinticEaseInOut(p); | |
221 | break; | |
222 | case sine_in: | |
223 | p_new = SineEaseIn(p); | |
224 | break; | |
225 | case sine_out: | |
226 | p_new = SineEaseOut(p); | |
227 | break; | |
228 | case sine_in_out: | |
229 | p_new = SineEaseInOut(p); | |
230 | break; | |
231 | case circular_in: | |
232 | p_new = CircularEaseIn(p); | |
233 | break; | |
234 | case circular_out: | |
235 | p_new = CircularEaseOut(p); | |
236 | break; | |
237 | case circular_in_out: | |
238 | p_new = CircularEaseInOut(p); | |
239 | break; | |
240 | case exponential_in: | |
241 | p_new = ExponentialEaseIn(p); | |
242 | break; | |
243 | case exponential_out: | |
244 | p_new = ExponentialEaseOut(p); | |
245 | break; | |
246 | case exponential_in_out: | |
247 | p_new = ExponentialEaseInOut(p); | |
248 | break; | |
249 | case elastic_in: | |
250 | p_new = ElasticEaseIn(p); | |
251 | break; | |
252 | case elastic_out: | |
253 | p_new = ElasticEaseOut(p); | |
254 | break; | |
255 | case elastic_in_out: | |
256 | p_new = ElasticEaseInOut(p); | |
257 | break; | |
258 | case back_in: | |
259 | p_new = BackEaseIn(p); | |
260 | break; | |
261 | case back_out: | |
262 | p_new = BackEaseOut(p); | |
263 | break; | |
264 | case back_in_out: | |
265 | p_new = BackEaseInOut(p); | |
266 | break; | |
267 | case bounce_in: | |
268 | p_new = BounceEaseIn(p); | |
269 | break; | |
270 | case bounce_out: | |
271 | p_new = BounceEaseOut(p); | |
272 | break; | |
273 | case bounce_in_out: | |
274 | p_new = BounceEaseInOut(p); | |
275 | break; | |
276 | case UNKNOWN: | |
277 | stop("Unknown easing function"); | |
278 | } | |
279 | return p_new; | |
280 | } | |
281 | ||
282 | ||
283 | //[[Rcpp::export]] | |
284 | NumericVector numeric_state_interpolator(List data, DataFrame states) { | |
285 | IntegerVector state_index = states("state"); | |
286 | NumericVector nframes_per_state = states("nframes"); | |
287 | std::vector<std::string> easer = states("ease"); | |
288 | int nelements = as<NumericVector>(data(0)).size(); | |
289 | int nstates = states.nrows(); | |
290 | int nframes = sum(nframes_per_state); | |
291 | int frame = 0; | |
292 | int state, element, currentframe, res_index; | |
293 | NumericVector res(nelements * nframes); | |
294 | ||
295 | for (state = 0; state < nstates; ++state) { | |
296 | if (easer[state] == "constant") { | |
297 | NumericVector state_from = data(state_index(state)); | |
298 | for (currentframe = 0; currentframe < nframes_per_state(state); ++currentframe) { | |
299 | res_index = (frame + currentframe) * nelements; | |
300 | for (element = 0; element < nelements; ++element) { | |
301 | res[res_index] = state_from[element]; | |
302 | ++res_index; | |
303 | } | |
304 | } | |
305 | } else { | |
306 | std::vector<double> ease_points = easeSeq(easer[state], nframes_per_state(state)); | |
307 | NumericVector state_from = data(state_index(state)); | |
308 | NumericVector state_to = data(state_index(state) + 1); | |
309 | for (element = 0; element < nelements; ++element) { | |
310 | for (currentframe = 0; currentframe < nframes_per_state(state); ++currentframe) { | |
311 | res_index = (frame + currentframe) * nelements + element; | |
312 | res[res_index] = state_from[element] + ease_points[currentframe] * (state_to[element] - state_from[element]); | |
313 | } | |
314 | } | |
315 | } | |
316 | frame += nframes_per_state(state); | |
317 | } | |
318 | ||
319 | return res; | |
320 | } | |
321 | ||
322 | //[[Rcpp::export]] | |
323 | NumericMatrix colour_state_interpolator(List data, DataFrame states) { | |
324 | IntegerVector state_index = states("state"); | |
325 | NumericVector nframes_per_state = states("nframes"); | |
326 | std::vector<std::string> easer = states("ease"); | |
327 | int nelements = as<NumericMatrix>(data(0)).nrow(); | |
328 | int nstates = states.nrows(); | |
329 | int nframes = sum(nframes_per_state); | |
330 | int frame = 0; | |
331 | int state, element, currentframe, res_index; | |
332 | NumericMatrix res(nelements * nframes, 4); | |
333 | ||
334 | for (state = 0; state < nstates; ++state) { | |
335 | if (easer[state] == "constant") { | |
336 | NumericMatrix state_from = data(state_index(state)); | |
337 | for (currentframe = 0; currentframe < nframes_per_state(state); ++currentframe) { | |
338 | res_index = (frame + currentframe) * nelements; | |
339 | for (element = 0; element < nelements; ++element) { | |
340 | res(res_index, 0) = state_from(element, 0); | |
341 | res(res_index, 1) = state_from(element, 1); | |
342 | res(res_index, 2) = state_from(element, 2); | |
343 | res(res_index, 3) = state_from(element, 3); | |
344 | ++res_index; | |
345 | } | |
346 | } | |
347 | } else { | |
348 | std::vector<double> ease_points = easeSeq(easer[state], nframes_per_state(state)); | |
349 | NumericMatrix state_from = data(state_index(state)); | |
350 | NumericMatrix state_to = data(state_index(state) + 1); | |
351 | for (element = 0; element < nelements; ++element) { | |
352 | for (currentframe = 0; currentframe < nframes_per_state(state); ++currentframe) { | |
353 | res_index = (frame + currentframe) * nelements + element; | |
354 | res(res_index, 0) = state_from(element, 0) + ease_points[currentframe] * (state_to(element, 0) - state_from(element, 0)); | |
355 | res(res_index, 1) = state_from(element, 1) + ease_points[currentframe] * (state_to(element, 1) - state_from(element, 1)); | |
356 | res(res_index, 2) = state_from(element, 2) + ease_points[currentframe] * (state_to(element, 2) - state_from(element, 2)); | |
357 | res(res_index, 3) = state_from(element, 3) + ease_points[currentframe] * (state_to(element, 3) - state_from(element, 3)); | |
358 | } | |
359 | } | |
360 | } | |
361 | frame += nframes_per_state(state); | |
362 | } | |
363 | ||
364 | return res; | |
365 | } | |
366 | ||
367 | //[[Rcpp::export]] | |
368 | CharacterVector constant_state_interpolator(List data, DataFrame states) { | |
369 | IntegerVector state_index = states("state"); | |
370 | NumericVector nframes_per_state = states("nframes"); | |
371 | std::vector<std::string> easer = states("ease"); | |
372 | int nelements = as<CharacterVector>(data(0)).size(); | |
373 | int nstates = states.nrows(); | |
374 | int nframes = sum(nframes_per_state); | |
375 | int frame = 0; | |
376 | int state, element, currentframe, res_index; | |
377 | CharacterVector res(nelements * nframes); | |
378 | ||
379 | for (state = 0; state < nstates; ++state) { | |
380 | if (easer[state] == "constant") { | |
381 | CharacterVector state_from = data(state_index(state)); | |
382 | for (currentframe = 0; currentframe < nframes_per_state(state); ++currentframe) { | |
383 | res_index = (frame + currentframe) * nelements; | |
384 | for (element = 0; element < nelements; ++element) { | |
385 | res[res_index] = state_from[element]; | |
386 | ++res_index; | |
387 | } | |
388 | } | |
389 | } else { | |
390 | std::vector<double> ease_points = easeSeq(easer[state], nframes_per_state(state)); | |
391 | CharacterVector state_from = data(state_index(state)); | |
392 | CharacterVector state_to = data(state_index(state) + 1); | |
393 | for (element = 0; element < nelements; ++element) { | |
394 | for (currentframe = 0; currentframe < nframes_per_state(state); ++currentframe) { | |
395 | res_index = (frame + currentframe) * nelements + element; | |
396 | if (ease_points[currentframe] < 0.5) { | |
397 | res[res_index] = state_from[element]; | |
398 | } else { | |
399 | res[res_index] = state_to[element]; | |
400 | } | |
401 | } | |
402 | } | |
403 | } | |
404 | frame += nframes_per_state(state); | |
405 | } | |
406 | ||
407 | return res; | |
408 | } | |
409 | //[[Rcpp::export]] | |
410 | List list_state_interpolator(List data, DataFrame states) { | |
411 | IntegerVector state_index = states("state"); | |
412 | NumericVector nframes_per_state = states("nframes"); | |
413 | std::vector<std::string> easer = states("ease"); | |
414 | int nelements = as<List>(data(0)).size(); | |
415 | int nstates = states.nrows(); | |
416 | int nframes = sum(nframes_per_state); | |
417 | int frame = 0; | |
418 | int state, element, currentframe, res_index; | |
419 | List res(nelements * nframes); | |
420 | ||
421 | for (state = 0; state < nstates; ++state) { | |
422 | if (easer[state] == "constant") { | |
423 | List state_from = data(state_index(state)); | |
424 | for (currentframe = 0; currentframe < nframes_per_state(state); ++currentframe) { | |
425 | res_index = (frame + currentframe) * nelements; | |
426 | for (element = 0; element < nelements; ++element) { | |
427 | res[res_index] = state_from[element]; | |
428 | ++res_index; | |
429 | } | |
430 | } | |
431 | } else { | |
432 | std::vector<double> ease_points = easeSeq(easer[state], nframes_per_state(state)); | |
433 | List state_from = data(state_index(state)); | |
434 | List state_to = data(state_index(state) + 1); | |
435 | for (element = 0; element < nelements; ++element) { | |
436 | for (currentframe = 0; currentframe < nframes_per_state(state); ++currentframe) { | |
437 | res_index = (frame + currentframe) * nelements + element; | |
438 | if (ease_points[currentframe] < 0.5) { | |
439 | res[res_index] = state_from[element]; | |
440 | } else { | |
441 | res[res_index] = state_to[element]; | |
442 | } | |
443 | } | |
444 | } | |
445 | } | |
446 | frame += nframes_per_state(state); | |
447 | } | |
448 | ||
449 | return res; | |
450 | } | |
451 | NumericVector align_num_elem(NumericVector from, NumericVector to) { | |
452 | NumericVector res; | |
453 | if (from.size() < to.size()) { | |
454 | if (from.size() == 0) { | |
455 | res = NumericVector(to.size(), mean(to)); | |
456 | } else { | |
457 | res = NumericVector(to.size()); | |
458 | for (int i = 0; i < res.size(); ++i) { | |
459 | res[i] = from[i % from.size()]; | |
460 | } | |
461 | } | |
462 | } else { | |
463 | res = from; | |
464 | } | |
465 | return res; | |
466 | } | |
467 | //[[Rcpp::export]] | |
468 | List numlist_state_interpolator(List data, DataFrame states) { | |
469 | IntegerVector state_index = states("state"); | |
470 | NumericVector nframes_per_state = states("nframes"); | |
471 | std::vector<std::string> easer = states("ease"); | |
472 | int nelements = as<List>(data(0)).size(); | |
473 | int nstates = states.nrows(); | |
474 | int nframes = sum(nframes_per_state); | |
475 | int frame = 0; | |
476 | int state, element, currentframe, res_index; | |
477 | List res(nelements * nframes); | |
478 | ||
479 | for (state = 0; state < nstates; ++state) { | |
480 | if (easer[state] == "constant") { | |
481 | List state_from = data(state_index(state)); | |
482 | for (currentframe = 0; currentframe < nframes_per_state(state); ++currentframe) { | |
483 | res_index = (frame + currentframe) * nelements; | |
484 | for (element = 0; element < nelements; ++element) { | |
485 | res[res_index] = state_from[element]; | |
486 | ++res_index; | |
487 | } | |
488 | } | |
489 | } else { | |
490 | std::vector<double> ease_points = easeSeq(easer[state], nframes_per_state(state)); | |
491 | List state_from = data(state_index(state)); | |
492 | List state_to = data(state_index(state) + 1); | |
493 | for (element = 0; element < nelements; ++element) { | |
494 | NumericVector state_from_vec = state_from[element]; | |
495 | NumericVector state_to_vec = state_to[element]; | |
496 | state_from_vec = align_num_elem(state_from_vec, state_to_vec); | |
497 | state_to_vec = align_num_elem(state_to_vec, state_from_vec); | |
498 | for (currentframe = 0; currentframe < nframes_per_state(state); ++currentframe) { | |
499 | res_index = (frame + currentframe) * nelements + element; | |
500 | NumericVector state_vec = state_from_vec + ease_points[currentframe] * (state_to_vec - state_from_vec); | |
501 | res[res_index] = state_vec; | |
502 | } | |
503 | } | |
504 | } | |
505 | frame += nframes_per_state(state); | |
506 | } | |
507 | ||
508 | return res; | |
509 | } | |
510 | ||
511 | //[[Rcpp::export]] | |
512 | CharacterVector phase_state_interpolator(List data, DataFrame states) { | |
513 | IntegerVector state_index = states("state"); | |
514 | NumericVector nframes_per_state = states("nframes"); | |
515 | std::vector<std::string> easer = states("ease"); | |
516 | int nelements = as<CharacterVector>(data(0)).size(); | |
517 | int nstates = states.nrows(); | |
518 | int nframes = sum(nframes_per_state); | |
519 | int frame = 0; | |
520 | int state, element, currentframe, res_index; | |
521 | CharacterVector res(nelements * nframes); | |
522 | ||
523 | for (state = 0; state < nstates; ++state) { | |
524 | if (easer[state] == "constant") { | |
525 | CharacterVector state_from = data(state_index(state)); | |
526 | for (currentframe = 0; currentframe < nframes_per_state(state); ++currentframe) { | |
527 | std::string type = currentframe == nframes_per_state(state) - 1 ? "raw" : "static"; | |
528 | res_index = (frame + currentframe) * nelements; | |
529 | for (element = 0; element < nelements; ++element) { | |
530 | res[res_index] = type; | |
531 | ++res_index; | |
532 | } | |
533 | } | |
534 | } else { | |
535 | std::vector<double> ease_points = easeSeq(easer[state], nframes_per_state(state)); | |
536 | CharacterVector state_from = data(state_index(state)); | |
537 | CharacterVector state_to = data(state_index(state) + 1); | |
538 | for (element = 0; element < nelements; ++element) { | |
539 | std::string type = state_from[element] == "enter" ? "enter" : state_to[element] == "exit" ? "exit" : "transition"; | |
540 | for (currentframe = 0; currentframe < nframes_per_state(state); ++currentframe) { | |
541 | res_index = (frame + currentframe) * nelements + element; | |
542 | res[res_index] = type; | |
543 | } | |
544 | } | |
545 | } | |
546 | frame += nframes_per_state(state); | |
547 | } | |
548 | ||
549 | return res; | |
550 | } | |
551 | ||
552 | //[[Rcpp::export]] | |
553 | DataFrame numeric_element_interpolator(NumericVector data, CharacterVector group, IntegerVector frame, CharacterVector ease) { | |
554 | std::deque<double> tweendata; | |
555 | std::deque<std::string> tweengroup; | |
556 | std::deque<int> tweenframe; | |
557 | int i, j, nframes; | |
558 | std::string groupString; | |
559 | std::string currentGroup = as<std::string>(group[0]); | |
560 | ||
561 | for (i = 1; i < data.size(); ++i) { | |
562 | groupString = as<std::string>(group[i]); | |
563 | if (currentGroup.compare(groupString) == 0) { | |
564 | nframes = frame[i] - frame[i-1]; | |
565 | std::vector<double> ease_points = easeSeq(as<std::string>(ease[i-1]), nframes); | |
566 | for (j = 0; j < ease_points.size(); ++j) { | |
567 | tweendata.push_back(data[i - 1] + ease_points[j] * (data[i] - data[i - 1])); | |
568 | tweengroup.push_back(groupString); | |
569 | tweenframe.push_back(j + frame[i-1]); | |
570 | } | |
571 | } else { | |
572 | tweendata.push_back(data[i - 1]); | |
573 | tweengroup.push_back(currentGroup); | |
574 | tweenframe.push_back(frame[i-1]); | |
575 | currentGroup = groupString; | |
576 | } | |
577 | } | |
578 | tweendata.push_back(data[i - 1]); | |
579 | tweengroup.push_back(currentGroup); | |
580 | tweenframe.push_back(frame[i-1]); | |
581 | ||
582 | return DataFrame::create( | |
583 | Named("data") = wrap(tweendata), | |
584 | Named("group") = wrap(tweengroup), | |
585 | Named("frame") = wrap(tweenframe), | |
586 | Named("stringsAsFactors") = false | |
587 | ); | |
588 | } | |
589 | ||
590 | //[[Rcpp::export]] | |
591 | DataFrame colour_element_interpolator(NumericMatrix data, CharacterVector group, IntegerVector frame, CharacterVector ease) { | |
592 | std::deque<double> tweendata1; | |
593 | std::deque<double> tweendata2; | |
594 | std::deque<double> tweendata3; | |
595 | std::deque<double> tweendata4; | |
596 | std::deque<std::string> tweengroup; | |
597 | std::deque<int> tweenframe; | |
598 | int i, j, nframes; | |
599 | std::string groupString; | |
600 | std::string currentGroup = as<std::string>(group[0]); | |
601 | ||
602 | for (i = 1; i < data.nrow(); ++i) { | |
603 | groupString = as<std::string>(group[i]); | |
604 | if (currentGroup.compare(groupString) == 0) { | |
605 | nframes = frame[i] - frame[i-1]; | |
606 | std::vector<double> ease_points = easeSeq(as<std::string>(ease[i-1]), nframes); | |
607 | for (j = 0; j < ease_points.size(); ++j) { | |
608 | tweendata1.push_back(data(i - 1, 0) + ease_points[j] * (data(i, 0) - data(i - 1, 0))); | |
609 | tweendata2.push_back(data(i - 1, 1) + ease_points[j] * (data(i, 1) - data(i - 1, 1))); | |
610 | tweendata3.push_back(data(i - 1, 2) + ease_points[j] * (data(i, 2) - data(i - 1, 2))); | |
611 | tweendata4.push_back(data(i - 1, 3) + ease_points[j] * (data(i, 3) - data(i - 1, 3))); | |
612 | tweengroup.push_back(groupString); | |
613 | tweenframe.push_back(j + frame[i-1]); | |
614 | } | |
615 | } else { | |
616 | tweendata1.push_back(data(i - 1, 0)); | |
617 | tweendata2.push_back(data(i - 1, 1)); | |
618 | tweendata3.push_back(data(i - 1, 2)); | |
619 | tweendata4.push_back(data(i - 1, 3)); | |
620 | tweengroup.push_back(currentGroup); | |
621 | tweenframe.push_back(frame[i-1]); | |
622 | currentGroup = groupString; | |
623 | } | |
624 | } | |
625 | tweendata1.push_back(data(i - 1, 0)); | |
626 | tweendata2.push_back(data(i - 1, 1)); | |
627 | tweendata3.push_back(data(i - 1, 2)); | |
628 | tweendata4.push_back(data(i - 1, 3)); | |
629 | tweengroup.push_back(currentGroup); | |
630 | tweenframe.push_back(frame[i-1]); | |
631 | ||
632 | return DataFrame::create( | |
633 | Named("data1") = wrap(tweendata1), | |
634 | Named("data2") = wrap(tweendata2), | |
635 | Named("data3") = wrap(tweendata3), | |
636 | Named("data4") = wrap(tweendata4), | |
637 | Named("group") = wrap(tweengroup), | |
638 | Named("frame") = wrap(tweenframe), | |
639 | Named("stringsAsFactors") = false | |
640 | ); | |
641 | } | |
642 | ||
643 | //[[Rcpp::export]] | |
644 | DataFrame constant_element_interpolator(CharacterVector data, CharacterVector group, IntegerVector frame, CharacterVector ease) { | |
645 | std::deque<std::string> tweendata; | |
646 | std::deque<std::string> tweengroup; | |
647 | std::deque<int> tweenframe; | |
648 | int i, j, nframes; | |
649 | std::string groupString; | |
650 | std::string currentGroup = as<std::string>(group[0]); | |
651 | ||
652 | for (i = 1; i < data.size(); ++i) { | |
653 | groupString = as<std::string>(group[i]); | |
654 | if (currentGroup.compare(groupString) == 0) { | |
655 | nframes = frame[i] - frame[i-1]; | |
656 | std::vector<double> ease_points = easeSeq(as<std::string>(ease[i-1]), nframes); | |
657 | for (j = 0; j < ease_points.size(); ++j) { | |
658 | if (ease_points[j] < 0.5) { | |
659 | tweendata.push_back(as<std::string>(data[i - 1])); | |
660 | } else { | |
661 | tweendata.push_back(as<std::string>(data[i])); | |
662 | } | |
663 | tweengroup.push_back(groupString); | |
664 | tweenframe.push_back(j + frame[i-1]); | |
665 | } | |
666 | } else { | |
667 | tweendata.push_back(as<std::string>(data[i - 1])); | |
668 | tweengroup.push_back(currentGroup); | |
669 | tweenframe.push_back(frame[i-1]); | |
670 | currentGroup = groupString; | |
671 | } | |
672 | ||
673 | } | |
674 | tweendata.push_back(as<std::string>(data[i - 1])); | |
675 | tweengroup.push_back(currentGroup); | |
676 | tweenframe.push_back(frame[i-1]); | |
677 | ||
678 | return DataFrame::create( | |
679 | Named("data") = wrap(tweendata), | |
680 | Named("group") = wrap(tweengroup), | |
681 | Named("frame") = wrap(tweenframe), | |
682 | Named("stringsAsFactors") = false | |
683 | ); | |
684 | } | |
685 | ||
686 | //[[Rcpp::export]] | |
687 | List list_element_interpolator(List data, CharacterVector group, IntegerVector frame, CharacterVector ease) { | |
688 | std::deque<SEXP> tweendata; | |
689 | std::deque<std::string> tweengroup; | |
690 | std::deque<int> tweenframe; | |
691 | int i, j, nframes; | |
692 | std::string groupString; | |
693 | std::string currentGroup = as<std::string>(group[0]); | |
694 | ||
695 | for (i = 1; i < data.size(); ++i) { | |
696 | groupString = as<std::string>(group[i]); | |
697 | if (currentGroup.compare(groupString) == 0) { | |
698 | nframes = frame[i] - frame[i-1]; | |
699 | std::vector<double> ease_points = easeSeq(as<std::string>(ease[i-1]), nframes); | |
700 | for (j = 0; j < ease_points.size(); ++j) { | |
701 | if (ease_points[j] < 0.5) { | |
702 | tweendata.push_back(data[i - 1]); | |
703 | } else { | |
704 | tweendata.push_back(data[i]); | |
705 | } | |
706 | tweengroup.push_back(groupString); | |
707 | tweenframe.push_back(j + frame[i-1]); | |
708 | } | |
709 | } else { | |
710 | tweendata.push_back(data[i - 1]); | |
711 | tweengroup.push_back(currentGroup); | |
712 | tweenframe.push_back(frame[i-1]); | |
713 | currentGroup = groupString; | |
714 | } | |
715 | ||
716 | } | |
717 | tweendata.push_back(data[i - 1]); | |
718 | tweengroup.push_back(currentGroup); | |
719 | tweenframe.push_back(frame[i-1]); | |
720 | List tweendata_list = wrap(tweendata); | |
721 | IntegerVector frame_vec = wrap(tweenframe); | |
722 | CharacterVector group_vec = wrap(tweengroup); | |
723 | List res = List::create( | |
724 | Named("data") = tweendata_list, | |
725 | Named("group") = group_vec, | |
726 | Named("frame") = frame_vec | |
727 | ); | |
728 | res.attr("class") = "data.frame"; | |
729 | res.attr("row.names") = seq_along(frame_vec); | |
730 | return res; | |
731 | } | |
732 | ||
733 | //[[Rcpp::export]] | |
734 | List numlist_element_interpolator(List data, CharacterVector group, IntegerVector frame, CharacterVector ease) { | |
735 | std::deque<NumericVector> tweendata; | |
736 | std::deque<std::string> tweengroup; | |
737 | std::deque<int> tweenframe; | |
738 | int i, j, nframes; | |
739 | std::string groupString; | |
740 | std::string currentGroup = as<std::string>(group[0]); | |
741 | ||
742 | for (i = 1; i < data.size(); ++i) { | |
743 | groupString = as<std::string>(group[i]); | |
744 | if (currentGroup.compare(groupString) == 0) { | |
745 | nframes = frame[i] - frame[i-1]; | |
746 | std::vector<double> ease_points = easeSeq(as<std::string>(ease[i-1]), nframes); | |
747 | NumericVector state_from_vec = data[i - 1]; | |
748 | NumericVector state_to_vec = data[i]; | |
749 | state_from_vec = align_num_elem(state_from_vec, state_to_vec); | |
750 | state_to_vec = align_num_elem(state_to_vec, state_from_vec); | |
751 | for (j = 0; j < ease_points.size(); ++j) { | |
752 | NumericVector state_vec = state_from_vec + ease_points[j] * (state_to_vec - state_from_vec); | |
753 | tweendata.push_back(state_vec); | |
754 | tweengroup.push_back(groupString); | |
755 | tweenframe.push_back(j + frame[i-1]); | |
756 | } | |
757 | } else { | |
758 | tweendata.push_back(data[i - 1]); | |
759 | tweengroup.push_back(currentGroup); | |
760 | tweenframe.push_back(frame[i-1]); | |
761 | currentGroup = groupString; | |
762 | } | |
763 | ||
764 | } | |
765 | tweendata.push_back(data[i - 1]); | |
766 | tweengroup.push_back(currentGroup); | |
767 | tweenframe.push_back(frame[i-1]); | |
768 | List tweendata_list = wrap(tweendata); | |
769 | IntegerVector frame_vec = wrap(tweenframe); | |
770 | CharacterVector group_vec = wrap(tweengroup); | |
771 | List res = List::create( | |
772 | Named("data") = tweendata_list, | |
773 | Named("group") = group_vec, | |
774 | Named("frame") = frame_vec | |
775 | ); | |
776 | res.attr("class") = "data.frame"; | |
777 | res.attr("row.names") = seq_along(frame_vec); | |
778 | return res; | |
779 | } | |
780 | ||
781 | //[[Rcpp::export]] | |
782 | DataFrame phase_element_interpolator(CharacterVector data, CharacterVector group, IntegerVector frame, CharacterVector ease) { | |
783 | std::deque<std::string> tweendata; | |
784 | std::deque<std::string> tweengroup; | |
785 | std::deque<int> tweenframe; | |
786 | int i, j, nframes; | |
787 | std::string groupString; | |
788 | std::string currentGroup = as<std::string>(group[0]); | |
789 | ||
790 | for (i = 1; i < data.size(); ++i) { | |
791 | groupString = as<std::string>(group[i]); | |
792 | if (currentGroup.compare(groupString) == 0) { | |
793 | nframes = frame[i] - frame[i-1]; | |
794 | std::string type = data[i - 1] == "enter" ? "enter" : data[i] == "exit" ? "exit" : data[i - 1] == "static" ? "static" : "transition"; | |
795 | for (j = 0; j < nframes; ++j) { | |
796 | if (j == 0 && (type == "transition" || type == "exit")) { | |
797 | tweendata.push_back("raw"); | |
798 | } else { | |
799 | tweendata.push_back(type); | |
800 | } | |
801 | tweengroup.push_back(groupString); | |
802 | tweenframe.push_back(j + frame[i-1]); | |
803 | } | |
804 | } else { | |
805 | tweendata.push_back(as<std::string>(data[i - 1])); | |
806 | tweengroup.push_back(currentGroup); | |
807 | tweenframe.push_back(frame[i-1]); | |
808 | currentGroup = groupString; | |
809 | } | |
810 | ||
811 | } | |
812 | tweendata.push_back(as<std::string>(data[i - 1])); | |
813 | tweengroup.push_back(currentGroup); | |
814 | tweenframe.push_back(frame[i-1]); | |
815 | ||
816 | return DataFrame::create( | |
817 | Named("data") = wrap(tweendata), | |
818 | Named("group") = wrap(tweengroup), | |
819 | Named("frame") = wrap(tweenframe), | |
820 | Named("stringsAsFactors") = false | |
821 | ); | |
822 | } | |
823 | ||
824 | //[[Rcpp::export]] | |
825 | DataFrame numeric_along_interpolator(NumericVector data, CharacterVector group, NumericVector time, bool history, bool keep_last, int nframes, CharacterVector ease) { | |
826 | std::deque<double> tweendata; | |
827 | std::deque<std::string> tweengroup; | |
828 | std::deque<int> tweenframe; | |
829 | std::string easer = as<std::string>(ease); | |
830 | ||
831 | int i,j,jj; | |
832 | bool before,after,same,last; | |
833 | double pos,interp; | |
834 | ||
835 | for (i = 1; i <= nframes; ++i) { | |
836 | for (j = 0; j < data.size(); ++j) { | |
837 | last = j == data.size() - 1; | |
838 | jj = last ? j : j + 1; | |
839 | before = time[j] <= i; | |
840 | after = time[jj] > i; | |
841 | same = group[j] == group[jj]; | |
842 | if ((history && same && before) || ((!same || last) && keep_last && before)) { | |
843 | tweendata.push_back(data[j]); | |
844 | tweengroup.push_back(as<std::string>(group[j])); | |
845 | tweenframe.push_back(i); | |
846 | } | |
847 | if (same && before == after) { | |
848 | pos = (i - time[j]) / (time[jj] - time[j]); | |
849 | pos = easePos(pos, easer); | |
850 | interp = data[j] + (data[jj] - data[j]) * pos; | |
851 | tweendata.push_back(interp); | |
852 | tweengroup.push_back(as<std::string>(group[j])); | |
853 | tweenframe.push_back(i); | |
854 | } | |
855 | } | |
856 | } | |
857 | ||
858 | return DataFrame::create( | |
859 | Named("data") = wrap(tweendata), | |
860 | Named("group") = wrap(tweengroup), | |
861 | Named("frame") = wrap(tweenframe), | |
862 | Named("stringsAsFactors") = false | |
863 | ); | |
864 | } | |
865 | //[[Rcpp::export]] | |
866 | DataFrame colour_along_interpolator(NumericMatrix data, CharacterVector group, NumericVector time, bool history, bool keep_last, int nframes, CharacterVector ease) { | |
867 | std::deque<double> tweendata1; | |
868 | std::deque<double> tweendata2; | |
869 | std::deque<double> tweendata3; | |
870 | std::deque<double> tweendata4; | |
871 | ||
872 | std::deque<std::string> tweengroup; | |
873 | std::deque<int> tweenframe; | |
874 | std::string easer = as<std::string>(ease); | |
875 | ||
876 | int i,j,jj; | |
877 | bool before,after,same,last; | |
878 | double pos; | |
879 | ||
880 | for (i = 1; i <= nframes; ++i) { | |
881 | for (j = 0; j < data.nrow(); ++j) { | |
882 | last = j == data.nrow() - 1; | |
883 | jj = last ? j : j + 1; | |
884 | before = time[j] <= i; | |
885 | after = time[jj] > i; | |
886 | same = group[j] == group[jj]; | |
887 | if ((history && same && before) || ((!same || last) && keep_last && before)) { | |
888 | tweendata1.push_back(data(j, 0)); | |
889 | tweendata2.push_back(data(j, 1)); | |
890 | tweendata3.push_back(data(j, 2)); | |
891 | tweendata4.push_back(data(j, 3)); | |
892 | tweengroup.push_back(as<std::string>(group[j])); | |
893 | tweenframe.push_back(i); | |
894 | } | |
895 | if (same && before == after) { | |
896 | pos = (i - time[j]) / (time[j + 1] - time[j]); | |
897 | pos = easePos(pos, easer); | |
898 | tweendata1.push_back(data(j, 0) + (data(j + 1, 0) - data(j, 0)) * pos); | |
899 | tweendata2.push_back(data(j, 1) + (data(j + 1, 1) - data(j, 1)) * pos); | |
900 | tweendata3.push_back(data(j, 2) + (data(j + 1, 2) - data(j, 2)) * pos); | |
901 | tweendata4.push_back(data(j, 3) + (data(j + 1, 3) - data(j, 3)) * pos); | |
902 | tweengroup.push_back(as<std::string>(group[j])); | |
903 | tweenframe.push_back(i); | |
904 | } | |
905 | } | |
906 | } | |
907 | ||
908 | return DataFrame::create( | |
909 | Named("data1") = wrap(tweendata1), | |
910 | Named("data2") = wrap(tweendata2), | |
911 | Named("data3") = wrap(tweendata3), | |
912 | Named("data4") = wrap(tweendata4), | |
913 | Named("group") = wrap(tweengroup), | |
914 | Named("frame") = wrap(tweenframe), | |
915 | Named("stringsAsFactors") = false | |
916 | ); | |
917 | } | |
918 | ||
919 | //[[Rcpp::export]] | |
920 | DataFrame constant_along_interpolator(CharacterVector data, CharacterVector group, NumericVector time, bool history, bool keep_last, int nframes, CharacterVector ease) { | |
921 | std::deque<std::string> tweendata; | |
922 | std::deque<std::string> tweengroup; | |
923 | std::deque<int> tweenframe; | |
924 | std::string easer = as<std::string>(ease); | |
925 | ||
926 | int i,j,jj; | |
927 | bool before,after,same,last; | |
928 | double pos; | |
929 | ||
930 | for (i = 1; i <= nframes; ++i) { | |
931 | for (j = 0; j < data.size(); ++j) { | |
932 | last = j == data.size() - 1; | |
933 | jj = last ? j : j + 1; | |
934 | before = time[j] <= i; | |
935 | after = time[jj] > i; | |
936 | same = group[j] == group[jj]; | |
937 | if ((history && same && before) || ((!same || last) && keep_last && before)) { | |
938 | tweendata.push_back(as<std::string>(data[j])); | |
939 | tweengroup.push_back(as<std::string>(group[j])); | |
940 | tweenframe.push_back(i); | |
941 | } | |
942 | if (same && before == after) { | |
943 | pos = (i - time[j]) / (time[j + 1] - time[j]); | |
944 | pos = easePos(pos, easer); | |
945 | if (pos < 0.5) { | |
946 | tweendata.push_back(as<std::string>(data[j])); | |
947 | } else { | |
948 | tweendata.push_back(as<std::string>(data[j + 1])); | |
949 | } | |
950 | tweengroup.push_back(as<std::string>(group[j])); | |
951 | tweenframe.push_back(i); | |
952 | } | |
953 | } | |
954 | } | |
955 | ||
956 | return DataFrame::create( | |
957 | Named("data") = wrap(tweendata), | |
958 | Named("group") = wrap(tweengroup), | |
959 | Named("frame") = wrap(tweenframe), | |
960 | Named("stringsAsFactors") = false | |
961 | ); | |
962 | } | |
963 | ||
964 | //[[Rcpp::export]] | |
965 | List list_along_interpolator(List data, CharacterVector group, NumericVector time, bool history, bool keep_last, int nframes, CharacterVector ease) { | |
966 | std::deque<SEXP> tweendata; | |
967 | std::deque<std::string> tweengroup; | |
968 | std::deque<int> tweenframe; | |
969 | std::string easer = as<std::string>(ease); | |
970 | ||
971 | int i,j,jj; | |
972 | bool before,after,same,last; | |
973 | double pos; | |
974 | ||
975 | for (i = 1; i <= nframes; ++i) { | |
976 | for (j = 0; j < data.size(); ++j) { | |
977 | last = j == data.size() - 1; | |
978 | jj = last ? j : j + 1; | |
979 | before = time[j] <= i; | |
980 | after = time[jj] > i; | |
981 | same = group[j] == group[jj]; | |
982 | if ((history && same && before) || ((!same || last) && keep_last && before)) { | |
983 | tweendata.push_back(data[j]); | |
984 | tweengroup.push_back(as<std::string>(group[j])); | |
985 | tweenframe.push_back(i); | |
986 | } | |
987 | if (same && before == after) { | |
988 | pos = (i - time[j]) / (time[j + 1] - time[j]); | |
989 | pos = easePos(pos, easer); | |
990 | if (pos < 0.5) { | |
991 | tweendata.push_back(data[j]); | |
992 | } else { | |
993 | tweendata.push_back(data[j + 1]); | |
994 | } | |
995 | tweengroup.push_back(as<std::string>(group[j])); | |
996 | tweenframe.push_back(i); | |
997 | } | |
998 | } | |
999 | } | |
1000 | ||
1001 | List tweendata_list = wrap(tweendata); | |
1002 | IntegerVector frame_vec = wrap(tweenframe); | |
1003 | CharacterVector group_vec = wrap(tweengroup); | |
1004 | List res = List::create( | |
1005 | Named("data") = tweendata_list, | |
1006 | Named("group") = group_vec, | |
1007 | Named("frame") = frame_vec | |
1008 | ); | |
1009 | res.attr("class") = "data.frame"; | |
1010 | res.attr("row.names") = seq_along(frame_vec); | |
1011 | return res; | |
1012 | } | |
1013 | //[[Rcpp::export]] | |
1014 | List numlist_along_interpolator(List data, CharacterVector group, NumericVector time, bool history, bool keep_last, int nframes, CharacterVector ease) { | |
1015 | std::deque<NumericVector> tweendata; | |
1016 | std::deque<std::string> tweengroup; | |
1017 | std::deque<int> tweenframe; | |
1018 | std::string easer = as<std::string>(ease); | |
1019 | ||
1020 | int i,j,jj; | |
1021 | bool before,after,same,last; | |
1022 | double pos; | |
1023 | ||
1024 | for (i = 1; i <= nframes; ++i) { | |
1025 | for (j = 0; j < data.size(); ++j) { | |
1026 | last = j == data.size() - 1; | |
1027 | jj = last ? j : j + 1; | |
1028 | before = time[j] <= i; | |
1029 | after = time[jj] > i; | |
1030 | same = group[j] == group[jj]; | |
1031 | if ((history && same && before) || ((!same || last) && keep_last && before)) { | |
1032 | tweendata.push_back(data[j]); | |
1033 | tweengroup.push_back(as<std::string>(group[j])); | |
1034 | tweenframe.push_back(i); | |
1035 | } | |
1036 | if (same && before == after) { | |
1037 | NumericVector state_from_vec = data[j]; | |
1038 | NumericVector state_to_vec = data[j + 1]; | |
1039 | state_from_vec = align_num_elem(state_from_vec, state_to_vec); | |
1040 | state_to_vec = align_num_elem(state_to_vec, state_from_vec); | |
1041 | pos = (i - time[j]) / (time[j + 1] - time[j]); | |
1042 | pos = easePos(pos, easer); | |
1043 | NumericVector state_vec = state_from_vec + pos * (state_to_vec - state_from_vec); | |
1044 | tweendata.push_back(state_vec); | |
1045 | tweengroup.push_back(as<std::string>(group[j])); | |
1046 | tweenframe.push_back(i); | |
1047 | } | |
1048 | } | |
1049 | } | |
1050 | ||
1051 | List tweendata_list = wrap(tweendata); | |
1052 | IntegerVector frame_vec = wrap(tweenframe); | |
1053 | CharacterVector group_vec = wrap(tweengroup); | |
1054 | List res = List::create( | |
1055 | Named("data") = tweendata_list, | |
1056 | Named("group") = group_vec, | |
1057 | Named("frame") = frame_vec | |
1058 | ); | |
1059 | res.attr("class") = "data.frame"; | |
1060 | res.attr("row.names") = seq_along(frame_vec); | |
1061 | return res; | |
1062 | } | |
1063 | //[[Rcpp::export]] | |
1064 | DataFrame phase_along_interpolator(CharacterVector group, NumericVector time, bool history, bool keep_last, int nframes) { | |
1065 | std::deque<std::string> tweendata; | |
1066 | std::deque<std::string> tweengroup; | |
1067 | std::deque<int> tweenframe; | |
1068 | ||
1069 | int i,j,jj; | |
1070 | bool before,after,same,last; | |
1071 | ||
1072 | for (i = 1; i <= nframes; ++i) { | |
1073 | for (j = 0; j < group.size(); ++j) { | |
1074 | last = j == group.size() - 1; | |
1075 | jj = last ? j : j + 1; | |
1076 | before = time[j] <= i; | |
1077 | after = time[jj] > i; | |
1078 | same = group[j] == group[jj]; | |
1079 | if ((history && same && before) || ((!same || last) && keep_last && before)) { | |
1080 | tweendata.push_back("raw"); | |
1081 | tweengroup.push_back(as<std::string>(group[j])); | |
1082 | tweenframe.push_back(i); | |
1083 | } | |
1084 | if (same && before == after) { | |
1085 | tweendata.push_back("transition"); | |
1086 | tweengroup.push_back(as<std::string>(group[j])); | |
1087 | tweenframe.push_back(i); | |
1088 | } | |
1089 | } | |
1090 | } | |
1091 | ||
1092 | return DataFrame::create( | |
1093 | Named("data") = wrap(tweendata), | |
1094 | Named("group") = wrap(tweengroup), | |
1095 | Named("frame") = wrap(tweenframe), | |
1096 | Named("stringsAsFactors") = false | |
1097 | ); | |
1098 | } | |
1099 | ||
1100 | //[[Rcpp::export]] | |
1101 | NumericVector numeric_at_interpolator(NumericVector from, NumericVector to, NumericVector at, CharacterVector ease) { | |
1102 | int n = from.size(), i; | |
1103 | double pos; | |
1104 | std::string easer = as<std::string>(ease); | |
1105 | NumericVector res(n); | |
1106 | ||
1107 | for (i = 0; i < n; ++i) { | |
1108 | pos = easePos(at[i], easer); | |
1109 | res[i] = from[i] + (to[i] - from[i]) * pos; | |
1110 | } | |
1111 | ||
1112 | return res; | |
1113 | } | |
1114 | //[[Rcpp::export]] | |
1115 | NumericMatrix colour_at_interpolator(NumericMatrix from, NumericMatrix to, NumericVector at, CharacterVector ease) { | |
1116 | int n = from.nrow(), i; | |
1117 | double pos; | |
1118 | std::string easer = as<std::string>(ease); | |
1119 | NumericMatrix res(n, from.ncol()); | |
1120 | ||
1121 | for (i = 0; i < n; ++i) { | |
1122 | pos = easePos(at[i], easer); | |
1123 | res(i, _) = from(i, _) + (to(i, _) - from(i, _)) * pos; | |
1124 | } | |
1125 | ||
1126 | return res; | |
1127 | } | |
1128 | //[[Rcpp::export]] | |
1129 | CharacterVector constant_at_interpolator(CharacterVector from, CharacterVector to, NumericVector at, CharacterVector ease) { | |
1130 | int n = from.size(), i; | |
1131 | double pos; | |
1132 | std::string easer = as<std::string>(ease); | |
1133 | CharacterVector res(n); | |
1134 | ||
1135 | for (i = 0; i < n; ++i) { | |
1136 | pos = easePos(at[i], easer); | |
1137 | res[i] = pos < 0.5 ? from[i] : to[i]; | |
1138 | } | |
1139 | ||
1140 | return res; | |
1141 | } | |
1142 | //[[Rcpp::export]] | |
1143 | List list_at_interpolator(List from, List to, NumericVector at, CharacterVector ease) { | |
1144 | int n = from.size(), i; | |
1145 | double pos; | |
1146 | std::string easer = as<std::string>(ease); | |
1147 | List res(n); | |
1148 | ||
1149 | for (i = 0; i < n; ++i) { | |
1150 | pos = easePos(at[i], easer); | |
1151 | res[i] = pos < 0.5 ? from[i] : to[i]; | |
1152 | } | |
1153 | ||
1154 | return res; | |
1155 | } | |
1156 | //[[Rcpp::export]] | |
1157 | List numlist_at_interpolator(List from, List to, NumericVector at, CharacterVector ease) { | |
1158 | int n = from.size(), i; | |
1159 | double pos; | |
1160 | std::string easer = as<std::string>(ease); | |
1161 | List res(n); | |
1162 | ||
1163 | for (i = 0; i < n; ++i) { | |
1164 | NumericVector state_from_vec = from[i]; | |
1165 | NumericVector state_to_vec = to[i]; | |
1166 | state_from_vec = align_num_elem(state_from_vec, state_to_vec); | |
1167 | state_to_vec = align_num_elem(state_to_vec, state_from_vec); | |
1168 | pos = easePos(at[i], easer); | |
1169 | NumericVector state_vec = state_from_vec + pos * (state_to_vec - state_from_vec); | |
1170 | res[i] = state_vec; | |
1171 | } | |
1172 | ||
1173 | return res; | |
1174 | } | |
1175 | //[[Rcpp::export]] | |
1176 | NumericVector numeric_fill_interpolator(NumericVector data, CharacterVector ease) { | |
1177 | NumericVector res(data.size(), NA_REAL); | |
1178 | int i,j,last = -1; | |
1179 | std::string easer = as<std::string>(ease); | |
1180 | std::vector<double> easepos; | |
1181 | ||
1182 | for (i = 0; i < data.size(); ++i) { | |
1183 | if (NumericVector::is_na(data[i])) continue; | |
1184 | if (last != -1) { | |
1185 | easepos = easeSeq(easer, i - last); | |
1186 | for (j = 1; j < easepos.size(); ++j) { | |
1187 | res[last + j] = data[last] + easepos[j] * (data[i] - data[last]); | |
1188 | } | |
1189 | } | |
1190 | res[i] = data[i]; | |
1191 | last = i; | |
1192 | } | |
1193 | ||
1194 | return res; | |
1195 | } | |
1196 | //[[Rcpp::export]] | |
1197 | NumericMatrix colour_fill_interpolator(NumericMatrix data, CharacterVector ease) { | |
1198 | NumericMatrix res(data.nrow(), data.ncol()); | |
1199 | std::fill(res.begin(), res.end(), NA_REAL); | |
1200 | int i,j,last = -1; | |
1201 | std::string easer = as<std::string>(ease); | |
1202 | std::vector<double> easepos; | |
1203 | ||
1204 | for (i = 0; i < data.nrow(); ++i) { | |
1205 | if (NumericVector::is_na(data(i, 0))) continue; | |
1206 | if (last != -1) { | |
1207 | easepos = easeSeq(easer, i - last); | |
1208 | for (j = 1; j < easepos.size(); ++j) { | |
1209 | res(last + j, _) = data(last, _) + easepos[j] * (data(i, _) - data(last, _)); | |
1210 | } | |
1211 | } | |
1212 | res(i, _) = data(i, _); | |
1213 | last = i; | |
1214 | } | |
1215 | ||
1216 | return res; | |
1217 | } | |
1218 | //[[Rcpp::export]] | |
1219 | CharacterVector constant_fill_interpolator(CharacterVector data, CharacterVector ease) { | |
1220 | CharacterVector res(data.size(), NA_STRING); | |
1221 | int i,j,last = -1; | |
1222 | std::string easer = as<std::string>(ease); | |
1223 | std::vector<double> easepos; | |
1224 | ||
1225 | for (i = 0; i < data.size(); ++i) { | |
1226 | if (CharacterVector::is_na(data[i])) continue; | |
1227 | if (last != -1) { | |
1228 | easepos = easeSeq(easer, i - last); | |
1229 | for (j = 1; j < easepos.size(); ++j) { | |
1230 | res[last + j] = easepos[j] < 0.5 ? data[last] : data[i]; | |
1231 | } | |
1232 | } | |
1233 | res[i] = data[i]; | |
1234 | last = i; | |
1235 | } | |
1236 | ||
1237 | return res; | |
1238 | } | |
1239 | //[[Rcpp::export]] | |
1240 | List list_fill_interpolator(List data, CharacterVector ease) { | |
1241 | List res(data.size()); | |
1242 | int i,j,last = -1; | |
1243 | std::string easer = as<std::string>(ease); | |
1244 | std::vector<double> easepos; | |
1245 | ||
1246 | for (i = 0; i < data.size(); ++i) { | |
1247 | if (data[i]==R_NilValue) continue; | |
1248 | if (last != -1) { | |
1249 | easepos = easeSeq(easer, i - last); | |
1250 | for (j = 1; j < easepos.size(); ++j) { | |
1251 | res[last + j] = easepos[j] < 0.5 ? data[last] : data[i]; | |
1252 | } | |
1253 | } | |
1254 | res[i] = data[i]; | |
1255 | last = i; | |
1256 | } | |
1257 | return res; | |
1258 | } | |
1259 | //[[Rcpp::export]] | |
1260 | List numlist_fill_interpolator(List data, CharacterVector ease) { | |
1261 | List res(data.size()); | |
1262 | int i,j,last = -1; | |
1263 | std::string easer = as<std::string>(ease); | |
1264 | std::vector<double> easepos; | |
1265 | ||
1266 | for (i = 0; i < data.size(); ++i) { | |
1267 | if (data[i]==R_NilValue) continue; | |
1268 | if (last != -1) { | |
1269 | easepos = easeSeq(easer, i - last); | |
1270 | NumericVector state_from_vec = data[last]; | |
1271 | NumericVector state_to_vec = data[i]; | |
1272 | state_from_vec = align_num_elem(state_from_vec, state_to_vec); | |
1273 | state_to_vec = align_num_elem(state_to_vec, state_from_vec); | |
1274 | res[last] = data[last]; | |
1275 | for (j = 1; j < easepos.size(); ++j) { | |
1276 | NumericVector state_vec = state_from_vec + easepos[j] * (state_to_vec - state_from_vec); | |
1277 | res[last + j] = state_vec; | |
1278 | } | |
1279 | } | |
1280 | res[i] = data[i]; | |
1281 | last = i; | |
1282 | } | |
1283 | return res; | |
1284 | } |
0 | context("along") | |
1 | ||
2 | df <- data.frame( | |
3 | x = c(1, 5, 7, 10), | |
4 | y = c(4, 3, 7, -1), | |
5 | col = c('black', 'red', 'green', 'blue'), | |
6 | type = letters[1:4], | |
7 | stringsAsFactors = FALSE | |
8 | ) | |
9 | ||
10 | test_that("tween_along works", { | |
11 | tween <- tween_along(df, ease = 'linear', nframes = 10, along = x) | |
12 | expect_equal(nrow(tween), 30) | |
13 | expect_equal(tween$col[22], '#77B784FF') | |
14 | expect_equal(tween$y[8], 3.25) | |
15 | ||
16 | tween <- tween_along(df, ease = 'linear', nframes = 10, along = x, history = FALSE) | |
17 | expect_equal(nrow(tween), 9) | |
18 | expect_equal(tween$col[8], '#77B784FF') | |
19 | expect_equal(tween$y[2], 3.75) | |
20 | }) | |
21 | ||
22 | test_that("tween_along throws errors", { | |
23 | expect_error(tween_along(df, ease = 'linear', nframes = 10, along = 1)) | |
24 | expect_error(tween_along(df, ease = 'linear', nframes = 10, along = x, id = 1)) | |
25 | expect_error(tween_along(df, ease = 'linear', nframes = 10, along = x, range = c(0, 0))) | |
26 | expect_error(tween_along(df[1,], ease = 'linear', nframes = 10, along = x)) | |
27 | }) |
0 | context("at") | |
1 | ||
2 | df1 <- data.frame(x = 1:2, y = 4:5, col = 'black', type = letters[1:2], stringsAsFactors = FALSE) | |
3 | df2 <- data.frame(x = 11:12, y = 14:15, col = 'white', type = letters[1], stringsAsFactors = FALSE) | |
4 | ||
5 | test_that("tween_at works", { | |
6 | tween <- tween_at(df1, df2, 0.5, 'linear') | |
7 | expect_equal(nrow(tween), 2) | |
8 | expect_named(tween, names(df1)) | |
9 | expect_equal(tween$x, c(6, 7)) | |
10 | expect_equal(tween$col[1], '#767676FF') | |
11 | }) | |
12 | ||
13 | test_that("tween_at handles weird input", { | |
14 | tween <- tween_at(df1, df2[1,], 0.5, 'linear') | |
15 | expect_equal(nrow(tween), 2) | |
16 | tween <- tween_at(df1[1,], df2, 0.5, 'linear') | |
17 | expect_equal(nrow(tween), 2) | |
18 | tween <- tween_at(df1, df2[integer(),], 0.5, 'linear') | |
19 | expect_equal(nrow(tween), 0) | |
20 | tween <- tween_at(df1[integer(),], df2, 0.5, 'linear') | |
21 | expect_equal(nrow(tween), 0) | |
22 | expect_error(tween_at(df1[c(1,2,1), ], df2, 0.5, 'linear')) | |
23 | expect_error(tween_at(df1, df2, numeric(), 'linear')) | |
24 | expect_error(tween_at(df1, df2, 0.5, character())) | |
25 | }) | |
26 | ||
27 | test_that('tween_at works with vectors', { | |
28 | tween <- tween_at(df1$x, df2$x, 0.5, 'linear') | |
29 | expect_is(tween, 'numeric') | |
30 | expect_equal(tween, c(6,7)) | |
31 | expect_error(tween_at(df1$x, df2$col)) | |
32 | }) |
0 | context("components") | |
1 | ||
2 | df <- data.frame( | |
3 | x = c(1, 5, 7, 10), | |
4 | y = c(4, 3, 7, -1), | |
5 | col = c('black', 'red', 'green', 'blue'), | |
6 | type = letters[1:4], | |
7 | stringsAsFactors = FALSE | |
8 | ) | |
9 | ||
10 | test_that("tween_components works", { | |
11 | tween <- tween_components(df, 'linear', nframes = 10, time = c(1, 7, 13, 20)) | |
12 | expect_equal(nrow(tween), 10) | |
13 | expect_equal(tween$x[6], 19/3) | |
14 | expect_equal(tween$col[2], '#52170AFF') | |
15 | expect_equal(max(tween$.frame), 10) | |
16 | expect_true(all(tween$.phase[c(1,4,7,10)] == 'raw')) | |
17 | expect_true(all(tween$.phase[-c(1,4,7,10)] == 'transition')) | |
18 | ||
19 | tween <- tween_components(df, 'linear', nframes = 10, time = c(1, 7, 13, 20), rep(c(1,2), 2)) | |
20 | expect_equal(nrow(tween), 14) | |
21 | expect_equal(tween$x[12], 25/3) | |
22 | expect_equal(tween$col[2], '#152910FF') | |
23 | expect_equal(max(tween$.frame), 10) | |
24 | expect_true(all(tween$.phase[c(1,5,10,14)] == 'raw')) | |
25 | expect_true(all(tween$.phase[-c(1,5,10,14)] == 'transition')) | |
26 | }) | |
27 | ||
28 | test_that("enter/exit works", { | |
29 | tween <- tween_components(df, 'linear', nframes = 20, time = c(1, 7, 13, 20), enter = function(df) { | |
30 | df$x <- 0 | |
31 | df$col <- 'red' | |
32 | df | |
33 | }, enter_length = 3) | |
34 | expect_equal(nrow(tween), 20) | |
35 | expect_equal(tween$x[3], 2/3, tolerance = 1e-7) | |
36 | expect_equal(tween$col[2], '#A41A0AFF') | |
37 | expect_equal(max(tween$.frame), 20) | |
38 | expect_true(all(tween$.phase[1:3] == 'enter')) | |
39 | }) | |
40 | ||
41 | test_that("weird input gets caught", { | |
42 | tween <- tween_components(df, 'linear', nframes = 0, time = c(1, 7, 13, 20)) | |
43 | expect_equal(nrow(tween), 0) | |
44 | tween <- tween_components(df[integer(), ], 'linear', nframes = 10, time = numeric()) | |
45 | expect_equal(nrow(tween), 0) | |
46 | expect_error(tween_components(df, 'linear', nframes = 10, time = 1)) | |
47 | expect_error(tween_components(df, 'linear', nframes = 0, time = c(1, 7, 13, 20), id = 1)) | |
48 | }) |
0 | context("events") | |
1 | ||
2 | df <- data.frame( | |
3 | x = c(1, 5, 7, 10), | |
4 | y = c(4, 3, 7, -1), | |
5 | col = c('black', 'red', 'green', 'blue'), | |
6 | type = letters[1:4], | |
7 | stringsAsFactors = FALSE | |
8 | ) | |
9 | ||
10 | test_that("tween_events works", { | |
11 | tween <- tween_events(df, 'linear', 20, x, x + 2) | |
12 | expect_equal(nrow(tween), 17) | |
13 | expect_equal(max(tween$.frame), 20) | |
14 | expect_true(all(tween$.phase[c(4, 8, 13, 17)] == 'raw')) | |
15 | expect_true(all(tween$.phase[-c(4, 8, 13, 17)] == 'static')) | |
16 | ||
17 | ||
18 | tween <- tween_events(df, 'linear', 20, x, enter = function(df) { | |
19 | df$x <- 0 | |
20 | df$col <- 'red' | |
21 | df | |
22 | }, enter_length = 3) | |
23 | ||
24 | ||
25 | expect_equal(nrow(tween), 23) | |
26 | expect_equal(max(tween$.frame), 20) | |
27 | expect_true(all(tween$.phase[c(6, 13, 17, 23)] == 'raw')) | |
28 | expect_true(all(tween$.phase[-c(6, 13, 17, 23)] == 'enter')) | |
29 | expect_equal(tween$x[2], 0.2) | |
30 | expect_equal(tween$col[3], '#931B0BFF') | |
31 | }) | |
32 | ||
33 | test_that("weird input gets handled", { | |
34 | expect_error(tween_events(df, 'linear', 20)) | |
35 | tween <- tween_events(df, 'linear', 0, x) | |
36 | expect_equal(nrow(tween), 0) | |
37 | tween <- tween_events(df[integer(), ], 'linear', 10, x) | |
38 | expect_equal(nrow(tween), 0) | |
39 | }) |
0 | context("fill") | |
1 | ||
2 | df <- data.frame( | |
3 | x = c(1, NA, NA, NA, 6, 4, NA, NA, NA, NA, 20), | |
4 | type = c('a', NA, NA, NA, 'b', 'c', NA, NA, NA, NA, 'd'), | |
5 | col = c('red', NA, NA, NA, 'blue', 'green', NA, NA, NA, NA, 'black'), | |
6 | stringsAsFactors = FALSE | |
7 | ) | |
8 | test_that("tween_fill works", { | |
9 | tween <- tween_fill(df, 'linear') | |
10 | expect_equal(dim(df), dim(tween)) | |
11 | expect_equal(tween$x[3], 3.5) | |
12 | expect_equal(tween$col[10], '#110010FF') | |
13 | ||
14 | expect_equal(tween_fill(df$col, 'linear'), tween$col) | |
15 | ||
16 | tween <- tween_fill(df[-c(1, 11), 1], 'linear') | |
17 | expect_equal(tween, df$x[-c(1, 11)]) | |
18 | }) |
0 | context("state") | |
1 | ||
2 | df1 <- data.frame(x = 1:2, y = 4:5, col = 'black', type = letters[1:2], stringsAsFactors = FALSE) | |
3 | df2 <- data.frame(x = 11:12, y = 14:15, col = 'white', type = letters[1], stringsAsFactors = FALSE) | |
4 | ||
5 | test_that("tween_state works", { | |
6 | tween <- tween_state(df1, df2, ease = 'linear', nframes = 5) | |
7 | expect_equal(max(tween$.frame), 5) | |
8 | expect_true(all(tween$.phase[c(1:2, 9:10)] == 'raw')) | |
9 | expect_true(all(tween$.phase[c(3:8)] == 'transition')) | |
10 | expect_true(all(tween$.id == rep(1:2, 5))) | |
11 | expect_equal(tween$col[5], '#767676FF') | |
12 | expect_equal(tween$x[7], 8.5) | |
13 | expect_equal(tween$type[4:5], c('b', 'a')) | |
14 | }) | |
15 | ||
16 | test_that("keep_state works", { | |
17 | keep <- keep_state(df1, 5) | |
18 | expect_equal(max(keep$.frame), 5) | |
19 | expect_true(all(keep$.phase[c(9:10)] == 'raw')) | |
20 | expect_true(all(keep$.phase[c(1:8)] == 'static')) | |
21 | }) | |
22 | ||
23 | test_that("enter/exit works", { | |
24 | tween <- tween_state(df1, df2[1,, drop = FALSE], 'linear', 5, exit = function(df) { | |
25 | df$x <- 0 | |
26 | df$col <- 'red' | |
27 | df | |
28 | }) | |
29 | expect_equal(nrow(tween), 9) | |
30 | expect_true(all(tween$.phase[c(4,6,8)] == 'exit')) | |
31 | expect_equal(tween$col[8], '#BA1808FF') | |
32 | expect_equal(tween$x[8], 0.5) | |
33 | }) |