10  Using shinylive

shinylive is an extension and R package that allows for embedding a serverless Shiny application in a Quarto page, as we do in Figure 10.1.

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 600

library(shiny)
library(bslib)

# Define UI for app that draws a histogram ----
ui <- page_sidebar(
  sidebar = sidebar(open = "open",
    numericInput("n", "Sample count", 100),
    checkboxInput("pause", "Pause", FALSE),
  ),
  plotOutput("plot", width=500)
)

server <- function(input, output, session) {
  data <- reactive({
    input$resample
    if (!isTRUE(input$pause)) {
      invalidateLater(1000)
    }
    rnorm(input$n)
  })
  
  output$plot <- renderPlot({
    op <- par(cex = 0.5)
    
    hist(data(),
      breaks = 40,
      xlim = c(-2, 2),
      ylim = c(0, 1),
      lty = "blank",
      xlab = "value",
      freq = FALSE,
      main = ""
    )
    
    x <- seq(from = -2, to = 2, length.out = 500)
    y <- dnorm(x)
    lines(x, y, lwd=1.5)
    
    lwd <- 5
    abline(v=0, col="red", lwd=lwd, lty=2)
    abline(v=mean(data()), col="blue", lwd=lwd, lty=1)
    
    legend(legend = c("Normal", "Mean", "Sample mean"),
      col = c("black", "red", "blue"),
      lty = c(1, 2, 1),
      lwd = c(1, lwd, lwd),
      x = -2,
      y = 1
    )
  }, res=140)
}

# Create Shiny app ----
shinyApp(ui = ui, server = server)
Figure 10.1: Interactive shinylive example showing random samples from a Normal distribution. Click the ‘Pause’ checkbox to freeze on a sample. Use the ‘Sample count’ option to choose the number of samples. The black line shows the Normal distribution being sampled from, and the dashed red line shows the population mean. The solid blue line shows the sample mean. The grey histogram shows the sample.
```{shinylive-r}
#| standalone: true
#| viewerHeight: 600

{CODE GOES HERE}
```
library(shiny)
library(bslib)

# Define UI for app that draws a histogram ----
ui <- page_sidebar(
  sidebar = sidebar(open = "open",
    numericInput("n", "Sample count", 100),
    checkboxInput("pause", "Pause", FALSE),
  ),
  plotOutput("plot", width=500)
)

server <- function(input, output, session) {
  data <- reactive({
    input$resample
    if (!isTRUE(input$pause)) {
      invalidateLater(1000)
    }
    rnorm(input$n)
  })
  
  output$plot <- renderPlot({
    op <- par(cex = 0.5)
    
    hist(data(),
      breaks = 40,
      xlim = c(-2, 2),
      ylim = c(0, 1),
      lty = "blank",
      xlab = "value",
      freq = FALSE,
      main = ""
    )
    
    x <- seq(from = -2, to = 2, length.out = 500)
    y <- dnorm(x)
    lines(x, y, lwd=1.5)
    
    lwd <- 5
    abline(v=0, col="red", lwd=lwd, lty=2)
    abline(v=mean(data()), col="blue", lwd=lwd, lty=1)
    
    legend(legend = c("Normal", "Mean", "Sample mean"),
      col = c("black", "red", "blue"),
      lty = c(1, 2, 1),
      lwd = c(1, lwd, lwd),
      x = -2,
      y = 1
    )
  }, res=140)
}

# Create Shiny app ----
shinyApp(ui = ui, server = server)

10.1 The fundamentals of a Shiny app

We don’t have space here for a tutorial on how to use Shiny. There is a learning curve, and we can recommend a number of online resources for you to get up to speed with this package, including:

The bare bones

To implement a Shiny app, you need to define three things in your R code:

  1. a user interface object that defines how the user interacts with the app (sliders, checkboxes, plots, etc.)
  2. a server function that returns values to be displayed by the user interface
  3. a call to the shinyApp() function
library(shiny)   # of course, you have to make the Shiny package available

ui <- {CODE DEFINING USER INTERFACE}

server <- function(input, output, session) {CODE DEFINING WHAT HAPPENS}

shinyApp(ui = ui, server = server)

10.2 What you need to do special for shinylive

Under normal circumstances, the Shiny code with the three elements above would run on a Shiny server, and you wouldn’t need to do much else. But with shinylive and Quarto you need to place the app on the page, and tell Quarto that you want to use the shinylive filter. Firstly, the header of the page should be defined in YAML as, e.g.:

---
title: "Using `shinylive`"
filters:
  - shinylive
format: 
  html:
    css: assets/fix_editor.css
---
Note

The format: section is required to fix a layout problem with r-shinylive interactive editors, described in Section 10.3.

This requires a new kind of fenced block stating that it is {shinylive-r}:

```{shinylive-r}
{CODE GOES HERE}
```

and there are new block-level arguments controlling on-page display, e.g.

```{shinylive-r}
#| standalone: true
#| viewerHeight: 600

{CODE GOES HERE}
```
Important

The standalone:true setting is required for the compiled app to run in your public repository. This setting tells shinylive that the entire app is contained within the fenced code block.

10.2.1 Example

The shinylive example in Figure 10.2 below is fairly short but includes some important points of difference from Figure 10.1.

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 600

library(shiny)
library(ggplot2)
library(DT)

if (FALSE) {
  library(munsell)
}

ui <- fluidPage(
  plotOutput("plot", brush = "plot_brush"),
  DTOutput("table")
)

