11

In short: I'm looking for a way to get the exact coordinates of a series of mouse positions (on-clicks) in an interactive x/y scatter plot rendered by ggplot2 and ggplotly.

I'm aware that plotly (and several other interactive plotting packages for R) can be combined with Shiny, where a box- or lazzo select can return a list of all data points within the selected subspace. This list will be HUGE in most of the datasets I'm analysing, however, and I need to be able to do the analysis reproducibly in an R markdown format (writing a few, mostly less than 5-6, point coordinates is much more readable). Furthermore, I have to know the exact positions of the clicks to be able to extract points within the same polygon of points in a different dataset, so a list of points within the selection in one dataset is not useful.

The grid.locator() function from the grid package does almost what I'm looking for (the one wrapped in fx gglocator), however I hope there is a way to do the same within an interactive plot rendered by plotly (or maybe something else that I don't know of?) as the data sets are often HUGE (see the plot below) and thus being able to zoom in and out interactively is very much appreciated during several iterations of analysis.

enter image description here

Normally I have to rescale the axes several times to simulate zooming in and out which is exhausting when doing it MANY times. As you can see in the plot above, there is a LOT of information in the plots to explore (the plot is about 300MB in memory).

Below is a small reprex of how I'm currently doing it using grid.locator on a static plot:

library(ggplot2)
library(grid)

p <- ggplot(mtcars, aes(wt, mpg)) +
  geom_point()

locator <- function(p) {
  # Build ggplot object
  ggobj <- ggplot_build(p)

  # Extract coordinates
  xr <- ggobj$layout$panel_ranges[[1]]$x.range
  yr <- ggobj$layout$panel_ranges[[1]]$y.range

  # Variable for selected points
  selection <- data.frame(x = as.numeric(), y = as.numeric())
  colnames(selection) <- c(ggobj$plot$mapping$x, ggobj$plot$mapping$y)

  # Detect and move to plot area viewport
  suppressWarnings(print(ggobj$plot))
  panels <- unlist(current.vpTree()) %>%
    grep("panel", ., fixed = TRUE, value = TRUE)
  p_n <- length(panels)
  seekViewport(panels, recording=TRUE)
  pushViewport(viewport(width=1, height=1))

  # Select point, plot, store and repeat
  for (i in 1:10){
    tmp <- grid.locator('native')
    if (is.null(tmp)) break
    grid.points(tmp$x,tmp$y, pch = 16, gp=gpar(cex=0.5, col="darkred"))
    selection[i, ] <- as.numeric(tmp)
  }
  grid.polygon(x= unit(selection[,1], "native"), y= unit(selection[,2], "native"), gp=gpar(fill=NA))
  #return a data frame with the coordinates of the selection
  return(selection)
}

locator(p)

and from here use the point.in.polygon function to subset the data based on the selection.

A possible solution could be to add, say 100x100, invisible points to the plot and then use the plotly_click feature of event_data() in a Shiny app, but this is not at all ideal.

Thanks in advance for your ideas or solutions, I hope my question was clear enough.

-- Kasper

  • 1
    could this be helpful: https://shiny.rstudio.com/articles/plot-interaction.html – MLavoie Feb 07 '18 at 20:49
  • It's exactly a feature like that I was hoping existed with plotly or other interactive widgets. I have posted a feature request on the plotly R github repo, hoping for an answer soon: https://github.com/ropensci/plotly/issues/1194 – Kasper Skytte Andersen Feb 09 '18 at 09:54
  • Here is a quite detailed answer to a similar question that might be helpful? https://stackoverflow.com/a/46249646/5350621 – David Oct 19 '19 at 16:46
  • Please check my answer [here](https://stackoverflow.com/a/58766072/9841389) - the JS part also works without shiny. – ismirsehregal Sep 14 '22 at 08:31
  • Have you looked at other options than ggplot plot ? like echarts4r -> https://echarts4r.john-coene.com/articles/brush.html?q=zoom#brush – David Muñoz Tord Jun 28 '23 at 08:59

1 Answers1

0

I used ggplot2. Besides the materials at https://shiny.rstudio.com/articles/plot-interaction.html, I'd like to mention the following:

Firstly, when you create the plot, don't use "print( )" within "renderPlot( )", or the coordinates would be wrong. For instance, if you have the following in UI:

plotOutput("myplot", click = "myclick")

The following in the Server would work:

output$myplot <- renderPlot({
    p = ggplot(data = mtcars, aes(x=mpg, y=hp)) + geom_point()
    p
})

But the clicking coordinates would be wrong if you do:

output$myplot <- renderPlot({
    p = ggplot(data = mtcars, aes(x=mpg, y=hp)) + geom_point()
    print(p)
})

Then, you could store the coordinates by adding to the Server:

mydata = reactiveValues(x_values = c(), y_values = c())

observeEvent(input$myclick, {

    mydata$x_values = c(mydata$x_values, input$myclick$x)
    mydata$y_values = c(mydata$y_values, input$myclick$y)

})

In addition to X-Y coordinates, when you use facet with ggplot2, you refer to the clicked facet panel by

input$myclick$panelvar1
Chris L.
  • 9
  • 1
  • Thanks for your suggestion. But I'm looking for getting it to work with plotly or similar interactive visualisation widgets. It works fine with ggplot2+shiny alone, but not with plotly (through the ggplotly function). So being able to zoom in the plot while at the same time being able to extract coordinates in a selection. As you can see on the plot, there are normally thousands of points in the type of data I have to use it for. – Kasper Skytte Andersen Dec 04 '18 at 09:25
  • You can add zoom with ggplot, just check this article https://shiny.rstudio.com/articles/plot-interaction-advanced.html – dracodoc May 12 '20 at 13:36