Shiny: Interactive Web Apps in R

Shiny is a framework that turns R code and figures into interactive web applications. Allow's showtime out past looking at a built-in example Shiny app.

                                          library(shiny)                              runExample("01_hello")                      

In the bottom panel of the resulting Shiny app (Figure 9.1), we tin meet the R script that is essential to running any Shiny app: app.R. Accept a minute to explore how the app works and how the script code is structured. The beginning role of the script (ui <-) defines the app's user interface (UI) using directives that partition the resulting web page and placement of input and output elements. The 2d office of the script defines a function called server with arguments input and output. This function provides the logic required to run the app (in this case, to draw the histogram of the Old Faithful Geyser Data). Third, ui and server are passed to a function called shinyApp which creates the application.54

Hello Shiny example app

Effigy ix.i: Hello Shiny example app

Running a Elementary Shiny App

Shiny apps allow users to interact with a data set. Let's say nosotros accept access to a dummy information set name_list.csv. Begin by taking a quick look at what this CSV file contains.

                              namesDF <-                                    read.csv("https://www.finley-lab.com/files/data/name_list.csv",                                                  stringsAsFactors =                  Simulated)                                  str(namesDF)                          
            ## 'information.frame':    200 obs. of  four variables: ##  $ First: chr  "Sarah" "Boris" "Jessica" "Diane" ... ##  $ Final : chr  "Poole" "Dowd" "Wilkins" "Murray" ... ##  $ Age  : int  15 52 12 58 56 iv 14 0 28 27 ... ##  $ Sex activity  : chr  "F" "1000" "F" "F" ...          

Given the namesDF information, our goal over the next few sections is to develop a Shiny application that allows users to explore age distributions for males and females. We begin by defining a very basic Shiny application script upon which we can build the desired functionality. The script file that defines this basic awarding is called app.R and is provided in the code block beneath called "version 1". Follow the subsequent steps to create and run this Shiny app:

  1. Create a new directory called "ShinyPractice".
  2. In the "ShinyPractice" directory, create a blank R script called app.R.
  3. Copy the code in "app.R version ane" into app.R.
  4. Run the Shiny app from RStudio. There are two ways to do this: 1) employ the RStudio button in the elevation right of the script window or; 2) type the function runApp('file location') in the RStudio panel, where file location is replaced with the relative path to the directory where app.R is saved.
                                                # app.R version 1                                                                  library(shiny)                                names.df <-                                    read.csv("https://www.finley-lab.com/files/data/name_list.csv")                                                  # Define UI                                ui <-                                    fluidPage(                                                                  titlePanel("Random Names Analysis"),                                                                  sidebarLayout(                                                                  sidebarPanel("our inputs will be here"),                                                                  mainPanel("our output will appear hither")                                  )                )                                                  # Define server logic                                server <-                                    function(input, output) {}                                                  # Create Shiny app                                                  shinyApp(ui =                  ui,                  server =                  server)                          

If everything compiles correctly, y'all should go a window that looks like Effigy 9.2. In that location are a few things to notice. First, nosotros read in the CSV file at the beginning of the app.R script then that these data can be accessed in subsquent evolution steps (although nosotros don't use information technology yet in this initial application). Second, our Shiny app is non interactive yet considering there is no way for a user to input whatever information—the page is view-only. Third, the function function(input,output) {} in app.R does not include any code, reflecting that nosotros are not yet using any user inputs or creating whatever outputs.

Your first Shiny app

FIGURE 9.2: Your first Shiny app

Adding User Input

Shiny bundle widgets are used to collect user provided inputs (visit the Shiny Widget Gallery to go a sense of the unlike kinds of input dialogues). In the subsequent development we'll use slider bars, dropdown menus, and bank check boxes. All widget functions, at minimum, include an inputId and label argument. The character string assigned to the inputId statement will be the variable name used to access the value(southward) selected or provided past the user. This variable name is used in the application development and non seen by the user. The character string assigned to the label argument appears in the UI above the given input widget and serves to provide a convenient clarification of the desired input.

Building on the "version 1" code, we add a dropdown menu to the script's UI portion using the selectInput function to the "version 2" lawmaking below. Update app.R and rerun the awarding. What is our inputId for the dropdown carte? What is our label? Tin can yous see where the label is displayed in the app when yous rerun information technology?

