Wie beginnt man einen Artikel, den man so gar nicht schreiben wollte..? Tatsächlich war es der Zufall, der zu diesem kleinen Benchmark einiger JavaScript-Chart-Libraries führte. Für mein aktuelles, kleines Programmierprojekt war ich auf der Suche nach möglichst einfach benutzbaren Diagramm- bzw. Chart-Libraries zur Darstellung in einer Web-UI, was insofern die Nutzung von JavaScript nahelegt.
Gesucht: Diagramm- / Chart-Libraries in JavaScript
Wie üblich begann ich die Suche per Suchmaschine, somit fanden sich etliche Artikel nach dem Motto „die top, besten, schönsten, vielseitigsten JavaScript-Chart-Libraries für 2021“ oder ähnliches, insofern sollte es eigentlich nicht schwer sein, passende Kandidaten zu finden. Meine Kriterien waren ebenfalls flugs definiert:
- Open Source Lizenz, bevorzugt MIT oder Apache License. Nichts gegen kommerzielle Anbieter, aber da es sich um ein kleines, privates und ebenfalls nichtkommerzielles Projekt handelt, und die erzeugten Diagramme bislang definitiv kein Core-Feature darstellen, bevorzuge ich doch ein Open-Source-Produkt.
- Aktive (Weiter-)Entwicklung. Für ein neues Projekt möchte man doch gerne auf Komponenten setzen, deren Entwicklung nicht eingefroren oder gar aufgegeben erscheint. Falls der letzte Beitrag zu einer Library etwa vor fünf Jahren stattgefunden hat, ist dies ein Ausschlusskriterium.
- Verfügbarkeit von Standard-Diagrammtypen, mindestens das, was als Line-Chart bezeichnet wird, oder auch eine Timeseries-Darstellung.
- Ansprechendes, modernes Design der Standard-Diagrammtypen. Das Auge isst bekanntlich mit, insofern sollten die erzeugten Charts eben nicht aussehen wie anno 1980 per Schreibmaschine gezeichnet. Natürlich sind die Designs bei den meisten Libraries vielfältig anpassbar, aber damit wollte ich mich – wenn überhaupt, dann später beschäftigen.
Nach diesen Kriterien fiel beispielsweise D3.js bereits heraus. Zwar ist D3.js eine hervorragende JavaScript-Library – oder vielleicht doch eher ein Framework – zur Visualisierung jedwelcher Daten, aber ich wollte die Diagramme nicht erst programmieren müssen. Meine Anforderung war vielmehr, die Daten per Ajax-Request im Hintergrund im JSON-Format zu laden und diese anschließend der Diagramm-Library vor die Füße zu werfen und zu sagen „Hier, mach mal!“, so dass alles Weitere von der Library übernommen wird und ein ansprechendes Diagramm entsteht.
Datenformat und Hintergründe
Das Datenformat ist dabei sehr einfach, es handelt sich um ein Array mit Objekten, in denen jeweils ein Datum und ein Wert angegeben ist. Im Beispiel:
{ results: [ { x: "2021-02-13 00:05:30", y: 10.51 }, { x: "2021-02-13 00:15:32", y: 15.72 }, [...] }
Daraus soll ein Diagramm entstehen, in dem horizontal eine Zeitachse abgebildet wird, zu denen vertikal auf der Y-Achse die Werte enthalten sind. Im besten Fall sorgt die Library selbst für eine sinnvolle Darstellung der Beschriftungen, denn je nach Anzahl der Werte ist es auf der X-Achse kaum möglich, alle Datumsangaben in der übermittelten Form anzugeben. Insgesamt erschien mir die Aufgabe aber dennoch als recht einfach, schließlich findet man Derartiges zuhauf in den Weiten des Web.
Die Kandidaten
In die engere Auswahl hatten es folgende JavaScript Chart-Libraries geschafft:
ApexCharts
ApexCharts punktete auf den ersten Blick mit einem vielversprechenden Design, sehr aktiver Weiterentwicklung (zum Zeitpunkt des Schreibens dieses Artikels erfolgte die letzte Änderung drei Tage zuvor), einer große Auswahl an unterschiedlichen Diagrammtypen, guter Dokumentation, vielen Code-Beispiele etc.. Insofern hinterließ ApexCharts zunächst einen guten Eindruck. Ein wenig getrübt wurde dieser durch den Umstand, dass ApexCharts inzwischen „Partner“ von FusionCharts ist, wobei zu vermuten ist, dass ApexCharts als Basis der kommerziellen Variante FusionCharts fungiert. FusionCharts selbst wird als allumfassende, kommerzielle Lösung für Charts und Dashboards angepriesen, die auch nicht ganz günstig ist. Bei derartigen Konstellationen werde ich immer ein wenig hellhörig bzw. vorsichtig, da die Vergangenheit zeigt, dass zu oft eine Weiterentwicklung eher im kommerziellen Zweig stattfindet, während die Open-Source-Variante vernachlässigt wird. Da die Entwicklung jedoch öffentlich bei GitHub einsehbar ist, es regelmäßig Commits gibt und ApexCharts unter der MIT-Lizenz steht, erschien mir die Gefahr einer „feindlichen Übernahme“ als eher gering.
Chart.js
Chart.js beschreibt sich als simple, flexible JavaScript Chart-Library für Designer und Entwickler. Die Anzahl der unterschiedlichen Chart-Typen ist zwar geringer als bei ApexCharts, aber für den genannten Einsatz vollkommen ausreichend, das Standard-Design gefiel mir ebenfalls. Chart.js besitzt sehr viele Beitragende auf GitHub, der letzte Commit war zum aktuellen Zeitpunkt vor weniger als 24 Stunden, insofern könnte man durchaus von sehr aktiver Weiterentwicklung sprechen. Die Dokumentation ist ebenfalls sehr gut, die Beispiele erscheinen jedoch etwas weniger ausgearbeitet als bei ApexCharts.
Apache ECharts
Apache ECharts, ursprünglich entwickelt von Baidu und erst seit Ende Januar 2021 zum Top-Level-Projekt der Apache Software Foundation befördert, besitzt eine schier unglaubliche Auswahl an unterschiedlichen Diagrammtypen. Mit ECharts dürften sich alle Diagramm-Wünsche erfüllen lassen, inklusive derjenigen, von denen man noch gar nichts wusste. Dazu kommen Tools wie beispielsweise ein Theme-Builder zur Anpassung an die eigene CI, alle Beispiele stehen zur Ausführung in einer interaktiven Umgebung zur Verfügung. Letztlich vermag man Apache ECharts eher mit D3.js zu vergleichen, jedoch mit einem Ansatz auf einer höheren Ebene. Angesichts der Vielzahl an Features für ein simples Diagramm fast zu schade, aber da die Standard-Diagrammtypen allesamt enthalten sind, sollten diese auch gut eingesetzt werden können, insofern kam Apache ECharts mit in die Auswahl.
Sonstiges
Die meisten weiteren JavaScript Chart-Libraries, auf die ich einen kurzen Blick geworfen hatte, waren entweder kommerziell, längere Zeit nicht weiterentwickelt worden, oder schlicht und einfach vom Design her eine Katastrophe. Daher verzichte ich lieber auf Nennung der Namen…
Ein paar erste Schritte mit den Chart-Libraries
Da ich eigentlich nur schnell mal ein paar Werte in einem Diagramm visualisieren wollte, hatte ich gar nicht vor, unterschiedliche Chart-Libraries zu testen. Im Gegenteil, aufgrund aller genannten Eigenschaften schien mir ApexCharts eine gute Wahl zu sein, ein ausgereiftes, gut dokumentiertes Projekt, laut Feature-Liste auch mit guter Performance ausgestattet, noch dazu fand sich in der Dokumentation ein vollständiges Beispiel für das Füllen eines Diagramms per Ajax-Request mit jQuery – besser konnte man die Anforderungen kaum abbilden. Tatsächlich habe ich mich auch bei der Struktur des JSON-Formats an ApexCharts angelehnt. Das Go-(Golang-)Backend erzeugt also jenes Array aus Datumsangabe und Wert, eine Antwort dauert aktuell auf dem Testsystem zwischen 22 und 40ms, je nach Anzahl der Werte.
Die ersten Schritte beim Einsatz einer neuen Library sind bei mir nahezu immer identisch. Im besten Fall ist in der Dokumentation ein Beispiel zu finden, das sich für den Einbau in den eigenen Code eignet. Somit lässt sich das Verhalten der Library in der eigenen Umgebung testen, es lassen sich etwaige Konflikte feststellen usw.. Im konkreten Fall war es mir beispielsweise unwichtig, wie das Ergebnis vom Design her aussieht, schließlich sollte es erst einmal funktionieren, so dass das Diagramm aus den Daten erzeugt werden konnte. Auch bzgl. der Nutzung der zugrunde liegenden JavaScript-Dateien gehe ich in diesem Schritt einen pragmatischen Weg, schließlich stehen die Dateien auf diversen CDNs zur Verfügung, ein Herunterladen zwecks Einbindung ist somit nicht nötig, um die Chart-Library auszuprobieren.
Schnell eingesetzt: ApexCharts
Tatsächlich ist der Code sehr nahe am Beispiel von ApexCharts, zumindest wurde in der ersten Version der Inhalt der Antwort des Ajax-Requests sofort an ApexCharts übergeben. Um die Ergebnisse des Benchmarks nicht zu verfälschen, habe ich dies später so umgebaut, dass zunächst ein Array erzeugt wird, in das die Werte-Paare hinein kopiert werden. Danach wird dieses neue Array ApexCharts zur Verfügung gestellt. Um es vorweg zu nehmen – die zweite Variante inkl. Kopie der Werte war geringfügig schneller.
Der vollständige Code sieht wie folgt aus:
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/apexcharts"></script> <script type="text/javascript"> var options = { chart: { type: 'line', height: 400, toolbar: { show: false, }, }, series: [], title: { text: "TITLE", }, noData: { text: 'Loading...' } } var chartDOM = $("#chart2")[0]; var chart = new ApexCharts(chartDOM, options); chart.render(); var url = '/api/example/query-range/?id=' + myID; axios({ method: 'GET', url: url, }).then(function(response) { // zweiter Versuch mit Kopie in ein Array var chartData = []; response.data.results.forEach((item) => { chartItem = { x: item.x, y: item.y }; chartData.push(chartItem); }); chart.updateSeries([{ name: myTitle, data: chartData }]) // erste Version, direkte Übergabe des Arrays der Response //chart.updateSeries([{ // name: myTitle, // data: response.data.results //}]) }) </script>
Die (kommentierte) erste Version war noch einfacher, hierbei konnten, genau wie im Beispiel in der ApexCharts-Dokumentation angegeben, einfach die Ergebnisse des Requests an ApexCharts übergeben werden. Erzeugt wurde ein simples „Line“-Diagramm, bei dem alle Werte durch eine Linie verbunden wurden. Im ersten Versuch bestand das Array der Ergebnisse aus ca. 200 Elementen, d.h. Datum-Wert-Paaren. Das Diagramm wurde erzeugt, aber gefühlt genehmigte sich ApexCharts dazu recht viel Zeit, es vergingen zwei, drei Sekunden, bis die gewünschte Linie im Diagramm erschien. Das kam mir recht viel vor, immerhin waren es nur 200 Elemente. Wie würde es mit knapp 2000 aussehen?
Natürlich lassen sich nicht unbegrenzt viele Werte darstellen – irgendwann ist eine Grenze der Sinnhaftigkeit erreicht, so dass es besser wäre, wenn die Server-Seite dafür sorgt, dass Zeitabschnitte zusammengefasst werden. Schließlich ist es nicht notwendig, beispielsweise bei Betrachtung einer Zeitspanne mehrerer Jahre auf alle Werte eines Tages zugreifen zu können, in dem Fall kann der Server den Tagesdurchschnitt berechnen und die zu übermittelnde Datenmenge wesentlich verringern. Aber andererseits sollten ein paar Hundert Datensätze auch kein Problem sein, ansonsten wird die Diagrammdarstellung zu ungenau.
Von programmiertechnischer Seite betrachtet ist ApexCharts hingegen sehr angenehm zu verwenden. Die notwendige Konfiguration ist auf ein Minimum beschränkt, das Ergebnis sieht dabei bereits ansprechend aus. Und natürlich lassen sich alle Einstellungen umfassend anpassen, doch wie erwähnt wäre das erst der nächste Schritt.
Der Test mit knapp 2000 Elementen war jedoch ernüchternd, es dauerte schlicht und einfach eine Ewigkeit, bis das Resultat im Diagramm erschien. Vielleicht wären Optimierungen möglich, aber noch einmal – eigentlich wollte ich der Library nur ein paar Werte zur Verfügung stellen und alles Weitere ApexCharts überlassen. Mit einer derartig langsamen Darstellung hätte ich jedenfalls nicht gerechnet.
Mehr Konfiguration: Chart.js
Daraufhin habe ich das Beispiel mit der nächsten JavaScript Chart-Library umgesetzt. Der Code für Chart.js sieht wie folgt aus:
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script> var ctx = $('#myChart'); var myChart = new Chart(ctx, { type: 'line', data: { datasets: [{ label: myTitle, data: [] }] }, options: { scales: { xAxes: [{ type: 'time', time: { unit: 'hour' } }] } } }); var url = '/api/example/query-range/?id=' + myID; axios({ method: 'GET', url: url, }).then(function(response) { myChart.data.datasets[0].label = "foobar"; myChart.data.datasets.forEach((dataset) => { var chartData = []; response.data.results.forEach((item) => { chartItem = { x: new Date(item.x), y: item.y }; chartData.push(chartItem); }); dataset.data = chartData }); myChart.update(); }) </script>
Beim ersten Ausprobieren fiel mir auf, dass hier doch etwas mehr Vorarbeit notwendig war, die Konfiguration ging nicht so leicht von der Hand. Um zu einem Diagramm zu gelangen, verlangt Chart.js das Setzen einiger Optionen, der Aufwand ist in der Summe höher als bei ApexCharts. Und das Default-Design ist auch noch sehr überarbeitungsbedürftig, doch war dies zunächst nicht relevant.
Wie zu erkennen ist, mussten die Datums-Angaben zunächst in JavaScript-Date-Instanzen konvertiert werden, danach wird das data
-Array des Datasets der Chart-Instanz ersetzt und die update()
-Methode aufgerufen. Auch dies fühlte sich etwas weniger ausgefeilt an als bei ApexCharts, aber es funktionierte.
Und immerhin – das Ergebnis in Form des Diagramms wurde wesentlich schneller gezeichnet! Bei den knapp 200 Elementen war der Chart quasi sofort da, bei 2000 dauerte es ein, zwei Sekunden des Innehaltens. Insofern punktete Chart.js hier gewaltig. Jedoch wäre es aufwändiger gewesen, das Design so anzupassen, dass es den Vorstellungen entsprochen hätte.
Mehr Performance: Apache ECharts
Und davon abgesehen war nun die Neugier geweckt, weshalb ich mich an einen dritten Versuch mit Apache ECharts wagte. Der Code sieht wiederum fast identisch aus:
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/echarts@5.0.2/dist/echarts.min.js"></script> <script type="text/javascript"> var chartDOM = $("#chart")[0]; var myChart = echarts.init(chartDOM); var option = { xAxis: { type: 'category', data: [] }, yAxis: { type: 'value' }, series: [{ data: [], type: 'line', animation: false }] }; myChart.setOption(option); var url = '/api/example/query-range/?id=' + myID; var dateList = []; var valueList = []; axios({ method: 'GET', url: url, }).then(function(response) { response.data.results.forEach((item) => { dateList.push(item.x); valueList.push(item.y); }); myChart.setOption({ xAxis: { data: dateList }, series: [{ data: valueList }] }); }) </script>
Anstatt eines Arrays mit Wert-Paaren mussten hier jedoch zunächst zwei Arrays mit korrespondierenden Werten gebildet werden, eines für die Datumsangaben, und eines mit den Werten an sich. Der Rest ist wiederum aus den vorherigen Beispielen bekannt.
Bereits der erste Versuch mit knapp 200 Werten versprach viel, das Diagramm war quasi sofort da, ich konnte zumindest keine Wartezeit feststellen. Umso spannender war die Frage, wie die Performance bei ca. 2000 Werten aussehen würde.
Bemerkenswerterweise änderte sich bei 2000 Werten die Geschwindigkeit fast überhaupt nicht! Es dauerte keine Sekunde, bis das Diagramm vollständig dargestellt war. Das überraschte mich dann schon…
Nun aber mal richtig: Der Benchmark
Um gefühlte Eindrücke auf eine sinnvollere Basis zu stellen und belastbare Ergebnisse zu erhalten, folgte nun der Benchmark mittels der Performance-Analyse der Chrome DevTools. Im Template war dabei nur der Code aktiv, der zu der jeweils untersuchten Chart-Library gehörte, d.h. entsprechend nur einer der oben dargestellten JavaScript-Abschnitte. Die Libraries sollten sich nicht gegenseitig beeinflussen. Der Test erfolgte in derselben Umgebung mit derselben Anzahl geöffneter Chrome-Tabs, alle weiteren gestarteten Programme waren ebenfalls identisch. Die Aufzeichnung der Performance wurde jeweils drei Mal durchgeführt. Die folgenden Screenshots zeigen dabei einen typischen Durchlauf, während der Tests gab es keine signifikanten Ausreißer in die eine oder andere Richtung. Vom Backend wurden knapp 2000 Werte geliefert, die dargestellt werden sollten.
Die Geschwindigkeit von ApexCharts zeigte sich dabei als noch desaströser als erwartet:
Im oberen Bereich ist ein Ausschnitt des Diagramms zu sehen, unten die Analyse der Performance. Mit ca. 25 Sekunden ist ApexCharts der „Spitzenreiter“ – allerdings der Langsamkeit.
Wesentlich besser sieht dies bei Chart.js aus:
Nach etwas mehr als zwei Sekunden ist das Diagramm gezeichnet, zum Thema Design und Anpassungen habe ich mich ja bereits geäußert.
Der klare Gewinner ist jedoch Apache ECharts:
Selbst bei knapp 2000 Werten hat sich der Eindruck von wegen „weniger als eine Sekunde“ bestätigt, nach 0,71 Sekunden war Apache ECharts bereits mit dem Zeichnen des Diagramms fertig.
Benchmark-Ergebnisse
Auch die Ergebnisse dieses Benchmarks lassen sich wiederum als Diagramm darstellen:
Bemerkenswerterweise stellt ApexCharts bei Nennung der „Features“ sogar dessen hohe Geschwindigkeit heraus. Im interaktiven Beispiel können Tausende Werte hinzugefügt werden, wobei deren Darstellung in einer Zeit von weniger als einer Sekunde erfolgt. Wie das funktionieren soll, habe ich allerdings nicht weiter ergründet, und angesichts der hervorragenden Ergebnisse von Apache ECharts muss ich gestehen, dass es mich auch nicht sonderlich interessiert. Schließlich besteht die Umsetzung des Beispiels aus genau dem Code, den ApexCharts in der Dokumentation zur Verfügung stellt. Der Test ist meines Erachtens somit mehr als fair, da der Code für die Konkurrenten Chart.js und Apache ECharts angepasst werden musste, bei ApexCharts jedoch nahezu unverändert ist. Übrigens war die „erste Version“, d.h. die direkte Übergabe des JSON-Results an ApexCharts noch ca. drei Sekunden langsamer als bei vorheriger Kopie in ein neues Array.
Von Null auf Zweihundert: Apache ECharts
Mit einem solch gewaltigen Vorsprung hatte ich jedoch niemals gerechnet, somit dürfte klar sein, welche JavaScript Chart-Library hier zum Einsatz kommt. Über die an manchen Stellen noch eher rudimentäre Dokumentation und die chinesischen Schriftzeichen in einigen Beispielen von Apache ECharts sehe ich dabei gerne hinweg.
Das soll als kleiner Ausflug in JavaScript und Chart-Libraries auch erst einmal reichen. Wie eingangs erwähnt, eigentlich sollte die Darstellung eines Diagramms in der Web-UI nur eine Zugabe, ein Schmankerl sein, weit entfernt von der Kernfunktionalität. Aber die Ergebnisse erschienen mir wiederum zu interessant, um sie nicht in einem Artikel zu verwursten…
Vielen Dank für den super Vergleich.
hatte erst vor ein paar Tagen Chart.js (kannte kein anderes) für mein Webprojekt (siehe Link) eingesetzt, doch da funktioniert sowas einfaches wie das Anzeigen von Werten nur über Plugins und dazu auch noch super umständlich (zumindest wenn nicht über cdn)..
Daher werde ich, nach diesem Artikel und nachdem ich mir die Beispiele und Möglichkeiten angeschaut habe, direkt auf ECharts umsteigen 😉
Guter Artikel, toller Benchmark!
Auch ich stehe gerade vor der Wahl eines Charts Library (für ein Angular Projekt ) und hatte exakt diese 3 ins Auge gefasst. Dieser Artikel hat mir sehr geholfen, kurzum: Apex ist raus.
Vielen Dank dafür!