Valid XHTML     Valid CSS2    

Production Automatisée de

Graphiques, Statistiques et Documents

                     gilles.hunault "at" univ-angers.fr

 

-- partie 1 sur 5 : arbres et graphes

 

Table des matières cliquable

  1. Exemple élémentaire de tracé d'un arbre avec DOT

  2. Théorème des quatre carrés de Lagrange en DOT via PHP

  3. Calcul de la matrice d'adjacence d'un graphe et tracé via DOT

  4. Tracés DOT en ligne

  5. Tracé d'un échiquier en SVG via PHP

  6. Canviz, Javascript et conversions de formats de graphes

  7. Tracé de l'arbre d'un document XHTML valide

  8. Animation Javascript d'un graphe défini en XML

  9. Graphiques en barres

10. Méthodologie du développement

 

Il est possible d'afficher toutes les solutions via ?solutions=1 et de toutes les masquer avec ?solutions=0.

 

1. Exemple élémentaire de tracé d'un arbre avec DOT

Ecrire un graphe en langage DOT pour décrire la structure classique de XHTML : "html, head and body, title est dans le head". On générera un fichier PNG. Comment le visualiser rapidement à partir de la ligne de commandes ? On devra obtenir un graphe identique au graphe ci-dessous.

               non su

Reprendre le graphe en utilisant des boites rectangulaires pour chaque noeud comme dans le graphe ci-dessous.

               non su

Peut-on faire des cadres emboités avec DOT, comme ci-dessous ?

               non su

Comment produire le graphe ci-dessous avec DOT ? Est-ce un arbre ?

               non su

Solution :  

Le langage DOT est très simple : un arc dans un graphe orienté se définit par les deux caractères -> et se termine par la fin de ligne. Pour décrire la structure classique html, head and body, title est dans le head, on peut donc se contenter du fichier xhtml1.dot suivant :


     digraph xhtml {
       html -> head
       html -> body
       head -> title
     }
     

Toutefois, la documentation de DOT montre qu'on peut regrouper les arcs issus d'un même sommet. Voici donc une solution un peu plus concise, dans le fichier xhtml1.dot suivant :


     digraph xhtml {
       html -> { head ; body }
       head -> title
     }
     

Pour produire le fichier PNG associé, il faut exécuter la commande :


     dot -T png xhtml1.dot -o xhtml1.png
     

Pour visualiser rapidement ce premier fichier PNG nommé xhtml1.png, on peut profiter du fait que Firefox sait afficher les fichiers PNG donc pour visualiser, on peut taper la commande :


     firefox xhtml1.png &
     

D'autres logiciels de visualisation d'images sont possibles, dont Geeqie pour Linux, on peut donc aussi taper la commande :


     gq & # gq est un alias mis pour geeqie dans bash
     

et profiter du fait que geeqie met ses listes des fichiers à jour dès qu'on crée un fichier. Mais il y a de nombreuses autres solutions qui sont capables de n'afficher que le fichier demandé dont eog (Eye of Gnome), soit la commande


     eog xhtml1.png 2>> /dev/null &
     

Voir le lien visionneuses pour d'autres solutions de visualisations (voir d'édition) de graphiques. On peut aussi utiliser la commande gnome-open xhtml1.png pour ouvrir le fichier-image, même si gnome-open a sans doute été remplacé par xdg-open... Plus d'infos sur xdg-open ici.

Pour produire des boites, la lecture du manuel dotguide.pdf (copie locale) fournit la réponse, soit le fichier xhtml2.dot suivant et le png associé :


     digraph xhtml {
       node [shape=box]
       html -> { head ; body }
       head -> title
     }
     

Par contre il n'est pas possible de produire des cadres emboités de façon simple, sauf à recourir à des "sous-graphes".

Le dessin du carré proposé parait simple, mais le texte


     # ceci est simple...
     digraph carre {
        A -> B
        B -> C
        C -> D
        D -> A
        { rank=same ; A ; B ; }
        { rank=same ; C ; D ; }
     } # fin de carre dans carre.dot
     

ne produit malheureusement pas ce qu'on veut :

               non su

merci à Nicolas MALCOMBRE d'avoir fourni une solution simple :


     digraph abcd {
     
        node [style="filled"]
     
        A -> B ;
        B -> C ;
        D -> C [ dir=back ; ] ;
        D -> A ;
     
        A [ fillcolor="#d62728" ]
        B [ fillcolor="#1f77b4" ]
        C [ fillcolor="#2ca02c" ]
        D [ fillcolor="#ff7f0e" ]
     
        { rank=same ; A ; B }
        { rank=same ; D ; C }
     }
     

 

2. Théorème des quatre carrés de Lagrange en DOT via PHP

Le théorème des quatre carrés de Lagrange dit que tout entier positif ou nul peut se décomposer en au moins une somme de 4 carrés.


           ∀n≥0, ∃a,b,c,d tels que n = a2 + b2 + c2 + d2
 

Voici quelques exemples de décomposition :

n a b c d
10 3 1 0 0
831 27 10 1 1
920 30 4 2 0
314 17 5 0 0
712 26 6 0 0
839 27 10 3 1
27 5 1 1 0
933 30 5 2 2

Ecrire un programme PHP qui donne l'arbre des décompositions d'un entier n passé en paramètre, arbre obtenu en décomposant chacune des décompositions à son tour, comme ci-dessous pour n=839.

               non su

