Série temporelle

tutorial de programmation
en processing

time tunnel
  The Time tunnel, Serie TV anglais ~1960

Série temporelle

Au premier cours, nous avons représenter le temps sous différents formes. Les séries temporelles sont des représentations des événements divers dans une périodes de temps donnée. En statistique, en traitement de signal et au mathématique financière, le séries temporelles sont des suites des valeurs numériques représentant son évolution dans des intervalles de temps. Des exemples typiques sont l'évolution du Dow Jones, la population d'une pays dans un périodes données ou même la fréquence cardiaques.

 

Ressource

Insee - France en fait et chiffres

Cia - The World Factbook

meteo-world.com

 

Le code du projet

Le projet suivant est un code simplifié, venant de Data visualization, de Ben Fry, 2008. Les données vient du site de l'Insee qui le propose sous forme d'un fichiers xml. Nous l'avons préalablement changé en fichier .tsv en l'enregistrant en txt (choisi Tab comme valeur de séparation) et ensuite la terminaison a été modifié en tsv.

Le but finale est de représenter l'évolution des dépenses de l'enseignement en premier, deuxième degré et dans le supérieur. Au lieu d'afficher les différents évolutions sur la même page, nous allons créer des onglets pour pouvoir consulter chaque courbe séparée.

premier dégrée second dégrée
Dépense pour l'enseignement du premier degré Dépense pour l'enseignement du premier degré

 

class table

Il y a quelques petits modification de la class table. Nous avons besoin les noms et nombres des colonnes pour créer les onglets. Pour ce faires nous procédons comme d'habitude, mais pour exclure le premier cellule, nous utilisons la méthode subset() qui extrait des String dans une array des String à partir de son deuxième argument, qui est dans notre cas 1. Donc toute les noms des colonnes seront enregistrer dans la liste de ColumnNames et la variable de columnCount enregistre les nombre des colonnes.

String[] columns = split(rows[0], TAB);// enregistre les noms de colonnes
columnNames = subset(columns, 1); // ignore la cellule en haut à gauche
columnCount = columnNames.length

