Arcs de cercle avec GD, canvas ou SVG

Le but est de dessiner un Arc de cercle propotionnel à une note ou un pourcentage, avec les technologies php et/ou javascript comme le montre l'animation ci-dessous.

Les contraintes sont:

  1. Un arc de cercle propotionnel à un pourcentage variable
  2. Un cercle de rayon 70px,
  3. Afficher le pourcentage au centre en Arial bold
  4. Le trait doit être très épais (14px mini).

0
Il est possible d'ultiliser côté serveur

  • une image générée avec la | bibliothèque GD |

Côté client on dispose de deux composants HTML pour le réaliser:

  • un [ CANVAS ]
  • un [ SVG ]

Avec la bibliothéque GD en PHP

Introduction

La bibliothéque GD est une librairie qui permet de manipuler et créer des images en PHP. Il faut donc disposer de cette bibliothéque et de la bonne version . Vous pouvez utiliser la fonction gd_info() pour connaître les informations sur la bibliothéque installée. (ici pas de souci de version, avec les fonctions que nous allons utiliser)

Cette méthode, consiste donc à créer un fichier createArc.php afin qu'en réponse à la requête http://elisabeth.pointal.org/doc/demo/createArc.php?pct=64 on reçoive une image générée via php avec l'arc correspondant.

L'affichage est alors très aisée dans ce cas:

 <!-- Une première image -->
 <img src="http://elisabeth.pointal.org/doc/demo/createArc.php?pct=64" alt="Pourcentage de réussite 64%" />
 <!-- Une seconde -->
 <img src="http://elisabeth.pointal.org/doc/demo/createArc.php?pct=80" alt="Pourcentage de réussite 80%" />

Par contre, il n'est pas question de réaliser une animation avec cette technologie.

Création d'une image avec GD

Je détaille ici les étapes pour obtenir une image qui correspond grossièrement à mon objectif:

<?php
/**
 * @author elisabeth pointal
 * @description création d'une image png 200x200 contenant un arc de cercle proportionnel
 * à un pourcentage provenant de la variable $_GET pct
 */

//1 Récupération du  pourcentage
//-----------------------------
 $pct = 75; //valeur par défaut
 if(isset($_GET["pct"])){
     $pct = intVal($_GET["pct"]);
 }

 //2 Calcul
 //-----------
 //On normalise le pourcentage pour qu'il soit un nombre entre 0 et 100
 $pct = $pct%101;
 //L'angle de départ 
 $start = 180;
 //L'angle final : depart + pourcentage de 360°
 $end = $start + 3.6*$pct;
 
 //3 Création de l'image
 //---------------------
 // on créé l'image en vraies couleurs avec 
 //une largeur de 200 pixels et une hauteur de 200 pixels
 $image = imagecreatetruecolor(200 ,200 );

 //4 Dessin
 //--------
 // on créé la couleur de l'arc de cercle, couleur RGB correspondant à la couleur HTML #02E5ED 
 //et on l'alloue à l'image, la fonction imagecolorallocate retourne un entier identifiant de la couleur 
 $color = imagecolorallocate($image,2,229,237);
//on dessine l'arc de cercle, il y a justement une fonction pour
// les paramètres sont 
// - l'image
// - l'abcisse du centre : 100
// - l'ordonnée du centre : 100
// - la largeur de l'ellipse : 2 x 70 = 140
// - la hauteur de l'ellipse : 2 x 70 = 140
// - l'angle de départ en degré: $start qui vaut 180°
// - l'angle final en degré: $end
// - la couleur : $color
  imagearc ($image , 100 , 100 , 140 ,  140 , $start ,  $end , $color );
 
 // on ajoute le texte "64 %"
 $white = imagecolorallocate($image, 255, 255, 255);
 //Les paramètres de imagettftext
 //- l'image $image
 //- la taille des caractères : 24
 //- l'angle de rotation du texte: 0°
 //- la position en abcisse: 70
 //- la position en ordonnée: 100
 //- l'identifiant de la couleur: $white
 //- l'adresse de la police de caractères:'font/arial-bold.ttf'  
 //(je précise que j'ai téléchargé le fichier et l'ai mis à cette adresse )
 //- le texte à afficher : $pct." %"
 imagettftext($image, 24, 0,70, 100,$white,'font/arial-bold.ttf',$pct." %");

