La spirale du climat avec gnuplot

Où l'on reproduit la spirale du climat avec des outils que j'aime bien pour fêter la fin de la canicule

Pour fêter la “fin” de la canicule de fin juin 2026 je propose d’apprendre à reproduire la fameuse spirale du climat d’Ed Hawkins avec des outils que j’aime bien, awk, gnuplot, ffmpeg et évidemment du shell.

Le résultat final, vidéo de 153Ko :

Tout le code mentionné dans cet article est dans ce dépôt git.

Chaque figure vient avec le code complet ayant permis de le générer. Si jamais vous vous perdez dans les explications vous pouvez toujours vous y référer.

Si vous ne voulez pas télécharger et préparer les données vous même, le fichier est dispo ici.

Récolter et préparer les données

Téléchargement

Les données sont disponibles sous format CSV sur le site de la NASA. Il est possible de les télécharger dans son terminal à l’aide de curl :

curl -Ls https://data.giss.nasa.gov/gistemp/tabledata_v4/GLB.Ts+dSST.csv >
data.csv

ou de wget :

wget -O data.csv https://data.giss.nasa.gov/gistemp/tabledata_v4/GLB.Ts+dSST.csv

Le fichier a cette tête :

Land-Ocean: Global Means
Year,Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec,J-D,D-N,DJF,MAM,JJA,SON
1880,-.19,-.25,-.10,-.17,-.10,-.22,-.19,-.11,-.15,-.24,-.23,-.19,-.18,***,***,-.12,-.17,-.21
1881,-.20,-.16,.02,.04,.06,-.19,.01,-.04,-.16,-.22,-.19,-.07,-.09,-.10,-.18,.04,-.07,-.19
[...]

Masser les données

Puisque je suis meilleur en awk qu’en gnuplot j’ai tendance à bien préparer les données en amont pour éviter de faire trop de magie noire dans gnuplot. Je ne vais pas trop prémacher le travail non plus parce qu’on veut tout de même apprendre des choses dans gnuplot.

Ici notre objectif est d’obtenir chaque anomalie sur une ligne séparée. Puisqu’il va falloir faire “tourner” l’affichage des anomalies plutôt que de les afficher de gauche à droite on veut aussi savoir à quel mois de l’année elle correspond. La valeur textuelle du mois ne nous intéresse pas vraiment, c’est plutôt son chiffre (de 1 à 12) qui va nous être utile pour décaler les points sur le cercle. On veut garder l’année pour pouvoir l’afficher au centre du cercle.

Première chose à faire est de dire à awk qu’il s’agit d’un CSV. Pour cela on met la variable FS (Field Separator) à ,.

< data.csv awk '
BEGIN { FS="," }
'

Attention ! Cela n’implique pas qu’awk parse automatiquement le CSV correctement, simplement qu’il sépare les champs sur les virgules. Ici cela ne fonctionne que parce que les données ne contiennent pas de virgules. Un vrai parseur de CSV est bien plus compliqué que cela. Alternativement l’awk maintenu par Kernighan inclu une option --csv permettant de “réellement” comprendre les CSV. Les deux premières lignes de CSV ne nous intéressent pas. On peut donc commencer notre traitement après. Pour cela on conditionne un bloc d’action {} par une condition étant vraie uniquement après avoir passé la seconde ligne. Cette condition est NR>2, NR (Number Record) étant une variable contenant le numéro de la ligne en cours :

< data.csv awk '
BEGIN { FS="," }
NR>2  { print }
'

Il ne reste plus qu’à choisir ce que l’on veut afficher. On ne veut pas toute la ligne, mais les colonnes de 2 à 13, celles contenant les anomalies. On y ajoute l’année (la première colonne) à la fin :

< data.csv awk '
BEGIN { FS="," }
NR>2  { for(i=2;i<14;i++) print $i,$1 }
'

