The following is a guest post by Nick Moreton. I gotta say I find this idea rather intriguing. I know that I love working in HTML, SVG, and CSS, so when Nick shares that we can use that for structure and use the data directly to style a chart, that appeals to me. And then to know that, because you’re using Angular, the chart can automatically change when the data changes… that’s just dang cool.
As soon as I began playing around with AngularJS it struck me that its ability to grab data and use it directly in markup could offer a real quick and simple way to create data visualizations.
In this tutorial I will run through creating three different sorts of charts using both inline CSS and SVG.
Why Angular?
If you’ve ever written to the DOM from JavaScript or jQuery you will know how quickly your code can begin to look messy, especially if you are using several variables. Angular allows for data to be used right in the markup, leading to clean, easy to read and understand code.
There are also some great visualization libraries out there, but they come with a lot of default styling right out of the box. By using Angular, the visualizations are completely unopinionated, and will pick up your style right off the bat.
I’m not saying this is the best way of creating data visualizations, but it certainly appeals to me!
Setting up our Angular App
Basic App setup
Firstly we need to set up an Angular app and a controller to house our functionality and data.
(function(){
var app = angular.module('graphApp',[]);
app.controller('graphController', function($scope){
// Code goes here!
});
})();
Default options
Next, we set up some default variables, bound to the controller $scope, that we will use to control the size of our chart, as well as labels for the X and Y axis.
$scope.width = 600;
$scope.height = 400;
$scope.yAxis = "Sales";
$scope.xAxis = "2014"
Data
We then add our data, written in JSON, bound to the $scope
of our controller.
$scope.data = [
{
label: 'January',
value: 36
},
{
label: 'February',
value: 54
},
// .... and so on .....
{
label: 'November',
value: 252
},
{
label: 'December',
value: 342
}
];
Find the maximum
Finally, we write a loop to cycle through our data to find the maximum value and set this as a variable. We will be using this later to position the elements in our visualizations.
$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;
}
And that’s it for our JavaScript. Really this is all about setting up our data and variables for use later in our markup.
Set up markup, templating and CSS
We now need to set up our visualization app markup and CSS.
<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>
In the HTML we can start to pull the data out of the JavaScript directly in to the markup, both as content (such as the X and Y labels) and as inline style to control the height and width of our chart.
{{height}}
variable for the width
CSS property – this is because in our CSS we will rotate this counter clockwise by 90 degrees.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;
}
Bar Chart
OK, to create the data on our charts we’re going to use Angular’s ng-repeat
function. This will cycle through our data array and output whatever markup we wrap in the ng-repeat
for each entry.
First off, we’ll create a bar chart. To begin with, I have some default CSS set up for the bar class that sets the position to absolute and adds a background color.
.bar {
background: blue;
position: absolute;
bottom: 0;
}
Then, using ng-repeat
, to create a <div>
with class ‘bar’ for each entry we would write
<div ng-repeat="bar in data" class="bar"></div>
This code goes inside our <div class="graph"></div>
.
We can now use our Angular data (and a little bit of maths!) in some inline CSS to control the height and width of each bar
<div ng-repeat="bar in data" class="bar" style="height:{{bar.value / max * height}}px; width:{{width / data.length - 5}}px;"></div>
The height is the value, divided by the maximum (as set up in our Angular app), multiplied by the total height of our chart. This ensures that the highest value in our data will take up the full height of the chart.
The width is the total width divided by the number of entries, with 5px knocked off to create some spacing once we place our bars on the X axis.
Finally, we need to position the bars along the X axis using the left
CSS property.
<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>
Here, we are using $index
, an Angular variable that will start at 0 and increase for each subsequent bar. We divide the index by the total number of entries and multiply this by the full width of the chart. This places the first bar at 0 and then spaces the rest of the bars equally across the chart.
It’s worth noting here that if you wish to have a fluid chart you could multiply by 100 and use % as the unit rather than pixels.
And that’s it – our bar chart has now been created and you can start getting fancy with your CSS to style it up!
The final full chart code looks like this:
<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>
See the Pen 1fe35094c4c1d447bdf59c5588167274 by Nick Moreton (@nickmoreton) on CodePen.
Dot chart
The method for a dot chart is very similar.
Again, I have some default CSS for the dots:
.dot {
background: blue;
width: 10px;
height: 10px;
border-radius: 50%;
position: absolute;
}
For the dots, we don’t need to worry about widths or heights, we simply use the same data and maths to position the dots from the left and from the bottom.
The only difference is we add 0.5 to $index
so that the dots are positioned in the center of the space afforded to it.
<div ng-repeat="dot in data" class="dot" style="bottom:{{dot.value / max * height}}px; left:{{($index + 1) / data.length * width}}px;"></div>
The full chart looks like this:
<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>
See the Pen 0ee74365fd766311a52aa0d129c22ea8 by Nick Moreton (@nickmoreton) on CodePen.
SVG Line Chart
As well as inline CSS, we can use Angular data values in SVG data.
The CSS for this is:
svg {
position: absolute;
transform: rotateX(180deg);
left: 0;
}
line {
stroke:red;
stroke-width:3px;
}
I’m rotating the SVG as by default it processes values from the top, and we need this to flipped to take values from the bottom.
Firstly, we need to create an SVG that spans the full area of our graph, and then inside that use our ng-repeat
on a line
element.
Each line
element requires a start and end point on both the X (x1, x2) and Y (y1, y2) axis.
The X axis is pretty simple – we follow the same system as before, so that each line is spaced out evenly across the chart, starting at 0 using $index
, and ending where the next line starts, using $index + 1
.
For the Y axis this is where the $index
variable comes in to its own, as it allows us to select values from previous or next entries in our array.
The initial Y point of each line gets the value from the previous data entry using data[$index - 1].value
before we then apply similar maths as before. For the second Y point we can just call the straight value from the entry.
This may sound complicated (and I can assure you that working it out was a bit of a head shrinker!) but hopefully this explanation coupled with the code below should help you make sense of it!
<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>
The final chart code looks like this:
<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>
See the Pen a3e89703d7671fa81d5c2ceb20f7b150 by Nick Moreton (@nickmoreton) on CodePen.
Where next?
You can use the $index
variable in class names too, like this <element class="classname{{$index}}">
which allows for fine control of each bar, dot or line – which we can use to animate these visualizations.
I have worked up a full dot/line chart example with animation, as well as CSS tooltips using the labels from each entry in our data. You can see this here.
I’ve also created a Pen with all of these examples.
I hope this tutorial is of some use, and I’d love to hear about any visualizations you create with Angular!
I’ve really been thinking of where to begin my Angular exploration and this looks like a good point as I’m working on a ERP that requires such graphs. Would have been nice to see some with pie charts as well but I guess that’d have required some SVG and you could have thought of it as overkill for a simple post
Great introduction to AngularJs! I also liked AngularJs from the start. I have made many interactive web apps using it to make parts of the web app easy to interact with. You can see some of them on Codepen here: http://codepen.io/netsi1964/tag/angularjs/
/Sten
Very nice Sten…
Very interesting, and by ‘interesting’ I mean ‘awesome’! I always reach straight for D3 when I want to do data visualization (even while working with data visualization within an angular app. Clearly I have been overlooking that SVG is just HTML like everything else, and angular does a killer job of extending HTML. Seeing angular constructs in SVG declarations has my nerd senses tingling. Thanks for this post!
I agree. J
I’ve wanted to really get into SVG’s and the like, but I always hesitate to use it in my projects because of IE issues. I realize that there are fallback options and such in most cases but I can’t think of one for this. Any articles or ideas that could overcome that little problem?
@Eric, according to “can I use” websitet (http://caniuse.com/#search=svg) you should not really hesitate to use SVG. Globally 93,83% of all browsers used support SVG.
The part when you put “Data:…” That is not json, it’s a javascript object.
Yeah but in a real world situation it’s probably gonna be a json feed.
I’m in the middle of a project that is doing this very thing, using angular right in the middle of svg’s. What’s cool is you can delete an element from your data model and the coresponding graphic element goes away without any other code.
I am facing a problem though, so beware that Chrome throws fits when it sees svg’s with the unfamiliar markup and directives. (firefox does too, but less so). It still seems to work OK, but not sure how to solve this yet.
I love how these demos never work in IE. I am not blaming the developer here. I am blaming the browser. I use IE for work purposes. Don’t judge :]
Nice Tutorial
I would suggest using the
ng-style
directive instead of usingstyle
+ interpolation. Something like this:+1 for ng-style. To add to your example though, I would suggest using “min-height” as opposed to “height” on the bar graphs to use CSS transitions to animate it.
Thanks guys
Yeah
ng-style
looks a good fit for this, I’ll take that on board for sureIn regards to using transitions, Chris posted a really cool trick for animating inline styles that would work on height, or any other appropriate property, that I’ve used before for stuff like this.
Now for the bigger challenge: pie charts! Hooray for trigonometry.
How does it perform with larger datasets. I like the declarative nature of this approach (vs. d3), but I fear that the number of $watches for any moderately large dataset would lead to performance issues.
I don’t think in such case a $watch would be the best approach. It might be better to somehow come up with a “dirty” flag or similar. Because when data changes, you’ll generally know about it well before Angular could’ve picked it up in a $watch. And when it does, you could emit an event that triggers reloading the data and rerendering the graph.
The thing is, Angular creates implicit watches for any {{expr}} expressions and ng-repeat/if/show/class/style attributes. It’s the same reason that creating large data tables with ng-repeat can cause performance issues—each ng-repeated row creates an implicit watch. More watchers = slower app since each watch is evaluated at least once on each digest cycle.
We’ve run into similar issues at Next Big Sound, and one of our solutions was to use one-time binding (aka bind-once) with watchCollection(). Check out http://stackoverflow.com/questions/23903389/do-bindings-nested-inside-of-a-lazy-one-time-ng-repeat-binding-bind-just-once
Most people may never run into such performance issues, but it’s good to know nonetheless. I like your approach!
Alexandros Marinos wrote an in-depth article about a declarative Angular-based approach to data visualizations. It’s a great follow-up to this one if you found it interesting: http://alexandros.resin.io/angular-d3-svg/
Although the learning curve with AngularJS is steep and it requires a good amount of time to understand the concepts behind it (if you’re not already firm with the concepts), once you’re done I’m sure you’ll love it and you’ll see the value of it in context of creating interactive and modular data visualizations.