У меня есть блестящее приложение 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)
Нет, это было не намеренно. Его следует перерисовывать только при изменении фильтра :)
Ах! Вот и все, не так ли? Мне просто нужно удалить Shiny.onInputChange("filter_data", JSON.stringify(filters)); А sorted_data на самом деле — это отфильтрованные_данные в моем коде. Я только сейчас это заметил.
Это не помогло. Я обновил код, поэтому теперь он повторно отображает график только при фильтрации.
Проблема в том, что когда 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)
Отлично! Огромное спасибо за решение и объяснение!
Без проблем. Рад, что это работает для вас
График перерисовывается каждый раз, когда таблица сортируется. Это намеренно? Или вам нужен повторный рендеринг только при изменении фильтра?