Graphen-Visualisierung mit Neo4j

Keine Kommentare

In diesem Artikel möchte ich nach einer kurzen Einführung in die Graphen-Theorie einen Überblick über die NoSQL-Datenbank Neo4j geben. Insbesondere werde ich auf die Möglichkeiten eingehen, die Neo4j bei der Visualisierung von Graphen anbietet.

Was ist ein Graph?

Unter einem Graphen versteht man eine abstrakte Struktur, die aus einer Menge von Knoten V  (wie Vertex) und einer Menge an Kanten E  (wie Edges) besteht. Bei gerichteten Graphen (die uns im Folgenden interessieren) ist E eine Teilmenge des kartesischen Produkts V x V. Im Allgemeinen werden Graphen so dargestellt, dass Knoten als Kreise, Kanten als Linien bzw. Pfeile zwischen den Kreisen repräsentiert werden:

Mit dieser Graphen-Struktur lassen sich bestimmte Kategorien von Problemen sehr effizient lösen, z.B. das Auffinden der kürzesten Pfade zwischen zwei Punkten. Wenn es um die Speicherung und Abfrage von Graphen geht, wird man früher oder später auf die NoSQL-Datenbank Neo4j stoßen.

NoSQL-Überblick

Unter dem Sammelbegriff NoSQL werden alle Datenbanken-Typen zusammengefasst, die sich in folgenden Aspekten vom klassischen relationalen Paradigma unterscheiden:

  • Datenstrukturen
  • Datenmengen
  • Zugriffsgeschwindigkeit

Eine Klassifizierung von NoSQL-Datenbanken findet in der Regel nach der Art der Datenstrukturen statt, da dies oft maßgeblich für die APIs ist. NoSQL-Datenbanken lassen sich grob in diese vier Hauptkategorien einteilen:

  • Key-Value-Datenbanken
  • Dokumentenorientierte Datenbanken
  • Wide-Column-Datenbanken
  • Graphen-Datenbanken

Neo4j

Neo4j gehört in die Kategorie der Graphen-Datenbanken und ist sicher einer der populärsten Vertreter aus diesem Bereich. Der Name Neo4j leitet sich ab von Network Engine for Objects, die Abkürzung 4j gibt einen Hinweis darauf, dass es früher nur ein Java-API gab, was aber mittlerweile nicht mehr der Fall ist.

Property-Graph

Als interne Datenstruktur verwendet Neo4j einen gerichteten Graphen, dessen Knoten und Kanten mit so genannten Labels und Attributen versehen werden können. Man nennt das Ganze dann auch Property Graph. Attribute sind einfache Datentypen wie String, Integer etc. oder auch Arrays dieser Typen. Ein Label ist immer ein String. Mit Labels können leicht Teilmengen von Knoten gebildet und so später effizient gesucht werden.

Diese zeigt ein einfaches Beispiel eines Property-Graphen. Der linke Knoten hat die zwei Attribute name und age und die beiden Labels Person und Developer, die zur Klassifizierung des Knotens herangezogen werden und später auch die grafische Darstellung beeinflussen können. Eine Kante zwischen dem Entwickler und dem Knoten, der die Programmiersprache Java repräsentiert, hat ein Label KNOWS und zwei Attribute since und level. Dadurch lassen sich zum Beispiel alle Entwickler ermitteln, die mindestens zehn Jahre Erfahrung in Programmiersprache X haben.

Cypher

Die Sprache zur Datenmanipulation und Formulierung von Abfragen heißt, passend zu Neo(4j), Cypher. Cypher ist eine deklarative Sprache, die in ihren Grundzügen an SQL erinnert. Knoten und Kanten kann man extrem intuitiv mit einer ASCII-Notation beschreiben. In der einfachsten Form werden Knoten mit runden Klammern () notiert, Kanten mit einem Pfeil –>. So erzeugt z. B. folgendes Cypher- Statement einen Knoten ohne Attribute und Labels:

$ CREATE ();
+-------------------+
| No data returned. |
+-------------------+
Nodes created: 1
44 ms

Das ist zugegebenermaßen nicht sonderlich spannend. Einen Teil des vorherigen Beispiels würden wir so anlegen:

$ CREATE (john :Person :Developer {name: "John", age: 36}),
(java :ProgrammingLanguage {name: "Java"} ),
(john) -[:KNOWS {since: 2004, level: "expert"}
]->(java);
+-------------------+
| No data returned. |
+-------------------+
Nodes created: 2
Relationships created: 1
Properties set: 5
Labels added: 3
105 ms