server <- function(input, output, session) {
  output$plot <- renderPlot(
    ggplot(mtcars) +
      geom_point(aes(x = mpg, y = disp))
  )
  output$table <- renderDT({
    brushedPoints(mtcars, input$plot_brush)
  })
}

shinyApp(ui = ui, server = server)
Figure 10.2: shinylive example using ggplot2, DT, and allowing for interactive selection of points from the graph.
```{shinylive-r}
#| standalone: true
#| viewerHeight: 600

# Import required libraries
library(shiny)
library(ggplot2)
library(DT)

# ggplot2 will not work in shinylive without this - see callout
if (FALSE) {
  library(munsell)
}

# Define the user interface
ui <- fluidPage(
  plotOutput("plot", brush = "plot_brush"),
  DTOutput("table")
)

# Define the server code
server <- function(input, output, session) {
  output$plot <- renderPlot(
    ggplot(mtcars) +
      geom_point(aes(x = mpg, y = disp))
  )
  output$table <- renderDT({
    brushedPoints(mtcars, input$plot_brush)
  })
}

# Run the shinyapp
shinyApp(ui = ui, server = server)
```
# Import required libraries
library(shiny)
library(ggplot2)
library(DT)

# ggplot2 will not work in shinylive without this - see callout
if (FALSE) {
  library(munsell)
}

# Define the user interface
ui <- fluidPage(
  plotOutput("plot", brush = "plot_brush"),
  DTOutput("table")
)

# Define the server code
server <- function(input, output, session) {
  output$plot <- renderPlot(
    ggplot(mtcars) +
      geom_point(aes(x = mpg, y = disp))
  )
  output$table <- renderDT({
    brushedPoints(mtcars, input$plot_brush)
  })
}

# Run the shinyapp
shinyApp(ui = ui, server = server)
Using ggplot2 with shinylive

There is a known issue with ggplot2 and shinylive such that no output is produced due to a missing suggested dependency. A workaround is indicated at this StackOverflow page :

library(ggplot2)

if (FALSE) {
  library(munsell)
}

10.3 Adding the R editor

The examples above allow for user interaction, but not user editing of the code. With shinylive it is possible to include an editor so that users can modify the code of the app directly, in their own browser.

To do this, include the code block setting: #| components: [editor, viewer], as in Figure 10.3.

Tip

Quarto book pages are tall rather than wide, so it is often helpful to stack the editor and viewer vertically, with the options:

#| components: [editor, viewer]
#| layout: vertical
Important

The layout for the r-shinylive editor inherits text alignment from the surrounding <div> and can be misaligned. This book template includes a .css file that fixes this, and which must be included in the YAML header for the page:

format: 
  html:
    css: assets/fix_editor.css
#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 600
#| components: [viewer, editor]
#| layout: vertical

library(shiny)
library(ggplot2)
library(DT)

if (FALSE) {
  library(munsell)
}

ui <- fluidPage(
  plotOutput("plot", brush = "plot_brush"),
  DTOutput("table")
)

server <- function(input, output, session) {
  output$plot <- renderPlot(
    ggplot(mtcars) +
      geom_point(aes(x = mpg, y = disp))
  )
  output$table <- renderDT({
    brushedPoints(mtcars, input$plot_brush)
  })
}

shinyApp(ui = ui, server = server)
Figure 10.3: shinylive example using ggplot2, DT, and allowing for interactive selection of points from the graph.
```{shinylive-r}
#| standalone: true
#| viewerHeight: 600
#| components: [viewer, editor]
#| layout: vertical

# Import required libraries
library(shiny)
library(ggplot2)
library(DT)

# ggplot2 will not work in shinylive without this - see callout
if (FALSE) {
  library(munsell)
}

# Define the user interface
ui <- fluidPage(
  plotOutput("plot", brush = "plot_brush"),
  DTOutput("table")
)

# Define the server code
server <- function(input, output, session) {
  output$plot <- renderPlot(
    ggplot(mtcars) +
      geom_point(aes(x = mpg, y = disp))
  )
  output$table <- renderDT({
    brushedPoints(mtcars, input$plot_brush)
  })
}

# Run the shinyapp
shinyApp(ui = ui, server = server)
```
# Import required libraries
library(shiny)
library(ggplot2)
library(DT)

# ggplot2 will not work in shinylive without this - see callout
if (FALSE) {
  library(munsell)
}

# Define the user interface
ui <- fluidPage(
  plotOutput("plot", brush = "plot_brush"),
  DTOutput("table")
)

# Define the server code
server <- function(input, output, session) {
  output$plot <- renderPlot(
    ggplot(mtcars) +
      geom_point(aes(x = mpg, y = disp))
  )
  output$table <- renderDT({
    brushedPoints(mtcars, input$plot_brush)
  })
}

# Run the shinyapp
shinyApp(ui = ui, server = server)
Challenge

What happens if you change line 17 of the code in the editor to:

geom_point(aes(x = mpg, y = disp, color=as.factor(cyl)))

and run the code (click on the triangle, or press Shift-Return/Cmd-Shift-Return)

10.4 Installing the shinylive extension

For local use and development you will need to install the shinylive package:

install.packages("shinylive")

To install the shinylive Quarto extension, use the commmand:

quarto add quarto-ext/shinylive
Important

The shinylive extension is installed as part of this template, and the shinylive package is installed via the DESCRIPTION file. This extension should work automatically in the rendered GitHub pages.