rshinyplotlyshinyjs

How to get coordinates from a R plotly figure


I have been struggling like mad to solve an apparently basic question. Imagine you have a scatter plot, with say ... 10 markers. I suppose this plot has been generated using plotly within a Shiny environment.

One can easily get the coordinates of these markers using the event_data("plotly_click") code.

Now imagine you do not need the coordinates of these markers, but the coordinates generated by a mouse click but precisely where no marker exists (for example because you would like to set a new marker exactly there, and you would like to re-use the information coming from that mouse click).

I cannot obtain such a behavior using onclick(), or whatever.

Any idea ?


Solution

  • You could add a D3 event listener to your plot

    Plotly.d3.select('.plotly').on('click', function(d, i) {})
    

    and then

    The new x and y value are then pushed to the existing graph

    Plotly.extendTraces(myPlot, {
          x: [[x]],
          y: [[y]]
        }, [1]);
    

    Complete R code

    library("plotly")
    library("htmlwidgets")
    
    p <- plot_ly(x = c( -2, 0, 2 ),y = c( -2, 1, 2), type = 'scatter' ,mode = 'lines+markers') %>% 
      add_trace(x=c(-1,0.4,2),y=c(2, 0, -1),type='scatter',mode='lines+markers') %>% 
      layout(hovermode='closest')
    javascript <- "
    var myPlot = document.getElementsByClassName('plotly')[0];
    Number.prototype.between = function (min, max) {
      return this >= min && this <= max;
    };
    
    Plotly.d3.select('.plotly').on('click', function(d, i) {
      var e = Plotly.d3.event;
      var bg = document.getElementsByClassName('bg')[0];
      var x = ((e.layerX - bg.attributes['x'].value + 4) / (bg.attributes['width'].value)) * (myPlot.layout.xaxis.range[1] - myPlot.layout.xaxis.range[0]) + myPlot.layout.xaxis.range[0];
        var y =((e.layerY - bg.attributes['y'].value + 4) / (bg.attributes['height'].value)) * (myPlot.layout.yaxis.range[0] - myPlot.layout.yaxis.range[1]) + myPlot.layout.yaxis.range[1]
      if (x.between(myPlot.layout.xaxis.range[0], myPlot.layout.xaxis.range[1]) && 
    y.between(myPlot.layout.yaxis.range[0], myPlot.layout.yaxis.range[1])) {
        Plotly.extendTraces(myPlot, {
          x: [[x]],
          y: [[y]]
        }, [1]);
      }
    });"
    p <- htmlwidgets::prependContent(p, onStaticRenderComplete(javascript), data=list(''))
    p
    

    Interactive Javascript example

    var traces = [{
      x: [1, 2, 3, 4],
      y: [10, 15, 13, 17],
      mode: 'markers',
      type: 'scatter'
    }];
    
    traces.push({
      x: [2, 3, 4, 5],
      y: [16, 5, 11, 9],
      mode: 'markers',
      type: 'scatter'
    });
    
    traces.push({
      x: [1, 2, 3, 4],
      y: [12, 9, 15, 12],
      mode: 'markers',
      type: 'scatter'
    });
    
    traces.push({
      x: [],
      y: [],
      mode: 'lines+markers',
      type: 'scatter'
    });
    
    var myPlot = document.getElementById('myPlot')
    Plotly.newPlot('myPlot', traces, {hovermode: 'closest'});
    
    Number.prototype.between = function(min, max) {
      return this >= min && this <= max;
    };
    
    Plotly.d3.select(".plotly").on('click', function(d, i) {
      var e = Plotly.d3.event;
      var bg = document.getElementsByClassName('bg')[0];
      var x = ((e.layerX - bg.attributes['x'].value + 4) / (bg.attributes['width'].value)) * (myPlot.layout.xaxis.range[1] - myPlot.layout.xaxis.range[0]) + myPlot.layout.xaxis.range[0];
      var y = ((e.layerY - bg.attributes['y'].value + 4) / (bg.attributes['height'].value)) * (myPlot.layout.yaxis.range[0] - myPlot.layout.yaxis.range[1]) + myPlot.layout.yaxis.range[1]
      if (x.between(myPlot.layout.xaxis.range[0], myPlot.layout.xaxis.range[1]) &&
        y.between(myPlot.layout.yaxis.range[0], myPlot.layout.yaxis.range[1])) {
        Plotly.extendTraces(myPlot, {
          x: [
            [x]
          ],
          y: [
            [y]
          ]
        }, [3]);
      }
    });
    <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
    <div id="myPlot" style="width:100%;height:100%"></div>


    Shiny example

    library(shiny)
    library("plotly")
    library("htmlwidgets")
    
    ui <- fluidPage(
      plotlyOutput("plot")
    )
    
    server <- function(input, output) {
       javascript <- "
    function(el, x){
      Number.prototype.between = function (min, max) {
       return this >= min && this <= max;
      };
    
      Plotly.d3.select('.plotly').on('click', function(d, i) {
      var e = Plotly.d3.event;
      var bg = document.getElementsByClassName('bg')[0];
      var x = ((e.layerX - bg.attributes['x'].value + 4) / (bg.attributes['width'].value)) * (el.layout.xaxis.range[1] - el.layout.xaxis.range[0]) + el.layout.xaxis.range[0];
      var y =((e.layerY - bg.attributes['y'].value + 4) / (bg.attributes['height'].value)) * (el.layout.yaxis.range[0] - el.layout.yaxis.range[1]) + el.layout.yaxis.range[1]
      if (x.between(el.layout.xaxis.range[0], el.layout.xaxis.range[1]) && y.between(el.layout.yaxis.range[0], el.layout.yaxis.range[1])) {
        Plotly.extendTraces(el, {
         x: [[x]],
         y: [[y]]
        }, [1]);
      }
    });
    }"
      output$plot <- renderPlotly({
        plot_ly(x = c( -2, 0, 2 ),y = c( -2, 1, 2), type = 'scatter' ,mode = 'lines+markers') %>% 
        add_trace(x=c(-1,0.4,2),y=c(2, 0, -1),type='scatter',mode='lines+markers') %>% 
        layout(hovermode='closest') %>% onRender(javascript)
      })
    }
    
    shinyApp(ui = ui, server = server)