On pourra utiliser le sous-programme suivant quatrecarres.php -- archive du code php -- pour obtenir une décomposition :


     <?php
     
     ######################################################################################
     
     function ds4c($nombre) {  # décomposition en somme de 4 carrés
     
     ######################################################################################
     
     $dbg = 0 ; # 0 en standard, 1 pour debug
     
     # valeur limite : la racine carrée du nombre
     
     $racine = round(sqrt($nombre)) ;
     
     $n1 = $racine ;
     
     # on boucle sur n1, n2 et n3 seulement,
     # n4 s'en déduira
     # comme il peut y avoir plusieurs solutions,
     # on renvoie ce qu'on a trouvé le plus tot possible
     
     while ($n1>=0) {
       $cn1 = $n1*$n1 ;
       $n2  = $racine ;
       while ($n2>=0) {
         $cn2 = $n2*$n2 ;
         $n3  = $racine ;
         while ($n3>=0) {
           $cn3 = $n3*$n3 ;
           $cn4 = $nombre - ($cn1+$cn2+$cn3) ;
           if ($dbg==1) {
             echo " n1 $n1 n2 $n2 n3 $n3 cn4 $cn4 \n" ;
           } # fin si
           if ($cn4>=0) {
             $n4 = sqrt($cn4) ;
             if ($dbg==1) {
               echo "   n4 possible $n4 \n" ;
             } # fin si
             if (round($n4)==$n4) {
               return( array($n1,$n2,$n3,$n4) ) ;
             } ; # fin si
           } ; # fin si
           $n3-- ;
         } # fin tant que sur n3
         $n2-- ;
       } # fin tant que sur n2
       $n1-- ;
     } # fin tant que sur n1
     
     return( array("?","?","?","?") ) ;
     
     } # fin fonction ds4c
     
     ?>
     

Solution :  

Consulter la page lagrange.php qui contient un lien vers son code-source ; la page lagrange2.php en est une version plus "user friendly".

Une solution AJAX est mise en oeuvre dans la page lagrange3.php. Quelles critiques peut-on en faire ?

Pour n=635 le fichier produit par la page ressemble très fortement à n635.dot.txt.

Merci à Nicolas JOUSSET d'avoir fourni la solution récursive suivante lagrange3rec.php


     <?php
     
     ######################################################################################
     
     function ds4c($nombre) {  # décomposition en somme de 4 carrés
     
     ######################################################################################
     
         $dbg = 0 ; # 0 en standard, 1 pour debug
     
         # valeur limite : la racine carrée du nombre
     
         $racine = round(sqrt($nombre)) ;
     
         $n1 = $racine ;
     
         # on boucle sur n1, n2 et n3 seulement,
         # n4 s'en déduira
         # comme il peut y avoir plusieurs solutions,
         # on renvoie ce qu'on a trouvé le plus tot possible
     
         while ($n1>=0) {
             $cn1 = $n1*$n1 ;
             $n2  = $racine ;
             while ($n2>=0) {
                 $cn2 = $n2*$n2 ;
                 $n3  = $racine ;
                 while ($n3>=0) {
                     $cn3 = $n3*$n3 ;
                     $cn4 = $nombre - ($cn1+$cn2+$cn3) ;
                     if ($dbg==1) {
                         echo " n1 $n1 n2 $n2 n3 $n3 cn4 $cn4 \n" ;
                     } # fin si
                     if ($cn4>=0) {
                         $n4 = sqrt($cn4) ;
                         if ($dbg==1) {
                             echo "   n4 possible $n4 \n" ;
                         } # fin si
                         if (round($n4)==$n4) {
                             return( array($n1,$n2,$n3,$n4) ) ;
                         } ; # fin si
                     } ; # fin si
                     $n3-- ;
                 } # fin tant que sur n3
                 $n2-- ;
             } # fin tant que sur n2
             $n1-- ;
         } # fin tant que sur n1
     
         return( array("?","?","?","?") ) ;
     
     } # fin fonction ds4c
     
     /**
      * @param $x            noeud en cours de traitement
      * @param $labels       chaine permettant le renommage des noeud
      * @param $arcs         chaine permettant la définition des arcs
      * @param $cnt          suffixe servant à différencier chaque noeud
      * @param $no           préfixe servant à différencier chaque noeud
      * @param $noeudPere    Noeud père
      */
     function putInDot($x, &$labels, &$arcs, &$cnt, $no, $noeudPere) {
         foreach (ds4c($x) as $fin) {
             $noeud = $no . ($cnt++);
             $labels .= "   $noeud [label = $fin] \n";
             $arcs .= "    $noeudPere -> $noeud\n";
             // Si le nombre trouvé n'est ni un 0 ni un 1 alors on réitère la procédure
             if (!($fin == 0 || $fin == 1)){
                 putInDot($fin, $labels, $arcs, $cnt, $no, $noeud);
             }
         }
     }
     
     $n = 635;
     
     echo "Decomposition de lagrange de $n\n";
     print_r(ds4c($n));
     
     echo "code dot associé : \n";
     ## 1. Début du fichier
     
     $varDot = "digraph g$n { \n";
     
     # Initialisation des variables nécessaires à la fonction putInDot
     $no = "no_"; // préfixe servant à différencier chaque noeud
     $cnt = 0;    // suffixe servant à différencier chaque noeud
     $arcs = "";  // Lignes qui permettent de remplacer le nom des noeuds (Ex : no_1) par leur vraie valeur (Ex : 12)
     
     # Appel de la fonction putInDot sur la racine
     putInDot($n, $labels, $arcs, $cnt, $no, $n);
     
     # ajout du contenu de la définition du graphe
     $varDot .= $labels; // renommage des noeuds
     $varDot .= $arcs;   // définition des arcs
     
     
     ## 2. Fin du fichier
     
     $varDot .= "} \n";
     
     echo $varDot;
     
     $fileDot = "pourDot.txt";
     file_put_contents($fileDot, $varDot);
     echo "\n vous pouvez consulter le fichier $fileDot \n";
     
     ## 3. Exec de la cmd Dot
     
     $filePng = "lagrange.png";
     shell_exec("dot -T png $fileDot -o $filePng");
     
     ## 4. Visualisation
     echo "\n vous pouvez consulter le fichier $filePng \n";
     
     system("eog $filePng");
     
     ?>
     

 