print var1,var2 va afficher les valeurs de var1 et var2, séparées par le séparateur de champ de sortie, suivi d’un retour à la ligne. Ce séparateur de champ de sortie, nommé OFS (Output Field Separator) et stocké dans la variable du même nom, a pour valeur par défaut l’espace. Puisqu’il n’y a pas d’espace dans nos données il n’est pas utile de le changer, gnuplot parsera correctement le résultat.

Reste à récupérer le nombre du mois en cours. On peut le dériver de la valeur de i. Quand i vaut 2, on affiche la première anomalie de l’année (janvier), quand i vaut 3 on affiche la seconde (février) etc. On peut donc simplement imprimer i-1 dans notre fichier :

< data.csv awk '
BEGIN { FS="," }
NR>2 { for(i=2;i<14;i++) print i-1,$i,$1 }
'

Redirigé dans un fichier ce code nous donnera un fichier sous la forme :

1 -.19 1880
2 -.25 1880
3 -.10 1880
4 -.17 1880
[...]

Dans la suite de l’article ces données sont contenues dans un fichier data.dat. On peut s’arrêter ici et passer à gnuplot.

Construire le graph

Étape 1 : Vérifier que les données font sens

gnuplot permet très rapidement d’afficher des données numériques. Cela permet de contrôler si tout semble bon. Pour cela on peut simplement choisir une sortie et demander d’afficher une colonne :

set term pngcairo size 800,800
plot 'data.dat' u 2

Première ligne on demande d’utiliser le moteur de rendu pngcairo (qui produit de jolies résultats généralement), pour une taille de png de 800 pixels par 800. Deuxième ligne on demande à gnuplot de plotter le seconde colonne de notre fichier. u est un raccourci pour le mot clef using. Il permet de définir à quelle colonne on veut s’intéresser. Par défaut si une seule colonne est précisée ses données seront affichées en y avec en x les numéros de lignes. Puisque l’on a les anomalies dans la seconde colonne et qu’elles ordonnées de la plus vieilles à la plus récente dans le fichier le résultat va nous afficher l’évolution des anomalies dans le temps. Pour exécuter ce script il faut le mettre dans un fichier (fig1.gp par exemple) puis appeler la commande suivante :

gnuplot -c fig1.gp > fig1.png

La commande créera le fichier fig1.png suivant :

On affiche simplement les anomalies en y le long de l'axe x
figure 1 : On affiche simplement les anomalies en y le long de l'axe x
script

Alternativement il est possible d’ajouter le shebang #! /usr/bin/gnuplot -c1 en début de fichier pour pouvoir le lancer lui même comme ceci :

./fig1.gp > fig1.png

A noter, si l’on ne souhaite pas créer une joli png pérenne mais simplement visualiser rapidement les données on peut aussi récupérer les données avec un pipe de la sorte :

< data.dat gnuplot -p -e 'plot "-" u 2'

On peut même afficher le résultat en ascii art directement dans le terminal :

< data.dat gnuplot -e 'set term dumb;plot "-" u 2'

  1.5 +--------------------------------------------------------------------+   
      |       +      +       +       +      +       +       +      + A  AA |   
      |                                                    "-" u 2    A AA |   
      |                                                             AAAAAA |   
    1 |-+                                                       A   AAAAAA-|   
      |                                                     A A   A AAAAA  |   
      |                                                  A AA AAAAAAAAAAA  |   
      |                                                 A   AAAAAAAAA  A   |   
  0.5 |-+                                           AA  AAAAAAAAAAAAA    +-|   
      |                           AA      A      A AAAAAAAAAAAAA A         |   
      |                    A      AAA   A AAA  A A AAAAAAAAAAAAA A         |   
      |A  A    AA          A AAA AAAAAAAAAAAAAAAAAAAAAAAA A                |   
    0 |A+ AA  AAAAA   A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA  A              +-|   
      |AAAAAAAAAAAA  AAAAAAAAAAAAA  AAAAAAAAAAAAAAAAA                      |   
      |AAAAAAAAAAAAA AAAAAAAAAAAA    AAAAA   AAAAAAA                       |   
      |AAAAAAA AAAAAAAAAAAAAAA AA      A A A A                             |   
 -0.5 |-AA AAAA  AAAAA AAA   A A                                         +-|   
      | AA  A    AAAAA A     A                                             |   
      |  A  A      A                                                       |   
      |       +      + A     +       +      +       +       +      +       |   
   -1 +--------------------------------------------------------------------+   
      0      200    400     600     800    1000    1200    1400   1600    1800 
                                                                               

