Canvas

JavaScript: Animiertes Säulendiagramm mit HTML Canvas

1. Juli 2009
von

Mit HTML Canvas braucht man nicht mehr auf Tricks zurückgreifen, sondern kann direkt in JavaScript punktgenaue Grafiken zeichnen. Wir stellen das anhand eines animierten Säulendiagramms vor. Schauen wir uns an wie Rechtecke inklusive Farbverlauf gezeichnet werden.

In JavaScript können mit HTML Canvas im Webbrowser komplexe Grafiken programmiert werden – dazu stehen Methoden wie fillRect(),bezierCurveTo() und createLinearGradient() zur Verfügung. Am Beispiel eines animierten Säulendiagramms stellt dieses Tutorial die Grafikprogrammierung mit HTML Canvas vor und beschreibt wie Rechtecke inklusive Farbverlauf gezeichnet werden sowie der Ursprung des Koordinatensystems verschoben wird.

Beispiel: Animiertes Säulendiagramm — Live Demo plus Quelltext (neues Fenster)

Welche Webbrowser unterstützen HTML Canvas?

HTML Canvas wurde von Apple mit dem Dashboard von Mac OS X eingeführt. Heute wird es unter anderem von den Webbrowsern Chrome, Safari 1.3+, Firefox 1.5+, Konqueror und Opera 9.00+ unterstützt. Für den Internet Explorer steht HTML Canvas mit dem ExplorerCanvas-JavaScript von Google zur Verfügung.

Spezifiziert ist HTML Canvas derzeit als Entwurf in HTML 5 von der WHATWG und dem W3C.

Rechteck zeichnen

Erster Schritt für das animierte Säulendiagramm ist das Zeichnen eines gefüllten Rechtecks mit fillRect():

fillRect(in float x, in float y, in float w, in float h)

Die ersten beiden Werte x, y geben die linke obere Ecke des Rechtecks und die beiden folgenden w, h die Breite und Höhe des Rechtecks an. Alle Angaben erfolgen dabei in der Punkteinheit des Koordinatensystems von Canvas, dessen Ursprung links oben liegt:

var canvas = document.getElementById('canvas');
    if(canvas && canvas.getContext) {
        var ctx = canvas.getContext('2d');
        if(ctx) {
            ctx.fillRect(50, 25, 100, 150); // x, y, w, h
        }
}

Beispiel: fillRect() — Live Demo plus Quelltext (neues Fenster)

Canvas und der Grafikkontext

Im Beispiel sieht man, dass fillRect() und auch alle anderen Zeichenmethoden nicht direkt zum Canvas-Objekt gehören, sondern in einen 2D-Grafikkontext liegen, den man mit ctx = canvas.getContext(’2d’) erhält. Dadurch wird das Canvas-Element flexibler und lässt sich z.B. auch für 3D-Grafik nutzen: moz-glweb20- und opera-3d-Kontext.

Das HTML Canvas-Element selbst wird mit dem Startag <canvas>, dem Endtag </canvas> sowie der Breite und Höhe des Elements im width- und height-Attribut definiert:

<canvas id="canvas" width="200" height="200">
</canvas>

Webbrowser ohne Canvas-Unterstützung zeigen alles an, was zwischen dem Start- und End-Canvas-Tag liegt. Dies sollte man für sinnvolle Fallbacklösungen nutzen. Im Fall eines Diagramms zum Beispiel für ein Bild:

<canvas id="canvas" width="510" height="200">
  <p><img src="image/Umsatz_2042.png" width="510" height="200" alt="Umsatz 2042" /></p>
</canvas>

Ursprung verschieben

Um zur Ausgabe der aufrechtstehende Säulen die Koordinaten nicht selbst umrechnen zu müssen, wird die Transformationsmatrix verändert. Dazu wird der Ursprung mit translate(20, canvas.height – 30) in den unteren Bereich der Zeichenfläche verschoben und die Koordinaten mit scale(1, -1) an der x-Achse gespiegelt.


Beispiel: translate() — Live Demo plus Quelltext (neues Fenster)

Farbverlauf

Farbverläufe sind eine einfache Möglichkeit, eine Grafik ansprechender aussehen zu lassen. Ein linearer Farbverlauf wird im Canvas mit createLinearGradient(x1, y1, x2, y2) erzeugt. Die x/y-Koordinaten geben den Start- und Endpunkt der Linie an über die sich der Farbverlauf erstreckt.

