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
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
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.
Reprendre le graphe en utilisant des boites rectangulaires pour chaque noeud comme dans le graphe ci-dessous.
Peut-on faire des cadres emboités avec DOT, comme ci-dessous ?
Comment produire le graphe ci-dessous avec DOT ? Est-ce un arbre ?
Solution : afficher la 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.pngPour 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 bashet 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.dotne produit malheureusement pas ce qu'on veut :
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 829 28 6 3 0 300 17 3 1 1 488 22 2 0 0 123 11 1 1 0 839 27 10 3 1 27 5 1 1 0 340 18 4 0 0 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.
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 : afficher la 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 6Et 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.phpSolution : afficher la 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 : afficher la 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 nxn où n 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 : afficher la 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 : afficher la 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.
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 : afficher la 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 : afficher la 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 tableauxSolution : afficher la 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 :
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 :
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 barplotModalités Effectifs Pourcentages 1 Femmes 15 75 % 2 Hommes 5 25 %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 :
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
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 OKCe 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 litJsonCela 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 : afficher la 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 à la page principale de (gH)