Étape 2 : centrer le graph sur l’origine

On sait que l’on va tracer des coordonnées autour d’un cercle. Il est donc pratique de se centrer à l’origine avent de faire un peu de trigonométrie. Pour cela on peut choisir à la main les coordonnées x et y visibles :

set term pngcairo size 800,800

set xrange [-3.2:3.2]
set yrange [-3.2:3.2]

plot 'data.dat' u 2

Ces commandes doivent apparaître avant la commande plot qui est toujours dernière.

On centre le graph sur le 0 en prenant assez d'espace autour pour
afficher les valeurs à venir
figure 2 : On centre le graph sur le 0 en prenant assez d'espace autour pour afficher les valeurs à venir
script

On voit encore quelques valeurs mais la grande majorité sont en dehors de notre champ de vision.

Étape 3 : Retirer toutes les décorations par défaut

Le résultat final n’a pas d’axes ni de légende. Pour les retirer on utilise les commandes unset, toujours avant le plot. border représente les axes, xtics les valeurs dessus (-3, -2, -1…) et les petits traits les accompagnants :

unset border
unset xtics
unset ytics

On retire également la légende avec not (ou no title) renseigné en fin de commande plot :

plot 'data.dat' u 2 not
On retire toute la décoration
figure 3 : On retire toute la décoration
script

Étape 4 : Ajouter nos décorations

Maintenant que tout est retiré sauf les données on va ajouter notre propre décoration. On veut un fond noir et une font spécifique ainsi que sa taille :

set term pngcairo background "black" size 800,800 font "Helvetica,20"

des cercles blancs :

set object 1 circle at 0,0 front size 0.5 fc "white" lw 3
set object 2 circle at 0,0 front size 1   fc "white" lw 3
set object 3 circle at 0,0 front size 2.5 fc "white" lw 6
set object 4 circle at 0,0 front size 3   fc "white" lw 6

et du texte blanc pour les légender :

set label "-0.5°C"   at -0.25,0.3 front textcolor "white"
set label "0°C"   at -0.1,0.8 front textcolor "white"
set label "1.5°C" at -0.2,2.25 front textcolor "white"
set label "2°C"   at -0.1,2.75 front textcolor "white"
On ajoute notre décoration. Oui le -0.5°C déborde mais flemme de le
rendre plus petit. C'est possible en lui précisant une font spécifique avec une
autre taille.
figure 4 : On ajoute notre décoration. Oui le -0.5°C déborde mais flemme de le rendre plus petit. C'est possible en lui précisant une font spécifique avec une autre taille.
script

Étape 5 : Tracer les données en spirale

Vient le moment de tracer les points non plus de gauche à droite mais en spirale. Pour cela on va utiliser une fonctionnalité bien pratique de gnuplot, la possibilité d’appeler des fonctions dans la commande plot.

Habituellement si on veut tracer des points en x et en y il faut faire :

plot 'data.dat' u numcolonnex:numcolonney not

avec numcolonnex et numcolonney des entiers faisant référence à ue colonne dans data.dat.

Mais si l’on veut faire un calcul avec les valeurs dans le fichier et plotter le résultat du calcul2, on peut mettre des parenthèses et utiliser une expression dedans :

plot 'data.dat' u ($numcolonnex*2):($numcolonney+1) not

Ainsi la commande ne va pas plotter les points avec les valeurs dans la colonne numcolonnex et la colonne numcolonney directement mais d’abord multiplier la première par 2 et ajouter 1 à la seconde, puis afficher les résultats. Dans une expression il faut ajouter un $ devant l’entier de la colonne.