//5 La réponse: retourne l'image créée
 header("Content-type: image/png"); //la ligne qui change tout ! à commenter pour le débuggage!
 imagepng($image); //renvoie une image sous format png
 imagedestroy($image); //IMPORTANT! détruit l'image, libérant ainsi de la mémoire 
Voici le résultat:

L'image de l'Arc de cercle avec GD

Le résultat précédent est loin d'être satisfaisant, il reste quelques améliorations à apporter:

  1. Le trait est peu fluide → en créant une image plus grande le trait sera plus fluide
  2. On ne peut pas définir l'épaisseur du trait → on va créer un arc plein avec la fonction imagefilledarc et un autre arc plein avec un rayon plus petit à l'intérieur de la couleur du fond. Principe de création d'un trait épais pour un arc de cercle par extraction du coeur d'un arc de cercle plein
  3. Le fond est noir au lieu d'être transparent → il faut définir la couleur du fond comme transparente avec la fonction imagecolortransparent.

Le code devient alors:

<?php
//1 Récupération du  pourcentage
//-----------------------------
 $pct = 75; //valeur par défaut
 if(isset($_GET["pct"])){
     $pct = intVal($_GET["pct"]);
 }

 //2 Calcul
 //-----------
 //On normalise le pourcentage pour qu'il soit un nombre entre 0 et 100
 $pct = $pct%100;
 //L'angle de départ 
 $start = 180;
 //L'angle final : depart + pourcentage de 360°
 $end = $start + 3.6*$pct;
 
 //facteur d'agrandissement
 $k = 3;
 $w = 200 * $k;
 
 //3 Création de l'image
 //---------------------
//Création de l'image de taille $w pixels sur $w pixels
 $image = imagecreatetruecolor($w, $w);
 
 //On alloue la couleur noire RGB=(0,0,0)
 $maskTransparent = imagecolorallocate($image, 0, 0, 0);
 //On déclare cette couleur comme transparente
 imagecolortransparent($image, $maskTransparent);
 

 //4 Dessin
 //--------
//On alloue notre couleur bleue rgb(2,229,237) = HTML #02E5ED 
 $color = imagecolorallocate($image,2,229,237);
//on dessine un premier arc de cercle plein de ma couleur centré au centre de l'image
// d'un rayon de 70 + 7 = 77 , on en  déduit la largeur et hauteur 77 x 2 = 154 , puis avec le facteur d'agrandissement $k
 imagefilledarc($image, $w/2, $w/2, 154*$k, 154*$k, $start, $end, $color, IMG_ARC_PIE);
 
 //On dessine le deuxième arc de cercle plein transparent à l'intérieur du premier avec un angle légérement plus grand
 // et un rayon plus de 14px petit 70 -7 = 63 (63 * 2 = 126)
 imagefilledarc($image, $w/2 ,$w/2, 126*$k , 126*$k, $start-2, $end+2, $maskTransparent, IMG_ARC_PIE);
 
 $white = imagecolorallocate($image, 255, 255, 255);
 imagettftext($image, 24*$k, 0, $w/2-30*$k, $w/2, $white, 'font/arial-bold.ttf', $pct." %");
 
 //5 La réponse: retourne l'image créée
 //-------------------------------------
  header("Content-type: image/png"); //la ligne qui change tout ! à commenter pour le débuggage
 imagepng($image); //renvoie une image sous format png
 imagedestroy($image); //IMPORTANT!! détruit l'image, libérant ainsi de la mémoire

Le résultat est alors avec la taille de l'image contrainte à 200px de largeur:

Avec un canvas

C'est sans doute, la plus simple et rapide à mettre en place des méthodes. Il suffit d'ajouter une balise Canvas au DOM puis de dessiner l'arc de cercle avec du javascript au chargement de la page.

le code

La balise canvas:

<canvas id="canvas0" width="200" height="200"></canvas>

Le javascript:

