首页 > 解决方案 > 如何强制输入跨模块采用相同的值

问题描述

我正在开发一个闪亮的仪表板,该仪表板大量使用闪亮的模块,我的客户要求我这样做,以便仪表板的各个选项卡中的相同两个输入采用相同的值,而不管选项卡如何。我在执行此操作时遇到了很大的问题,并且能够使用您将在下面找到的玩具示例重新创建它。

#app.R

library(data.table)
library(shiny)
library(ggplot2)
library(ggthemes)
library(shinythemes)
source("Modules.R")

penguins <<- as.data.table(palmerpenguins::penguins)

ui = uiOutput("ui")
inputs <<- reactiveValues(species = NULL, island = NULL)

server <- function(input, output, session) {
  bill_species_server("tab1")
  flipper_mass_scatter_server("tab2")
  
  output$ui = renderUI({
    fluidPage(
      titlePanel("", "Penguin Dashboard"),
       tabsetPanel(
         tabPanel("Bill Length by Species",
                ui_code("tab1")
        ),
        tabPanel("Flipper Length by Body Mass",
                 ui_code("tab2")
        )
      )
    )
  })
}

# Run the application 
shinyApp(ui = ui, server = server)
#Modules.R

ui_code = function (id) {
  ns = NS(id)
  
  sidebarLayout(position = "left",
    sidebarPanel(
      selectInput(ns("species"), "Choose 1+ species:", choices = penguins[, sort(unique(species))], multiple = TRUE),
      selectInput(ns("island"), "Choose 1+ islands:", choices = penguins[, sort(unique(island))], multiple = TRUE)
    ),
    mainPanel(
      plotOutput(ns("plot"))
    )
  )
}

bill_species_server = function(id) {
  
  moduleServer(id, function(input, output, session) {
    
    observeEvent(inputs$species, ignoreInit = TRUE, ignoreNULL = TRUE, {
      if (length(inputs$species) > 0) {
        updateSelectInput(session = session, inputId = "species", selected = inputs$species)
      }
    })
    
    observeEvent(inputs$island, ignoreInit = TRUE, ignoreNULL = TRUE, {
      if (length(inputs$island) > 0) {
        updateSelectInput(session = session, inputId = "island", selected = inputs$island)
      }
      
    })
    
    output$plot = renderPlot({
      
      if (length(input$species) > 0) {
        penguins = penguins[species %in% input$species]
      }
      
      if (length(input$island) > 0) {
        penguins = penguins[island %in% input$island]
      }
      
      ggplot(penguins) + geom_histogram(aes(x = `bill_length_mm`, fill = species)) + scale_fill_canva(palette = "Striking and energetic")
    })
    
    observeEvent(input$species, ignoreNULL = TRUE, ignoreInit = TRUE, {
      inputs$species = input$species
    })
    
    observeEvent(input$island, ignoreNULL = TRUE, ignoreInit = TRUE, {
      inputs$island = input$island
    })
    
    
  })
  return(inputs)
}

flipper_mass_scatter_server = function (id) {
  

  moduleServer(id, function(input, output, session) {
    
    observeEvent(inputs$species, ignoreInit = TRUE, ignoreNULL = TRUE, {
      if (length(inputs$species) > 0) {
        updateSelectInput(session = session, inputId = "species", selected = inputs$species)
      }
    })
    
    observeEvent(inputs$island, ignoreInit = TRUE, ignoreNULL = TRUE, {
      if (length(inputs$island) > 0) {
        updateSelectInput(session = session, inputId = "island", selected = inputs$island)
      }
      
    })
    
    output$plot = renderPlot({
      
      if (length(input$species) > 0) {
        penguins = penguins[species %in% input$species]
      }
      
      if (length(input$island) > 0) {
        penguins = penguins[island %in% input$island]
      }
      
      ggplot(penguins) + geom_point(aes(x = `flipper_length_mm`, y = body_mass_g, colour = species)) + scale_colour_canva(palette = "Striking and energetic")
    })
    
    observeEvent(input$species, ignoreNULL = TRUE, ignoreInit = TRUE, {
      inputs$species = input$species
    })
    
    observeEvent(input$island, ignoreNULL = TRUE, ignoreInit = TRUE, {
      inputs$island = input$island
    })
  })
  return(inputs)
}

所以我试图在这个玩具示例中链接的两个输入是speciesisland。我已经进行了设置,以便当有人在任一输入上进行新选择时,观察者应该更新一个全局变量,在这种情况下我标记为inputs. 然后如果inputs被更新,则另一个选项卡应该更新它自己的 selectInput。