Wichtig ist dabei, dass diese Punkte in dem Bereich liegen, in dem das Zeichnen erfolgt. Im Fall der maximal 120 Punkte hohen Säulen ist dies eine Linie von 0, 0 nach 0, 120 zum Füllen der Säulen von unten nach oben. Liegen die Endpunkte des Farbverlaufs außerhalb der gezeichneten Figur, dann bleibt die Figur einfarbig wie das createLinearGradient()-Beispiel zeigt:

Beispiel: createLinearGradient() — Live Demo plus Quelltext (neues Fenster)

Die Farben des Farbverlaufs werden mit addColorStop(offset, color) gesetzt. Mit offset wird die Position der Farbe auf der Linie beschrieben. Dieser Wert liegt zwischen 0.0 und 1.0. 0.0 steht für den Startpunkt der Linie, 1.0 für den Endpunkt. Die Farbe wird als String mit einem CSS 3-Farbwert angegeben. Folgender Programmcode definiert einen Farbverlauf von Gelb, nach Rot in der Mitte, zu Blau im Endpunkt:

var fill = ctx.createLinearGradient(0, 0, 0, canvas.height - 1);

fill.addColorStop(0.0, 'yellow');       // Gelb
fill.addColorStop(0.5, '#ff0000');      // Rot
fill.addColorStop(1.0, 'rgb(0,0,255)'); // Blau

Beispiel: addColorStop() — Live Demo plus Quelltext (neues Fenster)

Status der Transformationsmatrix für Hintergrund retten

Während für die Säulen der Ursprung in den unteren Bereich verschoben wird, soll sich der Farbverlauf für den Hintergrund dagegen über den gesamten Canvas von der linken oberen bis zur rechten unteren Ecke erstrecken. Es wird also einmal eine Transformationsmatrix für den Ursprung links oben im Canvas-Element (die vorgegebene Transformationsmatrix) und eine zweite für das Zeichnen der Säulen mit dem verschobenen Ursprung benötigt. Dies kann man im Canvas leicht mit den save()- und restore()-Methoden erreichen:

save() rettet alle Eigenschaften des Canvas auf einen Stack. Dazu gehören u.a. die gesetzten Farben, die Strichstärke, die Schattenwerte und die Transformationsmatrix.

restore() holt diese Werte wieder vom Stack runter und setzt die Eigenschaften auf ihre zuvor geretteten Werte zurück. Damit reduziert sich der Wechsel des Ursprungs auf folgenden Programmcode:

Hintergrund zeichnen;

save(); // Ursprung retten

  translate(); // Ursprung verschieben
  scale();     // Koordinaten spiegeln

  Säulen zeichnen;

restore(); // Ursprung wiederherstellen

Beispiel: Animiertes Säulendiagramm — Live Demo plus Quelltext (neues Fenster)

Animation

Im letzten Schritt werden die Säulen animiert. Eine einfache Schleife für Animationen ist folgende:

forever {
  Neue Positionen berechnen;
  Objekte zeichnen;
  40 Millisekunden warten; // für 25 Bilder/Sekunde
}

In JavaScript gibt es jedoch keine Methoden wie waitTime() oder delay(), um ein Skript 40 Millisekunden warten zu lassen. Stattdessen stellt JavaScript setTimeout() und setTimeInterval() zur Verfügung:

timer = setTimeout(expression, timeout) führt nach Ablauf der in Millisekunden angegebenen timeout-Zeit einmalig den JavaScript-Ausdruck expression aus.

Beispiel: setTimeout() — Live Demo plus Quelltext (neues Fenster)

timer = setInterval(expression, interval) führt dagegen den Ausdruck im Abstand von interval Millisekunden wiederholt aus, bis der so gestartete Zeitgeber mit clearInterval(timer) wieder gelöscht wird – genau das, was für eine Animation benötigt wird.

function startTimer() {
    timer = setInterval(logTimeDiff, 1000 / 25);
}

function logTimeDiff() {
    if(count++ > 20) {
        clearInterval(timer); // Zeitgeber stoppen
    }

    // Zeitdifferenz ausgeben
}

Beispiel: setInterval() — Live Demo plus Quelltext (neues Fenster)