Dans une expression il est possible d’appeler une fonction :

f(x)=x*2
plot 'data.dat' u (f($numcolonnex)):($numcolonney+1) not

Sachant tout cela on peut créer deux fonctions qui permettent de mapper les deux valeurs de chaque anomalie (mois et intensité de l’anomalie) à un point sur la cible. J’explique pas la trigo mais le code est celui-ci :

PI=3.14159
step=2*PI/12
x(r,n)=r*cos(n*step)
y(r,n)=r*sin(n*step)

plot 'data.dat' u (x($2,$1)):(y($2,$1)) not

Pour chaque anomalie, gnuplot calcule son x en passant l’intensité de l’anomalie (la distance entre l’origine et le point) et le mois (le décalage d’angle par rapport à l’origine du cercle) à une fonction x() qui retourne la bonne valeur. Pareil pour y. Puisqu’il y a douze mois on a séparé le cercle complet (2π) en 12.

On trace les données non pas de gauche à droite mais en spirale sur notre
cible
figure 5 : On trace les données non pas de gauche à droite mais en spirale sur notre cible
script

Un problème est évident, les valeurs minimales sont sur le vrai zéro du graph au lieu d’être sur notre cercle blanc notant le zéro. Les valeurs maximales, qui devraient être autour de 1.5 sont plutôt vers 0.5.

Étape 6 : Augmenter les anomalies pour correspondre aux cercles blancs

Pour remédier à ce problème rien de plus simple : on ajoute 1 à la valeur anomalies :

plot 'data.dat' u (x($2+1,$1)):(y($2+1,$1)) not

C’est déjà ce que l’on avait fait en plaçant les cercles.

On place l'intensité des anomalies correctement en leur ajoutant 1
figure 6 : On place l'intensité des anomalies correctement en leur ajoutant 1
script

Le résultat paraît correct, des max proches de 1.5, des min en dessous de -0.5 mais jamais à 0.

par ailleurs l’ajout de 1 aurait aussi pu être fait dans les fonctions x et y.

x(r,n)=(r+1)*cos(n*step)
y(r,n)=(r+1)*sin(n*step)

mais c’est l’occasion de montrer que l’on peut mélanger les calculs dans les fonctions et dans l’expression du plot.

Étape 7 : Détecter un problème de décalage

À terme la spirale va être animée. Il faut donc choisir un point de départ et un sens de rotation qui fasse sens. Admettons que l’on veuille que le cercle commence avec janvier en haut et tourne dans le sens horaire. Est-ce que c’est notre cas ici ?

Pour vérifier on peut utiliser le style de graph labels qui permet de placer du texte. Jusqu’à maintenant on utilisait le style par défaut, les points. Pour préciser un nouveau style il faut utiliser with (ou w en raccourci) et le nom du style.

plot 'data.dat' u (x($2+1,$1)):(y($2+1,$1)) w labels not

Mais cela ne va pas fonctionner parce que les différents styles prennent un nombre différents de colonnes en “arguments”. Le u et le w sont liés.

Ici les coordonnées auxquelles placer le texte restent les mêmes mais on va ajouter une colonne à notre u pour choisir le texte.

Ces colonnes sont comme ceci :

u x:y:texte

Le texte peut provenir du fichier de donnée. En utilisant la fonction stringcolumn on peut récupérer une valeur du fichier. Ici on pourrait afficher le chiffre correspondant au mois colonne une mais histoire d’apprendre un truc en plus on peut utiliser la colonne virtuelle 0, qui n’existe pas dans le fichier de donnée mais que gnuplot maintient de façon à ce qu’elle contienne toujours le numéro de la ligne en cours. Ainsi stringcolumn(0) renverra 0 sur la première ligne, 1 sur la seconde et ainsi de suite.

La couleur doit être modifiée parce que celle par défaut pour le texte est le noir et les chiffres ne seraient pas visibles. Pour modifier la couleur du texte il faut ajouter textcolor (tc) suivi du nom d’une couleur au plot.

