04. August 2015 · von Joscha Karger

Wasserfalldiagramm mit Chart.js

Kürzlich stellte sich mir die Aufgabe, die Veränderungen einiger Unternehmensdaten in einem Wasserfalldiagramm im Browser darzustellen. Um dabei auf eine fundierte Basis zu setzen, bot sich das JavaScript-Framework Chart.js an, das zwar keine Möglichkeit besitzt, Wasserfalldiagramme zu erstellen, jedoch mit seinen Säulendiagrammen wichtige Grundlagen für ein Wasserfalldiagramm liefert. Chart.js bietet die Möglichkeit, einfach sowohl Erweiterungen als auch neue Diagrammtypen zu erstellen. Dementsprechend habe ich einen neuen Diagrammtyp für Wasserfalldiagramme und zusätzlich eine Erweiterung der Rectangle-Klasse erstellt.

Im Folgenden werde ich das Erstellen neuer Diagrammtypen, sowie das Erweitern vorhandener Klassen in Chart.js erläutern.

Gate 4 - Wasserfalldiagramm
Gate 4 – Wasserfalldiagramm

 

Einen eigenen Diagrammtypen erstellen

Um einen eigenen Diagrammtyp in Chart.js zu erstellen, erweitert man die globale Chart-Klasse. Dazu gibt man neben eigenen Methoden, einen Namen, Optionen, eine Initialisierungsmethode und eine Zeichenmethode an. Was diesen Teil angeht, so liefert die Dokumentation von Chart.js eine nette und kurze Beschreibung, sowie nützliches Anschauungsmaterial in Form eines kleinen Code-Beispiels. Für mein Wasserfalldiagramm sieht mein Code also zunächst so aus:

(function(){
    var root = this,
        Chart = root.Chart,

    var defaultConfig = {};

    Chart.Type.extend({
        name: "Wasserfall",
        defaults: defaultConfig,
        initialize: function(data){} ,
        draw: function(){}
    })
}).call(this);

Hierbei ist defaultConfig ein Array, der später verschiedene Variablen enthalten soll, die für die zukünftige, angepasste Verwendung konfigurierbar bleiben sollen.

Für alles Weitere habe ich mich stark am bereits vorhandenen Säulendiagramm orientiert. Mein Wasserfalldiagramm soll Start- und Endwert, sowie jede Änderung als Säule anzeigen. Die Y-Position dieser Änderungssäulen ist dabei abhängig vom vorhergehenden Wert.
Letztendlich erwarte ich an Daten: Labels, als Namen der einzelnen Spalten und einen Datensatz mit Konfigurationen und darzustellenden Daten. Dementsprechend könnte ein Datensatz z.B. so aussehen:

var waterfallData = {
    labels : ["Start", "Änderung 1", "Änderung 2", "Endwert"],
    dataset: {
        //Hier ein paar Konfigurationen
        strokeColor : "black",
        fillColor : "red",
        ...
        data : [50, -10, 60]
    }
}

Meine Daten enthalten nun vier Labels, ein paar Konfigurationen und drei Zahlenwerte als eigentliche Daten. Dabei habe ich ein Label mehr angegeben, als Datenwerte gibt, um noch eine Säule zu erzeugen, die rein den Endwert ohne Änderung angibt. In den Daten ist 50 der Startwert und -10 ist die erste Änderung, also der Startwert wird um 10 reduziert. Die zweite Änderung gibt an, dass der aktuelle Wert, also 40, um 60 erhöht wird. Somit ergibt sich ein Endwert von 100.

Die initialize-Methode wird aufgerufen, wenn das Diagramm initialisiert wird und ist somit der perfekte Platz, um Erweiterungen bereits vorhandener Klassen vorzunehmen und den gegebenen Datensatz für das spätere Zeichnen des Diagramms auszuwerten. So können nun z.B. eigene Methoden zur Berechnung der Positionen der einzelnen Säulen als Erweiterung der Scale-Klasse definiert werden:

this.ScaleClass = Chart.Scale.extend({
    calculateWaterfallBarX : function(barIndex){
        //Hier die X-Position der einzelnen Säulen anhand des Indexes berechnen
    }
})

Alternativ können aber auch Methoden von Chart.js genutzt werden.

