В R Shiny можно использовать несколько условий в условной панели?

#r #shiny #shiny-reactivity

Вопрос:

В приведенном ниже коде MWE две вкладки, созданные при выполнении кода, «Модуль обязательств» и «Процентные ставки», идентичны. Они предназначены как два разных пути к одной и той же таблице данных и диаграммам (показывающим скорости), которые в настоящее время генерируются при выполнении кода.

Но эти 2 вкладки должны расходиться по мере их дальнейшего развития с точки зрения других кнопок действий на боковой панели и с точки зрения кнопок действий, появляющихся в верхней части основных панелей для каждой из соответствующих вкладок. Для простого примера я хотел бы добавить кнопку действия «Тест» в модуль «Обязательства», но не в модуль «Процентные ставки».

Как бы я добавил несколько условий на условную панель, чтобы в этом случае кнопка действия «Тест» отображалась в модуле «Обязательства», но не во вкладках «Процентные ставки»? Как показано на рисунке внизу.

Моя скромная попытка сделать это отмечена # ПОПЫТКА # в приведенном ниже MWE; естественно, это не работает, поэтому мне пришлось это прокомментировать.

Код MWE:

 library(shiny)
library(shinyMatrix)
library(shinyjs)

matrix4Default <- matrix(c(0.2), 4, 1, dimnames = list(c("A", "B", "C", "D"), NULL))

matrix4Input <- function(x, matrix4Input) {
  matrixInput(
    x,
    value = matrix4Input,
    rows = list(extend = FALSE, names = TRUE),
    cols = list(
      extend = FALSE,
      names = FALSE,
      editableNames = FALSE
    ),
    class = "numeric"
  )
}

vectorBaseRate <- function(x, y) {
  a <- rep(y, x)
  b <- seq(1:x)
  c <- data.frame(x = b, y = a)
  return(c)
}

vectorBaseRatePlot <- function(w, x, y, z) {
  plot(
    w[, 1],
    sapply(w[, 2], function(x)
      gsub("%", "", x)),
    main = x,
    xlab = y,
    ylab = z
  )
}

ui <- pageWithSidebar(
  headerPanel("Model..."),
  sidebarPanel(fluidRow(helpText(h5(
    strong("Base Input Panel")
  ))), uiOutput("Panels")),
  mainPanel(tabsetPanel(
    tabPanel(
      "Liabilities module",
      value = 4,
      fluidRow(
        radioButtons(
          inputId = "showRates4",
          label = h5(strong(helpText(
            "Select model output to view:"
          ))),
          choices = c('Rates values', 'Rates plots'),
          selected = 'Rates values',
          inline = TRUE
        ),
        uiOutput('showTab4Results')
      )
    ),
    # close tab panel
    tabPanel(
      "Interest rates",
      value = 5,
      fluidRow(
        radioButtons(
          inputId = "showRates5",
          label = h5(strong(helpText(
            "Select model output to view:"
          ))),
          choices = c('Rates values', 'Rates plots'),
          selected = 'Rates values',
          inline = TRUE
        ),
        uiOutput('showTab5Results')
      )
    ),
    # close tab panel
    id = "tabselected"
  ))
) # close tabset panel, main panel, page with sidebar

server <- function(input, output, session) {
  matrix4   <- reactive(input$matrix4)
  baseRate  <-
    function() {
      vectorBaseRate(60, input$matrix4[1, 1])
    } # Must remain in server section
  
  output$Panels <- renderUI({
    conditionalPanel(
      condition = "input.tabselected==4 || input.tabselected==5", actionButton('modRates', 'Modify Rates'),
      # ATTEMPT # condition = "input.tabselected==4", actionButton('test','Test')
    ) # close conditional panel
  }) # close renderUI
  
  vectorRates <- reactive({
    if (is.null(input$modRates)){DF <- NULL}
    else {
      if (input$modRates < 1) {DF <- cbind(Period = 1:60, BaseRate = 0.2)}
      else {
        req(input$matrix4)
        DF <- cbind(Period = 1:60, BaseRate = baseRate()[, 2])
      } # close 2nd else
    } # close 1st else
    DF
  }) # close reactive
  
  observeEvent(input$resetRatesStruct, {updateMatrixInput(session, 'matrix4', matrix4Default)})
  
  output$table5 <- output$table4 <- renderTable({vectorRates()})
  
  output$graph5 <- output$graph4 <- renderPlot({
    vectorBaseRatePlot(vectorRates(), "A Variable", "Period", "Rate")
  })
  
  output$showTab4Results <- renderUI({
    if (input$showRates4 == 'Rates values'){tableOutput("table4")} 
    else {plotOutput("graph4")}
  })
  
  output$showTab5Results <- renderUI({
    if (input$showRates5 == 'Rates values'){tableOutput("table5")} 
    else {plotOutput("graph5")}
  })
  
  observeEvent(input$modRates,
               {
                 showModal(modalDialog(
                   matrix4Input("matrix4", if (is.null(input$matrix4))
                     matrix4Default
                     else
                       input$matrix4),
                   useShinyjs(),
                   footer = tagList(
                     actionButton("resetRatesStruct", "Reset"),
                     modalButton("Close")
                   )
                 ))
               } # close modalDialog, showModal, and showModal function
  ) # close observeEvent
} # close server

