Обновить блестящий ввод, чтобы соответствовать другому блестящему вводу: бесконечный цикл - PullRequest
0 голосов
/ 29 сентября 2019

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

Я ожидаю, что мне скажут не делать то, что я пытаюсь сделать, но сценарий использования состоит в том, что у меня есть много вкладок в dashboardPage(), и только две из них имеют общие элементы управления.Таким образом, размещение элементов управления для этих двух страниц на боковой панели может ввести пользователя в заблуждение.

Я сделал простой, рабочий пример того, как это сделать (используя панель инструментов, чтобы сделать более понятным, почему я хочу это сделать) на основе тесно связанного вопроса , на который был дан ответубедив просящего сделать что-то еще (что сработало в их случае, но не в моем).Приложение работает нормально, за исключением того, что по мере того, как оно становится все более и более сложным, вычисления иногда занимают достаточно много времени, чтобы я мог изменить один вход, а затем другой, прежде чем сервер Shiny успел обновить значения.Это приводит к бесконечной обратной связи (вход 1 обновляется, чтобы соответствовать входу 2, в то время как вход 2 обновляется, чтобы соответствовать входу 1, и затем это повторяется до тех пор, пока я хочу наблюдать).

library(shiny)
library(shinydashboard)

ui = dashboardPage(
    dashboardHeader(title = "Example"),
    dashboardSidebar(
        sidebarMenu(
            menuItem("Tab 1", tabName = "tab1", icon = icon("chart-line")),
            menuItem("Tab 2", tabName = "tab2", icon = icon("chart-line")),
            menuItem("Other Tab", tabName = "tab3", icon = icon("project-diagram"))
        )
    ),
    dashboardBody(
        tabItems(
            # First tab content
            tabItem(tabName = "tab1",
                # Input first number
                numericInput("input1", label = "Input 1", value = 1, min=1, step=1)
                ),
            # Second tab content
            tabItem(tabName = "tab2",
                # Input second number
                numericInput("input2", label = "Input 2", value = 1, min=1, step=1)
                ),
            # Third tab content
            tabItem(tabName = "tab3", "Unrelated content")
            )
        )
    )

server = function(input, output, session) {
    # Update inputs to match each other
    observeEvent(input$input1, {
        updateSelectInput(session = session,
                          inputId = "input2",
                          selected = input$input1)})
    observeEvent(input$input2, {
        updateSelectInput(session = session,
                          inputId = "input1",
                          selected = input$input2)})
}

shinyApp(ui = ui, server = server)

Вопрос: чтоесть ли другие способы иметь отдельные страницы с соответствующими элементами управления, которые контролируют обе страницы, но без необходимости помещать эти элементы управления на каждую страницу?Подвопрос: любой из этих методов позволит избежать проблемы бесконечного цикла?Следствие: я увидел статью, в которой, как мне кажется, визуализировались страницы пользовательского интерфейса из вспомогательных сценариев и передавались входные аргументы в URL-адреса этих сценариев, и это выглядело как отличная стратегия, но сейчас я не могу найти статью и пытаюсь ее выяснитьсамостоятельно.

1 Ответ

1 голос
/ 29 сентября 2019

На самом деле все гораздо проще.Вместо просмотра числовых входов вы можете наблюдать, какая вкладка выбрана, и обновлять определенный numericInput, когда пользователь попадает на эту вкладку.Поэтому все, что нам нужно, это предоставить id для sidebarMenu (id = "tabs", ...) и наблюдать содержимое этой входной переменной:

observe({
    if (req(input$tabs) == "tab2") {
      updateSelectInput(...)
    }
  })

enter image description here


Обновленный код:

library(shiny)
library(shinydashboard)

ui = dashboardPage(
  dashboardHeader(title = "Example"),
  dashboardSidebar(
    sidebarMenu(id = "tabs",
      menuItem("Tab 1", tabName = "tab1", icon = icon("chart-line")),
      menuItem("Tab 2", tabName = "tab2", icon = icon("chart-line")),
      menuItem("Other Tab", tabName = "tab3", icon = icon("project-diagram"))
    )
  ),
  dashboardBody(
    tabItems(
      # First tab content
      tabItem(tabName = "tab1",
              # Input first number
              numericInput("input1", label = "Input 1", value = 1000, min=1, step=1),
              plotOutput("plot1")
      ),
      # Second tab content
      tabItem(tabName = "tab2",
              # Input second number
              numericInput("input2", label = "Input 2", value = 1000, min=1, step=1),
              plotOutput("plot2")
      ),
      # Third tab content
      tabItem(tabName = "tab3", "Unrelated content")
    )
  )
)

