Visualisierung von länderbezogenen Daten


Mich interessieren Daten. Oft kann man Daten in Tabellenform aber schwer fassen. Deshalb habe ich versucht Daten die österreichischen Bundesländer zu visualisieren.

D.h. konkret möchte ich die Arbeitslosenquote der Bundesländer farblich als Österreichkarte darstellen. Dafür brauchen wir als erstes eine Karte Österreichs als *.svg Datei. Die ist schnell auf Wikipedia gefunden: Map of Austria.

Vorbereiten der Vektorgrafik

Ich habe diese Karte zwecks einfacherer Analyse in Processing leicht modifiziert. Das ist sehr einfach, da es sich bei .svg Dateien eigentlich nur um XML Dateien handelt. Man kann sie einfach in jedem Texteditor öffnen. Unter Linux geschieht das dann auch noch gleich mit Code-Highlighting. Folgende Tags wurden von mir einfach aus der Datei entfernt.

<title>Austria</title>
 <desc>Political map of Austria</desc>
 <filter id="f1">
 <feGaussianBlur in="SourceGraphic" stdDeviation="10"/>
 </filter>
 <path id="Shadow" filter="url(#f1)" ..........................................>

Modifizierte Datei:

Data Mining

Das Aufspüren und Aufbereiten der Daten ist in diesem Fall sehr einfach. Die Statistik Austria bietet sie in verschiedenen Formen an. Ich beschränke mich vorerst auf die Quote der Bundesländer im Jahr 2010 und kopiere die ensprechende Tabelle.

Die erste Visualisierung

Die .svg Datei Österreichs wird geladen und dann in die einzelenen Pfade der Bundesländer zerlegt. Die Daten der Arbeitslosenzahlen werden in einem Array in einer Reihe, den Pfaden der Bundesländern entsprechend abgelegt. Dann wird jeder Pfad gezeichnet und entsprechend der ArbeitslosenQuote eingefärbt.

Starte Applet

// PShape Objekte erzeugen
PShape austria;
PShape[] bundesl;

//Titel
String title="Arbeitslosenqote Bundesländer 2010";

// Daten von Statistik Austria
float[] arblQuote = {
3.9, //Burgenland
3.9, //Carinthia
3.6, //Lower_Austria
3.7, //Upper_Austria
2.9, //Salzburg
4.2, //Styria
2.8, //Tyrol
3.9, //Vorarlberg
7.3  //Vienna
};

//Grundfarbe der Visualisierung
int col=210;

void setup() {
size(900, 500);

//Vektorgrafik laden
austria = loadShape("Map_of_Austria.svg");

//Pfade der einzelnen Bundesländer extrahieren
bundesl = austria.getChildren();

//Formatierung des Originals deaktivieren
austria.disableStyle();
noLoop();
colorMode(HSB, 360, 100, 100);
textAlign(CENTER, CENTER);
}

void draw() {
background(0, 0, 100);
stroke(col, 20, 99);

//Zeichnen der Bundesländer
for (int i=0; i<bundesl.length; i++) {
//Füllfarbe festlegen
fill(col, arblQuote[i]*13, 100);
//Zeichnen der Pfade
shape(bundesl[i], 0, 0);
println(bundesl[i].getName());
}
//Titel
textSize(20);
text(title, width/4,height*1/20);
}

Legende:

Eine Legende wäre jetzt natürlich noch nett. Dafür sollte man natürlich auch noch den Farbbereich automatisch an den Wertbereich der Daten anpassen.

starte Applet

/* @pjs preload="Map_of_Austria.svg"; */

// PShape Objekte erzeugen
PShape austria;
PShape[] bundesl;

//Titel and Source
String title="Arbeitslosenqote Bundesländer 2010";
String source=" Quelle: Statistik Austria";

// Daten von Statistik Austria
float[] arblQuote = {
3.9, //Burgenland
3.9, //Carinthia
3.6, //Lower_Austria
3.7, //Upper_Austria
2.9, //Salzburg
4.2, //Styria
2.8, //Tyrol
3.9, //Vorarlberg
7.3  //Vienna
};

//Grundfarbe der Visualisierung
int col=210;

void setup() {
size(900, 500);

//Vektorgrafik laden
austria = loadShape("Map_of_Austria.svg");

//Pfade der einzelnen Bundesländer extrahieren
bundesl = austria.getChildren();

//Formatierung des Originals deaktivieren
austria.disableStyle();
noLoop();
colorMode(HSB, 360, 100, 100);
textAlign(CENTER, CENTER);
rectMode(CENTER);
}