shinyApp(ui, server)
 

введите описание изображения здесь

Ответ №1:

Вы можете просто добавить еще conditionalPanel один, используя другое условие.

Кроме того, я отбросил все renderUI , так как нет необходимости создавать панели условий на стороне сервера. Это должно привести к более быстрому пользовательскому интерфейсу.

Я добавил еще несколько кнопок, чтобы показать концепцию:

 library(shiny)
library(shinyMatrix)
library(shinyjs)

matrix4Default <- matrix(c(0.2), 4, 1, dimnames = list(c("A", "B", "C", "D"), NULL))

matrix4Input <- function(x, matrix4Input) {
  matrixInput(
    x,
    value = matrix4Input,
    rows = list(extend = FALSE, names = TRUE),
    cols = list(
      extend = FALSE,
      names = FALSE,
      editableNames = FALSE
    ),
    class = "numeric"
  )
}

vectorBaseRate <- function(x, y) {
  a <- rep(y, x)
  b <- seq(1:x)
  c <- data.frame(x = b, y = a)
  return(c)
}

vectorBaseRatePlot <- function(w, x, y, z) {
  plot(
    w[, 1],
    sapply(w[, 2], function(x)
      gsub("%", "", x)),
    main = x,
    xlab = y,
    ylab = z
  )
}

ui <- pageWithSidebar(
  headerPanel("Model..."),
  sidebarPanel(fluidRow(helpText(h5(
    strong("Base Input Panel")
  ))),
  conditionalPanel(
    condition = "input.tabselected==4 || input.tabselected==5", actionButton('modRates', 'Modify Rates')
  ), # close conditional panel
  conditionalPanel(
    condition = "input.tabselected==4", actionButton('test1','Test')
  )
  ),
  mainPanel(
    conditionalPanel(
      condition = "input.tabselected==4", actionButton('test2','A mainPanel test button')
    ),
    conditionalPanel(
      condition = "input.tabselected==5", actionButton('test3','Another mainPanel test button')
    ),
    tabsetPanel(
      selected = 4,
      conditionalPanel(
        condition = "input.tabselected==4", actionButton('test4','A tabsetPanel test button')
      ),
      conditionalPanel(
        condition = "input.tabselected==5", actionButton('test5','Another tabsetPanel test button')
      ),
      tabPanel(
        "Liabilities module",
        value = 4,
        fluidRow(
          radioButtons(
            inputId = "showRates4",
            label = h5(strong(helpText(
              "Select model output to view:"
            ))),
            choices = c('Rates values', 'Rates plots'),
            selected = 'Rates values',
            inline = TRUE
          ),
          conditionalPanel(condition = "input.showRates4 == 'Rates values'", tableOutput("table4")),
          conditionalPanel(condition = "input.showRates4 == 'Rates plots'", plotOutput("graph4"))
        )
      ),
      # close tab panel
      tabPanel(
        "Interest rates",
        value = 5,
        fluidRow(
          radioButtons(
            inputId = "showRates5",
            label = h5(strong(helpText(
              "Select model output to view:"
            ))),
            choices = c('Rates values', 'Rates plots'),
            selected = 'Rates values',
            inline = TRUE
          ),
          conditionalPanel(condition = "input.showRates5 == 'Rates values'", tableOutput("table5")),
          conditionalPanel(condition = "input.showRates5 == 'Rates plots'", plotOutput("graph5"))
        )
      ),
      # close tab panel
      id = "tabselected"
    ))
) # close tabset panel, main panel, page with sidebar

