Using AngularJS for Data Visualisations

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!

Note: I won't go through every aspect of Angular here, such as how the apps, controllers, etc work - I will be concentrating on the data. I have written 'A Hello World In AngularJS' that may help you understand some of that stuff, and there are plenty of other great resources around for getting started with Angular

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.

Note: The 'y' div uses the {{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!