Shiny Dashboards
Theming

Colin Rundel

Shiny & bootstrap

The interface provided by Shiny is based on the html elements, styling, and javascript provided by the Bootstrap library.

As we’ve seen so far, knowing the specifics of Bootstrap are not needed for working with Shiny - but understanding some of its conventions goes a long way to helping you customize the elements of your app (via custom CSS and other components).

This is not the only place that Bootstrap shows up in the R ecosystem - e.g. both RMarkdown and Quarto html documents use Bootstrap for styling as well.

shinydashboard limitations

The html interface elements in this package are based on a specific version of a style template called AdminLTE (v2) which is in turn based on Bootstrap (v3). Because of this additional template layer (and its bootstrap version dependency) we don’t have the flexibility to leverage bootstrap based theming using bslib.

Therefore theming options are limited to

  • Using one of the builtin color themes via the skin argument to dashboardPage()

  • Including custom CSS

  • Make use of the fresh package

Demo 08 - shinydashboard skins

demos/demo08.R

# Based on layout-mixed sample app
# https://github.com/rstudio/shinydashboard/blob/gh-pages/_apps/layout-mixed/app.R

library(shinydashboard)

shinyApp(
  ui = dashboardPage(
    skin = "blue",  # blue, black, purple, green, red, yellow
    dashboardHeader(title = "Mixed layout"),
    dashboardSidebar(disable = TRUE),
    dashboardBody(
      fluidRow(
        box(
          title = "Box title",
          status = "primary",
          plotOutput("plot1", height = 240)
        ),
        box(
          status = "warning",
          plotOutput("plot2", height = 240)
        )
      ),
      
      fluidRow(
        column(
          width = 4,
          box(
            title = "Title 1", solidHeader = TRUE, status = "primary",
            width = NULL,
            sliderInput("orders", "Orders", min = 1, max = 500, value = 120),
            radioButtons("fill", "Fill", inline = TRUE,
                         c(None = "none", Blue = "blue", Black = "black", red = "red")
            )
          ),
          box(
            width = NULL,
            background = "black",
            "A box with a solid black background"
          )
        ),
        column(
          width = 4,
          box(
            title = "Title 2",
            solidHeader = TRUE,
            width = NULL,
            p("Box content here")
          ),
          box(
            title = "Title 5",
            width = NULL,
            background = "light-blue",
            "A box with a solid light-blue background"
          )
        ),
        column(width = 4,
               box(
                 title = "Title 3",
                 solidHeader = TRUE, status = "warning",
                 width = NULL,
                 selectInput("spread", "Spread",
                             choices = c("0%" = 0, "20%" = 20, "40%" = 40, "60%" = 60, "80%" = 80, "100%" = 100),
                             selected = "60"
                 )
               ),
               box(
                 title = "Title 6",
                 width = NULL,
                 background = "maroon",
                 "A box with a solid maroon background"
               )
        )
      )
    )
  ), 
  server = function(input, output) {
    
    set.seed(122)
    histdata <- rnorm(500)
    
    output$plot1 <- renderPlot({
      if (is.null(input$orders) || is.null(input$fill))
        return()
      
      data <- histdata[seq(1, input$orders)]
      color <- input$fill
      if (color == "none")
        color <- NULL
      hist(data, col = color)
    })
    
    output$plot2 <- renderPlot({
      spread <- as.numeric(input$spread) / 100
      x <- rnorm(1000)
      y <- x + rnorm(1000) * spread
      plot(x, y, pch = ".", col = "blue")
    })
  }
)

bslib

The bslib R package provides a modern UI toolkit for Shiny and R Markdown based on Bootstrap. It facilitates:

  • Custom theming of Shiny apps and R Markdown documents.
    • Apps can even be themed interactively in real-time.
  • Use of modern versions of Bootstrap and Bootswatch
    • Shiny and R Markdown currently default to Bootstrap 3 and may continue to do so to maintain backwards compatibility.
  • Creation of delightful and customizable Shiny dashboards (next session!)

Bootswatch

Due to the ubiquity of Bootstrap a large amount of community effort has gone into developing custom themes - a large free collection of these are available at bootswatch.com/.

Demo 09 - Real-time theming

demos/demo09.Rmd

---
title: "Demo 09 - Dynamic theming"
output: 
  flexdashboard::flex_dashboard:
    theme: 
      version: 4
runtime: shiny
---

```{r global}
library(tidyverse)
library(flexdashboard)
ggplot2::theme_set(ggplot2::theme_bw())

d = readr::read_csv(here::here("data/weather.csv"))
```

```{r}
bslib::bs_themer()
```

