Preamble

library(shiny)
library(ggplot2)

Having covered some of the basics for both the user interface and backend server logic, we will now spend a little bit of time making our apps look fly. This lab will be primarily oriented around the layout of the UI with minimal server logic, but understand that everything from the preceding labs is also applicable here.

HTML

The first thing to understand about the Shiny user interface is that, under the hood, it’s nothing but HTML. Accordingly, we can add our own HTML right into the UI. Rather than using HTML directly, though, Shiny provides a list of wrapper functions that create it for us. Below we create a standard UI object using fluidPage() and add two headers and a paragraph. When we use print() to examine it, we see the raw HTML code. Interestingly enough, when we display it without print, R Markdown interprets it as real HTML and will create headers and paragraph objects accordingly. Here, h3() corresponds to a level 3 header which we typically make with ###. Note on the table of contents sidebar that these headers register as being part of our document:

# UI object created with fluidPage returns HTML code
ui <- fluidPage(
  h3("This is a header"), 
  p(style="color:teal;font-size:10px;text-align:center;", "Hey there")
)

# Using print() gives me R output so I can see what it looks like
print(ui)
## <div class="container-fluid">
##   <h3>This is a header</h3>
##   <p style="color:teal;font-size:10px;text-align:center;">Hey there</p>
## </div>
# Without print(), it's raw HTML so R Markdown renders it as such
ui

This is a header

Hey there

Fortunately for us, most of the UI functions we will be using are at a much higher level, requiring basically no knowledge of HTML. It is something worth being aware of, though, in case you ever want to slip in a little of your own design into the Shiny app.

Fun fact: R Studio is also written in HTML. You can right-click anywhere and click “Inspect Element” to verify yourself.

Lab

This lab will focus on some of the more basic layouts for Shiny applications. There is a lot of informatoin freely available on the web for more intricate designs, including the layout guide provided on the main Shiny website.

Column layout

The sidebar layout offered above is really just a fancy implementation of the grid system used by Shiny to organize the user interface. In discussing the grid system, it will be helpful to think of rows and columns. The function fluidRow() creates a row of which we can have any number – theoretically, rows could continue infinitely down the page. Within each row, however, we must specify columns. Within each row, there are 12 spaces available for columns, and we create columns of however many spaces with the function column(width, ...) where width is the number of spaces we wish a column to occupy.

In this example, we create a single fluid row with two column entries: the first is of width 4 and contains our input boxes while the second is of width 8, containing our plot. We see that this is nearly identical to the layout we had with sidebarLayout().

ui <- fluidPage(
  
  titlePanel("Columns and Rows"), 
  
  fluidRow(
    column(4,
           numericInput("n", label = "Number of obs:", value = 100),
           sliderInput("bins", label = "Number of bins:", min = 2, max = 20, value = 10)),
    column(8, 
           plotOutput("myhist"))
  )
)

server <- function(input, output) {
  
  n <- reactive(input$n)
  bins <- reactive(input$bins)
  
  output$myhist <- renderPlot({
    X <- rnorm(n())
    ggplot() + geom_histogram(aes(x = X), bins = bins())
  })
}

shinyApp(ui, server)

In fact, it is exactly the sidebar layout if we add the wellPanel() around our inputs (wellPanel() creates a CSS class around the inputs adding an inset border and gray background)

ui <- fluidPage(
  
  titlePanel("Columns and Rows"), 
  
  fluidRow(
    column(4,
           wellPanel( # added a well panel (thank goodness for Ctrl+I!)
             numericInput("n", label = "Number of obs:", value = 100),
             sliderInput("bins", label = "Number of bins:", min = 2, max = 20, value = 10)
           )
    ),
    column(8, 
           plotOutput("myhist"))
  )
)

server <- function(input, output) {
  
  n <- reactive(input$n)
  bins <- reactive(input$bins)
  
  output$myhist <- renderPlot({
    X <- rnorm(n())
    ggplot() + geom_histogram(aes(x = X), bins = bins())
  })
}

shinyApp(ui, server)

Question 2: Using fluidRow() (perhaps multiple times), add the necessary code to reproduce the Shiny app below (don’t worry too much about setting the values of sliders or inputs)

# Function for plot
freqpoly <- function(x1, x2, binwidth = 0.1, xlim = c(-3, 3)) {
  df <- data.frame(
    x = c(x1, x2),
    g = c(rep("x1", length(x1)), rep("x2", length(x2)))
  )

  ggplot(df, aes(x, colour = g)) +
    geom_freqpoly(binwidth = binwidth, linewidth = 1) +
    coord_cartesian(xlim = xlim)
}

