Text-Visualisierung

In der heutigen Vorlesung geht es um das Transformieren von Text in neue visuelle Formen.

Als Beispiel kann man sich die Arbeiten von Boris Müller und Jer Thorp anschauen, deren Projekte auf ähnlichen Grundideen basieren.

Die Arbeitsschritte für ein erstes Beispiel lassen sich in fünf Bausteine gliedern. Zuerst muss man einen Text auswählen, danach wird dieser geladen. In der Folge wird der geladene Text aufbereitet und verarbeitet, bevor er im letzten Schritt in eine neue visuelle Form gebracht wird.

Schriften in Processing

Für den ersten Arbeitsschritt ist ein Blick auf die Schriftnutzung von Processing empfehlenswert. Um Text in einer individuellen Schrift anzuzeigen, muss man über das Menü Tools | Create Font den gewünschten Font als VLW-Datei erstellen. Dabei muss man die gewünschte Schriftgröße angeben, da Processing die einzelnen Zeichen als Bild zur Verfügung stellt.

Um die Schrift zu laden, gibt es den Befehl loadFont( „myFont-size.vlw“ ). Der Nachteil an dieser Erstellung der Fonts ist, dass es zu Darstellungsproblemen führen kann, wenn man die Arbeit als PDF exportiert.

Umgehen kann man das Problem, wenn man sich per PFont.list() die vorhandenen Schriften auf dem System ausgeben lässt und sich einen Namen davon kopiert. Dann nutzt man den Befehl createFont(„myNewFont“, 14 ), gibt als ersten Parameter den Namen der Schrift an und als zweiten Parameter die gewünschte Schriftgröße.

Beispiel:

// der vorgeschlagene Weg aus der Hilfe
// -> Font über Tools | Create Font
font = loadFont( "LucidaSans-15.vlw" );
 
// der Weg um Schriften auch im PDF zu sehen 
// -> Aus PFont.list entnehmen
 
// Schriftlisten ausgeben
 
String[] fontList = PFont.list();
println( fontList );
 
font = createFont( "Franklin Gothic Demi Cond", 24 );

Text laden

Kommen wir nun aber zum ersten Arbeitsschritt, dem Laden des Textes. Über den Befehl loadStrings( url ) laden wir für unser Beispiele eine externe Text-Datei, die im Ordner data mit den Namen „sampleText.txt“ abgelegt ist.

Der Rückgabewert der Funktion loadStrings( url ) ist ein Array, das für jede Zeile bzw. Absatz einen neuen Eintrag in dem zurückgelieferten Array erstellt.

Beispiel:

// Dateiname
String url =  "sampleText.txt";
 
// Befehl zum Laden der Text-Datei
// Befüllen der initialen Liste
unfilteredTextList = loadStrings( url );

Text aufbereiten

Nun gilt es den Text für die Analyse aufzubereiten. Zuerst fasst man die einzelnen Einträge aus dem Array unfilteredTextList in einen gemeinsamen String mit dem Namen joinedText zusammen.

Nun legt man in der Variable delimiters die Trennzeichen für den Text fest, bevor man mit der Funktion splitTokens( joinedText, delimiters ) wieder ein Array mit dem Namen unfilteredWordList erstellt, dass alle Wörter als einzelne Einträge abspeichert.

Beispiel:

void prepareText()
{
  // den Text zu einem String zusammenfassen
  String joinedText = join( unfilteredTextList, " " );
 
  // Definition der Trennzeichen
  String delimiters = " ,.?!;:";
 
  // Teilen des gesamten joinedText in seine Bestandteile ( Wörter ), 
  // basierend auf den Trennzeichen
  unfilteredWordList = splitTokens( joinedText, delimiters );
}

Text analysieren

Die Textanalyse ist der größte Arbeitsschritt. Man kann es sich bildlich so vorstellen, dass man sich aus dem Array unfilteredWordList ein Wort herausgreift und mit jedem Eintrag in dieser Liste vergleicht, um festzustellen wie oft dieses Wort im gesamten Text vorkommt.

Realisiert wird dieser Ansatz mit zwei while-Schleifen, wobei die äußere ein Wort aus der Liste nimmt und die inneren alle Wörte aus der Liste zum Vergleich ausließt.

Der Vergleich zweier Strings erfolgt dann über currentWord.equals( wordInList ).

Der letzte Schritt der Analyse ist, dass das gefundene Wort und die Häufigkeit des Vorkommens in einer Instanz des Datencontainers WordElement in einer finalen Liste festgehalten wird, wenn es noch keinen Eintrag für dieses Wort gibt.

Dieser Teil der Analyse erfolgt in der Methode addWordToFilteredList(). Zu beachten ist hierbei, dass alle Strings, somit Wörter, mit der Methode myString.toLowerCase() als Kleinbuchstaben albgespeichert werden, damit es leichter beim Vergleichen ist.

Beispiel:

