While building my senior capstone project, iSprinkle, I needed a way to display a time series graph of data. A working demo can be found on my Codepen and a gist over on Github. Here’s what I learned.

Implementing a time series graph into an existing Angular app requires a few dependecies:

  • D3, a data visualization library
  • NVD3, a reusable chart library for D3
  • Angular-nvD3, for AngularJS directives
  • Moment.js, a library for working with JavaScript dates/times

Note: As of this writing, nvd3 supports D3 versions 3.5.3 and above, but not D3 4.x .

In this minimal example, our Angular app and controllers are named like so:

<body>
	<div ng-app="nvd3_timeseries">
		<div ng-controller="demo">
		<nvd3 options="options" data="data"></nvd3>
		</div>
	</div>
</body>

However, because some dependecies depend on others, we’ll need to make that they load in a particular order:

<head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.16.0/moment.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.js"></script>
    <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.4/nv.d3.min.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.4/nv.d3.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-nvd3/1.0.9/angular-nvd3.min.js"></script>
</head>

Finally, most of the code for the graph is found within the home controller. In $scope.options, we’ll find the configuration options for the chart. The actual data for the chart is stored in $scope.data, which should contain one or more time series objects in this format:

// xy_coordinate_pairs is a time series and should be an array of x, y coordinate pairs
// {x: xVal, y: yVal }
return [{
  values: xy_coordinate_pairs,
  key: 'Chart Key',
  color: '#1b75ba'
}];

In this example, the generateTimeSeries function is called to

// 1) Declare app
var nvd3_timeseries = angular.module('nvd3_timeseries', ['nvd3']);

// 2) Declare controller
nvd3_timeseries.controller('demo', ['$scope', function($scope) {

    // 3) Chart configuration
    $scope.options = {
        chart: {
            type: 'lineChart',
            height: 450,
            margin: {
                top: 20,
                right: 20,
                bottom: 40,
                left: 55
            },
            x: function(d) {
            /*  
            Moment.js will attempt to parse the x coordinate 
            and return a value in milliseconds
            */
                return moment.utc(d.x).valueOf();
            },
            y: function(d) {
                return d.y;
            },
            useInteractiveGuideline: true,
            xAxis: {
                axisLabel: 'Date',
                tickFormat: (function(d) {
                    // 3.1) Format the x ticks
                    return d3.time.format('%-m/%-d/%-Y')(new Date(d));
                })
            },
            yAxis: {
                axisLabel: 'Cats',
                axisLabelDistance: -10
            },
            callback: function(chart) {}
        },
        title: {
            enable: true,
            text: 'Time Series graph'
        }
    };

    $scope.dateRange = function dateRange(startDate, endDate) {
        // http://stackoverflow.com/a/23796069
        var dates = [];

        var currDate = startDate.clone().startOf('day');
        var lastDate = endDate.clone().startOf('day');

        while (currDate.add(1, 'days').diff(lastDate) <= 0) {
            dates.push(currDate.clone().toDate());
        }

        return dates;
    };

    // 4) Chart data
    $scope.data = generateTimeSeries();

    function generateTimeSeries() {
        // Generate an array of dates for plotting
        var dateRange = $scope.dateRange(moment().subtract(7, 'days'), moment());

        // Plottable will be an array of (x, y) objects
        var plottable = dateRange;

        plottable.forEach(function to_utc(date, index) {
            console.log('a[' + index + '] = ' + date);
            // set x value to date and y to a random value
            var rand = Math.round((Math.random() * (100 - 1) + 1));
            var point = {
                x: date,
                y: rand
            };
            // Replace original element
            plottable[index] = point;
        });

        // Return the plottable array for plotting
        return [{
            values: plottable,
            key: 'Cats',
            color: '#1b75ba'
        }];
    };
}]);

As an aside, although this chart is initiated with data instantly, we can set an empty data value like so:

$scope.data = [{
    "key": "Some key",
    "values": []
}];