Il y a encore une méthode qui vérifie que les données sont correctes, ce qui est utile dans un tableau qui comporte beaucoup des données (ce qui n'est pas notre cas). La méthode Float.isNaN vérifie si la donnée est un chiffre. NaN signifie is not a number. Étant donnée qu'il y a une ! devant la méthode, elle vérifie si ce n'est pas n'est pas un chiffre, alors si c'est un chiffre.

boolean isValid(int row, int col) {
if (row < 0) return false;
if (row >= rowCount) return false;
if (col >= data[row].length) return false;
if (col < 0) return false;
return !Float.isNaN(data[row][col]);
}

 

Le champ de représentation

Dans un premier temps, nous allons juste afficher le background avec les différents repères de notre série temporelle. Pour un raison de comodité, nous allons utiliser la méthode rectMode(CORNERS); qui prend comme référence le point en haut à gauche et en bas a droite.

background_timeSeries

Pour la suite de notre projet on aura besoin seulement la première partie du code. Néanmoins, regardez la ligne text("coinGX\ncoinGY", coinGX+5, coinGY); Le slash [alt gr+ 8] et le n (= new line) effectue une retour à la ligne.

float coinGX, coinGY;
float coinDX, coinDY;

PFont font;

void setup() {
size(720, 405);
font = createFont("Arial", 14);
textFont (font);

// coin en haut à gauche et en bas à droite
coinGX = 30;
coinDX = width - coinGX;
coinGY = 40;
coinDY = height - coinGY;
smooth();
}

void draw() {
background(224);

// champs des données
fill(255);
rectMode(CORNERS);
noStroke();
rect(coinGX, coinGY, coinDX, coinDY);

// ===== pour afficher les répères ========
// dessine les répères
stroke(0);
strokeWeight(5);
point(coinGX, coinGY);
// /n = nouveau ligne
fill(0);
textAlign(LEFT);
text("coinGX\ncoinGY", coinGX+5, coinGY);
point(coinDX, coinDY);
textAlign(RIGHT);
text("coinDX\ncoinDY", coinDX, coinDY);
}

Afficher les données

Dans un première temps, nous allons créer une methode pour afficher les données en forme des points. La méthode nous permet de choisir la colonne en question à travers son argument void drawDataPoints(int col) {....) Le reste de la méthode est +/- connu:

void drawDataPoints(int col) {
for (int row = 0; row < rowCount; row++) {
float value = data.getFloat(row, col);
float x = map(years[row], yearMin, yearMax, coinGX, coinDX);
float y = map(value, dataMin, dataMax, coinDY, coinGY);
strokeWeight(5);
stroke(#5679C1);
point(x, y);
}
}

Afficher les repères

Pour pouvoir comprendre les données, il manque des repères pour le temps et les valeurs. Il y a deux méthodes qui effectuons cela:

Dans la méthode drawYearLabels() nous allons afficher les années. Il n'y a pas des sens d'afficher chaque année de notre tableau, la visualization sera confus. Il est plus claire d'afficher seulement tous les 5 ans. Pour cela nous utilisons le modulo combiné avec une condition. Si l'année divisé par 5 à un reste = 0, l'année sera afficher, sinon ignorer.

void drawYearLabels() {
fill(0);
textSize(10);
textAlign(CENTER, TOP);
stroke(224);
strokeWeight(1);

for (int row = 0; row < rowCount; row++) {
if (years[row] % yearInterval == 0) {
// yearInterval = 5 (dans le setup)
float x = map(years[row], yearMin, yearMax, coinGX, coinDX);
text(years[row], x, coinDY + 5);
} } }

La méthode void drawDepenseLabels() {...} fonctionne d'une manière similaire, mais elle utilise la méthode ceil() afin d'arrondir les dépenses vers l'int l'inférieur le plus proche. 10.6 devient 10 dans notre cas 10000.00 devient 10000.

void drawDepenseLabels() {
fill(0);
textSize(10);
textAlign(RIGHT, BOTTOM);
stroke(224);
for (float i = dataMin; i < dataMax; i+=depenseInterval) {
float y = map(i, dataMin, dataMax, coinDY, coinGY);
text(ceil(i), coinGY -10, y);
}
}

Et enfin nous allons écrire le 'nom de l'onglet en haut à gauche. Pour pouvoir choisir la colonne, il y a un variable int currentColumn qui nous permet de choisir la colonne à afficher.

void drawTitle() {
fill(0);
textSize(20);
textAlign(LEFT);
String title = data.getColumnName(currentColumn);
text(title, coinGX, coinGY - 10);
}

Voici le code en zip


Les onglets

La méthode la plus compliqué est probablement celui qui dessine les onglets. Elle va calculer les coordonnées des onglets à partir de son nom. La méthode textAscent() nous donne l'hauteur de la police utiliser et textWidth() la largeur du texte. A toute les deux, on rajoute un marge. Le variable runningX suit l'endroit (l'extrémité droite de l'onglet) où le programme est actuellement. La méthode est bien commenté.

void drawTitleTabs() {
rectMode(CORNERS);
noStroke();
textSize(20);
textAlign(LEFT);

//lors de la premier utilisation de la méthode, allouer la mémoire pour
// les listes qui stockeront les valeurs de gauche et droite des tabs (=onglets)
if (tabLeft == null) {
tabLeft = new float[columnCount];
tabRight = new float[columnCount];
}

//commence avec le tab à gauche, runningX va sauter à la fin de la méthode au bord droite du tab
float runningX = coinGX;
// calcule haut et bas du tab
tabTop = coinGY - textAscent() - 15;
tabBottom = coinGY;

// dessine un tab après l'autre
for (int col = 0; col < columnCount; col++) {
String title = data.getColumnName(col);
// stocke le nom de la colonne dans une variable locale
tabLeft[col] = runningX;
// la position gauche de l'actuelle colonne
float titleWidth = textWidth(title);
// calcule la position droite du tab
tabRight[col] = tabLeft[col] + tabPad + titleWidth + tabPad;

// si la colonne est sélectionné (cf mousePressed) rempli blanc sinon gris pour le fond et noir ou gris pour le font
if (col == currentColumn)fill( 255);
else fill (224);
rect(tabLeft[col], tabTop, tabRight[col], tabBottom);
if (col == currentColumn) fill(50);
else fill(124);
text(title, runningX + tabPad, coinGY - 10);
// une fois le premier tab est fait, on change la position du runningX
runningX = tabRight[col];
}
}

L'interaction

La position de gauche et droite et haut et bas de chaque onglet est connu maintenant. Étant donnée que le haut et bas est identiqué pour chaque onglet, il suffit de vérifier la position de la souris relative à la position y des onglets une fois pour tous les onglet. Pour la position x, nous utilisons la tabLeft et tabRight dans un boucle qui regarde chaque onglet séparément.

void mousePressed() {
if (mouseY > tabTop && mouseY < tabBottom) {
for (int col = 0; col < columnCount; col++) {
if (mouseX > tabLeft[col] && mouseX < tabRight[col]) {
currentColumn = col;
} } } }

Voici le code en zip

 

Exercice

volume_timeSeries

 

 

retour index