0

I have data that looks something like the data set Orange where there are columns that might contain duplicate values, however, each row is unique.
My code:

library(shiny)
library(DT)
library(data.table)

d <- copy(Orange)
col_names <- names(Orange)
user_friendly_names <- c('TreeNumber', 'TreeAge', 'Circumference')


ui <- fluidPage(  
  sidebarLayout(
    sidebarPanel(
      h3("Filters:"),
      uiOutput("filters"),

      # Plot button
      fluidRow(column(2, align = "right",
                      actionButton("plot_graph_button", "Plot")))
    ),
    mainPanel(tableOutput("summary"))
  )
)

server <- function(input, output) {
  #### Create the filter lists for UI ####
  output$filters <- renderUI({
    if(is.null(col_names)) return(NULL)
    lapply(1:length(col_names), function(i) {
      col <- paste0(col_names[i])
      alias <- user_friendly_names[i]
      # Populate input with unique values from column
      selectizeInput(inputId = alias, label = paste(alias,':'),
                     choices = c('All', unique(d[[col]])), selected = 'All', multiple = T)
    })
  })

  output$summary <- renderTable({
    # Do not show a plot when the page first loads
    # Wait until the user clicks "Plot" button
    if (input$plot_graph_button == 0){
      return()
    }
    # Update code below everytime the "Plot" button is clicked
    input$plot_graph_button

    isolate({
      # Fresh copy of the full data set every time "Plot" button is clicked
      d <- copy(Orange)

      # Filter data based on UI
      for(f in 1:length(col_names)){
        print(paste("This is loop # ", f))

        if(eval(parse(text = paste0('is.null(input$',user_friendly_names[f],')')))){
          # If the user deleted "All" but failed to pick anything else default to "All" - do not filter
          break
        }else{
          if(eval(parse(text = paste0('input$',user_friendly_names[f]))) != "All"){
            print("FALSE -- Input is not == ALL")

            d <- d[d[[col_names[f]]] == unlist(eval(parse(text = paste0('input$',user_friendly_names[f])))), ]
          }else{
            print("TRUE -- Input is defaulted to ALL")
          }
        }
      }
      final_summary_table <<- d
    })
  })
}

shinyApp(ui = ui, server = server)

My issue is that these lists are able to select multiple inputs (which I want), however, I want to initially show all available choices in all menus (which it currently does) but what I need to change is I need to have it start filtering the other lists as soon as a selection is made (no matter which list the user goes to first) based on that unique rowed data set provided.
So, if the user goes to the 2nd list and chooses tree age of 1004 then the TreeNumber menu should change to c(1, 2, 3, 4, 5) - no change in this scenario but the Circumference menu should change to c(115, 156, 108, 167, 125), then if they pick a TreeAge now the menus get filtered down by both TreeAge and TreeNumber and so on.

Right now the way the code works is it doesn't filter anything until you click "Plot", so the user might think a search will yield a bunch of results, when in reality the combination may not exist.

Here is a good example of a search that you may expect to yield a lot of results, yet it only yields 1 row:

enter image description here

Please note: If you do not delete 'All' it will return 'All' even if you selected other options, it is a flaw in the code that I plan to address separately along with some other minor tweaks.

I also wanted to mention that I found this post Filter one selectInput based on selection from another selectInput? which is similar to mine, however, they are dealing with menus in a top-down approach and mine is going to be more flexible about which menu the user goes to first (also mine allows multiple selections).

Bear
  • 662
  • 1
  • 5
  • 20

1 Answers1

1
server <- function(input, output, session) {

  output$filters <- renderUI({
    # ...
  })

  lapply(seq_along(d), function(i) {
    observeEvent(input[[user_friendly_names[i]]], {
      for (j in seq_along(d)[-i]) {
        choices <- if ("All" %in% input[[user_friendly_names[i]]]) 
          unique(d[[j]]) else 
          unique(d[[j]][d[[i]] %in% input[[user_friendly_names[i]]]])
        choices <- c("All", choices)
        selected <- intersect(choices, input[[user_friendly_names[j]]])
        updateSelectInput(session = session, inputId = user_friendly_names[j], 
                          choices = choices, selected = selected)
      }
    })
  })

  observeEvent(input$plot_graph_button, {
    for (j in seq_along(d)) {
      updateSelectInput(session = session, inputId = user_friendly_names[j], 
                        choices = c("All", unique(d[[j]])), selected = "All")
    }
  })

  output$summary <- renderTable({
     # ...          
  })
}
Aurèle
  • 12,545
  • 1
  • 31
  • 49
  • The only issue I am finding with this is if the user re-selects input after clicking plot once, it continues to filter from the filtered list instead of getting a clean copy and starting over. – Bear Jul 31 '18 at 17:54
  • "All" disappears right away now because it is not in the data set as an option. Is there a way to keep it as an option? – Bear Jul 31 '18 at 18:46
  • It is close but now when I see when you click "Plot" it erases what the user selected right away. – Bear Aug 01 '18 at 14:38
  • Sorry for the misunderstanding. I thought you meant clicking "Plot" should give "a clean copy and start over" – Aurèle Aug 01 '18 at 16:09