Stopp nach 2 Sekunden Laufzeit

Die Säulenanimation soll nicht endlos lange laufen, sondern nach 2 Sekunden sollen die Säulen komplett sichtbar sein. Zum Stoppen der Animation merkt sich das Skript daher mit startTime = new Date().getTime() den Startzeitpunkt der Animation, vergleicht ihn in animate() mit der aktuellen Zeit und beendet die Animation durch das Löschen des Timers mit cleanInterval(timer).

var timer    = null; // JavaScript Timer
var duration =  2;   // Laufzeit in Sekunden
var fps      = 25;   // Bilder pro Sekunde

function animStart() {
    startTime = new Date().getTime();
    timer = setInterval(animate, 1000 / fps);
}

function animate() {
    var diffTime = new Date().getTime() - startTime;

    // Ende?
    if(diffTime >= 1000 * duration) {
        clearInterval(timer);
    }

    draw();
}

Flüssige Animation

Am setInterval()-Beispiel wird man wohl auch auf dem eigenen Rechner sehen können, dass die logTimeDiff()-Funktion nicht immer exakt nach 40 Millisekunden aufgerufen wird:

  • Timer start
  • 47
  • 49
  • 40
  • 41
  • 39
  • 57
  • 40

Die Unterschiede ergeben sich aus Faktoren wie der Genauigkeit des Zeitgebers des Betriebssystems, dem Verhalten des Webbrowsers, dem was sonst noch auf dem Rechner und im Webbrowser gerade abläuft. Damit die Animation trotzdem möglichst flüssig aussieht benutzt das Skript einen Skalierungsfaktor, der abhängig von der verstrichenen Zeit zwischen 0.0 und 1.0 liegt und in animate() berechnet wird: scale = diffTime / (1000 * duration). Mit diesem Faktor wird die Höhe der Säulen gezeichnet: fillRect(0, 0, 30, scale * value)

Am Ende scale auf 1.0 setzen

Da durch die Ungenauigkeit des Zeitgebers die verstrichenen Zeit am Ende über 2 Sekunden liegen kann und der Skalierungsfaktor dann größer als 1.0 ist, wird er zum Schluss auf den gewünschten Endwert von 1.0 gesetzt. So wird das Skript robust gegenüber dem ungenauen Zeitgeber und das Diagramm zeigt keine falschen Werte an:

// Ende?
if(diffTime >= 1000 * duration) {
    scale = 1.0; // Auf 1.0 setzen, damit die Säulen am Schluss mit
                 // Sicherheit mit dem richtigen Wert gezeichnet werden
    clearInterval(timer);
}

Namensraum per Objektliteral

Damit das fertige Skript nicht mit anderen gleichzeitig auf einer HTML-Seite eingebundenen Skripts kollidieren kann, ist es mit Hilfe des Objektliterals im eigenen Namensraum eingeschlossen. Aus:

var timer     = null; // JavaScript Timer
var startTime =  0;   // Startzeitpunkt der Animation
var fps       = 25;   // Bilder pro Sekunde

function anim() {
}

function animStart() {
  startTime = new Date().getTime();
  timer = setInterval(animate, 1000 / fps);
}

Wird im Objektliteral:

var bc = {
   timer    : null, // JavaScript Timer
   startTime:  0,   // Startzeitpunkt der Animation
   fps      : 25;   // Bilder pro Sekunde

   anim: function() {
   },

   animStart: function() {
       bc.startTime = new Date().getTime();
       bc.timer = setInterval(bc.animate, 1000 / bc.fps);
   }
};

Tipp: Das Objektliteral und darüber hinausgehende Möglichkeiten für den Aufbau von JavaScripts beschreibt Mathias Schäfer in Organisation von JavaScripten.

Zusammenfassung

Beispiel: Animiertes Säulendiagramm — Live Demo plus Quelltext (neues Fenster)

Dieses Tutorial hat anhand eines animierten Säulendiagramms vorgestellt wie in HTML Canvas direkt mit fillRect() gezeichnet, ein Farbverlauf mit createLinearGradient() und addColorStop() beschrieben, der Ursprung und die Ausrichtung der Koordinatenachsen mit translate() und scale() verändert, mit Hilfe von setInterval() die Ausgabe animiert und der Programmcode mit Hilfe des Objektliterals im eigenen Namensraum eingeschlossen wird.

