Archivi tag: Grafico a linee

Il problema dei grafici a linee curve

Dopo aver realizzato il nostro grafico a linee, potremmo pensare di utilizzare delle linee curve. Ma, come vedremo in questo articolo, ci troveremo ad affrontare una serie di problemi.

Dalle linee dritte a linee curve

Ripartendo dal codice nell’articolo precedente e cambiato le seguenti righe:

vertex(x1, height - 50 + tempMin[i] * - 20);
vertex(x1, height - 50 + tempMax[i] * - 20);

utilizzando curveVertex() al posto di vertex() otteniamo questo risultato:

grafico linee curve utilizzando curveVertex

Notiamo subito che mancano dei collegamenti con il primo e l’ultimo punto della serie di dati. Come indicato nel reference, questa funzione utilizza il primo e l’ultimo punto vengono usati per guidare la curvatura della linea.

A questo punto dobbiamo arbitrariamente aggiungere due punti e, in questo caso, conviene farlo prima e dopo il ciclo for. Qui sorge il secondo problema: che coordinate diamo a questi due punti che non derivano dal nostro dataset?

La soluzione più ovvia è utilizzare le coordinate del primo e dell’ultimo punto:

// Temperature minime
  int x1 = 68;
  beginShape();
  curveVertex(x1, height - 50 + tempMin[0] * - 20); 
  for (int i = 0; i < tempMin.length; i++) {
    fill(100, 190, 255);
    curveVertex(x1, height - 50 + tempMin[i] * - 20);
    noStroke();
    ellipse(x1, height - 50 + tempMin[i] * - 20, 10, 10);
    x1 += 40;
    stroke(100, 190, 255);
    noFill();
  }
  curveVertex(x1, height - 50 + tempMin[tempMin.length - 1] * - 20); 
  endShape();

  // Temperature massime
  x1 = 68;
  beginShape();
  curveVertex(x1, height - 50 + tempMax[0] * - 20); 
  for (int i = 0; i < tempMax.length; i++) {
    fill(255, 100, 100);
    curveVertex(x1, height - 50 + tempMax[i] * - 20);
    noStroke();
    ellipse(x1, height - 50 + tempMax[i] * - 20, 10, 10);
    x1 += 40;
    stroke(255, 100, 100);
    noFill();
  }
  curveVertex(x1, height - 50 + tempMax[tempMax.length - 1] * - 20); 
  endShape();

Se lanciamo il programma, il nostro grafico a linee curve diventa così:

Grafico a linee curve in Processing

Il risultato potrebbe sembrare soddisfacente ma un occhio esperto si potrebbe accorgere di un altro problema: la curvatura delle linee intorno ai punti dei dati può confonderci e portarci in errore quando leggiamo il grafico.

L’errore di precisione

Questo set di dati ha dei data point molto vicini tra loro e, quindi, il fenomeno non si vede chiaramente.

Ho realizzato quest’altra immagine per mostrarlo meglio: questa volta i punti sono inseriti in un array in modo casuale ma il principio è lo stesso di quello utilizzato nello sketch che stiamo realizzando.

Grafico a linee curve con errore

È evidente l’errore che esiste tra i primi due punti e gli ultimi due: la linea curva va oltre i valori in modo anomalo e portando a un’interpretazione errata del grafico.

Come trovare la soluzione definita al nostro problema? Abbiamo due possibilità:

  1. Rinunciare alle linee curve una volta per tutte
  2. Non usare la funzione curveVertex() ma usare le curve di Bézier e, quindi, la funzione bezierVertex().

La difficoltà dell’usare queste curve è di dover passare alla funzione tre coordinate x e y: due per i punti di controllo e una per il punto di ancoraggio. Se volete capire come funzionano, consiglio di provare a giocare al The Bézier Game (io ho rinunciato dopo poco).

Grafico a linee

Partiamo sempre dall’esempio su cui stiamo lavorando da un po’ di tempo a questa parte e creiamo un grafico a linee in Processing.

L’unica modifica al codice che dobbiamo fare è quella relativa alla grafica: lasciamo, dunque, inalterata la prima parte, quella relativa alla lettura dei dati dal file .csv e l’inserimento degli stessi in un array e passiamo direttamente alla grafica.

Ripensare la posizione della x

La prima differenza rispetto al grafico a barre sarà determinare correttamente la posizione x del valore estratto dall’array. Se, infatti, il ciascuna barra del grafico aveva una sua larghezza, ora dobbiamo rappresentare solo un punto.

Per risolvere il problema, pur mantenendo il risultato finale che avevamo realizzato nell’ultimo post, creo una nuova variabile chiamata x1 che mi servirà come nuovo riferimento e aggiungo un nuovo ciclo for solo per disegnare i punti di riferimento nel grafico.


