forked from hadley/mastering-shiny
-
Notifications
You must be signed in to change notification settings - Fork 0
/
basic-ui.Rmd
477 lines (359 loc) · 18.7 KB
/
basic-ui.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
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
# Basic UI
```{r, include = FALSE}
source("common.R")
source("demo.R")
```
## Introduction
Now that you have a basic app under your belt, we can start to explore the details that make Shiny tick.
As you saw in the previous chapter, Shiny encourages separation of the code that generates your user interface (the front end) from the code that drives your app's behaviour (the back end).
In this chapter, we'll focus on the front end, and give you a whirlwind tour of the HTML inputs and outputs provided by Shiny.
This gives you the ability to capture many types of data and display many types of R output.
You don't yet have many ways to stitch the inputs and outputs together, but we'll come back to that in Chapter \@ref(action-layout).
Here I'll mostly stick to the inputs and outputs built into Shiny itself.
However, there is a rich and vibrant community of extension packages, like [shinyWidgets](https://github.com/dreamRs/shinyWidgets), [colorpicker](https://github.com/daattali/colourpicker), and [sorttable](https://rstudio.github.io/sortable/).
You can find a comprehensive, actively-maintained list of other packages at <https://github.com/nanxstats/awesome-shiny-extensions>, maintained by [Nan Xiao](https://nanx.me/).
As usual, we'll begin by loading the shiny package:
```{r setup}
library(shiny)
```
## Inputs {#inputs}
As we saw in the previous chapter, you use functions like `sliderInput()`, `selectInput()`, `textInput()`, and `numericInput()` to insert input controls into your UI specification.
Now we'll discuss the common structure that underlies all input functions and give a quick overview of the inputs built into Shiny.
### Common structure
All input functions have the same first argument: `inputId`.
This is the identifier used to connect the front end with the back end: if your UI has an input with ID `"name"`, the server function will access it with `input$name`.
The `inputId` has two constraints:
- It must be a simple string that contains only letters, numbers, and underscores (no spaces, dashes, periods, or other special characters allowed!).
Name it like you would name a variable in R.
- It must be unique.
If it's not unique, you'll have no way to refer to this control in your server function!
Most input functions have a second parameter called `label`.
This is used to create a human-readable label for the control.
Shiny doesn't place any restrictions on this string, but you'll need to carefully think about it to make sure that your app is usable by humans!
The third parameter is typically `value`, which, where possible, lets you set the default value.
The remaining parameters are unique to the control.
When creating an input, I recommend supplying the `inputId` and `label` arguments by position, and all other arguments by name:
```{r, results = FALSE}
sliderInput("min", "Limit (minimum)", value = 50, min = 0, max = 100)
```
The following sections describe the inputs built into Shiny, loosely grouped according to the type of control they create.
The goal is to give you a rapid overview of your options, not to exhaustively describe all the arguments.
I'll show the most important parameters for each control below, but you'll need to read the documentation to get the full details.
### Free text
Collect small amounts of text with `textInput()`, passwords with `passwordInput()`[^basic-ui-1], and paragraphs of text with `textAreaInput()`.
[^basic-ui-1]: All `passwordInput()` does is hide what the user is typing, so that someone looking over their shoulder can't read it.
It's up to you to make sure that any passwords are not accidentally exposed, so we don't recommend using passwords unless you have had some training in secure programming.
```{r}
ui <- fluidPage(
textInput("name", "What's your name?"),
passwordInput("password", "What's your password?"),
textAreaInput("story", "Tell me about yourself", rows = 3)
)
```
```{r, echo = FALSE, out.width = NULL, message = FALSE}
demo <- demoApp$new("basic-ui/free-text", ui)
demo$takeScreenshot()
```
If you want to ensure that the text has certain properties you can use `validate()`, which we'll come back to in Chapter \@ref(action-feedback).
### Numeric inputs
To collect numeric values, create a constrained text box with `numericInput()` or a slider with `sliderInput()`.
If you supply a length-2 numeric vector for the default value of `sliderInput()`, you get a "range" slider with two ends.
```{r}
ui <- fluidPage(
numericInput("num", "Number one", value = 0, min = 0, max = 100),
sliderInput("num2", "Number two", value = 50, min = 0, max = 100),
sliderInput("rng", "Range", value = c(10, 20), min = 0, max = 100)
)
```
```{r, echo = FALSE, out.width = NULL, message = FALSE}
demo <- demoApp$new("basic-ui/numeric", ui)
demo$takeScreenshot()
```
Generally, I recommend only using sliders for small ranges, or cases where the precise value is not so important.
Attempting to precisely select a number on a small slider is an exercise in frustration!
Sliders are extremely customisable and there are many ways to tweak their appearance.
See `?sliderInput` and <https://shiny.rstudio.com/articles/sliders.html> for more details.
### Dates
Collect a single day with `dateInput()` or a range of two days with `dateRangeInput()`.
These provide a convenient calendar picker, and additional arguments like `datesdisabled` and `daysofweekdisabled` allow you to restrict the set of valid inputs.
```{r}
ui <- fluidPage(
dateInput("dob", "When were you born?"),
dateRangeInput("holiday", "When do you want to go on vacation next?")
)
```
```{r, echo = FALSE, out.width = NULL, message = FALSE}
demo <- demoApp$new("basic-ui/date", ui)
demo$takeScreenshot()
```
Date format, language, and the day on which the week starts defaults to US standards.
If you are creating an app with an international audience, set `format`, `language`, and `weekstart` so that the dates are natural to your users.
### Limited choices
There are two different approaches to allow the user to choose from a prespecified set of options: `selectInput()` and `radioButtons()`.
```{r}
animals <- c("dog", "cat", "mouse", "bird", "other", "I hate animals")
ui <- fluidPage(
selectInput("state", "What's your favourite state?", state.name),
radioButtons("animal", "What's your favourite animal?", animals)
)
```
```{r, echo = FALSE, out.width = NULL, message = FALSE}
demo <- demoApp$new("basic-ui/limited-choices", ui)
demo$takeScreenshot()
```
Radio buttons have two nice features: they show all possible options, making them suitable for short lists, and via the `choiceNames`/`choiceValues` arguments, they can display options other than plain text.
`choiceNames` determines what is shown to the user; `choiceValues` determines what is returned in your server function.
```{r}
ui <- fluidPage(
radioButtons("rb", "Choose one:",
choiceNames = list(
icon("angry"),
icon("smile"),
icon("sad-tear")
),
choiceValues = list("angry", "happy", "sad")
)
)
```
```{r, echo = FALSE, out.width = NULL, message = FALSE}
demo <- demoApp$new("basic-ui/radio-icon", ui)
demo$takeScreenshot()
```
Dropdowns created with `selectInput()` take up the same amount of space, regardless of the number of options, making them more suitable for longer options.
You can also set `multiple = TRUE` to allow the user to select multiple elements.
```{r}
ui <- fluidPage(
selectInput(
"state", "What's your favourite state?", state.name,
multiple = TRUE
)
)
```
```{r, echo = FALSE, out.width = NULL}
# Can't easily automate because of dropdown js
knitr::include_graphics("images/basic-ui/multi-select.png", dpi = 300)
```
If you have a very large set of possible options, you may want to use "server-side" `selectInput()` so that the complete set of possible options are not embedded in the UI (which can make it slow to load), but instead sent as needed by the server.
You can learn more about this advanced topic at <https://shiny.rstudio.com/articles/selectize.html#server-side-selectize>.
There's no way to select multiple values with radio buttons, but there's an alternative that's conceptually similar: `checkboxGroupInput()`.
```{r}
ui <- fluidPage(
checkboxGroupInput("animal", "What animals do you like?", animals)
)
```
```{r, echo = FALSE, out.width = NULL, message = FALSE}
demo <- demoApp$new("basic-ui/multi-radio", ui)
demo$takeScreenshot()
```
If you want a single checkbox for a single yes/no question, use `checkboxInput()`:
```{r}
ui <- fluidPage(
checkboxInput("cleanup", "Clean up?", value = TRUE),
checkboxInput("shutdown", "Shutdown?")
)
```
```{r, echo = FALSE, out.width = NULL, message = FALSE}
demo <- demoApp$new("basic-ui/yes-no", ui)
demo$takeScreenshot()
```
### File uploads
Allow the user to upload a file with `fileInput()`:
```{r}
ui <- fluidPage(
fileInput("upload", NULL)
)
```
```{r, echo = FALSE, out.width = NULL, message = FALSE}
demo <- demoApp$new("basic-ui/upload", ui)
demo$takeScreenshot()
```
`fileInput()` requires special handling on the server side, and is discussed in detail in Chapter \@ref(action-transfer).
### Action buttons {#action-buttons}
Let the user perform an action with `actionButton()` or `actionLink()`:
```{r}
ui <- fluidPage(
actionButton("click", "Click me!"),
actionButton("drink", "Drink me!", icon = icon("cocktail"))
)
```
```{r, echo = FALSE, out.width = NULL, message = FALSE}
demo <- demoApp$new("basic-ui/action", ui)
demo$takeScreenshot()
```
Actions links and buttons are most naturally paired with `observeEvent()` or `eventReactive()` in your server function.
You haven't learned about these important functions yet, but we'll come back to them in Section \@ref(controlling-timing-of-evaluation).
You can customise the appearance using the `class` argument by using one of `"btn-primary"`, `"btn-success"`, `"btn-info"`, `"btn-warning"`, or `"btn-danger"`.
You can also change the size with `"btn-lg"`, `"btn-sm"`, `"btn-xs"`.
Finally, you can make buttons span the entire width of the element they are embedded within using `"btn-block"`.
```{r}
ui <- fluidPage(
fluidRow(
actionButton("click", "Click me!", class = "btn-danger"),
actionButton("drink", "Drink me!", class = "btn-lg btn-success")
),
fluidRow(
actionButton("eat", "Eat me!", class = "btn-block")
)
)
```
```{r, echo = FALSE, out.width = NULL, message = FALSE}
demo <- demoApp$new("basic-ui/action-css", ui)
demo$takeScreenshot()
```
The `class` argument works by setting the `class` attribute of the underlying HTML, which affects how the element is styled.
To see other options, you can read the documentation for Bootstrap, the CSS design system used by Shiny: [\<http://bootstrapdocs.com/v3.3.6/docs/css/\#buttons\>](http://bootstrapdocs.com/v3.3.6/docs/css/#buttons){.uri}.
### Exercises
1. When space is at a premium, it's useful to label text boxes using a placeholder that appears *inside* the text entry area.
How do you call `textInput()` to generate the UI below?
```{r, echo = FALSE, out.width = NULL, message = FALSE}
ui <- fluidPage(
textInput("name", NULL, placeholder = "Your name")
)
demo <- demoApp$new("basic-ui/placeholder", ui)
demo$takeScreenshot()
```
2. Carefully read the documentation for `sliderInput()` to figure out how to create a date slider, as shown below.
```{r, echo = FALSE, out.width = NULL, message = FALSE}
today <- as.Date("2021-01-01")
ui <- fluidPage(
sliderInput("delivery", "When should we deliver?",
value = today + 1,
min = today,
max = today + 7,
step = 1,
round = TRUE
)
)
demo <- demoApp$new("basic-ui/date-slider", ui)
demo$takeScreenshot()
```
3. Create a slider input to select values between 0 and 100 where the interval between each selectable value on the slider is 5.
Then, add animation to the input widget so when the user presses play the input widget scrolls through the range automatically.
4. If you have a moderately long list in a `selectInput()`, it's useful to create sub-headings that break the list up into pieces.
Read the documentation to figure out how.
(Hint: the underlying HTML is called `<optgroup>`.)
## Outputs {#outputs}
Outputs in the UI create placeholders that are later filled by the server function.
Like inputs, outputs take a unique ID as their first argument[^basic-ui-2]: if your UI specification creates an output with ID `"plot"`, you'll access it in the server function with `output$plot`.
[^basic-ui-2]: Note that the name of that argument is different for inputs (`inputId`) and outputs (`outputId`).
I don't use the name of the first argument because it's so important and I expect you to remember what it does without an additional hint.
Each `output` function on the front end is coupled with a `render` function in the back end.
There are three main types of output, corresponding to the three things you usually include in a report: text, tables, and plots.
The following sections show you the basics of the output functions on the front end, along with the corresponding `render` functions in the back end.
### Text
Output regular text with `textOutput()` and fixed code and console output with `verbatimTextOutput()`.
```{r}
ui <- fluidPage(
textOutput("text"),
verbatimTextOutput("code")
)
server <- function(input, output, session) {
output$text <- renderText({
"Hello friend!"
})
output$code <- renderPrint({
summary(1:10)
})
}
```
```{r, echo = FALSE, out.width = NULL, message = FALSE}
demo <- demoApp$new("basic-ui/output-text", ui, server)
demo$takeScreenshot()
```
Note that the `{}` are only required in render functions if need to run multiple lines of code.
As you'll learn shortly, you should do as little computation in your render functions as possible, which means you can often omit them.
Here's what the server function above would look like if written more compactly::
```{r}
server <- function(input, output, session) {
output$text <- renderText("Hello friend!")
output$code <- renderPrint(summary(1:10))
}
```
Note that there are two render functions which behave slightly differently:
- `renderText()` combines the result into a single string, and is usually paired with `textOutput()`
- `renderPrint()` *prints* the result, as if you were in an R console, and is usually paired with `verbatimTextOutput()`.
We can see the difference with a toy app:
```{r}
ui <- fluidPage(
textOutput("text"),
verbatimTextOutput("print")
)
server <- function(input, output, session) {
output$text <- renderText("hello!")
output$print <- renderPrint("hello!")
}
```
```{r, echo = FALSE, out.width = NULL, message = FALSE}
demo <- demoApp$new("basic-ui/text-vs-print", ui, server)
demo$takeScreenshot()
```
This is equivalent to the difference between `cat()` and `print()` in base R.
### Tables
There are two options for displaying data frames in tables:
- `tableOutput()` and `renderTable()` render a static table of data, showing all the data at once.
- `dataTableOutput()` and `renderDataTable()` render a dynamic table, showing a fixed number of rows along with controls to change which rows are visible.
`tableOutput()` is most useful for small, fixed summaries (e.g. model coefficients); `dataTableOutput()` is most appropriate if you want to expose a complete data frame to the user.
If you want greater control over the output of `dataTableOutput()`, I highly recommend the [reactable](https://glin.github.io/reactable/index.html) package by Greg Lin.
```{r}
ui <- fluidPage(
tableOutput("static"),
dataTableOutput("dynamic")
)
server <- function(input, output, session) {
output$static <- renderTable(head(mtcars))
output$dynamic <- renderDataTable(mtcars, options = list(pageLength = 5))
}
```
```{r, echo = FALSE, out.width = "100%", message = FALSE}
demo <- demoApp$new("basic-ui/output-table", ui, server)
demo$resize(width = 800)
demo$takeScreenshot()
```
### Plots
You can display any type of R graphic (base, ggplot2, or otherwise) with `plotOutput()` and `renderPlot()`:
```{r}
ui <- fluidPage(
plotOutput("plot", width = "400px")
)
server <- function(input, output, session) {
output$plot <- renderPlot(plot(1:5), res = 96)
}
```
```{r, echo = FALSE, out.width = NULL, message = FALSE}
demo <- demoApp$new("basic-ui/output-plot", ui, server)
demo$takeScreenshot()
```
By default, `plotOutput()` will take up the full width of its container (more on that shortly), and will be 400 pixels high.
You can override these defaults with the `height` and `width` arguments.
We recommend always setting `res = 96` as that will make your Shiny plots match what you see in RStudio as closely as possible.
Plots are special because they are outputs that can also act as inputs.
`plotOutput()` has a number of arguments like `click`, `dblclick`, and `hover`.
If you pass these a string, like `click = "plot_click"`, they'll create a reactive input (`input$plot_click`) that you can use to handle user interaction on the plot, e.g. clicking on the plot.
We'll come back to interactive plots in Shiny in Chapter \@ref(action-graphics).
### Downloads
You can let the user download a file with `downloadButton()` or `downloadLink()`.
These require new techniques in the server function, so we'll come back to that in Chapter \@ref(action-transfer).
### Exercises
1. Which of `textOutput()` and `verbatimTextOutput()` should each of the following render functions be paired with?
a. `renderPrint(summary(mtcars))`
b. `renderText("Good morning!")`
c. `renderPrint(t.test(1:5, 2:6))`
d. `renderText(str(lm(mpg ~ wt, data = mtcars)))`
2. Re-create the Shiny app from Section \@ref(plots), this time setting height to 300px and width to 700px.
Set the plot "alt" text so that a visually impaired user can tell that its a scatterplot of five random numbers.
3. Update the options in the call to `renderDataTable()` below so that the data is displayed, but all other controls are suppress (i.e. remove the search, ordering, and filtering commands).
You'll need to read `?renderDataTable` and review the options at <https://datatables.net/reference/option/>.
```{r}
ui <- fluidPage(
dataTableOutput("table")
)
server <- function(input, output, session) {
output$table <- renderDataTable(mtcars, options = list(pageLength = 5))
}
```
4. Alternatively, read up on [reactable](https://glin.github.io/reactable), and convert the above app to use it instead.
## Summary
This chapter has introduced you to the major input and output functions that make up the front end of a Shiny app.
This was a big info dump, so don't expect to remember everything after a single read.
Instead, come back to this chapter when you're looking for a specific component: you can quickly scan the figures, and then find the code you need.
In the next chapter, we'll move on to the back end of a Shiny app: the R code that makes your user interface come to life.