This repository has been archived by the owner on May 10, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 23
/
shadow_wake.Rmd
245 lines (171 loc) · 10.6 KB
/
shadow_wake.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
---
title: "shadow_wake"
author: "Danielle Navarro"
date: "22/11/2018"
output: github_document
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
library(tidyverse)
library(e1071)
library(gganimate)
```
One of the nice features of gganimate is the ability to create *shadows*, in which previous states of the animation can remain visible at later states in the animation. There are four shadow functions, `shadow_wake()`, `shadow_trail()`, `shadow_mark()` and `shadow_null()`. In this walkthrough I'll discuss the `shadow_wake()` function.
## Creating the animation
To illustrate the flexibility of the function, I'll start by creating a two dimensional Brownian bridge simulation using the `rbridge()` function from the `e1071` package:
```{r, createdata, cache=TRUE}
ntimes <- 20 # how many time points to run the bridge?
nseries <- 10 # how many time series to generate?
# function to generate the brownian bridges
make_bridges <- function(ntimes, nseries) {
replicate(nseries, c(0,rbridge(frequency = ntimes-1))) %>% as.vector()
}
# construct tibble
tbl <- tibble(
Time = rep(1:ntimes, nseries),
Horizontal = make_bridges(ntimes, nseries),
Vertical = make_bridges(ntimes, nseries),
Series = gl(nseries, ntimes)
)
glimpse(tbl)
```
We have a data frame with 10 separate time `Series`, each of which extends for 20 `Time` points, and plots the `Horizontal` and `Vertical` location of a particle that is moving along a Brownian bridge path. To see what the data looks like, here's a plot showing each time point as a separate facet:
```{r, basepic, cache=TRUE, fig.width=15, fig.height=15}
base_pic <- tbl %>%
ggplot(aes(
x = Horizontal,
y = Vertical,
colour = Series)) +
geom_point(
show.legend = FALSE,
size = 5) +
coord_equal() +
xlim(-2, 2) +
ylim(-2, 2)
base_pic + facet_wrap(~Time)
```
We can now create a basic animation using `transition_time()`, in which we can see each of the points moving smoothly along the path.
```{r, baseanim, cache=TRUE}
base_anim <- base_pic + transition_time(time = Time)
base_anim %>% animate()
```
## Basic use of shadow wake
To see what `shadow_wake()` does, we'll add it to the animation. The one required argument to the function is `wake_length`, which governs how "long" the wake is. The `wake_length` is a value from 0 to 1, where 1 means "the full length of the animation":
```{r, wake1, cache=TRUE}
wake1 <- base_anim + shadow_wake(wake_length = .1)
wake1 %>% animate()
```
Yay! We have shadows following along in the "wake" of each of our particles.
## Tinkering with detail and graphics devices
There's a bit of subtlety to this that is worth noting. By default, the animation leaves a shadow from each previous frame. Because this is a 100 frame animation (the gganimate default) and we asked for a `wake_length` of .1, it's leaving 10 dots behind each particle, and they fall off in size and transparency. That's a sensible default, but in many situations the interpolating frames in the animation aren't actually terribly meaningful in and of themselves, and you might want to have a "continuous" wake. To do this, the easiest solution is to increase the `detail` argument in the call to `animate()`. What this does is increase the number of interpolated frames between successive states of the animation. So if I set `detail = 5` the animation won't actually include any extra frames in the output, but the shadow wake will be computed as if there had been 5 additional frames between each "actual" frame:
```{r, wake1_detail, cache=TRUE}
wake1 %>% animate(detail = 5)
```
This is getting closer to something worthwhile, but it still looks a bit janky. When I rendered this on Adam Gruer's Mac it worked beautifully, but I'm rendering this on my Windows machine and it looks like garbage for some reason. Something odd is going on here. To fix this we need to tinker with the rendering. Under the hood, each frame is being rendered with the `png()` graphics device and by default on my machine it using the Windows GDI as the graphics device. Let's use Cairo instead:
```{r, wake1_cairo, cache=TRUE}
wake1 %>% animate(detail = 5, type = "cairo")
```
Much nicer!
## Changing the aesthetics of the wake
The `shadow_wake()` function allows you to control the appearance of the shadow in several ways. It's quite flexible, so you can change the length, size, transparency, colour and fill.
### Changing the length
To extend the shadow wake, alter the value of `wake_length`. In the previous version I set `wake_length = .1`, so the wake extends for 10% of the total length of the animation. To increase it to 20% I set `wake_length = .2`:
```{r, wake2, cache=TRUE}
wake2 <- base_anim + shadow_wake(wake_length = .2)
wake2 %>% animate(detail = 5, type = "cairo")
```
### (Not) changing the size
The default behaviour `shadow_wake()` is to leave a wake that decreases in size and becomes more transparent. We can suppress this behaviour if we want to. For example, to hold the size of the wake constant, set `size = NULL`, producing a shadow wake that becomes more transparent but does not shrink:
```{r, wake3, cache=TRUE}
wake3 <- base_anim + shadow_wake(wake_length = .1, size = NULL)
wake3 %>% animate(detail = 5, type = "cairo")
```
### (Not) changing the transparency
To stop `shadow_wake()` from modifying the transparency, we can set `alpha = NULL`. In this example, I hold the transparency and the size constant:
```{r, wake4, cache=TRUE}
wake4 <- base_anim + shadow_wake(wake_length = .1, size = NULL, alpha = NULL)
wake4 %>% animate(detail = 5, type = "cairo")
```
In this form, the shadow wake now looks like a long opaque "snake". The length of the wake is a lot clearer in this version.
## Fading the colour (and fill)
`shadow_wake()` also allows control of the `colour` and `fill` aesthetics of the wake, using the `colour` and `fill` arguments (no surprise there!) The behaviour of these two arguments is the same, and since our original plot only specifies the colour, I'll just use that. Let's take the last animation, but have the colour of the wake fade to black. This is done by setting `colour = 'black'`:
```{r, wake5, cache=TRUE}
wake5 <- base_anim +
shadow_wake(wake_length = .1,
size = NULL,
alpha = NULL,
colour = "black"
)
wake5 %>% animate(detail = 5, type = "cairo")
```
Very pretty!
## Easings for shadow wake
In the previous example, the wake changes only in colour, fading from the original colour to black. It's noticeable, however, that the colour isn't fading *linearly*. Instead it turns black very quickly. In the same way that the interpolation between successives states in the animation is governed by an easing function [LINK TO SARAH'S TUTORIAL] the `falloff` of the shadow wake is controlled by an easing function. By default, `shadow_wake()` assumes you want `falloff = 'cubic-in'` but you can modify this to used any of the `tweenr` easing functions. To illustrate this, let's have our shadow wake fade linearly to black, by setting `falloff = 'linear'`:
```{r, wake6, cache=TRUE}
wake6 <- base_anim +
shadow_wake(wake_length = .1,
size = NULL,
alpha = NULL,
colour = "black",
falloff = "linear"
)
wake6 %>% animate(detail = 5, type = "cairo")
```
## Try out different combinations!
Because `gganimate` aims to function as a true grammar, allowing extremely flexible combinations of constituent parts, you can produce some surprising (and often quite silly) variations just by playing around with things. For instance, the "bounce out" easing function is something that makes a lot of sense when you want to simulate the behaviour of a ball dropping and bouncing from a hard surface, but there's nothing stopping you from "bouncing" a colour aesthetic on the shadow wake. I'm not sure it's at all useful, but this is what happens when we take the previous animation and set `falloff = "bounce-out"` with a longer wake:
```{r, wake7, cache=TRUE}
wake7 <- base_anim +
shadow_wake(wake_length = .2,
size = NULL,
alpha = NULL,
colour = "black",
falloff = "bounce-out"
)
wake7 %>% animate(detail = 5, type = "cairo")
```
It's a little creepy looking, but neat. By playing with the combinations we can produce quite a few other variations. In the version below, I've done a few things. Firstly I've set it so that the fade goes to `colour = "white"`, and the size of the wake *increases* to `size = 15`, and (for no particular reason) used a quintic falloff:
```{r, wake8, cache=TRUE}
wake8 <- base_anim +
shadow_wake(wake_length = .3,
size = 15,
colour = "white",
falloff = "quintic-in"
)
wake8 %>% animate(detail = 5, type = "cairo")
```
Prettiness!
## To wrap or not to wrap the shadows
The other arguments to the function allow flexiblity in other ways. In this simulation it makes sense to "wrap" the shadow wake (i.e., allow shadows from the end of the animation to appear at the beginning) because the time series' are all designed to be cyclic: they end at the same state that they started. Sometimes that's undesirable (wrapping a shadow from 1977 onto a data point from 2018 is a bit weird since time is thankfully not a loop), so you can turn this off by setting `wrap = FALSE`.
This can also produce interesting effects!
```{r, wake9, cache=TRUE}
wake9 <- base_anim +
shadow_wake(wake_length = .3,
size = 15,
colour = "white",
falloff = "quintic-in",
wrap = FALSE
)
wake9 %>% animate(detail = 5, type = "cairo")
```
## Controlling which layers leave shadows
When the base plot has multiple layers, you can control which layers get shadow wake and which don't. Let's create a plot with multiple layers:
```{r, wake10, cache=TRUE}
newanim <- base_pic +
geom_point(colour = "black", size = 1, show.legend = FALSE) +
transition_time(time = Time) +
shadow_wake(wake_length = .2)
newanim %>% animate(detail = 5, type = "cairo")
```
This is really cool, but perhaps that's not what I want. I've constructed a plot with two layers, and maybe I only want to add shadow wake to the first one.
```{r}
newanim$layers
```
So let's suppose I want to exclude the second layer (the black dots I added over the top of the coloured ones). I can do this by setting `exclude_layer = 2`:
```{r, wake11, cache=TRUE}
newanim2 <- base_pic +
geom_point(colour = "black", size = 1, show.legend = FALSE) +
transition_time(time = Time) +
shadow_wake(wake_length = .2, exclude_layer = 2)
newanim2 %>% animate(detail = 5, type = "cairo")
```
Yay!