3. Calcul de la matrice d'adjacence d'un graphe et tracé via DOT

On suppose qu'on dispose des listes d'adjacence d'un graphe dans un fichier texte. Ecrire un programme PHP qui construit la matrice d'adjacence du graphe puis qui utilise DOT pour fournir une visualisation du graphe. On commencera par écrire un programme en CLI (ligne de commandes) avant de fournir une page Web qui lit les listes d'adjacence dans un <textarea>.

Voici un exemple d'entrée (fichier listeadj.txt) :


     0 1 4
     1 2 6
     2
     3 2 6
     4
     5 0 2
     6
     7 6
     

Et de sortie :


     
     
     Matrice d'adjacence d'un graphe dont on connait les listes d'adjacence.
     
      sommet numéro  1 : 0
      sommet numéro  2 : 1
      sommet numéro  3 : 2
      sommet numéro  4 : 3
      sommet numéro  5 : 4
      sommet numéro  6 : 5
      sommet numéro  7 : 6
      sommet numéro  8 : 7
     
     Voici la matrice d'adjacence associée au fichier listeadj.txt :
     
     num nom     1  2  3  4  5  6  7  8
       1 0       0  1  0  0  1  0  0  0
       2 1       0  0  1  0  0  0  1  0
       3 2       0  0  0  0  0  0  0  0
       4 3       0  0  1  0  0  0  1  0
       5 4       0  0  0  0  0  0  0  0
       6 5       1  0  1  0  0  0  0  0
       7 6       0  0  0  0  0  0  0  0
       8 7       0  0  0  0  0  0  1  0
     
      -- fin normale de matadj.php
     
     
               non su

Solution :  

