;(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); })();