Der folgende Beitrag ist ein Gastbeitrag von Nick Moreton. Ich muss sagen, ich finde diese Idee ziemlich faszinierend. Ich weiß, dass ich gerne mit HTML, SVG und CSS arbeite, daher spricht es mich an, wenn Nick erzählt, dass wir diese für die Struktur nutzen und die Daten direkt zur Gestaltung eines Diagramms verwenden können. Und dann zu wissen, dass das Diagramm dank Angular automatisch geändert werden kann, wenn sich die Daten ändern... das ist einfach verdammt cool.
Sobald ich mit AngularJS zu experimentieren begann, fiel mir auf, dass seine Fähigkeit, Daten zu erfassen und direkt in Markup zu verwenden, eine sehr schnelle und einfache Möglichkeit zur Erstellung von Datenvisualisierungen bieten könnte.
In diesem Tutorial werde ich die Erstellung von drei verschiedenen Arten von Diagrammen mit sowohl Inline-CSS als auch SVG durchgehen.
Warum Angular?
Wenn Sie jemals aus JavaScript oder jQuery in das DOM geschrieben haben, wissen Sie, wie schnell Ihr Code unübersichtlich werden kann, besonders wenn Sie mehrere Variablen verwenden. Angular ermöglicht die direkte Verwendung von Daten im Markup, was zu sauberem, leicht lesbarem und verständlichem Code führt.
Es gibt auch einige großartige Visualisierungsbibliotheken, aber diese bringen von Haus aus viel Standard-Styling mit. Durch die Verwendung von Angular sind die Visualisierungen völlig unvoreingenommen und übernehmen sofort Ihren Stil.
Ich sage nicht, dass dies die beste Methode zur Erstellung von Datenvisualisierungen ist, aber sie spricht mich definitiv an!
Einrichten unserer Angular App
Grundlegende App-Einrichtung
Zuerst müssen wir eine Angular-App und einen Controller einrichten, um unsere Funktionalität und Daten zu beherbergen.
(function(){
var app = angular.module('graphApp',[]);
app.controller('graphController', function($scope){
// Code goes here!
});
})();
Standardoptionen
Als Nächstes richten wir einige Standardvariablen ein, die an den Controller $scope gebunden sind und mit denen wir die Größe unseres Diagramms sowie Beschriftungen für die X- und Y-Achse steuern.
$scope.width = 600;
$scope.height = 400;
$scope.yAxis = "Sales";
$scope.xAxis = "2014"
Daten
Anschließend fügen wir unsere Daten hinzu, geschrieben in JSON, gebunden an den $scope unseres Controllers.
$scope.data = [
{
label: 'January',
value: 36
},
{
label: 'February',
value: 54
},
// .... and so on .....
{
label: 'November',
value: 252
},
{
label: 'December',
value: 342
}
];
Das Maximum finden
Schließlich schreiben wir eine Schleife, um unsere Daten zu durchlaufen, den Maximalwert zu finden und ihn als Variable festzulegen. Dies werden wir später verwenden, um die Elemente in unseren Visualisierungen zu positionieren.
$scope.max = 0;
var arrLength = $scope.data.length;
for (var i = 0; i < arrLength; i++) {
// Find Maximum X Axis Value
if ($scope.data[i].value > $scope.max)
$scope.max = $scope.data[i].value;
}
Das war's für unser JavaScript. Das Ganze dreht sich eigentlich nur darum, unsere Daten und Variablen für die spätere Verwendung in unserem Markup vorzubereiten.
Markup, Templating und CSS einrichten
Nun müssen wir das Markup und CSS für unsere Visualisierungs-App einrichten.
<div ng-app="graphApp">
<div ng-controller="graphController as graph">
<div class="graph" style="width:{{width}}px; height:{{height}}px;" >
<div class="y" style="width:{{height}}px;">{{yAxis}}</div>
<div class="x">{{xAxis}}</div>
</div>
</div>
</div>
Im HTML können wir beginnen, die Daten direkt aus JavaScript in das Markup zu ziehen, sowohl als Inhalt (wie die X- und Y-Achsenbeschriftungen) als auch als Inline-Stil, um die Höhe und Breite unseres Diagramms zu steuern.
{{height}} für die CSS-Eigenschaft width – das liegt daran, dass wir diesen in unserem CSS um 90 Grad gegen den Uhrzeigersinn drehen werden..chart {
border-left: 1px solid black;
border-bottom: 1px solid black;
margin: 60px auto;
position: relative;
}
.y {
position: absolute;
transform: rotate(-90deg);
transform-origin: bottom left;
bottom: 0;
padding: 5px;
}
.x {
position: absolute;
top: 100%;
width: 100%;
padding: 5px;
}
Balkendiagramm
Um die Daten in unseren Diagrammen zu erstellen, verwenden wir die ng-repeat-Funktion von Angular. Diese durchläuft unser Datenarray und gibt jedes Markup aus, das wir in ng-repeat für jeden Eintrag einschließen.
Zuerst erstellen wir ein Balkendiagramm. Zunächst habe ich etwas Standard-CSS für die Klasse ‘bar’ eingerichtet, das die Position auf absolut setzt und eine Hintergrundfarbe hinzufügt.
.bar {
background: blue;
position: absolute;
bottom: 0;
}
Dann würden wir mit ng-repeat, um für jeden Eintrag ein <div> mit der Klasse ‘bar’ zu erstellen, schreiben
<div ng-repeat="bar in data" class="bar"></div>
Dieser Code gehört in unser <div class="graph"></div>.
Wir können nun unsere Angular-Daten (und ein bisschen Mathematik!) in einem Inline-CSS verwenden, um die Höhe und Breite jedes Balkens zu steuern.
<div ng-repeat="bar in data" class="bar" style="height:{{bar.value / max * height}}px; width:{{width / data.length - 5}}px;"></div>
Die Höhe ist der Wert, geteilt durch das Maximum (wie in unserer Angular-App eingestellt), multipliziert mit der Gesamthöhe unseres Diagramms. Dies stellt sicher, dass der höchste Wert in unseren Daten die volle Höhe des Diagramms einnimmt.
Die Breite ist die Gesamtbreite geteilt durch die Anzahl der Einträge, abzüglich 5 Pixel, um etwas Abstand zu schaffen, sobald wir unsere Balken auf der X-Achse platzieren.
Schließlich müssen wir die Balken entlang der X-Achse mit der CSS-Eigenschaft left positionieren.
<div ng-repeat="bar in data" class="bar" style="height:{{bar.value / max * height}}px; width:{{width / data.length - 5}}px; left:{{$index / data.length * width}}px;"></div>
Hier verwenden wir $index, eine Angular-Variable, die bei 0 beginnt und für jeden nachfolgenden Balken steigt. Wir teilen den Index durch die Gesamtzahl der Einträge und multiplizieren dies mit der vollen Breite des Diagramms. Dies platziert den ersten Balken bei 0 und verteilt dann die restlichen Balken gleichmäßig über das Diagramm.
Es ist hier erwähnenswert, dass Sie, wenn Sie ein flüssiges Diagramm wünschen, mit 100 multiplizieren und % als Einheit anstelle von Pixeln verwenden könnten.
Und das war's – unser Balkendiagramm ist nun erstellt und Sie können mit Ihrem CSS kreativ werden, um es zu gestalten!
Der endgültige vollständige Diagramm-Code sieht so aus
<div class="chart" style="width:{{width}}px; height:{{height}}px;">
<!-- Labels -->
<div class="y" style="width:{{height}}px;">{{yAxis}}</div>
<div class="x">{{xAxis}}</div>
<!-- Data -->
<div ng-repeat="bar in data" class="bar" style="height:{{bar.value / max * height}}px; width:{{width / data.length - 5}}px; left:{{$index / data.length * width}}px;"></div>
</div>
Siehe den Pen 1fe35094c4c1d447bdf59c5588167274 von Nick Moreton (@nickmoreton) auf CodePen.
Punkt-Diagramm
Die Methode für ein Punkt-Diagramm ist sehr ähnlich.
Auch hier habe ich etwas Standard-CSS für die Punkte.
.dot {
background: blue;
width: 10px;
height: 10px;
border-radius: 50%;
position: absolute;
}
Für die Punkte müssen wir uns nicht um Breiten oder Höhen kümmern, wir verwenden einfach dieselben Daten und mathematischen Berechnungen, um die Punkte von links und von unten zu positionieren.
Der einzige Unterschied ist, dass wir 0,5 zu $index addieren, damit die Punkte in der Mitte des ihnen zugewiesenen Platzes positioniert werden.
<div ng-repeat="dot in data" class="dot" style="bottom:{{dot.value / max * height}}px; left:{{($index + 1) / data.length * width}}px;"></div>
Das vollständige Diagramm sieht so aus
<div class="chart" style="width:{{width}}px; height:{{height}}px;">
<!-- Labels -->
<div class="y" style="width:{{height}}px;">{{yAxis}}</div>
<div class="x">{{xAxis}}</div>
<!-- Data -->
<div ng-repeat="dot in data" class="dot" style="bottom:{{dot.value / max * height}}px; left:{{($index + 0.5) / data.length * width}}px;"></div>
</div>
Siehe den Pen 0ee74365fd766311a52aa0d129c22ea8 von Nick Moreton (@nickmoreton) auf CodePen.
SVG-Linien-Diagramm
Neben Inline-CSS können wir Angular-Datenwerte in SVG-Daten verwenden.
Das CSS dafür ist
svg {
position: absolute;
transform: rotateX(180deg);
left: 0;
}
line {
stroke:red;
stroke-width:3px;
}
Ich rotiere das SVG, da es standardmäßig Werte von oben verarbeitet und wir diese umgekehrt benötigen, um Werte vom Boden zu übernehmen.
Zuerst müssen wir ein SVG erstellen, das die gesamte Fläche unseres Diagramms einnimmt, und dann darin unsere ng-repeat-Schleife auf einem line-Element verwenden.
Jedes line-Element benötigt einen Start- und Endpunkt sowohl auf der X- (x1, x2) als auch auf der Y-Achse (y1, y2).
Die X-Achse ist ziemlich einfach – wir folgen demselben System wie zuvor, sodass jede Linie gleichmäßig über das Diagramm verteilt ist, beginnend bei 0 mit $index und endend dort, wo die nächste Linie beginnt, mit $index + 1.
Für die Y-Achse kommt die Variable $index hier voll zur Geltung, da sie uns erlaubt, Werte aus vorherigen oder nächsten Einträgen in unserem Array auszuwählen.
Der anfängliche Y-Punkt jeder Linie erhält den Wert vom vorherigen Datensatz über data[$index - 1].value, bevor wir dann ähnliche Berechnungen wie zuvor anwenden. Für den zweiten Y-Punkt können wir einfach den direkten Wert aus dem Eintrag verwenden.
Das mag kompliziert klingen (und ich versichere Ihnen, dass es eine ziemliche Kopfnuss war, es herauszufinden!), aber hoffentlich wird Ihnen diese Erklärung in Verbindung mit dem nachstehenden Code helfen, es zu verstehen!
<svg style="width:{{width}}px; height:{{height}}px;">
<line ng-repeat="line in data" x1="{{$index / data.length * width}}" y1="{{data[$index - 1].value / max * height}}" x2="{{($index + 1) / data.length * width}}" y2="{{line.value / max * height}}">
</line>
</svg>
Der endgültige Diagramm-Code sieht so aus
<div class="chart" style="width:{{width}}px; height:{{height}}px;">
<!-- Labels -->
<div class="y" style="width:{{height}}px;">{{yAxis}}</div>
<div class="x">{{xAxis}}</div>
<!-- Data -->
<svg style="width:{{width}}px; height:{{height}}px;">
<line ng-repeat="line in data"
x1="{{$index / data.length * width }}"
y1="{{data[$index - 1].value / max * height}}"
x2="{{($index + 1) / data.length * width}}"
y2="{{line.value / max * height}}">
</line>
</svg>
</div>
Siehe den Pen a3e89703d7671fa81d5c2ceb20f7b150 von Nick Moreton (@nickmoreton) auf CodePen.
Wo geht es weiter?
Sie können die Variable $index auch in Klassennamen verwenden, wie z. B. <element class="classname{{$index}}">, was eine präzise Steuerung jedes Balkens, Punkts oder jeder Linie ermöglicht – dies können wir zur Animation dieser Visualisierungen nutzen.
Ich habe ein vollständiges Dot/Line-Diagrammbeispiel mit Animation sowie CSS-Tooltips unter Verwendung der Beschriftungen aus jedem Eintrag in unseren Daten ausgearbeitet. Sie können es hier sehen.
Ich habe auch einen Pen mit all diesen Beispielen erstellt.
Ich hoffe, dieses Tutorial ist von Nutzen, und ich würde gerne hören, welche Visualisierungen Sie mit Angular erstellen!
Ich habe wirklich darüber nachgedacht, wo ich mit meiner Angular-Erforschung beginnen soll, und das sieht nach einem guten Punkt aus, da ich an einem ERP arbeite, das solche Grafiken benötigt. Es wäre schön gewesen, auch einige Tortendiagramme zu sehen, aber ich schätze, das hätte etwas SVG erfordert und Sie hätten es für einen einfachen Beitrag als übertrieben angesehen.
Tolle Einführung in AngularJs! Ich mochte AngularJs auch von Anfang an. Ich habe viele interaktive Web-Apps damit erstellt, um Teile der Web-App einfach bedienbar zu machen. Sie können einige davon auf Codepen hier sehen: http://codepen.io/netsi1964/tag/angularjs/
/Sten
Sehr schön, Sten…
Sehr interessant, und mit „interessant“ meine ich „großartig“! Ich greife immer sofort zu D3, wenn ich Datenvisualisierung machen will (auch während ich mit Datenvisualisierung innerhalb einer Angular-App arbeite. Offensichtlich habe ich übersehen, dass SVG nur HTML wie alles andere ist, und Angular leistet hervorragende Arbeit bei der Erweiterung von HTML. Angular-Konstrukte in SVG-Deklarationen lassen meine Nerd-Sinne kribbeln. Danke für diesen Beitrag!
Ich stimme zu. J
Ich wollte mich schon lange wirklich mit SVGs und Ähnlichem beschäftigen, aber ich zögere immer, es in meine Projekte einzubauen, wegen Problemen mit IE. Ich weiß, dass es in den meisten Fällen Fallback-Optionen gibt, aber dafür fällt mir keine ein. Gibt es Artikel oder Ideen, die dieses kleine Problem überwinden könnten?
@Eric, laut der „Can I use“-Website (http://caniuse.com/#search=svg) sollten Sie nicht wirklich zögern, SVG zu verwenden. Global unterstützen 93,83 % aller verwendeten Browser SVG.
Der Teil, in dem Sie „Data:…“ schreiben, ist kein JSON, sondern ein JavaScript-Objekt.
Ja, aber in einer realen Situation wird es wahrscheinlich ein JSON-Feed sein.
Ich bin mitten in einem Projekt, das genau das tut, Angular mitten in SVGs verwenden. Was cool ist, ist, dass man ein Element aus seinem Datenmodell löschen kann und das entsprechende grafische Element verschwindet ohne zusätzlichen Code.
Ich stehe jedoch vor einem Problem, also seien Sie gewarnt, dass Chrome Probleme macht, wenn es SVGs mit dem ungewohnten Markup und Direktiven sieht. (Firefox tut das auch, aber weniger). Es funktioniert aber immer noch gut, aber ich bin mir noch nicht sicher, wie ich das lösen kann.
Ich liebe es, wie diese Demos nie in IE funktionieren. Ich beschuldige hier nicht den Entwickler. Ich beschuldige den Browser. Ich benutze IE aus beruflichen Gründen. Nicht verurteilen :]
Schönes Tutorial
Ich würde vorschlagen, die
ng-style-Direktive anstelle der Verwendung vonstyle+ Interpolation zu verwenden. Etwas wie das hier+1 für ng-style. Um Ihr Beispiel zu ergänzen: Ich würde „min-height“ anstelle von „height“ bei den Balkendiagrammen vorschlagen, um CSS-Übergänge zur Animation zu verwenden.
Danke, Leute
Ja,
ng-stylescheint dafür gut geeignet zu sein, das werde ich auf jeden Fall berücksichtigen.Was die Verwendung von Übergängen angeht, hat Chris einen wirklich coolen Trick zum Animieren von Inline-Styles gepostet, der bei Höhe oder jeder anderen geeigneten Eigenschaft funktionieren würde, den ich schon früher für solche Dinge verwendet habe.
Nun zur größeren Herausforderung: Tortendiagramme! Hurra für Trigonometrie.
Wie ist die Performance bei größeren Datensätzen? Ich mag die deklarative Natur dieses Ansatzes (im Gegensatz zu D3), aber ich befürchte, dass die Anzahl der $watches für jeden moderat großen Datensatz zu Performance-Problemen führen würde.
Ich glaube nicht, dass in diesem Fall ein $watch der beste Ansatz wäre. Es könnte besser sein, irgendwie eine „dirty“-Flagge oder ähnliches zu entwickeln. Denn wenn sich Daten ändern, wissen Sie das im Allgemeinen lange bevor Angular es in einem $watch erfassen konnte. Und wenn es das tut, könnten Sie ein Ereignis auslösen, das das Neuladen der Daten und das Neuzeichnen des Diagramms auslöst.
Die Sache ist, dass Angular implizite Watches für alle {{expr}}-Ausdrücke und ng-repeat/if/show/class/style-Attribute erstellt. Es ist derselbe Grund, warum die Erstellung großer Datentabellen mit ng-repeat zu Performance-Problemen führen kann – jede ng-repeated-Zeile erstellt eine implizite Watch. Mehr Watcher = langsamere App, da jeder Watch mindestens einmal in jedem Digest-Zyklus ausgewertet wird.
Wir hatten bei Next Big Sound ähnliche Probleme, und eine unserer Lösungen war die Verwendung von Einmalbindungen (auch Bind-Once genannt) mit watchCollection(). Schauen Sie sich http://stackoverflow.com/questions/23903389/do-bindings-nested-inside-of-a-lazy-one-time-ng-repeat-binding-bind-just-once an.
Die meisten Leute werden vielleicht nie auf solche Performance-Probleme stoßen, aber es ist gut zu wissen. Ich mag Ihren Ansatz!
Alexandros Marinos schrieb einen ausführlichen Artikel über einen deklarativen, Angular-basierten Ansatz für Datenvisualisierungen. Er ist eine großartige Ergänzung zu diesem Beitrag, wenn Sie ihn interessant fanden: http://alexandros.resin.io/angular-d3-svg/
Obwohl die Lernkurve mit AngularJS steil ist und es eine ganze Menge Zeit erfordert, um die Konzepte dahinter zu verstehen (wenn Sie die Konzepte nicht bereits beherrschen), werden Sie es lieben, sobald Sie fertig sind, und den Wert davon im Kontext der Erstellung interaktiver und modularer Datenvisualisierungen erkennen.