Source: components/chart-line.js

;(function () {
    'use strict';

    /**
     * Draw simple line chart.
     * @namespace
     */
    db.libs.lineChart = (function($){

        var name = 'lineChart';

        /**
         * Animates chart to values to in series.
         * @public
         * @memberof db.libs.lineChart
         * @param {external:jQuery|string} id Selector or jQuery element
         * @param {array} data Series data to add
         * @param {string} duration Duration for animation
         * @return {external:jQuery} jQuery element
         */
        function stream(id, data, duration){
            var $id = $(id);
            var options = $id.data('options');
            var lines = $id.find('svg').get(0).querySelectorAll('.line');
            var animateTransform, points;

            var width = (options.width / (options.series[0].length - 1));
            if($id.width() >= options.width){
                options.width = options.width + width;
            }

            for(var i = 0; i < lines.length; i++){
                var path = lines[i].querySelector('path');
                var oldAnimateTransform = lines[i].querySelector('.animateTransform');
                if(oldAnimateTransform !== null){
                    lines[i].removeChild(oldAnimateTransform);
                    options.series[i].shift();
                }

                points = lines[i].querySelectorAll('circle');

                options.series[i].push(data[i]);
                series(id, options.series);
                path.setAttribute('d', options.lines[i].path);

                for(var c = 0; c < points.length; c++){
                    points[c].setAttribute('cy', options.lines[i].points[c].y);
                    points[c].setAttribute('cx', options.lines[i].points[c].x);
                }

                animateTransform = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
                animateTransform.setAttribute('attributeName', 'transform');
                animateTransform.setAttribute('class', 'animateTransform');
                animateTransform.setAttribute('type', 'translate');
                animateTransform.setAttribute('from', '0 0');
                animateTransform.setAttribute('to', (width* -1) + ' 0');
                animateTransform.setAttribute('dur', duration);
                animateTransform.setAttribute('begin', 'click');
                animateTransform.setAttribute('fill', 'freeze');

                lines[i].appendChild(animateTransform);
                lines[i].dispatchEvent( new Event("click", {"bubbles":true, "cancelable":false}) );
            }

            return $id;
        }

        /**
         * Animates chart to values to in series.
         * @public
         * @memberof db.libs.lineChart
         * @param {external:jQuery|string} id Selector or jQuery element
         * @return {external:jQuery} jQuery element
         */
        function update(id){
            var $id = $(id);
            var options = $id.data('options');
            var lines = $id.find('svg').get(0).querySelectorAll('.line');
            var path, points, animate, animationId;

            for(var i = 0; i < lines.length; i++){
                path = lines[i].querySelector('path');
                points = lines[i].querySelectorAll('circle');
                animationId = db.utils.uniqueId('lineChartAnimation');

                animate = document.createElementNS('http://www.w3.org/2000/svg', 'animate');
                animate.setAttribute('id', animationId);
                animate.setAttribute('attributeName', 'd');
                animate.setAttribute('from', path.getAttribute('d'));
                animate.setAttribute('to', options.lines[i].path);
                animate.setAttribute('dur', '0.3s');
                animate.setAttribute('begin', 'click');
                animate.setAttribute('fill', 'freeze');
                animate.setAttribute('keySplines', '0 0.75 0.25 1');
                animate.setAttribute('calcMode','spline');
                animate.setAttribute('keyTimes','0;1');
                path.appendChild(animate);

                for(var c = 0; c < points.length; c++){
                    animate = document.createElementNS('http://www.w3.org/2000/svg', 'animate');
                    animate.setAttribute('attributeName', 'cy');
                    animate.setAttribute('from', points[c].getAttribute('cy'));
                    animate.setAttribute('to', options.lines[i].points[c].y);
                    animate.setAttribute('dur', '0.3s');
                    animate.setAttribute('begin', animationId+'.begin');
                    //animate.setAttribute('begin', '0s');
                    animate.setAttribute('fill', 'freeze');
                    animate.setAttribute('keySplines', '0 0.75 0.25 1');
                    animate.setAttribute('calcMode','spline');
                    animate.setAttribute('keyTimes','0;1');
                    points[c].appendChild(animate);
                }

                path.dispatchEvent( new Event("click", {"bubbles":true, "cancelable":false}) );
            }

            //Clean up the once the animation is complete.
            setTimeout(function(){
                for(var l = 0; l < lines.length; l++){
                    path = lines[l].querySelector('path');
                    points = lines[l].querySelectorAll('circle');

                    path.setAttribute('d', options.lines[l].path);
                    path.innerHTML = '';

                    for(var r = 0; r < points.length; r++){
                        points[r].setAttribute('cy', options.lines[l].points[r].y);
                        points[r].innerHTML = '';
                    }
                }
            }, 300);

            return $id;
        }

        /**
         * Updates data for the chart
         * @public
         * @memberof db.libs.lineChart
         * @param {external:jQuery|string} [id] Selector or jQuery element
         * @param {array} data Series data. Accepts array or string that can be parsed to array using JSON.parse
         * @return {external:jQuery} jQuery element
         */
        function series(id, data){
            var $id = $(id);
            var options = $id.data('options');

            options.lines = [];
            options.series = db.libs.chart.parse(data);

            if(options.max === null){
                options.max = db.libs.chart.max(options.series);
            }

            for(var i=0; i < options.series.length; i++){
                options.lines.push( line(options, i) );
            }

            $id.data('options', options);

            return $id;
        }

        /**
         * Calculates path and points for a line
         * @private
         * @memberof db.libs.lineChart
         * @param {object} options
         * @param {number} s Index of line to calculate
         * @return {object}
         */
        function line(options, s){
            //Our line holds two array, one with the path, and one with each point used to dra the circle points
            var shape = { path: [], points:[] };

            //Local variables
            var percentage, y, x, prevX, prevY, h1y, h1x, h2y, h2x;

            //Set a staring point outside the screen. We need this to be able to dra a fill.
            shape.path.push('M-50,'+ (options.height + 50));

            //Loop each value in the series
            for(var i = 0; i < options.series[s].length; i++){
                //Calculate height as percentage
                percentage = Math.round((100 / options.max) * options.series[s][i]);

                //Calculate x and y coordinates for the value
                x = (options.width / (options.series[s].length - 1)) * i;
                y = options.height - (( options.height / 100 ) * percentage);

                //If this is the first value, draw a vertical line from the starting point and to the first points y-coordinate
                if(i === 0){
                    shape.path.push('V-50,'+ y);
                }

                //Draw path for value
                if(!options.smooth || i === 0){
                    //If we are drawing straight lines this is easy peasy.
                    shape.path.push( 'L' + x + ' ' + y );
                } else {
                    //If we are drawing smooth curves we calculate each handle for a curve
                    h1y = prevY;
                    h1x = prevX + ((options.width / (options.series[s].length - 1)) * 0.50);
                    h2y = y;
                    h2x = x - (options.width / (options.series[s].length - 1)) * 0.50;
                    shape.path.push( 'C' + h1x + ' ' +h1y + ', ' + h2x + ' ' + h2y + ',' + x + ' ' + y );
                }

                //Add x and y coordinates so we can dra circle-points for each value
                //if(i !== 0 && i !== (options.series[s].length - 1)){
                    shape.points.push({x: x, y: y, r: options.pointRadius});
                //}

                //We save x and y here so
                prevX = x;
                prevY = y;
            }
            shape.path.push( 'L' + (x + 50) + ',' + (options.height + 50) );
            shape.path.push('Z');
            shape.path = shape.path.join(' ');

            return shape;
        }

        /**
         * Render the chart
         * @private
         * @memberof db.libs.lineChart
         * @fires rendered
         * @param {external:jQuery|string} [id] Selector or jQuery element
         * @return {external:jQuery} jQuery element
         */
        function render(id){
            var $id = $(id);
            var options = $id.data('options');

            if(options.gridXSteps !== null){
                options.gridXLines = db.libs.chart.grid(options.gridXSteps, options.max);
            }

            if(options.gridXStepEvery !== null){
                options.gridXLines = db.libs.chart.grid(( options.max / options.gridXStepEvery ), options.max);
            }

            if(options.gridYSteps !== null){
                options.gridYLines = db.libs.chart.grid(options.gridYSteps, 100);
            }

            $id.html( Mustache.render(db.templates['chart-line'], options) );

            $id.get(0).dispatchEvent( new Event('rendered') );

            return $id;
        }

        /**
         * Initialize the component
         * @public
         * @memberof db.libs.lineChart
         * @param {external:jQuery|string} [id] Selector or jQuery element
         * @param {object} [options] Options can be passed to init or read from the data-options attribute on the element
         * @param {array} [options.series] Values used to create the chart
         * @param {number} [options.max=null] Highest y-axis value for the chart
         * @param {number} [options.pointRadius=5] Radius for each point gives as px
         * @param {boolean} [options.smooth=false] Smoothe curves
         * @param {number} [options.gridXSteps=null]
         * @param {number} [options.gridXStepEvery=null]
         * @param {number} [options.gridYSteps=null]
         * @return {array} Returns array of all targeted elements
         */
        function init(id, options){
            var $targets;

            if(id !== undefined){
                $targets = $(id);
            } else {
                $targets = $('.line[data-options]');
            }

            $targets.each(function(i, el){
                if( !db.utils.isInitialized(el, name) ){
                    var $el = $(el);

                    var defaults = {
                        series: [],
                        lines: [],
                        max: null,
                        pointRadius: 5,
                        smooth: false,
                        gridXSteps: null,
                        gridXStepEvery: null,
                        gridYSteps: null,
                    };

                    if(id === undefined){
                        options = Foundation.utils.data_options($el);
                    }
                    options = $.extend({}, defaults, options);

                    options.width = $el.width();
                    options.height = $el.outerHeight();

                    $el.data('options', options);

                    series($el, options.series);
                    render($el);

                    db.utils.initialized(el, name);
                }
            });

            return $targets;
        }

        return {
            init: init,
            reflow: function(){},
            series: series,
            render: render,
            update: update,
            stream: stream
        };

    })(jQuery);
})();