Une fois tout ça mis bout à bout on obtient la commande suivante :

plot 'data.dat' (x($2+1,$1)):(y($2+1,$1)):(stringcolumn(0)):(1) w labels tc "white" not

demandant de tracer le numéro de la ligne à l’emplacement de l’anomalie, toujours toujours en blanc. Mais si l’on lance ça comme ça on obtiendra un graph blanchi de chiffres :

Pleiiiiin de texte blanc sur une cible

Pour n’afficher que le début des données on pourrait modifier externellement le fichier data.dat mais, plus pratique, on peut dire à gnuplot de ne s’intéresser qu’à certaines lignes avec every. Placé directement après le nom du fichier, every permet de contrôler la première ligne traitée, la dernière et l’incrément entre elles3. En ne renseignant que la première et la dernière (every ::0::2) on peut n’afficher que les 3 premières lignes :

plot 'data.dat' every ::0::2 (x($2+1,$1)):(y($2+1,$1)):(stringcolumn(0)):(1) w labels tc "white" not
On débugue en plaçant du texte afin de comprendre dans quel ordre les
points sont placés
figure 7 : On débugue en plaçant du texte afin de comprendre dans quel ordre les points sont placés
script

Et patatra, non seulement janvier ne commence pas en haut mais en plus le graph tourne dans le sens trigonométrique4 !

Étape 8 : Sens horaire et mettre janvier en haut

Rien de plus simple pour corriger le sens de rotation, on peut multiplier les valeurs de mois par -1 :

plot 'data.dat' every ::0::2 u (x($2+1,$1*-1)):(y($2+1,$1*-1)):(stringcolumn(0)):(1) w labels tc "white" not
On inverse le sens de rotation en multipliant les mois par -1
figure 8 : On inverse le sens de rotation en multipliant les mois par -1
script

Super mais janvier n’est pas du tout en haut !

On peut deviner sur le graph que janvier est à 4 décalages d’angle d’être en haut du graph. On peut donc ajouter 45 à notre entier de mois :

plot 'data.dat' every ::0::2 u (x($2+1,$1*-1+4)):(y($2+1,$1*-1+4)):(stringcolumn(0)):(1) w labels tc "white" not
En décalant tout de 4 tranches de cercle en arrière on aligne janvier
avec le haut
figure 9 : En décalant tout de 4 tranches de cercle en arrière on aligne janvier avec le haut
script

Nickel, enfin prêt pour afficher les valeurs !

Étape 9 : afficher des lignes !

Ce n’est ni le style points ni le style labels qu’il nous faut mais bien le style lines (ou l) pour produire le résultat final. En testant sur les 100 premières lignes et en choisissant une épaisseur de trait de 4.5 (linewidth ou lw)

plot 'data.dat' every ::0::100 u (x($2+1,$1*-1+4)):(y($2+1,$1*-1+4)) w l lw 4.5 not
On trace les 100 premières lignes
figure 10 : On trace les 100 premières lignes
script

Voilà qui paraît tout à fait correct !

Étape 10 : Faire varier la couleur des lignes

Vient alors la partie la plus cool du graph, faire varier les couleurs. On veut un gradient de couleur de bleu (vers -1°C) à rouge (vers +1.5°C) en passant par blanc (+0°C). Pour cela on peut utiliser une palette. Il est possible de déclarer la palette simplement en écrivant :

set palette defined (-0.7 "blue", 0 "white", 1.6 "dark-red")

gnuplot va automatiquement créer une palette avec un joli dégradé de couleur entre chaque “noeud”. Il est possible de voir la palette en cours avec la commande test palette. Én l’occurence voici la notre :

Une jolie palette de couleur allant du bleu au rouge

Mais il faut encore un peu travailler. Si l’on utilise cette palette tel quel gnuplot va automatiquement mapper la valeur minimale sur le bleu et la valeur maximale sur le rouge sombre et ce peu importe la valeur du point. Si l’on trace le graph pour les 100 premiers points, dont le maximum est encore loin d’être près de +1.6°C, il apparaitra quand même en rouge !

