javascript - How do I draw directed arrows between rectangles of different dimensions in d3? -


i draw directed arcs between rectangles (nodes represented rectangles) in such way arrow-tip hits edge in graceful way. have seen plenty of posts on how circles (nodes represented circles). quite interestingly, d3 examples deal circles , squares (though squares lesser extent).

i have example code here. right best attempt can draw center-point center-point. can shift end point (where arrow should be), upon experimenting dragging rectangles around, arcs don't behave intended.

here's i've got. enter image description here

but need this. enter image description here

any ideas on how can in d3? there built-in library/function can type of thing (like dragging capabilities)?

a simple algorithm solve problem is

  • when node dragged following each of incoming/outgoing edges
    • let a node dragged , b node reached through outgoing/incoming edge
    • let linesegment line segment between centers of a , b
    • compute intersection point of a , linesegment, done iterating 4 segments make box , checking intersection of each of them linesegment, let ia intersection point of 1 of segments of a , linesegment, find ib in similar fashion

corner cases have considered haven't solved

  • when box's center inside other box there won't 2 segment intersections
  • when both intersections points same! (solved 1 in edit)
  • when graph multigraph edges render on top of each other

plunkr demo

edit: added check ia === ib avoid creating edge top left corner, can see on plunkr demo

$(document).ready(function() {    var graph = {      nodes: [        { id: 'n1', x: 10, y: 10, width: 200, height: 200 },        { id: 'n2', x: 10, y: 270, width: 200, height: 250 },        { id: 'n3', x: 400, y: 270, width: 200, height: 300 }      ],      edges: [        { start: 'n1', stop: 'n2' },        { start: 'n2', stop: 'n3' }      ],      node: function(id) {        if(!this.nmap) {          this.nmap = { };          for(var i=0; < this.nodes.length; i++) {            var node = this.nodes[i];            this.nmap[node.id] = node;          }        }        return this.nmap[id];      },      mid: function(id) {        var node = this.node(id);        var x = node.width / 2.0 + node.x,            y = node.height / 2.0 + node.y;        return { x: x, y: y };      }    };        var arcs = d3.select('#mysvg')    .selectall('line')    .data(graph.edges)    .enter()      .append('line')      .attr({        'data-start': function(d) { return d.start; },        'data-stop': function(d) { return d.stop; },        x1: function(d) { return graph.mid(d.start).x; },        y1: function(d) { return graph.mid(d.start).y; },        x2: function(d) { return graph.mid(d.stop).x; },        y2: function(d) { return graph.mid(d.stop).y },        style: 'stroke:rgb(255,0,0);stroke-width:2',        'marker-end': 'url(#arrow)'      });        var g = d3.select('#mysvg')      .selectall('g')      .data(graph.nodes)      .enter()        .append('g')        .attr({          id: function(d) { return d.id; },          transform: function(d) {            return 'translate(' + d.x + ',' + d.y + ')';          }        });    g.append('rect')      .attr({        id: function(d) { return d.id; },        x: 0,        y: 0,        style: 'stroke:#000000; fill:none;',        width: function(d) { return d.width; },        height: function(d) { return d.height; },        'pointer-events': 'visible'      });        function point(x, y) {      if (!(this instanceof point)) {        return new point(x, y)      }      this.x = x      this.y = y    }    point.add = function (a, b) {      return point(a.x + b.x, a.y + b.y)    }    point.sub = function (a, b) {      return point(a.x - b.x, a.y - b.y)    }    point.cross = function (a, b) {      return a.x * b.y - a.y * b.x;    }    point.scale = function (a, k) {      return point(a.x * k, a.y * k)    }    point.unit = function (a) {      return point.scale(a, 1 / point.norm(a))    }    point.norm = function (a) {      return math.sqrt(a.x * a.x + a.y * a.y)    }    point.neg = function (a) {      return point(-a.x, -a.y)    }        function pointinsegment(s, p) {      var = s[0]      var b = s[1]      return math.abs(point.cross(point.sub(p, a), point.sub(b, a))) < 1e-6 &&        math.min(a.x, b.x) <= p.x && p.x <= math.max(a.x, b.x) &&         math.min(a.y, b.y) <= p.y && p.y <= math.max(a.y, b.y)     }        function linelineintersection(s1, s2) {      var = s1[0]      var b = s1[1]      var c = s2[0]      var d = s2[1]      var v1 = point.sub(b, a)      var v2 = point.sub(d, c)      //if (math.abs(point.cross(v1, v2)) < 1e-6) {      //  // collinear      //  return null      //}      var knum = point.cross(        point.sub(c, a),        point.sub(d, c)      )      var kden = point.cross(        point.sub(b, a),        point.sub(d, c)      )      var ip = point.add(        a,        point.scale(          point.sub(b, a),          math.abs(knum / kden)        )      )      return ip    }        function segmentsegmentintersection(s1, s2) {      var ip = linelineintersection(s1, s2)      if (ip && pointinsegment(s1, ip) && pointinsegment(s2, ip)) {        return ip      }    }          function boxsegmentintersection(box, linesegment) {      var data = box.data()[0]      var topleft = point(data.x, data.y)      var topright = point(data.x + data.width, data.y)      var botleft = point(data.x, data.y + data.height)      var botright = point(data.x + data.width, data.y + data.height)      var boxsegments = [        // top        [topleft, topright],        // bot        [botleft, botright],        // left        [topleft, botleft],        // right        [topright, botright]      ]      var ip      (var = 0; !ip && < 4; += 1) {        ip = segmentsegmentintersection(boxsegments[i], linesegment)      }      return ip    }        function boxcenter(a) {      var data = a.data()[0]      return point(        data.x + data.width / 2,        data.y + data.height / 2      )    }        function buildsegmentthroughcenters(a, b) {      return [boxcenter(a), boxcenter(b)]    }        // should return {x1, y1, x2, y2}    function getintersection(a, b) {      var segment = buildsegmentthroughcenters(a, b)      console.log(segment[0], segment[1])      var ia = boxsegmentintersection(a, segment)      var ib = boxsegmentintersection(b, segment)      if (ia && ib) {                // problem: arrows drawn after intersection box        // solution: move arrow toward other end                var unitv = point.unit(point.sub(ib, ia))        // k = width of marker        var k = 18        ib = point.sub(ib, point.scale(unitv, k))                return {          x1: ia.x,          y1: ia.y,          x2: ib.x,          y2: ib.y        }        }    }        var drag = d3.behavior.drag()    .origin(function(d) {       return d;    })    .on('dragstart', function(e) {      d3.event.sourceevent.stoppropagation();    })    .on('drag', function(e) {      e.x = d3.event.x;      e.y = d3.event.y;            var id = 'g#' + e.id      var target = d3.select(id)      target.data().x = e.x      target.data().y = e.y      target.attr({        transform: 'translate(' + e.x + ',' + e.y + ')'      });            d3.selectall('line[data-start=' + e.id + ']')        .each(function (d) {          var line = d3.select(this)          var other = d3.select('g#' + line.attr('data-stop'))          var intersection = getintersection(target, other)          intersection && line.attr(intersection)        })            d3.selectall('line[data-stop=' + e.id + ']')        .each(function (d) {          var line = d3.select(this)          var other = d3.select('g#' + line.attr('data-start'))          var intersection = getintersection(other, target)          intersection && line.attr(intersection)        })      })    .on('dragend', function(e) {          });    g.call(drag);  })
      svg#mysvg { border: 1px solid black;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>    <svg id="mysvg" width="800" height="800">    <defs>      <marker id="arrow" markerwidth="10" markerheight="10" refx="0" refy="3" orient="auto" markerunits="strokewidth">        <path d="m0,0 l0,6 l9,3 z" fill="#f00" />      </marker>    </defs>  </svg>


Comments

Popular posts from this blog

Django REST Framework perform_create: You cannot call `.save()` after accessing `serializer.data` -

Why does Go error when trying to marshal this JSON? -