Nun zur draw-Methode. Nutzt man die von Chart.js standardmäßig für Säulendiagramme genutzte Rectangle-Klasse, sollte man in der initialize-Methode für jede zu zeichnende Säule ein Objekt erstellen, das eine Erweiterung der Rectangle-Klasse ist. Dadurch kann nun in der eigenen Draw-Methode die Draw-Methode des Rectangle-Elements aufgerufen werden, was einem etwas Arbeit erleichtert.
In meinem Fall habe ich sogar eine eigene Erweiterung des Rectangle-Elements hierfür genutzt. Darauf komme ich später noch einmal zurück. Für jetzt ist es im Grunde nur wichtig, jedes mal die X- und Y-Position der zu zeichnenden Säule neu zu bestimmen, genauso wie die Höhe der Säule zu kalkulieren und diese zu zeichnen. Für den Fall, dass wie beim Säulendiagramm von Chart.js noch ein Rahmen samt zugehörigem Raster gewünscht ist, lassen sich diese ganz einfach mit der draw-Methode der Scale-Klasse zeichnen. Das Raster sollte jedoch vor den Säulen gezeichnet werden, um zu vermeiden, dass diese übermalt werden. In meinem Fall war es gewünscht, dass die einzelnen Säulen mit einer Linie verbunden sein sollen, sodass ein Rahmen über das gesamte Diagramm entsteht. Diese Linie habe ich an dieser Stelle ebenfalls hinzugefügt.

Neben der draw- und initialize-Methode ist es durchaus sinnvoll, weitere Methoden zu erstellen, wie z.B. eine update- oder eine resize-Methode.

Einen Diagrammtypen erweitern
Nun möchte ich das Erweitern eines bereits vorhandenen Typs oder einer bereits vorhandenen Klasse behandeln. Dazu nehme ich das oben genannte Beispiel der Erweiterung der Rectangle-Klasse von Chart.js. Mit einer Erweiterung der Rectangle-Klasse habe ich so eine eigene Rectangle-Klasse im Sinne meines Wasserfalldiagramms erstellt. Dies ist im Grunde genommen noch simpler, als das Erstellen eines neuen Typs. Ich denke ein Beispiel verdeutlicht besser, worum es geht:

Chart.WaterfallRectangle =  Chart.Rectangle.extend({
/*Ich überschreibe die Draw-Methode um unterschiedliche Farben für Steigerungen und Senkungen des 
Wertes zu verwenden */
draw: function(fillColor){
    var ctx = this.ctx,
        halfWidth = this.width/2,
        leftX = this.x - halfWidth,
        rightX = this.x + halfWidth,
        top = this.base - (this.base - this.y),
        halfStroke = this.strokeWidth / 2;

    if(this.showStroke)
    {
        leftX += halfStroke;
        rightX -= halfStroke;
        top += halfStroke;
    }

    ctx.beginPath();
    //Hier ist die eigentliche Änderung
    ctx.fillStyle = fillColor;
    ctx.strokeStyle = this.strokeColor;
    ctx.lineWidth = this.strokeWidth;

    ctx.moveTo(leftX, this.base);
    ctx.lineTo(leftX, top);
    ctx.lineTo(rightX, top);
    ctx.lineTo(rightX, this.base);
    ctx.fill();

    if(this.showStroke)
    {
        ctx.stroke();
    }
}
});

Bis auf die angegebene Änderung ist das obige Codebeispiel deckungsgleich mit dem, was Chart.js mit der Rectangle-Klasse liefert. Durch die Änderung kann ich nun jedes mal, wenn ich die Methode aufrufe eine Farbe mit übergeben, um die Säulen individuell zu färben.
Zu beachten ist bei einer Erweiterung, dass dabei die entsprechende Klasse oder der entsprechende Diagrammtyp erweitert wird und nicht die Typ-Basis-Klasse.

//Neuer Diagrammtyp
Chart.Type.extend({
    name:"myDiagram",
    ...
})

//Erweiterung eines vorhandenen Diagrammtyps
Chart.types.myDiagram.extend({
    name:"myExtendedDiagram",
    ...
})

Wie oben gezeigt ist es also recht unkompliziert, eine Erweiterung oder einen neuen Diagrammtypen für Chart.js zu erstellen. Jedoch sollte man sich darauf einstellen, sich oft auch mit dem Chart.js Code auseinanderzusetzen, da es nur wenige Alternativen gibt, herauszufinden, wie dieser arbeitet, wenn man nicht alles selbst schreiben möchte.



Diesen Blogeintrag bewerten:

2 Stimmen mit durchschnittlich 3/5 Punkten

Haben Sie Fragen zu diesem Artikel oder brauchen Sie Unterstützung?

Nehmen Sie mit uns Kontakt auf!

Wir unterstützen Sie gerne bei Ihren Vorhaben!


Schreibe einen Kommentar

Kontakt.
Lassen Sie sich von uns beraten
Wir freuen uns über Ihr Interesse an unseren Leistungen. Hinterlassen Sie
uns Ihren Namen, Ihre Telefonnummer und E-Mail Adresse – wir melden
uns schnellstmöglich bei Ihnen.
Kontakt aufnehmen