mirror of
https://github.com/chartjs/Chart.js.git
synced 2025-12-08 20:36:08 +00:00
528 lines
16 KiB
JavaScript
528 lines
16 KiB
JavaScript
'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() {
|
|
var me = this;
|
|
var barCount = 0;
|
|
helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) {
|
|
var meta = me.chart.getDatasetMeta(datasetIndex);
|
|
if (meta.bar && me.chart.isDatasetVisible(datasetIndex)) {
|
|
++barCount;
|
|
}
|
|
}, me);
|
|
return barCount;
|
|
},
|
|
|
|
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
|
|
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) {
|
|
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)) {
|
|
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 datasetCount = me.getBarCount();
|
|
|
|
var tickWidth = xScale.width / xScale.ticks.length;
|
|
var categoryWidth = tickWidth * xScale.options.categoryPercentage;
|
|
var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2;
|
|
var fullBarWidth = categoryWidth / datasetCount;
|
|
|
|
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(ruler) {
|
|
var xScale = this.getScaleForId(this.getMeta().xAxisID);
|
|
if (xScale.options.barThickness) {
|
|
return xScale.options.barThickness;
|
|
}
|
|
return xScale.options.stacked ? ruler.categoryWidth * xScale.options.barPercentage : 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, ruler) {
|
|
var me = this;
|
|
var meta = me.getMeta();
|
|
var xScale = me.getScaleForId(meta.xAxisID);
|
|
var barIndex = me.getBarIndex(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 * barIndex) +
|
|
(ruler.barSpacing / 2) +
|
|
(ruler.barSpacing * barIndex);
|
|
},
|
|
|
|
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) {
|
|
|
|
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)) {
|
|
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(ease) {
|
|
var me = this;
|
|
var easingDecimal = ease || 1;
|
|
var metaData = me.getMeta().data;
|
|
var dataset = me.getDataset();
|
|
var i, len;
|
|
|
|
for (i = 0, len = metaData.length; i < len; ++i) {
|
|
var d = dataset.data[i];
|
|
if (d !== null && d !== undefined && !isNaN(d)) {
|
|
metaData[i].transition(easingDecimal).draw();
|
|
}
|
|
}
|
|
},
|
|
|
|
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(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
|
|
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.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(cornerIndex) {
|
|
return corners[(startCorner + cornerIndex) % 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();
|
|
}
|
|
};
|
|
|
|
rectangle.pivot();
|
|
},
|
|
|
|
calculateBarBase: function(datasetIndex, index) {
|
|
var me = this;
|
|
var meta = me.getMeta();
|
|
var xScale = me.getScaleForId(meta.xAxisID);
|
|
var base = 0;
|
|
|
|
if (xScale.options.stacked) {
|
|
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)) {
|
|
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 datasetCount = me.getBarCount();
|
|
|
|
var tickHeight = yScale.height / yScale.ticks.length;
|
|
var categoryHeight = tickHeight * yScale.options.categoryPercentage;
|
|
var categorySpacing = (tickHeight - (tickHeight * yScale.options.categoryPercentage)) / 2;
|
|
var fullBarHeight = categoryHeight / datasetCount;
|
|
|
|
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(ruler) {
|
|
var me = this;
|
|
var yScale = me.getScaleForId(me.getMeta().yAxisID);
|
|
if (yScale.options.barThickness) {
|
|
return yScale.options.barThickness;
|
|
}
|
|
return yScale.options.stacked ? ruler.categoryHeight * yScale.options.barPercentage : ruler.barHeight;
|
|
},
|
|
|
|
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) {
|
|
|
|
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)) {
|
|
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 barIndex = me.getBarIndex(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 * barIndex) +
|
|
(ruler.barSpacing / 2) +
|
|
(ruler.barSpacing * barIndex);
|
|
}
|
|
});
|
|
};
|