Cercle, arc et graphe circulaire, avec Canvas et JavaScript

Un algorithme pour dessiner un graphe en tarte avec libellés, exploitant HTML 5 et un peu de trigonométrie...

Pour dessiner un simple cercle, on utilise la méthode arc de Canvas avec une longueur de 2 * PI.

<script>
function circle()
{
var canvas = document.getElementById("canvas1");
var context = canvas.getContext("2d");
context.beginPath();
context.lineWidth="2";
context.arc(100, 100, 90, 0, 2 * Math.PI);
context.stroke();
}
circle();
</script>

Le canevas a une taille de 200 x 200 pixels. Le centre du cercle en x=100, y=100 est donné par arguments x et y de la fonction arc.

Le rayon de 90 pixels est le troisième argument. Les troisième et quatrième arguments donnent l'angle de départ, 0 et l'angle final, deux fois PI.

On peut ignorer l'argument sur le sens de rotation qui est superflu ici.

Le cercle peut être rempli avec la méthode fill

Pour remplir le cercle, on ajoute une commande fill au code précédent, mais aussi on assigne l'attribut fillStyle pour définir la couleur.
La fonction stroke peut être conservée pour ajouter une bordure ou être omise pour un cercle coloré uniquement.

De même que l'on a créé la méthode fillRect pour tracer un rectangle plein, on définit une fonction fillCircle pour obtenir un cercle plein.

function fillCircle()
{
  var canvas = document.getElementById("canvas2");
  var context = canvas.getContext("2d");
  context.beginPath();
  context.fillStyle="#FF4422"
  context.arc(80, 80, 70, 0, 2 * Math.PI);
  context.fill();
}
fillCircle(); 

Créer un graphe en tarte

On crée un graphe composé de secteurs circulaires ou "parts de tarte", en créant ces secteurs avec les méthodes arc, lineTo et fill.

  1. Pour partager le graphe, on additionne les valeurs à attribuer aux secteur, chaque arc sera proportionnel à la valeur statistique représentée, et en pourcentage du nombre PI * 2, qui est la circonférence du cercle.
  2. Chaque part de tarte correspond à un arc dont la position va de la fin de l'arc précédent, à une nouvelle position qui correspond à PI * 2 * valeur / total.
    On trace l'arc avec la fonction arc (on ne dessine pas le cercle entier au départ).
  3. Il faut ajouter une correction si l'on veut que la première part soit en haut, pour cela on déduit PI / 2 qui correspond à un quart de circonférence.
  4. On trace un rayon qui va du centre à cette dernière position. Comme l'arc il sera en fait invisible sur le graphe car il a même couleur que le secteur.
    En fait, comme on vient de tracer l'arc, la fonction moveTo est inutile, il suffit de continuer le tracé qui va de la circonférence jusqu'au centre du cercle avec la méthode lineTo.
  5. On remplit le secteur de la couleur qui lui est affectée, avec la méthode fill. On choisit les couleurs successives dans une liste indexée préétablie.

Exemple:

Code source

function pie(ctx, w, h, datalist)
{
var radius = h / 2 - 5;
var centerx = w / 2;
var centery = h / 2;
var total = 0;
for(x=0; x < datalist.length; x++) { total += datalist[x]; };
var lastend=0;
var offset = Math.PI / 2;
for(x=0; x < datalist.length; x++)
{
var thispart = datalist[x];
ctx.beginPath();
ctx.fillStyle = colist[x];
ctx.moveTo(centerx,centery);
var arcsector = Math.PI * (2 * thispart / total);
ctx.arc(centerx, centery, radius, lastend - offset, lastend + arcsector - offset, false);
ctx.lineTo(centerx, centery);
ctx.fill();
ctx.closePath();
lastend += arcsector;
}
}
var datalist= new Array(35, 25, 20, 12, 7, 1);
var colist = new Array('blue', 'red', 'green', 'orange', 'gray', 'yellow');
var canvas = document.getElementById("canvas3");
var ctx = canvas.getContext('2d');
pie(ctx, canvas.width, canvas.height, datalist);

Les valeurs sont dans le tableau datalist et les couleurs dans colist. On parcourt ces deux tableaux dans la fonction pie() pour créer les secteurs successifs selon les valeurs et couleurs successives. Note: Ne pas utiliser la fonction for(x in tableau), cela ne fonctionne pas.

Il est possible d'aplatir le graphe pour créer un effet 3D, avec la fonction scale, voir l'article sur ellipse.

Le code a été testé avec tous les navigateurs supportant Canvas.

Des libellés sur le graphe circulaire

Afficher les libellés à coté du graphe avec leurs valeurs respectives n'est pas aussi parlant que si ces libellés étaient placés sur le graphe.

Navigateurs, parts de marché 05/2015
Exemple: Parts de marché des navigateurs en mai 2015 selon StatCounter.

Avec un peu de trigonométrie, on peut positionner ces libellés au centre de chaque part du graphe. Sauf lorsque les quartiers sont trop petits, cela deviendrait illisible, alors on omet simplement les libellés.

Code source

function pie(ctx, w, h)
{
  var radius = h * 2 - 5;
  var centerx = w / 2;
  var centery = h / 2;
  var lastend = 0;

  var offset = Math.PI / 2;
  var labelxy = new Array();

  var fontSize = Math.floor(canvas.height / 33);
  ctx.textAlign = 'center';
  ctx.font = fontSize + "px Arial";
  var total = 0;
  for(x=0; x < datalist.length; x++) { total += datalist[x]; };

  for(x=0; x < datalist.length; x++)
  {
    var thispart = datalist[x]; 
    ctx.beginPath();
    ctx.fillStyle = colist[x];
    ctx.moveTo(centerx,centery);
    var arcsector = Math.PI * (2 * thispart / total);
    ctx.arc(centerx, centery, radius, lastend - offset, lastend + arcsector - offset, false);
    ctx.lineTo(centerx, centery);
    ctx.fill();
    ctx.closePath();		
    if(thispart > (total / 20))
       labelxy.push(lastend + arcsector / 2 + Math.PI + offset);
    lastend += arcsector;	
  }
  
  var lradius = radius * 3 / 4; 
  ctx.strokeStyle = "rgb(0,0,0)";
  ctx.fillStyle = "rgb(0,0,0)";
  for(i=0; i < labelxy.length; i++)
  {	  
    var langle = labelxy[i];
    var dx = centerx + lradius * Math.cos(langle);
    var dy = centery + lradius * Math.sin(langle);	
    ctx.fillText(datalist[i], dx, dy);	
  }	
}

On a ajouté le tableau labelxy pour stocker la position de chaque libellé qui est calculée lors du dessin du graphe. Le texte est placé après car il peut déborder d'un secteur.
Lorsque une portion du graphe - donc un pourcentage statistique - est inférieure à 1/20 ème de la distribution totale, le libellé n'est pas placé sur le graphe pour éviter un entassement de texte qui deviendrait illisible. En fait, si un graphe en tarte à de trop nombreux quartiers, alors les pourcentages seraient mieux représentés sur un graphe en barres. Un autre chapitre à ajouter à ce tutoriel...

Une démonstration de ces algorithmes est donnée par le générateur de graphe circulaire en ligne disponible sur ce site.