Tracer un arc avec le canvas 2D et SVG

Comment tracer un arc d'un angle donné, centré sur un point O donné et à partir d'un point A donné, avec le contexte 2d du canvas et avec SVG ?
Tracé d'un arc
La tâche n'a rien d'évident, comme il est possible d'en juger d'après le code permettant de la remplir dans l'un et l'autre cas :
  • Avec le contexte 2d du canvas, il faut demander le tracé d'un chemin en utilisant les fonctions moveTo (x0, y0) et arcTo (x1, y1, x2, y2, r), l'arc étant défini comme la plus petite partie du cercle de rayon r tangent aux droites joignant les points de tangence de ce cercle avec la droite de (x1, y1) à (x0, y0), d'une part, et avec la droite de (x1, y1) à (x2, y2), d'autre part.
  • En SVG, il faut demander le tracé d'un chemin en utilisant un objet path dont l'attribut p prend la valeur M x0 y0 A r r 0 largeArc sweep x1 y1, où (x0, y0) est le point de départ de l'arc et (x1, y1) son point d'arrivée, le cercle dont l'arc est une partie étant de rayon r et de centre déterminé par une combinaison de largeArc et sweep.

La solution

Concernant le contexte 2d du canvas, il existe deux fonctions : arc () et arcTo ().
La première fonction est sympathique. Toutefois, elle ne prend pas en argument les coordonnées du point de départ de l'arc, mais un angle de départ sur un cercle dont les coordonnées du centre et le rayon doivent être founis.
Pour tracer un arc en fournissant les coordonnées du point de départ, il faut mobiliser la seconde fonction, nettement moins sympathique, pour deux raisons :
  • Elle ne permet pas de tracer des arcs balayant plus de 180 degrés. Pour tracer de tels arcs, il faut en combiner plusieurs.
  • Son usage nécessite des calculs géométriques pour déterminer les coordonnées des trois points à fournir à partir des données initiales, à savoir les coordonnées du centre O du cercle, le rayon r du cercle, les coordonnées du point de départ A, l'angle β que l'arc doit parcourir.
En matière de calculs, il faut calculer les coordonnées de B, point d'arrivée de l'arc, qui est l'image de A par une rotation d'angle β autour de O dans le sens trigonométrique.
Ensuite, les coordonnées de C peuvent être calculées de plusieurs manières, dont les suivantes :
  • appliquer à A une rotation de β / 2 autour de O, puis effectuer une translation de l'image de A ;
  • calculer l'intersection des deux demi-droites orthogonales à OA pour l'une et à OB pour l'autre.
Concernant SVG, il faut créer un objet path. Pas de limitation sur l'angle que l'arc peut balayer ici. Par ailleurs, les calculs se limitent à celui des coordonnées de B, comme indiqué plus tôt.
Toutefois, il y a bien une difficulté : c'est la détermination des valeurs des paramètres longArc et sweep à mentionner dans la description du chemin.

Le code JavaScript

Soit un arc partant d'un point A (350, 300) et décrivant un angle β de 60 degrés autour d'un centre O (200, 200)
Avec le contexte 2d du canvas, le code suivant permet de tracer l'arc (le calcul adopté est celui de l'intersection des tangentes) :
var xA = 350, yA = 300, xO = 200, yO = 200, beta = 60, xB, yB, dxA, dyA, dxB, dyB, xC, yC;

xB = Math.round (xO + (xA - xO) * Math.cos (beta) + (yA - yO) * Math.sin (beta));
yB = Math.round (yO - (xA - xO) * Math.sin (beta) + (yA - yO) * Math.cos (beta));
dxA = yO - yA;
dyA = xA - xO;
dxB = yB - yO;
dyB = xO - xB;
xC = (yB - yA + xA * dyA / dxA - xB * dyB / dxB) / (dyA / dxA - dyB / dxB);
yC = yB + (xC - xB) * dyB / dxB;
context2d.beginPath ();
context2d.moveTo (xA, yA);
context2d.arcTo (xC, yC, xB, yB, r);
context2d.stroke ();
Avec SVG, le code suivant permet de tracer l'arc :
var xA = 350, yA = 300, xO = 200, yO = 200, beta = 60, p, r, xB, yB;

p = document.createElementNS ("http://www.w3.org/2000/svg", "path");
r = Math.sqrt ((xA - xO) * (xA - xO) + (yA - yO) * (yA - yO));
xB = Math.round (xO + (xA - xO) * Math.cos (beta * Math.PI / 180.0) + (yA - yO) * Math.sin (beta * Math.PI / 180.0));
yB = Math.round (yO - (xA - xO) * Math.sin (beta * Math.PI / 180.0) + (yA - yO) * Math.cos (beta * Math.PI / 180.0));
p.setAttribute ("d", "M " + xA + " " + yA + " A " + r + " " + r + " 0 " + (beta > 180 ? 1 : 0) + " 0 " + xB + " " + yB);

