Search code examples
svgreact-flow

Create an SVG path (w/ arrow) connecting to a circle element


I've got a graph where source and target elements ('nodes') are circular and the connection between these two (an 'edge') must be an arrow, starting in the center of the source node and pointing to the edge of the target circle. I do have exact coordinates of both source and target nodes (center of the circle) and know the radius of the target node. Connecting them (path starting in the center of the source circle and ending in the center of the target circle) is pretty straight forward: M ${sourceX},${sourceY} L ${targetX - 50},${targetY}: enter image description here but can't wrap my head around the algo behind the actual desired result: (please note the target node can be moved around) enter image description here

Thanks!

semi-related link: https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a


Solution

  • You will need to calculate the angle of the arrow:

    let angle = Math.atan2(source.y - target.y, source.x - target.x);

    Next you calculate the position of the tip of the arrow as a point on a circle with the center in the target center and the radius R = radius of the target + width of the marker.

    let p = { x: target.x + R * Math.cos(angle), 
              y: target.y + R * Math.sin(angle)};
    

    Please read the comments in my code:

    let s = { x: 10, y: -32, r:2 }; //source
    let t = { x: -70, y: -85, r:4 }; //target
    
    
    //set the source and target attributes
    source.setAttribute("cx", s.x);
    source.setAttribute("cy", s.y);
    source.setAttribute("r", s.r);
    target.setAttribute("cx", t.x);
    target.setAttribute("cy", t.y);
    target.setAttribute("r", t.r);
    
    
    //the tip of the arrow must be on the target edge so you need to take in consideration the marker wudth
    let R = t.r + Number(mk.getAttribute("markerWidth"));
    
    let a = Math.atan2(s.y - t.y, s.x - t.x); //the angle
    // the point where the path ends
    let p = { x: t.x + R * Math.cos(a), y: t.y + R * Math.sin(a) };
    //set thed attribute of the arrow.
    arrow.setAttribute("d", `M${s.x},${s.y}L${p.x},${p.y}`);
    path{marker-end:url(#mk);}
    <svg viewBox="-100 -100 200 200">
      <marker id="mk" viewBox="0 0 4 4" markerWidth="5" markerHeight="5" refX="0" refY="2" orient="auto" fill="red">
        <polygon points="0,0 4,2 0,4" />
      </marker>
      <circle r="2" id="source" />
      <circle r="4" id="target" />
    
      <path id="arrow" stroke="red" d="M10,25L70,45" />
    </svg>