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