Search code examples
d3.jssvgtreeposition

How to determine if D3 tree node falls outside SVG container


I'm using this implementation of a D3 tree layout:

http://bl.ocks.org/robschmuecker/7880033

I've got my tree responding to outside events, and for that, I'd like to know if a certain node is currently invisible (outside of svg container), so that I could for example recenter it.

After some research I found this SO answer which looked exactly what I needed:

Getting Screen Positions of D3 Nodes After Transform

When dragging a node in the upper left corner and clicking on it, I would expect the coordinates to be [0,0]. But for some reason the x coordinate is always wrong by a pretty large offset. Made a fiddle about it here:

https://jsfiddle.net/syberyan/yd8L4v0q/

I modified the click() function so that it prints the coordinates of the target node.

function getElementCoords(element, coords) {
    var ctm = element.getCTM(),
    x = ctm.e + coords.x*ctm.a + coords.y*ctm.c,
    y = ctm.f + coords.x*ctm.b + coords.y*ctm.d;
    return {x: x, y: y};
};

function click(d) {
    if (d3.event.defaultPrevented) return; // click suppressed
    d3.selectAll('.node').each(function (node, i) {
    if (d === node) {
      let coords = getElementCoords(this, node);
      console.log(coords.x, coords.y); // shows coords relative to my svg container
    }
  });
}

I'm a D3 newbie so I probably did something wrong, but what? Or maybe there's a better way of doing this?


Solution

  • I didn't look too closely at the linked question or your current calculations. Instead, I think the calculations can be simplified to:

    function getElementCoords(element) {      
      // translate on node
      var trans = d3.transform(d3.select(element).attr('transform')).translate,
          // transform on parent "zoom" container
          transFormParent = d3.transform(d3.select(element.parentNode).attr('transform')),
          // translate on zoom container
          transParent = transFormParent.translate,
          // scale on zoom container
          scaleParent = transFormParent.scale,
          // final position of node
          pos = {
            x: trans[0] + transParent[0]/scaleParent[0],
            y: trans[1] + transParent[1]/scaleParent[1]
          };
    
        return pos;
    }
    

    Call it as:

    function click(d) {
      console.log(getElementCoords(this));
    }
    

    Updated fiddle.

    EDITS

    @Christian pointed out in the comments, my math was off, should have been:

    function getElementCoords(element) {      
      // translate on node
      var trans = d3.transform(d3.select(element).attr('transform')).translate,
          // transform on parent "zoom" container
          transFormParent = d3.transform(d3.select(element.parentNode).attr('transform')),
          // translate on zoom container
          transParent = transFormParent.translate,
          // scale on zoom container
          scaleParent = transFormParent.scale,
          // final position of node
          pos = {
            x: trans[0] * scaleParent[0] + transParent[0], 
            y: trans[1] * scaleParent[1] + transParent[1]
          };
    
        return pos;
    }