【问题标题】:Shiny: Global Reactive DatasetShiny:全球反应式数据集
【发布时间】:2018-10-25 20:01:04
【问题描述】:

我有一个通过查询 postgre 数据库构建的全局数据框(它将在 Global.R 中定义)。这个数据框需要在多个会话之间共享。

现在在 UI 中,对于每个会话,我需要显示一个包含此数据框内容的数据表。我还有一个 radioButton 对象,以便用户可以更改字段的值,在给定行的数据框中将其称为 decision,并且我希望显示或不显示数据表中的相应行(即如果仅decision == 0,则将数据框行显示为数据表中的一行)

问题: 我希望根据用户提供给decision 的值主动隐藏/显示数据表中的行,并且我希望在多个会话中发生这种情况

因此,如果有 2 个用户并且 user_1 将行 a 的 decision 的值从 0(显示)更改为 1(隐藏),我希望该行被被动隐藏在 user_1 和的数据表中user_2 无需刷新或按下操作按钮。

最好的方法是什么?

这是一个可重现的最小示例:

library(shiny)
library(dplyr)

# global data-frame
df <<- data.frame(id = letters[1:10], decision = 0)

update_decision_value <- function (id, dec) {
  df[df$id == id, "decision"] <<- dec
}

ui <- fluidPage(
  uiOutput('select_id'),
  uiOutput('decision_value'),
  dataTableOutput('my_table')
)

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

  filter.data <- reactive({
    df %>% 
      filter(decision == 0)
  })

  output$select_id <- renderUI({
    selectInput('selected_id', "ID:", choices = df$id)
  })

  output$decision_value <- renderUI({
    radioButtons(
      'decision_value',
      "Decision Value:",
      choices = c("Display" = 0, "Hide" = 1),
      selected = df[df$id == input$selected_id, "decision"]
    )
  })

  output$my_table <- renderDataTable({
    filter.data()
  })

  observeEvent(input$decision_value, {
    update_decision_value(input$selected_id, input$decision_value)
  })
}

shinyApp(ui, server)

【问题讨论】:

  • 一种方法是将变量保存RDS到磁盘,让一个会话提交对该文件的更改(阻止其他会话尝试相同)并使用reactiveFileReader轮询所有其他会话中的更改。也许还有更好的方法。
  • 另一种方法是将更改保存在数据库中,并且所有会话都可以从同一个表中读取/写入(使用例如 reactivePoll)。
  • 感谢@ismirsehregal,reactivePoll 接缝是个好主意(无论如何我都需要将更改保存在数据库中)。我只是不确定在这种情况下实现 reactivePoll 的 checkFunc 参数的最有效方法是什么。

标签: r shiny


【解决方案1】:

这是一个工作示例:

library(shiny)
library(dplyr)
library(RSQLite)

# global data-frame
df <- data.frame(id = letters[1:10], decision = 0, another_col = LETTERS[1:10])
con <- dbConnect(RSQLite::SQLite(), "my.db", overwrite = FALSE)

if (!"df" %in% dbListTables(con)) {
  dbWriteTable(con, "df", df)
}

# drop global data-frame
rm("df")

update_decision_value <- function (id, dec) {
  dbExecute(con, sprintf("UPDATE df SET decision = '%s' WHERE id = '%s';", dec, id))
}

