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