1

I need to draw an arc having initial point, radius and final point.

I am using the HTML5 canvas arc function (x, y, radius, startAngle, endAngle, anticlockwise) in JavaScript.

context.arc(x, y, radius, startAngle, endAngle, anticlockwise)

Have:

var initialPoint = { x: 20, y: 40 };
var radius = 40;
var finalPoint = { x: 180, y: 40 };

The expected results:

samples

please some help

  • `context.arc` will never draw an arc connecting initialPoint & finalPoint if the desired radius is 40. The arc passing through those 2 points is 80. Remember that `context.arc` **will always draw a partial-circle** so that curve cannot be "bent" to stretch between those 2 points. As suggested in your previous question on this subject...use `context.quadraticCurveTo` to "bend" a curve between 2 points. ;-) – markE Sep 25 '14 at 03:46

2 Answers2

6

You have to do a little math to find the center of a circle that matches your 3 constraints :
• intersects with initialPoints
• intersects with finalPoint
• has provided radius

Notice that there might be no result : if the points are further from twice the radius from one another no circle can match.
If points are < 2 * radius away, we have two results in fact, i don't know how you'd like your user to choose.

The math uses a few properties :
• the circles center are on the line perpendicular to p1, p2.
• pm, the middle point of (p1, p2) is also the middle point of (c1, c2).
• the triangles (p1, pm, c1) and (p1, pm, c2) have a 90° angle in pm (called 'triangle rectangle' in french, donno in english).

Here's a screenshot with the two possible arcs in green/red :

enter image description here

http://jsbin.com/jutidigepeta/1/edit?js,output

var initialPoint = { x: 100, y: 160 };
var radius = 90;
var finalPoint = { x: 240, y: 190 };

var centers = findCenters(radius,initialPoint, finalPoint );

Core function :

//
function findCenters(r, p1, p2) {
  // pm is middle point of (p1, p2)
  var pm = { x : 0.5 * (p1.x + p2.x) , y: 0.5*(p1.y+p2.y) } ;
  drawPoint(pm, 'PM (middle)');
  // compute leading vector of the perpendicular to p1 p2 == C1C2 line
  var perpABdx= - ( p2.y - p1.y );
  var perpABdy = p2.x - p1.x;
  // normalize vector
  var norm = Math.sqrt(sq(perpABdx) + sq(perpABdy));
  perpABdx/=norm;
  perpABdy/=norm;
  // compute distance from pm to p1
  var dpmp1 = Math.sqrt(sq(pm.x-p1.x) + sq(pm.y-p1.y));
  // sin of the angle between { circle center,  middle , p1 } 
  var sin = dpmp1 / r ;
  // is such a circle possible ?
  if (sin<-1 || sin >1) return null; // no, return null
  // yes, compute the two centers
  var cos = Math.sqrt(1-sq(sin));   // build cos out of sin
  var d = r*cos;
  var res1 = { x : pm.x + perpABdx*d, y: pm.y + perpABdy*d };
  var res2 = { x : pm.x - perpABdx*d, y: pm.y - perpABdy*d };
  return { c1 : res1, c2 : res2} ;  
}

utilities :

function sq(x) { return x*x ; }

function drawPoint(p, name) {
  ctx.fillRect(p.x - 1,p.y - 1,2, 2);
  ctx.textAlign = 'center';
  ctx.fillText(name, p.x, p.y+10);
}

function drawCircle(c, r) {
  ctx.beginPath();
  ctx.arc(c.x, c.y, r, 0, 6.28);
  ctx.strokeStyle='#000';
  ctx.stroke();
}

function drawCircleArc(c, r, p1, p2, col) {
  var ang1 = Math.atan2(p1.y-c.y, p1.x-c.x);
  var ang2 = Math.atan2(p2.y-c.y, p2.x-c.x);
  ctx.beginPath();
  var clockwise = ( ang1 > ang2);
  ctx.arc(c.x, c.y, r, ang1, ang2, clockwise);
  ctx.strokeStyle=col;
  ctx.stroke();
}

Edit :

Here a fiddle using 'side', a boolean that states which side of the arc we should choose.

http://jsbin.com/jutidigepeta/3/edit

GameAlchemist
  • 18,995
  • 7
  • 36
  • 59
  • You're welcome. My pseudo is GameAlchemist, i'm sure you can spend the time required to type it all. About your question, if i guess well, it's about knowing which side of the arc to draw, in that case you can either 1) have the user give a third point indicating the part of the space where the arc should be. 2) consider by convention that the arc should always be on the left of p1p2. Rq that in that case the order of (p1, p2) matters. – GameAlchemist Sep 25 '14 at 13:09
  • mmm second thought i guess that a boolean parameter left/right is a better option. Input is P1,P2,R, side. Just change in my code, in drawCircleArc, var clockwise = side; and make use only of c1, the first computed circle center. See my edit. – GameAlchemist Sep 25 '14 at 13:42
1

If anyone is looking for the equivalent in SVG (using D3.js), using the answer from opsb:

function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
  var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;

  return {
    x: centerX + (radius * Math.cos(angleInRadians)),
    y: centerY + (radius * Math.sin(angleInRadians))
  };
}

function describeArc(x, y, radius, startAngle, endAngle){
    var start = polarToCartesian(x, y, radius, endAngle);
    var end = polarToCartesian(x, y, radius, startAngle);
    var arcSweep = endAngle - startAngle <= 180 ? "0" : "1";
    var d = [
        "M", start.x, start.y, 
        "A", radius, radius, 0, arcSweep, 0, end.x, end.y
    ].join(" ");
    return d;       
}

function sq(x) { return x*x ; }

function drawCircleArcSVG(c, r, p1, p2, col) {
  var ang1 = Math.atan2(p1.y-c.y, p1.x-c.x)*180/Math.PI+90;
  var ang2 = Math.atan2(p2.y-c.y, p2.x-c.x)*180/Math.PI+90;
  var clockwise = side;
  var path = describeArc(c.x, c.y, r, ang1, ang2)
  console.log(path)
  svg.append("path").attr("d", path).attr("fill", "none").attr("stroke-width", 3).attr("stroke", col)
}

function drawPointSVG(p, name) {
    svg.append("circle").attr("cx", p.x).attr("cy", p.y).attr("r", 2)
    svg.append("text").attr("x", p.x).attr("y", p.y+10).style("font-size", 10).text(name).style("font-family", "Arial")
}

function drawCircleSVG(c, r) {
    svg.append("circle").attr("cx", c.x).attr("cy", c.y).attr("r", r).style("fill", "none").attr("stroke", "#000")
}

Working example: https://jsbin.com/qawenekesi/edit

Bas Wagenmaker
  • 206
  • 1
  • 7