Voici une première version pour la ligne de commande qui correspond à la sortie fournie :


     <?php
     #   # (gH)   -_-  matadj.php  ;  TimeStamp (unix) : 06 Août 2014 vers 16:06
     
     error_reporting(E_ALL | E_NOTICE ) ;
     
     ###################################################################################
     ###################################################################################
     
     echo "\n\nMatrice d'adjacence d'un graphe dont on connait les listes d'adjacence.\n\n" ;
     
     ###################################################################################
     ###################################################################################
     
     # aide éventuelle
     
     ###################################################################################
     
     if (!isset($argv[1])) {
     
       echo "\n" ;
       echo "syntaxe : php matadj.php fichier_LISTADJ\n" ;
       echo "exemple : php matadj.php ladj.txt où le fichier ladj.txt est:\n" ;
       echo "\n" ;
     
       echo "html head body \n" ;
       echo "head  \n" ;
       echo "body  \n" ;
       echo "title \n" ;
       echo "\n" ;
     
       echo "Le graphe résultat est visible à l'adresse\n\n" ;
       echo "   http://forge.info.univ-angers.fr/~gh/Pagsd/xhtml.png\n" ;
     
       echo "\n" ;
       exit(-1) ;
     
     } # fin si
     
     ###################################################################################
     
     # lecture du fichier, on en déduit le nombre de sommets
     
     ###################################################################################
     
     $dbg = 1 ; /* mettre 1 pour les cours et pour affichage détaillé */
     
     $nomf = $argv[1] ;
     if (!file_exists($nomf)) {
       echo "\n" ;
       echo "Fichier $nomf non vu. STOP.\n" ;
       echo "\n" ;
       exit(-1) ;
     } # fin si
     
     $nbsom    = -1      ; # nombre de sommets
     $sommets  = array() ; # tableau des noms de sommets
     $sommetsi = array() ; # tableau inverse des noms de sommets
     $listes   = array() ; # tableau des listes d'adjacence
     $matadj   = array() ; # matrice d'adjacence
     
     $ladj = file_get_contents($nomf) ;
     
     # on découpe suivant \n et on passe en revue les lignes
     
     $tladj = preg_split("/\n/",$ladj) ;
     
     if ($dbg==1) { print_r($tladj) ; } ;
     
     $nbl = 0 ;
     foreach ($tladj as $nums=>$liste) {
     
       if ($dbg==1) { echo " $nums => $liste \n" ; } ;
       $liste = trim($liste) ;
       $nbl++ ;
       if ($dbg==1) { echo "\n liste numéro $nbl : $liste\n "; } ;
     
       # traitement de la liste si non vide
     
       if (strlen($liste)>0) {
     
         # on convertit la liste en tableau
     
         $tdm = preg_split("/\s+/",$liste) ;
         $noms = $tdm[0] ;
     
         # si le premier sommet (l'origine) n'est pas connu, on l'ajoute
     
         if (!isset($sommetsi[ $noms])) {
           $nbsom++ ;
           $sommets[$nbsom]     = $tdm[0] ;
           echo "  sommet numéro ".sprintf("%2d",1+$nbsom)." : ".$sommets[$nbsom]."\n"  ;
           $sommetsi[ $tdm[0] ] = $nbsom ;
           $nums = $nbsom ;
           $listes[$nums] = array_slice($tdm,1,count($tdm)-1) ;
           if ($dbg==1) {
             echo "   on initialise la liste de $noms en position $nums avec : ".implode(" ",$listes[$nums])."\n" ;
           } # fin si debug
         } else {
           # sinon, on récupère son numéro
           $nums = $sommetsi[ $noms ] ;
           if ($dbg==1) {
               echo " sommet $noms déjà vu, numéro ".sprintf("%2d",1+$nums)."\n"  ;
               echo " sa liste était ".implode(" ",$listes[$nums])."\n" ;
               echo " et il faut ajouter ".implode(" ",array_slice($tdm,1,count($tdm)-1))."\n" ;
           } # fin si debug
           $listes[$nums] = array_merge($listes[$nums],array_slice($tdm,1,count($tdm)-1)) ;
           if ($dbg==1) {
              echo " sa liste est donc ".implode(" ",$listes[$nums])."\n" ;
           } # fin si debug
         } # fin si
     
         # on traite maintenant la liste courante des sommets (destination)
     
         foreach ($listes[$nums] as $num => $sommet) {
     
          # on ajoute un sommet non vu
          if (!isset($sommetsi[ $sommet ])) {
             $nbsom++ ;
             $sommets[$nbsom]    = $sommet ;
             $sommetsi[$sommet]  = $nbsom ;
             if ($dbg==1) {
                echo "  sommet numéro ".sprintf("%2d",1+$nbsom)." : ".$sommets[$nbsom]."\n"  ;
             } # fin si debug
             $listes[$nbsom] = array() ;
          } ; # fin si
     
         } # fin pour chaque sommet
     
       } # fin si
     
     } # fin pour chaque liste
     
     if ($dbg==1) { echo " voici les listes " ; print_r($listes) ; } ;
     
     ###################################################################################
     
     # initialisation de la matrice d'adjacence
     
     ###################################################################################
     
     # plus astucieux avec une ligne-tableau recopiée ?
     
     for ($isom=0;$isom<=$nbsom;$isom++) {
       $matadj[$isom] = array() ;
       for ($jsom=0;$jsom<=$nbsom;$jsom++) {
           $matadj[$isom][$jsom] = 0 ;
       } # fin pour jsom
     } # fin pour isom
     
     ###################################################################################
     
     # remplissage de la matrice d'adjacence
     
     ###################################################################################
     
     for ($isom=0;$isom<=$nbsom;$isom++) {
           $liste = $listes[$isom] ;
          if ($dbg==1) { echo " sommet $isom : liste => \n" ; print_r($liste) ; } ;
           foreach ($liste  as $sommet) {
              if ($dbg==1) { echo "    $isom : $sommet \n" ; } ;
              $jsom = $sommetsi[ $sommet ] ;
              $matadj[ $isom][ $jsom ] = 1 ;
           } # fin pour chaque liste
     } # fin pour isom
     
     echo "\nVoici la matrice d'adjacence associée au fichier $nomf :\n\n" ;
     
     echo "num nom    " ;
     for ($isom=0;$isom<=$nbsom;$isom++) {
       echo sprintf("%2d ",1+$isom) ;
     } # fin pour isom
     echo "\n" ;
     
     for ($isom=0;$isom<=$nbsom;$isom++) {
       echo sprintf(" %2d ",1+$isom) ;
       echo substr($sommets[$isom]."         ",0,7) ;
       for ($jsom=0;$jsom<=$nbsom;$jsom++) {
           echo " ".$matadj[$isom][$jsom]." " ;
       } # fin pour jsom
       echo "\n" ;
     } # fin pour isom
     echo "\n" ;
     
     ###################################################################################
     
     # création du fichier DOT
     
     ###################################################################################
     
     $ftmp  = "dot_tmp.gv.txt" ;
     $fh    = fopen($ftmp,"w") or die(" impossible d'écrire le fichier $ftmp. Stop\n.") ;
     fputs($fh,"digraph matrice {\n") ;
     
     for ($isom=0;$isom<=$nbsom;$isom++) {
       $liste = $listes[$isom] ;
       foreach ($liste as $sommet) {
            $lig = "   ".utf8_encode($sommets[$isom])." -> ".utf8_encode($sommet)." ;\n" ;
            fputs($fh,$lig) ;
            if ($dbg==1) { echo $lig ; } ;
       } # fin pour chaque sommet
     } # fin pour isom
     
     fputs($fh,"}\n") ;
     fclose($fh) ;
     
     $fpng = "dot_tmp.gv.png" ;
     
     ###################################################################################
     
     # exécution du fichier DOT
     
     ###################################################################################
     
     if ($dbg==1) { system("cat $ftmp") ; } ;
     $cmd = " dot -T png $ftmp -o $fpng " ;
     if ($dbg==1) { echo "\n $cmd \n" ; } ;
     system($cmd) ;
     
     
     ###################################################################################
     
     echo "\n\n -- fin normale de matadj.php\n\n" ;
     ?>
     