Look at the construction of the choices argument in selectInput. Information technology's a vector of options in the form "name" = "value" where the proper noun is what the user sees, and the value is what is returned to the server logic part of the app.R file.

                                                # app.R version two                                                                  library(shiny)                                names.df <-                                    read.csv("https://www.finley-lab.com/files/data/name_list.csv")                                                  # Ascertain UI                                ui <-                                    fluidPage(                                  titlePanel("Random Names Age Analysis"),                                  sidebarLayout(                                  sidebarPanel(                                  # Dropdown selection for Male/Female person                                                  selectInput(inputId =                  "sexInput",                  label =                  "Sexual practice:",                                  choices =                  c("Female"                  =                    "F",                                                  "Male"                  =                    "Thou",                                                  "Both"                  =                    "B"))                                  ),                                  mainPanel("our output will appear here")                                  )                )                                                  # Ascertain server logic                                server <-                                    role(input, output) {}                                                  # Create Shiny app                                                  shinyApp(ui =                  ui,                  server =                  server)                          

Calculation Output

Now that we've included the user input dialog, let's brand the application truly interactive by irresolute the output depending on user input. This is achieved by modifying the server logic portion of the script. Our goal is to plot an age distribution histogram in the main panel given the sex selected past the user.

Interactive Server Logic

Server logic is divers past two arguments: input and output. These objects are both list-like, so they can contain multiple other objects. We already know from the user input part of the app.R script that we have an input component chosen sexInput, which can be accessed in the reactive portion of the server logic by calling input$sexInput (find the use of the $ to access the input value associated with sexInput). In "version iii" of the application, we apply the information held in input$sexInput to subset names.df then create a histogram of names.df$Age. This histogram graphic is included as an chemical element in the output object and ultimately made bachelor in the UI portion of the script by referencing its name histogram.

Reactive portions of our app.R script's server logic are inside the server part. We create reactive outputs using Shiny's functions like renderPlot.55 Patently, renderPlot renders the contents of the function into a plot object; this is then stored in the output listing wherever we assign it (in this example, output$histogram). Notice the contents of the renderPlot function are independent non simply by regular parentheses, merely too by curly brackets (just 1 more piece of syntax to keep track of).

                                                      # app.R version three                                                                          library(shiny)                                    names.df <-                                        read.csv("https://world wide web.finley-lab.com/files/data/name_list.csv")                                                        # Define UI                                    ui <-                                        fluidPage(                                      titlePanel("Random Names Age Analysis"),                                      sidebarLayout(                                      sidebarPanel(                                      # Dropdown selection for Male person/Female                                                        selectInput(inputId =                    "sexInput",                    label =                    "Sex activity:",                                      choices =                    c("Female"                    =                      "F",                                                        "Male"                    =                      "M",                                                        "Both"                    =                      "B"))                                      ),                                      mainPanel("our output volition appear here")                                      )                  )                                                        # Define server logic                                    server <-                                        function(input, output) {                                                        output$histogram <-                                        renderPlot({                                                                          if(input$sexInput                    !=                                          "B"){                                      subset.names.df <-                                        subset(names.df, Sexual activity                    ==                                        input$sexInput)                                      }                    else                    {                                      subset.names.df <-                    names.df                                      }                                                                          ages <-                    subset.names.df$Age                                                                          # draw the histogram with the specified twenty bins                                                        hist(ages,                    col =                    'darkgray',                    border =                    'white')                                      })                                    }                                                        # Create Shiny app                                                        shinyApp(ui =                    ui,                    server =                    server)                              

Update your app.R server logic part to match the code in a higher place. Rerun the application. Annotation the appearance of the app doesn't change because we have not updated the UI portion with the resulting histogram.

Interactive User Interface

