Getting started

In this tutorial we’ll be building a simple Shiny app as we explore how we can use the Leaflet package to create interactive geo-spatial visualizations. So far, we’ve seen one way to create a shiny app: create a ui.r and server.r files in one directory. This is how you should organize your code for your larger projects. But Shiny offers other options for defining an app. In this tutorial I’ll be using the simpler “single-file” option, where we’ll save what would have been the contents of a server.r and ui.r file in variables and then pass those values directly into the shinyApp function ourselves.

For example:

library(shiny)

server <- function(input, output) {
  output$message <- renderText({ "Hello world!" })
}

ui <- fluidPage(
  sidebarLayout( sidebarPanel( p("Nothing to see here; move along.") )
               , mainPanel( textOutput('message') )
               )
)

shinyApp(ui = ui, server = server)

To follow along today you can just create a new file in your biol297 project called app.R. Once you enter code as above, RStudio will recognize it as a single-file shiny app and you’ll have a little “Run App” button on the tool bar. In my code examples below, I’m just going to show the parts of the app that have changed at each step.

To keep the exercise focused on the tools today, we’ll be working with a simple data set that contains state-by-state information about energy usage in the US provided by the Energy Information Administration. Call bio297::fetch to get state-energy-data.csv. It’s formatted to load with read.csv; take a quick look at the table now.

The variables are:

Roughing in the structure of the app

Each of your designs for your project apps have multiple elements and are fairly sophisticated. To keep yourself organized when implementing an app, I find that it’s a good practice to start with a rough prototype implementation of the UI. Once you’ve got a good feel for all of the inputs and outputs your app is going to use, it’s pretty straight forward to go back and add in the server and fancier UI code that you’ll need.

For this tutorial app our goals are:

The first thing we’ll do to get started on this project is create templates for our UI and server code; for now I’m just going to provide “to do” notes for myself using comments:

library(shiny)
library(leaflet)

server <- function(input, output) {
  
  # Do stuff.
  
}

ui <- fluidPage(
  
  # Page title
  
  # Link to source data
  
  # Sidebar with controls
  
  # Leaflet map
  
)

shinyApp(ui = ui, server = server)

Now let’s implement the UI. See the Shiny help pages for the titlePanel, helpText, and sidebarLayout functions. The a tag creates an HTML link.

ui <- fluidPage(
  
  # Page title
  titlePanel("US State Energy Profiles"),
  
  # Link to source data
  helpText( "Source data provided by the "
          , a("US Energy Information Administration", href = "http://www.eia.gov/state/")
          , "."
          ),
  
                 # Sidebar with controls
  sidebarLayout( sidebarPanel( selectInput( inputId = 'variable' 
                                          , label   = "Choose a variable"
                                          , choices = list()  # place holder!
                                          )
                             )
                 
                 # Leaflet map
               , mainPanel( leafletOutput( outputId = 'map') )
               )

)

shinyApp(ui = ui, server = server)

As you can see, we’ve named our single input variable variable and output variable map. For now we’ve left the set of choices for the dropdown blank. We could have hard coded the variable names, but we’ll do something a little more clever!

Source maps

For anyone planning to do a geo-spatial visualization, I’d strongly recommend spending some time reading through all of the documentation for the R Leaflet package. There are many possible sources of map data available. For this example, we’ll use a map of the US states from the maps package.

Using this package is fairly simple: you use the map function and designate which coordinate database to load. For example:

states <- map("state", fill = TRUE, plot = FALSE)

As is nearly always the case, we’re going to run into a data clean up problem! Let’s look at how states are named on the map:

states$names
##  [1] "alabama"                         "arizona"                        
##  [3] "arkansas"                        "california"                     
##  [5] "colorado"                        "connecticut"                    
##  [7] "delaware"                        "district of columbia"           
##  [9] "florida"                         "georgia"                        
## [11] "idaho"                           "illinois"                       
## [13] "indiana"                         "iowa"                           
## [15] "kansas"                          "kentucky"                       
## [17] "louisiana"                       "maine"                          
## [19] "maryland"                        "massachusetts:martha's vineyard"
## [21] "massachusetts:main"              "massachusetts:nantucket"        
## [23] "michigan:north"                  "michigan:south"                 
## [25] "minnesota"                       "mississippi"                    
## [27] "missouri"                        "montana"                        
## [29] "nebraska"                        "nevada"                         
## [31] "new hampshire"                   "new jersey"                     
## [33] "new mexico"                      "new york:manhattan"             
## [35] "new york:main"                   "new york:staten island"         
## [37] "new york:long island"            "north carolina:knotts"          
## [39] "north carolina:main"             "north carolina:spit"            
## [41] "north dakota"                    "ohio"                           
## [43] "oklahoma"                        "oregon"                         
## [45] "pennsylvania"                    "rhode island"                   
## [47] "south carolina"                  "south dakota"                   
## [49] "tennessee"                       "texas"                          
## [51] "utah"                            "vermont"                        
## [53] "virginia:chesapeake"             "virginia:chincoteague"          
## [55] "virginia:main"                   "washington:san juan island"     
## [57] "washington:lopez island"         "washington:orcas island"        
## [59] "washington:whidbey island"       "washington:main"                
## [61] "west virginia"                   "wisconsin"                      
## [63] "wyoming"

Inconveniently, the names aren’t the two letter abbreviations we have in our source data file. More over, the US census defines subregions for certain states. Fortunately, the maps package includes the table we need to transform our source table:

head(state.fips)
##   fips ssa region division abb    polyname
## 1    1   1      3        6  AL     alabama
## 2    4   3      4        8  AZ     arizona
## 3    5   4      3        7  AR    arkansas
## 4    6   5      4        9  CA  california
## 5    8   6      4        8  CO    colorado
## 6    9   7      1        1  CT connecticut

The abb vector gives us what we need to format our data table to match up with the set of states in the map. You’re all hopefully familiar with indexing on named rows by now! Make sure you understand why it’s important to convert the factor vector into a character vector (see what happens if you don’t).

energySource <- read.csv("data/state-energy-data.csv")
row.names(energySource) <- energySource$State

energy <- energySource[as.character(state.fips$abb), -1]

Global application variables

In a shiny app, every user that connect to the application gets their own instance of a server. Right now you are your only user, but when if host your applications on the web, you can have multiple users at once. So it’s a good idea to put any work that your application will always have to do outside of your server function. In our current app, it will always need to load the source data from the csv file, so let’s do that at the top of our script:

energySource <- read.csv("data/state-energy-data.csv")
row.names(energySource) <- energySource$State
energy <- energySource[as.character(state.fips$abb), -1]

states  <- map("state", fill = TRUE, plot = FALSE)

server <- function(input, output) {
  
  # Do stuff.
  
}

Now we can easily update our UI code to use the column names found on the table to dynamically create the input choices for the user (we want everything but the first column):

ui <- fluidPage(
  
  # Page title
  titlePanel("US State Energy Profiles"),
  
  # Link to source data
  helpText( "Source data provided by the "
          , a("US Energy Information Administration", href = "http://www.eia.gov/state/")
          , "."
          ),
  
                 # Sidebar with controls
  sidebarLayout( sidebarPanel( selectInput( inputId = 'variable' 
                                          , label   = "Choose a variable"
                                          , choices = colnames(energy)
                                          )
                             )
                 
                 # Leaflet map
               , mainPanel( leafletOutput( outputId = 'map') )
               )

)

shinyApp(ui = ui, server = server)

Basic server implementation

Now we’re ready to implement our server! It only needs to do one thing: provide an output for the map: in this case we’ll use the leaflet renderLeaflet rendering function. Let’s put in a static map from the examples as a placeholder:

server <- function(input, output) {
  
  output$map <- renderLeaflet({
    
    leaflet(data = states) %>% addTiles() %>%
      addPolygons(fillColor = topo.colors(10, alpha = NULL), stroke = FALSE)
    
  })
  
}

shinyApp(ui = ui, server = server)

Now we can use the colorNumeric function to create a color map to for one of our variables:

server <- function(input, output) {
  
  output$map <- renderLeaflet({
    
    pal <- colorNumeric(palette = "Blues", domain = energy$production.pct)
    
    leaflet(data = states) %>% addTiles() %>%
      addPolygons(fillColor = pal(energy$production.pct), stroke = FALSE)
    
  })
  
}

shinyApp(ui = ui, server = server)

Now all we need to do is add an input binding! Try it!