TwinXeon by Renaudet
/Accueil/Tous les articles
Génération dynamique d'images pour le Web
(cet article fait suite à un ce premier article)

Génération côté serveur (suite et fin)

Nous avions vu précédemment que les techniques de génération d'image côté serveur étaient les plus portables selon les navigateurs puisque ne reposant que sur la balise <IMG> supportée par tous les navigateurs depuis NCSA Mosaic.

Malheureusement, ces techniques ont deux inconvénients rédhibitoires : elles chargent beaucoup le serveur (espace mémoire, temps CPU) et toute modification du design du graphique nécessite une intervention dans le code, et donc une recompilation et parfois même un arrêt / relance du serveur.

Le premier point est à mon sens le plus important. A quoi nous sert la puissance CPU côté client (le navigateur Web) si le serveur est engorgé à force de générer des images en mémoire. Et il y a également l'aspect Entrée / Sorties : un graphique, même de petite taille, va rapidement représenter quelques Ko alors que bien souvent, les données brutes utilisées pour le créer ne représentent que quelques centaines d'octets.

Reprenons l'exemple du graphique de la fréquentation du site TwinXeon.

Ci-dessous, le graphique tel que généré via utilisation de l'API AWT au sein de la JVM du serveur :



L'analyse sous Firebug nous montre que l'image 'pèse' 28,6 Ko et que la génération (ici le client est situé sur le serveur) prend 227 msec.




La transmission des données, sur la pile IP seulement, prend 10 ms.

Comparons maintenant à la transmission des données brutes. Nous avons cette information puisque, comme nous l'avons vu dans l'article précédent, j'avais développé tout d'abord une Applet Java pour générer le graphique côté client, technique que j'abandonnais à cause du manque de support des Applets sous Android.

La requête est http://twinxeon.no-ip.org/moebius/showStats?action=sumOfRequestPerDay&type=txt&limit=120. Tapons donc cette requête dans notre Firefox préféré et analysons les informations données par Firebug :



On gagne 78 msec. à la génération et 10 msec. à la réception ! Fichtre. Qu'attendons-nous pour remplacer la génération du graphique côté serveur par un tracé côté client à partir des données brutes ? Oui, mais voilà, je cherche une technique compatible avec tous les navigateurs modernes, et également compatible avec le navigateur embarqué sur Android Froyo (à cause de mon Archos A70it).

HTML 5 et les canvas

On entend beaucoup parler du remplacement progressif de la technologie Flash par HTML 5. Jusqu'à présent, je ne m'étais pas trop intéressé à ça. Et puis, à la lecture de ce tutorial, je me suis dit qu'il serait bête de ne pas se lancer.

Un petit peu d'AJAX pour la récupération asynchrone des données :

function loadGraph(numberOfDays,width,height){
   try{
     var xhr;
     if (window.XMLHttpRequest){
         xhr = new XMLHttpRequest();
     }else
         if (window.ActiveXObject){
             xhr = new ActiveXObject("Microsoft.XMLHTTP");
         }
      xhr.onreadystatechange = function(){
         if(xhr.readyState==4){
            if(xhr.status==200){
              var dataLst = xhr.responseText;
              var liste = dataLst.split(";");
  draw('tutorial',liste,width,height,20,"Requêtes par jour ("+(liste.length-1)+" jours)");
            }
         }
      };
      request = "/moebius/showStats?action=sumOfRequestPerDay&type=txt&limit="
               + numberOfDays;
      xhr.open("GET", request,  true);
      xhr.send(null);
   }catch(ex){
      document.getElementById('error').innerHTML = "Impossible de charger les données";
   }
}


Et c'est parti pour le code JavaScript et l'utilisation du contexte graphique 2D de mon canvas :

function draw(canvasId,liste,width,height,margin,title){
      var canvas = document.getElementById(canvasId);
      if (canvas.getContext){
          // lookup max value
          var maxValue = 0.0;
          var currentValue = 0.0;
          nbPoint = liste.length-1;
          for(i=0;i<nbPoint;i++){
            currentValue = parseInt(liste[i]);
              if(currentValue>maxValue){
                  maxValue = currentValue;
              }
          }
          var xCoef = (width - 2*margin) / (nbPoint-1);
          var yCoef = (height - 2*margin) / maxValue;
         
          var ctx = canvas.getContext('2d');
          ctx.clearRect(0,0,width,height);
          ctx.shadowOffsetX = 0;
            ctx.shadowOffsetY = 0;
            ctx.shadowBlur = 0;
          ctx.strokeStyle = "#000000";
          ctx.beginPath();
          ctx.moveTo(margin,margin);
          ctx.lineTo(margin,height-margin);
          ctx.lineTo(width-margin,height-margin);
          ctx.stroke();
         
          var nbAxe = maxValue / 50.0;
          ctx.strokeStyle = "#c3c3c3";
          ctx.beginPath();
          for(i=1;i<nbAxe;i++){
              m = height-margin-yCoef*i*50;
              ctx.moveTo(margin+1,m);
              ctx.lineTo(width-margin,m);
          }
          ctx.stroke();
         
          ctx.fillStyle    = "#0000ff";
          ctx.font         = "normal 9px sans-serif";
          ctx.textBaseline = "top";
          for(i=1;i<nbAxe;i++){
              m = height-margin-yCoef*i*50;
              ctx.fillText  (i*50, 0, m-4);
          }
         
          ctx.strokeStyle = "#0000ff";
          ctx.shadowOffsetX = -7;
            ctx.shadowOffsetY = 4;
            ctx.shadowBlur = 2;
            ctx.shadowColor = "rgba(0, 0, 0, 0.3)";
         
          var x = margin;
          var y = height - margin - liste[0]*yCoef;
          ctx.beginPath();
          ctx.moveTo(x,y);
          for(i=1;i<nbPoint;i++){
              x = margin + i * xCoef;
              y = height - margin - liste[i]*yCoef;
              ctx.lineTo(x,y);
          }
          ctx.stroke();
         
          ctx.fillStyle    = "#0000ff";
          ctx.font         = "italic 10px sans-serif";
          ctx.textBaseline = "top";
          x = width / 2 - ((title.length/2)*5);
          y = height - 12;
          ctx.fillText  (title, x, y);
       }
}
 
Pas très compliqué en fait. Et avantage non négligeable : pour modifier le design du graphique, il suffit de republier la page HTML, ce qui ne nécessite pas d'arrêt / relance du serveur.

Passons au rendu maintenant :

Sous Firefox 9.0.1 :



Sous Internet Explorer 9 :



Et sous Android 2.2 Froyo :




Non seulement les trois navigateurs affichent parfaitement le graphique, mais en plus le résultat est strictement identique. De plus, notre graphique est vraiment dynamique puisqu'il est possible de redimensionner notre canvas à la volée. Sympa hein, l'effet d'ombrage sur la courbe ?
 
Vous pouvez retrouver la page de test sur TwinXeon à cette adresse : http://twinxeon.no-ip.org/public/canvas.html




Ajoutez votre commentaire :
  Votre pseudo :
  Votre adresse mail (obligatoire):
  Votre commentaire :
 
Site optimisé pour un affichage en 800x600 sous Firefox 8.x - ©Copyright 2011-2012 by Nicolas Renaudet