Search code examples
javascriptsvgconnectionrectangles

Svg draw connection line between two rectangles


I am currently programming a diagram editor in javascript with SVG.

I am stuck with a problem concerning connection between rectangles. I found a lot of resources to draw a connection between circles but nothing about rectangles.

So now what I got is that I can draw a connection line between two rectangles by dragging the line with the mouse but the connection is displayed inside them because i calculate the connection from the middle point of the rectangles.

As You can see in the picture below I made myself some thoughts but i don't get the final step.

I just want to draw the line that is marked red.

Example-picture for my problem: i want to draw the red line

Later on i want to drag the rectangles and the line should be updated but for now i just need to calculate this line.

Somebody got a good suggestion?


Solution

  • Say you have two rects and you know the center of them (cx1, cy1) and (cx2, cy2). You also have the width and height divided by 2 (i.e. the distance from the center to the sides): (w1, h1) and (w2, h2).

    The distance between them is:

    var dx = cx2 - cx1;
    var dy = cy2 - cy1;
    

    Then you can calculate the intersection point for the two rects with:

    var p1 = getIntersection(dx, dy, cx1, cy1, w1, h1);
    var p2 = getIntersection(-dx, -dy, cx2, cy2, w2, h2);
    

    Where getIntersection is:

    function getIntersection(dx, dy, cx, cy, w, h) {
      if (Math.abs(dy / dx) < h / w) {
        // Hit vertical edge of box1
        return [cx + (dx > 0 ? w : -w), cy + dy * w / Math.abs(dx)];
       } else {
        // Hit horizontal edge of box1
        return [cx + dx * h / Math.abs(dy), cy + (dy > 0 ? h : -h)];
        }
    };
    

    Here's an example:

    var rect1 = document.getElementById('rect1');
    var rect2 = document.getElementById('rect2');
    var cxn = document.getElementById('connection');
    
    updateConnection();
    
    function updateConnection() {
      // Top left coordinates
      var x1 = parseFloat(rect1.getAttributeNS(null, 'x'));
      var y1 = parseFloat(rect1.getAttributeNS(null, 'y'));
      var x2 = parseFloat(rect2.getAttributeNS(null, 'x'));
      var y2 = parseFloat(rect2.getAttributeNS(null, 'y'));
    
      // Half widths and half heights
      var w1 = parseFloat(rect1.getAttributeNS(null, 'width')) / 2;
      var h1 = parseFloat(rect1.getAttributeNS(null, 'height')) / 2;
      var w2 = parseFloat(rect2.getAttributeNS(null, 'width')) / 2;
      var h2 = parseFloat(rect2.getAttributeNS(null, 'height')) / 2;
    
      // Center coordinates
      var cx1 = x1 + w1;
      var cy1 = y1 + h1;
      var cx2 = x2 + w2;
      var cy2 = y2 + h2;
    
      // Distance between centers
      var dx = cx2 - cx1;
      var dy = cy2 - cy1;
    
      var p1 = getIntersection(dx, dy, cx1, cy1, w1, h1);
      var p2 = getIntersection(-dx, -dy, cx2, cy2, w2, h2);
    
      cxn.setAttributeNS(null, 'x1', p1[0]);
      cxn.setAttributeNS(null, 'y1', p1[1]);
      cxn.setAttributeNS(null, 'x2', p2[0]);
      cxn.setAttributeNS(null, 'y2', p2[1]);
    }
    
    function getIntersection(dx, dy, cx, cy, w, h) {
    if (Math.abs(dy / dx) < h / w) {
      // Hit vertical edge of box1
      return [cx + (dx > 0 ? w : -w), cy + dy * w / Math.abs(dx)];
     } else {
      // Hit horizontal edge of box1
      return [cx + dx * h / Math.abs(dy), cy + (dy > 0 ? h : -h)];
      }
    };
    
    function makeDraggable(evt) {
      var svg = evt.target;
      svg.addEventListener('mousedown', startDrag);
      svg.addEventListener('mousemove', drag);
      svg.addEventListener('mouseup', endDrag);
    
      function getMousePosition(evt) {
        var CTM = svg.getScreenCTM();
        return {
          x: (evt.clientX - CTM.e) / CTM.a,
          y: (evt.clientY - CTM.f) / CTM.d
        };
      }
    
      var selectedElement, offset;
    
      function startDrag(evt) {
        if (evt.target.classList.contains('draggable')) {
          selectedElement = evt.target;
          offset = getMousePosition(evt);
          offset.x -= parseFloat(selectedElement.getAttributeNS(null, "x"));
          offset.y -= parseFloat(selectedElement.getAttributeNS(null, "y"));
        }
      }
    
      function drag(evt) {
        if (selectedElement) {
          var coord = getMousePosition(evt);
          selectedElement.setAttributeNS(null, "x", coord.x - offset.x);
          selectedElement.setAttributeNS(null, "y", coord.y - offset.y);
          updateConnection();
        }
      }
    
      function endDrag(evt) {
        selectedElement = null;
      }
    }
    .static {
      cursor: not-allowed;
    }
    
    .draggable {
      cursor: move;
      fill: #007bff;
      fill-opacity: 0.1;
      stroke: #007bff;
      stroke-width: 0.2;
    }
    
    #connection {
      stroke-width: 0.1;
      stroke: red;
    }
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 20" onload="makeDraggable(evt)" width="400" height="200">
        
      <rect id="rect1" class="draggable" x="4" y="5" width="4" height="3"/>
      <rect id="rect2" class="draggable" x="18" y="5" width="3" height="5"/>
      <line id="connection" />
    </svg>