-
Notifications
You must be signed in to change notification settings - Fork 124
/
Copy patharranging.Rmd
360 lines (273 loc) · 24.5 KB
/
arranging.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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# (PART) Combining multiple views {-}
# Arranging views
One technique essential to high-dimensional data visualization is the ability to arrange multiple views. By arranging multiple low-dimensional graphics of the same (or similar) high-dimensional data, one can put
local summaries and patterns into a global context. When arranging multiple **plotly** objects, you have some flexibility in terms of how you arrange them: you could use `subplot()` to merge multiple **plotly** objects into a single object (useful for synchronizing zoom/pan events across multiple axes), place them in separate HTML tags (Section \@ref(arranging-htmlwidgets)), or embedded in a larger system for intelligently managing many views (Section \@ref(navigating-many-views)).
Ideally, when displaying multiple related data views, they are linked through an underlying data source to foster comparisons and enable posing of data queries [@Cook:2007uk]. Chapter \@ref(graphical-queries) shows how to build upon these methods for arranging views to link them (client-side) as well.
## Arranging plotly objects
\index{subplot()@\texttt{subplot()}}
The `subplot()` function provides a flexible interface for merging multiple **plotly** objects into a single object. It is more flexible than most trellis display frameworks (e.g., **ggplot2**'s `facet_wrap()`) as you don't have to condition on a value of common variable in each display [@trellis]. Its capabilities and interface are similar to the `grid.arrange()` function from the **gridExtra** package, which allows you to arrange multiple **grid** grobs in a single view, effectively providing a way to arrange (possibly unrelated) **ggplot2** and/or **lattice** plots in a single view [@RCore; @gridExtra; @lattice]. Figure \@ref(fig:subplot-simple) shows the most simple way to use `subplot()` which is to directly supply plotly objects.
```r
library(plotly)
p1 <- plot_ly(economics, x = ~date, y = ~unemploy) %>%
add_lines(name = "unemploy")
p2 <- plot_ly(economics, x = ~date, y = ~uempmed) %>%
add_lines(name = "uempmed")
subplot(p1, p2)
```
```{r subplot-simple, echo=FALSE, fig.cap="(ref:subplot-simple)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/subplot-simple.html"'}
knitr::include_graphics("images/subplot-simple.svg")
```
\index{subplot()@\texttt{subplot()}!Shared axes}
Although `subplot()` accepts an arbitrary number of plot objects, passing a _list_ of plots can save typing and redundant code when dealing with a large number of plots. Figure \@ref(fig:economics) shows one time series for each variable in the `economics` dataset and shares the x-axis so that zoom/pan events are synchronized across each series:
```r
vars <- setdiff(names(economics), "date")
plots <- lapply(vars, function(var) {
plot_ly(economics, x = ~date, y = as.formula(paste0("~", var))) %>%
add_lines(name = var)
})
subplot(plots, nrows = length(plots), shareX = TRUE, titleX = FALSE)
```
```{r economics, echo=FALSE, fig.cap = "(ref:economics)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/economics.html"'}
knitr::include_graphics("images/economics.svg")
```
Conceptually, `subplot()` provides a way to place a collection of plots into a table with a given number of rows and columns. The number of rows (and, by consequence, the number of columns) is specified via the `nrows` argument. By default, each row/column shares an equal proportion of the overall height/width, but as shown in Figure \@ref(fig:proportions), the default can be changed via the `heights` and `widths` arguments.
```{r proportions, echo = FALSE, fig.cap = "(ref:proportions)", out.width="50%"}
knitr::include_graphics("images/proportions.svg")
```
This flexibility is quite useful for a number of visualizations, for example, as shown in Figure \@ref(fig:joint), a joint density plot is really a subplot of joint and marginal densities. The **heatmaply** package is a great example of leveraging `subplot()` in a similar way to create interactive dendrograms [@heatmaply].
\indexc{plotly\_empty()}
\index{add\_trace()@\texttt{add\_trace()}!add\_histogram2dcontour()@\texttt{add\_histogram2dcontour()}}
```{r, eval = FALSE, summary = "Click to show code"}
# draw random values from correlated bi-variate normal distribution
s <- matrix(c(1, 0.3, 0.3, 1), nrow = 2)
m <- mvtnorm::rmvnorm(1e5, sigma = s)
x <- m[, 1]
y <- m[, 2]
s <- subplot(
plot_ly(x = x, color = I("black")),
plotly_empty(),
plot_ly(x = x, y = y, color = I("black")) %>%
add_histogram2dcontour(colorscale = "Viridis"),
plot_ly(y = y, color = I("black")),
nrows = 2, heights = c(0.2, 0.8), widths = c(0.8, 0.2), margin = 0,
shareX = TRUE, shareY = TRUE, titleX = FALSE, titleY = FALSE
)
layout(s, showlegend = FALSE)
```
```{r joint, echo = FALSE, fig.cap = "(ref:joint)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/joint.html"'}
knitr::include_graphics("images/joint.svg")
```
### Recursive subplots
\index{subplot()@\texttt{subplot()}!Recursive grid layouts}
The `subplot()` function returns a plotly object so it can be modified like any other plotly object. This effectively means that subplots work recursively (i.e., you can have subplots within subplots). This idea is useful when your desired layout doesn't conform to the table structure described in the previous section. In fact, you can think of a subplot of subplots like a spreadsheet with merged cells. Figure \@ref(fig:recursive) gives a basic example where each row of the outermost subplot contains a different number of columns.
```{r, eval = FALSE, summary = "Click to show code"}
plotList <- function(nplots) {
lapply(seq_len(nplots), function(x) plot_ly())
}
s1 <- subplot(plotList(6), nrows = 2, shareX = TRUE, shareY = TRUE)
s2 <- subplot(plotList(2), shareY = TRUE)
subplot(
s1, s2, plot_ly(), nrows = 3,
margin = 0.04, heights = c(0.6, 0.3, 0.1)
)
```
```{r recursive, echo = FALSE, fig.cap = "(ref:recursive)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/recursive.html"'}
knitr::include_graphics("images/recursive.svg")
```
The concept is particularly useful when you want plot(s) in a given row to have different widths from plot(s) in another row. Figure \@ref(fig:map-subplot) uses this recursive behavior to place many bar charts in the first row, and a single choropleth in the second row.
\index{layout()@\texttt{layout()}!hovermode@\texttt{hovermode}}
```{r, eval = FALSE, summary = "Click to show code"}
# specify some map projection/options
g <- list(
scope = 'usa',
projection = list(type = 'albers usa'),
lakecolor = toRGB('white')
)
# create a map of population density
density <- state.x77[, "Population"] / state.x77[, "Area"]
map <- plot_geo(
z = ~density, text = state.name,
locations = state.abb, locationmode = 'USA-states'
) %>%
layout(geo = g)
# create a bunch of horizontal bar charts
vars <- colnames(state.x77)
barcharts <- lapply(vars, function(var) {
plot_ly(x = state.x77[, var], y = state.name) %>%
add_bars(orientation = "h", name = var) %>%
layout(showlegend = FALSE, hovermode = "y",
yaxis = list(showticklabels = FALSE))
})
subplot(barcharts, margin = 0.01) %>%
subplot(map, nrows = 2, heights = c(0.3, 0.7), margin = 0.1) %>%
layout(legend = list(y = 1)) %>%
colorbar(y = 0.5)
```
```{r map-subplot, echo = FALSE, fig.cap = "(ref:map-subplot)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/map-subplot.html"'}
knitr::include_graphics("images/map-subplot.svg")
```
### Other approaches and applications
Using `subplot()` directly is not the _only_ way to create multiple views of a dataset with **plotly**. In some special cases, like scatterplot matrices and generalized pair plots, we can take advantage of some special methods designed specifically for these use cases.
#### Scatterplot matrices
\index{Chart types!Scatterplot matrix}
The plotly.js library provides a trace specifically designed and optimized for scatterplot matrices (splom). To use it, provide numeric variables to the `dimensions` attribute of the `splom` trace type.
```r
dims <- dplyr::select_if(iris, is.numeric)
dims <- purrr::map2(dims, names(dims), ~list(values=.x, label=.y))
plot_ly(
type = "splom", dimensions = setNames(dims, NULL),
showupperhalf = FALSE, diagonal = list(visible = FALSE)
)
```
```{r splom, echo = FALSE, fig.cap = "(ref:splom)"}
include_vimeo("325081084")
```
See <https://plot.ly/r/splom/> for more options related to the splom trace type.
#### Generalized pairs plot
\index{Chart types!Generalized pairs plot}
\index{ggplotly()@\texttt{ggplotly()}!GGally!ggpairs()@\texttt{ggpairs()}}
The generalized pairs plot is an extension of the scatterplot matrix to support both discrete and numeric variables [@gpp]. The `ggpairs()` function from the **GGally** package provides an interface for creating these plots via **ggplot2** [@GGally]. To implement `ggpairs()`, **GGally** introduces the notion of a matrix of **ggplot2** plot objects that it calls `ggmatrix()`. As Figure \@ref(fig:ggpairs) shows, the `ggplotly()` function has a method for converting ggmatrix objects directly:
```r
pm <- GGally::ggpairs(iris, aes(color = Species))
class(pm)
#> [1] "gg" "ggmatrix"
ggplotly(pm)
```
```{r ggpairs, echo = FALSE, fig.cap = "(ref:ggpairs)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/ggpairs.html"'}
knitr::include_graphics("images/ggpairs.svg")
```
As it turns out, **GGally** use `ggmatrix()` as a building block for other visualizations, like model diagnostic plots (`ggnostic()`). Sections \@ref(ggally-ggpairs) and \@ref(ggally-ggnostic) demonstrate how to leverage linked brushing in the `ggplotly()` versions of these plots.
#### Trellis displays with `subplot()` {#trellis-displays-subplot}
\index{subplot()@\texttt{subplot()}!Trellis display}
It's true that **ggplot2**'s `facet_wrap()`/`facet_grid()` provides a simple way to create trellis displays, but for learning purposes, it can be helpful to learn how to implement a similar trellis display with `plot_ly()` and `subplot()`. Figure \@ref(fig:subplot-trellis) demonstrates one approach, which leverages `subplot()`'s ability to reposition annotations and shapes. Specifically, the `panel()` function below, which defines the visualization method to be applied to each `variable` in the `economics_long` dataset, uses paper coordinates (i.e., graph coordinates on a normalized 0-1 scale) to place an annotation at the top-center of each panel as well as a rectangle shape behind the annotation. Note also the use of `ysizemode = 'pixel'` which gives the rectangle shape a fixed height (i.e., the rectangle height is always 16 pixels, regardless of the height of the trellis display).
\index{Specifying fonts!Annotations}
\index{layout()@\texttt{layout()}!shapes@\texttt{shapes}!Rectangles}
```{r, eval = FALSE, summary = "Click to show code"}
library(dplyr)
panel <- . %>%
plot_ly(x = ~date, y = ~value) %>%
add_lines() %>%
add_annotations(
text = ~unique(variable),
x = 0.5,
y = 1,
yref = "paper",
xref = "paper",
yanchor = "bottom",
showarrow = FALSE,
font = list(size = 15)
) %>%
layout(
showlegend = FALSE,
shapes = list(
type = "rect",
x0 = 0,
x1 = 1,
xref = "paper",
y0 = 0,
y1 = 16,
yanchor = 1,
yref = "paper",
ysizemode = "pixel",
fillcolor = toRGB("gray80"),
line = list(color = "transparent")
)
)
economics_long %>%
group_by(variable) %>%
do(p = panel(.)) %>%
subplot(nrows = NROW(.), shareX = TRUE)
```
```{r subplot-trellis, echo=FALSE, fig.cap = "(ref:subplot-trellis)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/subplot-trellis.html"'}
knitr::include_graphics("images/subplot-trellis.svg")
```
#### ggplot2 subplots
It's possible to combine the convenience of **ggplot2**'s `facet_wrap()`/`facet_grid()` with the more flexible arrangement capabilities of `subplot()`. Figure \@ref(fig:ggplot2-subplots) does this to show two different views of the `economics_long` data: the left-hand column displays each variable along time, while the right-hand column shows violin plots of each variable. For the implementation, each column is created through `ggplot2::facet_wrap()`, but then the trellis displays are combined with `subplot()`. In this case, **ggplot2** objects are passed directly to `subplot()`, but you can also use `ggplotly()` for finer control over the conversion of **ggplot2** to **plotly** (see also Chapter \@ref(improving-ggplotly)) before supplying that result to `subplot()`.
\index{ggplotly()@\texttt{ggplotly()}!ggplot2!geom\_violin()@\texttt{geom\_violin()}}
```r
gg1 <- ggplot(economics_long, aes(date, value)) + geom_line() +
facet_wrap(~variable, scales = "free_y", ncol = 1)
gg2 <- ggplot(economics_long, aes(factor(1), value)) +
geom_violin() +
facet_wrap(~variable, scales = "free_y", ncol = 1) +
theme(axis.text = element_blank(), axis.ticks = element_blank())
subplot(gg1, gg2)
```
```{r ggplot2-subplots, echo = FALSE, fig.cap = "(ref:ggplot2-subplots)"}
knitr::include_graphics("images/ggplot2-subplots.svg")
```
## Arranging htmlwidgets
Since **plotly** objects are also **htmlwidgets**, any method that works for arranging **htmlwidgets** also works for **plotly** objects. Moreover, since **htmlwidgets** are also **htmltools** tags, any method that works for arranging **htmltools** tags also works for **htmlwidgets**. Here are three common ways to arrange components (e.g., **htmlwidgets**, **htmltools** tags, etc.) in a single webpage:
1. __flexdashboard__: An R package for arranging components into an opinionated dashboard layout. This package is essentially a special **rmarkdown** template that uses a simple markup syntax to define the layout.
2. Bootstrap's grid layout: Both the **crosstalk** and **shiny** packages provide ways to arrange numerous components via Bootstrap's (a popular HTML/CSS framework) [grid layout system](https://getbootstrap.com/docs/4.1/layout/grid/).
3. CSS flexbox: If you know some HTML and CSS, you can leverage [CSS flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) to arrange components via the **htmltools** package.
Although **flexdashboard** is a really excellent way to arrange web-based content generated from R, it can payoff to know the other two approaches as their arrangement techniques are agnostic to an **rmarkdown** output format. In other words, approaches 2-3 can be used with any **rmarkdown** template^[Although HTML cannot possibly render in a PDF or Word document, **knitr** can automatically detect a non-HTML output format and embed a static image of the htmlwidget via the **webshot** package [@webshot].] or really _any_ framework for website generation. Although Bootstrap grid layout system (2) is expressive and intuitive, using it in a larger website that also uses a different HTML/CSS framework (e.g., Bulma, Skeleton, etc.) can cause issues. In that case, CSS flexbox (3) is a lightweight (i.e., no external CSS/JS dependencies) alternative that is less likely to introduce undesirable side effects.
### flexdashboard
Figure \@ref(fig:flexdashboard-ggplotly) provides an example of embedding `ggplotly()` inside **flexdashboard** [@flexdashboard]. Since **flexdashboard** is an **rmarkdown** template, it automatically comes with many things that make **rmarkdown** great: ability to produce standalone HTML, integration with other languages, and thoughtful integration with RStudio products like Connect. There are many other things to like about **flexdashboard**, including lots of easy-to-use theming options, multiple pages, storyboards, and even **shiny** integration. Explaining how the **flexdashboard** package actually works is beyond the scope of this book, but you can visit the website for documentation and more examples <https://rmarkdown.rstudio.com/flexdashboard/>.
```{r flexdashboard-ggplotly, echo = FALSE, fig.link="https://plotly-r.com/flexdashboard.html", fig.cap = "(ref:flexdashboard-ggplotly)"}
knitr::include_graphics("images/flexdashboard.png")
```
### Bootstrap grid layout
If you're already familiar with **shiny**, you may already be familiar with functions like `fluidPage()`, `fluidRow()`, and `column()`. These R functions provide an interface from R to Bootstrap's grid layout system. That layout system is based on the notion of rows and columns where each row spans a width of 12 columns. Figure \@ref(fig:fluid) demonstrates how one can use these functions to produce a standalone HTML page with three **plotly** graphs --- with the first plot in the first row spanning the full width and the other 2 plots in the second row of equal width. To learn more about this `fluidPage()` approach to layouts, see <https://shiny.rstudio.com/articles/layout-guide.html>.
```{r, echo = FALSE}
set.seed(100)
```
```r
library(shiny)
p <- plot_ly(x = rnorm(100))
fluidPage(
fluidRow(p),
fluidRow(
column(6, p), column(6, p)
)
)
```
```{r fluid, echo = FALSE, fig.cap = "(ref:fluid)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/fluid.html"'}
knitr::include_graphics("images/fluid.png")
```
It's also worth noting another, somewhat similar, yet more succinct, interface to grid's layout system provided by the `bscols()` function from the **crosstalk** package. You can think of it in a similar way to `fluidRow()`, but instead of defining `column()` width for each component individually, you can specify the width of several components at once through the `widths` argument. Also, importantly, this function works recursively; it returns a collection of **htmltools** tags and accepts them as input as well. The code below produces the same result as above, but is a much more succinct way of doing so.
```r
bscols(p, bscols(p, p), widths = 12)
```
Bootstrap is much more than just its grid layout system, so beware; using either of these approaches will impose Bootstrap's styling rules on other content in your webpage. If you are using another Cascading Style Sheet (CSS) framework for styling or just want to reduce the size of dependencies in your webpage, consider working with CSS flexbox instead of Bootstrap.
### CSS flexbox
\index{HTML in R}
Cascading Style Sheet (CSS) flexbox is a relatively new CSS feature that most modern web browsers natively support.^[For a full reference of which browsers/versions support flexbox, see <https://caniuse.com/#feat=flexbox>.] It aims to provide a general system for distributing space among multiple components in a container. Instead of covering this entire system, we'll cover its basic functionality, which is fairly similar to Bootstrap's grid layout system.
Creating a flexbox requires a flexbox container; in HTML speak, that means a `<div>` tag with a CSS style property of `display: flex`. By default, in this display setting, all the components inside that container will try fitting in a single row. To allow 'overflowing' components the freedom to 'wrap' into new row(s), set the CSS property of `flex-wrap: wrap` in the parent container. Another useful CSS property to know about for the 'parent' container is `justify-content`: in the case of Figure \@ref(fig:flexbox), I'm using it to horizontally `center` the components. Moreover, since I've imposed a width of 40% for the first two plots, the net effect is that we have 2 plots in the first two (spanning 80% of the page width), then the third plot wraps onto a new line.
```r
library(htmltools)
p <- plot_ly(x = rnorm(100))
# NOTE: you don't need browsable() in rmarkdown,
# but you do at the R prompt
browsable(div(
style = "display: flex; flex-wrap: wrap; justify-content: center",
div(p, style = "width: 40%; border: solid;"),
div(p, style = "width: 40%; border: solid;"),
div(p, style = "width: 100%; border: solid;")
))
```
```{r flexbox, echo = FALSE, fig.cap = "(ref:flexbox)", out.extra = if (knitr::is_html_output()) 'data-url="/interactives/flexbox.html"'}
knitr::include_graphics("images/flexbox.png")
```
From the code example in Figure \@ref(fig:flexbox), you might notice that `display: flex; flex-wrap: wrap` is quite similar to Bootstrap grid layout system. The main difference is that, instead of specifying widths in terms of 12 columns, you have more flexibility with how to size things, as well as how you handle extra space. Here, in Figure \@ref(fig:flexbox) I've used widths that are relative to the page width, but you could also use fixed widths (using fixed widths, however, is generally frowned upon). For those who would like to learn about more details about CSS flexbox, see <https://css-tricks.com/snippets/css/a-guide-to-flexbox/>.
## Arranging many views {#navigating-many-views}
\index{ggplotly()@\texttt{ggplotly()}!facet\_trelliscope()@\texttt{facet\_trelliscope()}}
As we've already seen in Figures \@ref(fig:freqpoly-facet), \@ref(fig:trellis-txhousing), and \@ref(fig:subplot-trellis), the trellis (aka small multiple) display is an effective way to see how a conditional distribution behaves under different conditions. In other words, the trellis display helps us understand how patterns or structure in the data changes across groups. However, trellis displays do have a limitation: they don't scale very well to a large number of groups.
Before trellis displays were formally introduced, @scagnostics-tukey proposed a solution to the problem of scatterplots not being able to scale to a large number of variables (i.e., it's time consuming to visualize 1000 scatterplots!). The proposed solution involved using quantitative measurements of various scatterplot characteristics (e.g., correlation, clumpiness, etc.) to help summarise and guide attention towards 'interesting' scatterplots. This idea, coined scagnostics (short for scatterplot diagnostics), has since been made explicit, and many other similar applications have been explored, even techniques for time-series [@Wilkinson:2005b; @Wilkinson:2008; @Wilkinson:2012]. The idea of associating quantitative measures with a graphical display of data can be generalized to include more than just scatterplots, and in this more general case, these measures are sometimes referred to as cognostics.
In addition to being useful for navigating exploration of many variables, cognostics can also be useful for exploring many subsets of data. This idea has inspired work on more general divide and recombine technique(s) for working with navigating through many statistical artifacts [@divide-recombine; @RHIPE], including visualizations [@trelliscope]. The **trelliscope** package provides a system for computing arbitrary cognostics on each panel of a trellis display as well as an interactive graphical user interface for defining (and navigating through) interesting panels based on those cognostics [@trelliscope-pkg]. This system also allows users to define the graphical method for displaying each panel, so **plotly** graphs can easily be embedded. The **trelliscope** package is currently built upon **shiny**, but as Figure \@ref(fig:trelliscope) demonstrates, the **trelliscopejs** package provides lower-level tools that allow one to create trelliscope displays without **shiny** [@trelliscopejs].
As the video behind Figure \@ref(fig:trelliscope) demonstrates, **trelliscopejs** provides two very powerful interactive techniques for surfacing 'interesting' panels: sorting and filtering. In this toy example, each panel represents a different country, and the life expectancy is plotted as a function of time. By default, **trelliscopejs** sorts panels by group alphabetically, which is why, on page load we see the first 12 countries (Afghanistan, Albania, Algeria, etc.). By opening the sort menu, we can pick and sort by any cognostic for any variable in the dataset. If no cognostics are supplied (as it the case here), some sensible ones are computed and supplied for us (e.g., mean, median, var, max, min). In this case, since we are primarily interested in life expectancy, we sort by life expectancy. This simple task allows us to quickly see the countries with the best and worst average life expectancy, as well as how it has evolved over time. By combining sort with filter, we can surface countries that perform well/poorly under certain conditions. For example, Cuba, Uruguay, Taiwan have great life expectancy considering their GDP per capita. Also, within the Americas, Haiti, Bolivia, and Guatemala have the poorest life expectancy.
```r
library(trelliscopejs)
data(gapminder, package = "gapminder")
qplot(year, lifeExp, data = gapminder) +
xlim(1948, 2011) + ylim(10, 95) + theme_bw() +
facet_trelliscope(~ country + continent,
nrow = 2, ncol = 6, width = 300,
as_plotly = TRUE,
plotly_args = list(dynamicTicks = T),
plotly_cfg = list(displayModeBar = F)
)
```
```{r trelliscope, echo = FALSE, fig.cap = "(ref:trelliscope)"}
include_vimeo("325778067")
```