简体   繁体   中英

How can I use checkboxGroupInput in Shiny to control several reactive layers in a ggplot2 plot efficiently?

I have made three reactive layers in my graph. In the reproducible example below, the graph starts with function1 drawn. If I check function2, shiny recalculates and redraws function1 and function2. Then if I tick function3, all 3 functions are recalculated and redrawn.

Say the functions I want to run are very long inferences that take several minutes each.

How can I make it so that when I check (or uncheck) one function, shiny does not recalculate and redraw all checked functions?

In the code below, I have included print statements which show that each reactive is run each time renderPlot is called (which is when input$fun changes).

library(shiny)
library(ggplot2)
x <-  seq(0, 10, by=0.1)

runApp(shinyApp(

  ui = shinyUI(fluidPage(
    titlePanel("Test Shiny"),
    sidebarLayout(
      sidebarPanel(
        checkboxGroupInput("fun", label = "Function", 
                           choices = list("function1: x^2" = 1, 
                                          "function2: x^2 + x" = 2, 
                                          "function3: x^2 - x" = 3),
                           selected = c(1))
      ),
      mainPanel(
        plotOutput("plot")
      )
    )
  )),

  server = shinyServer(function(input, output) {


    fn1 <- reactive({ 
      print("we are in fn1 <- reactive({})")
      if (1 %in% input$fun ) { 
        geom_line(mapping = aes(x, y=x^2), color="blue") }
    })


    fn2 <- reactive({
      print("we are in fn2 <- reactive({})")
      if (2 %in% input$fun)  { 
        geom_line(mapping = aes(x, y=x^2 + x), color="red") }
    })


    fn3 <- reactive({
      print("we are in fn3 <- reactive({})")
      if (3 %in% input$fun) { 
        geom_line(mapping = aes(x, y=x^2 - x), color="green") }
    })

    output$plot <- renderPlot({

      cat("\n we are in output$plot <-  renderPlot({}) \n")
      ggplot() + fn1() + fn2() + fn3()
    })  
  })
))

I can achieve this efficiency using single checkboxes (checkboxInput) but I would prefer not to use single checkboxes. Single checkboxes don't look as good, unless there is a way to make them look more like checkbox Group Inputs?

I have been trying to work this out and searching SO for some time. I would be very grateful for any help with this!!!

EDIT Here is some code in response to @Jimbou 's shiny code using base R plot() and lines() . Please see my comment below the shiny code @Jimbou provided.

    output$plot <- renderPlot({
  cat("\n we are in output$plot <- renderPlot({}) \n")
  plot(NULL, xlim = c(0,10), ylim = c(0,100))
  if(1 %in% input$fun) {
    print("we are in  if(1 %in% input$fun){} ")
    lines(x=x, y=x^2, col=2)
  }
  if(2 %in% input$fun) {
    print("we are in  if(2 %in% input$fun){} ")
    lines(x=x, y=x^2 + x, col=3)
  }
  if(3 %in% input$fun) {
    print("we are in  if(3 %in% input$fun){} ")
    lines(x=x, y=x^2 - x, col=4)
  }
})

I think the closest you can get is to delay the processing of your layers until you click an action button to explicitly tell the server when to start processing the plot.

library(shiny)
library(ggplot2)
x <-  seq(0, 10, by=0.1)

runApp(shinyApp(

  ui = shinyUI(fluidPage(
    titlePanel("Test Shiny"),
    sidebarLayout(
      sidebarPanel(
        checkboxGroupInput("fun", label = "Function", 
                           choices = list("function1: x^2" = 1, 
                                          "function2: x^2 + x" = 2, 
                                          "function3: x^2 - x" = 3),
                           selected = c(1)),
        actionButton(inputId = "btn_update_plot",
                     label = "Update Plot")
      ),
      mainPanel(
        plotOutput("plot")
      )
    )
  )),

  server = shinyServer(function(input, output) {


    p <- eventReactive(
      input$btn_update_plot,
      {
        ggp = ggplot()

        if (1 %in% input$fun ) 
        { 
          ggp <- ggp + geom_line(mapping = aes(x, y=x^2), color="blue") 
        }

        if (2 %in% input$fun)  
        { 
          ggp <- ggp + geom_line(mapping = aes(x, y=x^2 + x), color="red") 
        }

        if (3 %in% input$fun) 
        { 
          ggp <- ggp + geom_line(mapping = aes(x, y=x^2 - x), color="green") 
        }

        ggp
      }
    )

    output$plot <- renderPlot({
      print("we are in output$plot <-  renderPlot({})")
      p()
    })  
  })
))

