mirror of
https://github.com/chartjs/Chart.js.git
synced 2025-12-08 20:36:08 +00:00
Merge most of the horizontalBar controller into the bar one but also fix stack groups and bar positioning when scales are stacked or when a min and/or max tick values are explicitly defined. Note that this is a breaking change for derived controllers that rely on the following removed methods: `calculateBarBase`, `calculateBarX`, `calculateBarY`, `calculateBarWidth` and `calculateBarHeight`.
384 lines
10 KiB
JavaScript
384 lines
10 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() {
|
|
var me = this;
|
|
var meta;
|
|
|
|
Chart.DatasetController.prototype.initialize.apply(me, arguments);
|
|
|
|
meta = me.getMeta();
|
|
meta.stack = me.getDataset().stack;
|
|
meta.bar = true;
|
|
},
|
|
|
|
update: function(reset) {
|
|
var me = this;
|
|
var elements = me.getMeta().data;
|
|
var i, ilen;
|
|
|
|
me._ruler = me.getRuler();
|
|
|
|
for (i = 0, ilen = elements.length; i < ilen; ++i) {
|
|
me.updateElement(elements[i], i, reset);
|
|
}
|
|
},
|
|
|
|
updateElement: function(rectangle, index, reset) {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var meta = me.getMeta();
|
|
var dataset = me.getDataset();
|
|
var custom = rectangle.custom || {};
|
|
var rectangleOptions = chart.options.elements.rectangle;
|
|
|
|
rectangle._xScale = me.getScaleForId(meta.xAxisID);
|
|
rectangle._yScale = me.getScaleForId(meta.yAxisID);
|
|
rectangle._datasetIndex = me.index;
|
|
rectangle._index = index;
|
|
|
|
rectangle._model = {
|
|
datasetLabel: dataset.label,
|
|
label: chart.data.labels[index],
|
|
borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleOptions.borderSkipped,
|
|
backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleOptions.backgroundColor),
|
|
borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleOptions.borderColor),
|
|
borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleOptions.borderWidth)
|
|
};
|
|
|
|
me.updateElementGeometry(rectangle, index, reset);
|
|
|
|
rectangle.pivot();
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
updateElementGeometry: function(rectangle, index, reset) {
|
|
var me = this;
|
|
var model = rectangle._model;
|
|
var vscale = me.getValueScale();
|
|
var base = vscale.getBasePixel();
|
|
var horizontal = vscale.isHorizontal();
|
|
var ruler = me._ruler || me.getRuler();
|
|
var vpixels = me.calculateBarValuePixels(me.index, index);
|
|
var ipixels = me.calculateBarIndexPixels(me.index, index, ruler);
|
|
|
|
model.horizontal = horizontal;
|
|
model.base = reset? base : vpixels.base;
|
|
model.x = horizontal? reset? base : vpixels.head : ipixels.center;
|
|
model.y = horizontal? ipixels.center : reset? base : vpixels.head;
|
|
model.height = horizontal? ipixels.size : undefined;
|
|
model.width = horizontal? undefined : ipixels.size;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
getValueScaleId: function() {
|
|
return this.getMeta().yAxisID;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
getIndexScaleId: function() {
|
|
return this.getMeta().xAxisID;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
getValueScale: function() {
|
|
return this.getScaleForId(this.getValueScaleId());
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
getIndexScale: function() {
|
|
return this.getScaleForId(this.getIndexScaleId());
|
|
},
|
|
|
|
/**
|
|
* Returns the effective number of stacks based on groups and bar visibility.
|
|
* @private
|
|
*/
|
|
getStackCount: function(last) {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var scale = me.getIndexScale();
|
|
var stacked = scale.options.stacked;
|
|
var ilen = last === undefined? chart.data.datasets.length : last + 1;
|
|
var stacks = [];
|
|
var i, meta;
|
|
|
|
for (i = 0; i < ilen; ++i) {
|
|
meta = chart.getDatasetMeta(i);
|
|
if (meta.bar && chart.isDatasetVisible(i) &&
|
|
(stacked === false ||
|
|
(stacked === true && stacks.indexOf(meta.stack) === -1) ||
|
|
(stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) {
|
|
stacks.push(meta.stack);
|
|
}
|
|
}
|
|
|
|
return stacks.length;
|
|
},
|
|
|
|
/**
|
|
* Returns the stack index for the given dataset based on groups and bar visibility.
|
|
* @private
|
|
*/
|
|
getStackIndex: function(datasetIndex) {
|
|
return this.getStackCount(datasetIndex) - 1;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
getRuler: function() {
|
|
var me = this;
|
|
var scale = me.getIndexScale();
|
|
var options = scale.options;
|
|
var stackCount = me.getStackCount();
|
|
var fullSize = scale.isHorizontal()? scale.width : scale.height;
|
|
var tickSize = fullSize / scale.ticks.length;
|
|
var categorySize = tickSize * options.categoryPercentage;
|
|
var fullBarSize = categorySize / stackCount;
|
|
var barSize = fullBarSize * options.barPercentage;
|
|
|
|
barSize = Math.min(
|
|
helpers.getValueOrDefault(options.barThickness, barSize),
|
|
helpers.getValueOrDefault(options.maxBarThickness, Infinity));
|
|
|
|
return {
|
|
stackCount: stackCount,
|
|
tickSize: tickSize,
|
|
categorySize: categorySize,
|
|
categorySpacing: tickSize - categorySize,
|
|
fullBarSize: fullBarSize,
|
|
barSize: barSize,
|
|
barSpacing: fullBarSize - barSize,
|
|
scale: scale
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Note: pixel values are not clamped to the scale area.
|
|
* @private
|
|
*/
|
|
calculateBarValuePixels: function(datasetIndex, index) {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var meta = me.getMeta();
|
|
var scale = me.getValueScale();
|
|
var datasets = chart.data.datasets;
|
|
var value = Number(datasets[datasetIndex].data[index]);
|
|
var stacked = scale.options.stacked;
|
|
var stack = meta.stack;
|
|
var start = 0;
|
|
var i, imeta, ivalue, base, head, size;
|
|
|
|
if (stacked || (stacked === undefined && stack !== undefined)) {
|
|
for (i = 0; i < datasetIndex; ++i) {
|
|
imeta = chart.getDatasetMeta(i);
|
|
|
|
if (imeta.bar &&
|
|
imeta.stack === stack &&
|
|
imeta.controller.getValueScaleId() === scale.id &&
|
|
chart.isDatasetVisible(i)) {
|
|
|
|
ivalue = Number(datasets[i].data[index]);
|
|
if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) {
|
|
start += ivalue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
base = scale.getPixelForValue(start);
|
|
head = scale.getPixelForValue(start + value);
|
|
size = (head - base) / 2;
|
|
|
|
return {
|
|
size: size,
|
|
base: base,
|
|
head: head,
|
|
center: head + size / 2
|
|
};
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
calculateBarIndexPixels: function(datasetIndex, index, ruler) {
|
|
var me = this;
|
|
var scale = ruler.scale;
|
|
var isCombo = me.chart.isCombo;
|
|
var stackIndex = me.getStackIndex(datasetIndex);
|
|
var base = scale.getPixelForValue(null, index, datasetIndex, isCombo);
|
|
var size = ruler.barSize;
|
|
|
|
base -= isCombo? ruler.tickSize / 2 : 0;
|
|
base += ruler.fullBarSize * stackIndex;
|
|
base += ruler.categorySpacing / 2;
|
|
base += ruler.barSpacing / 2;
|
|
|
|
return {
|
|
size: size,
|
|
base: base,
|
|
head: base + size,
|
|
center: base + size / 2
|
|
};
|
|
},
|
|
|
|
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;
|
|
|
|
helpers.canvas.clipArea(chart.ctx, chart.chartArea);
|
|
|
|
for (; i<ilen; ++i) {
|
|
d = dataset.data[i];
|
|
if (d !== null && d !== undefined && !isNaN(d)) {
|
|
elements[i].draw();
|
|
}
|
|
}
|
|
|
|
helpers.canvas.unclipArea(chart.ctx);
|
|
},
|
|
|
|
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({
|
|
/**
|
|
* @private
|
|
*/
|
|
getValueScaleId: function() {
|
|
return this.getMeta().xAxisID;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
getIndexScaleId: function() {
|
|
return this.getMeta().yAxisID;
|
|
}
|
|
});
|
|
};
|