Une deuxième version en mode Web avec un sous-programme de calcul et traitement est disponible à l'adresse webmatadj.php.

 

4. Tracés DOT en ligne

A la réflexion, écrire du code DOT sous éditeur, le sauvegarder puis taper une commande dot -T png ... en ligne de commandes est un peu "fastidieux". Y a-t-il des pages Web qui interprètent du code DOT ?

Solution :  

Oui, bien sûr. Il y a par exemple graphs.grevian, jsviz, erdos et gv.

Même si elle n'est pas XHTML valide, la page erdos nous parait -- mais c'est bien sûr très subjectif -- la plus aboutie et donc c'est celle que nous recommandons.

 

5. Tracé d'un échiquier en SVG via PHP

Ecrire une page Web avec un programme PHP qui produit un échiquier en SVG de taille nxnn est passé en paramètre.

Expliquer ce que produit le code SVG suivant :


     <?xml version="1.0" encoding="UTF-8"?>
     <svg xmlns="http://www.w3.org/2000/svg" width="810" height="810" viewBox="-.05 -.05 8.1 8.1">
       <rect x="-.5" y="-.5" width="9" height="9" />
       <path fill="#FFF" d="M0,0H8v1H0zm0,2H8v1H0zm0,2H8v1H0zm0,2H8v1H0zM1,0V8h1V0zm2,0V8h1V0zm20V8h1V0zm2,0V8h1V0z"/>
     </svg>
     

Solution :  

Il pourrait être tentant de se baser sur l'échiquier proposé à la page formats.php. Voici donc une première solution où n est indiqué dans l'URL : chess1.php. Le code-source est ici.

Une solution plus "propre" consiste à proposer à l'utilisateur de choisir n dans une liste de sélection. On la trouvera ici avec un lien vers son code-source.

Le code SVG proposé montre une autre façon d'écrire des cases : les instructions H, V, v etc. réalisent des déplacements, absolus ou relatifs. Une fois ce code restructuré en  


     <?xml version="1.0" encoding="UTF-8"?>
     <svg xmlns="http://www.w3.org/2000/svg" width="810" height="810" viewBox="-0.05 -0.05 8.1 8.1">
       <rect x="-0.5" y="-0.5" width="9" height="9" />
       <path fill="#FFF" d="
          M0,0 H8 V1 H0 Z
          M0,2 H8 v1 H0 Z
          m0,2 H8 v1 H0 Z
          m0,2 H8 v1 H0 Z
          M1,0 V8 h1 V0 Z
          m2,0 V8 h1 V0 Z
          m2,0 V8 h1 V0 Z
          m2,0 V8 h1 V0
                           " />
     </svg>
     

on voit qu'il s'agit aussi d'un échiquier 8x8 défini à l'aide de l'élément <path> de SVG.

 

6. Canviz, Javascript et conversions de formats de graphes

Qu'est-ce que Canviz ?

Ecrire une page PHP dont le rendu est XHTML strict valide avec tout le code Javascript externalisé qui reprend la page Canviz demo. On utilisera les fonctions de std.php.

Peut-on convertir facilement des fichiers PNG de graphes en SVG ? Et réciproquement ?

Solution :  

Canviz est une bibliothèque Javascript de rendu pour les fichiers XDOT qui généralisent les fichiers langage DOT.

Voici une page PHP qui correspond aux contraintes : ghCanviz. Son code source est ici. On laisse le soin à la lectrice et au lecteur de trouver comment accéder aux fichiers Javascript qui sont inclus.

Si les graphes PNG sont produits par DOT, rien de plus simple : il suffit de changer de format de de sortie dans l'exécution de DOT. Sinon, cela risque d'être plus compliqué.

DOT gère de nombreux formats, comme on peut le voir sur l'image cliquable ci-dessous, extraite de la page image_fmts disponible sur le site de graphviz.

               non su

 

7. Tracé de l'arbre d'un document XHTML valide

Ecrire un programme PHP qui affiche l'arbre des éléments XHTML d'une URL donnée mais sans les contenus textuels. On produira l'arbre en SVG.

Comment afficher aussi les attributs ? Et le début des contenus textuels ?

Solution :  

Pas encore ! A ce jour (2016), aucun étudiant ni aucun internaute averti n'a fourni de solution satisfaisante.

 

8. Animation Javascript d'un graphe défini en XML

Réaliser dans une page Web un affichage progressif d'un graphe à raison d'un arc par seconde (durée paramétrable). On utilisera SVG. Le graphe sera écrit en XML au format GraphML ou DOTML. On pourra utiliser les fichiers suivants pour tester la page Web : xhtml.graphml, triangle.graphml, xhtml.dotml et triangle.dotml.

Au passage, comment tester qu'un fichier définissant un graphe est "valide" ?

Solution :  

Pas encore ! A ce jour (2016), aucun étudiant ni aucun internaute averti n'a fourni de solution satisfaisante.

 

9. Graphiques en barres

Question 9.1

Quelle est la différence entre un histogramme de classes et un histogramme de fréquences ?

