Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect mouseover of certain points within an HTML canvas?

I've built an analytical data visualization engine for Canvas and have been requested to add tooltip-like hover over data elements to display detailed metrics for the data point under the cursor.

For simple bar & Gaant charts, tree graphs and node maps with simple square areas or specific points of interest, I was able to implement this by overlaying absolutely-positioned DIVs with :hover attributes, but there are some more complicated visualizations such as pie charts and a traffic flow rendering which has hundreds of separate areas defined by bezeir curves.

Is is possible to somehow attach an overlay, or trigger an event when the user mouses over a specific closed path?

Each area for which hover needs to be specified is defined as follows:

context.beginPath();
context.moveTo(segmentRight, prevTop);
context.bezierCurveTo(segmentRight, prevTop, segmentLeft, thisTop, segmentLeft, thisTop);
context.lineTo(segmentLeft, thisBottom);
context.bezierCurveTo(segmentLeft, thisBottom, segmentRight, prevBottom, segmentRight, prevBottom);
/*
 * ...define additional segments...
 */
// <dream> Ideally I would like to attach to events on each path:
context.setMouseover(function(){/*Show hover content*/});
// </dream>
context.closePath();

Binding to an object like this is almost trivial to implement in Flash or Silverlight, since but the current Canvas implementation has the advantage of directly using our existing Javascript API and integrating with other Ajax elements, we are hoping to avoid putting Flash into the mix.

Any ideas?

like image 574
ryandenki Avatar asked Aug 03 '09 09:08

ryandenki


People also ask

How to get position of mouse in canvas?

The dimension of the canvas is found using the getBoundingClientRect() function. This method returns the size of an element and its position relative to the viewport. The position of x-coordinate of the mouse click is found by subtracting the event's x position with the bounding rectangle's x position.

How do you check if mouse is hovering over an element in JS?

You can simply use the CSS :hover pseudo-class selector in combination with the jQuery mousemove() to check whether the mouse is over an element or not in jQuery.

How can I see the HTML code of canvas?

In chrome type chrome://flags/ into the URL and once the page loads, find "Enable Developer Tools experiments". Close out of Chrome and once you load it back up, in the inspector you will find a new option under Profiles for "Capture Canvas Frame". Run that and it will output a list of every call performed.

What is the event name when the mouse moves over an object like an Image?

The onmouseover event occurs when the mouse pointer is moved onto an element, or onto one of its children.


2 Answers

You could handle the mousemove event and get the x,y coordinates from the event. Then you'll probably have to iterate over all your paths to test if the point is over the path. I had a similar problem that might have some code you could use.

Looping over things in this way can be slow, especially on IE. One way you could potentially speed it up - and this is a hack, but it would be quite effective - would be to change the color that each path is drawn with so that it is not noticeable by humans but so that each path is drawn in a different color. Have a table to look up colors to paths and just look up the color of the pixel under the mouse.

like image 137
Sam Hasler Avatar answered Sep 30 '22 14:09

Sam Hasler


Shadow Canvas

The best method I have seen elsewhere for mouseover detection is to repeat the part of your drawing that you want to detect onto a hidden, cleared canvas. Then store the ImageData object. You can then check the ImageData array for the pixel of interest and return true if the alpha value is greater than 0.

// slow part
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillRect(100,100,canvas.width-100,canvas.height-100);
var pixels = ctx.getImageData(0,0,canvas.width,canvas.height).data;

// fast part
var idx = 4 * (mouse_x + mouse_y * canvas.width) + 3;
if (pixels[idx]) { // alpha > 0
  ...
}

Advantages

  • You can detect anything you want since you're just repeating the context methods. This works with PNG alpha, crazy compound shapes, text, etc.
  • If your image is fairly static, then you only need to do this one time per area of interest.
  • The "mask" is slow, but looking up the pixel is dirt cheap. So the "fast part" is great for mouseover detection.

Disadvantages

  • This is a memory hog. Each mask is W*H*4 values. If you have a small canvas area or few areas to mask, it's not that bad. Use chrome's task manager to monitor memory usage.
  • There is currently a known issue with getImageData in Chrome and Firefox. The results are not garbage collected right away if you nullify the variable, so if you do this too frequently, you will see memory rise rapidly. It does eventually get garbage collected and it shouldn't crash the browser, but it can be taxing on machines with small amounts of RAM.

A Hack to Save Memory

Rather than storing the whole ImageData array, we can just remember which pixels have alpha values. It saves a great deal of memory, but adds a loop to the mask process.

var mask = {};
var len = pixels.length;
for (var i=3;i<len;i+=4) if ( pixels[i] ) mask[i] = 1;

// this works the same way as the other method
var idx = 4 * (mouse_x + mouse_y * canvas.width) + 3;
if (mask[idx]) {
  ...
}
like image 43
jcampbelly Avatar answered Sep 30 '22 14:09

jcampbelly