'use strict'; module.exports = function(Chart) { var helpers = Chart.helpers; Chart.defaults.bar = { hover: { mode: 'label' }, scales: { xAxes: [{ type: 'category', // Specific to Bar Controller categoryPercentage: 0.8, barPercentage: 0.9, // grid line settings gridLines: { offsetGridLines: true } }], yAxes: [{ type: 'linear' }] } }; Chart.controllers.bar = Chart.DatasetController.extend({ dataElementType: Chart.elements.Rectangle, initialize: function(chart, datasetIndex) { Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex); var me = this; var meta = me.getMeta(); var dataset = me.getDataset(); meta.stack = dataset.stack; // Use this to indicate that this is a bar dataset. meta.bar = true; }, // Correctly calculate the bar width accounting for stacks and the fact that not all bars are visible getStackCount: function() { var me = this; var meta = me.getMeta(); var yScale = me.getScaleForId(meta.yAxisID); var stacks = []; helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { var dsMeta = me.chart.getDatasetMeta(datasetIndex); if (dsMeta.bar && me.chart.isDatasetVisible(datasetIndex) && (yScale.options.stacked === false || (yScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) || (yScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) { stacks.push(dsMeta.stack); } }, me); return stacks.length; }, update: function(reset) { var me = this; helpers.each(me.getMeta().data, function(rectangle, index) { me.updateElement(rectangle, index, reset); }, me); }, updateElement: function(rectangle, index, reset) { var me = this; var meta = me.getMeta(); var xScale = me.getScaleForId(meta.xAxisID); var yScale = me.getScaleForId(meta.yAxisID); var scaleBase = yScale.getBasePixel(); var rectangleElementOptions = me.chart.options.elements.rectangle; var custom = rectangle.custom || {}; var dataset = me.getDataset(); rectangle._xScale = xScale; rectangle._yScale = yScale; rectangle._datasetIndex = me.index; rectangle._index = index; var ruler = me.getRuler(index); // The index argument for compatible rectangle._model = { x: me.calculateBarX(index, me.index, ruler), y: reset ? scaleBase : me.calculateBarY(index, me.index), // Tooltip label: me.chart.data.labels[index], datasetLabel: dataset.label, // Appearance horizontal: false, base: reset ? scaleBase : me.calculateBarBase(me.index, index), width: me.calculateBarWidth(ruler), backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor), borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped, borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor), borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth) }; rectangle.pivot(); }, calculateBarBase: function(datasetIndex, index) { var me = this; var meta = me.getMeta(); var yScale = me.getScaleForId(meta.yAxisID); var base = 0; if (yScale.options.stacked === true || (yScale.options.stacked === undefined && meta.stack !== undefined)) { var chart = me.chart; var datasets = chart.data.datasets; var value = Number(datasets[datasetIndex].data[index]); for (var i = 0; i < datasetIndex; i++) { var currentDs = datasets[i]; var currentDsMeta = chart.getDatasetMeta(i); if (currentDsMeta.bar && currentDsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i) && meta.stack === currentDsMeta.stack) { var currentVal = Number(currentDs.data[index]); base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0); } } return yScale.getPixelForValue(base); } return yScale.getBasePixel(); }, getRuler: function() { var me = this; var meta = me.getMeta(); var xScale = me.getScaleForId(meta.xAxisID); var stackCount = me.getStackCount(); var tickWidth = xScale.width / xScale.ticks.length; var categoryWidth = tickWidth * xScale.options.categoryPercentage; var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2; var fullBarWidth = categoryWidth / stackCount; var barWidth = fullBarWidth * xScale.options.barPercentage; var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage); return { stackCount: stackCount, tickWidth: tickWidth, categoryWidth: categoryWidth, categorySpacing: categorySpacing, fullBarWidth: fullBarWidth, barWidth: barWidth, barSpacing: barSpacing }; }, calculateBarWidth: function(ruler) { var me = this; var meta = me.getMeta(); var xScale = me.getScaleForId(meta.xAxisID); var options = xScale.options; var maxBarThickness = options.maxBarThickness || Infinity; var barWidth; if (options.barThickness) { return options.barThickness; } barWidth = options.stacked ? ruler.categoryWidth * options.barPercentage : ruler.barWidth; return Math.min(barWidth, maxBarThickness); }, // Get stack index from the given dataset index accounting for stacks and the fact that not all bars are visible getStackIndex: function(datasetIndex) { var me = this; var meta = me.chart.getDatasetMeta(datasetIndex); var yScale = me.getScaleForId(meta.yAxisID); var dsMeta, j; var stacks = [meta.stack]; for (j = 0; j < datasetIndex; ++j) { dsMeta = this.chart.getDatasetMeta(j); if (dsMeta.bar && this.chart.isDatasetVisible(j) && (yScale.options.stacked === false || (yScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) || (yScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) { stacks.push(dsMeta.stack); } } return stacks.length - 1; }, calculateBarX: function(index, datasetIndex, ruler) { var me = this; var meta = me.getMeta(); var xScale = me.getScaleForId(meta.xAxisID); var stackIndex = me.getStackIndex(datasetIndex); var leftTick = xScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo); leftTick -= me.chart.isCombo ? (ruler.tickWidth / 2) : 0; if (xScale.options.stacked) { return leftTick + (ruler.categoryWidth / 2) + ruler.categorySpacing; } return leftTick + (ruler.barWidth / 2) + ruler.categorySpacing + (ruler.barWidth * stackIndex) + (ruler.barSpacing / 2) + (ruler.barSpacing * stackIndex); }, calculateBarY: function(index, datasetIndex) { var me = this; var meta = me.getMeta(); var yScale = me.getScaleForId(meta.yAxisID); var value = Number(me.getDataset().data[index]); if (yScale.options.stacked || (yScale.options.stacked === undefined && meta.stack !== undefined)) { var sumPos = 0, sumNeg = 0; for (var i = 0; i < datasetIndex; i++) { var ds = me.chart.data.datasets[i]; var dsMeta = me.chart.getDatasetMeta(i); if (dsMeta.bar && dsMeta.yAxisID === yScale.id && me.chart.isDatasetVisible(i) && meta.stack === dsMeta.stack) { var stackedVal = Number(ds.data[index]); if (stackedVal < 0) { sumNeg += stackedVal || 0; } else { sumPos += stackedVal || 0; } } } if (value < 0) { return yScale.getPixelForValue(sumNeg + value); } return yScale.getPixelForValue(sumPos + value); } return yScale.getPixelForValue(value); }, draw: function() { var me = this; var chart = me.chart; var elements = me.getMeta().data; var dataset = me.getDataset(); var ilen = elements.length; var i = 0; var d; Chart.canvasHelpers.clipArea(chart.ctx, chart.chartArea); for (; i 0) { if (tooltipItems[0].yLabel) { title = tooltipItems[0].yLabel; } else if (data.labels.length > 0 && tooltipItems[0].index < data.labels.length) { title = data.labels[tooltipItems[0].index]; } } return title; }, label: function(tooltipItem, data) { var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; return datasetLabel + ': ' + tooltipItem.xLabel; } } } }; Chart.controllers.horizontalBar = Chart.controllers.bar.extend({ // Correctly calculate the bar width accounting for stacks and the fact that not all bars are visible getStackCount: function() { var me = this; var meta = me.getMeta(); var xScale = me.getScaleForId(meta.xAxisID); var stacks = []; helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { var dsMeta = me.chart.getDatasetMeta(datasetIndex); if (dsMeta.bar && me.chart.isDatasetVisible(datasetIndex) && (xScale.options.stacked === false || (xScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) || (xScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) { stacks.push(dsMeta.stack); } }, me); return stacks.length; }, updateElement: function(rectangle, index, reset) { var me = this; var meta = me.getMeta(); var xScale = me.getScaleForId(meta.xAxisID); var yScale = me.getScaleForId(meta.yAxisID); var scaleBase = xScale.getBasePixel(); var custom = rectangle.custom || {}; var dataset = me.getDataset(); var rectangleElementOptions = me.chart.options.elements.rectangle; rectangle._xScale = xScale; rectangle._yScale = yScale; rectangle._datasetIndex = me.index; rectangle._index = index; var ruler = me.getRuler(index); // The index argument for compatible rectangle._model = { x: reset ? scaleBase : me.calculateBarX(index, me.index), y: me.calculateBarY(index, me.index, ruler), // Tooltip label: me.chart.data.labels[index], datasetLabel: dataset.label, // Appearance horizontal: true, base: reset ? scaleBase : me.calculateBarBase(me.index, index), height: me.calculateBarHeight(ruler), backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor), borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped, borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor), borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth) }; rectangle.pivot(); }, calculateBarBase: function(datasetIndex, index) { var me = this; var meta = me.getMeta(); var xScale = me.getScaleForId(meta.xAxisID); var base = xScale.getBaseValue(); if (xScale.options.stacked || (xScale.options.stacked === undefined && meta.stack !== undefined)) { var chart = me.chart; var datasets = chart.data.datasets; var value = Number(datasets[datasetIndex].data[index]); for (var i = 0; i < datasetIndex; i++) { var currentDs = datasets[i]; var currentDsMeta = chart.getDatasetMeta(i); if (currentDsMeta.bar && currentDsMeta.xAxisID === xScale.id && chart.isDatasetVisible(i) && meta.stack === currentDsMeta.stack) { var currentVal = Number(currentDs.data[index]); base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0); } } return xScale.getPixelForValue(base); } return xScale.getBasePixel(); }, getRuler: function() { var me = this; var meta = me.getMeta(); var yScale = me.getScaleForId(meta.yAxisID); var stackCount = me.getStackCount(); var tickHeight = yScale.height / yScale.ticks.length; var categoryHeight = tickHeight * yScale.options.categoryPercentage; var categorySpacing = (tickHeight - (tickHeight * yScale.options.categoryPercentage)) / 2; var fullBarHeight = categoryHeight / stackCount; var barHeight = fullBarHeight * yScale.options.barPercentage; var barSpacing = fullBarHeight - (fullBarHeight * yScale.options.barPercentage); return { stackCount: stackCount, tickHeight: tickHeight, categoryHeight: categoryHeight, categorySpacing: categorySpacing, fullBarHeight: fullBarHeight, barHeight: barHeight, barSpacing: barSpacing }; }, calculateBarHeight: function(ruler) { var me = this; var meta = me.getMeta(); var yScale = me.getScaleForId(meta.yAxisID); var options = yScale.options; var maxBarThickness = options.maxBarThickness || Infinity; var barHeight; if (options.barThickness) { return options.barThickness; } barHeight = options.stacked ? ruler.categoryHeight * options.barPercentage : ruler.barHeight; return Math.min(barHeight, maxBarThickness); }, // Get stack index from the given dataset index accounting for stacks and the fact that not all bars are visible getStackIndex: function(datasetIndex) { var me = this; var meta = me.chart.getDatasetMeta(datasetIndex); var xScale = me.getScaleForId(meta.xAxisID); var dsMeta, j; var stacks = [meta.stack]; for (j = 0; j < datasetIndex; ++j) { dsMeta = this.chart.getDatasetMeta(j); if (dsMeta.bar && this.chart.isDatasetVisible(j) && (xScale.options.stacked === false || (xScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) || (xScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) { stacks.push(dsMeta.stack); } } return stacks.length - 1; }, calculateBarX: function(index, datasetIndex) { var me = this; var meta = me.getMeta(); var xScale = me.getScaleForId(meta.xAxisID); var value = Number(me.getDataset().data[index]); if (xScale.options.stacked || (xScale.options.stacked === undefined && meta.stack !== undefined)) { var sumPos = 0, sumNeg = 0; for (var i = 0; i < datasetIndex; i++) { var ds = me.chart.data.datasets[i]; var dsMeta = me.chart.getDatasetMeta(i); if (dsMeta.bar && dsMeta.xAxisID === xScale.id && me.chart.isDatasetVisible(i) && meta.stack === dsMeta.stack) { var stackedVal = Number(ds.data[index]); if (stackedVal < 0) { sumNeg += stackedVal || 0; } else { sumPos += stackedVal || 0; } } } if (value < 0) { return xScale.getPixelForValue(sumNeg + value); } return xScale.getPixelForValue(sumPos + value); } return xScale.getPixelForValue(value); }, calculateBarY: function(index, datasetIndex, ruler) { var me = this; var meta = me.getMeta(); var yScale = me.getScaleForId(meta.yAxisID); var stackIndex = me.getStackIndex(datasetIndex); var topTick = yScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo); topTick -= me.chart.isCombo ? (ruler.tickHeight / 2) : 0; if (yScale.options.stacked) { return topTick + (ruler.categoryHeight / 2) + ruler.categorySpacing; } return topTick + (ruler.barHeight / 2) + ruler.categorySpacing + (ruler.barHeight * stackIndex) + (ruler.barSpacing / 2) + (ruler.barSpacing * stackIndex); } }); };