8

I am new to r-plotly and trying to figure out how to handle clicks which are not on the data. It seems that using event_data("plotly_click") I get events that are on points from the data, but so far have not figured out how to do this for clicks which are not close to the data, but just on the white part of the plot.

Shiny click events from plots can do this and I just get the x and y of the click. I want similar, but for plotly plots.

Can I specify click events to be from anywhere on the plotly plot, not just on the data?

EDIT: Surprisingly this does not exist yet in plotly. See this feature request

https://github.com/plotly/plotly.js/issues/2696

https://github.com/ropensci/plotly/issues/1194

So until this feature is added, I guess my question is what options are there to do this? Seems like such a basic feature, that I hope someone with more knowledge of JavaScript/Shiny/Plotly has hacked into.

ismirsehregal
  • 30,045
  • 5
  • 31
  • 78
user3763801
  • 395
  • 4
  • 10
  • How were you able to get plot clicks outside of the data area of the plot for non-plotly plots? I haven't figured this out. – jzadra Sep 21 '21 at 18:32

2 Answers2

5

Edit: The Plotly.plot() function has been deprecated - now using Plotly.update() instead:

Please check the following workaround based on this codepen I found via this question.

The following is using plotlyProxy instead of re-rendering the plot, which is faster:

library(plotly)
library(shiny)
library(htmlwidgets)

initDF <- data.frame(x = 1:10, y = 1:10)

ui <- fluidPage(
  plotlyOutput("myPlot"),
  verbatimTextOutput("click")
)

server <- function(input, output, session) {
  
  js <- "
    function(el, x, inputName){
      var id = el.getAttribute('id');
      var gd = document.getElementById(id);
      var d3 = Plotly.d3;
      Plotly.update(id).then(attach);
        function attach() {
          gd.addEventListener('click', function(evt) {
            var xaxis = gd._fullLayout.xaxis;
            var yaxis = gd._fullLayout.yaxis;
            var bb = evt.target.getBoundingClientRect();
            var x = xaxis.p2d(evt.clientX - bb.left);
            var y = yaxis.p2d(evt.clientY - bb.top);
            var coordinates = [x, y];
            Shiny.setInputValue(inputName, coordinates);
          });
        };
  }
  "
  
  clickposition_history <- reactiveVal(initDF)
  
  observeEvent(input$clickposition, {
    clickposition_history(rbind(clickposition_history(), input$clickposition))
  })
  
  output$myPlot <- renderPlotly({
    plot_ly(initDF, x = ~x, y = ~y, type = "scatter", mode = "markers") %>%
      onRender(js, data = "clickposition")
  })
  
  myPlotProxy <- plotlyProxy("myPlot", session)
  
  observe({
    plotlyProxyInvoke(myPlotProxy, "restyle", list(x = list(clickposition_history()$x), y = list(clickposition_history()$y)))
  })
  
  output$click <- renderPrint({
    clickposition_history()
  })
}

shinyApp(ui, server)

result

Related GitHub issue and PR.

Thanks @unumileappassionato for providing the solution regarding the small horizontal offset in my initial answer.

ismirsehregal
  • 30,045
  • 5
  • 31
  • 78
2

Edit: The Plotly.plot() function has been deprecated - now using Plotly.update() instead:

Based on ismirsehregal's answer, please note that subtracting the parent object's top and left dimensions instead of the plot margins seems to work. See following example:

library(plotly)
library(shiny)
library(htmlwidgets)

ui <- fluidPage(
  plotlyOutput("graph"),
  verbatimTextOutput("click")
)

server <- function(input, output, session) {
  
  js <- "
    function(el, x, inputName){
      var id = el.getAttribute('id');
      var gd = document.getElementById(id);
      var d3 = Plotly.d3;
      Plotly.update(id).then(attach);
        function attach() {
          var coordinates = [null, null]
    
          gd.addEventListener('click', function(evt) {
            var xaxis = gd._fullLayout.xaxis;
            var yaxis = gd._fullLayout.yaxis;
            var bb = evt.target.getBoundingClientRect();
            var x = xaxis.p2d(evt.clientX - bb.left);
            var y = yaxis.p2d(evt.clientY - bb.top);
            var coordinates = [x, y];
            Shiny.setInputValue(inputName, coordinates);
          });
        };
  }
  "
  clickposition_history <- reactiveVal(data.frame(x = 1:10, y = 1:10))
  
  observeEvent(input$clickposition, {
    clickposition_history(rbind(clickposition_history(), input$clickposition))
  })
  
  output$graph <- renderPlotly({
    plot_ly(clickposition_history(), x = ~x, y = ~y, type = "scatter", mode = "markers") %>%
      onRender(js, data = "clickposition")
  })
  
  output$click <- renderPrint({
    input$clickposition
  })
}

shinyApp(ui, server)

I adapted the code from sleighsoft's implementation here: https://github.com/plotly/plotly.js/pull/5443

ismirsehregal
  • 30,045
  • 5
  • 31
  • 78