Quelle est la méthode la plus rapide (moins d'une minute) pour produire un histogramme de fréquences en PNG si on connait le titre, les modalités et les effectifs correspondant ?

Comment trace-t-on ces deux types d'histogrammes avec le logiciel R ? On viendra bien sûr ajouter le nom des modalités et le titre de la figure dans le cas d'un histogramme de fréquences.

Quelles précautions faut-il prendre si on veut pouvoir comparer plusieurs histogrammes de fréquences ?

Question 9.2

Modifier la page de jphistopct pour qu'on dispose d'un paramètre supplémentaire nommé couleur et rédiger la documentation associée dans la page Web de présentation.

Ecrire ensuite un formulaire Web pour interfacer la nouvelle page de jphistopct y compris avec une liste de sélection pour la couleur. On s'arrangera pour gérer une liste quelconque de modalités et le titre éventuellement accentué. On affichera le graphique sous le formulaire.

Question 9.3

Ecrire une version PHP en ligne de commandes qui utilise un seul paramètre, le nom d'un fichier-texte. Ce fichier peut contenir plusieurs séries de données à tracer. Chaque série contient un titre, la liste des modalités puis la liste des valeurs. Ce fichier pourra être au format CSV, JSON, ou XML. On fournira un help via les paramètres -h et --help. On basera le nom du fichier de sortie PNG sur le nom du fichier d'entrée.

Exemples possibles (mais non imposés) de tels fichiers (archive hfreq.zip) :

hfreqXmp.xml       hfreqXmp.csv       hfreqXmp.json       

Que pourrait-on ajouter à ces fichiers de données ?

On pourra suivre le plan suivant afin d'avoir un programme assez complet et testé :


     ## 1. test des paramètres
     
        # si pas assez de paramètres, affichage de l'aide
        # si trop de paramètres, on le dit et on arrete
        # si le paramètre est -h ou --help, affichage de l'aide
     
     ## 2. vérifications sur le fichier
     
       # il doit y avoir un point dans l'identifiant du fichier
       # le nom du fichier (partie avant le point) ne doit pas être vide
       # le type du fichier doit être CSV JSON ou XML
       # il faut que le fichier existe
     
     ## 3. traitement des données
     
       # si le fichier est de type csv,  utiliser le sous-programme correspondant
       # si le fichier est de type json, utiliser le sous-programme correspondant
       # si le fichier est de type xml,  utiliser le sous-programme correspondant
     
       # dans tous les cas, renvoyer
       #  - le tableau des titres,
       #  - le tableau des modalités,
       #  - le tableau des effectifs
     
       # produire les png à l'aide ces trois tableaux
     

Solution :  

Réponse 9.1

Un histogramme de classes est la visualisation de la densité d'une variable quantitative, comme par exemple celle de l'age de personnes, exprimé en années, ou la longueur de séquences protéiques, exprimée en aa (acides aminés). Voir par exemple l'histogramme de l'exercice 1 pour la série 2 d'exercices de ce cours. Pour un tel histogramme, une fois le problème délicat du nombre de classes (ou bins en anglais) résolu, les boites -- ou rectangles ou encore, les barres -- doivent être contigues pour bien montrer la nature continue de la variable statistique sous-jacente.

A l'opposé, un histogramme de fréquences sert à représenter la distribution d'une variable qualitative et il doit être composé de rectangles bien séparés, pour faire ressortir la nature discrète (au sens statistique de "discontinu") de la variable étudiée.

La méthode la plus rapide (moins d'une minute) pour produire un histogramme de fréquences en PNG si on connait le titre, les modalités et les effectifs correspondant consiste à passer par un programme ou un script dédié, comme par exemple notre page Web nommée jphistopct.

Les fonctions R correspondantes sont respectivement nommées hist() et barplot(). On essaiera les commandes

     help("hist")        ; 
     example("hist")     ; 
     help("barplot")     ; 
     example("barplot")  ; 

pour comprendre et savoir comment utiliser ces fonctions.

Ainsi, pour représenter les effectifs absolus x et y correspondant aux nombres de femmes et d'hommes dans un échantillon de données, on peut écrire, avec R :


     # exemple de données : x=15 femmes et y=5 hommes
     
     x <- 15  # nhFemmes serait mieux
     y <-  5  # nhHommes serait mieux
     
     barplot( c(x,y) )
     

On obtient alors l'histogramme de fréquences suivant :

               non su

Il est «raisonnable» d'ajouter un titre et le nom des modalités, soit les codes  :


     # exemple de données : 15 femmes et 5 hommes
     
     nhFemmes <- 15
     nhHommes <-  5
     effectifsAbsolus <- c(nhFemmes,nhHommes)
     names(effectifsAbsolus) <- c("Femmes","Hommes")
     
     # titre intégré à la fonction barplot()
     
     barplot( effectifsAbsolus, main="Répartition du sexe" )
     
     # titre ajouté ensuite via title
     
     barplot( effectifsAbsolus,col="yellow")
     title( main="Répartition du sexe" )
     

Voici le nouvel histogramme de fréquences obtenu :

               non su

Mais il est en général conseillé de calculer, fournir et tracer les effectifs relatifs (fréquences, pourcentages) plutôt que les effectifs absolus, soit :


     ###############################################
     #
     # tracé  d'histogramme de fréquences
     #
     ###############################################
     #                               (gH), oct. 2015
     
     # exemple de données : 15 femmes et 5 hommes
     
     nhFemmes <- 15
     nhHommes <-  5
     effectifsAbsolus <- c(nhFemmes,nhHommes)
     
     # calcul des effectifs relatifs (pourcentages)
     
     pcts        <- 100*(effectifsAbsolus/sum(effectifsAbsolus))
     modalites   <- c("Femmes","Hommes")
     names(pcts) <- modalites
     f_pcts      <- paste(pcts,"%")
     print(resultats <- data.frame(modalites,effectifsAbsolus,f_pcts) ) # non conseillé
     
     # mieux :
     
     resultats <- data.frame(modalites,effectifsAbsolus,f_pcts)
     names(resultats) <- c("Modalités","Effectifs","Pourcentages")
     print(resultats)
     
     # affichage "normalisé" de l'histogramme de fréquences
     
     barplot( pcts,                         # ceci
              main="Répartition du sexe",   # est une façon pratique
              ylim=c(0,100),                # de documenter
              col="darkblue"                # le code R
     ) # fin de barplot
     

       Modalités Effectifs Pourcentages
     1    Femmes        15         75 %
     2    Hommes         5         25 %
     
               non su

Il est alors possible de comparer des histogrammes d'effectifs relatifs (avec une même échelle), ce que ne permettent pas les histogrammes d'effectifs absolus, sauf à vouloir induire en erreur comme on peut le voir ci-dessous :

barplot01.png

          

barplot02.png

Le choix des valeurs 15 pour le nombre de femmes et 5 pour le nombre d'hommes est astucieux ou pratique dans la mesure où les pourcentages correspondants s'obtiennent par une simple multiplication par 5.

Réponse 9.2

Consulter jphistopct2 pour la production d'histogrammes avec couleur. La page f_jphistopct2 fournit un formulaire pour l'interfacer.

Si on donne une couleur non reconnue par jpgraph comme pomme, l'histogramme n'est pas affiché. A la place, on obtient une image d'erreur comme

               non su

Comme la liste des couleurs (visible ici) est longue -- environ 880 couleurs, il serait fastidieux de fournir une liste de sélection. Nous avons préférer laisser l'utilisateur consulter la page des couleurs dans une autre fenêtre. Pour répondre exactement à l'énoncé (afficher l'image sous le formulaire et couleur OK), on utilisera le formulaire f_jphistopct3. On notera que la vérification des couleurs est dynamique et qu'on met navy si la couleur n'existe pas.

Réponse 9.3

Le fichier jphisto.php fournit la solution demandée en ligne de commandes. Voici quelques exemples d'utilisation :


     $gh> php jphisto.php # équivalent à php jphisto.php -h (ou --help)
     
     jphisto.php : création automatisée d'une série de PNG
     
     
      Pour utiliser jphisto.php, vous ne devez passer qu'un seul paramètre,
      à savoir un fichier CSV, JSON ou XML. Consultez
     
          http://forge.info.univ-angers.fr/~gh/Pagsd/pagsd1.php?solutions=1#tdm8
     
      pour le format des fichiers à utiliser.
     
     $gh> php jphisto.php data image.png
     
     
      Trop de paramètres, il n'en faut qu'un seul, le nom du fichier à traiter.
     
      Pour plus de détails sur ce programme ou utilisez le paramètre --help.
     
     $gh> php jphisto.php .valeurs
     
     
      Erreur dans jphisto.php : l'identificant du fichier passé en paramètre
      est incorrect car il n'y a pas de nom avant le point. Vous ne pouvez pas appeler
      votre fichier seulement .csv, .json ou .xml
     
     $gh> php jphisto.php data.txt
     
     
      Erreur dans jphisto.php : le type du fichier passé en paramètre
      n'est pas correct. Ce doit être CSV, JSON ou XML.
     
     $gh> php jphisto.php valHisto.csv
     
     
      Erreur dans jphisto.php : fichier "valHisto.csv" non vu.
     
     $gh> php jphisto.php hfreqXmp.csv
     
     
      jphist.php : traitement du fichier hfreqXmp.csv
     
     lecture de hfreqXmp.csv
     
      3  images à produire.
      image 1 / 3 : hfreqXmp01.png OK
      image 2 / 3 : hfreqXmp02.png OK
      image 3 / 3 : hfreqXmp03.png OK
     

Ce qu'il manque à ces fichiers, c'est sans doute la valeur de l'effectif total (le nombre de valeurs en tout), de façon à pouvoir tester la somme des effectifs.

Le fait d'avoir écrit sous forme de commentaires le plan du programme induit l'organisation physique des fichiers dans le cas d'un travail collaboratif :


     <?php
     
     include("jphisto_test_Param.php") ; ## 1. test des paramètres
     
        # si pas assez de paramètres, affichage de l'aide
        # si trop de paramètres, on le dit et on arrete
        # si le paramètre est -h ou --help, affichage de l'aide
     
     include("jphisto_verif_Fic.php") ; ## 2. vérifications sur le fichier
     
       # il doit y avoir un point dans l'identifiant du fichier
       # le nom du fichier (partie avant le point) ne doit pas être vide
       # le type du fichier doit être CSV JSON ou XML
       # il faut que le fichier existe
     
     include("jphisto_traite_Dat.php") ; ## 3. traitement des données
     
       # si le fichier est de type csv,  utiliser le sous-programme correspondant
       # si le fichier est de type json, utiliser le sous-programme correspondant
       # si le fichier est de type xml,  utiliser le sous-programme correspondant
     
       # dans tous les cas, renvoyer
       #  - le tableau des titres,
       #  - le tableau des modalités,
       #  - le tableau des effectifs
     
       # produire les png à l'aide ces trois tableaux
     
     ?>
     

Il va de soi qu'alors une telle organisation doit indiquer quels sous-programmes sont utilisés, avec le détail des entrées et des sorties, comme par exemple :


     
     ## partie 3.2 lecture d'un fichier JSON
     
     ##############################################
     
     function litJson($pNomFicEnt) {
     
     ####################################################################################
     
     /*************************************************************************************/
     /**                                                                                  */
     /** cette fonction lit un fichier JSOn pour produire trois tableaux                  */
     /** le seul paramètre est le nom d'un fichier JSON                                   */
     /** ce fichier existe forcément (le test d'existence a déjà été fait en amont)       */
     /** voici un exemple minimal de structure JSON utilisée :                            */
     /**                                                                                  */
     /**   "histo1" : {                                                                   */
     /**     "titre"     :  "Dossier ELF : répartition du code-sexe" ,                    */
     /**     "modalités" :  [ "Femmes" , "Hommes" ]                  ,                    */
     /**     "effectifs" :  [ 64       , 35       ]                                       */
     /**   }                                                                              */
     /**                                                                                  */
     /** on doit donc avoir trois objets, nommés respectivement                           */
     /** "titre", "modalités" et "effectifs"                                              */
     /**                                                                                  */
     /** exemple d'utilisation de la fonction :                                           */
     /**                                                                                  */
     /**   list($tabTitres,$tabModalites,$tabEffectifs) = litXml($nomFicEnt) ;            */
     /**                                                                                  */
     /*************************************************************************************/
     
     
     [...]
     
     ##############################################
     
     } # fin de fonction litJson
     

Cela permet notamment au développeur de la partie 3.3 (production du PNG) d'écrire sa fonction sans attendre la fin de l'écriture de la partie 3.2 (lecture des données) :


     ## partie 3.3 (production du PNG) à partir des trois tableaux d'informations
     
     /*************************************************************************************/
     /*                                                                                   */
     /*  en principe, on doit avoir l'exécution de la lecture via du code                 */
     /*  comme                                                                            */
     /*                                                                                   */
     /*     list($tabTitres,$tabModalites,$tabEffectifs) = litJson($nomFicEnt) ;          */
     /*                                                                                   */
     /*  pour le test unitaire, on remplace cette lecture par la définition               */
     /*  manuelle des trois tableaux et de la base des noms de fichiers :                 */
     /*                                                                                   */
     /*  $tabTitres    = array() ; $iPng = -1 ;                                           */
     /*  $iPng++ ; $lesTitres[ $iPng ] = " test ELF" ;                                    */
     /*  $iPng++ ; $lesTitres[ $iPng ] = " test HERS" ;                                   */
     /*                                                                                   */
     /*  [...]                                                                            */
     /*                                                                                   */
     /*  $baseNom      = "testUnit" ;                                                     */
     /*                                                                                   */
     /*************************************************************************************/
     
     produitPng($tabTitres,$tabModalites,$tabEffectifs,$baseNom) ;
     
     [...]
     
     
     ## arrivé ici on doit disposer de testUnit01.png et de testUnit02.png
     

 

10. Méthodologie du développement

Nous avons vu avec la décomposition de Lagrange (archive de la solution lagrange.zip) et avec la matrice d'adjacence d'un graphe (archive de la solution matadj.zip) qu'il y a une méthode pour écrire du PHP afin d'aboutir progressivement à une page Web compléte utilisable sans rechargement de la page. Détailler cette méthode.

Quelles sont les contraintes supplémentaires rajoutées par le fait qu'on utilise un exécutable en ligne de commandes qui produit une image ?

Solution :  

Un programme PHP qui s'exécute en ligne de commandes et une page Web en PHP qui utilise AJAX pour afficher des données sont des programmes dont le «coeur» est le même mais dont «l'enrobage» sont différents. Il est sans doute raisonnable de commencer avec la ligne de commande et d'utiliser $ARGV pour les paramètres et d'écrire et de tester les sous-programmes de calcul, de lecture et d'écriture avant de passer (un problème à la fois) à une page Web classique, sans doute avec l'utilisation du du mode GET et de la variable-tableau $_GET pour la gestion des paramètres. Il restera ensuite à redesigner la page Web avec des sous-programmes et des paramètres pour décider d'afficher ou non l'entête de la page Web (partie <html> et <head>) pour pouvoir récupérer la partie à insérer en AJAX.

Si le programme PHP (ou la page Web) utilise un exécutable en ligne de commandes qui produit une image, il faut bien sûr d'abord vérifier que l'exécutable est accessible sur le serveur. Ensuite, il faut se rappeler qu'en ligne de commandes c'est l'utilisateur "normal" qui exécute le code alors que pour une page Web le browser utilise un autre utilisateur qui n'a pas les mêmes droits. Par exemple sous Linux l'utilisateur associé au navigateur est nommé www-data et il fait partie du groupe www-data. Il faut aussi choisir si on écrit dans la zone temporaire du serveur Web (répertoire /tmp du serveur, qui n'est sans doute pas le répertoire /tmp du système d'exploitation sous-jacent) ou si on écrit dans un répertoire personnel qui ne sert qu'à cela (avec sans doute les droits définis sous Linux par chmod +777)) sachant qu'alors le système ne fait pas "le ménage" dans ce répertoire et qu'il faut donc le vider manuellement de temps en temps. Et «naturellement», on utilisera la fonction de PHP nommée tempnam() pour éviter les accès concurrents...

 

Code-source de cette page ;  fichiers inclus : pagsd_inc.php et pagsd.js.

 

 

retour gH    Retour à la page principale de   (gH)