Logo Oracle Deutschland   Application Express Community
Mehr Charts geht nicht: "D3js" in APEX-Anwendungen integrieren
Erscheinungsmonat APEX-Version Datenbankversion
November 2014 ab 4.0 ab 11.2.0.4

Das freie Visualisierungsframework d3js (D3 = DDD = Data Driven Documents) gewinnt mehr und mehr Popularität unter Web-Entwicklern. Abbildung 1 zeigt die Homepage von d3js.org - die Fülle an Diagrammen und Visualisierungen, die sich mit D3 realisieren lassen, grenzt wirklich ans Unglaubliche.

D3 Homepage (www.d3js.org)

Abbildung 1: D3 Homepage (www.d3js.org)

In diesem Community Tipp werden Sie erfahren, wie Sie ein D3-Diagramm in Ihre APEX-Applikation aufnehmen. Das Ergebnis wird ein D3 Bubble Chart anhand der Daten der Tabelle EMP sein (Abbildung 2). Wenn Sie dieses erste Diagramm umgesetzt haben, wird es Ihnen leicht fallen, andere Visualisierungen umzusetzen, denn die Vorgehensweise ist immer gleich.

Das Ziel: Ein D3 Bubble Chart auf Basis der Tabelle EMP

Abbildung 2: Das Ziel: Ein D3 Bubble Chart auf Basis der Tabelle EMP

Allerdings muss vorab gesagt werden, dass D3 (ganz im Gegensatz zum in APEX integrierten AnyChart) keine Diagramm-Engine ist, bei der man einen Chart-Typ und eine Datenquelle wählt und dann fertig ist. D3 ist vielmehr ein auf HTML5 basierendes Javascript-Framework, welches es dem Entwickler leichter macht, solche Diagramme zu programmieren. Und wie Sie im Verlauf des Tipps sehen werden, muss man sich tatsächlich mit Javascript, HTML5 und SVG auseinandersetzen, um mit D3 arbeiten zu können. Abschrecken lassen sollte man sich davon aber nicht: Die Möglichkeiten sind so vielfälig, dass es die Einarbeitung absolut wert ist - und so schwierig ist es denn auch nicht.

In D3 werden alle Diagramme aus HTML-Elementen zusammengesetzt. Die HTML5-Standard hinzugekommenen SVG-Elemente bieten eine sehr komfortable Grundlage - Abbildung 3 zeigt eine kleine Auswahl.

SVG Grundelemente mit zugehörigem HTML5-Code

Abbildung 3: SVG Grundelemente mit zugehörigem HTML5-Code

Es lässt sich leicht erkennen, dass der in Abbildung 2 dargestellte Bubble Chart aus Instanzen des SVG-Elements <circle> zusammengesetzt wurde. Programmiert man ein Diagramm mit D3, so ist man nach wie vor für das Setzen der SVG-Elemente verantwortlich - man setzt keinen HTML-Code, vielmehr fügt man die Elemente mit Javascript-Aufrufen zur Webseite hinzu. Das Framework D3 spielt seine Stärken an zwei speziellen Punkten aus.

  • D3 bietet Javascript-Funktionen, die das Verknüpfen der SVG-Elemente mit den konkreten Daten sehr leicht machen
  • D3 liefert Layout-Klassen mit, welche die konkreten Parameter für die SVG-Elemente berechnen - so übernimmt D3 beim Bubble Chart die Berechnung der Koordinaten und der Radien der beteiligten Kreise.

Doch damit erst mal genug der Vorrede - beginnen Sie nun mit der konkreten Umsetzung des Bubble-Chart in APEX. Wir fangen dabei nicht bei Null an, sondern nehmen eines der vielen Beispiele, welche der Autor des D3-Frameworks bereitgestellt hat, als Vorlage. Den Bubble Chart mitsamt Beispieldaten und Code finden Sie auf http://bl.ocks.org/mbostock/4063269.

Bubble Chart Beispielvorlage des Autors von d3js

Abbildung 4: Bubble Chart Beispielvorlage des Autors von d3js

Wenn Sie sich den Code ansehen, so stellen Sie fest, dass der Beispielcode aus zwei Abschnitten besteht. Der erste ist eine HTML-Seite mit dem nötigen Javascript-Code und der zweite enthält die darzustellenden Daten im JSON-Format. Um dieses Beispiel auf die Tabelle EMP anzupassen, müssen wir also ...

  • die Daten der Tabelle EMP als JSON bereitstellen - die JSON-Daten müssen per URL (per AJAX-Request) abrufbar sein. Wichtig ist dabei das JSON-Format.
  • den Javascript Code so anpassen, dass er besagte JSON-Daten verwendet und sich "sauber" in die APEX-Anwendung einpasst.