Now nosotros update the UI part of app.R to make the app interactive. In the "version 4" lawmaking below, the plotOutput("histogram") function in ui accesses the histogram component of the output listing and plots it in the main panel. Copy the code below and rerun the application. You take now created your first Shiny app!

                                                      # app.R version iv                                                                          library(shiny)                                    names.df <-                                        read.csv("https://www.finley-lab.com/files/data/name_list.csv")                                                        # Define UI                                    ui <-                                        fluidPage(                                      titlePanel("Random Names Historic period Analysis"),                                      sidebarLayout(                                      sidebarPanel(                                      # Dropdown selection for Male/Female                                                        selectInput(inputId =                    "sexInput",                    label =                    "Sex:",                                      choices =                    c("Female"                    =                      "F",                                                        "Male"                    =                      "M",                                                        "Both"                    =                      "B"))                                      ),                                      mainPanel(plotOutput("histogram"))                                      )                  )                                                        # Define server logic                                    server <-                                        function(input, output) {                                                        output$histogram <-                                        renderPlot({                                                        if(input$sexInput                    !=                                          "B"){                                      subset.names.df <-                                        subset(names.df, Sex                    ==                                        input$sexInput)                                      }                    else                    {                                      subset.names.df <-                    names.df                                      }                                                                          ages <-                    subset.names.df$Historic period                                                                          # draw the histogram with the specified twenty bins                                                        hist(ages,                    col =                    'darkgray',                    border =                    'white')                                      })                                    }                                                        # Create Shiny app                                                        shinyApp(ui =                    ui,                    server =                    server)                              

More Avant-garde Shiny App: Michigan Campgrounds

The Michigan Section of Natural Resource (DNR) has made substantial investments in open-sourcing its data via the Michigan DNR'southward open data initiative. Nosotros'll apply some of these data to motivate our next Shiny application. Specifically we'll work with the DNR State Park Campgrounds data bachelor hither as a CSV file.

                              u <-                    "https://www.finley-lab.com/files/data/Michigan_State_Park_Campgrounds.csv"                                sites <-                                    read.csv(u,                  stringsAsFactors =                  Truthful)                                  str(sites)                          
            ## 'information.frame':    151 obs. of  19 variables: ##  $ X          : num  -84.iv -84.4 -83.viii -83.8 -83.eight ... ##  $ Y          : num  42.9 42.9 42.v 42.five 42.5 ... ##  $ OBJECTID   : int  10330 10331 5986 5987 5988 5989 5990 5991 5992 5993 ... ##  $ Blazon       : Gene due west/ 1 level "Campground": ane 1 ane 1 1 1 i 1 1 1 ... ##  $ Detail_Type: Factor westward/ two levels "","Land Park": two 2 2 2 2 2 2 2 2 ii ... ##  $ DISTRICT   : Factor w/ 8 levels "Baraga","Bay City",..: viii 8 8 viii eight 8 half dozen 6 6 4 ... ##  $ COUNTY     : Factor w/ 50 levels "Alcona","Baraga",..: 10 10 27 27 27 27 47 47 47 7 ... ##  $ FACILITY   : Factor w/ 76 levels "Algonac State Park",..: 58 58 7 7 6 7 1 1 1 two ... ##  $ Camp_type  : Factor w/ 7 levels "Equestrian","Group",..: 3 v 3 5 6 half-dozen three 3 5 3 ... ##  $ TOTAL_SITE : int  203 1 144 10 25 25 219 75 2 297 ... ##  $ ADA_SITES  : int  0 0 0 0 0 0 0 iii 0 13 ... ##  $ name       : Factor w/ 114 levels "Algonac State Park  ",..: 88 88 xi 9 8 10 2 three 1 4 ... ##  $ Ownership  : Factor west/ 1 level "State": i 1 1 1 i one ane 1 1 i ... ##  $ Management : Cistron w/ 1 level "DNR": 1 i 1 i 1 i 1 ane one 1 ... ##  $ Surface    : Factor due west/ i level "Dirt": 1 1 1 i 1 ane 1 1 one 1 ... ##  $ Use_       : Factor w/ 2 levels "","Recreation": ii 2 2 two ii 2 2 2 2 2 ... ##  $ Condition  : Factor west/ two levels "Good","Unknown": 2 2 2 2 2 2 2 2 two 2 ... ##  $ Lat        : num  42.9 42.9 42.v 42.5 42.5 ... ##  $ Long       : num  -84.4 -84.4 -83.eight -83.8 -83.8 ...          

