1

I am interested in creating an interactive plot in plotly.js, where a user can click on the plot and add a new point at that position. Essentially this means retrieving the plot coordinates of a mouse click. Most of the built-in click events deal with capturing a click on an already plotted marker, rather than an arbitrary position within the plot.

The following question deals with this issue, Plotly.js create a point on click. However, the code appears to have been made obsolete by changes to plotly.js at some point. An example of the answer to that question was demo'd on codepen. It appears that the xy mouse position detection now only updates within the toolbar region. My guess is that there was a renaming in the various components that make up the plot.

Link to non-functioning code from the comments of that answers: https://codepen.io/circleoncircles/pen/abObLLE

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: '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]);
  }
});

Plotly.d3.select(".plotly").on('mousemove', 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])) {
    console.log("Location X:"+x+" Y"+y)
   document.getElementById("xvalue").value = x;
   document.getElementById("yvalue").value = y;
  }
});

Does anybody have an updated method for this?

jranalli
  • 718
  • 1
  • 6
  • 14
  • 1
    "It appears that the xy mouse position detection now only within the toolbar region." Typo? Seems to be missing a couple words. Also, have you tried looking at the changelog for plotly? This sounds like a breaking change. Usually authors document these. – Alan H. Apr 05 '22 at 17:06
  • Yep, typo. Adjusted language. I looked into pulling some historical versions from the CDN and the change occurs from version 1.41.3 to 1.42.0. – jranalli Apr 05 '22 at 17:12
  • I don’t see anything obviously helpful here, but I don’t know Plotly at all. Release notes for that version: https://github.com/plotly/plotly.js/releases/tag/v1.42.0 (It does mention coords and something about x|y) – Alan H. Apr 05 '22 at 17:17
  • 1
    Thanks @AlanH. Your suggestion to look at the changelog led me to the old CDN versions, which led me to looking at alternate elements I could work on. – jranalli Apr 05 '22 at 23:45
  • 1
    really wish Plotly would include this as default functionality. – dangel Dec 26 '22 at 03:17

1 Answers1

1

I think I've got it. I would still consider this relatively hacky, so if someone else has a better way to handle it, I'm all ears and would be happy to assign a better answer.

All you really need to do is get the bounding box of the element that represents the plot field. It appears that one such element is that with the class gridlayer. Then I grabbed its bounding rect.

So the computation lines in the above would become:

var bgrect = document.getElementsByClassName('gridlayer')[0].getBoundingClientRect();
var x = ((e.x - bgrect['x']) / (bgrect['width'])) * (myPlot.layout.xaxis.range[1] - myPlot.layout.xaxis.range[0]) + myPlot.layout.xaxis.range[0];
var y = ((e.y - bgrect['y']) / (bgrect['height'])) * (myPlot.layout.yaxis.range[0] - myPlot.layout.yaxis.range[1]) + myPlot.layout.yaxis.range[1];

One additional change is necessary if you want to use the latest version of plotly. d3 is no longer embedded in plotly, so it's necessary to get d3 from CDN (the most recent version I found to work for this is v5).

<script src="https://cdn.plot.ly/plotly-2.11.0.min.js"></script>
<script src="//d3js.org/d3.v5.min.js"></script>

In the script you just reference d3 at the root then:

d3.select(".plotly").on('click', function(d, i) {
var e = d3.event;

I revised the codepen example that was posted previously to this new method https://codepen.io/jranalli/pen/eYyVVgr

jranalli
  • 718
  • 1
  • 6
  • 14