diff --git a/docs/04-Bar-Chart.md b/docs/04-Bar-Chart.md index 777acc7a7e7..ef2cd3c19a6 100644 --- a/docs/04-Bar-Chart.md +++ b/docs/04-Bar-Chart.md @@ -49,6 +49,7 @@ borderSkipped | `String or Array` | Which edge to skip drawing the borde hoverBackgroundColor | `Color or Array` | Bar background color when hovered hoverBorderColor | `Color or Array` | Bar border color when hovered hoverBorderWidth | `Number or Array` | Border width of bar when hovered +stack | `String` | The ID of the group to which this dataset belongs to (when stacked, each group will be a separate stack) An example data object using these attributes is shown below. diff --git a/samples/bar/bar-stacked-group.html b/samples/bar/bar-stacked-group.html new file mode 100644 index 00000000000..074e3eac08c --- /dev/null +++ b/samples/bar/bar-stacked-group.html @@ -0,0 +1,105 @@ + + + + + Stacked Bar Chart with Groups + + + + + + +
+ +
+ + + + + diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index ecf43b19bce..1d41386b6d7 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -35,21 +35,33 @@ module.exports = function(Chart) { 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. - this.getMeta().bar = true; + meta.bar = true; }, - // Get the number of datasets that display bars. We use this to correctly calculate the bar width - getBarCount: function() { + // Correctly calculate the bar width accounting for stacks and the fact that not all bars are visible + getStackCount: function() { var me = this; - var barCount = 0; + var meta = me.getMeta(); + var yScale = me.getScaleForId(meta.yAxisID); + + var stacks = []; helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { - var meta = me.chart.getDatasetMeta(datasetIndex); - if (meta.bar && me.chart.isDatasetVisible(datasetIndex)) { - ++barCount; + 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 barCount; + + return stacks.length; }, update: function(reset) { @@ -103,7 +115,8 @@ module.exports = function(Chart) { var base = yScale.getBaseValue(); var original = base; - if (yScale.options.stacked) { + 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]); @@ -111,7 +124,8 @@ module.exports = function(Chart) { 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)) { + 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, original) : Math.max(currentVal, original); } @@ -127,18 +141,18 @@ module.exports = function(Chart) { var me = this; var meta = me.getMeta(); var xScale = me.getScaleForId(meta.xAxisID); - var datasetCount = me.getBarCount(); + var stackCount = me.getStackCount(); - var tickWidth = xScale.width / xScale.ticks.length; + 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 fullBarWidth = categoryWidth / stackCount; var barWidth = fullBarWidth * xScale.options.barPercentage; var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage); return { - datasetCount: datasetCount, + stackCount: stackCount, tickWidth: tickWidth, categoryWidth: categoryWidth, categorySpacing: categorySpacing, @@ -149,46 +163,50 @@ module.exports = function(Chart) { }, calculateBarWidth: function(ruler) { - var xScale = this.getScaleForId(this.getMeta().xAxisID); + var me = this; + var meta = me.getMeta(); + var xScale = me.getScaleForId(meta.xAxisID); if (xScale.options.barThickness) { return xScale.options.barThickness; } - return xScale.options.stacked ? ruler.categoryWidth * xScale.options.barPercentage : ruler.barWidth; + return 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; + // 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) { - meta = this.chart.getDatasetMeta(j); - if (meta.bar && this.chart.isDatasetVisible(j)) { - ++barIndex; + 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 barIndex; + return stacks.length - 1; }, calculateBarX: function(index, datasetIndex, ruler) { var me = this; var meta = me.getMeta(); var xScale = me.getScaleForId(meta.xAxisID); - var barIndex = me.getBarIndex(datasetIndex); + 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 * barIndex) + + (ruler.barWidth * stackIndex) + (ruler.barSpacing / 2) + - (ruler.barSpacing * barIndex); + (ruler.barSpacing * stackIndex); }, calculateBarY: function(index, datasetIndex) { @@ -197,16 +215,17 @@ module.exports = function(Chart) { var yScale = me.getScaleForId(meta.yAxisID); var value = Number(me.getDataset().data[index]); - if (yScale.options.stacked) { - + if (yScale.options.stacked || + (yScale.options.stacked === undefined && meta.stack !== undefined)) { var base = yScale.getBaseValue(); var sumPos = base, - sumNeg = base; + sumNeg = base; 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)) { + 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; @@ -324,6 +343,27 @@ module.exports = function(Chart) { }; 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(); @@ -368,7 +408,8 @@ module.exports = function(Chart) { var base = xScale.getBaseValue(); var originalBase = base; - if (xScale.options.stacked) { + 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]); @@ -376,7 +417,8 @@ module.exports = function(Chart) { 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)) { + 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, originalBase) : Math.max(currentVal, originalBase); } @@ -392,18 +434,18 @@ module.exports = function(Chart) { var me = this; var meta = me.getMeta(); var yScale = me.getScaleForId(meta.yAxisID); - var datasetCount = me.getBarCount(); + 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 / datasetCount; + var fullBarHeight = categoryHeight / stackCount; var barHeight = fullBarHeight * yScale.options.barPercentage; var barSpacing = fullBarHeight - (fullBarHeight * yScale.options.barPercentage); return { - datasetCount: datasetCount, + stackCount: stackCount, tickHeight: tickHeight, categoryHeight: categoryHeight, categorySpacing: categorySpacing, @@ -415,11 +457,33 @@ module.exports = function(Chart) { calculateBarHeight: function(ruler) { var me = this; - var yScale = me.getScaleForId(me.getMeta().yAxisID); + var meta = me.getMeta(); + var yScale = me.getScaleForId(meta.yAxisID); if (yScale.options.barThickness) { return yScale.options.barThickness; } - return yScale.options.stacked ? ruler.categoryHeight * yScale.options.barPercentage : ruler.barHeight; + return ruler.barHeight; + }, + + // 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) { @@ -428,16 +492,17 @@ module.exports = function(Chart) { var xScale = me.getScaleForId(meta.xAxisID); var value = Number(me.getDataset().data[index]); - if (xScale.options.stacked) { - + if (xScale.options.stacked || + (xScale.options.stacked === undefined && meta.stack !== undefined)) { var base = xScale.getBaseValue(); var sumPos = base, - sumNeg = base; + sumNeg = base; 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)) { + 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; @@ -460,20 +525,16 @@ module.exports = function(Chart) { var me = this; var meta = me.getMeta(); var yScale = me.getScaleForId(meta.yAxisID); - var barIndex = me.getBarIndex(datasetIndex); + 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 * barIndex) + + (ruler.barHeight * stackIndex) + (ruler.barSpacing / 2) + - (ruler.barSpacing * barIndex); + (ruler.barSpacing * stackIndex); } }); }; diff --git a/src/scales/scale.linear.js b/src/scales/scale.linear.js index 301af6bc986..319243b23e5 100644 --- a/src/scales/scale.linear.js +++ b/src/scales/scale.linear.js @@ -28,21 +28,43 @@ module.exports = function(Chart) { me.min = null; me.max = null; - if (opts.stacked) { - var valuesPerType = {}; + var hasStacks = opts.stacked; + if (hasStacks === undefined) { + helpers.each(datasets, function(dataset, datasetIndex) { + if (hasStacks) { + return; + } + + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && + meta.stack !== undefined) { + hasStacks = true; + } + }); + } + + if (opts.stacked || hasStacks) { + var valuesPerStack = {}; helpers.each(datasets, function(dataset, datasetIndex) { var meta = chart.getDatasetMeta(datasetIndex); - if (valuesPerType[meta.type] === undefined) { - valuesPerType[meta.type] = { + var key = [ + meta.type, + // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined + ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), + meta.stack + ].join('.'); + + if (valuesPerStack[key] === undefined) { + valuesPerStack[key] = { positiveValues: [], negativeValues: [] }; } // Store these per type - var positiveValues = valuesPerType[meta.type].positiveValues; - var negativeValues = valuesPerType[meta.type].negativeValues; + var positiveValues = valuesPerStack[key].positiveValues; + var negativeValues = valuesPerStack[key].negativeValues; if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { helpers.each(dataset.data, function(rawValue, index) { @@ -65,7 +87,7 @@ module.exports = function(Chart) { } }); - helpers.each(valuesPerType, function(valuesForType) { + helpers.each(valuesPerStack, function(valuesForType) { var values = valuesForType.positiveValues.concat(valuesForType.negativeValues); var minVal = helpers.min(values); var maxVal = helpers.max(values); diff --git a/src/scales/scale.logarithmic.js b/src/scales/scale.logarithmic.js index 160d35f23fd..5d1d329f9ab 100644 --- a/src/scales/scale.logarithmic.js +++ b/src/scales/scale.logarithmic.js @@ -32,18 +32,40 @@ module.exports = function(Chart) { me.max = null; me.minNotZero = null; - if (opts.stacked) { - var valuesPerType = {}; + var hasStacks = opts.stacked; + if (hasStacks === undefined) { + helpers.each(datasets, function(dataset, datasetIndex) { + if (hasStacks) { + return; + } + + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && + meta.stack !== undefined) { + hasStacks = true; + } + }); + } + + if (opts.stacked || hasStacks) { + var valuesPerStack = {}; helpers.each(datasets, function(dataset, datasetIndex) { var meta = chart.getDatasetMeta(datasetIndex); + var key = [ + meta.type, + // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined + ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), + meta.stack + ].join('.'); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { - if (valuesPerType[meta.type] === undefined) { - valuesPerType[meta.type] = []; + if (valuesPerStack[key] === undefined) { + valuesPerStack[key] = []; } helpers.each(dataset.data, function(rawValue, index) { - var values = valuesPerType[meta.type]; + var values = valuesPerStack[key]; var value = +me.getRightValue(rawValue); if (isNaN(value) || meta.data[index].hidden) { return; @@ -61,7 +83,7 @@ module.exports = function(Chart) { } }); - helpers.each(valuesPerType, function(valuesForType) { + helpers.each(valuesPerStack, function(valuesForType) { var minVal = helpers.min(valuesForType); var maxVal = helpers.max(valuesForType); me.min = me.min === null ? minVal : Math.min(me.min, minVal); diff --git a/test/controller.bar.tests.js b/test/controller.bar.tests.js index fea88633ce9..38f1b733a1a 100644 --- a/test/controller.bar.tests.js +++ b/test/controller.bar.tests.js @@ -52,41 +52,612 @@ describe('Bar controller tests', function() { expect(meta.yAxisID).toBe('firstYScaleID'); }); - it('should correctly count the number of bar datasets', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: [], type: 'line'}, - {data: [], hidden: true}, - {data: []}, - {data: []} - ], - labels: [] - } + it('should correctly count the number of stacks ignoring datasets of other types and hidden datasets', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: [], type: 'line'}, + {data: [], hidden: true}, + {data: []}, + {data: []} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller.getStackCount()).toBe(2); }); + }); - var meta = chart.getDatasetMeta(1); - expect(meta.controller.getBarCount()).toBe(2); + it('should correctly count the number of stacks when a group is not specified', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: []}, + {data: []}, + {data: []}, + {data: []} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller.getStackCount()).toBe(4); + }); }); - it('should correctly get the bar index accounting for hidden datasets', function() { - var chart = window.acquireChart({ - type: 'bar', - data: { - datasets: [ - {data: []}, - {data: [], hidden: true}, - {data: [], type: 'line'}, - {data: []} - ], - labels: [] - } + it('should correctly count the number of stacks when a group is not specified and the scale is stacked', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: []}, + {data: []}, + {data: []}, + {data: []} + ], + labels: [] + }, + options: { + scales: { + xAxes: [{ + stacked: true + }], + yAxes: [{ + stacked: true + }] + } + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller.getStackCount()).toBe(1); }); + }); - var meta = chart.getDatasetMeta(1); - expect(meta.controller.getBarIndex(0)).toBe(0); - expect(meta.controller.getBarIndex(3)).toBe(1); + it('should correctly count the number of stacks when a group is not specified and the scale is not stacked', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: []}, + {data: []}, + {data: []}, + {data: []} + ], + labels: [] + }, + options: { + scales: { + xAxes: [{ + stacked: false + }], + yAxes: [{ + stacked: false + }] + } + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller.getStackCount()).toBe(4); + }); + }); + + it('should correctly count the number of stacks when a group is specified for some', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: []}, + {data: []} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(3); + expect(meta.controller.getStackCount()).toBe(3); + }); + }); + + it('should correctly count the number of stacks when a group is specified for some and the scale is stacked', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: []}, + {data: []} + ], + labels: [] + }, + options: { + scales: { + xAxes: [{ + stacked: true + }], + yAxes: [{ + stacked: true + }] + } + } + }); + + var meta = chart.getDatasetMeta(3); + expect(meta.controller.getStackCount()).toBe(2); + }); + }); + + it('should correctly count the number of stacks when a group is specified for some and the scale is not stacked', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: []}, + {data: []} + ], + labels: [] + }, + options: { + scales: { + xAxes: [{ + stacked: false + }], + yAxes: [{ + stacked: false + }] + } + } + }); + + var meta = chart.getDatasetMeta(3); + expect(meta.controller.getStackCount()).toBe(4); + }); + }); + + it('should correctly count the number of stacks when a group is specified for all', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack2'}, + {data: [], stack: 'stack2'} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(3); + expect(meta.controller.getStackCount()).toBe(2); + }); + }); + + it('should correctly count the number of stacks when a group is specified for all and the scale is stacked', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack2'}, + {data: [], stack: 'stack2'} + ], + labels: [] + }, + options: { + scales: { + xAxes: [{ + stacked: true + }], + yAxes: [{ + stacked: true + }] + } + } + }); + + var meta = chart.getDatasetMeta(3); + expect(meta.controller.getStackCount()).toBe(2); + }); + }); + + it('should correctly count the number of stacks when a group is specified for all and the scale is not stacked', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack2'}, + {data: [], stack: 'stack2'} + ], + labels: [] + }, + options: { + scales: { + xAxes: [{ + stacked: false + }], + yAxes: [{ + stacked: false + }] + } + } + }); + + var meta = chart.getDatasetMeta(3); + expect(meta.controller.getStackCount()).toBe(4); + }); + }); + + it('should correctly get the stack index accounting for datasets of other types and hidden datasets', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: []}, + {data: [], hidden: true}, + {data: [], type: 'line'}, + {data: []} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller.getStackIndex(0)).toBe(0); + expect(meta.controller.getStackIndex(3)).toBe(1); + }); + }); + + it('should correctly get the stack index when a group is not specified', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: []}, + {data: []}, + {data: []}, + {data: []} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller.getStackIndex(0)).toBe(0); + expect(meta.controller.getStackIndex(1)).toBe(1); + expect(meta.controller.getStackIndex(2)).toBe(2); + expect(meta.controller.getStackIndex(3)).toBe(3); + }); + }); + + it('should correctly get the stack index when a group is not specified and the scale is stacked', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: []}, + {data: []}, + {data: []}, + {data: []} + ], + labels: [] + }, + options: { + scales: { + xAxes: [{ + stacked: true + }], + yAxes: [{ + stacked: true + }] + } + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller.getStackIndex(0)).toBe(0); + expect(meta.controller.getStackIndex(1)).toBe(0); + expect(meta.controller.getStackIndex(2)).toBe(0); + expect(meta.controller.getStackIndex(3)).toBe(0); + }); + }); + + it('should correctly get the stack index when a group is not specified and the scale is not stacked', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: []}, + {data: []}, + {data: []}, + {data: []} + ], + labels: [] + }, + options: { + scales: { + xAxes: [{ + stacked: false + }], + yAxes: [{ + stacked: false + }] + } + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller.getStackIndex(0)).toBe(0); + expect(meta.controller.getStackIndex(1)).toBe(1); + expect(meta.controller.getStackIndex(2)).toBe(2); + expect(meta.controller.getStackIndex(3)).toBe(3); + }); + }); + + it('should correctly get the stack index when a group is specified for some', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: []}, + {data: []} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller.getStackIndex(0)).toBe(0); + expect(meta.controller.getStackIndex(1)).toBe(0); + expect(meta.controller.getStackIndex(2)).toBe(1); + expect(meta.controller.getStackIndex(3)).toBe(2); + }); + }); + + it('should correctly get the stack index when a group is specified for some and the scale is stacked', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: []}, + {data: []} + ], + labels: [] + }, + options: { + scales: { + xAxes: [{ + stacked: true + }], + yAxes: [{ + stacked: true + }] + } + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller.getStackIndex(0)).toBe(0); + expect(meta.controller.getStackIndex(1)).toBe(0); + expect(meta.controller.getStackIndex(2)).toBe(1); + expect(meta.controller.getStackIndex(3)).toBe(1); + }); + }); + + it('should correctly get the stack index when a group is specified for some and the scale is not stacked', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: []}, + {data: []} + ], + labels: [] + }, + options: { + scales: { + xAxes: [{ + stacked: false + }], + yAxes: [{ + stacked: false + }] + } + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller.getStackIndex(0)).toBe(0); + expect(meta.controller.getStackIndex(1)).toBe(1); + expect(meta.controller.getStackIndex(2)).toBe(2); + expect(meta.controller.getStackIndex(3)).toBe(3); + }); + }); + + it('should correctly get the stack index when a group is specified for all', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack2'}, + {data: [], stack: 'stack2'} + ], + labels: [] + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller.getStackIndex(0)).toBe(0); + expect(meta.controller.getStackIndex(1)).toBe(0); + expect(meta.controller.getStackIndex(2)).toBe(1); + expect(meta.controller.getStackIndex(3)).toBe(1); + }); + }); + + it('should correctly get the stack index when a group is specified for all and the scale is stacked', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack2'}, + {data: [], stack: 'stack2'} + ], + labels: [] + }, + options: { + scales: { + xAxes: [{ + stacked: true + }], + yAxes: [{ + stacked: true + }] + } + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller.getStackIndex(0)).toBe(0); + expect(meta.controller.getStackIndex(1)).toBe(0); + expect(meta.controller.getStackIndex(2)).toBe(1); + expect(meta.controller.getStackIndex(3)).toBe(1); + }); + }); + + it('should correctly get the stack index when a group is specified for all and the scale is not stacked', function() { + [ + 'bar', + 'horizontalBar' + ].forEach(function(barType) { + var chart = window.acquireChart({ + type: barType, + data: { + datasets: [ + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack1'}, + {data: [], stack: 'stack2'}, + {data: [], stack: 'stack2'} + ], + labels: [] + }, + options: { + scales: { + xAxes: [{ + stacked: false + }], + yAxes: [{ + stacked: false + }] + } + } + }); + + var meta = chart.getDatasetMeta(1); + expect(meta.controller.getStackIndex(0)).toBe(0); + expect(meta.controller.getStackIndex(1)).toBe(1); + expect(meta.controller.getStackIndex(2)).toBe(2); + expect(meta.controller.getStackIndex(3)).toBe(3); + }); }); it('should create rectangle elements for each data item during initialization', function() { @@ -239,9 +810,7 @@ describe('Bar controller tests', function() { options: { scales: { xAxes: [{ - type: 'category', - stacked: true, - barPercentage: 1 + type: 'category' }], yAxes: [{ type: 'linear', @@ -254,10 +823,10 @@ describe('Bar controller tests', function() { var meta0 = chart.getDatasetMeta(0); [ - {b: 290, w: 93, x: 86, y: 161}, - {b: 290, w: 93, x: 202, y: 419}, - {b: 290, w: 93, x: 318, y: 161}, - {b: 290, w: 93, x: 436, y: 419} + {b: 290, w: 83, x: 86, y: 161}, + {b: 290, w: 83, x: 202, y: 419}, + {b: 290, w: 83, x: 318, y: 161}, + {b: 290, w: 83, x: 434, y: 419} ].forEach(function(values, i) { expect(meta0.data[i]._model.base).toBeCloseToPixel(values.b); expect(meta0.data[i]._model.width).toBeCloseToPixel(values.w); @@ -268,10 +837,10 @@ describe('Bar controller tests', function() { var meta1 = chart.getDatasetMeta(1); [ - {b: 161, w: 93, x: 86, y: 32}, - {b: 290, w: 93, x: 202, y: 97}, - {b: 161, w: 93, x: 318, y: 161}, - {b: 419, w: 93, x: 436, y: 471} + {b: 161, w: 83, x: 86, y: 32}, + {b: 290, w: 83, x: 202, y: 97}, + {b: 161, w: 83, x: 318, y: 161}, + {b: 419, w: 83, x: 434, y: 471} ].forEach(function(values, i) { expect(meta1.data[i]._model.base).toBeCloseToPixel(values.b); expect(meta1.data[i]._model.width).toBeCloseToPixel(values.w); @@ -296,9 +865,7 @@ describe('Bar controller tests', function() { options: { scales: { xAxes: [{ - type: 'category', - stacked: true, - barPercentage: 1 + type: 'category' }], yAxes: [{ type: 'linear', @@ -311,10 +878,10 @@ describe('Bar controller tests', function() { var meta0 = chart.getDatasetMeta(0); [ - {b: 290, w: 93, x: 86, y: 161}, - {b: 290, w: 93, x: 202, y: 419}, - {b: 290, w: 93, x: 318, y: 161}, - {b: 290, w: 93, x: 436, y: 419} + {b: 290, w: 83, x: 86, y: 161}, + {b: 290, w: 83, x: 202, y: 419}, + {b: 290, w: 83, x: 318, y: 161}, + {b: 290, w: 83, x: 434, y: 419} ].forEach(function(values, i) { expect(meta0.data[i]._model.base).toBeCloseToPixel(values.b); expect(meta0.data[i]._model.width).toBeCloseToPixel(values.w); @@ -325,10 +892,10 @@ describe('Bar controller tests', function() { var meta1 = chart.getDatasetMeta(1); [ - {b: 161, w: 93, x: 86, y: 32}, - {b: 290, w: 93, x: 202, y: 97}, - {b: 161, w: 93, x: 318, y: 161}, - {b: 419, w: 93, x: 436, y: 471} + {b: 161, w: 83, x: 86, y: 32}, + {b: 290, w: 83, x: 202, y: 97}, + {b: 161, w: 83, x: 318, y: 161}, + {b: 419, w: 83, x: 434, y: 471} ].forEach(function(values, i) { expect(meta1.data[i]._model.base).toBeCloseToPixel(values.b); expect(meta1.data[i]._model.width).toBeCloseToPixel(values.w); @@ -337,6 +904,144 @@ describe('Bar controller tests', function() { }); }); + it('should get the correct bar points for grouped stacked chart if the group name is same', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [10, -10, 10, -10], + label: 'dataset1', + stack: 'stack1' + }, { + data: [10, 15, 0, -4], + label: 'dataset2', + stack: 'stack1' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + scales: { + xAxes: [{ + type: 'category' + }], + yAxes: [{ + type: 'linear', + stacked: true + }] + } + } + }); + + var meta0 = chart.getDatasetMeta(0); + + [ + {b: 290, w: 83, x: 86, y: 161}, + {b: 290, w: 83, x: 202, y: 419}, + {b: 290, w: 83, x: 318, y: 161}, + {b: 290, w: 83, x: 434, y: 419} + ].forEach(function(values, i) { + expect(meta0.data[i]._model.base).toBeCloseToPixel(values.b); + expect(meta0.data[i]._model.width).toBeCloseToPixel(values.w); + expect(meta0.data[i]._model.x).toBeCloseToPixel(values.x); + expect(meta0.data[i]._model.y).toBeCloseToPixel(values.y); + }); + + var meta = chart.getDatasetMeta(1); + + [ + {b: 161, w: 83, x: 86, y: 32}, + {b: 290, w: 83, x: 202, y: 97}, + {b: 161, w: 83, x: 318, y: 161}, + {b: 419, w: 83, x: 434, y: 471} + ].forEach(function(values, i) { + expect(meta.data[i]._model.base).toBeCloseToPixel(values.b); + expect(meta.data[i]._model.width).toBeCloseToPixel(values.w); + expect(meta.data[i]._model.x).toBeCloseToPixel(values.x); + expect(meta.data[i]._model.y).toBeCloseToPixel(values.y); + }); + }); + + it('should get the correct bar points for grouped stacked chart if the group name is different', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [1, 2], + stack: 'stack1' + }, { + data: [1, 2], + stack: 'stack2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + scales: { + xAxes: [{ + type: 'category' + }], + yAxes: [{ + stacked: true, + type: 'linear' + }] + } + } + }); + + var meta = chart.getDatasetMeta(1); + + [ + {x: 108, y: 258}, + {x: 224, y: 32} + ].forEach(function(values, i) { + expect(meta.data[i]._model.base).toBeCloseToPixel(484); + expect(meta.data[i]._model.width).toBeCloseToPixel(40); + expect(meta.data[i]._model.x).toBeCloseToPixel(values.x); + expect(meta.data[i]._model.y).toBeCloseToPixel(values.y); + }); + }); + + it('should get the correct bar points for grouped stacked chart', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [1, 2], + stack: 'stack1' + }, { + data: [0.5, 1], + stack: 'stack2' + }, { + data: [0.5, 1], + stack: 'stack2' + }], + labels: ['label1', 'label2', 'label3', 'label4'] + }, + options: { + scales: { + xAxes: [{ + type: 'category' + }], + yAxes: [{ + stacked: true, + type: 'linear' + }] + } + } + }); + + var meta = chart.getDatasetMeta(2); + + [ + {b: 371, x: 108, y: 258}, + {b: 258, x: 224, y: 32} + ].forEach(function(values, i) { + expect(meta.data[i]._model.base).toBeCloseToPixel(values.b); + expect(meta.data[i]._model.width).toBeCloseToPixel(40); + expect(meta.data[i]._model.x).toBeCloseToPixel(values.x); + expect(meta.data[i]._model.y).toBeCloseToPixel(values.y); + }); + }); + it('should update elements when the scales are stacked and the y axis is logarithmic', function() { var chart = window.acquireChart({ type: 'bar', @@ -564,10 +1269,11 @@ describe('Bar controller tests', function() { var chart = window.acquireChart(this.config); var meta = chart.getDatasetMeta(0); var xScale = chart.scales[meta.xAxisID]; + var yScale = chart.scales[meta.yAxisID]; var categoryPercentage = xScale.options.categoryPercentage; var barPercentage = xScale.options.barPercentage; - var stacked = xScale.options.stacked; + var stacked = yScale.options.stacked; var totalBarWidth = 0; for (var i = 0; i < chart.data.datasets.length; i++) { @@ -613,8 +1319,10 @@ describe('Bar controller tests', function() { ticks: { min: 'March', max: 'May', - }, - stacked: true, + } + }], + yAxes: [{ + stacked: true }] } } @@ -638,11 +1346,12 @@ describe('Bar controller tests', function() { afterEach(function() { var chart = window.acquireChart(this.config); var meta = chart.getDatasetMeta(0); + var xScale = chart.scales[meta.xAxisID]; var yScale = chart.scales[meta.yAxisID]; var categoryPercentage = yScale.options.categoryPercentage; var barPercentage = yScale.options.barPercentage; - var stacked = yScale.options.stacked; + var stacked = xScale.options.stacked; var totalBarHeight = 0; for (var i = 0; i < chart.data.datasets.length; i++) { @@ -684,12 +1393,14 @@ describe('Bar controller tests', function() { data: this.data, options: { scales: { + xAxes: [{ + stacked: true + }], yAxes: [{ ticks: { min: 'March', max: 'May', - }, - stacked: true, + } }] } }