server <- function(input, output, session) {
  matrix4   <- reactive(input$matrix4)
  baseRate  <- function() {
    vectorBaseRate(60, input$matrix4[1, 1])
  } # Must remain in server section
  
  vectorRates <- reactive({
    if (is.null(input$modRates)){DF <- NULL}
    else {
      if (input$modRates < 1) {DF <- cbind(Period = 1:60, BaseRate = 0.2)}
      else {
        req(input$matrix4)
        DF <- cbind(Period = 1:60, BaseRate = baseRate()[, 2])
      } # close 2nd else
    } # close 1st else
    DF
  }) # close reactive
  
  observeEvent(input$resetRatesStruct, {updateMatrixInput(session, 'matrix4', matrix4Default)})
  
  output$table5 <- output$table4 <- renderTable({vectorRates()})
  
  output$graph5 <- output$graph4 <- renderPlot({
    vectorBaseRatePlot(vectorRates(), "A Variable", "Period", "Rate")
  })
  
  observeEvent(input$modRates,
               {
                 showModal(modalDialog(
                   matrix4Input("matrix4", if (is.null(input$matrix4))
                     matrix4Default
                     else
                       input$matrix4),
                   useShinyjs(),
                   footer = tagList(
                     actionButton("resetRatesStruct", "Reset"),
                     modalButton("Close")
                   )
                 ))
               } # close modalDialog, showModal, and showModal function
  ) # close observeEvent
} # close server

shinyApp(ui, server)
 

Комментарии:

1. Спасибо, вы также показываете, как добавлять дополнительные кнопки в разных возможных местах, и именно к этому я и веду. Также вы показываете, как у вас может быть несколько условных панелей для одного и того же «условия =». Работает в разделе пользовательского интерфейса. Чтобы сделать это в renderUI, я публикую другой ответ (я больше возился с ним), в который вы можете вложить условные панели. Причина, по которой я визуализировал панели в разделе сервера с помощью renderUI, заключалась в том, чтобы остановить эту странную вспышку при первом вызове приложения. Но я попробую ваше предложение о перемещении условных панелей обратно в пользовательский интерфейс и посмотрю, есть ли еще мигание вызова.

2. По моему опыту, использование consitionalPanel является наиболее гибким решением для введения условных элементов пользовательского интерфейса. С renderUI вами нужно дождаться сервера и завершения всей необходимой связи. До сих пор я не видел никаких вспышек в приложениях, которые я создал.

3. Я конвертирую свой код not-MWE, как вы рекомендуете, устраняя renderUI и перемещая все условные панели в раздел пользовательского интерфейса. Это, безусловно, течет лучше и интуитивно имеет больше смысла. Я проверю мигание вызова в коде not-MWE (я его уже вижу) и опубликую этот код, чтобы узнать, есть ли другой способ справиться с миганием блестящего вызова. РендерУИ действительно накладывает некоторые ограничения и рад отойти от этого!

4. Звучит хорошо — дайте мне знать, если у вас есть MWE, показывающий эту проблему с миганием. Овации

5. Я закончил переносить все условные панели с сервера в раздел пользовательского интерфейса, устраняя renderUI и его сложности в полном модуле, из которого был извлечен этот MWE. Никаких вспышек вызова! По крайней мере, для этого модуля «Обязательства». Я собираюсь попробовать те же модификации для другого связанного модуля, надеюсь, в этом случае не будет мигания вызова. Этому методу пользовательского интерфейса, безусловно, легче следовать.

Ответ №2:

Вы можете использовать оператор if, так как вы визуализируете пользовательский интерфейс на сервере. В качестве альтернативы вы можете создать всю боковую панель на сервере, что даст вам немного больше гибкости. Теперь вы можете фактически удалить всю условную панель и просто сгенерировать кнопки для использования в пользовательском интерфейсе, используя значения, выбранные на вкладке «Ввод», в качестве условий, это зависит от вас.

 output$Panels <- renderUI({
    conditionalPanel(
      condition = "input.tabselected==4 || input.tabselected==5", 
      actionButton('modRates', 'Modify Rates'),
      if(input$tabselected == 4){
        actionButton('test','Test')
      }
      # ATTEMPT # condition = "input.tabselected==4", actionButton('test','Test')
    ) # close conditional panel
  }) # close renderUI
 

Комментарии:

1. Привет, Винсент, это помещает кнопку действия «Тест» на вкладку 4, а не на вкладку 5, как предполагалось. Однако до этого изменения любые изменения на вкладке 4 (путем нажатия кнопки «Изменить ставки» на боковой панели и внесения изменений во входную сетку в модальном диалоговом окне) мгновенно отражались в обеих таблицах/графиках на вкладках 4 и 5. И наоборот. Но с вашим изменением изменение на вкладке 4 не отображается на вкладке 5 до тех пор, пока снова не будет нажата кнопка «Изменить ставки», и наоборот. Знаете ли вы, как восстановить реактивность, которая была там раньше?

Ответ №3:

Ниже приведен полный код MWE для решения этой проблемы при одновременном отображении условных панелей в server разделе с использованием renderUI (возможно, я ошибаюсь, еще немного протестирую, но этот renderUI метод, я думаю, решил некоторые проблемы с миганием вызова приложения).