void draw() {
background(0, 0, 100);
stroke(col, 20, 99);

// Koordinatensystem speichern
pushMatrix();
translate(0, 30);

//Zeichnen der Bundesländer
for (int i=0; i<bundesl.length; i++) {
//Füllfarbe festlegen
fill(col, map(arblQuote[i],getMinValue(arblQuote),getMaxValue(arblQuote),20,100), 100);
//Zeichnen der Pfade
shape(bundesl[i], 0, 0);
println(bundesl[i].getName());
}
//Koordinatensystem repositionieren
popMatrix();

//Legende zeichnen
legende(10, 50, 2.8, 7.3);

text(source, width/2, height*19/20);
//Titel
textSize(20);
text(title, width/4, height*1/20);
}

void legende(int x, int y, float valMin, float valMax) {
pushMatrix();
translate(x+10, y+10);
fill(0, 0, 0);
text("Legende:", 30, 0);
translate(0, 25);

//Min und Max Werte für die Beschriftung finden
int minVal=floor(valMin);
int maxVal=ceil(valMax);
float step= (float) (maxVal-minVal)/5;

//Farbfelder und Beschriftungen zeichnen
for (int i =0; i<6; i++) {
fill(col, map(minVal+step*i,getMinValue(arblQuote),getMaxValue(arblQuote),20,100), 100);
rect (0, i*20, 20, 10);
String legValue = " %";
fill(0, 0, 0);
text( (float)round(((float) minVal+step*i) * 100 )/100+" %", 30, i*20);
}
popMatrix();
}

