在 R闪亮中过滤可反应的数据。反应性和失效稍后

问题描述 投票:0回答:1

我有一个运行良好的 R 闪亮应用程序,但是当我通过按“获取数据”按钮检索数据时,服务器函数中的所有组件都会执行两次,而我只希望它们执行一次。我只希望它执行一次的原因是,第二次执行会导致应用程序中的绘图重新渲染,当我在远程服务器上运行它时,这一点很明显。

我附上了代码的简化版本。请注意,范围变量未应用于此简化版本,但我将其包含在内是为了显示两个反应式数据集 dat_subset 和 **dat_filt ** 之间的差异,这是真实应用程序按预期工作所需的。

我知道由于 invalidateLater(500) 代码,代码确实执行了两次 - 但如果我不包含该代码,当我过滤可反应时,绘图不会重新渲染。

我只想在按下 get_data 时执行一次代码,但我也希望在过滤表中的数据时重新渲染和更新柱形图。

所以我的问题是,当过滤表格时,我可以触发重新渲染绘图,而不必使用 invalidateLater 函数吗?

这是代码:

library(shiny)
library(htmlwidgets)
library(reactable)
library(tidyr)
library(dplyr)
library(ggplot2)
library(shinyjs)
library(shinyWidgets)



jsCode <- 'shinyjs.getSortedAndFilteredData = function() {
  try {
    var instance = Reactable.getInstance("dat_table");
    if (instance) {
      var filteredIdx = instance.sortedFlatRows.map(x => x.index + 1);
      Shiny.onInputChange("filtered_data", filteredIdx);
    }
  } catch (err) {
    console.error(err);
  }
}'


# Define UI for application that draws a histogram
ui <- fluidPage(
  useShinyjs(),
  extendShinyjs(text = jsCode, functions = c("getSortedAndFilteredData")),
  theme = shinythemes::shinytheme("lumen"),
  fluidRow(
    column(width = 10,
           actionButton("get_data", "Get Data", class = "btn-primary")
    )
  ),
  fluidRow(
    column(width = 7,
           plotOutput("age_distribution_plot", height = 300)
    )
  ),
  fluidRow(
    column(width = 10,
           reactableOutput("dat_table")
    )
  )
)

get_age_cat_plot = function(dat){
  dat$age_cat <- cut(dat$age, breaks=c(6, 11, 21, 36, Inf), labels = c("<10","11-20","21-35","36+"), right = TRUE) 
  d <- dat %>% group_by(gender, age_cat) %>% summarise(count = n(), .groups="keep") %>% na.omit()
  d %>%
      ggplot(aes(factor(age_cat, levels=rev(levels(dat$age_cat))), count, fill = gender)) +
      scale_fill_manual(values = c("M"="#7285A5","F" = "pink3","U"="lightgray"))+
      geom_col(alpha=0.3, width=0.8, color="darkgrey") + theme_classic()+
      geom_text(aes(label = count),  # Adding percentage labels
                position = position_stack(vjust = 0.5), 
                color = "black", size = 5) +labs(y = "age", x="count") 
}


server <- shinyServer(function(input, output, session) {
    ranges <- reactiveValues(x = NULL, y = NULL)
    gene_table_ready <- reactiveVal(FALSE)

     dat <-  eventReactive(input$get_data,{
        print("GETTING THE DATA ")
        ranges$x <- NULL; ranges$y <- NULL
        gene_table_ready(TRUE)
        age <- sample(0:75, 200, replace = TRUE)
        gender <- sample(c("M", "F"), 200, replace = TRUE)
        data.frame(age = age, gender = gender)
    })
      
      dat_subset <- reactive({
        print("getting dat subset")
        dat <- dat()
        if (!is.null(ranges$x)) 
          dat <- subset(dat, chr_start >= ranges$x[1] & chr_start <= ranges$x[2])
        dat
      })
      observe({
        if(gene_table_ready()){
          js$getSortedAndFilteredData()
          invalidateLater(500)
        }
      })
      dat_filt <- reactive({
        print("FILTERING....")
        dat <- dat_subset()
        if(!is.null(input$filtered_data))
          dat <- dat[input$filtered_data, ]
        dat
      })
      output$dat_table <- renderReactable({
        print("Updating the data table")
        dat <- dat_subset()
        reactable(
          dat,
          filterable = TRUE,
        ) 
      })
      output$age_distribution_plot <- renderPlot({
        print("Getting age cat plot... ")
        get_age_cat_plot(dat_filt())
      })
    

    })
shinyApp(ui = ui, server = server)
javascript r shiny shiny-reactivity reactable
1个回答
0
投票

