|  | function arrowConnector() { | 
        
          |  |  | 
        
          |  | var svg, arrows; | 
        
          |  |  | 
        
          |  | function render() { | 
        
          |  |  | 
        
          |  | if(d3.select(".arrow-connector-container").empty()) { | 
        
          |  | svg = d3.select("body").append("svg") | 
        
          |  | .attr("xmlns", "http://www.w3.org/2000/svg") | 
        
          |  | .classed("arrow-connector-container", true) | 
        
          |  | .style("position", "absolute") | 
        
          |  | .style("top", "0") | 
        
          |  | .style("left", "0") | 
        
          |  | .style("width", "100%") | 
        
          |  | .style("height", "100%") | 
        
          |  | .style("pointer-events", "none"); | 
        
          |  | } else { | 
        
          |  | svg = d3.select(".arrow-connector-container"); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | arrows = svg.selectAll("line") | 
        
          |  | .data(getTargets()); | 
        
          |  |  | 
        
          |  | arrows.enter() | 
        
          |  | .append("line") | 
        
          |  | .classed("arrow-connector", true); | 
        
          |  |  | 
        
          |  | arrows | 
        
          |  | .attr("x1", function(d) { return d[0].x }) | 
        
          |  | .attr("y1", function(d) { return d[0].y }) | 
        
          |  | .attr("x2", function(d) { return d[1].x }) | 
        
          |  | .attr("y2", function(d) { return d[1].y }); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function getTargets() { | 
        
          |  | var targets = []; | 
        
          |  | d3.selectAll("[data-arrow-target]") | 
        
          |  | .each(function(d,i) { | 
        
          |  | fromCorners = edgesToCorners(this); | 
        
          |  | d3.selectAll(this.dataset.arrowTarget).each(function(dd,ii) { | 
        
          |  | var toCorners = edgesToCorners(this); | 
        
          |  |  | 
        
          |  | // check all possible combinations of eligible endpoints for the shortest distance | 
        
          |  | var fromClosest, toClosest, distance; | 
        
          |  | fromCorners.forEach(function(from) { | 
        
          |  | toCorners.forEach(function(to) { | 
        
          |  | if(distance == null || hypotenuse( to.x-from.x, to.y-from.y ) < distance) { | 
        
          |  | distance = hypotenuse( to.x-from.x, to.y-from.y ); | 
        
          |  | fromClosest = from; | 
        
          |  | toClosest = to; | 
        
          |  | } | 
        
          |  | }); | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | targets.push([fromClosest,toClosest]); | 
        
          |  |  | 
        
          |  | }); | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | return targets; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | // gets from the sides of a bounding rect (left, right, top, bottom) | 
        
          |  | // to its corners (topleft, topright, bottomleft, bottomright) | 
        
          |  | function edgesToCorners(element) { | 
        
          |  | var corners = []; | 
        
          |  | ["left","right"].forEach(function(i) { ["top","bottom"].forEach(function(j) { corners.push({"x":i,"y":j}); }); }); | 
        
          |  | return corners.map(function(corner) { | 
        
          |  | return { | 
        
          |  | "x": element.getBoundingClientRect()[corner.x] + window.pageXOffset, | 
        
          |  | "y": element.getBoundingClientRect()[corner.y] + window.pageYOffset | 
        
          |  | }; | 
        
          |  | }); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | // this seems good to have | 
        
          |  | function hypotenuse(a, b) { | 
        
          |  | return Math.sqrt( Math.pow(a,2) + Math.pow(b,2) ); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | return render; | 
        
          |  |  | 
        
          |  | } |