Menu Close

Across StackExchange and the Plotly.js community forums, there seems to be one frequently asked question that has not yet been answered: how can you get accurate coordinates from a click event on a Plotly chart?

Take your standard scatter plot:

Figure 1

If I hover my mouse over (4, 14), and click, the current Plotly.js onClick event data returns data for each trace at that same x value. So, you would get (4, 17) for trace 0, (4, 11) for trace 1, and (4, 12) for trace 2. This is great if I’m trying to find out information about the traces, but this is not useful at all if I’m trying to get the point on the grid on which I clicked.

Of course, with the event data you get all kinds of other somewhat useful information and functions. etpinard gets close to a solution in this codepen ( by using the xaxis and yaxis built-in p2c function. Unfortunately, this doesn’t exactly work, and there is little to no documentation on these conversion functions. There are a few other efforts that take into account the margins of the chart div (, but these also fall short of the mark when it comes to accuracy.

So, if you want the exact coordinates of where you clicked, what are your options? Go rogue D3 and write a custom function? Switch charting libraries completely out of frustration?

The answer, as is the answer to most of my questions with Plotly, is to layer traces. In a nutshell, layering a heatmap trace below all other traces on the plot, utilizing the same axes as the other traces, enables you to get the exact coordinates of a click. How exactly does this work?

By definition, a heatmap is a data visualization where every individual (x, y) pair is assigned a z value, and then colored according to that z-value. The basic Plotly.js heatmap can be seen here ( The Plotly.js heatmap is a combination of three arrays: a 1-d x-axis array, a 1-d y-axis array, and a 2-d z array. Because this chart covers the entire x-y space of the plot, we can get the x-y coordinates for any click event. However, the exactness of those coordinates is based on the x-y intervals from the heatmap’s data.

Figure 2

So, let’s make a heatmap with extremely granular x-y intervals: Looks pretty cool, huh? Let’s use something like this to get accurate click data for a scatter plot.

Figure 3

The first step is to generate the heatmap trace according to your data. If we are using Plotly’s out of the box scatter example, the x axis range is [0,6] and the y-axis range is [0,18]. Let’s plan for the x-y scale of the heatmap to be 0.01, so using two separate loops, we prime the x and y arrays for the heatmap:

var x = [];  
var y = [];  
for(i = 0; i < 6; i = i + 0.01){  
for(j = 0; j < 18; j = j + 0.01){  

Now that the x and y arrays are created, we can use these to generate an appropriately sized z array. Because we don’t actually care about the z value for this implementation, we will set each z array value to 0:

var z = [];  
for (j = 0; j < y.length; j++) {  
  var temp = [];  
  for (k = 0; k < x.length; k++) {  

The final step is to configure the heatmap data trace using the three arrays we just created. A few notes: the colorscale is manually set so that a z-value of 0 shows as white, to prevent the trace from being visible behind the other traces. Setting the showscale value to false hides the colorbar in the legend, and setting the hoverinfo to ‘x’ prevents the heatmap from showing its values as you pan across the chart. The xgap and ygap values allow the axes grid lines to continue to show through the hidden heatmap layer. I’m sure there is more parameters that could be added to make this trace even more difficult to discern, but I’ll leave that for you to play around with.

var trace4 = {  
  x: x,  
  y: y,  
  z: z,  
  type: "heatmap",  
  colorscale: [["0.0", "rgb(255, 255, 255, 0.5)"], ["1.0", "rgb(255, 255, 255, 0.5)"]],  
  xgap: 1,  
  ygap: 1,  
  hoverinfo: "x",  
  showscale: false  

Now, adding this trace into the data array for the scatter plot doesn’t make it look much different than before:

Figure 4

The last thing that needs to be done is to actually bind to the click event and see the data. This can vary depending on your implementation (react-ploty, Angular, etc), so I’ll leave the implementation up to you. The click event data returns an array of points. Each object in this array has a curveNumber attribute; this number is the index of that data trace in the data array that was utilized to make the chart. Using an array filter function, you can grab the specific object that relates to the heatmap trace, then use the x and y attributes belonging to that object, and voila, you have the exact coordinates of your click.

The full solution is up and running in a rudimentary codepen here:

This solution may add overhead to the chart render because it adds an extra trace to your chart. Also, if your chart has multiple axes scales, things could go sideways quickly. However, this is one sure-fire way to get the exact coordinates that you clicked upon, without some wonky point to coordinate conversions that return different values each time. Enjoy!

Posted in Blog


  1. Invizzz

    when you press the button, the program will lose accuracy with the error with which you press the button. If you hold down the button and then release it, the cursor definition will have an error by the amount of the difference in this hold in pixels. Therefore, the best solution would be to add these events and add an additional variable to the calculation.

    It is my realisation:

    var downClickX, downClickY;
    var diffX = 0;
    var diffY = 0;
    gd.addEventListener(‘mousedown’, function(evt) {
    let xInDataCoord = xaxis.p2c(evt.x – l);
    let yInDataCoord = yaxis.p2c(evt.y – t);
    downClickX = xInDataCoord;
    downClickY = yInDataCoord;

    gd.addEventListener(‘mouseup’, function(evt) {
    let xInDataCoord = xaxis.p2c(evt.x – l);
    let yInDataCoord = yaxis.p2c(evt.y – t);
    diffX = xInDataCoord – downClickX;
    diffY = yInDataCoord – downClickY;

    gd.addEventListener(‘mousemove’, function(evt) {
    let xInDataCoord = xaxis.p2c(evt.x – l + diffX);
    let yInDataCoord = yaxis.p2c(evt.y – t + diffY);

    console.log(‘x : ‘ + xInDataCoord);
    console.log(‘y : ‘ + yInDataCoord);

    • Jim McHugh

      Thank you for your input on this topic. I have a few questions regarding your method:

      Do you notice any issues when using the built-in p2c function? As stated above, I had many issues with the consistency of what the return value ends up representing, and could not “math” it out to ever be spot-on.

      Additionally, with my method of using a hidden heatmap trace below the other traces in the plot, I am latching into the plotly_click event as provided by the Plotly library (described here: Based on this, the movement of the mouse between the mousedown and mouseup event should be negligible in the plotly_click event. Have you experienced otherwise with the builtin Plotly.js provided event handlers?”

      Please let me know if this needs any editing. Thanks!


      Sammy Hamilton
      Data Scientist

  2. Thorsten Wingenroth

    Wow, thank you so much! Finally a solution that worked. All the other things I found on the web were either not working or inaccurate. But yours is working perfectly, even when zooming.
    An issue I had was that the heatmap hid my gridlines. So I set opacity: 0 for the heatmap.

  3. Tein Gan

    This seems to only work on previous versions of plotly prior to the v2 release…any idea how to make this work for the latest version?

Leave a Reply

Your email address will not be published. Required fields are marked *