ui <- fluidPage(
  # code here
)

server <- function(input, output) {
  output$myplot <- renderPlot({
    x1 <- rpois(input$n1, input$lambda1)
    x2 <- rpois(input$n2, input$lambda2)
    freqpoly(x1, x2, binwidth = input$binwidth, xlim = c(-1, 10))
  }, res = 96)
}

shinyApp(ui, server)

Tabs

Often a convenient way to partition different types of data is with the use of tabs created with tabsetPanel(). This pairs particularly well with the use of sidebars

ui <- fluidPage(
  titlePanel("Tabsets"), 
  sidebarLayout(
    sidebarPanel(
      numericInput("n", label = "Number of obs:", value = 100),
      sliderInput("bins", label = "Number of bins:", min = 2, max = 20, value = 10)
    ), 
    
    mainPanel(
      # Create tabset with tabesetPanel()
      tabsetPanel(
        # Then each panel gets it own tabPanel
        tabPanel("Tab 1 - Plot", 
                 plotOutput("myhist")
                 ), 
        tabPanel("Tab 2 - Summary", 
                 textOutput("summary"))
      )
    )
  )
)

server <- function(input, output) {
  
  X <- reactive(rnorm(input$n))
  bins <- reactive(input$bins)
  
  output$summary <- renderText({
    paste0("The mean of X is: ", round(mean(X()), 4), 
           " and the SD is :", round(sd(X()), 4))
  })
  
  output$myhist <- renderPlot({
    ggplot() + geom_histogram(aes(x = X()), bins = bins())
  })
}

shinyApp(ui, server)

Of course, we don’t have to use sidebar, and the tabset is not limited to just being output – you are free to make your Shiny app as complicated as you would like.

ui <- fluidPage(
  
  titlePanel("Columns and Rows"), 
  
  fluidRow(
    column(4,
           tabsetPanel(
             tabPanel("Choose N: ", 
                      wellPanel(
                        numericInput("n", label = "Number of obs:", value = 100))), 
             tabPanel("Choose Bins:", 
                      wellPanel(
                        sliderInput("bins", label = "Number of bins:", min = 2, max = 20, value = 10)
                      )))),
    column(8, plotOutput("myhist"))),
  fluidRow(column(12, textOutput("summary")))
)


server <- function(input, output) {
  
  X <- reactive(rnorm(input$n))
  bins <- reactive(input$bins)
  
  output$summary <- renderText({
    paste0("The mean of X is: ", round(mean(X()), 4), 
           " and the SD is :", round(sd(X()), 4))
  })
  
  output$myhist <- renderPlot({
    ggplot() + geom_histogram(aes(x = X()), bins = bins())
  })
}


shinyApp(ui, server)

The only important thing is that you have fun.

Miscellaneous

Here are a few odds and ends that are worth knowing but are not mission critical

Simplifying UI

As we mentioned at the very beginning of this lab, all of the objects returned by the UI (and consequently, each of the functions used in the UI) return some form of HTML. This means that it’s not strictly necessary that all of your UI code is written in the UI function. Consider the following

# Define my sidebar outside of the UI
mySidebar <- sidebarLayout(
  sidebarPanel(
    numericInput("n", label = "Number of obs:", value = 100),
    sliderInput("bins", label = "Number of bins:", min = 2, max = 20, value = 10)
  ), 
  mainPanel(
    plotOutput("myhist")
  )
)

ui <- fluidPage(
  # This is defined elsewhere
  mySidebar
)

server <- function(input, output) {
  output$myhist <- renderPlot({
    ggplot() + geom_histogram(aes(x = rnorm(input$n)), bins = input$bins)
  })
}

shinyApp(ui, server)

I don’t immediately recommend doing this with your code as it can be increasingly more difficult to identify where things are located, to troubleshoot, or make modifications as necessary. However, if you have an app that uses several different tabsets or the navigation pages, it could be helpful to include relevant code together. A common way this is done involves creating R script files for each tab containing all of the relevant code and then using source() to read it in at the beginning. For example, if we had written code for navbar pages 1 and 2 and saved them in navbar1.R and navbar2.R, respectively, our resulting app may look like this:

# This won't work so don't try and run it.

# This reads in any R code from these files and evaluates it
source("navbar1.R")
source("navbar2.R")

ui <- navbarPage("My Application",
  tabPanel("Component 1", navbar1_object),
  tabPanel("Component 2", navbar2_object),
)
server <- function(input, output) {
  # backend code
}
shinyApp(ui, server)