Comme on peut voir sur la palette, les valeurs que l’on a renseigné en la créant ne sont pas celles qui apparaissent. Én réalité elles ne servent qu’à établir les proportions de la palette. Par exemple ces trois palettes sont équivalentes :

set palette defined (  -1 "blue",   0 "white", 1 "dark-red")
set palette defined (   0 "blue", 0.5 "white", 1 "dark-red")
set palette defined ( -10 "blue",  -5 "white", 0 "dark-red")

parce que la valeur du blanc est toujours pile entre les deux extrêmes. Pour mapper la palettes à des valeurs “fixes” il faut lui donner son range, comme pour les abscisses x et y. Pour cela on utilise cbrange :

set cbrange [-0.7:1.6]
unset colorbox

Ainsi la valeur minimale de la palette correspond bien à -0.7°C et la valeur maximales à +1.6°C. Au passage on retire la visualisation du gradient qui est automatiquement ajoutée.

Finalement on utilise la palette en précisant que la couleur de la ligne (linecolor ou lc) utilise la palette en cours. u prend alors une troisième colonne, celle renseignant la couleur, qui est simplement la valeur de l’anomalie. En traçant tous les points :

plot 'data.dat' every ::0::1762 u (x($2+1,$1*-1+4)):(y($2+1,$1*-1+4)):2 w l lw 4.5 lc palette not
Variation des couleurs avec la palette
figure 11 : Variation des couleurs avec la palette
script

Étape 11 : Préparer l’animation

Pour animer la vidéo il faut créer chaque frame séparément. Il est possible de paramétrer le nombre de ligne traité6 via la ligne de commande et d’appeler successivement le script avec le paramètre 1 puis 2 puis 3 depuis le shell de la sorte :

gnuplot -c fig11.gp 1 > 1.png
gnuplot -c fig11.gp 2 > 2.png
gnuplot -c fig11.gp 3 > 3.png

Ainsi 1.png contiendrait le graph avec un seul point, 2.png avec deux points etc. C’est ce que je ferais de manière assez naturelle mais il est possible de boucler dans gnuplot et d’apprendre autre chose que du shell.

Puisque la boucle sera dans le script gnuplot il faut un moyen de construire le nom du fichier dans lequel on veut stocker le résultat et le faire varier selon le nombre de points que l’on veut afficher. En supposant un dossier frames qui contiendra les futures frames et un utilisant la fonction sprintf :

outfile=sprintf('frames/%d.png',1700)
set output outfile

Plutôt que d’envoyer le png sur la sortie standard et de faire une redirection shell le script créé lui même le fichier frames/1700.png suite à l’appel :

./fig12.gp

Reste à faire varier la valeur 1700.

Le script de l’étape

Étape 12 : Boucler pour créer toutes les frames

Il existe une syntaxe gnuplot permettant de boucler sur un bloc de code :

do for [i=0:100] {
    plot blabla
}

On pourrait alors utiliser ce que l’on a vu à l’étape précédente pour créer successivement toutes les frames de notre vidéo. Soit on met à la place de 100 le nombre de lignes de notre fichier de donnée, comme on a fait jusque là, soit on essaye cette fois-ci de s’adapter au prochain téléchargement du jeu de donnée qui contiendra plus de mois et on tente de le variabiliser. Pour connaitre le nombre de lignes dans le fichier il est possible d’utiliser la commande stats. Comme plot elle prend en arguments le nom du fichier à traiter et un u lui disant sur quelle colonne faire des stats. Bien que toutes les colonnes contiennent le même nombre de lignes (ce qui nous intéresse) il faut renseigner la seconde (les anomalies) parce que xrange et yrange ont été définies en fonction de ces valeurs. Si l’on mettait 1 (les mois) gnuplot serait gêné parce que la plupart des valeurs rencontrées (celles supérieures à 3) seraient en dehors des xrange et yrange paramétrés.