Beginnen wir also: Erzeugen Sie sich eine APEX-Anwendung mit einer leeren Anwendungsseite. Navigieren Sie dann in die Seitenbearbeitung. Als erstes stellen wir die Daten im JSON-Format bereit: Erzeugen Sie dazu einen neuen AJAX-Callback (Abbildung 5).

Neuen AJAX-Callback erzeugen

Abbildung 5: Neuen AJAX-Callback erzeugen

Wählen Sie als Prozesstyp PL/SQL aus und geben Sie ihrem AJAX-Callback im nächsten Dialog den Namen getEmpBubbleData. Belassen Sie die Auswahlliste des Prozesspunkts bei On Demand - Run this process when requested by AJAX.

Namen und Prozesspunkt festlegen

Abbildung 6: Namen und Prozesspunkt festlegen

Im nächsten Dialog hinterlegen Sie den Code, welcher die Daten der Tabelle EMP im (richtigen) JSON-Format zurückgibt. Wenn Sie sich das JSON auf der Beispielseite genauer ansehen, so merken Sie, dass das Beispiel die JSON-Daten in einem hierarchischen Format erwartet. Grund ist, (für die Experten) dass das verwendete D3 Pack Layout in der Lage ist, auch hierarchische Bubble-Charts darzustellen (eine Blase enthält dann wiederum viele andere Blasen).

Für unser Beispiel brauchen wir zwar keine Hierarchie - wir möchten einfach nur eine "Blase" pro Zeile in der Tabelle EMP, dennoch muss das JSON als Hierarchie mit einem Elternelement und darunter mehreren Kinderelementen vorliegen. Der PL/SQL-Code, mit dem das generiert wird, sieht so aus:

declare
  l_firstrow boolean := true;
begin
  htp.p('{"children": [');
  for i in (
    select ename, sal, job from emp 
  ) loop
    if l_firstrow then 
      l_firstrow := false;
      htp.p('{'); 
    else 
      htp.p(',{'); 
    end if;
    htp.p(apex_javascript.add_attribute('ename', i.ename));
    htp.p(apex_javascript.add_attribute('sal', i.sal));
    htp.p(apex_javascript.add_attribute('job', i.job, false,false));
    htp.p('}');
  end loop;
  htp.p(']}');
end;

Hinterlegen Sie diesen Code für Ihren neuen AJAX-Callback und klicken Sie auf die Schaltfläche Create Process. Das Testen ist sehr einfach - starten Sie Ihre APEX-Seite (diese ist natürlich nach wie vor leer) und ändern Sie die URL in der Adressleiste des Browsers wie folgt um (das rot markierte einbauen):

http://.../apex/f?p={APP_ID}:{APP_PAGE_ID}:{SESSION}:APPLICATION_PROCESS=getEmpBubbleData:::

Hat man ein Browser-Plugin zur JSON-Darstellung installiert, so sieht das Ergebnis wie in Abbildung 7 aus (ohne Plugin sieht man den JSON-Code im Rohformat).

Daten der Tabelle EMP im JSON-Format - passend für den D3 Bubble Chart

Abbildung 7: Daten der Tabelle EMP im JSON-Format - passend für den D3 Bubble Chart

Wir haben die Daten nun im korrekten JSON-Format. Als nächstes geht es an die Erstellung des Diagramms. Erzeugen Sie auf der Seite zunächst eine neue Region vom Typ HTML. Zunächst nimmt diese nur einen sog. DIV-Container auf - wir legen also fest, wo in der Seite das D3-Diagramm erscheinen soll. Mit dem verwendeten HTML Tag <DIV> können Sie aber schon gleich die Größe des Diagramms festlegen. Hinterlegen Sie also folgenden HTML-Code als Regionsquelle.

<div id="chart" style="width: 100%; height: 600px"></div>

Neben der Definition der Regionsgröße ist vor allem das Attribut id wichtig. Dies ist der interne Name des DIV-Containers und unter diesem Namen kann er im Javascript-Code referenziert werden. Das werden wir noch brauchen, um das D3-Diagramm genau innerhalb der APEX HTML-Region darzustellen.