/**
* Dessine un arc de cercle proportionnel au pourcentage pct dans le canvas d'identifiant id
* @param id string l'identifiant du canvas
* @param pct float le pourcentage
* */
function drawCanvasArc(id, pct){
  var canvas = document.getElementById(id);
  var context = canvas.getContext("2d");
   
  //je calcule la fin de mon arc en radian
  var start = Math.PI;
  var end = pct* Math.PI/50 + start;
   
  //on supprime le dessin précédent
  context.clearRect(0, 0, canvas.width, canvas.height);
  
  //on commence une ligne
  context.beginPath();
  //l'épaisseur de la ligne
  context.lineWidth = 14;
  //La couleur de la ligne
  context.strokeStyle = '#83f954';
  //Définition de l'arc de cercle
  //- l'abcisse du centre : 100
  //- l'ordonnée du centre : 100
  //- le rayon de l'arc : 70
  //- l'angle de départ (en radian) : start
  //- l'angle final : end
  //- le sens de rotation : false (aiguille d'une montre)
  context.arc(100, 100, 70, start ,end ,false);
  // Dessine la ligne
  context.stroke();
  
  // Définit le style de police de caractère, l'alignement et la couleur
  context.font="bold 30px Arial";
  context.textAlign="center"; 
  context.fillStyle ="#fff";
  // Ecrit pct +" %" à la position (100, 100)
  context.fillText(pct + " %",100,100);
}
//on dessine l'arc
drawCanvasArc( "canvas0", 75);

Le résultat

Animation

Pour animer l'affichage de l'arc de cercle, on utilitse une fonction récursive avec un setTimeout comme suit :

/**
* Anime l'affichage de l'arc de cercle
* @param id string l'identifiant du canvas
* @param currentpct float le pourcentage courant à afficher au moment t
* @param pct float le pourcentage final à afficher
*/
function animatedArc(id, currentpct, pct){
     //le processus se termine quand currentpct a atteind pct
     if(currentpct <= pct ){
         // On dessine l'arc courant avec la fonction définie au paragraphe précédent
         drawCanvasArc( id, currentpct);
         // On passe au pourcentage suivant
         setTimeout(function(){ animateArc( id, currentpct+1, pct); }, 100); 
     }
}
// On lance l'animation en commençant par un pourcentage de 0 et le pourcentage final 75
animatedArc("canvas0", 0 , 75);

Avec un SVG

Le [ SVG ] offre beaucoup plus de possibilités que les précédentes technologies. En effet, il s'agit de dessin vectoriel. Les éléments dessinés sont des éléments du DOM et se comportent comme tels. On peut donc :

  • les écrire directement dans la page HTML
  • les générer avec php comme tous les éléments de la page
  • les modifier via javascript
  • définir leur style en css
  • les animer
  • …etc

Les arcs svg - La balise <path>

Ce n'est pas ce qu'il y a plus simple de dessiner un arc de cercle en svg. Il faut utiliser une balise path et écrire le chemin d'un arc elliptique. Cet arc est défini par les deux points par lequels il passe, et son rayon. Je mets un exemple de suite et détaillerai les points problématique ensuite.

<!-- le css de mon arc un trait de 8px vert,  non rempli-->
<style>
 path{ 
    stroke:#13f10c; 
    stroke-width:8px; 
    fill:none;
 }
</style>
<svg width="200" height="200">
  <!-- Le plus compliqué, car il faut décrire le chemin avec l'attibut d
       M 30 100 les coordonnées du point de départ 
       A pour arc 
       70 70  sont les rayons respectifs en abcisse et en ordonnée
       0 la rotation de l'axe des abcisse     
       1 large-arc-flag  on choisit  le grand arc
       1 sweep-flag le sens de parcours dans le sens des aiguilles d'une montre
       100 170 les coordonnées de mon point final -->
  
  <path id="arc" d="M 30 100 A 70 70 0 1 1 100 170"></path>
</svg>
On obtient l'arc suivant avec ce chemin. On peut créer 4 arcs différents avec ses 2 points en jouant sur les paramètres larg-arc-flag et sweep-flag , c'est là qu'il faut faire attention. (Cliquez sur les arcs pour obtenir leur “chemin”);
x (0°) y sweep-flag = 1

Création de l'Arc svg avec php

Pour créer un tel arc il nous suffit juste de calculer l'attribut d de l'élément path

/**
 * Calcul les coordonnées d'un point d'un cercle à partir de son angle en degré
 * pour un repère orienté en dans le sens direct (tourne de x>0 vers y>0) 
 * @param array $center tableau des coordonnées du centre du cercle
 * @param float $radius rayon du cercle
 * @param float $angleInDegrees angle en degré du point sur le cercle
 * @return array[x,y] tableau des coordonnées cartésiennes du point
 */
function polarToCartesian($center, $radius, $angleInDegrees) {
    $angleInRadians = $angleInDegrees * M_PI / 180.0;

    return array(
        "x" => $center["x"] + $radius * cos($angleInRadians),
        "y" => $center["y"] + $radius * sin($angleInRadians)
    );
}