ui <- fluidPage(textOutput("shiny_session"),
                uiOutput('select_id'),
                uiOutput('decision_value'),
                dataTableOutput('my_table'))

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

  output$shiny_session <- renderText(paste("Shiny session:", session$token))

  session$onSessionEnded(function() {
    if (!is.null(con)) {
      dbDisconnect(con)
      con <<- NULL # avoid warning; sqlite uses single connection for multiple shiny sessions
    }
  })

  df_ini <- dbGetQuery(con, "SELECT id, decision FROM df;")
  all_ids <- df_ini$id

  df <- reactivePoll(
    intervalMillis = 100,
    session,
    checkFunc = function() {
      req(con)
      df_current <- dbGetQuery(con, "SELECT id, decision FROM df;")
      if (all(df_current == df_ini)) {
        return(TRUE)
      }
      else{
        df_ini <<- df_current
        return(FALSE)
      }
    },
    valueFunc = function() {
      dbReadTable(con, "df")
    }
  )

  filter.data <- reactive({
    df() %>%
      filter(decision == 0)
  })

  output$select_id <- renderUI({
    selectInput('selected_id', "ID:", choices = all_ids)
  })

  output$decision_value <- renderUI({
    radioButtons(
      'decision_value',
      "Decision Value:",
      choices = c("Display" = 0, "Hide" = 1),
      selected = df()[df()$id == input$selected_id, "decision"]
    )
  })

  output$my_table <- renderDataTable({
    filter.data()
  })

  observeEvent(input$decision_value, {
    update_decision_value(input$selected_id, input$decision_value)
  })
}

shinyApp(ui, server)

编辑 ------------------------------------------------

更新版本通过避免比较整个表来减少数据库负载,而只搜索闪亮会话方面的未知更改(考虑到 ms-timestamp,每个决策更改都会更新):

library(shiny)
library(dplyr)
library(RSQLite)

# global data-frame
df <- data.frame(id = letters[1:10], decision = 0, last_mod=as.numeric(Sys.time())*1000, another_col = LETTERS[1:10])
con <- dbConnect(RSQLite::SQLite(), "my.db", overwrite = FALSE)

if (!"df" %in% dbListTables(con)) {
  dbWriteTable(con, "df", df)
}

# drop global data-frame
rm("df")

update_decision_value <- function (id, dec) {
  dbExecute(con, sprintf("UPDATE df SET decision = '%s', last_mod = '%s' WHERE id = '%s';", dec, as.numeric(Sys.time())*1000, id))
}

ui <- fluidPage(textOutput("shiny_session"),
                uiOutput('select_id'),
                uiOutput('decision_value'),
                dataTableOutput('my_table'))

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

  output$shiny_session <- renderText(paste("Shiny session:", session$token))

  session$onSessionEnded(function() {
    if (!is.null(con)) {
      dbDisconnect(con)
      con <<- NULL # avoid warning; sqlite uses single connection for multiple shiny sessions
    }
  })

  df_session <- dbReadTable(con, "df")
  all_ids <- df_session$id
  last_known_mod <- max(df_session$last_mod)

  df <- reactivePoll(
    intervalMillis = 100,
    session,
    checkFunc = function() {
      req(con)
      df_changed_rows <- dbGetQuery(con, sprintf("SELECT * FROM df WHERE last_mod > '%s';", last_known_mod))
      if(!nrow(df_changed_rows) > 0){
        return(TRUE)
      }
      else{
        changed_ind <- match(df_changed_rows$id, df_session$id)
        df_session[changed_ind, ] <<- df_changed_rows
        last_known_mod <<- max(df_session$last_mod)
        return(FALSE)
      }
    },
    valueFunc = function() {
      return(df_session)
    }
  )

  filter.data <- reactive({
    df() %>%
      filter(decision == 0)
  })

  output$select_id <- renderUI({
    selectInput('selected_id', "ID:", choices = all_ids)
  })

  output$decision_value <- renderUI({
    radioButtons(
      'decision_value',
      "Decision Value:",
      choices = c("Display" = 0, "Hide" = 1),
      selected = df()[df()$id == input$selected_id, "decision"]
    )
  })

  output$my_table <- renderDataTable({
    filter.data()
  })

  observeEvent(input$decision_value, {
    update_decision_value(input$selected_id, input$decision_value)
  })
}

shinyApp(ui, server)

【讨论】:

  • 在您的实际代码中,您可能希望增加intervalMillis。如果每 100 毫秒向它们抛出一个选择,大多数 sql-server 都不会高兴。但这需要根据您的设置(表格大小等)进行调整。
猜你喜欢
  • 1970-01-01
  • 2019-12-02
  • 2015-11-24
  • 2017-01-17
  • 2018-03-28
  • 2014-04-14
  • 1970-01-01
  • 1970-01-01
  • 2016-08-10
相关资源
最近更新 更多