Eine Abfrage nach allen Entwicklern mit langjähriger Programmiererfahrung sieht dann so aus:

MATCH (dev :Developer)-[knows:KNOWS]-(:ProgrammingLanguage)
WHERE 2016 - knows.since > 10
RETURN dev;
╒═════════════════════╕
│dev                  │
╞═════════════════════╡
│{name: John, age: 36}│
└─────────────────────┘

Als Ergebnis bekommen wir auf der Kommandozeile einen Knoten in ASCII-Darstellung. Das ist in vielen Fällen natürlich wenig hilfreich. Je nach verwendeter API bekommt man in der Programmiersprache seiner Wahl selbstverständlich eine bessere Repräsentation. Dies soll aber gar nicht der Fokus dieses Artikels sein.

Möge der Graph mit dir sein!

Ich möchte mich im Folgenden auf die visuelle Darstellung von Neo4j-Graphen konzentrieren. Dazu verwende ich ein etwas komplexeres Beispiel, das die Beziehungsgeflechte zwischen den Jedi und Sith aus den Star Wars-Filmen darstellt. Das vollständige Cypher-Skript sw_force_ddl.cql finden Sie auf GitHub. Das Meta-Modell sieht ganz grob so aus, dass Jedi und Sith sich gegenseitig töten, untereinander trainieren und auch noch mit einigen Randfiguren mehr oder weniger stark interagieren.

Wenn Sie einen Neo4j-Server mit der Standard-Konfiguration starten, steht auf Port 7474 eine Webanwendung bereit, der so genannte Neo4j-Browser. Nach dem allerersten Aufruf müssen Sie sich mit den Default Credentials einloggen und anschließend neue Zugangsdaten festlegen. Anschließend kopieren Sie das CREATEStatement aus sw_force_ddl.cql

CREATE
// Jedi ...
(yoda :Jedi :UnknownSpecies {name:"Yoda"}),
(dooku :Jedi :Sith :Human {name: "Count Dooku", sith_name: "Darth Tyranus"}),
(qui_gon :Jedi :Human {name: "Qui-Gon Jinn"}),
(obi_wan :Jedi :Human {name: "Obi Wan Kenobi"}),
...

in die Eingabezeile am oberen Rand des Arbeitsbereichs und führen diese mit dem Play-Symbol ganz rechts daneben aus. Danach führen wir ganz analog die Abfrage des gesamten Graphen durch:

MATCH (n) RETURN n;

Das Ergebnis wird dann so oder so ähnlich dargestellt:

Im oberen Bereich der Graphen-Darstellung (daneben gibt es auch noch eine tabellarische, textuelle und eine für die API-Ebene) listet der Neo4j Browser die Labels aller gefundenen Knoten samt der jeweiligen Vorkommen auf. Von insgesamt 15 Knoten haben z. B. 7 das Label Sith. Darunter befindet sich die Zusammenfassung aller gefundenen Kanten, die per Default alle in grau dargestellt werden. Die Label mit den häufigsten Vorkommen erhalten automatisch gut unterscheidbare Farben zugewiesen, ebenso wird ein Attribut der Knoten und Kanten zur Beschriftung herangezogen. Beim Mouseover über einen Knoten erfolgt ganz unten eine Auflistung aller Label und Attribute. Der gesamte Bereich kann noch expandiert werden; in der dann erscheinenden Ansicht kann man u.a. auch noch in den Graphen zoomen.

Per Default werden die Knoten mit Label Human grün dargestellt, die mit Jedi blau. Da es aber z. B. mehr Knoten mit Label Human als Jedi gibt, werden auch die menschlichen Jedi grün eingefärbt. Außerdem gefällt den Sith natürlich das Plüschrosa als Erkennungsfarbe nicht. Das alles lässt sich aber individuell anpassen.

Einfache Anpassungen im Neo4j-Browser

Wenn man im oberen Bereich auf eines der Label klickt, wird am unteren Rand der Ansicht ein einfacher Editor angezeigt, mit dem man das Aussehen der Knoten und Kanten beeinflussen kann:

Hier kann man aus einigen vorgeschlagenen Farben wählen, den Durchmesser der Knoten anpassen und eines der Attribute zur Beschriftung auswählen. Bei Kanten sieht der Editor ganz ähnlich aus; hier wählt man aber anstatt des Durchmessers die Dicke des Pfeils aus. Weitergehende Anpassungen nimmt man konfigurativ vor, über ein:

GRASS – Graph Style Sheet

Das Aussehen der Knoten und Kanten ist über eine so genannte GRASS-Datei (abgeleitet von Graph Style Sheet) nahezu beliebig steuerbar. Die von Neo4j automatisch generierten Stylesheets finden Sie in der linken Hauptnavigation unter Favoriten (5-zackiger Stern) und dann unter Graph Style Sheet. Es öffnet sich ein Popup, dessen Inhalt (zumindest in der Community Edition) leider nicht editierbar ist:

node {
    diameter: 50px;
    color: #A5ABB6;
    border-color: #9AA1AC;
    border-width: 2px;
    text-color-internal: #FFFFFF;
    font-size: 10px
}
 
relationship {
    color: #A5ABB6;
    shaft-width: 1px;
    font-size: 8px;
    padding: 3px;
    text-color-external: #000000;
    text-color-internal: #FFFFFF;
    caption: '<type>';
}
 
node.Jedi {
    color: #68BDF6;
    border-color: #5CA8DB;
    text-color-internal: #FFFFFF;
    caption: '{name}';
}
 
node.Human {
  color: #6DCE9E;
  border-color: #60B58B;
  text-color-internal: #FFFFFF;
  caption: '{name}'
}
 
node.Muun {
  color: #FF756E;
  border-color: #E06760;
  text-color-internal: #FFFFFF;
  caption: '{name}';
}
 
node.Sith {
    color: #DE9BF9;
    border-color: #BF85D6;
    text-color-internal: #FFFFFF;
    caption: '{sith_name}'
}

Das sieht im Prinzip stark nach Cascading Style Sheets aus. Dies werden wir uns bei der Umsetzung folgender Anforderungen an unsere individuelle Darstellung zunutze machen:

  • Jedi sollen in grün dargestellt werden
  • Sith in rot
  • Sith, die einst Jedi waren (Anakin Skywalker + Count Dooku): rot mit grünem Rand
  • Beschriftung der Sith-Knoten: Titel als Sith und Geburtsname in Klammern
  • Liebesbeziehungen in rot und etwas deutlicher

Zunächst geben wir allen Knoten einen größeren Durchmesser und eine größere Schriftgröße:

node {
    diameter: 100px;
    color: #A5ABB6;
    ...
    font-size: 16px;
    font-weight: bold;
}

Jedi in positivem Grün:

node.Jedi {
    color: #80ff80;
    border-color: #60df60;
    text-color-internal: #202020;
    caption: '{name}';
}

Sith in aggressivem Rot und mit angepasster Beschriftung, bei der wir zwei Knoten-Attribute kombinieren:

node.Sith {
    color: #ff0000;
    border-color: #df0000;
    text-color-internal: #FFFFFF;
    font-size: 14px;
    caption: '{sith_name} ({name})';
}

Und Macht-Nutzer, die die Seiten gewechselt haben, bekommen eine abweichende Umrandung:

node.Jedi.Sith {
    border-color: #60df60;
}

Abschließend spezialisieren wir noch die Darstellung der Kantenart LOVES:

relationship.LOVES {
    shaft-width: 5px;
    color: #e00000;
    font-size: 12px;
    text-color-external: #e00000;
    caption: '<3 <3 <3';
}

Die Änderungen nehmen wir in einem Texteditor vor und ziehen die gespeicherte Datei auf die passende Stelle im Neo4j-Browser. Das Ergebnis wird sofort sichtbar:

Kleine Spoiler-Warnung: Die Beziehung zwischen Luke Skywalker und Rey, von der wir praktisch gar nichts wissen, wurde wie folgt angelegt, was in der Darstellung aber nicht ersichtlich ist:

(luke)-[:FATHER_OF {possibility: .5}]->(rey)

Prinzipiell können Sie beliebig unterschiedliche Styles (sogar je nach Query) verwenden, um die Sachverhalte auch noch visuell zu unterstreichen. Was bei diesem relativ kleinen Graphen auch schon auffällt, ist die Tatsache, dass eine Darstellung schnell unübersichtlich wird. In realen Anwendungen wird aber in der Regel nie alles betrachtet (macht man bei relationalen Tabellen auch eher selten), sondern mit Queries gearbeitet. Wenn wir z. B. die Ausbildungsbeziehungen der Sith betrachten, ist die folgende Darstellung schon übersichtlicher:

