-
Notifications
You must be signed in to change notification settings - Fork 124
/
Copy pathanimation.Rmd
143 lines (115 loc) · 7.33 KB
/
animation.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
# Animating views
## Animation API
Both `plot_ly()` and `ggplotly()` support [key frame](https://en.wikipedia.org/wiki/Key_frame) animations through the `frame` argument/aesthetic. They also support an `ids` argument/aesthetic to ensure smooth transitions between objects with the same id (which helps facilitate [object constancy](https://bost.ocks.org/mike/constancy/)). Figure \@ref(fig:animation-ggplotly) recreates the famous gapminder animation of the evolution in the relationship between GDP per capita and life expectancy evolved over time [@gapminder]. The data is recorded on a yearly basis, so the year is assigned to `frame`, and each point in the scatterplot represents a country, so the country is assigned to `ids`, ensuring a smooth transition from year to year for a given country.
```r
data(gapminder, package = "gapminder")
gg <- ggplot(gapminder, aes(gdpPercap, lifeExp, color = continent)) +
geom_point(aes(size = pop, frame = year, ids = country)) +
scale_x_log10()
ggplotly(gg)
```
```{r animation-ggplotly, echo = FALSE, fig.cap = "(ref:animation-ggplotly)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/animation-ggplotly.html"'}
knitr::include_graphics("images/animation-ggplotly.png")
```
As long as a `frame` variable is provided, an animation is produced with play/pause button(s) and a slider component for controlling the animation. These components can be removed or customized via the `animation_button()` and `animation_slider()` functions. Moreover, various animation options, like the amount of time between frames, the smooth transition duration, and the type of transition easing may be altered via the `animation_opts()` function. Figure \@ref(fig:animation-opts) shows the same data as Figure \@ref(fig:animation-ggplotly), but doubles the amount of time between frames, uses linear transition easing, places the animation buttons closer to the slider, and modifies the default `currentvalue.prefix` settings for the slider.
\index{animation\_opts()@\texttt{animation\_opts()}}
\index{animation\_button()@\texttt{animation\_button()}}
\index{animation\_slider()@\texttt{animation\_slider()}}
\index{Specifying fonts!Animation slider}
```r
base <- gapminder %>%
plot_ly(x = ~gdpPercap, y = ~lifeExp, size = ~pop,
text = ~country, hoverinfo = "text") %>%
layout(xaxis = list(type = "log"))
base %>%
add_markers(color = ~continent, frame = ~year, ids = ~country) %>%
animation_opts(1000, easing = "elastic", redraw = FALSE) %>%
animation_button(
x = 1, xanchor = "right", y = 0, yanchor = "bottom"
) %>%
animation_slider(
currentvalue = list(prefix = "YEAR ", font = list(color="red"))
)
```
```{r animation-opts, echo = FALSE, fig.cap = "(ref:animation-opts)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/animation-opts.html"'}
knitr::include_graphics("images/animation-opts.png")
```
If `frame` is a numeric variable (or a character string), frames are always ordered in increasing (alphabetical) order; but for factors, the ordering reflects the ordering of the levels. Consequently, factors provide the most control over the ordering of frames. In Figure \@ref(fig:animation-factors), the continents (i.e., frames) are ordered according to their average life expectancy across countries within the continent. Furthermore, since there is no meaningful relationship between objects in different frames of Figure \@ref(fig:animation-factors), the smooth transition duration is set to 0. This helps avoid any confusion that there is a meaningful connection between the smooth transitions. Note that these options control both animations triggered by the play button or via the slider.
```r
meanLife <- with(gapminder, tapply(lifeExp, INDEX = continent, mean))
gapminder$continent <- factor(
gapminder$continent, levels = names(sort(meanLife))
)
base %>%
add_markers(data = gapminder, frame = ~continent) %>%
hide_legend() %>%
animation_opts(frame = 1000, transition = 0, redraw = FALSE)
```
```{r animation-factors, echo = FALSE, fig.cap = "(ref:animation-factors)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/animation-factors.html"'}
knitr::include_graphics("images/animation-factors.png")
```
Both the `frame` and `ids` attributes operate on the trace level, meaning that we can target specific layers of the graph to be animated. One obvious use case for this is to provide a background which displays every possible frame (which is not animated) and overlay the animated frames onto that background. Figure \@ref(fig:animation-targets) shows the same information as Figure \@ref(fig:animation-opts), but layers animated frames on top of a background of all the frames. As a result, it is easier to put a specific year into a global context.
```r
base %>%
add_markers(
color = ~continent, showlegend = F,
alpha = 0.2, alpha_stroke = 0.2
) %>%
add_markers(color = ~continent, frame = ~year, ids = ~country) %>%
animation_opts(1000, redraw = FALSE)
```
```{r animation-targets, echo = FALSE, fig.cap = "(ref:animation-targets)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/animation-targets.html"'}
knitr::include_graphics("images/animation-targets.png")
```
## Animation support {#animation-support}
At the time of writing, the scatter plotly.js trace type is really the only trace type with full support for animation. That means, we need to get a little imaginative to animate certain things, like a population pyramid chart (essentially a bar chart) using `add_segments()` (a scatter-based layer) instead of `add_bars()` (a non-scatter layer). Figure \@ref(fig:profile-pyramid) shows projections for male and female population by age from 2018 to 2050 using data obtained via the **idbr** package [@idbr].
\index{Chart types!Population pyramid}
\index{add\_trace()@\texttt{add\_trace()}!add\_segments()@\texttt{add\_segments()}}
```r
library(idbr)
library(dplyr)
us <- bind_rows(
idb1(
country = "US",
year = 2018:2050,
variables = c("AGE", "NAME", "POP"),
sex = "male"
),
idb1(
country = "US",
year = 2018:2050,
variables = c("AGE", "NAME", "POP"),
sex = "female"
)
)
us <- us %>%
mutate(
POP = if_else(SEX == 1, POP, -POP),
SEX = if_else(SEX == 1, "Male", "Female")
)
plot_ly(us, size = I(5), alpha = 0.5) %>%
add_segments(
x = ~POP, xend = 0,
y = ~AGE, yend = ~AGE,
frame = ~time,
color = ~factor(SEX)
)
```
```{r profile-pyramid, echo = FALSE, fig.cap = "(ref:profile-pyramid)"}
include_vimeo("317101075")
```
\index{add\_trace()@\texttt{add\_trace()}!Line simplification}
Although population pyramids are quite popular, they aren't necessarily the best way to visualize this information, especially if the goal is to compare the population profiles over time. It's much easier to compare them along a common scale, as done in Figure \@ref(fig:profile-lines). Note that, when animating lines in this fashion, it can help to set [`line.simplify`](https://plot.ly/r/reference/#scatter-line-simplify) to `FALSE` so that the number of points along the path is left unaffected.
```r
plot_ly(us, alpha = 0.5) %>%
add_lines(
x = ~AGE, y = ~abs(POP),
frame = ~time,
color = ~factor(SEX),
line = list(simplify = FALSE)
) %>%
layout(yaxis = list(title = "US population"))
```
```{r profile-lines, echo = FALSE, fig.cap = "(ref:profile-lines)"}
include_vimeo("317101952")
```