---
title: "R Shiny Part II"
subtitle: "Statistical Computing & Programming"
author: "Shawn Santo"
institute: ""
date: "06-10-20"
output:
xaringan::moon_reader:
css: "slides.css"
lib_dir: libs
nature:
highlightStyle: github
highlightLines: true
countIncrementalSlides: false
editor_options:
chunk_output_type: console
---
```{r include=FALSE}
knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE,
comment = "#>", highlight = TRUE,
fig.align = "center")
```
## Supplementary materials
Companion videos
- [R Shiny part 1 recap](https://warpwire.duke.edu/w/L9QDAA/)
- [Enhancing the UI](https://warpwire.duke.edu/w/N9QDAA/)
- [Enhancing the server function part 1](https://warpwire.duke.edu/w/Z9QDAA/)
- [Enhancing the server function part 2](https://warpwire.duke.edu/w/a9QDAA/)
Additional resources
- Shiny [reactivity](https://shiny.rstudio.com/articles/#reactivity)
- Shiny [code quality](https://shiny.rstudio.com/articles/#code-quality)
---
class: inverse, center, middle
# Recall
---
## What is Shiny?
.pull-left[
- Shiny is an R package.
- Build web-based apps with R in RStudio.
- Shiny can incorporate CSS themes and JavaScript actions.
]
.pull-right[
![](images/shinylogo.png)
]
---
## How does Shiny work?
![](images/server1.png)
---
## How does Shiny work?
![](images/server2.png)
---
## How does Shiny work?
![](images/server3.png)
---
## Main components of Rshiny
.pull-left[
```{r eval=FALSE}
# Load package shiny
library(shiny)
# Define UI for application
ui <- fluidPage(
)
# Define server logic
server <- function(input, output) {
}
# Build and run the application
shinyApp(ui = ui, server = server)
```
]
.pull-right[
- Function `fluidPage()` creates a dynamic HTML user interface you see when you
look at an RShiny app. Convention is to save this as an object named `ui`.
- Function `server()` is user-defined and contains R commands your computer
or external server need to run the app.
- Function `shinyApp()` builds the app based on the user interface and
server pair of code.
]
---
## User interface review
- Build the user interface inside function `fluidPage()` and save it
as an object named `ui`.
- Function `fluidPage()` scales its components in real-time to fill all
available browser width - dynamic HTML user interface.
- Build inputs with `*Input(inputId, label, ...)`.
- Build outputs with `*Output(outputId, ...)`.
- Separate multiple inputs and outputs with commas.
- Run your app after each added input or output to minimize complications
later on.
---
## Function `server()` review
- The server function does the work in terms of building and rebuilding R
objects.
- Save output you build to `output$`.
- Build output with a `render*()` function.
- Access inputs with `input$`.
- Multiple outputs can be placed in the server function.
- Reactivity happens automatically when you use inputs to build rendered
outputs.
---
class: inverse, center, middle
# UI Layouts
---
## Layouts
- Use layout functions to position elements in your app.
- Use panels to group elements into a single unit for aesthetic or
functional purposes.
- Design your own layout or use a packaged layout.
---
## Rows with `fluidRow()`
```{r eval=FALSE}
ui <- fluidPage(
fluidRow(
# add inputs/outputs to row 1
),
fluidRow(
# add inputs/outputs to row 2
),
fluidRow(
# add inputs/outputs to row 3
)
)
```
---
## Rows and columns
```{r eval=FALSE}
ui <- fluidPage(
fluidRow(
column(width = 5,
# add inputs/outputs
# column width 5
),
column(width = 7,
# add inputs/outputs
# column width 7
)
),
fluidRow(
column(width = 8, offset = 2,
# add inputs/outputs
# 2 units in from left
)
)
)
```
---
## Rows and columns example
.small[
```{r eval=FALSE}
ui <- fluidPage(
fluidRow(
column(width = 5,
passwordInput(inputId = "pass",
label = "Enter password:"),
actionButton(inputId = "passbtn",
label = "Submit password")
),
column(width = 7,
paste("Add some description in row 1",
"of the column with a width of 7",
"here...", sep = " ")
)
),
fluidRow(
column(width = 8, offset = 2,
checkboxGroupInput(inputId = "checks",
label = "",
choices = c("Choice 1",
"Choice 2",
"Choice 3",
"Choice 4")),
"Add some more text in Row 2 here..."
)
)
)
```
]
---
## Panels
![](images/panels.png)
---
## Packaged layout: `sidebarLayout()`
.pull-left[
.tiny[
```{r eval=FALSE}
ui <- fluidPage(
# give a title in quotes
titlePanel(),
sidebarLayout(
sidebarPanel(
# inputs/outputs
),
mainPanel(
# inputs/outputs
)
)
)
```
]
]
.pull-right[
![](images/sidebarlayout.png)
]
---
## Packaged layout: `navbarPage()`
.pull-left[
.tiny[
```{r eval=FALSE}
ui <- fluidPage(
navbarPage(title = "Navigation Bar Layout",
tabPanel("Tab 1",
# add inputs/outputs
),
tabPanel("Tab 2",
# add inputs/outputs
),
tabPanel("Tab 3",
# add inputs/outputs
),
tabPanel("Tab 4",
# add inputs/outputs
)
)
)
```
]
]
.pull-right[
![](images/navbar1.png)
]
---
## Packaged layout: `navbarPage()`
.pull-left[
.tiny[
```{r eval=FALSE}
ui <- fluidPage(
navbarPage(title = "Navigation Bar Layout",
tabPanel("Tab 1",
# add inputs/outputs
),
tabPanel("Tab 2",
# add inputs/outputs
),
tabPanel("Tab 3",
# add inputs/outputs
),
tabPanel("Tab 4",
# add inputs/outputs
)
)
)
```
]
]
.pull-right[
![](images/navbar2.png)
]
---
## Layout recap
- Use `fluidRow()` to arrange elements in rows; use columns() to
arrange elements in columns, where total width is 12
- Use `sidebarPanel()` and `mainPanel()` to partition app with the packaged
layout function sidebarLayout()
- Use `tabPanel()` with packaged layouts `navbarPage()` or `navbarMenu()`.
---
class: inverse, center, middle
# Reactivity
---
## Frequency of code execution
- Shiny will run the whole script the first time your app is launched.
- Each time a new user visits your app, Shiny runs the server function
again, one time.
- As users interact with widgets, Shiny will re-run the corresponding R
expressions that depend on a widget whose value was changed.
---
## Consider the simple app
.pull-left[
.tiny[
```{r eval=FALSE}
library(shiny)
library(tidyverse)
# Build UI
ui <- fluidPage(
textInput(inputId = "title",
label = "Enter a title"),
numericInput(inputId = "num",
label = "Number of variables",
value = 100,
min = 1),
plotOutput(outputId = "hist")
)
# Define server function
server <- function(input, output) {
output$hist <- renderPlot({
ggplot(as_tibble(rexp(input$num)),
aes(x = value)) +
geom_histogram(bins = 10) +
labs(title = input$title)
})
}
# Run the application
shinyApp(ui = ui, server = server)
```
]
]
.pull-right[
![](images/appbuild3.png)
]
---
## Is there a problem?
.pull-left[
.tiny[
```{r eval=FALSE}
library(shiny)
library(tidyverse)
# Build UI
ui <- fluidPage(
textInput(inputId = "title",
label = "Enter a title"),
numericInput(inputId = "num",
label = "Number of variables",
value = 100,
min = 1),
plotOutput(outputId = "hist")
)
# Define server function
server <- function(input, output) {
output$hist <- renderPlot({
ggplot(as_tibble(rexp(input$num)),
aes(x = value)) +
geom_histogram(bins = 10) +
labs(title = input$title)
})
}
# Run the application
shinyApp(ui = ui, server = server)
```
]
]
.pull-right[
- Every time you change the title, `labs(title = input$title)` new random
numbers will be generated. If a single input changes in a block of code
inside a render function, then the entire block of code is re-run.
- This is very inefficient and can cause problems.
]
---
## Attempted solution
.pull-left[
.tiny[
```{r eval=FALSE}
library(shiny)
library(tidyverse)
# Build UI
ui <- fluidPage(
textInput(inputId = "title",
label = "Enter a title"),
numericInput(inputId = "num",
label = "Number of variables",
value = 100,
min = 1),
plotOutput(outputId = "hist")
)
# Define server function
server <- function(input, output) {
data <- rexp(input$num)
output$hist <- renderPlot({
ggplot(as_tibble(data),
aes(x = value)) +
geom_histogram(bins = 10) +
labs(title = input$title)
})
}
# Run the application
shinyApp(ui = ui, server = server)
```
]
]
.pull-right[
```r
Error in .getReactiveEnvironment()$currentContext():
Operation not allowed without
an active reactive context.
(You tried to do something that can
only be done from inside a reactive
expression or observer.)
```
- Reactive inputs must be in a reactive-type function.
]
---
## Reactive expressions
The render functions are reactive-type functions. Function `reactive()`
builds a reactive object. The object will respond to every
reactive source in the code.
Rather than
```{r eval=FALSE}
data <- rexp(input$num)
```
use
```{r eval=FALSE}
data <- reactive({rexp(input$num)})
```
--
A reactive expression has two special properties:
1. call a reactive expression like a function, `data()`;
2. reactive expressions cache their values, the value is retained until
it becomes invalidated.
---
## Solution
.tiny[
```{r eval=FALSE}
library(shiny)
library(tidyverse)
# Build UI
ui <- fluidPage(
textInput(inputId = "title",
label = "Enter a title"),
numericInput(inputId = "num",
label = "Number of variables",
value = 100,
min = 1),
plotOutput(outputId = "hist")
)
# Define server function
server <- function(input, output) {
data <- reative({rexp(input$num)})
output$hist <- renderPlot({
ggplot(as_tibble(data()),
aes(x = value)) +
geom_histogram(bins = 10) +
labs(title = input$title)
})
}
# Run the application
shinyApp(ui = ui, server = server)
```
]
---
## Complementary functions for reactivity
| Function | Purpose |
|------------------:|:---------------------------------------|
| `isolate()` | prevent reactions |
| `observeEvent()` | trigger code, useful for action button |
| `observe()` | similar to `observeEvent()` |
| `eventReactive()` | delay reactions |
---
class: inverse, center, middle
# Dynamic UI
---
## Dynamic interface
Shiny offers four main approaches to build a dynamic UI:
1. Function `conditionalPanel()`: wraps UI elements, does require very
very minimal JavaScript knowledge
2. Function `renderUI()`: use in `server()` in conjunction with the
`uiOutput()` function in `ui`, lets you generate calls to UI functions
and make the results appear in a predetermined place in the UI.
3. Functions `insertUI()` and `removeUI()`: allow you to add or remove pieces
of UI code
4. Use JavaScript directly
---
## References
- Shiny. (2019). Shiny.rstudio.com. https://shiny.rstudio.com/