Search code examples
rshinyreactivedt

Use edited value from DataTable in a reactive() function in Shiny


How do I pass a datatable cell edit into a reactiveVal(), then use it in calculations in a reactive() function?

When I change the number in the Goals Column, I expect the color columns to change. For example, currently all of the color columns are "yellow" for the 3rd row, Analyte = Tom. If I change the goal for that row to something large, like 55, all the colors should change to "green" because the Goal will be larger than the Median/95thpercentile/maximum.

I have found 2 methods (linked below) that I tried in my code, and the colors still don't change. It seems like Method 2 is exactly what I'm trying to do - edit in the table and see a change in another column based on a reactive() calc.

Method 1 Method 2

I have the print("Running") code in finished_all() to see if that reactive code is being re-run when I update the table. It does not re-print. It seems that either start_goal() is not being updated or finished_all is not being rerun with the new start_goal() values.

What am I missing here? It seems like I am misunderstanding something about Shiny.

Code below. Sidenote, the colors will be actual colors using formattable, I took it out for simplicity.


library(shiny)
library(shinydashboard)
library(tidyverse)
library(purrr)
library(DT) 

##########################################################################################################*

# Universal ----

initialdata <- tibble(
  Analyte_Short=    c(rep("Flo",2), rep("Pete",2), rep("Tom",2)),
  Result_Num    = c(0.3, 47, 0, 2.5, .9, 5),
  Source=   rep(c("A", "B"),3),
  Method=   c(rep("500a",2), rep("600a",2), rep("700a",2)),
  RESULT_UNIT=  c(rep("MG/L", 6)),
  Analyte_Group=    c(rep("Group1",2), rep("Group2",2), rep("Group3",2)),
  MCL=  c(rep(4,4), rep(as.numeric(NA), 2)),
  SMCL=c(rep(2,2), rep(as.numeric(NA), 4))
) %>%
  mutate(ID= row_number()) 

finaldata <- tibble(
  Analyte_Short =   c("Flo","Pete","Tom"),
  Method =  c("500a","600a","700a"),
  Process = rep("filt",3),
  Removal = c(0.007, 1, .4)
)  %>%
  mutate(ID= row_number()) %>%
  pivot_wider(names_from = Process, values_from = Removal) 

all_mcl <- initialdata %>%
  select(c(Analyte_Group, Analyte_Short, MCL, SMCL, RESULT_UNIT)) %>%
  distinct()

relevantanalytes <-all_mcl$Analyte_Short

###########################################################################################################*

# UI ----

# * Sidebar ----
sidebar <- dashboardSidebar(
  width = 325,
  sidebarMenu(id = "tab", 
              menuItem("Goals", tabName = "goals"),
              menuItem(style = 'float:right, padding: 10px', 
                       "Sources",
                       tabName = "flows",
                       startExpanded = TRUE,
                       div(style = 'float:right',
                           actionButton(inputId = "reset_sliders", label = "Reset Sliders")),
                       br(),
                       sliderInput(inputId = "A", label = "A", min = 0, max = 5, value = 1, step = .1),
                       sliderInput(inputId = "B", label = "B", min = 0, max = 5, value = 3, step = .1)
              ))) 

goals <- tabItem(tabName = "goals", box(width = 8, DT::DTOutput("MCLtable"))) 

ui =
  dashboardPage(
    skin = "green",
    dashboardHeader(title = "Reactive table"),
    sidebar,
    dashboardBody(tabItems(goals))
  )

#########################################################################################################*

# SERVER ----