Nosotros see that Michigan has 151 state park campgrounds, and our information frame contains 19 variables. Let's create a Shiny app UI in which the user selects desired campground specifications, and the app displays the list of resulting campgrounds and their location on a map. The complete Shiny app.R is shown below. Create a new directory called CampsitesMI, and copy and paste the following code into a script file chosen app.R. Start out by running the application to come across how information technology works. Examine the example lawmaking and how information technology relates to the application that you lot are running. The following sections particular each of the app'south components.

                                                # app.R                                                  # CampsitesMI                                                                  library(shiny)                                  library(maps)                                  library(ggplot2)                                sites <-                                    read.csv(                                  "https://www.finley-lab.com/files/data/Michigan_State_Park_Campgrounds.csv",                                                  stringsAsFactors =                  True)                                ui <-                                    fluidPage(                                                  titlePanel("Michigan Campsite Search"),                                                  sidebarLayout(                                  sidebarPanel(                                  sliderInput("rangeNum",                                  label =                  "Number of campsites:",                                  min =                  0,                                  max =                  420,                                  value =                  c(0,420),                                  step=                  xx                                                  ),                                                  selectInput("blazon",                                  label =                  "Type of campsites:",                                  levels(sites$Camp_type)                                  ),                                                  checkboxInput("ada",                                  label =                  "ADA Sites Available:",                                  Fake)                                  ),                                                  mainPanel(                                  plotOutput("plot1"),                                  br(),                                  htmlOutput("text1")                                  )                                  )                )                                server <-                                    function(input, output) {                                                  output$text1 <-                                    renderText({                                                  sites1 <-                                    subset(sites,                                  TOTAL_SITE                  >=                                    input$rangeNum[1]                  &                                                                    TOTAL_SITE                  <=                                    input$rangeNum[2]                  &                                                                    Camp_type                  ==                                    input$blazon                  &                                                                                      if(input$ada){ ADA_SITES                  >                                                      0                  }                  else                  {ADA_SITES                  >=                                                      0})                                                  if(nrow(sites1)                  >                                                      0){                                  outStr <-                    "<ul>"                                                  for(site                  in                  sites1$FACILITY){                                  outStr <-                                    paste0(outStr,"<li>",site,"</li>")                                  }                                  outStr <-                                    paste0(outStr,"</ul>")                                  }                  else                  {                                  outStr <-                    ""                                                  }                                                  paste("<p>At that place are",                                  nrow(sites1),                                  "campgrounds that match your search:</p>",                                  outStr)                                                  })                                                  output$plot1 <-                                    renderPlot({                                  sites1 <-                                    subset(sites,                                  TOTAL_SITE                  >=                                    input$rangeNum[1]                  &                                                                    TOTAL_SITE                  <=                                    input$rangeNum[ii]                  &                                                                    Camp_type                  ==                                    input$type                  &                                                                                      if(input$ada){ ADA_SITES                  >                                                      0                  }                  else                  {ADA_SITES                  >=                                                      0})                                                  miMap <-                                    map_data("state",                  region =                  "michigan")                                  plt <-                                    ggplot()                  +                                                                                      geom_polygon(data=miMap,                  aes(ten=long,y=lat,group=group),                                                  colour=                  "black",                  fill=                  "gray")                  +                                                                                      coord_fixed(ratio =                  ane)                                                  if(nrow(sites1)                  >                                                      0){                                  plt <-                  plt                  +                                                      geom_point(data =                  sites1,aes(ten=Long,y=Lat),                  colour=                  "red")                                  }                                                  plot(plt)                                                  })                                }                                                  # Create Shiny app ----                                                  shinyApp(ui =                  ui,                  server =                  server)                          

Michigan Campgrounds UI

First, let'due south look at the structure of the page. Similar to our first awarding, we once again use a fluidPage layout with title panel and sidebar. The sidebar contains a sidebar panel and a primary panel. Our sidebar panel has three user input widgets:

  • sliderInput: Allows user to specify a range of campsites desired in their campground. Since the maximum number of campsites in any Michigan state park campground is 411, 420 was called equally the maximum.
  • selectInput: Allows user to select what type of campsites they desire. To go the entire listing of camp types, we used the information frame, sites, loaded at the beginning of the script.
  • checkboxInput: Allows the user to see only campgrounds with ADA sites bachelor.
Table ix.1: Interactive elements
Element ID Clarification Function to Create
input\(rangeNum </td> <td style="text-align:left;"> desired range of campsite quantity </td> <td style="text-align:left;"> sliderInput </td> </tr> <tr> <td way="text-marshal:left;"> input\)blazon desired army camp type selectInput
input\(ada </td> <td mode="text-align:left;"> desired ADA site availability </td> <td style="text-marshal:left;"> checkboxInput </td> </tr> <tr> <td style="text-align:left;"> output\)plot1 map with campground markers renderPlot
output$text1 HTML-formatted list of campgrounds renderText

