📜 ⬆️ ⬇️

Drawing an ellipse at an arbitrary canvas angle on javascript

In the process of developing one application, I was faced with the need to draw ellipses at an arbitrary canvas angle on JavaScript. I did not want to use any frameworks in such a simple project, so I went in search of a manual article on this topic. The search was not successful, so I had to deal with the task myself, and I decided to share my experience with you.

We formalize the problem. We need a function drawEllipse (coords, sizes, vector) , where:


The article provides three ways to solve this problem.
')


Bezier curves were chosen as the first method. To construct such a curve, four points are required: initial, final, and two control points.



The desired ellipse will consist of two such curves, and it is easy to guess that the aforementioned points at each of them will be the vertices of a rectangle. Let's explore our ellipse.



  1. We have some vector
    Find the unit vector


    Find the unit vector
    To do this, we recall the property of the scalar product of vectors to vanish if they are perpendicular:
    In this way:
  2. Find vectors , points A 1 , A 2 , B 1 , B 2





  3. Find vectors , points C 1 , C 2 , C 3 , C 4





  4. Recall that to draw an ellipse we need two Bezier curves:
    • The 1st has a starting point B 1 , ending point B 2 , passing through point A 1
    • 2nd has a starting point B 2 , ending B 1 , passing through point A 2
    Recall also that to build Bezier curves, we need control points. Without thinking twice, I first framed the vertices of the rectangle into which the ellipse is inscribed. This decision turned out to be a mistake, because if we consider the construction of a Bezier curve, we find that it does not touch the segment connecting the two control points.
    We draw the moment of construction of the Bezier curve at the point at which it (the curve) will be closest to the segment between the control points. In our case, it will look like this:



    From the figure it is obvious that the distance from this point (A 1 ) to the segment between control points (C 1 , C 2 ) will be a quarter of the distance between the center of the desired ellipse (O) and the same segment (C 1 , C 2 ), then there is:

  5. Denote OA by x. Solve the equation


    Thus, to obtain an ellipse with the necessary parameters, we need to multiply the vector on parameter , then return to the calculations described in paragraphs 1-4. As a result, we obtain sets of points ( B 1 , C 1 , C 2 , B 2 and B 2 , C 3 , C 4 , B 1 ) to construct two Bezier curves, together representing the desired figure.



Actually demo and code:

function drawEllipse(ctx, coords, sizes, vector) { var vLen = Math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); //    var e = [vector[0]/vLen, vector[1]/vLen]; //   e || vector var p = 4/3; //  var a = [e[0]*sizes[0]*p, e[1]*sizes[0]*p]; //   a,   var b = [e[1]*sizes[1], -e[0]*sizes[1]]; //   b //   A1, B1, A2, B2 var dotA1 = [coords[0]+a[0], coords[1]+a[1]]; var dotB1 = [coords[0]+b[0], coords[1]+b[1]]; var dotA2 = [coords[0]-a[0], coords[1]-a[1]]; var dotB2 = [coords[0]-b[0], coords[1]-b[1]]; //   c1, c2 var c1 = [a[0]+b[0], a[1]+b[1]]; var c2 = [a[0]-b[0], a[1]-b[1]]; //   C1, C2, C3, C4 var dotC1 = [coords[0]+c1[0], coords[1]+c1[1]]; var dotC2 = [coords[0]+c2[0], coords[1]+c2[1]]; var dotC3 = [coords[0]-c1[0], coords[1]-c1[1]]; var dotC4 = [coords[0]-c2[0], coords[1]-c2[1]]; //    ctx.strokeStyle = 'black'; ctx.beginPath(); ctx.moveTo(dotB1[0], dotB1[1]); //   ctx.bezierCurveTo(dotC1[0], dotC1[1], dotC2[0], dotC2[1], dotB2[0], dotB2[1]); //    ctx.bezierCurveTo(dotC3[0], dotC3[1], dotC4[0], dotC4[1], dotB1[0], dotB1[1]); //    ,     ctx.stroke(); ctx.closePath(); //   a   a = [e[0]*sizes[0], e[1]*sizes[0]]; //       ,  ,       ctx.beginPath(); ctx.moveTo(coords[0]+a[0], coords[1]+a[1]); ctx.lineTo(coords[0]-a[0], coords[1]-a[1]); ctx.moveTo(coords[0]+b[0], coords[1]+b[1]); ctx.lineTo(coords[0]-b[0], coords[1]-b[1]); ctx.strokeStyle = 'red'; ctx.stroke(); ctx.closePath(); } 



Upd. After reviewing the comments, I wrote the function of drawing an ellipse through a parametric equation, and it turned out that the shape that is obtained using Bezier curves does not quite exactly coincide with the ellipse. On the overlay of the figures, it can be seen that the object drawn by Bezier curves (red) is in some places wider than the regular ellipse (blue). Here is a demo of overlaying shapes.
 function drawEllipseParam(ctx, coords, sizes, angle, segments) { ctx.save(); ctx.translate(coords[0], coords[1]); ctx.rotate(angle); ctx.beginPath(); var x, y, firstTime=true; var dt = 2*Math.PI/segments; for(var t=0; t<2*Math.PI; t+=dt) { x = sizes[0]*Math.cos(t); y = sizes[1]*Math.sin(t); if(firstTime) { firstTime = false; ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } } ctx.strokeStyle = 'blue'; ctx.stroke(); ctx.closePath(); ctx.restore(); } 



Upd. The comments suggested a more native and simple way to draw an inclined ellipse (thanks to subzey ). Leave here, so as not to get lost. Here is a demo .

 function drawEllipse(ctx, coords, sizes, angle) { ctx.beginPath(); ctx.save(); //    ctx.translate(coords[0], coords[1]); //      ctx.rotate(angle); //       ctx.scale(1, sizes[1]/sizes[0]); //    ctx.arc(0, 0, sizes[0], 0, Math.PI*2); //   ctx.restore(); //  ,         ctx.strokeStyle = 'green'; ctx.stroke(); //  ctx.closePath(); } 

Source: https://habr.com/ru/post/255485/


All Articles