123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550 |
- '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);
- 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(index) {
- var me = this;
- var meta = me.getMeta();
- var xScale = me.getScaleForId(meta.xAxisID);
- var datasetCount = me.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 !== me.chart.data.labels.length) {
- var perc = xScale.ticks.length / me.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(ruler) {
- var xScale = this.getScaleForId(this.getMeta().xAxisID);
- if (xScale.options.barThickness) {
- return xScale.options.barThickness;
- }
- 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, 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);
- 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(index) {
- var me = this;
- var meta = me.getMeta();
- var yScale = me.getScaleForId(meta.yAxisID);
- var datasetCount = me.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 !== me.chart.data.labels.length) {
- var perc = yScale.ticks.length / me.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(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 : 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);
- }
- });
- };
|