I'm making an app with shiny
which will allow users to click to select points on an image. I'm using ggplot2
to display the points as they're selected, as red dots on the image.
I have this working pretty close to the way I want, except that every time the user clicks a new point, the entire image gets reloaded*. Ideally, I'd be re-plotting the data each click, but not reloading the entire image.
My question is, is it possible to have the plot points reload reactively, but leave the background image alone (since it won't change between clicks)?
My actual app is more involved than this, but here is my best attempt at a minimal reproducible example of the issue I'd like to address (note you'll need to adjust image.file
to point to a jpg file on your machine in order to run this; I don't know how to make the image itself reproducible, sorry):
library( ggplot2 )
library( jpeg )
library( grid )
library( shiny )
#### pre-run setup ####
# set up a function for loading an image file as a grob
grob_image <- function( file ) {
grid::rasterGrob( jpeg::readJPEG( file ), interpolate = TRUE )
}
# initiate a ggplot theme for use in plotting
# (just getting rid of everything so we only see the image itself)
theme_empty <- theme_bw()
theme_empty$line <- element_blank()
theme_empty$rect <- element_blank()
theme_empty$strip.text <- element_blank()
theme_empty$axis.text <- element_blank()
theme_empty$plot.title <- element_blank()
theme_empty$axis.title <- element_blank()
# set the image input file
image.file <- "session2_ebbTriggerCountMap.jpg"
#### UI ####
ui <- fluidPage(
# display the image, with any click-points
fluidRow(
plotOutput("plot",
click = "image_click"
)
)
)
### SERVER ####
server <- function(input, output, session) {
# initialise a data.frame for collecting click points
data.thisimage <- data.frame( x = rep( NA_real_, 100L ), y = rep( NA_real_, 100L ) )
# initalise the plot (this is the image on which to put any points we get)
# the `geom_blank` here is to set up the x and y axes as per the width and height of the image
img <- grob_image( image.file )
base <- ggplot() +
geom_blank( data = data.frame( x = c( 0, dim( img$raster )[2] ), y = c( 0, dim( img$raster )[1] ) ),
mapping = aes( x = x, y = y )
) +
theme_empty +
annotation_custom( grob = img )
# plot the image
output$plot <- renderPlot( {
base
} )
#### click action ####
# watch for a mouse click (point selected on the plot)
observeEvent( input$image_click, {
# add a row of data to the data frame
data.thisimage[ which( is.na( data.thisimage$x ) )[1L], ] <<- c(
input$image_click$x, input$image_click$y
)
# re-render the plot with the new data
output$plot <<- renderPlot( {
base +
geom_point( data = data.thisimage[ !is.na( data.thisimage$x ), ],
mapping = aes( x = as.numeric( x ), y = as.numeric( y ) ),
colour = "red" )
} )
} )
}
shinyApp(ui, server)
Since the image gets reloaded with every mouse click, I'm anticipating problems with reactivity of the UI, CPU load, and data transfer load. Is there any way to alleviate that?
* it's probably obvious from the code itself, but I've proved it to myself by watching CPU load while clicking over and over again with a large-ish image loaded.
NOTE the closest I could find to my problem was this SO question. Unfortunately it doesn't resolve the issue of reloading the image, only speeding up the rendering of data points, which is not my problem here. Update large plots in Shiny without Re-Rendering