0

I have an svg path which I can draw. With d3js I calculate a convex hull around the path with d3.geom.hull(...). Now I have some svg objects like nodes (svg:circle) and I want to find out whether the node is in the hull or not. How can I realize that? Here is a picture from my svg view:

View of the SVG

EDIT: My goal is to the node elements in the hull (which are within the path), not only at the edge of the path.

Chryb
  • 547
  • 1
  • 12
  • 34

2 Answers2

1

Here's an easy way to do that:

  1. Calculate your hull geometry, get back the coordinates array that d3.geom.hull gives you.

  2. Add your new point to your original data array and calculate d3.geom.hull again on this array.

  3. Compare the array of points returned from step 1 with the array of points returned from step 2 to see if the calculated hull has changed. If it has, then the point is outside the convex hull. If there is no change, then it's inside the convex hull.

This might be performance-intensive if you have a really large dataset.

Here's some simple code to demonstrate:

   // Some Random points
   var coords = d3.range(50).map(function() {return [Math.random(),Math.random()]})
   yourHull = d3.geom.hull(coords)
   // Random new point
   newCoord = [Math.random(),Math.random()]
   coords.push(newCoord)
   newHull = d3.geom.hull(coords)

   //The easy case to spot
   if (newHull.length != yourHull.length) {
      console.log("Outside")
   }
   //If the array lengths are the same, the point values may have changed
   else {
   var outside = false;
   for (var x = 0; x < yourHull.length;x++) {
      for (var y = 0; y < 2;y++) {
         if (yourHull[x][y] != newHull[x][y]) {
            outside = true;
            break;
         }
      }
   }

   if (outside) {
      console.log("outside")
   }
   else {
      console.log("on the hull")
   }
  }
Elijah
  • 4,609
  • 3
  • 28
  • 36
  • This won't work if the point is exactly on the hull, will it? – Lars Kotthoff Dec 01 '14 at 19:46
  • You mean on the border? With this method, if a point is exactly on the computed hull border, it will register as intersecting with the existing hull (it won't return a different array result). – Elijah Dec 01 '14 at 20:11
1

The fastest way of doing this is to have the browser do all the actual work. In particular, use the method document.getElementFromPoint() to have the rendering engine determine the overlap.

The idea is simple -- add the point you're interested in behind the hull, then check whether the above method gives you the point or the hull. The code looks like this.

function isInside(point) {
  var c = svg.insert("circle", "path.hull")
    .attr("r", 1)
    .attr("cx", point[0])
    .attr("cy", point[1]);

  var bounds = c.node().getBoundingClientRect();

  var atPoint = document.elementFromPoint(bounds.left, bounds.top);
  var inside = atPoint == c.node() ? false : true;

  c.remove();

  return inside;
}

The only slightly tricky bit is to convert the relative coordinates of the point to absolute coordinates -- the above code assumes that the SVG is a top-level element on the page itself or not translated by the containing elements. If this is not the case, adjust the code as appropriate.

The big advantage over the other answer is that the runtime does not depend on the size of the hull (as in the number of points defining it). It only depends on the number of points you want to check.

Complete demo here.

Lars Kotthoff
  • 107,425
  • 16
  • 204
  • 204
  • What does these `c.node()` do? I get an `NotFoundError: DOM Exception 8: An attempt was made to reference a Node in a context where it does not exist.` error. – Chryb Dec 02 '14 at 15:16
  • 1
    It gets the DOM node object from the D3 selection. You can't call this on an enter selection (I'm guessing that this is what you did). The element needs to exist in the DOM, but you can remove it immediately after checking whether it is obscured by the hull. – Lars Kotthoff Dec 02 '14 at 15:19
  • A last question: I save the x and y in a `g` over the circle and not in the `cx`, `cy` attribute. Is there a easy way to access it? Now all results are true because of all nodes looks like the same. – Chryb Dec 02 '14 at 15:41
  • 1
    The coordinates to check are determined by the bounding box of the element, so the method should work regardless of how you position it. Make sure you're using the actual `circle` element (not the `g`) and that its container element isn't translated. – Lars Kotthoff Dec 02 '14 at 16:02
  • Ok, than is the problem that the `g`is translated. – Chryb Dec 02 '14 at 16:05
  • [This question](http://stackoverflow.com/questions/442404/retrieve-the-position-x-y-of-an-html-element) should help with figuring out the position. – Lars Kotthoff Dec 02 '14 at 16:10
  • Do I have to add the correct positions to the circle in the `cx` and `cy` attribute? – Chryb Dec 02 '14 at 16:19
  • No. As I said earlier, this method is using the bounding box to determine the position. – Lars Kotthoff Dec 02 '14 at 16:22
  • I add a offset from the svg to the bounds, but now the `atPoint` variable is always the svg element. – Chryb Dec 02 '14 at 16:56
  • Could you provide a an example of where this goes wrong please? – Lars Kotthoff Dec 02 '14 at 17:00
  • I tried it exactly like you do except that my nodes has this structure: `` The position is added with the force layout by d3. I iterate through all nodes, get the coords, and run the method. Now I calculate the offset with var `svg_offset = document.getElementsByTagName("svg")[0].getBoundingClientRect(); var offset = [svg_offset.left, svg_offset.top];` and add it to the relative point `var x = pt[0] + offset[0], y = pt[1] + offset[1];`. – Chryb Dec 02 '14 at 17:10
  • Now it works. I don't change anything to the last comment :/... But thanks for your help! – Chryb Dec 02 '14 at 17:55