"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);
// Use this to indicate that this is a bar dataset.
this.getMeta().bar = true;
},
// Get the number of datasets that display bars. We use this to correctly calculate the bar width
getBarCount: function getBarCount() {
var barCount = 0;
helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) {
var meta = this.chart.getDatasetMeta(datasetIndex);
if (meta.bar && this.chart.isDatasetVisible(datasetIndex)) {
++barCount;
}
}, this);
return barCount;
},
update: function update(reset) {
helpers.each(this.getMeta().data, function(rectangle, index) {
this.updateElement(rectangle, index, reset);
}, this);
},
updateElement: function updateElement(rectangle, index, reset) {
var meta = this.getMeta();
var xScale = this.getScaleForId(meta.xAxisID);
var yScale = this.getScaleForId(meta.yAxisID);
var scaleBase = yScale.getBasePixel();
var rectangleElementOptions = this.chart.options.elements.rectangle;
var custom = rectangle.custom || {};
var dataset = this.getDataset();
helpers.extend(rectangle, {
// Utility
_xScale: xScale,
_yScale: yScale,
_datasetIndex: this.index,
_index: index,
// Desired view properties
_model: {
x: this.calculateBarX(index, this.index),
y: reset ? scaleBase : this.calculateBarY(index, this.index),
// Tooltip
label: this.chart.data.labels[index],
datasetLabel: dataset.label,
// Appearance
base: reset ? scaleBase : this.calculateBarBase(this.index, index),
width: this.calculateBarWidth(index),
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 meta = this.getMeta();
var yScale = this.getScaleForId(meta.yAxisID);
var base = 0;
if (yScale.options.stacked) {
var chart = this.chart;
var datasets = chart.data.datasets;
var value = datasets[datasetIndex].data[index];
if (value < 0) {
for (var i = 0; i < datasetIndex; i++) {
var negDS = datasets[i];
var negDSMeta = chart.getDatasetMeta(i);
if (negDSMeta.bar && negDSMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
base += negDS.data[index] < 0 ? negDS.data[index] : 0;
}
}
} else {
for (var j = 0; j < datasetIndex; j++) {
var posDS = datasets[j];
var posDSMeta = chart.getDatasetMeta(j);
if (posDSMeta.bar && posDSMeta.yAxisID === yScale.id && chart.isDatasetVisible(j)) {
base += posDS.data[index] > 0 ? posDS.data[index] : 0;
}
}
}
return yScale.getPixelForValue(base);
}
return yScale.getBasePixel();
},
getRuler: function(index) {
var meta = this.getMeta();
var xScale = this.getScaleForId(meta.xAxisID);
var datasetCount = this.getBarCount();
var tickWidth;
if (xScale.options.type === 'category') {
tickWidth = xScale.getPixelForTick(index + 1) - xScale.getPixelForTick(index);
} else {
// Average width
tickWidth = xScale.width / xScale.ticks.length;
}
var categoryWidth = tickWidth * xScale.options.categoryPercentage;
var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2;
var fullBarWidth = categoryWidth / datasetCount;
if (xScale.ticks.length !== this.chart.data.labels.length) {
var perc = xScale.ticks.length / this.chart.data.labels.length;
fullBarWidth = fullBarWidth * perc;
}
var barWidth = fullBarWidth * xScale.options.barPercentage;
var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage);
return {
datasetCount: datasetCount,
tickWidth: tickWidth,
categoryWidth: categoryWidth,
categorySpacing: categorySpacing,
fullBarWidth: fullBarWidth,
barWidth: barWidth,
barSpacing: barSpacing
};
},
calculateBarWidth: function(index) {
var xScale = this.getScaleForId(this.getMeta().xAxisID);
var ruler = this.getRuler(index);
return xScale.options.stacked ? ruler.categoryWidth : ruler.barWidth;
},
// Get bar index from the given dataset index accounting for the fact that not all bars are visible
getBarIndex: function(datasetIndex) {
var barIndex = 0;
var meta, j;
for (j = 0; j < datasetIndex; ++j) {
meta = this.chart.getDatasetMeta(j);
if (meta.bar && this.chart.isDatasetVisible(j)) {
++barIndex;
}
}
return barIndex;
},
calculateBarX: function(index, datasetIndex) {
var meta = this.getMeta();
var xScale = this.getScaleForId(meta.xAxisID);
var barIndex = this.getBarIndex(datasetIndex);
var ruler = this.getRuler(index);
var leftTick = xScale.getPixelForValue(null, index, datasetIndex, this.chart.isCombo);
leftTick -= this.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 * barIndex) +
(ruler.barSpacing / 2) +
(ruler.barSpacing * barIndex);
},
calculateBarY: function(index, datasetIndex) {
var meta = this.getMeta();
var yScale = this.getScaleForId(meta.yAxisID);
var value = this.getDataset().data[index];
if (yScale.options.stacked) {
var sumPos = 0,
sumNeg = 0;
for (var i = 0; i < datasetIndex; i++) {
var ds = this.chart.data.datasets[i];
var dsMeta = this.chart.getDatasetMeta(i);
if (dsMeta.bar && dsMeta.yAxisID === yScale.id && this.chart.isDatasetVisible(i)) {
if (ds.data[index] < 0) {
sumNeg += ds.data[index] || 0;
} else {
sumPos += ds.data[index] || 0;
}
}
}
if (value < 0) {
return yScale.getPixelForValue(sumNeg + value);
} else {
return yScale.getPixelForValue(sumPos + value);
}
}
return yScale.getPixelForValue(value);
},
draw: function(ease) {
var easingDecimal = ease || 1;
helpers.each(this.getMeta().data, function(rectangle, index) {
var d = this.getDataset().data[index];
if (d !== null && d !== undefined && !isNaN(d)) {
rectangle.transition(easingDecimal).draw();
}
}, this);
},
setHoverStyle: function(rectangle) {
var dataset = this.chart.data.datasets[rectangle._datasetIndex];
var index = rectangle._index;
var custom = rectangle.custom || {};
var model = rectangle._model;
model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor));
model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
},
removeHoverStyle: function(rectangle) {
var dataset = this.chart.data.datasets[rectangle._datasetIndex];
var index = rectangle._index;
var custom = rectangle.custom || {};
var model = rectangle._model;
var rectangleElementOptions = this.chart.options.elements.rectangle;
model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor);
model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor);
model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth);
}
});
// including horizontalBar in the bar file, instead of a file of its own
// it extends bar (like pie extends doughnut)
Chart.defaults.horizontalBar = {
hover: {
mode: "label"
},
scales: {
xAxes: [{
type: "linear",
position: "bottom"
}],
yAxes: [{
position: "left",
type: "category",
// Specific to Horizontal Bar Controller
categoryPercentage: 0.8,
barPercentage: 0.9,
// grid line settings
gridLines: {
offsetGridLines: true
}
}]
},
elements: {
rectangle: {
borderSkipped: 'left'
}
},
tooltips: {
callbacks: {
title: function(tooltipItems, data) {
// Pick first xLabel for now
var title = '';
if (tooltipItems.length > 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({
updateElement: function updateElement(rectangle, index, reset, numBars) {
var meta = this.getMeta();
var xScale = this.getScaleForId(meta.xAxisID);
var yScale = this.getScaleForId(meta.yAxisID);
var scaleBase = xScale.getBasePixel();
var custom = rectangle.custom || {};
var dataset = this.getDataset();
var rectangleElementOptions = this.chart.options.elements.rectangle;
helpers.extend(rectangle, {
// Utility
_xScale: xScale,
_yScale: yScale,
_datasetIndex: this.index,
_index: index,
// Desired view properties
_model: {
x: reset ? scaleBase : this.calculateBarX(index, this.index),
y: this.calculateBarY(index, this.index),
// Tooltip
label: this.chart.data.labels[index],
datasetLabel: dataset.label,
// Appearance
base: reset ? scaleBase : this.calculateBarBase(this.index, index),
height: this.calculateBarHeight(index),
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)
},
draw: function () {
var ctx = this._chart.ctx;
var vm = this._view;
var halfHeight = vm.height / 2,
topY = vm.y - halfHeight,
bottomY = vm.y + halfHeight,
right = vm.base - (vm.base - vm.x),
halfStroke = vm.borderWidth / 2;
// Canvas doesn't allow us to stroke inside the width so we can
// adjust the sizes to fit if we're setting a stroke on the line
if (vm.borderWidth) {
topY += halfStroke;
bottomY -= halfStroke;
right += halfStroke;
}
ctx.beginPath();
ctx.fillStyle = vm.backgroundColor;
ctx.strokeStyle = vm.borderColor;
ctx.lineWidth = vm.borderWidth;
// Corner points, from bottom-left to bottom-right clockwise
// | 1 2 |
// | 0 3 |
var corners = [
[vm.base, bottomY],
[vm.base, topY],
[right, topY],
[right, bottomY]
];
// Find first (starting) corner with fallback to 'bottom'
var borders = ['bottom', 'left', 'top', 'right'];
var startCorner = borders.indexOf(vm.borderSkipped, 0);
if (startCorner === -1)
startCorner = 0;
function cornerAt(index) {
return corners[(startCorner + index) % 4];
}
// Draw rectangle from 'startCorner'
ctx.moveTo.apply(ctx, cornerAt(0));
for (var i = 1; i < 4; i++)
ctx.lineTo.apply(ctx, cornerAt(i));
ctx.fill();
if (vm.borderWidth) {
ctx.stroke();
}
},
inRange: function (mouseX, mouseY) {
var vm = this._view;
var inRange = false;
if (vm) {
if (vm.x < vm.base) {
inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.x && mouseX <= vm.base);
} else {
inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.base && mouseX <= vm.x);
}
}
return inRange;
}
});
rectangle.pivot();
},
calculateBarBase: function (datasetIndex, index) {
var meta = this.getMeta();
var xScale = this.getScaleForId(meta.xAxisID);
var base = 0;
if (xScale.options.stacked) {
var value = this.chart.data.datasets[datasetIndex].data[index];
if (value < 0) {
for (var i = 0; i < datasetIndex; i++) {
var negDS = this.chart.data.datasets[i];
var negDSMeta = this.chart.getDatasetMeta(i);
if (negDSMeta.bar && negDSMeta.xAxisID === xScale.id && this.chart.isDatasetVisible(i)) {
base += negDS.data[index] < 0 ? negDS.data[index] : 0;
}
}
} else {
for (var j = 0; j < datasetIndex; j++) {
var posDS = this.chart.data.datasets[j];
var posDSMeta = this.chart.getDatasetMeta(j);
if (posDSMeta.bar && posDSMeta.xAxisID === xScale.id && this.chart.isDatasetVisible(j)) {
base += posDS.data[index] > 0 ? posDS.data[index] : 0;
}
}
}
return xScale.getPixelForValue(base);
}
return xScale.getBasePixel();
},
getRuler: function (index) {
var meta = this.getMeta();
var yScale = this.getScaleForId(meta.yAxisID);
var datasetCount = this.getBarCount();
var tickHeight;
if (yScale.options.type === 'category') {
tickHeight = yScale.getPixelForTick(index + 1) - yScale.getPixelForTick(index);
} else {
// Average width
tickHeight = yScale.width / yScale.ticks.length;
}
var categoryHeight = tickHeight * yScale.options.categoryPercentage;
var categorySpacing = (tickHeight - (tickHeight * yScale.options.categoryPercentage)) / 2;
var fullBarHeight = categoryHeight / datasetCount;
if (yScale.ticks.length !== this.chart.data.labels.length) {
var perc = yScale.ticks.length / this.chart.data.labels.length;
fullBarHeight = fullBarHeight * perc;
}
var barHeight = fullBarHeight * yScale.options.barPercentage;
var barSpacing = fullBarHeight - (fullBarHeight * yScale.options.barPercentage);
return {
datasetCount: datasetCount,
tickHeight: tickHeight,
categoryHeight: categoryHeight,
categorySpacing: categorySpacing,
fullBarHeight: fullBarHeight,
barHeight: barHeight,
barSpacing: barSpacing,
};
},
calculateBarHeight: function (index) {
var yScale = this.getScaleForId(this.getMeta().yAxisID);
var ruler = this.getRuler(index);
return yScale.options.stacked ? ruler.categoryHeight : ruler.barHeight;
},
calculateBarX: function (index, datasetIndex) {
var meta = this.getMeta();
var xScale = this.getScaleForId(meta.xAxisID);
var value = this.getDataset().data[index];
if (xScale.options.stacked) {
var sumPos = 0,
sumNeg = 0;
for (var i = 0; i < datasetIndex; i++) {
var ds = this.chart.data.datasets[i];
var dsMeta = this.chart.getDatasetMeta(i);
if (dsMeta.bar && dsMeta.xAxisID === xScale.id && this.chart.isDatasetVisible(i)) {
if (ds.data[index] < 0) {
sumNeg += ds.data[index] || 0;
} else {
sumPos += ds.data[index] || 0;
}
}
}
if (value < 0) {
return xScale.getPixelForValue(sumNeg + value);
} else {
return xScale.getPixelForValue(sumPos + value);
}
}
return xScale.getPixelForValue(value);
},
calculateBarY: function (index, datasetIndex) {
var meta = this.getMeta();
var yScale = this.getScaleForId(meta.yAxisID);
var barIndex = this.getBarIndex(datasetIndex);
var ruler = this.getRuler(index);
var topTick = yScale.getPixelForValue(null, index, datasetIndex, this.chart.isCombo);
topTick -= this.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 * barIndex) +
(ruler.barSpacing / 2) +
(ruler.barSpacing * barIndex);
}
});
};
|