/**
 * Ecrit l'attribut d du path d'un arc de cercle 
 * @param array $center coordonnées du centre du cercle
 * @param float $radius rayon du cercle
 * @param float $startAngle angle de départ de l'arc en degré 
 * @param float $endAngle angle final de l'Arc en degré 
 * @return string attribut d du path de l'arc de cercle
 */
function describeArc($center, $radius, $startAngle, $endAngle){
    //Petite correction quand end = start +360 car les deux points sont confondus
    if( $endAngle = $startAngle + 360){
        $endAngle -= 0.1;
    }
    //calcul les coordonnées du point de départ et du point final
    $start = polarToCartesian( $center, $radius, $startAngle);
    $end = polarToCartesian( $center, $radius, $endAngle);
    
    //si le secteur angulaire est supérieure à 180° large-arc-flag = 1
    $largeArcFlag = $endAngle - $startAngle <= 180 ? "0" : "1";

    $d = array(
            "M", $start["x"], $start["y"],
            "A", $radius, $radius, 0, $largeArcFlag, 1, $end["x"], $end["y"]
    );

    return join( " ", $d );
}

$center = array( "x" => 100, "y" => 100); 

Puis de l'appeler lors de la construction de la page

<?php 
header('Content-Type: text/html; charset=utf-8');
?>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="title" content="Génération d'un arc de cercle avec SVG" />
    <style>
    path{ 
        stroke:#13f10c; 
        stroke-width:14px; 
        fill:none;
    }
    svg>text{
        stroke: #fff;
        fill: #fff;
        font-size:30px;
        font-family:Arial;
        font-weight: bold;
    }
    </style>
</head>
<body>
    <svg width="200" height="200">
        <path d="<?=describeArc($center , 70, -180, $end);?>" ></path>
        <text text-anchor="middle" x="100" y="100" > <?=$pct?> % </text>
    </svg>
</body>
</html>
On peut voir le résultat à l'adresse http://elisabeth.pointal.org/doc/demo/createSVGArc.php?pct=70

Création de l'Arc svg avec javascript

On peut faire exactement la même chose avec du javascript qu'avec du php. Les fonctions sont les mêmes, seule le langage change (i.e. Création de l'Arc SVG avec php). Il nous faut juste calculer le chemin d de notre arc à l'aide de la fonction describeArc(center, radius, startAngle, endAngle) et de l'attribuer à l'élément path de notre svg.

On garde, à peu de choses près le html et le css :

  <svg width="200" height="200">
     <!-- J'ajoute une balise g qui  permet de grouper des éléments d'un svg -->
     <g>
        <path></path>
        <text text-anchor="middle" x="100" y="100">0</text>
      </g>
   </svg>

Et pour créer mon arc proportionnel :

// Le pourcentage
var pct = 55;
//Angle de départ
var start = -180;
//Angle final
var end = start + pct * 3.6;

// On met à jour le DOM
var g = document.querySelector("svg>g");
g.querySelector("path").setAttribute("d", describeArc(100, 100, 70, start, end));
g.querySelector("text").textContent = pct + " %";
0

Animation

Le plus simple, ici, étant donné que l'on modifie simultanément l'arc et le texte est d'utiliser la même méthode que pour le canvas : une fonction récursive avec un setTimeout.

Mais Le SVG dispose de possibilités d'animations très importantes: en jouant sur la couleur, l'opacité, les styles en général, avec les transformations (translation, rotation), en modifiant les attributs des objets en général… etc

Ces animations peuvent être définies dans le svg même avec la balise <animate>, dans le css pour des animations sur les styles , ou avec javascript.

Pour conclure

  • Pour la compatibilité avec tous les navigateurs, la solution de générer l'image avec la | bibliothèque GD | est la meilleure. En effet, cette méthode revient à insérer une image dans la page, avec, pour attribut src, l'url du programme qui génère l'image.
  • Pour sa simplicité et sa rapidité, l'utilisation d'un [ CANVAS ]. Le code javascript est simple, il existe une méthode pour dessiner une arc, on peut aussi animer l'affichage facilement.
  • Par contre le dessin vectoriel avec [ SVG ], plus complexe, est beaucoup plus puissant si vous voulez ajouter des éléments sur votre dessin, les animer… etc.

Elisabeth Pointal 22/06/2017 13:44