Die Demos, Anwendungen und Tutorials in der Linkliste zeigen weitere Möglichkeiten des HTML Canvas-Elements.

Material zum Download

Zip-Archiv mit allen ausführbaren Beispielen.

Links

**** 38911 BASIC BYTES FREE ****

READY.
[]

16 Kommentare zu „JavaScript: Animiertes Säulendiagramm mit HTML Canvas
  1. Turicon am 1. Juli 2009 um 10:23

    Vielen Dank für den Beitrag. Bis wir das aber wirklich “ohne Probleme” einsetzen können, werden noch viele Jahre ins Land gehen. Wozu soll ich den Aufwand betreiben, wenn ich eh noch eine “Fallback-Grafik” einbinden muss?

    Habe es eben erst wieder in einer größeren Firma und deren Partner erlebt: einzig verfügbarer Browser an den Arbeitsplatz-PCs? Klar, IE6! Und natürlich ohne JavaScript.

    Irgendwie fehlt mir dann das Verständnis für die Admins dieser Netzwerke, immer noch (und überhaupt) diesen Browser einzusetzen…

  2. Wolfgang am 1. Juli 2009 um 10:38

    @Turicon: Die Admins folgen dem Leitspruch: “Never change a running system”. Das Problem ist, dass mit “running” keine Rücksicht auf Webseiten genommen wird. Und mit deaktiviertem JavaScript sind sie auch gleich das Problem möglicher Sicherheitslücken los. Auch heutzutage empfehlen ja immer wieder mal irgendwelche Fachzeitschriften oder andere Quellen JavaScript abzuschalten.

  3. Frank Mey am 1. Juli 2009 um 11:06

    DeFacto-Standard bei Mittelständlern ist IE 6 , in 50% der Fälle mit Javascript.

  4. Andreas F. am 1. Juli 2009 um 11:12

    Wenn man Firmen als Zielkunden/Besucher avisiert, ist es eigentlich immer eine gute Idee, wenn Webseiten auch ohne Javascript funktionieren. Das Plugin “Noscript” wird ja auch immer beliebter.

    Welche Fachzeitschriften empfehlen denn, Javascript abzuschalten? Das wäre ja mal ein Grund, in diese dann vielleicht doch nicht ganz schlechten Zeitschriften einen Blick zu werfen…

  5. Frank am 1. Juli 2009 um 11:46

    Es gibt für alles Gesetzte…..wieso keins um den IE6 ENDLICH zu entfernen? ;)

  6. Frank Mey am 1. Juli 2009 um 14:29

    Für IE6 eine abgespeckte Version der Seite anzeigen lassen und damit sanften Upgrade-Druck ausüben könnte den Prozess etwas beschleunigen.

  7. Oliver am 1. Juli 2009 um 15:22

    Man kann ja auch Tabellen und CSS verwenden um schicken Säulendiagramme zu machen. Wenn man das will, kann man diese ja auch animieren, indem man padding und margin ändern. vom “Aufwand” her kommt das aufs gleiche raus wie mit Canvas, nur dass man sich das Fallback sparen kann.

    kein JS => keine Anmination
    keine Grafiken => Alles ohne Verläufe
    kein CSS => Daten in Tabelle

    Das habe ich z. B. da verwendet (ohne Animation der Säulen)
    -> http://parteigezwitscher.de/#stats_hl

    Das Ding ist eine ganz normale Tabelle mit ein paar CSS Tweaks.

  8. Oliver am 1. Juli 2009 um 23:47

    Ich habe es jetzt mal so geändert, dass die Balken in der Tabelle noch animiert werden, indem das padding der spans um die Werte geändert wird. Es funktioniert in allen getesteten Browsern wunderbar. :-)

    Getestet:
    - IE 6/7/8
    - FF 2/3
    - Opera 9.5/9.6/10
    - Safari 4
    - Chrome 2

    Für die Animation einfach einen anderen Zeitraum wählen. Beim normalen Aufruf wird nichts animiert, weil das Javascript erst ganz unten auf der Seite geladen wird und dann macht die Animation keinen Sinn. Und ohne Javascript ist halt nichts animiert und die Balken sofort da.

  9. Frank Mey am 2. Juli 2009 um 09:22

    @Oliver:
    Tabellen für das Darstellen von Werten zu benutzen, ist ja eigentlich im Sinne des Erfinders. Diese Werte aber in der padding-Höhe der Span-Klasse zu verstecken ist schon grenzwertig. Was soll ein Google-Bot mit so einer Darstellung anfangen ?

  10. Oliver am 2. Juli 2009 um 09:46

    Die Werte stehen doch im Quelltext der Seite und Google juckt doch das CSS nicht!? Link mal angeklickt? :-)

    Google dürfte eher noch dankbar sein, weil die Werte strukturiert sind. Der Hauptgedanke war aber, dass ich die Seite auch mit einem “Konsolenbrowser” lesbar halten wollte.

  11. reto kuhn am 17. Dezember 2009 um 10:13

    Google ExplorerCanvas bringt die HTML5 Canvas Funktionalität in den Internet Explorer.
    http://code.google.com/p/explorercanvas/

  12. Felix.S am 18. Februar 2010 um 18:02

    Sicherheitslücken und Javascript: das ich nicht lache!
    Mit Javascript kann man allerhöchstens den Browserverlauf bedingt auslesen.

    Und Im Moment ist mir der IE sowas von egal!
    Ich programmiere die Site z.Zt. nur für mich und will nun mal ein Malprogramm mit Canvas probieren zu schreiben.

  13. [...] JavaScript: vertikale Balken darstellen?   Heute, 13:21 Vielleicht hilft dir ja dies oder dies oder dies [...]

  14. rendro am 4. Oktober 2010 um 15:39

    Ich hab grad im Rahmen einer Projektarbeit meiner Uni eine Javascript-Bibliothek geschrieben mit der man sehr einfach Formen auf dem canvas Element zeichenn und animieren kann.

    In den Beispielen hab ich auch ein Chart mit drin. Schaut es euch doch mal an ;) Wenn ihr was mit dem canvas element machen wollt, dann spart ihr euch mit “cajal” viele Zeilen Code!

    Das ganze is open-source. Also probiert es einfach mal aus und wenn ihr findet, dass es Verbesserungswürdig ist, dann schreibt einfach ein “issue” oder forked es und macht nen push request :P

    Grüße, rendro

    http://github.com/dotsunited/cajal

  15. Franzisca am 14. Juni 2014 um 19:55

    Super elegante Lösung, die ich gerne in meine Seite einbauen möchte.

    Nun aber zu folgender Problematik.
    Ich habe x1-xn Links, die jeweils ein Diagramm ausgeben sollen.
    Sprich Link1 zeigt Diagramm1; Link2 zeigt Diagramm2 usw.
    Das Ganze kann ich per Parameterübergabe bei onClick realisieren, jedoch scheitere ich dabei, die Values entsprechend anzupassen.
    Damit ich nicht für jedes Digramm das Skript neu einbinden muss, möchte ich einfach nur die values austauschen.
    An dieser Position: values : [87, 70, 90, 50, 105, 120, 75, 83, 110, 76, 100, 120], // Angezeigte Werte
    werden die Werte eines Diagramms angezeigt, gerne hätte ich aber etwas in einer Form wie:

    values : [87, 70, 90, 50, 105, 120, 75, 83, 110, 76, 100, 120];
    [87, 70, 90, 50, 105, 120, 75, 83, 110, 76, 100, 120];
    [87, 70, 90, 50, 105, 120, 75, 83, 110, 76, 100, 120],

    Wie würde müsste das syntaktisch richtig aussehen und wie die Schleife dazu?

    Versuch (der wissentlich nicht stimmt):
    switch (values){
    case “1″: [x1, x2,...,xn];
    break;
    case “2″: [y1, y2,...,yn];
    break;
    case “n”: [z1, z2,...,zn];
    break;
    default:
    echo (“Es sind keine Werte vorhanden”);
    }
    So in etwa hätte ich das gerne. Aber wie?

Ein Kommentar? Schön!

Wir freuen uns immer über Leser, die durch nützliche und konstruktive Beiträge zum Thema eine Diskussion anstoßen oder den Artikel mit weiteren Informationen anreichern. Alle Kommentare werden in diesem Sinne moderiert. Zum Kommentar-Fairplay gehört für uns auch der Einsatz von rel="nofollow". Bitte verwenden Sie zudem als Namen weder eine Domain noch ein spamverdächtiges Wort. Vielen Dank!