Chapter 10 Dynamic UI

This chapter is in development...

Exercise 10.1.5.2

Complete the user interface below with a server function that updates input$county choices based on input$state. For an added challenge, also change the label from “County” to “Parrish” for Louisana and “Borrough” for “Alaska”.

Exercise 10.1.5.5

What is at the heart of the problem described at https://community.rstudio.com/t/29307?

Solution. Solution

Updating all three sliders creates a circular reference!

Exercise 10.2.3.1

Use a hidden tabset to show additional controls only if the user checks an “advanced” check box.

Exercise 10.2.3.2

Create that allows the user to select from geom_smooth(), geom_histogram(), or geom_point(). Use a hidden tabset to allow the user to select different options depending on the geom. geom_smooth() should have a text both for the model, and checkbox for whether or not to add standard errors. geom_histogram() should have a numeric input for the bin width, and geom_point() doesn’t need any additional options.

Exercise 10.2.3.3

Create a wizard interface that steers the user along the path …

Exercise 10.3.4.1

Take this very simple app based on the initial example in the chapter:

How could you instead implement it using dynamic visibility? If you implement dynamic visibility, how could you keep the values in sync when you change the controls?

Exercise 10.3.4.3

Add support for date and date-time columns make_ui() and filter_var().

Solution. Solution

In order to complete this, I had to

  1. make a new dummy dataframe I called x in order to test for dates
  2. include checking for is.Date in the make_ui and filter_var functions
  3. Change tableOutput and renderTable to DT::renderTableOutput and DT::renderTableOutput because renderTable was rendering the dates as numbers and I think this could be because it uses xtable() for HTML table rendering?
# 8.4.3.2
library(shiny)
library(purrr)
library(tidyverse)

make_ui <- function(x, var) {
  if (is.numeric(x)) {
    rng <- range(x, na.rm = TRUE)
    sliderInput(var, var, min = rng[1], max = rng[2], value = rng)
  } else if (is.factor(x)) {
    levs <- levels(x)
    selectInput(var, var, choices = levs, selected = levs, multiple = TRUE)
  } else if (lubridate::is.Date(x)) {
    rng <- range(x, na.rm = TRUE)
    dateInput(var, var, min = rng[1], max = rng[2], value = rng[1])
  } else {
    # No control, so don't filter
    NULL
  }
}


filter_var <- function(x, val) {
  if (is.numeric(x)) {
    !is.na(x) & x >= val[1] & x <= val[2]
  } else if (is.factor(x)) {
    x %in% val
  } else if (lubridate::is.Date(x)) {
    x %in% val
  } else {
    TRUE
  }
}

library(shiny)

dfs <- keep(ls("package:datasets"), ~ is.data.frame(get(.x, "package:datasets")))

# add a dataframe with dates in it since I cant find one in the datasets above
# rep 5 dates five times, each include 1 factor a-e
x <- data.frame(date = c(rep(as.Date("2020/1/1"), 5),
                         rep(as.Date("2020/2/2"), 5),
                         rep(as.Date("2020/3/3"), 5),
                         rep(as.Date("2020/4/4"), 5),
                         rep(as.Date("2020/5/5"), 5)),
                fac = as.factor(c("a", "b", "c", "d", "e")))

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      #selectInput("dataset", label = "Dataset", choices = c(dfs, "x")),
      uiOutput("filter")
    ),
    mainPanel(
      DT::dataTableOutput("data")
    )
  )
)

server <- function(input, output, session) {
  
  # data is either my dummy dataset or from datasets
  data <- reactive(x)
  
  vars <- reactive(names(data()))
  
  output$filter <- renderUI(
    # take eahc column name and make ui
    # data()[[.x]] is each column
    # and .x is each column name (vars())
    map(vars(), ~ make_ui(data()[[.x]], .x))
  )
  
  selected <- reactive({
    # take each column name and filer var
    # with the first argument the column in the data
    # and the second argument the input$vars()
    # so for date check that input[[date]] in data[[1]]
    each_var <- map(vars(), ~ filter_var(data()[[.x]], input[[.x]]))
    
    # notes from @mapaulacaldas
    # collapse list of TRUE and FALSE using `&`
    # conditions <- list(TRUE, TRUE, TRUE, FALSE)
    # purrr::reduce(conditions, `&`) ==
    # ((conditions[[1]] & conditions[[2]]) & conditions[[3]]) & conditions[[4]]
    reduce(each_var, `&`)
  })
  
  # subset the data by the vars that are true
  output$data <- DT::renderDataTable(data()[selected(), ])
}
# Run the application 
shinyApp(ui = ui, server = server)

Exercise 10.3.4.5

(Hard) Make a wizard that allows the user to upload their own dataset. The first page should handle the upload. The second should handle reading it, providing one drop down for each variable that lets the user select the column type. The third page should provide some way to get a summary of the dataset.

Solution. Solution

I wasn’t really sure what was meant by “some way to get a summary of the dataset” So I’m just using the summary function.

library(shiny)
library(readr)

make_dropdown <- function(name_of_vector) {
  selectInput(inputId = name_of_vector, label =  name_of_vector, choices = 
                c("numeric", "character", "logical"))
}  

ui <- fluidPage(
  tags$style("#wizard { display:none; }"),
  tabsetPanel(id = "wizard",
              tabPanel("page1", 
                       fileInput("data_input", "input"),
                       actionButton("page12", "next")
              ),
              tabPanel("page2",
                         sidebarLayout(
                           sidebarPanel(
                             uiOutput("type_of")
                           ),
                           mainPanel(
                             tableOutput('type_table')
                           )),
                       actionButton("page21", "prev"),
                       actionButton("page23", "next")
              ),
              tabPanel("page3", 
                       tableOutput("summary_table"),
                       actionButton("page32", "prev")
              )
  )
)

server <- function(input, output, session) {
  
  ################ WIZARD  ###############################
  
  switch_tab <- function(page) {
    updateTabsetPanel(session, "wizard", selected = page)
  }
  
  observeEvent(input$page12, switch_tab("page2"))
  observeEvent(input$page21, switch_tab("page1"))
  observeEvent(input$page23, switch_tab("page3"))
  observeEvent(input$page32, switch_tab("page2"))
  
  ##################### FILE INPUT #######################
  
  dat <- reactive({
    req(input$data_input)
    read.csv(input$data_input$datapath)
  })
  
  ##################### TABLE TYPE #######################
  
  # make a dropdown using the names of each column
  output$type_of <- renderUI({ map(names(dat()), ~ make_dropdown(.x)) })
  
  
  # switch the type of column based on the input
  # name of vector == "Sepal.Length"
  # vector == Sepal.Length
  change_type <- function(vector, name_of_vector) {
    switch(input[[name_of_vector]],
           "numeric" = vector <- as.numeric(vector),
           "character" = vector <- as.character(vector),
           "logical" = vector <- as.complex(vector)
    )
  }
  
  # convert the supplied data to a list
  # use imap because it is a condensed version o map
  # with two arguments == x & name_of_x
  # so we don't need to supply it arguments beyond the list!
  df<- reactive({
    dat() %>% 
      as.list() %>% 
      imap(change_type) %>% 
      as_tibble()
  })
  
  # create an output of the data's names
  # and their types
  output$type_table <- renderTable(data.frame(
    names = names(df()),
    type = map_chr(df(), function(x) typeof(x)))
  )
  
  ##################### TABLE OUTPUT #####################
  
  output$summary_table <- renderTable( summary(df()) )
}

shinyApp(ui = ui, server = server)