Navigieren Sie nun zu den Seitenattributen - dort werden wir den Javascript-Code hinterlegen. Im Bereich Javascipt - File URLs hinterlegen Sie als erstes die URL zur D3js-Bibliothek (http://d3js.org/d3.v3.min.js). Abbildung 8 zeigt, wie Sie diese direkt aus dem Internet laden können. Alternativ können Sie diese natürlich auch in Ihren APEX Workspace als statische Datei laden und dann mit der URL #APP_IMAGES#d3.v3.min.js referenzieren.

D3 Bibliothek laden - hier direkt aus dem Internet

Abbildung 8: D3 Bibliothek laden - hier direkt aus dem Internet

Tragen Sie danach bei Function and Global Variable Declaration den folgenden, etwas längeren Javascript-Code ein. Der Code ist aus dem in Abbildung 4 vorgestellten Beispiel für einen Bubble Chart abgeleitet und mit Kommentaren zum besseren Verständnis versehen. Bevor Sie die Seite dann speichern, tragen Sie noch die Javascript-Funktion showChart() bei Execute when Page Loads ein - damit der Chart auch wirklich dargestellt wird.

function showChart() {

  // D3 Hilfsfunktion - Leitet eine HTML-Farbe aus einem numerischen
  // oder String-Wert ab.
  var color = d3.scale.category20();  

  // Das "Pack Layout" erzeugt den Bubble-Chart
  // Die Klasse berechnet für jeden Knoten in den JSON-Daten die Position und den Radius
  // eines passenden Kreises. Das konkrete Setzen des Kreises findet weiter unten statt.
  var pack = d3.layout.pack()
    .size([$("#chart").width(),$("#chart").height()])  
       // Größe entspricht der Regionsgröße (#chart entspr. id="chart")
    .sort(function (a,b) {return 1;})   
       // Sortierungsfunktion "1" - also keine Sortierung
    .value(function(d) { return d.sal; })
       // "value" legt die Größe der Blase fest - aus dem "sal" abgeleitet
    .padding(1.5);
       // 1.5 Punkt Abstand zwischen den Blasen
  

  // Hier wird ein "SVG-Container" <svg></svg> innerhalb des DIV Containers 
  // eingehängt. Dieser nimmt die SVG-Elemente des D3-Diagramms auf. Die Größe  
  // entspricht wiederum der des DIV-Containers.
  var svg = d3.select("#chart").append("svg")
    .attr("width",  $("#chart").width())
    .attr("height", $("#chart").height())
  ;
  
  // Dies ist eine APEX-Javascript-Funktion. Die JSON-Daten werden per
  // Aufruf des erstellten AJAX-Callbacks "getEmpBubbleData" abgeholt. Wenn die Daten 
  // da sind, werden sie an die folgende Javascript-Funktion "addData" übergeben.
  apex.server.process(
    'getEmpBubbleData',
    {},   
    {
      success: addData,
      dataType: "json"
    }
  );
  
  function addData(root) {
    // Dieser Code entspricht in der Bubble Chart Vorlage von Mike Bostock
    // dem Inhalt des d3.json Aufrufs in Zeile 28. Die JSON-Daten werden
    // mit der SVG-Region und dem "Pack" Layout verknüft. Der gleiche
    // Code kann auch ein Diagramm aktualisieren, dess es findet ein "Join"
    // zwischen bereits darstellten Daten und neuen Daten statt. Das ist
    // einer der Mehrwerte des D3-Frameworks.
 
    // Die Variable "node" enthält eine "Collection" der Knoten in den
    // JSON-Daten. Hier also für jede Zeile der Tabelle EMP zzgl. des
    // Wurzelknotens. Alle Operationen auf der Variable "node" werden
    // auf alle Elemente der Collection angewendet.
    var node = svg.datum(root).selectAll("g")
      .data(pack.nodes)
      .enter()
      .append("g")
      .attr("class",     function(d) { return d.children ? "nonleaf" : "leaf"; })
      .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
      .attr("style",     "opacity: 0")
    ;

    // Zu Beginn (siehe drei Zeilen vorher) steht die Opacity auf 0 - die Elemente
    // sind also nicht sichtbar. Nun wird eine "transition" hinzugefügt. Über zwei
    // Sekunden wird die "Opacity" von 0 auf 100 gebracht. Das Ergebnis ist ein
    // "fade in" des Diagramms.
    node.transition()
      .attr("style", "opacity: 100")
      .duration(2000)
    ;

    // Hier passiert es: Der Knoten wird mit einem SVG-Element "circle", also
    // einem Kreis versehen. Man sieht, wir das Attribut "r", also der Radius
    // gesetzt wird. Hier wird einfach der errechnete Wert aus dem Layout
    // genommen - man könnte aber auch etwas anderes verwenden.

    // Zur Festlegung der Fill-Farbe wird das eingangs erstellte "color" Objekt
    // verwendet. Es macht einfach aus dem String des JOBS eine Farbe. Man muss
    // sich also nicht darum kümmern, genug Farben definiert zu haben. 
    // Das Wurzelelement soll nicht darstellt werden (fill = "none")
    node.filter(function(d) { return !d.children; }).append("circle")
      .attr("r", function(d) { return d.r; })
      .style("fill", function(d) { return color(d.job); })
    ;

    // Hier wird jede Blase mit Text versehen. Dazu dient das SVG-Element <text>
    node.filter(function(d) { return !d.children; }).append("text")
      .attr("dy", ".3em")
      .style("text-anchor", "middle")
      .text(function(d) { return d.ename.substring(0, d.r / 3) +  " " + d.sal; })
    ;
  }
}

Der fertige Bereich Javascript innerhalb der Seitenattribute sollte dann etwa wie in Abbildung 9 aussehen.

Javascript Bereich der Seite: fertig.

Abbildung 9: Javascript Bereich der Seite: fertig.

Speichern Sie die Seite nun ab und starten Sie sie. Sie erhalten einen fertigen D3 Bubble Chart - auf Ihre eigenen Tabellendaten - wie in Abbildung 10 dargestellt.

Ein fertiger Bubble Chart auf die Tabelle EMP

Abbildung 10: Ein fertiger Bubble Chart auf die Tabelle EMP

Man sieht deutlich, dass das Erstellen eines Diagramms mit D3 einen Programmieraufwand mit sich bringt. Im Gegenzug erhält man als APEX-Entwickler eine nahezu unerhörte Freiheit beim Erstellen der Diagramme. Da man die SVG-Elemente direkt setzt und alle Attribute steuern kann, ist auch das Verändern kleinerer Details wie Liniendicken oder des Abstands der Blasen voneinander überhaupt kein Problem mehr. Im Zweifel lässt sich alles mit Javascript-Code und den Funktionen des D3-Frameworks umsetzen.

Auf die gleiche Art und Weise können Sie nun übrigens auch andere D3-Diagrammtypen umsetzen. Nimmt man die Beispiele des D3js Autors Mike Bostock als Vorlage, ist die Vorgehensweise immer gleich.

  • Es braucht eine Datenquelle im JSON-Format. Hier kann man mit einem AJAX-Callback arbeiten. Achten Sie dabei darauf, dass das JSON-Format auch zum verwendeten Diagrammtyp passt - unterschiedliche Diagrammtypen erwarten unterschiedliche JSON-Formate. PL/SQL auf der Serverseite kann allerdings sehr gut mit Javascript im Browser zusammenspielen. Es spricht nichts dagegen, mit PL/SQL "vorläufigen" JSON-Code zu generieren, der dann erst im Browser in das für das Diagramm korrekte Format gebracht wird.
  • Es braucht eine APEX Region vom Typ HTML mit einem DIV-Container, dessen Größe mit Style-Attributen definiert ist und der per "id"-Attribut für Javascript referenzierbar ist.
  • Schließlich muss der Javascript Code hinterlegt werden, welcher die Daten vom AJAX-Callback holt und das D3-Diagramm erzeugt. Hier nimmt man ebenfalls am besten die Vorlagen von Mike Bostock als Ausgangspunkt und passt sie Schritt für Schritt an die eigene Umgebung an.
  • Zum Abschluß muss die erzeugte Javascript-Funktion on Page Load aufgerufen werden.

Wer noch andere Diagrammtypen "in Aktion" sehen möchte, sei auf diese APEX-Beispielapplikation verwiesen: https://apex.oracle.com/pls/apex/f?p=19660:1. Darin sind vier D3-Diagrammtypen beispielhaft umgesetzt: Bubble-Chart, Tree Map, Force-Directed Graph und Sunburst Partition. Wer kein Javascript programmieren möchte, findet fertige D3-Plugins auf dieser Seite: https://apex.oracle.com/pls/apex/f?p=25787:2. Dort stehen einfach bedienbare APEX-Plugins für D3 Bubble- und Sunburst Diagramme bereit.

Weitere Informationen finden Sie hier:

Zurück zur Community-Seite