Here a solution using the base R plotting functionality with lines.

library(shiny)
library(ggplot2)
x <-  seq(0, 10, by=0.1)

runApp(shinyApp(

  ui = shinyUI(fluidPage(
    titlePanel("Test Shiny"),
    sidebarLayout(
      sidebarPanel(
        checkboxGroupInput("fun", label = "Function", 
                           choices = list("function1: x^2" = 1, 
                                          "function2: x^2 + x" = 2, 
                                          "function3: x^2 - x" = 3),
                           selected = c(1))
      ),
      mainPanel(
        plotOutput("plot")
      )
    )
  )),

  server = shinyServer(function(input, output) {

    output$plot <- renderPlot({
      plot(NULL, xlim = c(0,10), ylim = c(0,100))
      if(1 %in% input$fun) lines(x=x, y=x^2, col=2)
      if(2 %in% input$fun) lines(x=x, y=x^2 + x, col=3)
      if(3 %in% input$fun) lines(x=x, y=x^2 - x, col=4)
    })  
  })
))

I have thought about a way to do this. In the code below, the checkboxGroupInput has options “function1”, “function2” or “function3” as before. If “function1” and “function2” are checked, and then the user checks “function3”, only function3 will be calculated. In the code in the original post, shiny would compute all three functions.

Any change in the checkboxGroupInput will invalidate all 3 of these reactive expressions fn1 <- reactive({...}) , fn2 <- reactive({...}) , fn3 <- reactive({...}) causing them to be re-run.

However in the new code below, each reactive expression above (if it has already been checked) returns a cached value via another reactive expression

I have included print statements to show that checking or unchecking one box in the checkboxGroupInput (“function1”, “function2” or “function3”) will not cause Shiny to recalculate all the checked functions as shiny would have done in my original code above

I mentioned using single checkboxes before, so I have also included an example of a single checkbox, “relation4”, to show that using a single checkbox, checkboxInput(), does not cause other reactives to re-running unnecessarily

NB watch what is printed to the console

library(shiny)
library(ggplot2)

runApp(shinyApp(

  ui = shinyUI(fluidPage(
    titlePanel("Test Shiny"),
    sidebarLayout(
      sidebarPanel(
        checkboxGroupInput("fun", label = "Function", 
                           choices = list("function1: x^2" = 1, 
                                          "function2: x^2 + x" = 2, 
                                          "function3: x^2 - x" = 3),
                           selected = c(1)),
        h5("A single checkbox"),
        checkboxInput("relation4", label = "relation4", value = FALSE)
      ),
      mainPanel(
        plotOutput("plot")
      )
    )
  )),

  server = shinyServer(function(input, output) {

    x <-  seq(0, 10, by=0.1)

    fn1.geom <- reactive({
      print("we are in fn1.geom <- reactive({})")
      geom_line(mapping = aes(x, y=x^2), color="blue")
    })
    fn1 <- reactive({
      print("we are in fn1 <- reactive({})")
      if (1 %in% input$fun ) { 
        fn1.geom()}
    })

    fn2.geom <- reactive({
      print("we are in fn2.geom <- reactive({})")
      geom_line(mapping = aes(x, y=x^2 + x), color="red") 
    })
    fn2 <- reactive({
      print("we are in fn2 <- reactive({})")
      if (2 %in% input$fun)  { 
        fn2.geom()}
    })

    fn3.geom <- reactive({
      print("we are in fn3.geom <- reactive({})")
      geom_line(mapping = aes(x, y=x^2 - x), color="green")
    })
    fn3 <- reactive({
      print("we are in fn3 <- reactive({})")
      if (3 %in% input$fun) { 
        fn3.geom() }
    })


    # using single checkbox input (checkboxInput() above)
    relation4.geom <- reactive({
      print("we are in relation4.geom <- reactive({})")
        list( geom_line(mapping = aes(x, y=x), color="orange"),
              geom_line(mapping = aes(x, y=6*x), color="purple"),
              geom_line(mapping = aes(x, y=11*x), color="violet") ) 
    })
    relation4 <- reactive({
      print("we are in relation4 <- reactive({})")
      if (input$relation4) {
        relation4.geom()
      }
    })


    output$plot <- renderPlot({
      cat("\n we are in output$plot <-  renderPlot({}) \n")
      ggplot() + fn1() + fn2() + fn3() + relation4() 
    })

  })
))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM