Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to determine nearby SVG elements on a mouse event?

Tags:

svg

d3.js

I am using D3 JavaScript library to construct an SVG diagram and assign mouse events to them. The SVG diagram can be complex, made with many arbitrary path elements. I have been struggling to figure out a way to determine all the nearby elements on a mouseover event. So, for example, when the mouse cursor moves over the diagram I can determine all the component SVG elements within a radius of N pixels from the cursor.

I am completely stumped on how to tackle this. I am not even sure if there is an API that allows me determine if an SVG constituent element is within a bounding region?

Any hints on how to tackle this would be most appreciated.

like image 864
pliske Avatar asked Sep 28 '12 20:09

pliske


2 Answers

Preface: @Duopixel's comment on your original post is great -- I didn't know that method exists. However, it looks like it only supports a rectangular region per the MSDN docs, whereas it seems more intuitive to have a circle around the mouse.

A couple of other techniques:

  1. Use an invisible, thick stroke that "expands" your element, if you're not otherwise using the stroke for styling, or draw a transparent line/shape on top of the element that is some amount larger. Listen to the 'click' event for these elements.

  2. Do a pairwise comparison of every element with that of the mouse's click radius. I made an example here: http://jsfiddle.net/AkPN2/5/. I only implemented circles because they were easy -- you'd need to add the geometry for rectangles.

like image 161
ZachB Avatar answered Sep 20 '22 21:09

ZachB


I ended up using the method in SVGEdit suggested by DuoPixel. It uses other SVGEdit method defined in svgcanvas.js so it's not really meant as a standalone. Here's a working link to DuoPixel method suggested in the comments: https://github.com/duopixel/Method-Draw/blob/master/method-draw/src/svgcanvas.js#L600

With the specific implementation in case the link changes:

// This method sends back an array or a NodeList full of elements that
// intersect the multi-select rubber-band-box on the current_layer only.
//
// Since the only browser that supports the SVG DOM getIntersectionList is Opera,
// we need to provide an implementation here. We brute-force it for now.
//
// Reference:
// Firefox does not implement getIntersectionList(), see https://bugzilla.mozilla.org/show_bug.cgi?id=501421
// Webkit does not implement getIntersectionList(), see https://bugs.webkit.org/show_bug.cgi?id=11274
var getIntersectionList = this.getIntersectionList = function(rect) {
if (rubberBox == null) { return null; }
var parent = current_group || getCurrentDrawing().getCurrentLayer();
if(!curBBoxes.length) {
// Cache all bboxes
curBBoxes = getVisibleElementsAndBBoxes(parent);
}
var resultList = null;
try {
resultList = parent.getIntersectionList(rect, null);
} catch(e) { }
if (resultList == null || typeof(resultList.item) != "function") {
resultList = [];
if(!rect) {
var rubberBBox = rubberBox.getBBox();
var bb = {};
for(var o in rubberBBox) {
bb[o] = rubberBBox[o] / current_zoom;
}
rubberBBox = bb;
} else {
var rubberBBox = rect;
}
var i = curBBoxes.length;
while (i--) {
if(!rubberBBox.width || !rubberBBox.width) continue;
if (svgedit.math.rectsIntersect(rubberBBox, curBBoxes[i].bbox)) {
resultList.push(curBBoxes[i].elem);
}
}
}
// addToSelection expects an array, but it's ok to pass a NodeList
// because using square-bracket notation is allowed:
// http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html
return resultList;
};
like image 35
Jean-Philippe Jodoin Avatar answered Sep 20 '22 21:09

Jean-Philippe Jodoin