问题是,当

dat_subset
失效时,
dat_filt
dat_table
都失效。 然后存在一个竞争条件,即哪个后果链首先完成。 但实际上,
input$filtered_data
的表更新和JS更新是非常慢的。 您的绘图首先渲染,但它正确地使用了最新的
dat_filt
和不正确的旧
input$filtered_data
。 所以第一个短暂闪现的情节是错误的。

我建议在

reactiveVal
前面添加一个
input$filtered_data
。 使用观察者来保持过滤的更新。 但是,当您重新计算
dat
时,请手动将
reactiveVal
设置为您知道最终将来自更新后的
input$filtered_data

library(shiny)
library(htmlwidgets)
library(reactable)
library(tidyr)
library(dplyr)
library(ggplot2)
library(shinyjs)
library(shinyWidgets)



jsCode <- 'shinyjs.getSortedAndFilteredData = function() {
  try {
    var instance = Reactable.getInstance("dat_table");
    if (instance) {
      var filteredIdx = instance.sortedFlatRows.map(x => x.index + 1);
      Shiny.onInputChange("filtered_data", filteredIdx);
    }
  } catch (err) {
    console.error(err);
  }
}'


# Define UI for application that draws a histogram
ui <- fluidPage(
  useShinyjs(),
  extendShinyjs(text = jsCode, functions = c("getSortedAndFilteredData")),
  theme = shinythemes::shinytheme("lumen"),
  fluidRow(
    column(width = 10,
           actionButton("get_data", "Get Data", class = "btn-primary")
    )
  ),
  fluidRow(
    column(width = 7,
           plotOutput("age_distribution_plot", height = 300)
    )
  ),
  fluidRow(
    column(width = 10,
           reactableOutput("dat_table")
    )
  )
)

get_age_cat_plot = function(dat){
  dat$age_cat <- cut(dat$age, breaks=c(6, 11, 21, 36, Inf), labels = c("<10","11-20","21-35","36+"), right = TRUE) 
  d <- dat %>% group_by(gender, age_cat) %>% summarise(count = n(), .groups="keep") %>% na.omit()
  d %>%
    ggplot(aes(factor(age_cat, levels=rev(levels(dat$age_cat))), count, fill = gender)) +
    scale_fill_manual(values = c("M"="#7285A5","F" = "pink3","U"="lightgray"))+
    geom_col(alpha=0.3, width=0.8, color="darkgrey") + theme_classic()+
    geom_text(aes(label = count),  # Adding percentage labels
              position = position_stack(vjust = 0.5), 
              color = "black", size = 5) +labs(y = "age", x="count") 
}


server <- shinyServer(function(input, output, session) {
  ranges <- reactiveValues(x = NULL, y = NULL)
  gene_table_ready <- reactiveVal(FALSE)
  
  # Add a buffer that you can control.  Use filtered_data_2() instead of input$filtered_data
  filtered_data_2 <- reactiveVal(NULL) 
  observeEvent(input$filtered_data, {
    filtered_data_2(input$filtered_data)
  })
  
  dat <-  eventReactive(input$get_data,{
    print("GETTING THE DATA ")
    ranges$x <- NULL; ranges$y <- NULL
    gene_table_ready(TRUE)
    filtered_data_2(1:200) # Force the update here.  Shiny will ignore the JS update that is the same as this.
    age <- sample(0:75, 200, replace = TRUE)
    gender <- sample(c("M", "F"), 200, replace = TRUE)
    data.frame(age = age, gender = gender)
  })
  
  dat_subset <- reactive({
    print("getting dat subset")
    dat <- dat()
    if (!is.null(ranges$x)) 
      dat <- subset(dat, chr_start >= ranges$x[1] & chr_start <= ranges$x[2])
    dat
  })
  observe({
    if(gene_table_ready()){
      js$getSortedAndFilteredData()
      invalidateLater(500)
    }
  })
  dat_filt <- reactive({
    print("FILTERING....")
    dat <- dat_subset()
    if(!is.null(filtered_data_2()))   # use the new reactiveVal
      dat <- dat[filtered_data_2(), ] # use the new reactiveVal
    dat
  })
  output$dat_table <- renderReactable({
    print("Updating the data table")
    dat <- dat_subset()
    reactable(
      dat,
      filterable = TRUE,
    ) 
  })
  output$age_distribution_plot <- renderPlot({
    print("Getting age cat plot... ")
    get_age_cat_plot(dat_filt())
  })
  
  
})
shinyApp(ui = ui, server = server)
© www.soinside.com 2019 - 2024. All rights reserved.