(function($){
var settings = {};
$.fn.barChart = function(options){
var defaults = {
vertical : false,
bars : [],
hiddenBars : [],
milestones : [],
colors : [
"#f44336", "#e91e63", "#9c27b0", "#673ab7", "#3f51b5",
"#2196f3", "#03a9f4", "#00bcd4", "#009688", "#4caf50",
"#8bc34a", "#cddc39", "#ffeb3b", "#ffc107", "#ff9800",
"#ff5722", "#795548", "#9e9e9e", "#607d8b", "#263238"
],
barColors : {},
dateFormat : 'DD.MM.YYYY HH:mm',
barGap : 5,
totalSumHeight : 25,
defaultWidth : 40,
defaultColumnWidth : 65
};
settings = $.extend(settings, defaults, options);
$(this)
.css( 'height', settings.height && !settings.vertical ? settings.height : 'auto' )
.addClass('bar-chart')
.addClass( settings.vertical ? 'bar-chart-vertical' : '' )
.wrap('
');
$.proxy(init, this)();
this.handler = this;
return this;
};
// init
function init(){
settings.maxHeight = settings.vertical ? $(this).width() : ( $(this).height() - settings.totalSumHeight );
settings.maxWidth = settings.vertical ? settings.defaultWidth : $(this).width();
settings.barGapPercent = settings.barGap / (settings.maxWidth / 100);
var bars = colorizeBars(settings.bars, settings.colors);
var columns = groupByKey(bars, settings.hiddenBars);
$.proxy(drawY, this)(columns);
console.time('draw X');
$.proxy(drawX, this)(columns);
console.timeEnd('draw X');
$.proxy(drawTooltip, this)();
$.proxy(drawLegend, this)(bars, settings.hiddenBars);
$.proxy(subscribe_tooltip, this)();
$.proxy(subscribe_legend, this)();
return this;
};
// up to date
function update(){
var bars = colorizeBars(settings.bars, settings.colors);
var columns = groupByKey(bars, settings.hiddenBars);
$(this).html('');
$.proxy(drawY, this)(columns);
$.proxy(drawX, this)(columns);
return this;
};
// group bar values by keys
function groupByKey(bars, hiddenBars){
var hiddenBarsArray = hiddenBars || [];
var columns = {};
bars.forEach(function(bar){
if (hiddenBarsArray.indexOf(bar.name) !== -1) {
return true;
}
bar.values.forEach(function(value){
columns[ value[0] ] = columns[ value[0] ] || [];
columns[ value[0] ].push({ value : parseFloat(value[1]), name : bar.name, color : bar.color });
});
});
return columns;
};
// set bars colors
function colorizeBars(bars, colors){
colorIndex = 0;
bars.forEach(function(bar){
if (typeof bar.color === 'undefined') {
bar.color = colors[colorIndex];
}
colorIndex++;
if (colorIndex >= colors.length) {
colorIndex = 0;
}
});
return bars;
};
// find max value through all bars
function findMax(columns){
var result = 0;
for (var i in columns) {
if (columns.hasOwnProperty(i)) {
var max = 0;
columns[i].forEach(function(value){
max += value.value;
});
if (max > result) {
result = max;
}
}
}
return result;
};
// find total sum of all values through all bars
function totalSum(columns){
var result = 0;
for (var i in columns) {
if (columns.hasOwnProperty(i)) {
columns[i].forEach(function(value){
result += value.value;
});
}
}
return result;
};
// draw y-milestones
function drawY(columns){
var $container = $('').addClass( settings.vertical ? 'bar-x' : 'bar-y');
var max = findMax(columns);
var milestonesCount = Math.round( max ).toString().length;
var multiplier = Math.pow(10, milestonesCount - 1);
max = settings.vertical ? Math.ceil(max) : Math.ceil(max / multiplier) * multiplier;
var step = (max / 5);
if (step < 1) {
step = 1;
}
var top = 0;
var value = 0;
while (top < settings.maxHeight) {
top = (value * settings.maxHeight) / max;
var gridValue = value;
if (gridValue < 1000) {
gridValue = gridValue.toFixed(2);
}
if (gridValue >= 1000 && gridValue <= 1000000) {
gridValue = (gridValue / 1000).toFixed(2) + ' K';
}
if (gridValue >= 1000000 && gridValue <= 1000000000) {
gridValue = (gridValue / 1000000).toFixed(2) + ' M';
}
var $gridValue = $('')
.addClass( settings.vertical ? 'bar-x-value' : 'bar-y-value')
.css( settings.vertical ? { left : top } : { bottom: top } )
.html('' + gridValue + '
');
$container.append( $gridValue );
value += step;
}
$(this).append($container);
return this;
};
// draw x-values
function drawX(columns){
var keys = Object.keys(columns);
var columnsCount = keys.length;
var columnWidth = Math.round((settings.maxWidth - settings.barGap * (columnsCount + 1)) / columnsCount);
if (settings.vertical) {
columnWidth = settings.defaultWidth;
}
var max = findMax(columns);
var total = totalSum(columns);
if (!settings.vertical) {
if (columnWidth < settings.defaultColumnWidth) { //settings.defaultColumnWidth = 65
$(this).addClass('bar-titles-vertical');
}
columnWidth = (columnWidth / (settings.maxWidth / 100));
}
keys.sort(function(a,b){ return parseInt(a) - parseInt(b); });
for (var k in keys) {
if (keys.hasOwnProperty(k)) {
var key = keys[k];
var column = columns[key];
var localMax = 0;
var localSum = 0;
var localMaxHeight = 0;
//sort values desc
column.sort(function (a, b) { return b.value - a.value; });
column.forEach(function(bar){
localMax = bar.value > localMax ? bar.value : localMax;
localSum += bar.value;
});
localMaxHeight = (localMax * settings.maxHeight / max);
var text = key.toString()
//it's timestamp, so let's format it
if (text.length === 10 && text == parseInt(text)) {
text = formatDate(new Date(text * 1000));
}
var $barTitle = $('').addClass('bar-title').html( text );
var $barValue = $('')
.addClass('bar-value')
.css(
settings.vertical ?
{ width : localMaxHeight } :
{ height : localMaxHeight }
);
var $bar = $('')
.addClass('bar')
.css(
settings.vertical ?
{ height : columnWidth } :
{ width : columnWidth + '%', marginLeft : settings.barGapPercent + '%' }
)
.attr({ 'data-id' : key })
.append( $barValue )
.append( $barTitle );
$(this).append( $bar );
var bottom = 0;
var previousBottom = 0;
var previousHeight = 0;
console.time('bar lines');
var appendixArray = [];
column.forEach(function (bar) {
var height = localMaxHeight / localMax * bar.value;
var percentage = (bar.value / (total / 100)).toFixed(2);
bottom = previousHeight + previousBottom;
$appendix = $('')
.addClass('bar-line')
.attr({
'data-percentage' : (percentage + '%'),
'data-name' : bar.name,
'data-value' : bar.value
})
.css(
settings.vertical ?
{ background : bar.color, width: height, left: bottom } :
{ background: bar.color, height: height, bottom: bottom }
);
$barValue.append( $appendix );
previousBottom = bottom;
previousHeight = height;
});
console.timeEnd('bar lines');
var tmpSum = localSum.toString().split('.');
if (tmpSum[0].length >= 5) {
tmpSum[0] = tmpSum[0].replace(/(\d)(?=(\d{3})+$)/g, '$1 ');
}
localSum = tmpSum.join('.');
$barValueSum = $('')
.addClass('bar-value-sum')
.css(
settings.vertical ?
{ left : previousBottom + previousHeight } :
{ bottom : previousBottom + previousHeight }
)
.html( localSum ); // + currency
$bar.append( $barValueSum );
}
}
return this;
};
// adds tooltip markup to dom
function drawTooltip(){
if ($(this).find('.tooltip').length === 0) {
$(this).append(
''
);
}
return this;
};
// legend
function drawLegend(bars, hiddenBars){
var $legend = $('').addClass('bar-legend legend');
$(this).parent().append( $legend );
bars.forEach(function(bar){
var $checkbox = $('')
.addClass('checkbox')
.addClass( hiddenBars.indexOf(bar.name) === -1 ? 'checked' : '' )
.css({ 'background-color' : bar.color });
var $legendItem = $('')
.addClass('legend-item')
.css({ color : bar.color })
.html( bar.name )
var $legendItemWrapper = $('')
.addClass('legend-item-wrapper')
.append( $checkbox )
.append( $legendItem );
$legend.append( $legendItemWrapper );
});
return this;
};
// mousemove and mouseleave pon bar
function subscribe_tooltip(){
var $barLines = $(this).find('.bar-line');
var $tooltip = $(this).find('.tooltip');
$barLines.on('mousemove', function(e){
$(this).parents('.bar').addClass('bar-active');
$tooltip.css({
top: e.pageY - 65, // + $(this).offset().top
left: e.pageX - 65 // + $(this).offset().left
});
$tooltip.find('.tooltip-title').html( $(this).data('name') );
$tooltip.find('.tooltip-change').html( $(this).data('value') + '' + $(this).data('percentage') + '' );
$tooltip.removeClass('hidden');
});
$barLines.on('mouseleave', function(e){
$tooltip.addClass('hidden');
$(this).parents('.bar').removeClass('bar-active');
});
return this;
};
// checkbox click and double click
function subscribe_legend(){
/**
* emulate single and double clicks pon same element
*/
var clicks = 0;
var timer = null;
var delay = 200;
var $self = $(this);
var $legendItemWrapper = $(this).parent().find('.legend-item-wrapper');
$legendItemWrapper.on('mouseleave', function(){
var barName = $(this).find('.legend-item').html();
var $bar = $('.bar-line[data-name="' + barName + '"]');
$bar.removeClass('active');
});
$legendItemWrapper.on('mouseenter', function(){
var barName = $(this).find('.legend-item').html();
var $bar = $('.bar-line[data-name="' + barName + '"]');
$bar.addClass('active');
});
$legendItemWrapper.on('click', function(e){
e.preventDefault();
var $this = $(this);
clicks++;
if (clicks === 1) {
timer = setTimeout(function(){
clearTimeout(timer);
var name = $this.find('.legend-item').html();
var isChecked = $this.find('.checkbox').hasClass('checked');
$('.bar-line[data-name="' + name + '"]').toggleClass('hidden');
$this.find('.checkbox').toggleClass('checked');
if (isChecked) {
settings.hiddenBars.push(name);
} else {
var index = settings.hiddenBars.indexOf(name);
if (index >= 0) {
settings.hiddenBars.splice(index, 1);
}
}
$.proxy(update, $self)();
clicks = 0;
}, delay);
} else {
clearTimeout(timer);
var $checkbox = $(this).find('.checkbox');
var $checkboxes = $(this).parent().find('.checkbox.checked');
var checkedCount = $checkboxes.length;
if (checkedCount === 1 && $checkbox.hasClass('checked')) {
$(this).parent().find('.checkbox').addClass('checked');
} else {
$(this).parent().find('.checkbox').removeClass('checked');
$checkbox.addClass('checked');
}
var checkboxes = [];
$(this).parent().find('.checkbox:not(.checked)').each(function(){
checkboxes.push( $(this).next('.legend-item').html() );
});
settings.hiddenBars = checkboxes;
//self.update();
$.proxy(update, $self)();
clicks = 0;
}
});
$legendItemWrapper.on('dblclick', function(e){
e.preventDefault();
});
return this;
};
// dateformat to dd/mm/yyyy
function formatDate(dt) {
var dd = dt.getDate();
var mm = dt.getMonth() + 1;
var yyyy = dt.getFullYear().toString().substring(2);
return [ dd, mm, yyyy ].join('.');
};
}(jQuery));