Фильтрация данных в Reactable в R Shiny. Реактивность и недействительностьПозже

У меня есть блестящее приложение R, которое работает нормально, но когда я получаю данные, нажимая кнопку «Получить данные», все компоненты в функции сервера выполняются дважды, и я хочу, чтобы они выполнялись только один раз. Причина, по которой я хочу, чтобы он выполнялся только один раз, заключается в том, что второе выполнение вызывает повторный рендеринг графиков в приложении, что заметно, когда я запускаю его на удаленном сервере.

Я приложил упрощенную версию кода. Обратите внимание, что переменная диапазонов не применяется в этой упрощенной версии, но я включил ее, чтобы показать различия между двумя реактивными наборами данных dat_subset и **dat_filt** , которые необходимы для правильной работы реального приложения.

Я знаю, что код выполняется дважды из-за кода validateLater(500), но если я не включу его, графики не будут повторно отображаться, когда я фильтрую реагирующие элементы.

Я хочу, чтобы код выполнялся только один раз, когда я нажимаю get_data, но я также хочу, чтобы график столбца повторно отображался и обновлялся, когда я фильтрую данные в таблице.

Итак, мой вопрос: могу ли я запустить повторный рендеринг графика, когда таблица фильтруется, без использования функции validateLater?

Вот код:

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)

График перерисовывается каждый раз, когда таблица сортируется. Это намеренно? Или вам нужен повторный рендеринг только при изменении фильтра?

Michael Dewar 29.08.2024 16:09

Нет, это было не намеренно. Его следует перерисовывать только при изменении фильтра :)

Tota Juliusdottir 29.08.2024 16:41

Ах! Вот и все, не так ли? Мне просто нужно удалить Shiny.onInputChange("filter_data", JSON.stringify(filters)); А sorted_data на самом деле — это отфильтрованные_данные в моем коде. Я только сейчас это заметил.

Tota Juliusdottir 29.08.2024 16:53

Это не помогло. Я обновил код, поэтому теперь он повторно отображает график только при фильтрации.

Tota Juliusdottir 29.08.2024 17:33
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
4
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема в том, что когда dat_subset становится недействительным, это делает недействительными и dat_filt, и dat_table. Затем возникает условие гонки, какая цепочка последствий завершится первой. Но на самом деле обновление таблицы, а затем обновление JS input$filtered_data происходит очень медленно. Ваш график отображается первым, но он правильно использует новейший dat_filt с неправильным старым input$filtered_data. Итак, первый сюжет, который всплывает на мгновение, неверен.

Я предлагаю добавить reactiveVal для буферизации input$filtered_data. Используйте наблюдатель, чтобы обновить его с помощью обновлений фильтрации JS. Но когда вы пересчитываете 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)

Отлично! Огромное спасибо за решение и объяснение!

Tota Juliusdottir 29.08.2024 18:40

Без проблем. Рад, что это работает для вас

Michael Dewar 29.08.2024 19:27

Другие вопросы по теме