void analyzeText()
{
 
  int count = 0;
  int maxCount = unfilteredWordList.length;
 
  // Ein Wort aus der Liste mit den anderen Wörtern vergleichen
  while(count < maxCount )
  {
 
    // aktuelles Wort ausder Liste
    String currentWord = unfilteredWordList[ count ];
 
    int count2 = 0;
    int wordCount = 0;
 
    // Vergleich der Wörter
    while( count2 < maxCount )
    {
      // aktuelles Vergleichswort
      String wordInList = unfilteredWordList[ count2 ];
 
      //println( "currentWord: " + currentWord + 
" wordInList: " + wordInList );
 
      // Vergleich
      if( currentWord.equals( wordInList ) )
      {
        wordCount++;
      }
 
      count2++;  
    }
 
    // Wort in der Liste ablegen,
    // soweit es noch moeglich ist
    addWordToFilteredList( currentWord, wordCount );
 
    count++;
  }
}

Eine weitere Besonderheit ist der Einsatz einer ArrayList anstelle eines Arrays. Für eine ArrayList wird kein Datentyp für den Inhalt bestimmt und auch keine Größe der Liste definiert. Somit ist sie flexibler im Einsatz, aber auch langsamer in der Verarbeitung. Der Grund für den Einsatz ist hier, dass man im Vorfeld nicht weiß, wie viele Einträge in der Liste erstellt werden müssen.

Beispiel:

// Unterschied Array & ArrayList
 
// Array
String [] unfilteredWordList = new String[ 10 ];
// Schreiben
unfilteredWordList[ 0 ] = "myWord";
// Lesen
println( unfilteredWordList[ 0 ] );
// Abfrage der Laenge
println( unfilteredWordList.length );
 
// ArrayList
ArrayList filteredWordList = new ArrayList();
// Schreiben
filteredWordList.add( "myWord" );
// Lesen
println( filteredWordList.get( 0 ) );
 
String myWord = ( String ) filteredWordList.get( 0 );
 
// Abfrage der Laenge
println( filteredWordList.size() );

Die Elemente die man in der Liste filteredWordList festhält sind wie oben beschrieben Instanzen von der eigens erstellten Klasse WordElement, die das Wort und die Häufigkeit im Text repräsentiert.

Beispiel:

// Hülle
class WordElement
{
  int wordCount;
  String title;
 
  // Konstruktor
  WordElement()
  {    
  } 
}

Sobald man einen Wert über ArrayList.get( index ) ausliest, ist zu beachten, dass man den Datentyp dafür angeben muss, damit eine Zuweisung erfolgen kann.

Beispiel:

// Element aus Liste auslesen, Typ muss angegeben werden
WordElement newElementInList = 
(WordElement) filteredWordList.get( count );

Nun vergleicht man das neu erstellte WordElement mit denen aus der Liste filteredWordList. Sollte es schon vorhanden sein, wird die Variable foundInList auf true gesetzt. Durch die letzte if-Abfrage wird festgestellt, ob noch kein Eintrag für dieses Element in der Liste vorhanden ist. Wenn ja wird das neue Element per ArrayList.add( element ) hinzugefügt.

Beispiel:

if( newElementInList.title.equals( newElement.title ) )
{
  foundInList = true; 
}

Visualisierung der Wörter

Jetzt kommen wir zu der Kür: die Visualisierung der Wörter und deren Häufigkeit in dem geladenen Text.

Abermals mit einer while-Schleife iteriert man über die Liste filteredWordList und liest die einzelnen Instanzen vom Typ WordElement aus. In diesem Beispiel wird jedes Wort und dessen Häufigkeit als Text dargestellt und auf Basis der Häufigkeit wird noch ein Kreis in der passenden Größe gezeichnet.

Beispiel:

WordElement newElementInList = 
(WordElement) filteredWordList.get( count ); 
 
// Title des Elements
String title = newElementInList.title;
 
// Anzahl des Elements
String wordCount = str( newElementInList.wordCount );
 
// Schriftgröße auf Basis der Häufigkeit des newElements
textFont( font, newElementInList.wordCount * 5 );
 
int nextY = 20 + count * 50;
int circleSize = newElementInList.wordCount * 5;
 
ellipse( 20, nextY, circleSize, circleSize );
 
// Text anzeigen
text( title + ", " +  wordCount, 50, nextY + 10 );

Hausaufabe

Auf Basis dieses Beispiels ist eine Arbeit zu erstellen, bei der mehrere Texte als Quelle dienen, die jeweils zu unterschiedlichen visuellen Ergebnissen führen. Textquelle und visuelle Transformation könnten auch gemeinsam auf dem grafischen Endprodukt zu sehen sein.

Hierbei kann neben der Häufigkeit auch die Länge der Wörter, der Buchstaben, die Position im Alphabet etc. als Regler für neue visuelle Formen dienen.

Außerdem sollte jeder in der nächsten Einheit seine Idee für seine Abschlussarbeiten so aufbereiten, dass sie mir verständlich präsentiert werden kann. Für mich ist diese Präsentation wichtig, damit ich in der letzten Einheit noch auf eure Wünsche eingehen kann.

Wichtig ist dabei, sowohl die Abgabe der Processing-Sketches als auch der Texte.

Informationen zum Dateiformat, der Bezeichnung und zum Upload der Dateien sind unter dem Punkt Prüfungsleistung zu finden.

Die Beispiele der Woche können hier herunter geladen werden.