Arranging Layouts

Author

Will Gammerdinger

Learning Objectives

In this lesson, you will: - Implement sidebars into your Shiny app - Create tabsets - Implement different shinythemes

Introduction

Until now most of our work has focused on server side implementations of concepts. We have discussed the UI components required to carry the tasks that we want, but we haven’t discussed how we want to arrange them. This section will almost exclusively focus on the UI and how to arrange and organize the components of the UI into a visually appealing app.

Adding a title

You may want your app to have a title. You can add this with the titlePanel() function. The syntax for doing this looks like:

titlePanel("<title_of_your_app>")

The default behavior of titlePanel() is to left-align the title. If you would like center your title you will need to use an h1() function within your titlePanel() function. The h1() function is borrowed from HTML nonmenclature and refers to the largest sized header. The smallest sized header in HTML is h6() and the values in between h1() and h6() (h2(), h3(), h4(), h5()) span the range of sizes between h1() and h6(). Within the h[1-6]() family of function, there is an align argument that accepts the ("left", "center", and "right") for the alignment. So a center aligned title could looke like:

titlePanel(
  h1("<title_of_your_app>", align = "center")
)

We can add this title to our previous app:

library(shiny)
library(ggplot2)

ui <- fluidPage(
  titlePanel(
    h1("My iris Shiny App", align = "center")
  ),
  sidebarLayout(
    sidebarPanel(
      selectInput("y_axis_input", "Select y-axis", choices = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width"))
    ),
    mainPanel(
      plotOutput("plot")
    )
  )
)

server <- function(input, output) {
  output$plot <- renderPlot({
    ggplot(iris) +
      geom_point(aes(x = Species, y = .data[[input$y_axis_input]]))
  })
}

shinyApp(ui = ui, server = server)

This app would now look like:

Creating columns

We have shown how to use the sidebarLayout() function, but we may want to have multiple columns in our data and the sidebarLayout() function can’t help to much with that. Fortunately, there is the fluidRow() function that allows us to divide up the row into columns using the column() function nested within it. The first argument within the column() function defines the width of the column and the sum of all of the widths of a column for a given fluidRow() function should sum to 12. If the width value in fluidRow() is left undefined then it defaults to 12. An example of this syntax would be:

fluidRow(
  column(<width_of_first_column>,
    <objects_in_first_column>
  ),
  column(<width_of_second_column>,
    <objects_in_second_column>
  )

We could make equally sized columns in our app like:

library(shiny)
library(ggplot2)

ui <- fluidPage(
  titlePanel(
    h1("My iris Shiny App", align = "center")
  ),
  fluidRow(
    column(6,
      h3("First column"),
      selectInput("y_axis_input", "Select y-axis", choices = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width"))
    ),
    column(6,
      h3("Second column"),
      plotOutput("plot")
    )
  )
)

server <- function(input, output) {
  output$plot <- renderPlot({
    ggplot(iris) +
      geom_point(aes(x = Species, y = .data[[input$y_axis_input]]))
  })
}

shinyApp(ui = ui, server = server)

This app would look like:

Nesting columns

You can also nest fluidRow() functions within the column() function of another fluidRow() function. Once again, the important rule when using fluidRow() is that the sum of the column()’s width value within each fluidRow() sums to 12. Let’s take a look at some example syntax of how this would look:

  fluidRow(
    column(<width_of_first_main_column>,
      fluidRow(
        column(<width_of_first_subcolumn_of_the_first_main_column>,
          <objects_in_first_column_first_subcolumn>
        ),
        column(<width_of_second_subcolumn_of_the_first_main_column>,
          <objects_in_first_column_second_subcolumn>
        )
      )
    ),
    column(<width_of_second_main_column>,
      <objects_in_second_column>
    )
  )
)

We can apply these principles to our app:

library(shiny)
library(ggplot2)

ui <- fluidPage(
  titlePanel(
    h1("My iris Shiny App", align = "center")
  ),
  fluidRow(
    column(7,
      fluidRow(
        column(6,
        h3("First column: First subcolumn"),
        selectInput("x_axis_input", "Select x-axis", choices = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width"))
        ),
        column(6,
        h3("First column: Second subcolumn"),
        selectInput("y_axis_input", "Select y-axis", choices = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width"))
        )
      )
    ),
    column(5,
      h3("Second column"),
      plotOutput("plot")
    )
  )
)

server <- function(input, output) {
  output$plot <- renderPlot({
    ggplot(iris) +
      geom_point(aes(x = .data[[input$x_axis_input]], y = .data[[input$y_axis_input]]))
  })
}

shinyApp(ui = ui, server = server)

This app would look like:

Multiple Rows

Now that we’ve seen how to make multiple columns, let’s breifly talk about multiple rows. Instead of nesting fluidRow() function within one another, we will treat them like we’ve treated objects that go underneath one another, by putting complete fluidRow() functions underneath each other in out code. An example of this syntax can be seen below:

fluidRow(
  column(<width_of_first_column_in_the_first_row>,
    <objects_in_first_column_first_row>
  ),
  column(<width_of_second_column_in_the_first_row>,
    <objects_in_second_column_first_row>
  )
),
fluidRow(
  column(<width_of_first_column_in_the_second_row>,
    <objects_in_first_column_second_row>
  ),
  column(<width_of_second_column_in_the_second_row>,
    <objects_in_second_column_second_row>
  )
)

We can apply the concept of multiple rows to our app:

library(shiny)
library(ggplot2)

ui <- fluidPage(
  titlePanel(
    h1("My iris Shiny App", align = "center")
  ),
  fluidRow(
    column(6,
      h3("First Row: First column"),
      selectInput("x_axis_input", "Select x-axis", choices = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width"))
    ),
      column(6,
      h3("First Row: Second column"),
      selectInput("y_axis_input", "Select y-axis", choices = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width"))
    )
  ),
  fluidRow(
      h3("Second Row"),
      plotOutput("plot")
  )
)

server <- function(input, output) {
  output$plot <- renderPlot({
    ggplot(iris) +
      geom_point(aes(x = .data[[input$x_axis_input]], y = .data[[input$y_axis_input]]))
  })
}

shinyApp(ui = ui, server = server)

This app would look like:

Thematic Breaks

Thematic breaks are a great way to break-up different parts of an app. The function to add a themetic break is also borrowed from HTML and it is called hr(). The syntax for using it is very simple:

hr()

In the previous example, if we wanted to break up the selectInput() functions from the plotOutput() function, then we could add a thematic break like:

library(shiny)
library(ggplot2)

ui <- fluidPage(
  titlePanel(
    h1("My iris Shiny App", align = "center")
  ),
  fluidRow(
    column(6,
           h3("First Row: First column"),
           selectInput("x_axis_input", "Select x-axis", choices = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width"))
    ),
    column(6,
           h3("First Row: Second column"),
           selectInput("y_axis_input", "Select y-axis", choices = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width"))
    )
  ),
  hr(),
  fluidRow(
    h3("Second Row"),
    plotOutput("plot")
  )
)

server <- function(input, output) {
  output$plot <- renderPlot({
    ggplot(iris) +
      geom_point(aes(x = .data[[input$x_axis_input]], y = .data[[input$y_axis_input]]))
  })
}

shinyApp(ui = ui, server = server)

This app would now look like:

Creating tabs without a navigation bar

An app might require that the user can click on various tabs on a single page. While this could be accomplished with the navigation bar, this can be accomplished by providing different tabs that can easily be embedded within parts of an app using tabsetPanel(). The syntax looks like:

tabsetPanel(
  tabPanel("<title_of_tab_1>",
    <content_of_tab_1>
  ),
  tabPanel("<title_of_tab_2>",
    <content_of_tab_2>
  ),
)

We will embed this within mainPanel() function below, but it doesn’t necessarily need to be that way:

library(shiny)
library(ggplot2)
library(DT)

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      selectInput("x_axis_input", "Select x-axis", choices = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width")),
      selectInput("y_axis_input", "Select y-axis", choices = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width"))
    ),
    mainPanel(
      tabsetPanel(
        tabPanel("Plot", 
          plotOutput("plot")
        ),
        tabPanel("Table",
          DTOutput("table")
        )
      )
    )
  )
)

server <- function(input, output) {
  output$plot <- renderPlot({
    ggplot(iris) +
      geom_point(aes(x = .data[[input$x_axis_input]], y = .data[[input$y_axis_input]]))
  })
  output$table <- renderDT({
    iris[,c(input$x_axis_input, input$y_axis_input), drop = FALSE]
  })
}

shinyApp(ui = ui, server = server)

This app would look like:

Note: navbarPage() can also be embedded within mainPanel(). However, traditionally you will see navbarPage() be used to as the banner at the top of the app while tabsetPanel() will be used as different tabs within a single tab of navbarPage().

Shiny Themes

Until now we have used the default UI color scheme within Shiny. However, that is not the only option. There is a package of preset themes that app developers can choose from to make their app more visually appealing. This package is called shinythemes and it has over a {dozen different themes](https://rstudio.github.io/shinythemes/) that app developers can choose from. Implementing a theme is straightforward once you’ve loaded the shinythemes package:

ui <- fluidPage(theme = shinytheme("<name_of_shiny_theme>")
  <rest_of_the_ui>
)

We can add this to the previous app we made:

library(shiny)
library(ggplot2)
library(DT)
library(shinythemes)

ui <- fluidPage(theme = shinytheme("cerulean"),
  navbarPage("My iris dataset",
    tabPanel("Inputs",
      selectInput("x_axis_input", "Select x-axis", choices = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width")),
      selectInput("y_axis_input", "Select y-axis", choices = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width"))
    ),
    navbarMenu("Outputs",
      tabPanel("Plot",
        plotOutput("plot")
      ),
      tabPanel("Table",
        DTOutput("table")   
      )
    )
  )
)

server <- function(input, output) {
  output$plot <- renderPlot({
    ggplot(iris) +
      geom_point(aes(x = .data[[input$x_axis_input]], y = .data[[input$y_axis_input]]))
  })
  output$table <- renderDT({
    iris[,c(input$x_axis_input, input$y_axis_input), drop = FALSE]
  })
}

shinyApp(ui = ui, server = server)

This app would look like:

If you wanted to see how various themes might look on your specific app, instead of in the gallery provided by shinythemes, then you can apply them from a selection menu by adding this code to your app:

ui <- fluidPage(themeSelector(),
  <rest_of_the_ui>
)

Next Lesson >>

Back to Schedule


This lesson has been developed by members of the teaching team at the Harvard Chan Bioinformatics Core (HBC). These are open access materials distributed under the terms of the Creative Commons Attribution license (CC BY 4.0), which permits unrestricted use, distribution, and reproduction in any medium, provided the original author and source are credited.