Вот вложенность условных панелей, которая решила эту проблему для меня:

 output$Panels <- renderUI({
    conditionalPanel(
      condition = "input.tabselected==4 || input.tabselected==5", actionButton('modRates', 'Modify Rates'),
        conditionalPanel(
          condition = "input.tabselected==4", actionButton('test', 'Test'),
        ) # close 2nd conditional panel
    ) # close 1st conditional panel
  }) # close renderUI
 

Завершите MWE, включив вышеуказанное:

 library(shiny)
library(shinyMatrix)
library(shinyjs)

matrix4Default <- matrix(c(0.2), 4, 1, dimnames = list(c("A", "B", "C", "D"), NULL))

matrix4Input <- function(x, matrix4Input) {
  matrixInput(
    x,
    value = matrix4Input,
    rows = list(extend = FALSE, names = TRUE),
    cols = list(
      extend = FALSE,
      names = FALSE,
      editableNames = FALSE
    ),
    class = "numeric"
  )
}

vectorBaseRate <- function(x, y) {
  a <- rep(y, x)
  b <- seq(1:x)
  c <- data.frame(x = b, y = a)
  return(c)
}

vectorBaseRatePlot <- function(w, x, y, z) {
  plot(
    w[, 1],
    sapply(w[, 2], function(x)
      gsub("%", "", x)),
    main = x,
    xlab = y,
    ylab = z
  )
}

ui <- pageWithSidebar(
  headerPanel("Model..."),
  sidebarPanel(fluidRow(helpText(h5(
    strong("Base Input Panel")
  ))), uiOutput("Panels")),
  mainPanel(tabsetPanel(
    tabPanel(
      "Liabilities module",
      value = 4,
      fluidRow(
        radioButtons(
          inputId = "showRates4",
          label = h5(strong(helpText(
            "Select model output to view:"
          ))),
          choices = c('Rates values', 'Rates plots'),
          selected = 'Rates values',
          inline = TRUE
        ),
        uiOutput('showTab4Results')
      )
    ),
    # close tab panel
    tabPanel(
      "Interest rates",
      value = 5,
      fluidRow(
        radioButtons(
          inputId = "showRates5",
          label = h5(strong(helpText(
            "Select model output to view:"
          ))),
          choices = c('Rates values', 'Rates plots'),
          selected = 'Rates values',
          inline = TRUE
        ),
        uiOutput('showTab5Results')
      )
    ),
    # close tab panel
    id = "tabselected"
  ))
) # close tabset panel, main panel, page with sidebar

server <- function(input, output, session) {
  
  matrix4   <- reactive(input$matrix4)
  baseRate  <-
    function() {vectorBaseRate(60, input$matrix4[1, 1])} # Must remain in server section
  
  output$Panels <- renderUI({
    conditionalPanel(
      condition = "input.tabselected==4 || input.tabselected==5", actionButton('modRates', 'Modify Rates'),
        conditionalPanel(
          condition = "input.tabselected==4", actionButton('test', 'Test'),
        ) # close 2nd conditional panel
    ) # close 1st conditional panel
  }) # close renderUI
  
  vectorRates <- reactive({
    if (is.null(input$modRates)){DF <- NULL}
    else {
      if (input$modRates < 1) {DF <- cbind(Period = 1:60, BaseRate = 0.2)}
      else {
        req(input$matrix4)
        DF <- cbind(Period = 1:60, BaseRate = baseRate()[, 2])
      } # close 2nd else
    } # close 1st else
    DF
  }) # close reactive
  
  observeEvent(input$resetRatesStruct, {updateMatrixInput(session, 'matrix4', matrix4Default)})
  
  output$table5 <- output$table4 <- renderTable({vectorRates()})
  
  output$graph5 <- output$graph4 <- renderPlot({
    vectorBaseRatePlot(vectorRates(), "A Variable", "Period", "Rate")
  })
  
  output$showTab4Results <- renderUI({
    if (input$showRates4 == 'Rates values'){tableOutput("table4")} 
    else {plotOutput("graph4")}
  })
  
  output$showTab5Results <- renderUI({
    if (input$showRates5 == 'Rates values'){tableOutput("table5")} 
    else {plotOutput("graph5")}
  })
  
  observeEvent(input$modRates,
               {
                 showModal(modalDialog(
                   matrix4Input("matrix4", if (is.null(input$matrix4))
                     matrix4Default
                     else
                       input$matrix4),
                   useShinyjs(),
                   footer = tagList(
                     actionButton("resetRatesStruct", "Reset"),
                     modalButton("Close")
                   )
                 ))
               } # close modalDialog, showModal, and showModal function
  ) # close observeEvent
} # close server

shinyApp(ui, server)