int x1 = 68;
  
for(int i = 0; i < tempMin.length; i++) {
  ellipse(x1, height - 50 + tempMin[i] * - 20, 10, 10);
  ellipse(x1, height - 50 + tempMax[i] * - 20, 10, 10);
  x1 += 40;
}

Ecco il risultato:

Grafico a linee in Processing

Creiamo il grafico a linee

Creare un grafico a linee potrebbe essere più complesso di quanto ipotizzato: dovremmo, infatti, creare manualmente una linea per congiungere tra loro i punti disegnati in precedenza.

Per fortuna ci vengono in aiuto alcune funzione di Processing: beginShape()vertex() ed endShape().

Con beginShape() diciamo al programma di iniziare a creare una forma, aggiungiamo tutti i vertici che la compongono con vertex() e, una volta richiamata la funzione endShape(), Processing si preoccuperà di collegare insieme tutti i punti.

Come al solito, consiglio di verificare sul reference i parametri opzionali che possiamo usare per ciascuna funzione. Per questo esempio il comportamento di default è quello che fa al caso nostro ma in futuro potrebbero tornarvi utili anche le altre possibilità.

Dovendo creare due grafici a barre, dovremo utilizzare due cicli for:


int x1 = 68;

beginShape();
for (int i = 0; i < tempMin.length; i++) {
  vertex(x1, height - 50 + tempMin[i] * - 20);
  ellipse(x1, height - 50 + tempMin[i] * - 20, 10, 10);
  x1 += 40;
}
endShape();

x1 = 68;
beginShape();
for (int i = 0; i < tempMax.length; i++) {
  vertex(x1, height - 50 + tempMax[i] * - 20);
  ellipse(x1, height - 50 + tempMax[i] * - 20, 10, 10);
  x1 += 40;
}
endShape();

Grafico a linee beginShape, vertex, endShape

A questo punto possiamo eliminare i grafici a barre che avevamo tenuto come riferimento e andare a sistemare i colori per ottenere un risultato finale soddisfacente:

Grafico a linee finale in Processing

Codice completo

Guardando il codice vi sembrerà un po’ verboso, in particolare per quanto riguarda le assegnazioni dei colori. Facendo qualche esperimento vi renderete conto di quanto sia importante inserire le istruzioni giuste per evitare problemi di visualizzazione.


/*
 * Grafico a linee
 * Federico Pepe, 20.05.2018
 * http://blog.federicopepe.com/processing
 */

Table csv;

float tempMin[], tempMax[];

void setup() {
  size(500, 500);
  background(255);
  noStroke();
  csv = loadTable("data.csv", "header");

  println("Numero righe: " + csv.getRowCount());
  println("Numero colonne: " + csv.getColumnCount());

  tempMin = new float[0];
  tempMax = new float[0];

  for (int i = 1; i < csv.getColumnCount(); i++) {
    tempMin = append(tempMin, csv.getFloat(0, i));
    tempMax = append(tempMax, csv.getFloat(3, i));
  }

  printArray(tempMin);

  println("Il valore minimo è: " + min(tempMin));
  println("Il valore massimo è: " + max(tempMin));

  noLoop();

  // Scritte relative all'anno
  int x = 50;
  int year = 2008;
  
  for (int i = 0; i < tempMin.length; i++) {
    fill(0, 127);
    text(year, x + 2, height - 30);
    x += 40;
    year++;
  }
  
  // Temperature minime
  int x1 = 68;
  beginShape();
  for (int i = 0; i < tempMin.length; i++) {
    fill(100, 190, 255);
    vertex(x1, height - 50 + tempMin[i] * - 20);
    noStroke();
    ellipse(x1, height - 50 + tempMin[i] * - 20, 10, 10);
    x1 += 40;
    stroke(100, 190, 255);
    noFill();
  }
  endShape();
  
  // Temperature massime
  x1 = 68;
  beginShape();
  for (int i = 0; i < tempMax.length; i++) {
    fill(255, 100, 100);
    vertex(x1, height - 50 + tempMax[i] * - 20);
    noStroke();
    ellipse(x1, height - 50 + tempMax[i] * - 20, 10, 10);
    x1 += 40;
    stroke(255, 100, 100);
    noFill();
  }
  endShape();

  // Griglia di riferimento
  textAlign(RIGHT, CENTER);
  for (int j = 0; j <= 20; j++) {
    if (j % 5 == 0) {
      fill(0, 127);
      text(j + "°", 25, height - 52 + (j * - 20));
      stroke(0, 30);
    } else {
      stroke(0, 15);
    }
    line(30, height - 50 + (j * - 20), 470, height - 50 + (j * - 20));
  }
}

void draw() {
}