server = function(input, output, session) {
  # some (not so) long computation
  long_comp1 <- reactive({
    x <- sample(input$input1, size=10000000, replace = TRUE)
    y <- sample(input$input1, size=10000000, replace = TRUE)
    m <- matrix(x, nrow = 500, ncol=200)
    n <- matrix(y, nrow = 200, ncol=500)
    p <- n %*% m  
    p
  })
  output$plot1 <- renderPlot({
    hist(long_comp1(), main = paste("input1 is", input$input1))
  })

  # some (not so) long computation
  long_comp2 <- reactive({
    x <- sample(input$input2, size=10000000, replace = TRUE)
    y <- sample(input$input2, size=10000000, replace = TRUE)
    m <- matrix(x, nrow = 500, ncol=200)
    n <- matrix(y, nrow = 200, ncol=500)
    p <- n %*% m  
    p
  })

  output$plot2 <- renderPlot({
    hist(long_comp2(), main = paste("input2 is", input$input2))
  })

  # Update inputs to match each other
  observe({
    if (req(input$tabs) == "tab2") {
      updateSelectInput(session = session,
                        inputId = "input2",
                        selected = input$input1)
    }
  })

  observe({
    if (req(input$tabs) == "tab1") {
      updateSelectInput(session = session,
                        inputId = "input1",
                        selected = input$input2)
    }
  })
}

shinyApp(ui = ui, server = server)

Предыдущий ответ, который не сработал:

Я думаю, что это может быть решением.Один набор reactiveValues отслеживает, когда длинные вычисления завершены.Другой набор reactiveValues получает метки времени последнего изменения в input$input1 и input$input2.Функция reactive решает, нужно ли обновлять входы, основываясь на входных значениях и реактивных значениях длительного завершения задания.Наконец, входные данные обновляются на основе более поздней отметки времени двух входных виджетов.Внутри того же наблюдателя реактивные значения длинных заданий сбрасываются до FALSE, так что обновления до тех пор, пока они не будут завершены, не обновляются.Насколько я вижу, он избегает бесконечного цикла.

Альтернативой является установка наблюдателя, который обновляет входные данные, чтобы реагировать на кнопку, чтобы пользователь мог решить, когда распространять входные значения на другие вкладки.Если вычисления действительно длинные, то это потенциально сэкономит время, поскольку вычисления на другой вкладке будут выполняться только после того, как пользователь убедится, что ему нужно новое входное значение.


Код:

library(shiny)
library(shinydashboard)

ui = dashboardPage(
  dashboardHeader(title = "Example"),
  dashboardSidebar(sidebarMenu(
    menuItem("Tab 1", tabName = "tab1", icon = icon("chart-line")),
    menuItem("Tab 2", tabName = "tab2", icon = icon("chart-line")),
    menuItem(
      "Other Tab",
      tabName = "tab3",
      icon = icon("project-diagram")
    )
  )),
  dashboardBody(tabItems(
    # First tab content
    tabItem(
      tabName = "tab1",
      # Input first number
      numericInput(
        "input1",
        label = "Input 1",
        value = 1000,
        min = 1,
        step = 1
      ),
      plotOutput("plot1")
    ),
    # Second tab content
    tabItem(
      tabName = "tab2",
      # Input second number
      numericInput(
        "input2",
        label = "Input 2",
        value = 1000,
        min = 1,
        step = 1
      ),
      plotOutput("plot2")
    ),
    # Third tab content
    tabItem(tabName = "tab3", "Unrelated content")
  ))
)

server = function(input, output, session) {

  # monitor whether long jobs are done
  long_jobs <- reactiveValues(job1=FALSE, job2=FALSE)

  # some (not so) long computation
  long_comp1 <- reactive({
      a <- input$input1
      x <- sample(a, size=100000000, replace = TRUE)
      y <- sample(a, size=100000000, replace = TRUE)
      m <- matrix(x, nrow = 5000, ncol=200)
      n <- matrix(y, nrow = 200, ncol=5000)
      p <- n %*% m  
      p
  })

  output$plot1 <- renderPlot({
    hist(long_comp1(), main = paste("input1 is", input$input1))
    long_jobs$job1 <- TRUE
  })

  # some (not so) long computation
  long_comp2 <- reactive({
    a <- input$input2
    x <- sample(a, size=100000000, replace = TRUE)
    y <- sample(a, size=100000000, replace = TRUE)
    m <- matrix(x, nrow = 5000, ncol=200)
    n <- matrix(y, nrow = 200, ncol=5000)
    p <- n %*% m  
    p
  })

  output$plot2 <- renderPlot({
    hist(long_comp2(), main = paste("input2 is", input$input2))
    long_jobs$job2 <- TRUE
  })

  # monitor when inputs have last changed
  time_stamps <- reactiveValues()

  observeEvent(input$input1, {
    time_stamps$in1_ts <- Sys.time()
  })

  observeEvent(input$input2, {
    time_stamps$in2_ts <- Sys.time()
  })

  # update if inputs are different and long jobs are done
  update_inputs <- reactive({
    if (req(input$input1) != req(input$input2) & all(c(long_jobs$job1, long_jobs$job2))) {
      TRUE
    } else {
      FALSE
    }
  })

  # Update inputs to match each other
  # taking the input that changed last
  observeEvent(update_inputs(), {
    new_input <- ifelse(time_stamps$in1_ts > time_stamps$in2_ts, "input1", "input2")
    updateSelectInput(session = session,
                      inputId = "input2",
                      selected = input[[new_input]])
    updateSelectInput(session = session,
                      inputId = "input1",
                      selected = input[[new_input]])
    # reset the long job reactive values
    # because now the inputs have changed and 
    # plots need to be recalculated
    long_jobs$job1 <- FALSE
    long_jobs$job2 <- FALSE
  })

}

shinyApp(ui = ui, server = server)
...