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