```{r}
d_vars = d |>
  select(where(is.numeric)) |>
  names()

d_city = reactive({
  d |>
    filter(city %in% input$city)
})
```

Col {data-width=800}
-------------------------------------

###

```{r}
selectInput(
  "city", "Select a city",
  choices = c("Chicago", "Durham", "Sedona", "New York", "Los Angeles")
)

renderPlot({
  d_city() |>
    ggplot(aes(x=time, y=temp)) +
    geom_line()
})
```

Col {data-width=200}
-------------------------------------

### Min temperature 

```{r}
renderGauge({
  gauge(
    min(d_city()$temp),
    min = 0, max=120, symbol = "°F",
    gaugeSectors(success=c(60,90), warning=c(0,50), danger=c(90,120))
  )
})
```

### Max temperature

```{r}
renderGauge({
  gauge(
    max(d_city()$temp),
    min = 0, max=120, symbol = "°F",
    gaugeSectors(success=c(60,90), warning=c(0,50), danger=c(90,120))
  )
})
```

### Avg temperature

```{r}
renderValueBox({
  avg = mean(d_city()$temp) |> round(1)
  valueBox(
    avg,
    caption = "Avg temp",
    icon = "fa-thermometer-half",
    color = case_when(
      avg >= 0 & avg < 50 ~ "warning",
      avg >=50 & avg < 90 ~ "success",
      avg >=90 & avg < 120 ~ "danger"
    )
  )
})
```

bs_theme()

Provides a high level interface to adjusting the theme for an entire Shiny app,

  • Change bootstrap version via version argument

  • Pick a bootswatch theme via bootswatch argument

  • Adjust basic color palette (bg, fg, primary, secondary, etc.)

  • Adjust fonts (base_font, code_font, heading_font, font_scale)

  • and more

The object returned by bs_theme() can be passed to the theme argument of fluidPage() and similar page UI elements.

For flexdashboard the arguments are passed in via the front matter under theme:.

thematic

Simplified theming of ggplot2, lattice, and {base} R graphics. In addition to providing a centralized approach to styling R graphics, thematic also enables automatic styling of R plots in Shiny, R Markdown, and RStudio.

In the case of our flexdashboard (or other shiny app), all we need to do is to include a call to thematic_shiny() before the app is loaded.

  • Using the value "auto" will attempt to resolve the bg, fg, accent, or font values at plot time.

Demo 10 - thematic

demos/demo10.Rmd

---
title: "Demo 10 - thematic"
output: 
  flexdashboard::flex_dashboard:
    theme: 
      version: 4
runtime: shiny
---

```{r global}
library(tidyverse)
library(flexdashboard)
ggplot2::theme_set(ggplot2::theme_bw())

d = readr::read_csv(here::here("data/weather.csv"))
```

```{r}
bslib::bs_themer()
thematic::thematic_shiny(bg = "auto", fg = "auto", font = "auto")
```

```{r}
d_vars = d |>
  select(where(is.numeric)) |>
  names()

d_city = reactive({
  d |>
    filter(city %in% input$city)
})
```

Col {data-width=800}
-------------------------------------

###

```{r}
selectInput(
  "city", "Select a city",
  choices = c("Chicago", "Durham", "Sedona", "New York", "Los Angeles")
)

renderPlot({
  d_city() |>
    ggplot(aes(x=time, y=temp)) +
    geom_line()
})
```

Col {data-width=200}
-------------------------------------

### Min temperature 

```{r}
renderGauge({
  gauge(
    min(d_city()$temp),
    min = 0, max=120, symbol = "°F",
    gaugeSectors(success=c(60,90), warning=c(0,50), danger=c(90,120))
  )
})
```

### Max temperature

```{r}
renderGauge({
  gauge(
    max(d_city()$temp),
    min = 0, max=120, symbol = "°F",
    gaugeSectors(success=c(60,90), warning=c(0,50), danger=c(90,120))
  )
})
```

### Avg temperature

```{r}
renderValueBox({
  avg = mean(d_city()$temp) |> round(1)
  valueBox(
    avg,
    caption = "Avg temp",
    icon = "fa-thermometer-half",
    color = case_when(
      avg >= 0 & avg < 50 ~ "warning",
      avg >=50 & avg < 90 ~ "success",
      avg >=90 & avg < 120 ~ "danger"
    )
  )
})
```

Your turn - Exercise 07

Using the code provided in exercises/ex07.Rmd add the following call to the app and try running the dashboard.

thematic::thematic_shiny(bg = "auto", fg = "auto", font = "auto")
  • Try changing the main theme, the background and foreground colors

  • Try changing the accent colors

  • Try changing the fonts