float getMaxValue ( float[] vals) {
float [] values= new float [vals.length];
arrayCopy(vals,values);
Arrays.sort(values);
return values[values.length-1];

float getMinValue ( float[] vals) {
float [] values= new float [vals.length];
arrayCopy(vals,values);
Arrays.sort(values);
return values[0];
}

Beschriftung:

Damit das Ganze einen echten Mehrwert gegenüber der konventionellen Erstellung in einem Grafikprogramm bietet, sollte das Programm die Grafik automatisch beschriften. Dafür müssen wir die in der original-.svg Datei englischsprachigen Titel durch deutschsprachige ersetzen.

Die Quoten und Namen der Bundesländer sollen in der Mitte der jeweiligen Pfade platziert werden. Dafür müssen wir die minimalen und maximalen x und y Koordinaten aller Punkte eines Pfades ermitteln. Dann zeichnen wir genau in die Mitte.

Starte Applet

Änderungen zum Beispiel vorher:

  1. Hintergrundfarbe geändert, damit die Beschriftung besser lesbar wird.
    background(0, 0, 80);
    
  2. In draw() werden die Koordinaten der Beschriftungstexte ermittelt und dann gezeichnet.
    <pre>//Zeichnen der Quoten
    fill(360,0,100);
    float [] coords = new float [2];
    coords= getCoords(bundesl[i].getChild(0));
    textSize(18);
    text(  (float)round(((float) arblQuote[i]) * 100 )/100+" %", coords[0],coords[1]);
    
  3. Funktion zur Ermittlung der Koordinaten.
    float [] getCoords (PShape thisShape) {
    
    float xMax=0;
    float yMax=0;
    float xMin=100000;
    float yMin=100000;
    float [] coords = new float [2];
    
    //Ermitteln der maximalen und minimalen Pfad-Koordinaten
    for (int i =0; i<thisShape.getVertexCount(); i++) {
    if (thisShape.getVertexX(i)>xMax)  xMax=thisShape.getVertexX(i);
    if (thisShape.getVertexY(i)>yMax)  yMax=thisShape.getVertexY(i);
    if (thisShape.getVertexX(i)<xMin)  xMin=thisShape.getVertexX(i);
    if (thisShape.getVertexY(i)<yMin)  yMin=thisShape.getVertexY(i);
    }
    coords[0]=(xMin+xMax)/2;
    coords[1]=(yMin+yMax)/2;
    return coords;
    }
    

Interaktivität:

starte Applet

    1. Erweitern der Datenbasis
      Damit in unserer Anwendung nun auch Entwicklungen sichtbar werden, verwende ich nicht nur die Daten aus dem Jahr 2010, sondern eine ganze Tabelle mit den Daten der Jahre 1995-2010, wie sie von Statistik Austriaveröffentlicht werden. Damit diese von Processing auf einfache Weise automatisch eingelesen werden können, habe ich eine .csv Tabelle der folgender Struktur erstellt:

      • unnötige Spalten und Zeilen gelöscht
      • die , durch . ersetzt
      • die Bundesländer-Beschriftung von links nach rechts kopiert
      • im Sketch Ordner als list.csv gespeichert

      Download: list.csv

      1995;1996;1997;1998;1999;2000;2001;2002;2003;2004;2005;2006;2007;2008;2009;2010;
      3.8;4.2;4.3;3.4;3.6;3.2;4.1;4.3;4.2;5.6;6;5;3.7;3.6;4.6;3.9;Burgenland
      3;2.9;3.4;3.5;3.5;3.1;3.2;2.7;3.4;4.6;4.8;4.4;3.9;3.4;4.2;3.9;Kärnten
      3.4;3.8;3.8;4.4;3.3;3;3.2;3.6;3.5;4.2;4.3;4;3.6;3.4;4.3;3.6;Niederösterreich
      3.2;3.7;3.5;3.5;3.5;3.1;2.9;3.1;3.3;3.7;4;3.2;3.2;2.6;4;3.7;Oberösterreich
      2.7;3.2;3.2;3.4;2.7;2.3;1.9;2.8;2.2;3.7;3.2;3.1;3;2.5;3.2;2.9;Salzburg
      3.5;4.1;4;3.8;3.2;3.2;3.7;3.8;4;3.7;4.1;3.9;3.7;3.4;4.6;4.2;Steiermark
      2.9;3.2;3.4;2.9;2.5;2.5;2.3;2;2.6;3.3;3.5;2.9;2.8;2.4;2.9;2.8;Tirol
      3.3;3.9;3.9;3.6;3.5;2.4;2.4;2.5;4.1;4.1;5.3;4.4;3.6;3.9;4.9;3.9;Vorarlberg
      5.3;5.9;6.3;6.3;5.7;5.7;5.8;7.3;7.8;8.9;9.1;8.8;8.3;6.7;7.5;7.3;Wien

    2. Automatisches Einlesen der Daten aus einer .csv -Datei
      Hier habe ich als Ausgansbasis ein Skript von che-wei wang verwendet: http://cwwang.com/2008/02/23/csv-import-for-processing/. Mit ein paar kleinen  Modifikationen liest es auch in unserem Programm die .csv-Datei ein. Und speichert die Daten in einem 2-dimenstionalen Array.
    3. Ermitteln der minimalen und maximalen Werte
      Das ist jetzt nicht mehr ganz so einfach, wie vorher, aber kein allzu großes Problem, das in setup() erledigt wird:

      //Ermitteln der minimalen und maximalen Werte für die Farbwahl
      for (int j=0; j<csv[0].length;j++) {
      for (int i=0; i<arblQuote.length;i++) {
      arblQuote[i]=parseFloat(csv[i+1][j]);
      }
      if (getMinValue(arblQuote)<minVal) minVal=getMinValue(arblQuote);
      if (getMaxValue(arblQuote)>maxVal) maxVal=getMaxValue(arblQuote);
      }
      

      Damit werden dann in weiterer Folge unsere Werte mit konstanten Farbwerten über alle Jahre hinweg beschriftet.

    4. Übertragen der Jahreszahlen und des aktuell gewählten Jahres in entsprechende Variablen
      //Auslesen der Jahre
      years=new int[csv[0].length-1];
      for (int i=0; i< csv[0].length-1;i++) {
      years[i]=parseInt(csv[0][i]);
      }
      selYear=0;
      
    5. In draw(): Daten des aktuellen Jahres übertragen.
      //Daten des aktiven Jahres laden
      for (int i=0; i<arblQuote.length;i++) {
      arblQuote[i]=parseFloat(csv[i+1][selYear]);
      }
      
    6. Für jedes Bundesland den Namen anzeigen, wenn sich die Maus darüber befindet. (Funktioniert beim Bundesland Tirol nur bedingt!)
      //Namen anzeigen
      if (bundesl[i].getChild(0).contains(mouseX, mouseY)) {
      fill(0, 0, 40);
      textSize(14);
      text(bundesl[i].getName(), coords[0], coords[1]+20);
      }
      
    7. Interface, für die Auswahl der Jahre
      int yearSelector (int [] years) {
      
      noFill();
      pushMatrix();
      strokeWeight(1.5);
      
      //Linie zeichnen
      stroke(0, 70, 90);
      line(width-5, 0, width-5, height);
      strokeWeight(1);
      
      //Beschriftung
      translate(width-50, -10);
      for (int i=0; i<years.length; i++) {
      translate(0, height/years.length);
      
      //Hervorheben des gewählten Jahres
      if (years[i]==years[selYear]) {
      fill(0, 70, 90);
      triangle(35, 0, 45, -10, 45, 10);
      textSize(24);
      fill(360);
      }
      else {
      textSize(12);
      fill(0, 0, 60);
      }
      text(years[i], 0, 0);
      }
      popMatrix();
      return selYear ;
      }
      
    8. Mausaktivität abrufen um damit das gewünschte Jahr zu wählen
      //Mausposition auswerten
      void mouseMoved () {
      if (mouseX > width-60 && mouseX<width) {
      selYear=(int) map(mouseY, 0, height, 0, years.length);
      }
      }
      

Und diverse kleinere Modifikationen, z.B.: die Anpassung der Textpositionen an die Länge des Textinhalts usw. Den gesamten Quellcode findest du im Applet.


					
Advertisements

6 Kommentare

  1. Flo

    Achtung: in der Zeile
    for (int i=0; i<bundesl.length; i++) {
    muss es jeweils richtigerweise
    for (int i=0; i<bundesl.length-1; i++) {
    heissen, da ansonsten ein outofbonderror passiert.

    • Thomas Koberger

      nein, es gibt 9 bundesländer. also ist .length = 9. das letzte bundesland hat den index 8. da bei i = 9 aber die schleife nicht mehr durchlaufen wird, sollte es funktionieren, wie es ist.

  2. Gerhard Breuß

    Hallo,
    tolle Arbeit. Habe deine Arbeitsschritte nachvollzogen und die jeweiligen .pde ausprobiert. Leider läuft das letzte nicht.

    Exception in thread „Animation Thread“ java.lang.ArrayIndexOutOfBoundsException: 1
    at sketch_jun03d.setup(sketch_jun03d.java:117)
    at processing.core.PApplet.handleDraw(Unknown Source)
    at processing.core.PApplet.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:662)

    Liegt der Fehler in der list.csv?

    1995;1996;1997;1998;1999;2000;2001;2002;2003;2004;2005;2006;2007;2008;2009;2010; 3,8;4,2;4,3;3,4;3,6;3,2;4,1;4,3;4,2;5,6;6;5;3,7;3,6;4,6;3,9;Burgenland
    3,4;3,8;3,8;4,4;3,3;3;3,2;3,6;3,5;4,2;4,3;4;3,6;3,4;4,3;3,6;Niederoesterreich 3;2,9;3,4;3,5;3,5;3,1;3,2;2,7;3,4;4,6;4,8;4,4;3,9;3,4;4,2;3,9;Kaernten 3,2;3,7;3,5;3,5;3,5;3,1;2,9;3,1;3,3;3,7;4;3,2;3,2;2,6;4;3,7;Oberoesterreich 2,7;3,2;3,2;3,4;2,7;2,3;1,9;2,8;2,2;3,7;3,2;3,1;3;2,5;3,2;2,9;Salzburg 3,5;4,1;4;3,8;3,2;3,2;3,7;3,8;4;3,7;4,1;3,9;3,7;3,4;4,6;4,2;Steiermark 2,9;3,2;3,4;2,9;2,5;2,5;2,3;2;2,6;3,3;3,5;2,9;2,8;2,4;2,9;2,8;Tirol
    3;3,9;3,9;3,6;3,5;2,4;2,4;2,5;4,1;4,1;5,3;4,4;3,6;3,9;4,9;3,9;Vorarlberg 5,3;5,9;6,3;6,3;5,7;5,7;5,8;7,3;7,8;8,9;9,1;8,8;8,3;6,7;7,5;7,3;Wien

  3. Gerhard Breuß

    Nachtrag: Der Fehler wird in der letzten Zeile dieses Codeblocks angezeigt.

    //create csv array based on # of rows and columns in csv file
    csv = new String [lines.length][csvWidth];

    //parse values into 2d array
    for (int i=0; i < lines.length; i++) {
    String [] temp = new String [lines.length];
    temp= split(lines[i], ';');
    for (int j=0; j < temp.length; j++) {
    csv[i][j]=temp[j];

    • Thomas Koberger

      habe die origninal csv -datei als link eingefügt. bitte versuchen sie es mit dieser.

      • Gerhard Breuß

        Danke, Thomas!
        Das war prompt und funktioniert jetzt. Es lag tatsächlich an der .csv.
        LG Gerhard

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: