D3.js innerhalb von React: Datenbindung, SVG und die Eigentumsregel von useEffect
Erfahren Sie, was D3 wirklich ist, wie das Einbinden, Aktualisieren und Entfernen von Daten funktioniert, warum SVG dafür geeignet ist, und wie die DOM-Kontrolle aufgeteilt werden kann, damit D3 und React bei einem Balkendiagramm zusammenarbeiten.
Quellen: Harnessing the Power of Data Visualization: Integrating D3.js with React
Drei Dinge, die man zuerst wissen sollte
- D3 ist ein Werkzeugkasten, keine Diagrammbibliothek. Diejenigen, die etwas wie Chart.js erwarten, stoßen auf Schwierigkeiten. D3 ist eine untere Ebene zur Verknüpfung von Daten mit DOM-Elementen, was sowohl seine Leistungsfähigkeit als auch seine steilere Lernkurve erklärt.
- React und D3 konkurrieren um dieselben DOM-Node-Objekte. Die Lösung ist einfach: React rendernt den SVG-Container, während D3 alles darin über einen
useEffectverwaltet. Wenn man diese Regel ignoriert, verbringt man Stunden damit, seltsame Konflikte zu beseitigen. - Das enter-, update- und exit-Muster wirkt anfangs seltsam. Nach ein paar Anwendungen wird es verständlich, und andere Ansätze für datengetriebene Grafiken erscheinen ungeschickt.
Was D3 tatsächlich ist
Der Name steht für Data-Driven Documents. Das Wichtigste hier ist das Wort „Documents“: DOM-Elemente, deren Aussehen durch Daten bestimmt wird. D3 verknüpft Arrays mit Elementen und ermöglicht es, das Erscheinungsbild als Funktion der Werte in diesen Arrays zu definieren. Ein Diagramm ist nur ein mögliches Ergebnis, und die Bibliothek selbst kennt keine Diagrammtypen.
Darin liegen sowohl die Stärke als auch die Schwierigkeit. D3 trifft keine Entscheidungen für Sie. Jede visuelle Eigenschaft, jeder Achenskalentick, jede Farbe sowie jede Interaktion muss von Ihnen festgelegt werden. Dadurch erhalten Sie völlige kreative Freiheit – allerdings auch viel Tippen.
Datenbindung und das DOM
Genau hier geben die meisten Anfänger auf, daher lohnt es sich, das Schritt für Schritt zu durchgehen.
In herkömmlichem JavaScript erstellt man ein Element, füllt es mit Inhalt und fügt es der Seite hinzu. Mit D3 wählt man eine Gruppe von Elementen aus, einschließlich solcher, die noch nicht existieren, bindet ein Array an diese Auswahl und erhält anschließend drei Gruppen zurück:
- Elemente, die bereits existieren und nur neue Daten benötigen (die Update-Selektion),
- Datenpunkte, für die es noch kein Element gibt (die Enter-Selektion),
- Elemente, deren Daten verschwunden sind (die Exit-Selektion).
Das Seltsame ist, Methoden auf einer leeren Auswahl aufrufen zu müssen. Man wählt alle rect-Elemente innerhalb eines SVGs aus, das keine enthält, ruft anschließend .data(myArray) auf, dann .enter() und danach .append('rect') – und plötzlich erscheinen Rechtecke. Tatsächlich erzeugt .enter() einen Platzhalter für jedes Datensatz ohne Element, und .append() wandelt jeden Platzhalter in einen echten Knoten um. Man kann es sich so vorstellen: „Für jeden Datensatz ohne Element wird ein solcher erstellt.“
Vorher wirkt der D3-Code wie Zauberei. Danach liest er sich natürlicher.
Visualisierungs-eigenschaften aus Daten setzen
Sobald die Daten gebunden sind, ist der nächste Schritt leichter nachvollziehbar. Jedes Attribut oder jede Schriftart kann aus dem Datensatz berechnet werden:
selection.attr('height', d => yScale(d.value))
Hier ist d das Datum, das mit diesem bestimmten Element verknüpft ist, und yScale ist eine Funktion, die Ihren Wertebereich in Pixel umwandelt. Die Höhe der Balken hängt direkt von ihren Daten ab. Ändern Sie die Daten, führen Sie die Anweisung erneut aus – die Höhe passt sich an. Fast alles in einer D3-Visualisierung reduziert sich auf Variationen dieses Konzepts: Eine visuelle Eigenschaft wird auf eine Funktion des begrenzten Wertes gesetzt.
Warum SVG?
D3 zeichnet in SVG, und die Gründe dafür liegen über der reinen Historie hinaus.
SVG ist vektorbasiert, wodurch Formen mathematisch definiert werden anstelle von Pixelgittern. Ein Balkendiagramm, das auf einem Laptop scharf erscheint, ist ohne zusätzliche Anstrengungen auch auf einem 4K-Bildschirm genauso scharf, und die Exporte werden bei unpraktischen Auflösungen nicht unscharf.
Für die Interaktivität sind SVG-Formen normale DOM-Elemente und nehmen genauso am Ereignissystem des Browsers teil wie ein Button. Hover-, Klick- und Touch-Operationen funktionieren ohne zusätzliche Prüfmechanismen, da man einfach nur Zuhörer (Listeners) hinzufügt.
Die Kosten zeigen sich in größeren Mengen. Tausende von SVG-Node verlangsamen die Seite, weshalb das Darstellen von 50.000 separaten Punkten als einzelne Elemente nachteilig ist. Die Canvas-Berechnung eignet sich besser für solche Fälle. Für Dashboards, Geschäftsgrafiken und die meisten Analysetools ist SVG in Ordnung, und die Integration von Ereignissen lohnt sich dabei.
Interaktivität und Flexibilität
Bei Datenexplorations-Interfaces, bei denen Nutzer Daten aus verschiedenen Blickwinkeln schneiden, filtern und untersuchen, übertrifft D3 statische Grafikwerkzeuge. Man hat die Kontrolle über jedes Ereignis und jede Übergangsphase.
Die meisten Diagrammbibliotheken bieten Ihnen Diagrammtypen zur Konfiguration an: Balkendiagramme, Liniendiagramme, Kreisdiagramme, mit Optionen für Farben, Beschriftungen und Legenden. D3 hingegen stellt Bausteine bereit: Skalen, Achsen, Pfadgeneratoren und Layout-Algorithmen. Ein Diagrammtyp ist lediglich eine Kombination dieser Bausteine, weshalb es keine Liste der unterstützten Typen gibt. Wenn eine Visualisierung nirgends vorhanden ist, können Sie sie selbst erstellen – dabei sind nur SVG und Mathematik begrenzend einflussreich.
Der Aufwand ist zusätzliche Arbeit. Ob sich das lohnt, hängt vom Projekt ab. Für Standard-Business-Diagramme reichen Recharts oder Chart.js aus, und Sie können weitermachen. Für maßgeschneiderte, interaktive Grafiken, bei denen die Präsentation von entscheidender Bedeutung ist, ist D3 der bessere Weg.
Die Funktionen, die Sie ständig verwenden werden
Es handelt sich hier nicht um einen vollständigen Referenzleitfaden, sondern nur um die Funktionen, die im täglichen Einsatz vorkommen:
- d3.select() holt sich das erste Element, das mit einem Selektor übereinstimmt. In React ist
d3.select(svgRef.current)die übliche Methode, um den Container zu erhalten. - d3.selectAll() gibt alle Übereinstimmungen zurück. Die Datenbindung beginnt in der Regel hier, da mehrere Elemente beteiligt sind.
- selection.data() verknüpft jedes Array-Element mit einem Element, sodass
din späteren Aufrufen verfügbar ist. Alles Weitere hängt von diesem Schritt ab. - selection.enter() liefert die Platzhalterauswahl für Daten, die keinen Knoten haben, und
.append()erstellt anschließend die Knoten. - selection.attr() setzt Attribute, wie beispielsweise die oben gezeigte Höhenlinie, die man Hunderte Male schreiben wird.
.attr() oder .style(), sodass die Werte sanft interpoliert werden und ein plötzlicher Wechsel in eine Animation umgewandelt wird.d3.max(data, d => d.value).D3 in React verwenden
Das Problem, das niemand erwähnt, bis man darauf stößt: React behält einen virtuellen DOM bei und führt seine eigenen Änderungen am echten DOM durch, während D3 direkt den echten DOM bearbeitet. Wenn beide innerhalb eines Elements arbeiten, überschreibt Reacts Neulayout die Arbeit von D3, oder D3 fügt Knoten ein, von denen React nichts weiß, wodurch das nächste Layout durcheinandergerät.
Der funktionierende Ansatz besteht darin, die Verantwortlichkeiten aufzuteilen. React rendernt den SVG-Container und greift nicht in dessen Inhalt ein. D3 erreicht diesen Container über ein ref und führt alle seine Arbeiten in einem useEffect aus. Wenn sich die Daten ändern, triggert ein Zustandsupdate diesen Effekt, und D3 löscht und zeichnet neu auf. Keine der Bibliotheken stört die andere.
1. Die Pakete installieren
Fügen Sie D3 zusammen mit React und ReactDOM über Ihren Paketmanager hinzu.
npm install d3 react react-dom
Die Nutzer von Yarn tun dasselbe mit ihrem eigenen add-Befehl.
yarn add d3 react react-dom
2. Schreiben Sie die Komponente
Die React-Seite hat nur eine Aufgabe: Ein SVG-Element darzustellen und D3 einen Referenzwert dazu zu übergeben. Nichts anderes innerhalb der Visualisierung gehört zu React.
3. Zeichnen Sie in useEffect
Innerhalb des Effects schreiben Sie D3 genauso wie außerhalb von React, wobei Sie den Referenzwert verwenden, um den Container zu finden. Zwei Skalen erledigen die Koordinatenberechnungen – eine wandelt Kategorielabel in horizontale Positionen um und die andere wandelt Datenwerte in vertikale um. Die Höhe jeder Stange entspricht dem unteren Rand des Diagramms minus dem skalierten Wert. Dieser Teil bereitet vielen Probleme, weil die y-Achse im SVG nach unten zeigt, sodass höhere Stangen kleineren y-Werten entsprechen.
4. Rendern Sie die Komponente
Platzieren Sie das Komponente in Ihrer Anwendung und übergeben Sie ihr ein Datenarray. Ändern Sie das Array, und der Chart wird neu gezeichnet: React bemerkt die geänderte Eigenschaft, der Effect wird ausgelöst, und D3 löscht und zeichnet neu. Die Verbindung zwischen dem Anwendungsstate und der Visualisierung ist automatisch.
5. Starten Sie die Anwendung
Führen Sie den Entwicklungsserver mit npm aus.
npm start
Oder mit Yarn.
yarn start
Im Browser werden Sie einen Balkendiagramm anzeigen sehen. Von dort an folgt alles demselben Muster: Hilfetexte beim Überfahren, Farbskalen, animierte Aktualisierungen, Zoom-Funktion sowie weitere Diagrammtypen. All das basiert auf diesem Prinzip und nutzt die oben aufgeführten Funktionen.
Zusammenfassung
D3 und React arbeiten zusammen, sobald die Struktur richtig ist. React kümmert sich um den Container und den Lebenszyklus der Komponente, während D3 für die Darstellung darin verantwortlich ist. Halten Sie diese Grenzen klar, damit jede Bibliothek ihre eigentliche Aufgabe erfüllen kann.
Der Vorteil besteht darin, Visualisierungen erstellen zu können, die in keinem Diagramm-Paket enthalten sind, da Sie mit Primitiven statt mit Konfigurationsoptionen arbeiten. Wenn das Grafikdesign selbst zum Wert des Produkts gehört, ist das von Bedeutung. Wenn ein Dashboard lediglich ein Liniendiagramm benötigt, verwenden Sie Recharts und vermeiden Sie so aufwändige Einstellungen.