stats 'data.dat' u 2

Une fois cet appel fait un ensemble de variables contenant des statistiques sur cette colonne a été créé. Dans cet ensemble on trouve STATS_records contenant le nombre de lignes. On peut l’utiliser pour notre boucle :

do for [i=0:STATS_records] {
    outfile=sprintf('frames/%d.png',i)
    set output outfile
    plot 'data.dat' every ::0::i u (x($2+1,$1*-1+4)):(y($2+1,$1*-1+4)):2 w l lw 4.5 lc palette not
}

La variable i, allant de 0 au nombre de lignes permet de faire varier le nom du fichier à créer et le nombre de ligne que plot traite via l’appel à every.

Le script de l’étape

Étape 13 : Afficher les années et ne pas tout générer

Deux choses. Un, il n’est pas nécessaire pour notre vidéo de générer toutes les frames. La vidéo va aller vite et il serait inutile de tout générer. On peut ajouter un pas à notre boucle, lui disant d’avancer de 5 en 5 plutôt que de 1 en 1 :

res=5
do for [i=0:STATS_records:res] {

Par ailleurs pour afficher les années au centre du cercle il est possible d’enchainer un autre plot :

plot 'data.dat' every ::0::i u (x($2+1,$1*-1+4)):(y($2+1,$1*-1+4)):2 w l lw 4.5 lc palette not,\
     '' every ::i::i u (0):(0):(stringcolumn(3)) w labels tc "white" not

Pour cela il faut terminer le premier par une virgule et un backslash \. Il faut à nouveau renseigner la source de la donnée mais puisque c’est la même il est possible de mettre un '' vide. On dit ensuite que l’on ne veut afficher la valeur que pour la ligne courante grâce à un every qui commence et termine à i. Finalement on utilise le style labels en précisant que les valeurs de x et y sont 0 et la valeur du texte le contenu de la colonne 3.

Affichage de l'année au milieu du cercle
figure 15 : Affichage de l'année au milieu du cercle
script

On peut aussi afficher la progression du traitement grâce à une commande print placée dans la boucle :

print "Doing ",outfile

Le script de l’étape

Étape 14 : Paralléliser la création des frames

Créer toutes les frames prend un peu de temps. Environ 22s sur mon portable un peu puissant. Créer la frame 100 ne dépend pas de la création de la frame 50, il devrait donc être possible de les créer en parallèle dans un ordre quelconque.

Idéalemment on devrait pouvoir appeler le script gnuplot en lui disant à quelle ligne commencer et quelle ligne terminer :

./fig15 100 200

Le script ne produirait alors que les frames de 100 à 200.

Pour pouvoir faire cela on permet au script gnuplot de prendre en argument deux chiffres. gnuplot le permet facilement à l’aide des variables ARG1, ARG2 etc. Il suffit de les placer dans l’initialisation de notre boucle :

do for [i=ARG1:ARG2:res] {
    ...
}

Pour savoir comment intelligement découper le travail il nous faut un script externe. Je propose ce script awk qui, sur la base du nombre de cœurs de processeur disponible et la quantité de lignes dans le fichier à traiter, renvoie les “seaux” de lignes à traiter ensemble. Un seau par processeur :

nc="$(< /proc/cpuinfo grep -c "^processor")"
! [ "$nc" ] && nc=4
< data.dat awk -vp="$nc" '
END{
    bucket=int(NR/p)
    for(i=0;i<3;i++)
        print bucket*i+1+i,bucket*(i+1)+1+i
    print bucket*i+1+i,NR
}'

qui génère :

1 442
443 884
885 1326
1327 1764

Si l’on ne parvient pas à récupérer le nombre de cœur on utilise 4 par défaut. Plus qu’à brancher tout ça derrière un xargs pour exécuter les commandes gnuplot qui vont bien de manière parallèle :

nc="$(< /proc/cpuinfo grep -c "^processor")"
! [ "$nc" ] && nc=4
< data.dat awk -vp="$nc" '
END{
    bucket=int(NR/p)
    for(i=0;i<3;i++)
        print bucket*i+1+i,bucket*(i+1)+1+i
    print bucket*i+1+i,NR
}' | xargs -P"$nc" -n2 gnuplot -c ./figures/fig15.gp

Ainsi le script va lancer en simultané les commandes :

./figures/fig15.gp 1 442
./figures/fig15.gp 443 884
./figures/fig15.gp 885 1326
./figures/fig15.gp 1327 1764

Doublant la vitesse de génération des frames sur ma machine.

Le script de l’étape

Construire l’animation

ffmpeg est capable de prendre une liste d’images et d’en faire une vidéo. La commande la plus immédiate est :

ffmpeg -y -i 'frames/%d.png' \
          -pix_fmt yuv420p \
          -framerate 30 \
          -crf 36 video.mp4

Seulement si la numérotation des images n’est pas consécutive, et c’est notre cas puisque l’on ne génère qu’une image sur cinq, la vidéo ne contiendra qu’une seule image.

Pleins de solutions plus ou moins alambiquées s’offrent à nous.

Renuméroter les images

En dehors de gnuplot

Il est possible de traiter les frames générées et de les renuméroter. Pour cela on récupère la liste des frames :

find frames -type f

on les trie par ordre numérique et non alphabétique :

find frames -type f | sort -t'/' -nk2

et on les renomme dans un ordre consécutif :

find frames -type f |
    sort -t'/' -nk2 |
    awk '{printf "mv %s",$0;sub("[0-9]+",NR);printf " %s\n",$0}' |
    sh

Notre commande ffmpeg fonctionnera nickel.

Dans gnuplot

Alternativement il est possible dans gnuplot de créer des numéros d’image consécutifs. En incrémentant une variable à chaque fois que l’on passe dans la boucle on peut à la fois générer une seule frame sur cinq et avoir des chiffres consécutifs :

v=0
do for [i=0:STATS_records:res] {
    v=v+1
    outfile=sprintf('frames/%d.png',v)
    set output outfile
    print "Doing ",outfile
    plot 'data.dat' every ::0::i u (x($2+1,$1*-1+4)):(y($2+1,$1*-1+4)):2 w l lw 4.5 lc palette not,\
         '' every ::i::i u (0):(0):(stringcolumn(3)) w labels not
}

Malheureusement cette approche ne fonctionne pas avec celle de paralléliser la création des frames.

Créer une liste arbitraire

On a vu comment adapter les noms des fichiers aux besoisn d’ffmpeg mais il est possible d’adapter ffmpeg aux noms de nos fichiers. ffmpeg peut prendre en option une liste d’images dans un fichier texte et produire la vidéo en concaténant toutes les images. Cela permet d’être totalement indépendant·e sur la nature, le tri et les noms des images.

Dans notre cas on peut produire cette liste avec la commande suivante :

find frames -type f -name '*.png' |
    sort -t'/' -n -k2 |
    xargs printf "file %s\n" > list.txt

La liste a le format :

file frames/0.png
file frames/5.png
file frames/10.png
file frames/15.png
file frames/20.png

Non seulement on s’assure que le tri est celui qu’on souhaite mais pas besoin que les chiffres soient consécutifs non plus.

La commande ffmpeg adaptée est :

ffmpeg -f concat \
       -r 30 \
       -i list.txt \
       -pix_fmt yuv420p \
       -crf 36 video.mp4 2>/dev/null

  1. valide sur debian 

  2. calcul que l’on aurait pu faire en dehors de gnuplot en awk par exemple mais ça nous aurait empêcher d’apprendre des trucs à propos de gnuplot 

  3. et aussi les “blocs” de données mais ça nous concerne pas ici 

  4. ce qui fait entièrement sens mais ce n’est pas ce que l’on veut ! 

  5. qui décale en arrière parce qu’on a tout multiplié par -1 

  6. le 1762 dans le dernier bout de code juste au dessus 

  7. sans ça je peux pas la lire sur mon téléphone par exemple