Search code examples
javascriptpythonsvgplotlytrigonometry

Arc SVG Parameters


I've been trying to understand arc svg since it seems I need them in plotly -- my goal is to plot circle intersections.

My original idea was something like this:

enter image description here

for every intersection, to find the start and end coordinates as well as the height - but I am not very sure of where to go from here. It seems I am lacking the rotation and Large Arc Flag / Sweep parameters, and I am not sure how I would go about retrieving them. If anyone could point me into the right direction here, that would be great!


Solution

  • Circles and intercepting points

    MDN gives "A rx,ry xAxisRotate LargeArcFlag,SweepFlag x,y" as an arc in the path element. What are rx and ry? I would guess radius for x,y.

    I am guessing you would use it as

    // x,y start position
    // rx,ry radius x and y
    // x1,y1 end position
    <path d="M x,y A rx, ry, 0 1 1 x1, y1"/>
    

    Below is the problem solved as javascript. I have Commented the part you need for the SVG. The two end points (intercepts)

    There is a lot of redundancy but its not clear what you want so the code provides how to find other parts of two intersecting circles.

    Law of Cosines

    The math to solve the problem is called the law of cosines that is used to solve triangles.

    In this case the triangle is created from 3 lengths. One each of the circle radius and one is the distance between circle centers. The image gives more details

    enter image description here

    With the angle c you can find the lengths GE, DE, and EF. If you want the angle for the other side at point f just swap B and C.

    Example

    Move mouse to check intercept.

    const ctx = canvas.getContext("2d");
    const m = {
      x: 0,
      y: 0
    };
    document.addEventListener("mousemove", e => {
      var b = canvas.getBoundingClientRect();
      m.x = e.pageX - b.left - scrollX;
      m.y = e.pageY - b.top - scrollY;
    });
    const PI = Math.PI;
    const PI2 = Math.PI * 2;
    const circles = [];
    
    function circle(x, y, r, col, f = 0, t = PI2, w = 2) {
      var c;
      circles.push(c = { x, y,r, col, f, t, w});
      return c;
    };
    
    function drawCircle(A) {
      ctx.strokeStyle = A.col;
      ctx.lineWidth = A.w;
      ctx.beginPath();
      ctx.arc(A.x, A.y, A.r, A.f, A.t);
      ctx.stroke();
    }
    
    function mark(x, y, r, c) {
      ctx.strokeStyle = c;
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.arc(x, y, r, 0, PI2);
      ctx.stroke();
    }
    
    function line(A, B, c) {
      ctx.strokeStyle = c;
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.lineTo(A.x, A.y);
      ctx.lineTo(B.x, B.y);
      ctx.stroke();
    }
    
    // note I am sharing calc results between function  
    
    
    function circleIntercept(A, B) {
      var vx, vy, dist, c, d, x, y, x1, y1, x2, y2, dir, a1, a2;
      // Vec from A to B
      vx = B.x - A.x;
      vy = B.y - A.y;
      // Distance between
      dist = Math.sqrt(vx * vx + vy * vy);
      // Are the intercepting
      if (dist < A.r + B.r && dist > B.r - A.r) {
        c = (B.r * B.r - (dist * dist + A.r * A.r)) / (-2 * dist);
    
        // Find mid point on cord
        x = A.x + vx * (c / dist);
        y = A.y + vy * (c / dist);
        mark(x, y, 5, "blue");
    
        // Find circumference intercepts
    
        //#################################################################
        //=================================================================
        // SVG path 
        // Use x1,y1 and x2,y2 as the start and end angles of the ArcTo SVG
        d = Math.sqrt(A.r * A.r - c * c);
        x1 = x - vy * (d / dist);
        y1 = y + vx * (d / dist);
        x2 = x + vy * (d / dist);
        y2 = y - vx * (d / dist);
        // SVG path from above coords
        // d = `M ${x1}, ${y1} A ${A.r}, ${A,r1} 0, 1, 1, ${x2}, ${y2}`;
        //=================================================================
    
        // draw the chord
        line({x: x1,y: y1}, {x: x2,y: y2}, "red");
        
        // mark the intercepts
        mark(x1, y1, 5, "Green");
        mark(x2, y2, 5, "Orange");
    
        // Get direction from A to B
        dir = Math.atan2(vy, vx);
    
        // Get half inside sweep
        a1 = Math.acos(c / A.r);
    
        // Draw arc for A
        A.col = "black";
        A.w = 4;
        A.f = dir - a1;
        A.t = dir + a1;
        drawCircle(A);
        A.col = "#aaa";
        A.w = 2;
        A.f = 0;
        A.t = PI2;
    
        // inside sweep for B
        a2 = Math.asin(d / B.r);
    
        // Draw arc for B
        B.col = "black";
        B.w = 4;
        if (dist < c) {
          B.t = dir - a2;
          B.f = dir + a2;
        } else {
    
          B.f = dir + PI - a2;
          B.t = dir + PI + a2;
        }
        drawCircle(B);
        B.col = "#aaa";
        B.w = 2;
        B.f = 0;
        B.t = PI2;
    
      }
    
    }
    
    var w = canvas.width;
    var h = canvas.height;
    var cw = w / 2; // center 
    var ch = h / 2;
    var C1 = circle(cw, ch, ch * 0.5, "#aaa");
    var C2 = circle(cw, ch, ch * 0.8, "#aaa");
    
    function update(timer) {
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.globalAlpha = 1;
      if (w !== innerWidth || h !== innerHeight) {
        cw = (w = canvas.width = innerWidth) / 2;
        ch = (h = canvas.height = innerHeight) / 2;
        C1.x = cw;
        C1.y = ch;
        C1.r = ch * 0.5;
        ctx.lineCap = "round";
      }
      C2.x = m.x;
      C2.y = m.y;
      ctx.clearRect(0, 0, w, h);
    
    
      drawCircle(C1);
      drawCircle(C2);
      circleIntercept(C1, C2);
    
    
    
    
    
    
      requestAnimationFrame(update);
    }
    requestAnimationFrame(update);
    canvas {
      position: absolute;
      top: 0px;
      left: 0px;
    }
    <canvas id="canvas"></canvas>