DeanAttali
DeanAttali

Reputation: 26323

Is it possible to "clear" the brushed area of a plot in shiny?

Plots in shiny support click and brush handlers. Is it possible to "clear"/"remove"/"delete" the brushed rectangle without having the user click elsewhere on the plot? For example, if I wanted to just store the brushed coordinates once the brush is finished and then clear the plot, this is the code I would use but I don't know how to do the clearing bit.

library(ggplot2)
library(shiny)

runApp(shinyApp(
  ui = fluidPage(
    plotOutput("plot",
               brush = brushOpts("plotBrush", delay = 5000)),
    actionButton("clear", "Clear")
  ),
  server = function(input, output, session) {
    values <- reactiveValues(brush = NULL)

    output$plot <- renderPlot({
      ggplot(cars, aes(speed, dist)) + geom_point()
    })

    brush <- reactive({
      input$plotBrush
    })

    observeEvent(input$clear, {
      cat(str(brush()))
      # clear the brushed area
    })
  }
))

Upvotes: 9

Views: 4071

Answers (3)

nteetor
nteetor

Reputation: 1234

As of Shiny version 0.14, it is possible to use the session object to reset a plot's brush.

Below is a simple Shiny app demoing the use of session$resetBrush(<BRUSH_ID>) to clear away a brushed region. The app allows one to highlight a region of points or remove the brushed region while keeping the points highlighted or remove the brushed region and reset the color of the points.

See about halfway down at https://shiny.rstudio.com/reference/shiny/latest/session.html for the official documentation.

library(shiny)
library(ggplot2)

shinyApp(
  ui = fluidPage(
    plotOutput(
      outputId = "plot",
      brush = brushOpts(
        id = "plotBrush", 
        delay = 5000
      )
    ),
    actionButton("clearBrush", "Clear brush"),
    actionButton("resetPlot", "Reset plot")
  ),
  server = function(input, output, session) {
    output$plot <- renderPlot({
      ggplot(mtcars, aes(wt, mpg)) + 
        geom_point() +
        geom_point(
          data = brushedPoints(mtcars, brush),
          color = "#79D8CB",
          size = 2
        )
    })

    brush <- NULL
    makeReactiveBinding("brush")

    observeEvent(input$plotBrush, {
      brush <<- input$plotBrush
    })

    observeEvent(input$clearBrush, {
      session$resetBrush("plotBrush")
    })

    observeEvent(input$resetPlot, {
      session$resetBrush("plotBrush")
      brush <<- NULL
    })
  }
)

Upvotes: 10

Carson
Carson

Reputation: 2757

I find myself in a similar situation where I have multiple brushes and need a button to "clear the world". I haven't found an official way to remove the brush div with R code, but it turns out there is this awesome package called shinyjs ;)

library(shiny)
library(shinyjs)

ui <- fluidPage(
  useShinyjs(),
  actionButton("clear", "Clear brush"),
  fluidRow(
    column(
      width = 6,
      plotOutput("p1", brush = brushOpts("b1"))
    ),
    column(
      width = 6,
      plotOutput("p2", brush = brushOpts("b2"))
    )
  ),
  fluidRow(
    column(
      width = 6,
      verbatimTextOutput("brush1")
    ),
    column(
      width = 6,
      verbatimTextOutput("brush2")
    )
  )
)

server <- function(input, output) {

  values <- reactiveValues(
    brush1 = NULL,
    brush2 = NULL
  )

  # update reactive values when input values change
  observe({
    values$brush1 <- input$b1
    values$brush2 <- input$b2
  })

  # display brush details
  output$brush1 <- renderPrint({
    values$brush1
  })

  output$brush2 <- renderPrint({
    values$brush2
  })

  # clear brush values and remove the div from the page
  observeEvent(input$clear, {
    values$brush1 <- NULL
    values$brush2 <- NULL
    runjs("document.getElementById('p1_brush').remove()")
    runjs("document.getElementById('p2_brush').remove()")
  })

  output$p1 <- renderPlot({
    input$clear
    m <- brushedPoints(mtcars, values$brush1, allRows = TRUE)
    qplot(data = m, wt, mpg, colour = selected_) + 
      theme(legend.position = "none")
  })

  output$p2 <- renderPlot({
    input$clear
    m <- brushedPoints(mtcars, values$brush2, allRows = TRUE)
    qplot(data = m, wt, mpg, colour = selected_) +
      theme(legend.position = "none")
  })

}

shinyApp(ui, server)

IMO, shiny should really provide something like:

clearBrush <- function(id) {
  shinyjs::runjs(sprintf("document.getElementById('%s_brush').remove()", id))
}

Upvotes: 5

jpshanno
jpshanno

Reputation: 261

First a note on your server arguments. To assign reactiveValues you have to do it inside a reactive expression. So to capture the brush coordinates you need to use this

  observeEvent(input$plotBrush,{
    if(is.null(values$brush)){
    values$brush <- input$plotBrush}
  })

instead of this

brush <- reactive({
      input$plotBrush
    })

The second version creates a function called brush that you would call with brush().

One way to clear the plot upon brush is to test if values$brush is null, and alter what you do based on that. In this case if values$brush is not null then a blank plot is displayed and new points cannot be selected.

library(ggplot2)
library(shiny)

runApp(list(
  ui = fluidPage(
    plotOutput("plot",
               brush = brushOpts("plotBrush", 
                                 delay = 5000,
                                 resetOnNew = TRUE)
               # resetOnNew = TRUE clears the brush
               # each time a new plot is displayed.
               ),
    p("Brushed Points:"),
    verbatimTextOutput("brushedPoints")
  ),
  server = function(input, output, session) {
    values <- reactiveValues(brush = NULL)

    output$plot <- renderPlot({
      if(is.null(values$brush)){
        ggplot(cars, aes(speed, dist)) + geom_point()
      } else {
        ggplot(cars, aes(speed, dist)) + geom_blank()
      }
    })

    observeEvent(input$plotBrush,{
      #Run this whenever points are brushed
      if(is.null(values$brush)){
        values$brush <- input$plotBrush}
    })

    output$brushedPoints <- renderPrint({
      values$brush
    })
  }
))

A second option is also available, see https://stackoverflow.com/a/35066532/3229332 for the explanation

library(ggplot2)
library(shiny)

runApp(list(
  ui = fluidPage(
    plotOutput("plot",
               brush = brushOpts("plotBrush", 
                                 delay = 5000,
                                 resetOnNew = TRUE)
               # resetOnNew = TRUE clears the brush
               # each time a new plot is displayed.
    ),
    p("Brushed Points:"),
    verbatimTextOutput("brushedPoints")
  ),
  server = function(input, output, session) {
    values <- reactiveValues(brush = NULL)

    output$plot <- renderPlot({
      if(is.null(values$brush)){
        ggplot(cars, aes(speed, dist)) + geom_point()
      } else {
        ggplot(cars, aes(speed, dist)) + geom_blank()
      }
    })

    observeEvent(input$plotBrush,{
      #Run this whenever points are brushed
        output$plot <- renderPlot({
          if(is.null(values$brush)){
            ggplot(cars, aes(speed, dist)) + geom_point()
            values$brush <- input$plotBrush
          } else {
            ggplot(cars, aes(speed, dist)) + geom_blank()
          }
        })
        }
    )

    output$brushedPoints <- renderPrint({
      values$brush
    })
  }
))

Upvotes: 0

Related Questions