Shinylive is SO EASY

Max Kuhn

Quarto

  • It is the next generation of what Rmarkdown, bookdown, blogdown (and all of the ā€œdownsā€) started.

  • Code chunks can target R, python, SQl, bash, juila, and so on.

  • This is an open source technical publishing system that is independent of the target language (i.e., R, python, etc.).

Shiny and Servers

Historically, to let {people who are not you} run a shiny app, youā€™d need:

  • A shiny server of your own
  • A hosted server (e.g. shinyapps.io, Posit Connect)

The server runs your code and renders the results on your computer/phone/tv etc.

To use a Shiny app in Quarto, you would need to embed a frame that would show the app (from the external shiny server)


An example appā€¦

Understanding UMAP

#| label: fig-umap
#| viewerHeight: 550
#| standalone: true

library(shiny)
library(ggplot2)
library(bslib)
library(viridis)

# ------------------------------------------------------------------------------

light_bg <- "#fcfefe" # from aml4td.scss
grid_theme <- bs_theme(
  bg = light_bg, fg = "#595959"
)

# ------------------------------------------------------------------------------

theme_light_bl<- function(...) {

  ret <- ggplot2::theme_bw(...)

  col_rect <- ggplot2::element_rect(fill = light_bg, colour = light_bg)
  ret$panel.background  <- col_rect
  ret$plot.background   <- col_rect
  ret$legend.background <- col_rect
  ret$legend.key        <- col_rect

  ret$legend.position <- "top"

  ret
}

# ------------------------------------------------------------------------------

ui <- fluidPage(
  theme = grid_theme,
  fluidRow(

    column(
      width = 4,
      sliderInput(
        inputId = "min_dist",
        label = "Min Distance",
        min = 0.0,
        max = 1.0,
        value = 0.2,
        width = "100%",
        step = 0.2
      )
    ), # min distance
    column(
      width = 4,
      sliderInput(
        inputId = "neighbors",
        label = "Neighbors",
        min = 5,
        max = 45,
        value = 5,
        width = "100%",
        step = 10
      )
    ), # nearest neighbors

    column(
      width = 4,
      sliderInput(
        inputId = "supervised",
        label = "Amount of Supervision",
        min = 0.0,
        max = 0.7,
        value = 0,
        width = "100%",
        step = 0.1
      )
    ),
    fluidRow(
      column(
        width = 4,
        radioButtons(
          inputId = "initial",
          label = "Initialization",
          choices = list("Laplacian Eigenmap" = "spectral", "PCA" = "pca", 
                         "Random" = "random")
        )
      ),
      column(
        width = 6,
        align = "center",
        plotOutput('umap')
      )
    )
  ) # top fluid row
)

server <- function(input, output) {
  load(url("https://raw.githubusercontent.com/aml4td/website/main/RData/umap_results.RData"))

  output$umap <-
    renderPlot({
      
      dat <-
        umap_results[
          umap_results$neighbors == input$neighbors &
            umap_results$min_dist == input$min_dist &
            umap_results$initial == input$initial &
            # log10(umap_results$learn_rate) == input$learn_rate &
            umap_results$supervised == input$supervised,
        ]

      p <-
        ggplot(dat, aes(UMAP1, UMAP2, col = barley)) +
        geom_point(alpha = 1 / 3, cex = 3) +
        scale_color_viridis(option = "viridis") +
        theme_light_bl() +
        coord_fixed() +
        labs(x = "UMAP Embedding #1", y = "UMAP Embedding #2") +
        guides(col = guide_colourbar(barheight = 0.5))

      print(p)

    })
}

app <- shinyApp(ui = ui, server = server)

webR

R can be compiled to WebAssembly (aka wasm)

  • R and packages are made into a binary format that can be embedded in the website.
  • JavaScript is the interface that the browser uses.

See George Staggā€™s video for more information.

You can see which R packages can be used at the repository site. In mid May:

  • Built R packages: 19,531 (about 94% of CRAN).
  • Available R packages: 13,707 (about 66% of CRAN).

webR

shinylive

A project for deploying Shiny applications that will run completely in the browser via webR

  • Computations occur locally!

shinylive does almost all of the heavy lifting for you.


Iā€™ll focus on adding shiny apps to Quarto documents (a book, in my case)

shiny server: runs your code and renders the results on your client.

shinylive: downloads R & packages to client then runs R/shiny on the client

Setup

Download the extension for your Quarto project

quarto add quarto-ext/shinylive



You also have to add a Quarto/Pandoc filter reference in your _quarto.yml file:

filters:
  - shinylive

Code Chunks


Each shiny app goes in a single code chunk.

Instead of using {r} in the header, it is {shinylive-r}.

Also use the standalone option:


```{shinylive-r}
#| label: fig-shiny-spline
#| standalone: true

# shiny app code here

```

Package declarations

To declare packages, use library() calls

  • The renv package is used to figure this out.

Currently

  • Shinylive grabs the latest built version of a package from the wasm repository at render-time

Upcoming

  • Shinylive will bundle package files when the app is first built and tries to match package versions used at that time.

Ingesting Data and Code




The one rule is:

If you live in JavaScriptā€™s house,
you have to live by JavaScriptā€™s rules

Data access

You donā€™t get automatic access to your local objects in your workspace


Opening raw network sockets is not permitted from the WebAssembly sandbox (no curl).


webR patches download.file() to use an XHR requests (JS API for HTTP requests).


Itā€™s a work in progress. (GH issue: Add local folder support).

As always, be carefulā€¦


From Gordon Shotwell:

  • All the source code goes to the client
  • All of the data goes to the client
  • Donā€™t include credentials
  • Donā€™t include sensitive data

An Example Pattern

```{shinylive-r}
#| label: fig-potato
#| standalone: true

library(tidymodels)
1ļøāƒ£ # other library() calls 

2ļøāƒ£ load("https://raw.githubusercontent.com/{user}/{repo}/{branch}/{file}")

3ļøāƒ£ source("https://raw.githubusercontent.com/{user}/{repo}/{branch}/{file}")

4ļøāƒ£ app
```
  1. Packages should be listed in the code chunk.

  2. Load the data in the code chunk.

  3. source() with a local file wonā€™t work.

  4. Return the shiny app in the code chunk.

Why would Posit support this?

For many applications, shinylive is not the answer:

  • Client computing power may not be great.

  • Moving data to the client may be expensive.

  • Data/Code security is paramount to many.

  • Niceties of authentication, parameterization, support, etc that Connect offers.

More Information

Must watch: Running R-Shiny without a Server - posit::conf(2023)

Examples:

Specific examples by George Stagg:

Thanks

Thanks for the invitation to speak again!


Thanks to George Stagg, Joe Cheng, Winston Chang, Gordon Shotwell, and everyone else who made webR and shinylive happen.