Table 9.1 provides a listing of the diverse input and output elements. Take your time and track how the app defines and then uses each input and output.

Michigan Campgrounds Server Logic

In creating our server variable, we accept two functions that fill in our output elements:

  • renderText: Creates a character string of HTML code to print the bulleted list of available sites.
  • renderPlot: Creates a ggplot2 map of Michigan with campground markers.

Notation that both of these functions contain identical subsetting of the sites data frame into the smaller sites1 information frame (run across below). As y'all can see from the code, we apply the three inputs from the application to subset the information: rangeNum from the slider widget, type from the dropdown menu, and ada from the checkbox.

This repeated code can be avoided using Shiny's reactive expressions. These expressions volition update in value whenever the widget input values change. Come across here for more than information. The use of reactive expressions is beyond the scope of this chapter, but it is an important concept to be familiar with if yous plan to regularly create Shiny applications.

                                  sites1 <-                                        subset(sites,                                                        TOTAL_SITE                    >=                                        input$rangeNum[1]                    &                                                                                                                    TOTAL_SITE                    <=                                        input$rangeNum[ii]                    &                                                                                                                    Camp_type                    ==                                        input$blazon                    &                                                                                                                                        if(input$ada){ ADA_SITES                    >                                                            0                    }                    else                    {ADA_SITES                    >=                                                            0}                                      )                              

Calculation Leaflet to Shiny

Leaflet tin be easily incorporated in Shiny apps (Run across Chapter 8 of this text to acquire more most spatial data). In the Michigan Campgrounds example lawmaking, we used plotOutput and renderPlot to put a plot widget in our Shiny app. Similarly, in this code, we will use leafletOutput and renderLeaflet to add Leaflet widgets to our app. Create a new directory called CampsitesMI_Leaflet and re-create the following code into a new app.R script file.

                                                # app.R                                                  # CampsitesMI - Leaflet                                                                  library(shiny)                                  library(leaflet)                                  library(htmltools)                                sites <-                                    read.csv(                                  "https://www.finley-lab.com/files/data/Michigan_State_Park_Campgrounds.csv",                                                  stringsAsFactors =                  True)                                                  # Define UI for application that draws a histogram                                ui <-                                    fluidPage(                                                  # Awarding championship                                                  titlePanel("Michigan Campsite Search"),                                                  # Sidebar                                                  sidebarLayout(                                  sidebarPanel(                                  # slider input for number of campsites                                                  sliderInput("rangeNum",                                  label =                  "Number of campsites:",                                  min =                  0,                                  max =                  420,                                  value =                  c(0,420),                                  footstep=                  20                                                  ),                                                  selectInput("blazon",                                  label =                  "Blazon of campsites:",                                  levels(sites$Camp_type)                                  ),                                                  checkboxInput("ada",                                  characterization =                  "ADA Sites Bachelor:",                                  Faux)                                                  ),                                                  mainPanel(                                  # Bear witness the map of campgrounds                                                  leafletOutput("plot1"),                                  br(),                                  # Show the text list of campgrounds                                                  htmlOutput("text1")                                  )                                  )                )                                                server <-                                    office(input, output) {                                  sites <-                                    read.csv("Michigan_State_Park_Campgrounds.csv")                                                  output$text1 <-                                    renderText({                                  # create a subset of campsites based on inputs                                                  sites1 <-                                    subset(sites,                                  TOTAL_SITE                  >=                                    input$rangeNum[1]                  &                                                                    TOTAL_SITE                  <=                                    input$rangeNum[2]                  &                                                                    Camp_type                  ==                                    input$type                  &                                                                                      if(input$ada){ ADA_SITES                  >                                                      0                  }                  else                  {ADA_SITES                  >=                                                      0})                                  # create an HTML-formatted graphic symbol string to be output                                                  outStr <-                    "<ul>"                                                  for(site                  in                  sites1$FACILITY){                                  outStr <-                                    paste0(outStr,"<li>",site,"</li>")                                  }                                  outStr <-                                    paste0(outStr,"</ul>")                                                  #                                                  paste("<p>There are",                                  nrow(sites1),                                  "campgrounds that friction match your search:</p>",                                  outStr)                                                  })                                                  output$plot1 <-                                    renderLeaflet({                                  # create a subset of campsites based on inputs                                                  sites1 <-                                    subset(sites,                                  TOTAL_SITE                  >=                                    input$rangeNum[i]                  &                                                                    TOTAL_SITE                  <=                                    input$rangeNum[2]                  &                                                                    Camp_type                  ==                                    input$type                  &                                                                                      if(input$ada){ ADA_SITES                  >                                                      0                  }                  else                  {ADA_SITES                  >=                                                      0})                                                  if(nrow(sites1)                  >                                                      0){                                  leaflet(sites1)                  %>%                                                      addTiles()                  %>%                                                                                      addCircleMarkers(lng =                  ~Long,                  lat =                  ~Lat,                                  radius =                  5,                                  color =                  "ruddy",                                  characterization =                  mapply(function(x,y) {                                  HTML(sprintf('<em>%s</em><br>%s site(s)',                                  htmlEscape(10),                                  htmlEscape(y)))},                                  sites1$FACILITY,sites1$TOTAL_SITE,                  SIMPLIFY =                  F)                                  )                                  }                  else                  {                                  leaflet()                  %>%                                                      addTiles()                  %>%                                                                                      setView(                  -84.5555,                  42.7325,                  zoom =                  7)                                  }                                                  })                                }                                                  # Create Shiny app ----                                                  shinyApp(ui =                  ui,                  server =                  server)                          

