I'm familiar with the basics of shiny but struggling with something here. I would like to be able to add a ggplot layer when a point is clicked to highlight that point. I know this is possible with ggvis and there is a nice example in the gallery, but I would like to be able to use nearPoints()
to capture the click as ui input.
I have tried something (see below) which works apart from the ggplot layer appears and then disappears. I have tried all kinds of edits to this with reactive()
, eventReactive()
and so on.
Any help is much appreciated...
library(shiny)
library(ggplot2)
shinyApp(
ui = shinyUI(
plotOutput("plot", click = "clicked")
),
server = shinyServer(function(input, output) {
output$plot <- renderPlot({
ggplot(mtcars, aes(x = mpg, y = wt)) +
geom_point() +
geom_point(data = nearPoints(mtcars, input$clicked), colour = "red", size = 5)
})
})
)
I think I understand conceptually why this doesn't work. The plot has a dependency on input$clicked
which means that when input$clicked
changes the plot re-renders but this in turn resets input$clicked
. Bit of a catch 22 situation.
Please, try this:
library(shiny)
library(ggplot2)
# initialize global variable to record selected (clicked) rows
selected_points <- mtcars[0, ]
str(selected_points)
shinyApp(
ui = shinyUI(
plotOutput("plot", click = "clicked")
),
server = shinyServer(function(input, output) {
selected <- reactive({
# add clicked
selected_points <<- rbind(selected_points, nearPoints(mtcars, input$clicked))
# remove _all_ duplicates if any (toggle mode)
# http://stackoverflow.com/a/13763299/3817004
selected_points <<-
selected_points[!(duplicated(selected_points) |
duplicated(selected_points, fromLast = TRUE)), ]
str(selected_points)
return(selected_points)
})
output$plot <- renderPlot({
ggplot(mtcars, aes(x = mpg, y = wt)) +
geom_point() +
geom_point(data = selected(), colour = "red", size = 5)
})
})
)
If you click a point one time it is highlighted. If you click it a second time the highlight is turned off again (toggling).
The code uses a global variable selected_points
to store the actually highlighted (selected) points and an reactive expression selected()
which updates the global variable whenever a point is clicked.
The str(selected_points)
may help to visualize the working but can be removed.
There is a slightly different approach which uses observe()
instead of reactive()
and references the global variable selected_points
directly instead of returning the object from a function:
library(shiny)
library(ggplot2)
selected_points <- mtcars[0, ]
str(selected_points)
shinyApp(
ui = shinyUI(
plotOutput("plot", click = "clicked")
),
server = shinyServer(function(input, output) {
observe({
# add clicked
selected_points <<- rbind(selected_points, nearPoints(mtcars, input$clicked))
# remove _all_ duplicates (toggle)
# http://stackoverflow.com/a/13763299/3817004
selected_points <<-
selected_points[!(duplicated(selected_points) |
duplicated(selected_points, fromLast = TRUE)), ]
str(selected_points)
})
output$plot <- renderPlot({
# next statement is required for reactivity
input$clicked
ggplot(mtcars, aes(x = mpg, y = wt)) +
geom_point() +
geom_point(data = selected_points, colour = "red", size = 5)
})
})
)
Of course, you can use the global variable selected_points
directly in the ggplot
call instead of calling the reactive function selected()
. However, you have to ensure that renderPlot()
is executed whenever input$clicked
is changed. Therefore, the dummy reference to input$clicked
has to be included in the code within renderPlot()
.
Now, the reactive function selected()
is no longer needed and can be replaced by an observe()
expression. As opposed to reactive()
, observe()
doesn't return a value. It just updates the global variable selected_points
whenever input$clicked
is modified.
This approach avoids a global variable. Instead, it uses reactiveValues
to create a list-like object rv
with special capabilities for reactive programming (see ?reactiveValues
).
library(shiny)
library(ggplot2)
shinyApp(
ui = shinyUI(
plotOutput("plot", click = "clicked")
),
server = shinyServer(function(input, output) {
rv <- reactiveValues(selected_points = mtcars[0, ])
observe({
# add clicked
rv$selected_points <- rbind(isolate(rv$selected_points),
nearPoints(mtcars, input$clicked))
# remove _all_ duplicates (toggle)
# http://stackoverflow.com/a/13763299/3817004
rv$selected_points <- isolate(
rv$selected_points[!(duplicated(rv$selected_points) |
duplicated(rv$selected_points, fromLast = TRUE)), ])
str(rv$selected_points)
})
output$plot <- renderPlot({
ggplot(mtcars, aes(x = mpg, y = wt)) +
geom_point() +
geom_point(data = rv$selected_points, colour = "red", size = 5)
})
})
)
Please, note that in the observer
part references to rv
need to be encapsulated in isolate()
to ensure that only changes to input$clicked
will trigger execution of the code in observer
. Otherwise, we'll get an endless loop. Execution of renderPlot
is triggered whenever the reactive value rv
is changed.
Personally, I prefer approach 1 using reactive functions which make the dependencies (reactivity) more explicit. I find the dummy call to input$clicked in approach 2 less intuitive. Approach 3 requires a thorough understanding of reactivity and using isolate()
in the right places.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With