奇怪的是,我发现使用这段代码,如果我慢慢地做出选择,一切都很好!但是,当我快速连续单击 2 个以上的选项时,它会导致在当前选项卡中出现第二个选项出现的无限循环,然后消失,然后出现......等等。相反,当我选择了 3 个选项并且尝试快速连续删除选项,它只是不允许我删除所有选项!

太奇怪了。

任何人都知道我的代码有什么问题,以及如何强制两个选项卡中的输入保持与其他选项卡中选择的值相同?

谢谢!

标签: rshinyshinymodules

解决方案


我对解决这个问题的方式进行了重大调整,并提出了解决方案。基本上,我使用了 shinydashboard 并决定在我的模块之外定义species和selectInput 控件。island

然后将这些控件的值作为反应对象传递给模块,然后在绘制数据之前用于过滤数据。现在效果好多了!看看我的两个文件:

#app.R

library(data.table)
library(shiny)
library(ggplot2)
library(ggthemes)
library(shinythemes)
library(shinydashboard)
source("Modules.R")

penguins <<- as.data.table(palmerpenguins::penguins)

ui = dashboardPage(header = dashboardHeader(title = "Penguin Dashboard"),
                    sidebar = dashboardSidebar(
                     sidebarMenu(id = "tabs",
                                 selectInput("species", "Choose 1+ species:", choices = penguins[, sort(unique(species))], multiple = TRUE),
                                 selectInput("island", "Choose 1+ islands:", choices = penguins[, sort(unique(island))], multiple = TRUE),
                                 menuItem("Bill Length by Species", expandedName = "tab1", tabName = "tab1", startExpanded = TRUE,
                                          sliderInput("mass", "Select a range of body masses:", 
                                                      min = penguins[, min(body_mass_g, na.rm=TRUE)],
                                                      max = penguins[, max(body_mass_g, na.rm=TRUE)], 
                                                      value = penguins[, range(body_mass_g, na.rm=TRUE)])
                                          ),
                                 menuItem("Flipper Length by Body Mass", expandedName = "tab2", tabName = "tab2",
                                          checkboxGroupInput("sex", "Choose sex of penguins:", 
                                                             choices = c("male","female")))
                     )),
                     body = dashboardBody(
                       uiOutput("plots")
                       )
)

#inputs <<- reactiveValues(species = NULL, island = NULL)

server <- function(input, output, session) {
  #inputs <- reactiveValues(species=input$species, island=input$island)
  
  in_species = reactive({input$species})
  in_island = reactive({input$island})
  in_mass = reactive({input$mass})
  in_sex = reactive({input$sex})
  
  bill_species_server("tab1", in_species, in_island, in_mass)
  flipper_mass_scatter_server("tab2", in_species, in_island, in_sex)
  
  output$plots = renderUI({
    validate(need(!is.null(input$sidebarItemExpanded), ""))
    
    if (input$sidebarItemExpanded == "tab1") {
      ui_code("tab1")
    } else {
      ui_code("tab2")
    }
    
  })
}

# Run the application 
shinyApp(ui = ui, server = server)

#Modules.R

ui_code = function (id) {
  ns = NS(id)
  
      plotOutput(ns("plot"))
}

bill_species_server = function(id, in_species, in_island, in_mass) {
  
  moduleServer(id, function(input, output, session) {
    
    ns <- session$ns
    
    output$plot = renderPlot({

      if (length(in_species()) > 0) {
        penguins = penguins[species %in% in_species()]
      }

      if (length(in_island()) > 0) {
        penguins = penguins[island %in% in_island()]
      }
      
      penguins = penguins[body_mass_g %between% c(in_mass()[1], in_mass()[2])]
      
      ggplot(penguins) + geom_histogram(aes(x = `bill_length_mm`, fill = species)) + scale_fill_canva(palette = "Striking and energetic")
    })
  })
  
}

flipper_mass_scatter_server = function (id, in_species, in_island, in_sex) {
  

  moduleServer(id, function(input, output, session) {
    

    output$plot = renderPlot({

      if (length(in_species()) > 0) {
        penguins = penguins[species %in% in_species()]
      }
      
      if (length(in_island()) > 0) {
        penguins = penguins[island %in% in_island()]
      }
      
      if (length(in_sex()) > 0) {
        penguins = penguins[sex %in% in_sex()]
      }
      
      ggplot(penguins) + geom_point(aes(x = `flipper_length_mm`, y = body_mass_g, colour = species)) + scale_colour_canva(palette = "Striking and energetic")
    })

  })
  
}

推荐阅读