server = function(input, output, session){

  #* Reset sliders ----
  observeEvent(input$reset_sliders, {
    updateSliderInput(session=session, "A", value = .1)
    updateSliderInput(session=session, "B", value = 0)
  })
  
  #Calculate ratios based on inputs
  b_ratios <- reactive({ 
    
      rate <- c(.1, .7)
      rate <- c(input$A, input$B)
      total <- sum(rate)
      bbratio <- rate / total
      b_table <- tibble(Source = c("A", "B"),
                            Bl = bbratio)
    return(b_table)
  })

  # * finished_all() ----
  finished_all <- reactive({
    print("Running")
    st_goal <-  req(start_goal())
    
    b_summ <- initialdata %>%
      filter(Analyte_Short %in% relevantanalytes) %>%
      full_join(b_ratios(), by = "Source") %>%
      mutate(EachSource_Conc = Result_Num * Bl)  %>%
      group_by(Analyte_Short, RESULT_UNIT, ID)  %>%
      summarise(Blend_Conc = sum(EachSource_Conc), .groups = "drop") %>%
      rename(Raw = Blend_Conc,
             Units = RESULT_UNIT)
    
    finished <- finaldata %>%
      select(Analyte_Short, filt, ID, Method) %>%
      right_join(b_summ, by = c("Analyte_Short", "ID")) %>%
      
      mutate(PostA = Raw * (1-filt)) %>%
      select(-filt) %>%
      pivot_longer(c(Raw, PostA), names_to = "Location", values_to = "Concentration") %>%
      group_by(Analyte_Short) %>%
      summarize(FinishedMedian = median(Concentration, na.rm = TRUE),
                Finished95thP = quantile(Concentration, .95, na.rm = TRUE),
                FinishedMax = max(Concentration, na.rm = TRUE)) %>%
      
      right_join(all_mcl) %>%
      
      mutate(Median = round(FinishedMedian, 1),
             `95th Percentile` = round(Finished95thP, 1),
             Maximum = round(FinishedMax, 1),
             
             # This Goal column gets updated in the table, but doesn't seem to update here 
             # Goal = st_goal[Analyte_Short %>% as.characeter]
             Goal = st_goal[Analyte_Short]) %>%
      
      rename(`Analyte Group` = Analyte_Group,
             Analyte = Analyte_Short,
             Units = RESULT_UNIT) %>%
      select(`Analyte Group`, Analyte, Units, MCL, SMCL, Goal, Median, `95th Percentile`, Maximum) %>%
    
      # Goals here don't seem to be updated becuase the color labels don't change based on Goal column value
      mutate(MedColor = case_when(Median < Goal ~ "green",
                                  Median >= MCL ~ "red",
                                  Median >= SMCL ~ "orange",
                                  TRUE ~ "yellow"),
             P95Color = case_when(`95th Percentile` < Goal ~ "green",
                                  `95th Percentile` >= MCL ~ "red",
                                  `95th Percentile` >= SMCL ~ "orange",
                                  TRUE ~ "yellow"),
             MaxColor = case_when(Maximum < Goal ~ "green",
                                  Maximum  >= MCL ~ "red",
                                  Maximum >= SMCL ~ "orange",
                                  TRUE ~ "yellow"))
    return(finished)
  })

  # goals table ----
  start_goal <- reactiveVal(
    list(
      "Flo" =   2,
      "Pete"    =   4,
      "Tom" =   2   ))
  
  #cell update----

  observeEvent(input$finished_all_cell_edit, {
    
    i = input$finished_all_cell_edit$row
    j = input$finished_all_cell_edit$col+1
    v = input$finished_all_cell_edit$value
    
    temp_goal <- start_goal()
    
    temp_goal[[i]] <- v %>% as.numeric
    
    start_goal(temp_goal)
    
  })
  
  # create a dataframe that reactive values can be added to
  # df_mcltable <- reactiveValues(data=NULL)
  # 
  # # add reactive values to a df
  # observe({
  #   df_mcltable$data <- finished_all()
  # })
  #
  # observeEvent(input$df_mcltable_cell_edit, {
  # 
  #   i = input$df_mcltable_cell_edit$row
  #   j = input$df_mcltable_cell_edit$col  
  #   v = input$df_mcltable_cell_edit$value
  #   
  #   # df_mcltable$data[i, j+1] <- coerceValue(v, df_mcltable$data[i, j+1])
  #   
  #   temp_goal <- start_goal()
  #   
  #   temp_goal[[i]] <- v %>% as.numeric
  #   
  #   start_goal(temp_goal)
  # })
  
  # OUTPUTS----
  
  output$MCLtable <- renderDT( 
    
    # df_mcltable$data,
    finished_all(),
    escape = FALSE, #this needs to stay false due to much HTML in original code
    options = list(scrollY = 600, paging = FALSE),
    rownames = FALSE,
    editable = list(target = "cell", disable = list(columns = c(0:4,6,7))),
    selection = "none"
  )
  
}

shinyApp(ui , server)

Solution

  • Update: To make the above work, update the observeEvent() to be:

    observeEvent(input$MCLtable_cell_edit, {
    
    i = input$MCLtable_cell_edit$row
    j = input$MCLtable_cell_edit$col+1
    v = input$MCLtable_cell_edit$value
    
    temp_goal <- start_goal()
    
    temp_goal[[i]] <- v %>% as.numeric
    
    start_goal(temp_goal) })
    

    It seems that I needed to update the variable I was using in my output/UI, MCLtable, not the reactive finished_all() function

    Additionally, the order of the list in start_goal() needs to match the original Datatable exactly (ie, the order must be Flow, Pete, Tom, not Pete, Flow, Tom). Otherwise, the wrong row will be updated.