Run the Shiny app in the CampsitesMI_Leaflet directory. What are the differences between the plot widget in this app and the plot widget in the previous app (using ggplot2)?

The code inside renderLeaflet is displayed again below. As a reminder from our previous employ of Leaflet, we can use the magrittr package's pipe operator, %>%, to add properties to our Leaflet plot. In the addCircleMarkers part, y'all can come across that we used the mapply role to use HTML lawmaking to each of the markers. The HTML code just prints the site proper noun and number of sites within each marking label. The else statement serves the purpsose that if our subset, sites1, is empty, we render a map centered on Lansing, MI.

}

                                                # renderLeaflet function from app.R                                                output$plot1 <-                                    renderLeaflet({                                  # create a subset of campsites based on inputs                                                  sites1 <-                                    subset(sites,                                                  TOTAL_SITE                  >=                                    input$rangeNum[ane]                  &                                                                                                        TOTAL_SITE                  <=                                    input$rangeNum[2]                  &                                                                                                        Camp_type                  ==                                    input$type                  &                                                                                                                          if(input$ada){ ADA_SITES                  >                                                      0                  }                  else                  {ADA_SITES                  >=                                                      0})                                                                  if(nrow(sites1)                  >                                                      0){                                  leaflet(sites1)                  %>%                                                      addTiles()                  %>%                                                                                                                          addCircleMarkers(lng =                  ~Long,                  lat =                  ~Lat,                                                  radius =                  five,                                  color =                  "red",                                  label =                  mapply(part(x,y) {                                  HTML(sprintf('<em>%due south</em><br>%s site(s)',                                                  htmlEscape(ten),                                                  htmlEscape(y)))},                                                  sites1$FACILITY, sites1$TOTAL_SITE                                  )                                  )                                  }                  else                  {                                  leaflet()                  %>%                                                      addTiles()                  %>%                                                                                      setView(                  -84.5555,                  42.7325,                  zoom =                  7)                                  }                                                                  })                          

Why use Shiny?

In this chapter, we learned what a Shiny app is, what information technology's components are, how to run the app, and how to incorporate Leaflet plots. We can as well host these apps online on our ain server or the shinyapps.io server, which can exist accessed directly from RStudio. Hosting our apps on a server allows anyone with cyberspace access to collaborate with our widgets.

The shinyapps.io server allows complimentary hosting of apps within the monthly limits of v applications and 25 agile hours. Paid plans are likewise available. A user guide to deploying your application on shinyapps.io is available here. Set upwards a complimentary account and impress your friends by sending them links to your Shiny apps! In addition to hosting your applications, here are a few more things to notice with Shiny:

  • Swoop deeper into development by working through the RStudio Shiny tutorial series.
  • Contain CSS stylesheets to brand your apps fancier.
  • Explore the unmarried-file Shiny app structure.

Exercises

Exercise Shiny Learning objectives: practice updating ggplot2 plot aesthetics; modify Shiny HTML output; add an interactive Shiny chemical element.