0
votes

I am working on a Shiny application in which there are two slider inputs. These inputted values subset a data frame differently, and the subset of that data frame is then plotted into a scatterplot.

I am trying to prevent the scatterplot from being replotted unless the user clicks a "Go!" button. To try to achieve this, I am using the isolate() function on the slider input values, so that the data frame and plot are only updated when the "Go!" button is changed.

This seems to be working okay, but I am also trying to allow the user to use the selection tool in Plotly and see the data frame rows that correspond to that selection. However, when I attempt to do so, I receive an error ("Error: object 'datInput' not found"). This line is commented in the example below:

library(shiny)
library(plotly)

ui <- shinyUI(pageWithSidebar(
  headerPanel("Click the button"),
  sidebarPanel(
    sliderInput("val1", "Value 1:", min = 0, max = 1, value=0.5, step=0.1),
    sliderInput("val2", "Value 2:", min = 0, max = 1, value=0.5, step=0.1),
    actionButton("goButton", "Go!")
  ),
  mainPanel(
    plotlyOutput("plot1"),
    verbatimTextOutput("click")
  )
))

server <- shinyServer(function(input, output) {

  set.seed(1)
  dat <- data.frame(Case = paste0("case",1:15), val1=runif(15,0,1), val2=runif(15,0,1))
  dat$Case <- as.character(dat$Case)

  xMax = max(dat$val1)
  xMin = min(dat$val1)
  yMax = max(dat$val2)
  yMin = min(dat$val2)
  maxTemp = max(abs(xMax), abs(xMin))

  observeEvent(input$goButton,
       output$plot1 <- renderPlotly({
         # Use isolate() to avoid dependency on input$val1 and input$val2
         datInput <- isolate(subset(dat, val1 > input$val1 & val2 > input$val2))
         p <- qplot(datInput$val1, datInput$val2) +xlim(0, ceiling(maxTemp)) +ylim(0,1)
         ggplotly(p)
       })
  )

  d <- reactive(event_data("plotly_selected"))

  output$click <- renderPrint({
    if (is.null(d())){
      "Click on a state to view event data"
    }
    else{
      #str(d()$pointNumber) #Seems to be working
      datInput[d()$pointNumber,] #Error: object 'datInput' not found
    }
  })
})

shinyApp(ui, server)

Any ideas for a workaround for this issue would be appreciated!

EDIT:

Here is the solution as per @mlegge. I simply added the output after the user selects certain points:

library(shiny)
library(plotly)

ui <- shinyUI(pageWithSidebar(
  headerPanel("Click the button"),
  sidebarPanel(
    sliderInput("val1", "Value 1:", min = 0, max = 1, value=0.5, step=0.1),
    sliderInput("val2", "Value 2:", min = 0, max = 1, value=0.5, step=0.1),
    actionButton("goButton", "Go!")
  ),
  mainPanel(
    plotlyOutput("plot1"),
    verbatimTextOutput("click")
  )
))

set.seed(1)
dat <- data.frame(Case = paste0("case",1:15), val1=runif(15,0,1), val2=runif(15,0,1))
dat$Case <- as.character(dat$Case)

xMax = max(dat$val1)
xMin = min(dat$val1)
yMax = max(dat$val2)
yMin = min(dat$val2)
maxTemp = max(abs(xMax), abs(xMin))

server <- shinyServer(function(input, output) {

  # datInput only validated once the go button is clicked
  datInput <- eventReactive(input$goButton, {
    subset(dat, val1 > input$val1 & val2 > input$val2)
  })

  output$plot1 <- renderPlotly({
    # will wait to render until datInput is validated
    plot_dat <- datInput()

    p <- qplot(plot_dat$val1, plot_dat$val2) + xlim(0, ceiling(maxTemp)) +ylim(0,1)
    ggplotly(p)
  })

  d <- reactive(event_data("plotly_selected"))
  output$click <- renderPrint({
    if (is.null(d())){
      "Click on a state to view event data"
    }
    else{
      #str(d()$pointNumber)
      datInput()[d()$pointNumber+1,] #Working now
    }
  })
})

shinyApp(ui, server)
1

1 Answers

1
votes

You are not using isolate properly, a better solution is an eventReactive:

library(shiny)
library(plotly)

ui <- shinyUI(pageWithSidebar(
  headerPanel("Click the button"),
  sidebarPanel(
    sliderInput("val1", "Value 1:", min = 0, max = 1, value=0.5, step=0.1),
    sliderInput("val2", "Value 2:", min = 0, max = 1, value=0.5, step=0.1),
    actionButton("goButton", "Go!")
  ),
  mainPanel(
    plotlyOutput("plot1")
  )
))

set.seed(1)
dat <- data.frame(Case = paste0("case",1:15), val1=runif(15,0,1), val2=runif(15,0,1))
dat$Case <- as.character(dat$Case)

xMax = max(dat$val1)
xMin = min(dat$val1)
yMax = max(dat$val2)
yMin = min(dat$val2)
maxTemp = max(abs(xMax), abs(xMin))

server <- shinyServer(function(input, output) {

  # datInput only validated once the go button is clicked
  datInput <- eventReactive(input$goButton, {
    subset(dat, val1 > input$val1 & val2 > input$val2)
  })

  output$plot1 <- renderPlotly({
    # will wait to render until datInput is validated
    plot_dat <- datInput()

    p <- qplot(plot_dat$val1, plot_dat$val2) + xlim(0, ceiling(maxTemp)) +ylim(0,1)
    ggplotly(p)
  })
})

shinyApp(ui, server)

You'll notice that your data generation has been moved outside the server, this is because it only needs to be run once.

You also should never wrap an output object in an observer, instead control the input data with reactives.

enter image description here