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.
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
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.
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.
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)
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.
Here are a few odds and ends that are worth knowing but are not mission critical
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)