Le site arthur.bebou.netlib.re - retour accueil
git clone git://bebou.netlib.re/arthur.bebou
Log | Files | Refs |
index.sh (29674B)
1 #! page 2 title: Un exercice gnuplot 3 author: Arthur Pons 4 description: Ou comment j\'ai reproduit la killer feature de gitlab et fait migrer Marc Chantreux vers de horizons plus sobres 5 publication: 2024-10-24 6 7 fig() { 8 legend=$(< ~/git/git-cal-figures/plan sed -n "$1p") 9 <<. save_html main 10 <figure> 11 <img loading="lazy" src="fig$1.png" alt="$legend" /> 12 <figcaption> 13 figure $1 : $legend<br> 14 <a href="/git-cal/fig$1.gp">script pour graph</a> 15 </figcaption> 16 </figure> 17 . 18 } 19 20 section: main 21 22 On m'a fait la remarque que mon activité sur le gitlab de l'université de 23 Strasbourg n'était pas bien fournie : 24 25 ![Une frise chronologique, un carré représente une journée. Plus les carrés sont 26 bleus plus j'ai effectué de commit ce jour là](gitlab.png) 27 28 N'utilisant plus beaucoup cette forge je me suis demandé à quoi cette frise 29 ressemblerait pour les dépôts en local sur mon ordinateur. J'y ai vu un super 30 exercice gnuplot. 31 32 ## L'énoncé 33 34 Admettons que l'on ait un jeu de donnée de la sorte : 35 36 nombre-commits date 37 0 1 38 0 2 39 0 3 40 1 4 2023 Oct 22 41 0 5 2023 Oct 23 42 0 6 2023 Oct 24 43 0 7 2023 Oct 25 44 45 En première colonne un entier, le nombre de commit, en seconde la date soit au 46 format "numéro de la journée dans la semaine" ou "numéro de la journée dans la 47 semaine, année, trois premières lettres du mois en anglais, numéro de la journée 48 dans le mois". Les deux colonnes sont séparées par une tabulation, les données 49 dans la seconde colonne par un simple espace. La dernière ligne correspond à 50 aujourd'hui, la première ayant une date complète à aujourd'hui - 1 ans, les 51 quelques lignes avant le nombre de jours nécessaires pour remonter au dernier 52 lundi de l'année précédentes. Ces lignes sont là pour compléter la première 53 semaine de l'année. 54 55 Écrire un script gnuplot permettant de tracer un calendrier similaire à celui vu 56 plus tôt. 57 58 ### Pourquoi ce format un peu étrange ? 59 60 Parce qu'il va faciliter l'écriture du script gnuplot mais pas au point de 61 n'avoir besoin d'aucune astuce ou fonctionnalité un peu avancée. Lorsque l'on 62 écrit du gnuplot on est fréquemment confronté au même dilemme : pour gérer de 63 la complexité devrait-t-on ajouter du code en amont de gnuplot et lui préparer 64 le meilleur des formats possible ou devrait-on alourdir le script gnuplot pour 65 qu'il gère des sources de données moins spécifiques ? En fonction de votre 66 objectif et de vos compétences en gnuplot la réponse peut varier. 67 68 Ici je souhaite aborder quelques éléments de gnuplot sans trop me perdre dans 69 des digressions ou des points très avancés. J'ai donc opté pour une source de 70 données arrangeante et un script gnuplot pas *trop* compliqué. A l'inverse si 71 j'avais voulu explorer toute la puissance de gnuplot et distribuer un script se 72 suffisant à lui même je l'aurais écrit pour pouvoir lire la sortie par défaut de 73 `git log`. 74 75 Le code final ainsi que le script permettant de générer ce tableau un peu étrange 76 sont disponibles ici : http://git.bebou.netlib.re/git-cal/log.html 77 **Attention**, je compte faire bouger ce dépôt qui risque de fortement dévier 78 du code décrit dans cet article. Pas de panique si les deux divergent. Je fige 79 le code de l'article tel qu'il est avec ses défauts, c'est d'abord un outil 80 pédagogique. 81 82 83 **Addendum** : après une lecture commune de l'article avec des collègues 84 je me suis rendu compte que le calcul des ordonnées des jours était 85 inutilement complexe. Initialement j'ai eu l'idée d'ajouter les "faux" 86 jours en début de fichier. Pour pouvoir savoir combien en ajouter j'ai inscrit 87 le numéro de la journée dans la date et se faisant l'ordonnée que l'on cherche 88 justement à calculer. La donnée se trouvant déjà dans le fichier et n'ayant pas 89 besoin d'être calculée depuis le numéro de la ligne la formule pour calculer `y` 90 pourrait être : 91 92 word(strcol(2),1) au lieu de 93 int($0*-1)%7 94 95 On pourrait penser que cela simplifierait le script de génération du TSV mais le 96 numéro de la ligne est également utilisé pour calculer le numéro de la semaine 97 pour les `x` ! Pour se débarrasser totalement des petites formules mathématiques 98 malines il faudrait également insérer le numéro de la semaine dans le fichier . 99 Cependant cela ferait moins d'occasions d'apprendre des trucs dans cet article. 100 101 **Conclusion** : Pour des raisons pédagogiques je laisse l'article tel qu'il 102 est même si c'est un peu alambiqué mais je modifie le code dans le dépôt 103 `git-cal` pour le simplifier. Ne soyez pas étonné·es s'ils ne correspondent pas. 104 Au contraire cela peut être un bon exercice pour voir ce qui a changé depuis. 105 106 ### Une référence pour s'aider 107 108 Je dois une grande partie de ce que je sais au livre ["Gnuplot in Action, Second 109 Edition" de Philipp K. Janert, ISBN 110 9781633430181](https://www.manning.com/books/gnuplot-in-action-second-edition). 111 Je l'ai si vous voulez. Le bouquin couvre jusqu'à la version 5.5 de gnuplot et 112 n'intègre donc pas les nouveaux trucs cools de la récente version 6 mais pas 113 grave. On peut déjà faire bien assez avec l'existant. 114 115 Si vous voulez tenter de résoudre l'exercice vous même il y a tout ce dont vous 116 avez besoin dans ce livre y compris une solution presque toute faite dans 117 l'annexe D. 118 119 ## De 0 à 100 en 11 étapes 120 121 Pour info, voici le résultat final : 122 123 endsection 124 fig 1 125 section: main 126 127 ### Étape 1 128 129 Conseil trivial : lorsque l'on se lance dans la création d'un graphique avec 130 gnuplot[^1] je suggère de toujours commencer par le graph le plus simple. Cela 131 permet de rapidement : 132 133 * de confirmer que l'environnement de dev et le code de base sont bons 134 * de confirmer que les données sont les bonnes 135 * d'avoir un début d'intuition de ce qu'il y a dans le jeu de données 136 137 Ça tombe bien, c'est l'une des forces de gnuplot. Avec la ligne : 138 139 plot 'nbofcommits.tsv' using 0:1 with points 140 141 endsection 142 fig 2 143 section: main 144 145 On obtient instantanément un nuage de point. En abscisse le nombre de jours 146 depuis un an, en ordonnée le nombre de commits. On confirme visuellement que 147 tout semble être correct : on a une majorité de journées à zéro commits, le reste 148 entre 1 et 50, un gros trou durant l'été. Mais nous sommes encore bien loin de 149 l'arrivée. 150 151 La commande `plot`, le cœur véritable de gnuplot, se découpe ainsi : 152 153 * `plot` : le nom de la commande 154 * `nbofcommits.tsv` : le chemin du fichier à tracer 155 * `using` : un "modificateur" permettant de préciser quelle colonne de données 156 tracer 157 * `0:1` : les identifiant des colonnes à utiliser au format `x:y` 158 * `with` : un autre modificateur permettant d'indiquer avec quel "style" 159 tracer les données 160 * `points` : le style en question 161 162 Si l'on met tout ça bout à bout en français, on a demandé à gnuplot : 163 164 > Affiche la première colonne du fichier `nbofcommits.tsv` en fonction du numéro 165 > de ligne en traçant des points. 166 167 Pour les identifiants de colonnes `1` désigne la première colonne, `2` la 168 seconde. Pour tracer la seconde en fonction de la première il faut écrire `2:1`. 169 La colonne `0` est spéciale, c'est une pseudo-colonne qui contient pour chaque 170 point le numéro de la ligne en cours. Je reviens dessus [plus 171 tard](/git-cal/#tape-3). Ainsi `0:1` trace les valeurs contenues dans la 172 colonne `1` en fonction du numéro de ligne auxquels elles apparaissent. 173 174 Avec le fichier 175 176 2 177 4 178 8 179 16 180 32 181 182 La commande 183 184 plot 'nbofcommits.tsv' using 0:1 with points 185 186 trace la fonction puissance de 2. A savoir, vous risquez de rencontrer des 187 commandes du type : 188 189 plot 'nbofcommits.tsv' using 1 with points 190 # ou 191 plot 'nbofcommits.tsv' using :1 with points 192 193 Pas de panique, cela fonctionne parce que si la colonne `x` n'est pas précisée 194 alors c'est la pseudo-colonne `0` qui est utilisée. Ces deux commandes sont donc 195 équivalentes à ce que l'on a écrit dans notre script. 196 197 ### Étape 2 198 199 Super sauf que l'on veut des carrés ! Rien de plus simple, on peut ajouter suite 200 au modificateur `with` le type de point à utiliser. Il se trouve que le numéro 5 201 est un carré rempli. Vous pouvez consulter les points à votre disposition en 202 lançant la commande `test`. Chez moi voilà ce que cela donne : 203 204 ![Une liste de styles de lignes, On remarque que le style numéro 5 a pour point 205 un carré remplie](types.png) 206 207 On le précise 208 209 plot 'nbofcommits.tsv' using 0:1 with points pointtype 5 210 211 et hop: 212 213 endsection 214 fig 3 215 section: main 216 217 ### Étape 3 218 219 À ce stade la graphique reste linéaire dans le temps et encode la quantité de 220 commits par les ordonnées et non pas par la couleur. Remédions d'abord à cette 221 histoire d'ordonnées. Notre souhait est que l'ordonnée d'un carré corresponde à 222 son emplacement dans la semaine. Les lundi en haut de chaque colonne, les 223 dimanche en bas et rebelotte pour la prochaine semaine. Heureusement[^3] notre 224 fichier commence un lundi et ne saute aucune journée[^6]. On a donc le pattern 225 suivant : 226 227 ligne 1 -> lundi 228 ligne 2 -> mardi 229 [...] 230 ligne 7 -> dimanche 231 ligne 8 -> lundi 232 233 Un peu d'expérience avec les maths et/ou la programmation permet de déceler 234 qu'il est possible de jouer avec le modulo des numéros de ligne. En effet : 235 236 0%7 = 0, lundi -> 0 237 1%7 = 1, mardi -> 1 238 [...] 239 6%7 = 7, dimanche -> 7 240 7%7 = 0, lundi -> 0 241 242 Pourquoi on commence par 0 plutôt que 1 ? Parce qu'en informatique on commence 243 souvent par zéro et que c'est le cas de gnuplot quand on lui demande le numéro 244 d'une ligne. Ainsi, si l'on pouvait donner en `y` à la commande `plot` non pas 245 la valeur dans la première colonne mais le modulo de la ligne en cours on 246 pourrait jouer sur la hauteur des carrés comme on le souhaite. C'est ce que 247 permettent la fonction `column` et l'opérateur `%` : 248 249 plot 'nbofcommits.tsv' using (int(column(0))%7) with points pointtype 5 250 251 `column` permet d'accéder, en plus des colonnes du fichier, à ce que gnuplot 252 appelle des "pseudo-colonnes" ou "pseudocolumns" en anglais. Ces colonnes 253 n'existent pas dans le fichier et sont tenues à jour par gnuplot pour des 254 raisons pratiques. Dans la pseudo-colonne numéro 0 on retrouve le numéro de la 255 ligne courante. Ainsi pas besoin de l'ajouter à notre jeu de donnée ! On 256 convertit ce que nous renvoie la fonction en entier avec `int()`[^4] pour 257 pouvoir calculer le modulo 7. On notera que la totalité de l'opération est entre 258 parenthèse là où auparavant on indexait les colonnes directement avec des 259 entiers. Ces parenthèses permettent d'introduire une "inline transformation" ou 260 transformation en ligne. L'expression entre les parenthèses sera évaluée pour 261 chaque ligne et son résultat utilisé pour la colonne correspondante. En gros, si 262 vous voulez faire des maths ou appeler des fonctions à un endroit où vous auriez 263 normalement du inscrire une valeur particulière, utilisez des parenthèses. 264 265 On constate qu'effectivement tous les carrés se sont rangés en sept lignes, de 0 266 à 6 : 267 268 endsection 269 fig 4 270 section: main 271 272 ### Étape 4 273 274 On pourrait penser que les carrés se sont également alignés en colonne mais 275 c'est une illusion. Chaque carré est un peu plus à droite que le précédent. 276 Pour s'en convaincre on peut élargir le graphique qui avait pour l'instant les 277 dimensions par défaut de gnuplot. J'opte pour une largeur conséquente de 2000 278 pixels et une hauteur correspondant au ratio du nombre de semaines sur le nombre 279 de jours par semaine. Cela devrait nous donner un graphique final plutôt 280 équilibré : 281 282 width=2000 283 set term pngcairo size width,(width/(52/7)) 284 285 La première ligne correspond à la création d'une variable. La seconde détermine 286 le terminal que gnuplot ciblera. Sans rentrer dans les détails, gnuplot sait 287 tracer pour plusieurs "terminaux" différents. Historiquement ces "terminaux" 288 pouvaient être de vrais terminaux physiques. Aujourd'hui cela est plutôt synonyme 289 de types ou de formats de sortie (png, jpeg, latex, svg etc). C'est en 290 choisissant le terminal que l'on peut également modifier la taille de la sortie. 291 J'utilise ici `pngcairo` qui fait de jolis png mais `png` est également dispo 292 quoi que un peu plus vieillot pour un rendu peut-être un peu moins joli. Le 293 terminal `pngcairo` peut avoir tendance à générer des fichiers un peu gros mais 294 la marge de progression étant grande, l'utilisation d'outils type 295 [optipng](https://optipng.sourceforge.net/) est recommandée. 296 297 A noter le retour des parenthèses pour faire un rapide calcul là où nous aurions 298 normalement du fournir un entier. 299 300 endsection 301 fig 5 302 section: main 303 304 On remarque mieux le décalage horizontal des carrés. On va y remédier. 305 306 ### Étape 5 307 308 Comme on l'a fait pour les ordonnées, il convient de trouver le pattern adéquat 309 pour les abscisses. On souhaite que les paquets de 7 lignes dans le fichiers 310 soient tous sur la même colonne : 311 312 ligne 0 -> lundi semaine 1, 0/7 -> 0 313 ligne 1 -> mardi semaine 1, 1/7 -> 0,nnn 314 [...] 315 ligne 6 -> dimanche semaine 1, 6/7 -> 0,nnn 316 ligne 7 -> lundi semaine 2, 7/7 -> 1,nnn 317 ligne 8 -> mardi semaine 2, 8/7 -> 1,nnn 318 319 On remarque que la partie entière de la division du numéro de ligne par 7 320 devrait correspondre à notre besoin. En plus le fait que les numéros de lignes 321 commencent à 0 nous arrange ! Chouette ! La fonction `floor` va nous permettre 322 de répondre à notre besoin : 323 324 x(nl) = floor(nl/7) 325 y(nl) = int(nl)%7 326 327 plot 'nbofcommits.tsv' using (x($0)):(y($0)) with points pointtype 5 328 329 On découvre ici que l'on peut créer nos propres fonctions. gnuplot permet 330 d'écrire des fonctions qui peuvent ensuite être appelées dans différents 331 contextes comme ici dans une transformation en ligne. Deuxième nouveauté 332 l'utilisation du raccourci `$0`. Il est équivalent à `column(0)`. De manière 333 générale quand vous voulez, à l'intérieur d'une transformation en ligne, 334 accéder à une valeur numérique contenue dans la N^ième colonne alors vous 335 pouvez écrire `column(N)` ou `$N`. 336 337 Ainsi : 338 339 (int(column(0)/7) est devenu 340 (int($0)/7) que l'on met dans une fonction pour écrire 341 (x($0)) 342 343 endsection 344 fig 6 345 section: main 346 347 Super, nos carrés sont tous bien alignés ! 348 349 ### Étape 6 350 351 C'est super mais on voit pas grand chose. Puis les axes ne nous servent pas 352 vraiment. Puis la légende nous plus. Faisons une petite pause pour nettoyer tout 353 ça. Premièrement retirons les axes avec, au choix : 354 355 set border 0 356 unset border 357 358 Les arguments de `set border` paraitront étranges sauf aux personnes manipulant 359 le `chmod` depuis leur plus tendre enfance. En effet, il sont encodés sur quatre 360 bits, chacun déterminant si l'un des bords est tracé ou pas. Le bit de poids le 361 plus faible encode l'affichage de l'axe du bas, le second celui de gauche, le 362 troisième celui du haut et la quatrième celui de droite. Ainsi `0110` veut dire 363 "afficher l'axe de gauche et celui du haut". L'argument est la représentation 364 décimal de la valeur, ici `8*0+1*4+1*2+1*0=6`. Il est également possible de 365 l'écrire `set border 2+4`. Dans notre cas rien de tout ça ne nous importe 366 puisque l'on ne veut rien. Par ailleurs `unset border` et `set border 0` sont 367 équivalents. 368 369 Deuxièmement, retirons la légende : 370 371 unset key 372 373 Troisièmement retirons les "tics" : 374 375 unset tics 376 377 Finalement agrandissons les points en ajoutant un style après `with` : 378 379 plot 'nbofcommits.tsv' using (x($0)):(y($0)) with points pointtype 5 pointsize 4 380 381 endsection 382 fig 7 383 section: main 384 385 Le graph ci-dessus est un collage des étapes successives. Il est possible de 386 réaliser des graphiques comme celui-ci avec la commande `set multiplot`. 387 Chaque appel successif de la commande `plot` ajoutera un graphique supplémentaire 388 à la figure. La plupart du temps cela est utile pour tracer plusieurs graphs les 389 un à côté des autres comme ici. Pour que cela soit pratique `multiplot` peut 390 prendre un argument `layout` qui prendra en argument deux entiers, 391 respectivement le nombre de lignes et le nombre de colonnes à remplir. Ainsi le 392 graph ci-dessus a été construit comme ceci de façon à voir les effets des 393 commandes : 394 395 set term pngcairo size width,(width/(52/7)*3) 396 397 set multiplot layout 4,1 398 399 set border 0 400 plot 'nbofcommits.tsv' using (x($0)):(y($0)) with points pointtype 5 401 unset key 402 plot 'nbofcommits.tsv' using (x($0)):(y($0)) with points pointtype 5 403 unset tics 404 plot 'nbofcommits.tsv' using (x($0)):(y($0)) with points pointtype 5 405 plot 'nbofcommits.tsv' using (x($0)):(y($0)) with points pointtype 5 pointsize 4 406 407 Plus rarement `multiplot` permet, sans utilisation de `layout`, de tracer 408 n'importe où pour, par exemple, insérer un petit graph dans un autre et créer un 409 effet de "zoom". 410 411 ### Étape 7 412 413 A un moment où à un autre il va falloir s'occuper de la couleur. Il y a au moins 414 deux façons de faire varier la couleur de ce que l'on trace en fonction des 415 données dans gnuplot. 416 417 La première est d'utiliser une colonne de couleur dédiée dont on ira chercher 418 les valeurs dans le fichier. L'aide de gnuplot dit : 419 420 > When using the keywords `pointtype`, `pointsize`, or `linecolor` in a plot 421 > command, the additional keyword `variable` may be given instead of a number. 422 > In this case the corresponding properties of each point are assigned by 423 > additional columns of input data. Variable pointsize is always taken from the 424 > first additional column provided in a `using` spec. Variable color is always 425 > taken from the last additional column. 426 427 Autrement dit, en plus des colonnes x et y que l'on a jusque là utilisé il en 428 existe d'autres qui sont utilisées pour faire varier d'autres caractéristiques 429 des points. Ainsi 430 431 plot 'nbofcommits.tsv' using (x($0)):(y($0)):1 with points pointtype 5 pointsize 4 linecolor variable 432 433 Dit à gnuplot d'aller chercher la couleur du point dans la première colonne. Or, 434 l'une des manières que gnuplot a d'identifier les couleurs est par des entiers 435 de 0 à 15[^5] faisant référence aux couleurs prédéfinies par défaut de gnuplot. 436 C'est d'ici que vient le superbe violet, c'est la couleur `1`. Vous pouvez voir 437 toutes les couleurs dans l'image test de [l'étape 2](/git-cal/#tape-2). 438 439 endsection 440 fig 8 441 section: main 442 443 Les couleurs que l'on obtient ne veulent pas dire grand chose puisque l'on 444 indexe les 15 couleurs par défaut avec le nombre de commits durant cette 445 journée. Ce qu'il va nous falloir c'est un gradient de couleur qui représente 446 une intensité. 447 448 ### Étape 8 449 450 gnuplot propose une fonction de palette de couleur. Elle peut être déclarée de 451 plusieurs manières mais nous allons voir la suivante : 452 453 set palette defined ( 0 "#EEEEEE", 1 "light-blue", 20 "blue", 50 "dark-blue" ) 454 455 Comme vu précédement les couleurs peuvent être identifiées de plusieurs 456 manières différentes. En plus d'un entier faisant référence à l'une des 16 457 couleurs prédéfinies, quelques couleurs sont nommées et l'utilisation des codes 458 hexadécimaux est également possible. Chaque couleur nommée possède trois 459 valeur, celle par défaut, une plus claire nommée `light-` et une plus sombre 460 nommée `dark-`. Ainsi au dessus on décide d'attribuer à la valeur 0, c'est à 461 dire aux journées sans commits, un gris très clair, à la valeur 1 le bleu 462 clair, la valeur 20 le bleu et la valeur 50 le bleu foncé. gnuplot est 463 suffisamment intelligent pour créer une palette continue entre les valeurs. 464 Ainsi 35 aura pour couleur celle à mi chemin entre `blue` et `dark-blue`. Bien 465 sûr j'ai choisi les valeurs de façon à ce que le graph soit intéressant 466 visuellement pour mon activité mais on peut les adapter. Un moyen de le rendre 467 dynamique serait d'utiliser la commande `stats` pour récupérer le minimum, le 468 maximum, la moyenne etc et, sur cette base, construire une palette appropriée. 469 Cela sort du périmètre de cet exercice donc je passe. 470 471 Afin de mettre à profit notre palette il suffit de remplacer le mot clef 472 `variable` utilisé précedemment par `palette` : 473 474 plot 'nbofcommits.tsv' using (x($0)):(y($0)):1 with points pointtype 5 pointsize 4 linecolor palette 475 476 endsection 477 fig 9 478 section: main 479 480 Trois remarques : 481 482 1. On ne respecte plus exactement l'exercice puisque dans le graph gitlab il 483 n'existe que quatre couleurs prédéfinies pour des intervalles de valeurs. Je 484 trouve que ce système de palette est plus cool donc je me permets de 485 dévier. 486 2. Notre commande `plot` commence à être très longue, on verra comment réduire 487 sa taille bientôt. 488 3. On peut pas enlever le gros rectangle à droite qui faute de ne pas avoir de 489 légende n'est pas bien utile ? 490 491 ### Étape 9 492 493 Ce rectangle automatiquement ajouté lorsque l'on utilise une palette est appelé 494 `colorbox`. Il est très utile pour la légende dans la majorité des cas mais pas 495 ici. Pour le retirer on suit la même logique que pour les autres décorations : 496 497 unset colorbox 498 499 Maintenant que l'on a une vision du nombre de commits par jour on se rend compte 500 d'une petite bêtise faite plus haut et difficile à déceler. Le tout premier 501 "vrai" jour de notre jeu de donnée est un dimanche, avec un commit puis plus 502 rien pour un moment, le tout dernier un lundi. On repère le dimanche en question 503 tout en haut à gauche, le lundi tout en bas à droite. Or on veut que les 504 colonnes commencent un lundi et terminent un dimanche. Pour y arriver il faut se 505 souvenir que tout cela est tracé dans un repère. Pour les carrés du haut `y=6`, 506 ceux du bas `y=0`. Un moyen très simple de les inverser est donc de mulitplier 507 `y` par `-1` : 508 509 y(nl) = int(nl*-1)%7 510 511 endsection 512 fig 10 513 section: main 514 515 Super, notre lundi apparaît bien en haut et notre dimanche en bas. 516 517 Et pour rendre un peu plus lisible notre commande plot on peut la séparer sur 518 plusieurs lignes en les terminant pas des `\` : 519 520 plot 'nbofcommits.tsv' using (x($0)):(y($0)):1 \ 521 with points \ 522 pointtype 5 pointsize 5 linecolor palette 523 524 ### Étape 10 525 526 Si l'on retourne voir le graphique de gitlab on remarque qu'il y a des jours en 527 moins à gauche. En effet, il n'affiche que les 365 derniers jours. Or rappelez 528 vous, notre jeu de donnée contient des jours supplémentaires de l'année 529 précédente pour "remplir" la première semaine. C'est pratique puisque cela nous 530 permet d'avoir recours à l'astuce de [l'étape 3](/git-cal/#tape-3). Comment 531 pouvons nous les garder dans le jeu de donnée sans les afficher ? Je propose 532 une solution parmi d'autre qui permet d'introduire l'opérateur ternaire : 533 534 plot 'nbofcommits.tsv' u (words(strcol(2))>1 ? x($0):NaN):(y($0)):1 \ 535 with points \ 536 pointtype 5 pointsize 5 linecolor palette 537 538 Plusieurs choses ici : 539 540 1. `using` est devenu `u`. Dans gnuplot la plupart des mots clefs peuvent être 541 raccourcis. Cela obfusque un peu les scripts mais c'est bien pratique pour 542 les initié·es. Je me suis retenu jusque là mais la ligne devenant très longue 543 j'ai cédé ici. `pointtype` pourrait être `pt`, `with` `w` etc. 544 2. Dans une transformation en ligne on utilisait `column(N)` ou son raccourci 545 `$N` pour accéder aux valeurs des colonnes du fichiers ou des 546 pseudo-colonnes. Cette fonction ne renvoie que des valeurs numériques. Si 547 l'on souhaite récupérer une chaîne de caractère il faut utiliser sa soeur 548 `stringcolumn(N)` ou son raccourci `strcol(N)`. 549 3. `words()` est une fonction prenant en argument une chaîne de caractère et 550 renvoyant son nombre de mots. Les mots sont séparés par des blancs 551 (espaces, tabulations etc). `words("abcd")` renvoie `1`, `words("a b c d")` 552 renvoie `4`. `words(strcol(2))` renverra donc le nombre de mots de la 553 seconde colonne. 554 4. Les opérateurs `>`, `?`, `:`. `>` permet de faire une simple comparaison 555 comme on en trouve dans tous les langages. Ici on vérifie donc si la 556 seconde colonne contient strictement plus qu'un mot. L'opérateur `?` va 557 vérifier la valeur de vérité de la comparaison qui le précède et opter pour 558 l'expression précédant le `:` si elle est vraie et celle le suivant si elle 559 est fausse. Ici la valeur de la première colonne sera le résultat de 560 `x($0)` si la seconde colonne de notre fichier contient au moins deux mots 561 et sera `NaN` si elle est contient moins. 562 563 Pourquoi est-ce que cela fonctionne ? D'abord parce que cette comparaison permet 564 effectivement de discriminer entre les "faux" jours en début de fichier, n'ayant 565 qu'un entier dans leur seconde colonne, et les vrais en ayant quatre. Ensuite 566 parce que pour gnuplot `NaN` est particulier. Par défaut, s'il est rencontré 567 dans l'une des colonnes alors le point ne sera pas tracé. 568 569 Si vous testez ce code vous ne verrez pas les carrés disparaître. C'est parce 570 qu'il nous manque une information essentielle au début de notre script. Si la 571 fonction `words()` sépare les mots par des blancs ce n'est pas par hasard, 572 c'est le comportement par défaut de tout gnuplot. En écrivant `strcol(2)` on 573 pense donc faire référence à la colonne avec les dates mais pour gnuplot les 574 espaces et les tabulations sont tous les deux des séparateurs. La deuxième 575 colonne contient donc que les entiers de 1 à 7 du début de la colonne des 576 dates. Pour que gnuplot interprète le fichier comme on le fait, c'est à dire 577 avec uniquement les tabulations comme séparateur, il faut lui indiquer avant la 578 commande `plot` : 579 580 set datafile separator "\t" 581 582 Et tout fonctionnera comme prévu : 583 584 endsection 585 fig 11 586 section: main 587 588 Si jusqu'à maintenant tout s'était bien passé alors que gnuplot n'interprétait 589 pas les données comme on le faisait c'est parce que, par pure coïncidence, tout 590 ce que l'on faisait reposait sur le numéro de ligne ou la première colonne. 591 592 Il manque un minimum de contexte à ce graph non ? 593 594 ### Étape 11 595 596 On va d'abord faire un peu de place sur le graph pour pouvoir caser du texte : 597 598 set tmargin 1 599 set lmargin 6.5 600 601 `tmargin` pour "top margin", `lmargin` pour "left margin". A gauche on écrit les 602 jours : 603 604 set label "Mon" at -1.5, 0 605 set label "Thu" at -1.5,-3 606 set label "Sun" at -1.5,-6 607 608 Pour les mois cela est un peu plus compliqué. Puisqu'ils dépendent des données 609 on ne peut pas les placer "à la main" comme les jours mais on ne veut pas non 610 plus les afficher pour chaque point. Sinon on aurait 365 chaînes de caractères 611 qui noirciraient totalement le graph. On va donc à nouveau avoir recours à la 612 commande `plot` et l'astuce du `NaN` : 613 614 plot 'nbofcommits.tsv' u (words(strcol(2))>1 ? x($0):NaN):(y($0)):1 \ 615 w p \ 616 pt 5 ps 5 lc palette, \ 617 '' u (x($0)):(word(strcol(2),4) eq "01" ? 1.3:NaN):(word(strcol(2),3)) \ 618 w labels 619 620 J'ai tout raccourci comme ça vous pouvez prendre l'habitude de lire des scripts 621 de la sorte sans être perdu·es. Les nouveautés : 622 623 1. Lorsque l'on veut tracer plusieurs choses sur un même graph il faut 624 enchaîner les arguments à la commande `plot` sans répéter la commande elle 625 même. Si l'on avait par exemple deux fichiers `1.tsv` et `2.tsv` et que 626 l'on voulait les voir ensemble sur le même graphique on pourrait écrire : 627 628 `plot '1.tsv' u ... w ..., '2.tsv' u ... w ...` 629 630 Dans notre cas les données proviennent du même fichier, on aurait pu repréciser 631 `'nbofcommits.tsv'` mais c'est implicite. 632 633 2. La fonction `word(str,?)` qui renvoie le N^ième mot de la chaîne `str`. 634 Ainsi `word("a b c d",3)` renvoie `c`. 635 3. Le style `with labels`. Ce style est similaire à `with points` mais 636 pour des chaînes de caratères. Il nécessite une troisième colonne non 637 optionnelle qui sera la chaîne affichée. De façon à n'afficher les mois 638 que sur la première semaine on vérifie pour chaque ligne si le dernier mot 639 de la seconde colonne est `01`. Si oui alors on est le premier d'un nouveau 640 mois et l'ordonnée de la chaîne à afficher est `1.3` de façon à être 641 légèrement au dessus des carrés. Sinon c'est `NaN` et la chaîne ne 642 s'affiche pas. La gestion de l'abscisse est équivalente à ce que l'on fait 643 avec les carrés. Finalement on va chercher la chaîne à afficher dans le 644 troisième mot de la seconde colonne. 645 646 endsection 647 fig 1 648 section: main 649 650 Et voilà le travail. 651 652 ## Conclusion 653 654 Évidemment ce graphique sans interactivité, notamment sans pouvoir cliquer sur 655 les carrés etc n'est pas bien utile. Je pense que cela reste un bon exercice qui 656 permet de voir une quantité surprenante d'astuces gnuplot. 657 658 On voulait tracer des carrés et donc `w p pt 5` était parfait mais si l'on avait 659 voulu tracer des rectangles plus arbitraires on aurait pu utiliser le style `w 660 boxxy`. D'ailleurs je l'ai initialement fait comme cela avant de voir un 661 graphique étrangement ressemblant fait avec `w p` dans le bouquin "gnuplot in 662 action" que je recommande vivement. 663 664 [^1]: ou tout autre logiciel pour faire des graphs 665 [^2]: parce qu'il est écrit soit `:y` soit `y` tout court 666 [^3]: c'est à partir de maintenant que le format un peu bizarre de notre tsv 667 commence à nous être utile 668 [^4]: je suis surpris que column renvoie autre chose qu'un entier mais pas moyen 669 de le faire fonctionner sans la conversion. A creuser. 670 [^5]: qui bouclent, 16 fera référence à la même couleur que 0 671 [^6]: Aussi le chiffre que l'on cherche est sous nos yeux dans la seconde 672 colonne mais on fait comme si de rien n'était, [voir 673 l'addendum](#pourquoi-ce-format-un-peu-trange-). 674