1

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)
  }
})
job123
  • 11
  • 3
  • Have a look [here](https://stackoverflow.com/questions/32301813/adding-a-layer-to-the-current-plot-without-creating-a-new-one-in-ggplot2)...but seems to be impossible so far with ggpplot. Instead use base `plot()` and `lines()`. – Roman Jun 08 '17 at 08:07
  • I don’t see how to use `plot()` and `lines()` to achieve this? When `input$fun` is invalidated, shiny will re-run `fn1 <- reactive({…})`, `fn2 <- reactive({…})` and `fn3 <- reactive({})` every time. I liked the link you provided, thanks very much, that technique is a very useful option to have, I will probably use it – job123 Jun 09 '17 at 01:42

3 Answers3

0

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()
    })  
  })
))
Benjamin
  • 16,897
  • 6
  • 45
  • 65
  • Thank you very much, that was very helpful. I can achieve the desired efficiency with single checkboxes (`checkboxInput()`), they just don’t look as good in the `sidebarPanel` – job123 Jun 09 '17 at 02:02
0

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)
    })  
  })
))
Roman
  • 17,008
  • 3
  • 36
  • 49
  • Thank you very much, that is an improvement in performance. Shiny is still running the code for each checked function every time a box is ticked or unticked, as you can see from the `cat()` and `print()` statements I have added into `output$plot <- renderPlot({})` as an edit at the end of my original post. In my app, each function will take several minutes to compute, which is why I need to improve performance. Is there anything more with base R plot that can be done? – job123 Jun 09 '17 at 21:51
0

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() 
    })

  })
))
job123
  • 11
  • 3