L'exemple

Cliquez ici pour accéder à une page de test minimaliste. Vous pourrez visualiser le code et le récupérer pour travailler avec.

Les maths / La logique

Les maths avec le contexte 2d du canvas
La fonction arcTo (xC, yC, xB, yB, r) prend en argument les coordonnées de deux points C et B et un rayon r. De plus, elle exploite les coordonnées d'un point A constituant le dernier point du chemin en cours. Ainsi, pour tracer un arc joignant A à B, il faut préalablement avoir appelé moveTo (xA, yA).
A quoi sert C ? Et comment la fonction peut-elle connaître le centre du cercle auquel appartient l'arc ? Pour tracer l'arc, arcTo () détermine le cercle de rayon r inscrit dans le secteur angulaire ACB tel que B constitue un point de tangence avec le côté CB du secteur en question :
Tracé d'un arc avec arcTo () du contexte 2d du canvas : principe
Le tracé du chemin comprend deux choses indissociables :
  • le segment joignant A au point de tangence du cercle avec le côté CA ;
  • le plus petit arc du cercle inscrit dans le secteur ACB joignant ce point de tangence à B.
Tracé d'un arc avec arcTo () du contexte 2d du canvas : résultat
Si A constitue le point de tangence du cercle avec CA, le segment n'est pas tracé puisqu'il est nul. C'est ce résultat qu'il faut viser.
L'application de ces règles au cas dont il est question ici permet de formuler le problème géométrique suivant. Soit le point A à partir duquel il faut tracer un arc d'angle β emprunté au cercle de centre O et de rayon r. Où doivent se trouver les points B, l'autre extrémité de l'arc, et C, le sommet du secteur dans lequel le cercle est inscrit ?
Tracé d'un arc avec arcTo () du contexte 2d du canvas : calculs
Les coordonnées de B se calculent facilement, car B est l'image de A par la rotation de centre O et d'angle β. Attention ! Il s'agit du sens de rotation trigonométrique, inverse du sens de rotation standard du contexte 2d du canvas. Nous avons déjà vu comment calculer les coordonnées de l'image d'un point par une rotation autour d'un centre. Le résultat est :
  • xB = xO + (xA - xO) * cos (β) + (yA - yO) * sin (β)
  • yB = yO - (xA - xO) * sin (β) + (yA - yO) * cos (β)
Le calcul des coordonnées de C est plus délicat. Entre autres, deux solutions :
  • appliquer à A une rotation de β / 2 autour de O, puis effectuer une translation de l'image de A le long de OA ;
  • calculer l'intersection des deux demi-droites orthogonales à OA pour l'une et à OB pour l'autre.
La première solution est la plus simple, car nous avons aussi déjà vu comment calculer les coordonnées de l'image d'un point par une translation le long d'un vecteur. Soit D l'image de A par la rotation d'angle β / 2 autour de O :
  • xD = xO + (xA - xO) * cos (β / 2) + (yA - yO) * sin (β / 2)
  • yD = yO - (xA - xO) * sin (β / 2) + (yA - yO) * cos (β / 2)
Le triangle OAC est rectangle en A. Le cosinus de β / 2 se calcule donc par :
cos (β / 2) = r / OC
D'où :
OC = r / cos (β / 2)
Il ne reste plus qu'à effectuer la translation de D le long de OD d'un facteur OC / OD pour trouver les coordonnées de C :
  • xC = r * xD / cos (β / 2)
  • yC = r * yD / cos (β / 2)
La logique avec SVG
SVG ne construit par un arc à partir du cercle inscrit dans un secteur, mais à partir des intersections de deux cercles de même rayon.
Le chemin doit mentionner les coordonnées des points d'intersection A et B ainsi que le rayon r du cercle. Il doit aussi mentionner deux paramètres pouvant prendre la valeur 0 ou 1, largeArc et sweep. C'est la combinaison de ces deux paramètres qui détermine lequel des quatres arcs possibles SVG doit tracer :
Tracé d'un arc avec path en SVG : principe
Pour parvenir au résultat recherché ici, sweep doit toujours prendre la valeur 0, mais largeArc doit prendre une valeur différente selon l'angle β :
  • 0 si β est inférieur à 180 degrés ;
  • 1 si β est supérieur à 180 degrés.
Cela se résume par l'expression JavaScript :
(angle > 180 ? 1 : 0)
Cliquez ici pour tester la manière dont les diverses combinaisons de largeArc et sweep peuvent déterminer l'arc tracé par SVG.
Tracer un arc avec le canvas 2D et SVG