arthur.bebou

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