MATCH p=(master :Sith)-[:TRAINS]->(apprentice :Sith) RETURN p;

Diese und andere interessante Abfragen finden Sie auch im GitHub-Repository in der Datei sw_force.cql. Diese Abbildung offenbart im Übrigen noch eine fachliche Schwäche im Modell: Da es bei den Sith zu einem Zeitpunkt immer nur einen Meister und einen Schüler geben darf, müssten die Kanten vom Typ TRAINS einen Gültigkeitszeitraum als Attribut haben, wenn man es ganz genau nimmt. Und Abfragen müssten sich dann auf einen Stichtag innerhalb des Star-Wars-Universums beziehen.

Zusammenfassend lässt sich sagen, dass sich die Darstellung eines Graphen relativ einfach an die eigenen Bedürfnisse anpassen lässt. Knoten und Kanten können in Farbe und Form verändert werden, das gleiche gilt für die Beschriftungen. So können die für den jeweiligen Use Case relevanten Elemente auch optisch noch hervorgehoben werden. Wem das immer noch nicht reicht, der muss auf externe Tools zurückgreifen.

Daten-Export

Zunächst einmal bietet der Neo4j-Browser bereits an, einige Datei-Formate zu exportieren, namentlich sind das SVG, PNG und JSON:

Weitere Datei-Formate kann man über die Neo4j Shell Tools exportieren (diese CLI-Tools bieten darüber hinaus auch die Möglichkeit, verschiedene Formate zu importieren). Der folgende Aufruf exportiert den gesamten Graphen in das Standard-Format GraphML (optional kann auch eine Cypher-Query angegeben werden, um den Export auf eine Teilmenge des Graphen einzugrenzen):

export-graphml -o sw.graphml -t -r

GraphML-Dateien können von einer Vielzahl an Tools gelesen, weiterverarbeitet und gerendert werden. Im Bereich Open Source ist hier sicher Gephi am bekanntesten.

Individualsoftware mit D3.js

Eine weitere Alternative ist die Entwicklung individueller Software, bei der Sie gezielt alle Anforderungen an die Visualisierung selbst bestimmen. Hier eignet sich der Einsatz von Bibliotheken wie z. B. D3.js. Ein sehr schönes Beispiel für ein interaktives Graphen-Rendering mit D3.js finden Sie z. B. hier. Ein komplettes Beispiel einer SpringBoot-basierten Webanwendung, die Graphen aus einer Neo4j-Datenbank liest und mittels D3.js darstellt, finden Sie im Neo4j-GitHub-Repository. Dies kann eine gute Ausgangsbasis für eigene Projekte darstellen.

Kauf-Tools

Natürlich gibt es auch eine Reihe kommerzieller Tools im Bereich der Visualisierung von Graphen. Moderne Tools aus diesem Bereich sind z. B.

Beide haben Connectoren für Neo4j und verfügen über eine Vielzahl an weitergehenden Analysemöglichkeiten.

Fazit

Ich hoffe, ich konnten Ihnen mit diesem Artikel einen guten Überblick über die Möglichkeiten geben, wie Sie mit Neo4j und auch externen Tools Graphen-Strukturen visualisieren können. Starten Sie einfach mit dem Browser, den Neo4j mitbringt. Wenn Sie damit an Grenzen stoßen, greifen Sie auf externe Tools oder ggf. selbst entwickelte Software zurück. Ob sich eine Eigenentwicklung immer lohnt, hängt natürlich ganz von Ihren Anforderungen und Ihrem Budget ab. Ein Kauf-Tool muss dabei nicht zwingend die schlechteste Alternative sein.

Dieser Blog-Post ist erstmalig im Softwerker Vol. 8 erschienen. Den Softwerker kostenlos abonnieren: www.dersoftwerker.de.

Tobias Trelle

Diplom-Mathematiker Tobias Trelle ist Senior IT Consultant bei der codecentric AG, Solingen. Er ist seit knapp 20 Jahren im IT-Business unterwegs und interessiert sich für Software-Architekturen und skalierbare Lösungen. Tobias hält Vorträge auf Konferenzen und Usergruppen und ist Autor des Buchs „MongoDB: Ein praktischer Einstieg“.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.