diff --git a/docs/charts/bar.md b/docs/charts/bar.md index 02f080614a1..96be92f14cc 100644 --- a/docs/charts/bar.md +++ b/docs/charts/bar.md @@ -77,6 +77,7 @@ the color of the bars is generally set this way. | [`hoverBorderColor`](#interactions) | [`Color`](../general/colors.md) | - | Yes | `undefined` | [`hoverBorderWidth`](#interactions) | `number` | - | Yes | `1` | [`label`](#general) | `string` | - | - | `''` +| [`order`](#general) | `number` | - | - | 0 | [`xAxisID`](#general) | `string` | - | - | first x axis | [`yAxisID`](#general) | `string` | - | - | first y axis @@ -85,6 +86,7 @@ the color of the bars is generally set this way. | Name | Description | ---- | ---- | `label` | The label for the dataset which appears in the legend and tooltips. +| `order` | The drawing order of dataset. Also affects stacking order. | `xAxisID` | The ID of the x axis to plot this dataset on. | `yAxisID` | The ID of the y axis to plot this dataset on. diff --git a/docs/charts/bubble.md b/docs/charts/bubble.md index b94c98c17d5..c65f14c9b81 100644 --- a/docs/charts/bubble.md +++ b/docs/charts/bubble.md @@ -49,14 +49,18 @@ The bubble chart allows a number of properties to be specified for each dataset. | [`hoverBorderWidth`](#interactions) | `number` | Yes | Yes | `1` | [`hoverRadius`](#interactions) | `number` | Yes | Yes | `4` | [`hitRadius`](#interactions) | `number` | Yes | Yes | `1` -| [`label`](#labeling) | `string` | - | - | `undefined` +| [`label`](#general) | `string` | - | - | `undefined` +| [`order`](#general) | `number` | - | - | 0 | [`pointStyle`](#styling) | `string` | Yes | Yes | `'circle'` | [`rotation`](#styling) | `number` | Yes | Yes | `0` | [`radius`](#styling) | `number` | Yes | Yes | `3` -### Labeling +### General -`label` defines the text associated to the dataset and which appears in the legend and tooltips. +| Name | Description +| ---- | ---- +| `label` | The label for the dataset which appears in the legend and tooltips. +| `order` | The drawing order of dataset. ### Styling diff --git a/docs/charts/line.md b/docs/charts/line.md index bd2a24c98d2..9432632d4a4 100644 --- a/docs/charts/line.md +++ b/docs/charts/line.md @@ -54,6 +54,7 @@ The line chart allows a number of properties to be specified for each dataset. T | [`fill`](#line-styling) | boolean|string | Yes | - | `true` | [`label`](#general) | `string` | - | - | `''` | [`lineTension`](#line-styling) | `number` | - | - | `0.4` +| [`order`](#general) | `number` | - | - | 0 | [`pointBackgroundColor`](#point-styling) | `Color` | Yes | Yes | `'rgba(0, 0, 0, 0.1)'` | [`pointBorderColor`](#point-styling) | `Color` | Yes | Yes | `'rgba(0, 0, 0, 0.1)'` | [`pointBorderWidth`](#point-styling) | `number` | Yes | Yes | `1` @@ -76,6 +77,7 @@ The line chart allows a number of properties to be specified for each dataset. T | Name | Description | ---- | ---- | `label` | The label for the dataset which appears in the legend and tooltips. +| `order` | The drawing order of dataset. Also affects stacking order. | `xAxisID` | The ID of the x axis to plot this dataset on. | `yAxisID` | The ID of the y axis to plot this dataset on. diff --git a/docs/charts/mixed.md b/docs/charts/mixed.md index 35742ee9927..3993fa53abd 100644 --- a/docs/charts/mixed.md +++ b/docs/charts/mixed.md @@ -71,9 +71,9 @@ At this point we have a chart rendering how we'd like. It's important to note th } {% endchartjs %} - ## Drawing order +## Drawing order - By default, datasets are drawn so that first one is topmost. This can be altered by specifying `order` option to datasets. `order` defaults to `0`. + By default, datasets are drawn so that first one is top-most. This can be altered by specifying `order` option to datasets. `order` defaults to `0`. ```javascript var mixedChart = new Chart(ctx, { diff --git a/docs/charts/polar.md b/docs/charts/polar.md index 8403a387230..84275dd9357 100644 --- a/docs/charts/polar.md +++ b/docs/charts/polar.md @@ -70,6 +70,7 @@ All these values, if `undefined`, fallback to the associated [`elements.arc.*`]( ### Border Alignment The following values are supported for `borderAlign`. + * `'center'` (default) * `'inner'` diff --git a/docs/charts/radar.md b/docs/charts/radar.md index 1b1c7ef0714..c97ebf19903 100644 --- a/docs/charts/radar.md +++ b/docs/charts/radar.md @@ -52,6 +52,7 @@ They are often useful for comparing the points of two or more different data set {% endchartjs %} ## Example Usage + ```javascript var myRadarChart = new Chart(ctx, { type: 'radar', @@ -75,6 +76,7 @@ The radar chart allows a number of properties to be specified for each dataset. | [`borderWidth`](#line-styling) | `number` | - | - | `3` | [`fill`](#line-styling) | boolean|string | - | - | `true` | [`label`](#general) | `string` | - | - | `''` +| [`order`](#general) | `number` | - | - | 0 | [`lineTension`](#line-styling) | `number` | - | - | `0.4` | [`pointBackgroundColor`](#point-styling) | `Color` | Yes | Yes | `'rgba(0, 0, 0, 0.1)'` | [`pointBorderColor`](#point-styling) | `Color` | Yes | Yes | `'rgba(0, 0, 0, 0.1)'` @@ -93,6 +95,7 @@ The radar chart allows a number of properties to be specified for each dataset. | Name | Description | ---- | ---- | `label` | The label for the dataset which appears in the legend and tooltips. +| `order` | The drawing order of dataset. ### Point Styling diff --git a/docs/charts/scatter.md b/docs/charts/scatter.md index 0107fd4c238..8fc7726d3d8 100644 --- a/docs/charts/scatter.md +++ b/docs/charts/scatter.md @@ -32,6 +32,7 @@ var scatterChart = new Chart(ctx, { ``` ## Dataset Properties + The scatter chart supports all of the same properties as the [line chart](./line.md#dataset-properties). ## Data Structure diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index 0736cf6e1e0..d930b4855a2 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -114,6 +114,14 @@ function computeFlexCategoryTraits(index, ruler, options) { }; } +function getMatchingVisibleMetas(scale) { + var isHorizontal = scale.isHorizontal(); + return scale.chart._getSortedVisibleDatasetMetas() + .filter(function(meta) { + return meta.bar && isHorizontal ? meta.xAxisID === scale.id : meta.yAxisID === scale.id; + }); +} + module.exports = DatasetController.extend({ dataElementType: elements.Rectangle, @@ -206,19 +214,23 @@ module.exports = DatasetController.extend({ var me = this; var chart = me.chart; var scale = me._getIndexScale(); + var metasets = chart._getSortedVisibleDatasetMetas(); var stacked = scale.options.stacked; - var ilen = last === undefined ? chart.data.datasets.length : last + 1; + var ilen = metasets.length; var stacks = []; var i, meta; for (i = 0; i < ilen; ++i) { - meta = chart.getDatasetMeta(i); - if (meta.bar && chart.isDatasetVisible(i) && + meta = metasets[i]; + if (meta.bar && (stacked === false || (stacked === true && stacks.indexOf(meta.stack) === -1) || (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) { stacks.push(meta.stack); } + if (meta.index === last) { + break; + } } return stacks; @@ -293,23 +305,25 @@ module.exports = DatasetController.extend({ var scale = me._getValueScale(); var isHorizontal = scale.isHorizontal(); var datasets = chart.data.datasets; + var metasets = getMatchingVisibleMetas(scale); var value = +scale.getRightValue(datasets[datasetIndex].data[index]); var minBarLength = scale.options.minBarLength; var stacked = scale.options.stacked; var stack = meta.stack; var start = 0; + var ilen = metasets.length; var i, imeta, ivalue, base, head, size; if (stacked || (stacked === undefined && stack !== undefined)) { - for (i = 0; i < datasetIndex; ++i) { - imeta = chart.getDatasetMeta(i); + for (i = 0; i < ilen; ++i) { + imeta = metasets[i]; - if (imeta.bar && - imeta.stack === stack && - imeta.controller._getValueScaleId() === scale.id && - chart.isDatasetVisible(i)) { + if (imeta.index === datasetIndex) { + break; + } - ivalue = +scale.getRightValue(datasets[i].data[index]); + if (imeta.stack === stack) { + ivalue = +scale.getRightValue(datasets[imeta.index].data[index]); if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) { start += ivalue; } diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index b7f06816d2b..bf525ea9287 100644 --- a/src/controllers/controller.line.js +++ b/src/controllers/controller.line.js @@ -183,13 +183,19 @@ module.exports = DatasetController.extend({ var yScale = me._yScale; var sumPos = 0; var sumNeg = 0; + var metasets = chart._getSortedVisibleDatasetMetas(); + var ilen = metasets.length; var i, ds, dsMeta; if (yScale.options.stacked) { - for (i = 0; i < datasetIndex; i++) { - ds = chart.data.datasets[i]; - dsMeta = chart.getDatasetMeta(i); - if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) { + for (i = 0; i < ilen; ++i) { + dsMeta = metasets[i]; + if (dsMeta.index === datasetIndex) { + break; + } + + ds = chart.data.datasets[dsMeta.index]; + if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id) { var stackedRightValue = Number(yScale.getRightValue(ds.data[index])); if (stackedRightValue < 0) { sumNeg += stackedRightValue || 0; diff --git a/src/core/core.controller.js b/src/core/core.controller.js index d477d828246..4063b621d96 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -714,7 +714,7 @@ helpers.extend(Chart.prototype, /** @lends Chart */ { result.sort(function(a, b) { return a.order === b.order - ? b.index - a.index + ? a.index - b.index : a.order - b.order; }); @@ -728,14 +728,14 @@ helpers.extend(Chart.prototype, /** @lends Chart */ { */ drawDatasets: function(easingValue) { var me = this; - var metasets, i, ilen; + var metasets, i; if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) { return; } metasets = me._getSortedVisibleDatasetMetas(); - for (i = 0, ilen = metasets.length; i < ilen; ++i) { + for (i = metasets.length - 1; i >= 0; --i) { me.drawDataset(metasets[i], easingValue); } diff --git a/src/plugins/plugin.filler.js b/src/plugins/plugin.filler.js index 142505880b3..5acaa7beb13 100644 --- a/src/plugins/plugin.filler.js +++ b/src/plugins/plugin.filler.js @@ -299,9 +299,9 @@ module.exports = { beforeDatasetsDraw: function(chart) { var metasets = chart._getSortedVisibleDatasetMetas(); var ctx = chart.ctx; - var meta, i, ilen, el, view, points, mapper, color; + var meta, i, el, view, points, mapper, color; - for (i = 0, ilen = metasets.length; i < ilen; ++i) { + for (i = metasets.length - 1; i >= 0; --i) { meta = metasets[i].$filler; if (!meta || !meta.visible) { diff --git a/src/scales/scale.linear.js b/src/scales/scale.linear.js index a2199b66e78..c7f979bba5f 100644 --- a/src/scales/scale.linear.js +++ b/src/scales/scale.linear.js @@ -11,123 +11,129 @@ var defaultConfig = { } }; +var DEFAULT_MIN = 0; +var DEFAULT_MAX = 1; + +function getMatchingVisibleMetas(scale) { + var isHorizontal = scale.isHorizontal(); + + return scale.chart._getSortedVisibleDatasetMetas() + .filter(function(meta) { + return isHorizontal ? meta.xAxisID === scale.id : meta.yAxisID === scale.id; + }); +} + +function getStack(stacks, stacked, meta) { + var key = [ + meta.type, + // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined + ((stacked === undefined && meta.stack === undefined) ? meta.index : ''), + meta.stack + ].join('.'); + + if (stacks[key] === undefined) { + stacks[key] = { + pos: [], + neg: [] + }; + } + + return stacks[key]; +} + +function stackData(scale, stacks, meta, data) { + var opts = scale.options; + var stacked = opts.stacked; + var stack = getStack(stacks, stacked, meta); + var pos = stack.pos; + var neg = stack.neg; + var ilen = data.length; + var i, value; + + for (i = ilen - 1; i >= 0; --i) { + value = +scale.getRightValue(data[i]); + if (isNaN(value) || meta.data[i].hidden) { + continue; + } + + pos[i] = pos[i] || 0; + neg[i] = neg[i] || 0; + + if (opts.relativePoints) { + pos[i] = 100; + } else if (value < 0) { + neg[i] += value; + } else { + pos[i] += value; + } + } +} + +function updateMinMax(scale, meta, data) { + var ilen = data.length; + var i, value; + + for (i = 0; i < ilen; ++i) { + value = +scale.getRightValue(data[i]); + if (isNaN(value) || meta.data[i].hidden) { + continue; + } + + if (scale.min === null || value < scale.min) { + scale.min = value; + } + + if (scale.max === null || value > scale.max) { + scale.max = value; + } + } +} + module.exports = LinearScaleBase.extend({ determineDataLimits: function() { var me = this; var opts = me.options; var chart = me.chart; - var data = chart.data; - var datasets = data.datasets; - var isHorizontal = me.isHorizontal(); - var DEFAULT_MIN = 0; - var DEFAULT_MAX = 1; - - function IDMatches(meta) { - return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; - } + var datasets = chart.data.datasets; + var metasets = getMatchingVisibleMetas(me); + var hasStacks = opts.stacked; + var stacks = {}; + var ilen = metasets.length; + var i, meta, data; - // First Calculate the range me.min = null; me.max = null; - 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; - } - }); + for (i = 0; !hasStacks && i < ilen; ++i) { + meta = metasets[i]; + hasStacks = meta.stack !== undefined; + } } - 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 (valuesPerStack[key] === undefined) { - valuesPerStack[key] = { - positiveValues: [], - negativeValues: [] - }; - } - - // Store these per type - var positiveValues = valuesPerStack[key].positiveValues; - var negativeValues = valuesPerStack[key].negativeValues; - - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { - helpers.each(dataset.data, function(rawValue, index) { - var value = +me.getRightValue(rawValue); - if (isNaN(value) || meta.data[index].hidden) { - return; - } - - positiveValues[index] = positiveValues[index] || 0; - negativeValues[index] = negativeValues[index] || 0; - - if (opts.relativePoints) { - positiveValues[index] = 100; - } else if (value < 0) { - negativeValues[index] += value; - } else { - positiveValues[index] += value; - } - }); - } - }); - - helpers.each(valuesPerStack, function(valuesForType) { - var values = valuesForType.positiveValues.concat(valuesForType.negativeValues); - var minVal = helpers.min(values); - var maxVal = helpers.max(values); - me.min = me.min === null ? minVal : Math.min(me.min, minVal); - me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); - }); - - } else { - helpers.each(datasets, function(dataset, datasetIndex) { - var meta = chart.getDatasetMeta(datasetIndex); - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { - helpers.each(dataset.data, function(rawValue, index) { - var value = +me.getRightValue(rawValue); - if (isNaN(value) || meta.data[index].hidden) { - return; - } - - if (me.min === null) { - me.min = value; - } else if (value < me.min) { - me.min = value; - } - - if (me.max === null) { - me.max = value; - } else if (value > me.max) { - me.max = value; - } - }); - } - }); + for (i = 0; i < ilen; ++i) { + meta = metasets[i]; + data = datasets[meta.index].data; + if (hasStacks) { + stackData(me, stacks, meta, data); + } else { + updateMinMax(me, meta, data); + } } + helpers.each(stacks, function(stackValues) { + var values = stackValues.pos.concat(stackValues.neg); + var minVal = helpers.min(values); + var maxVal = helpers.max(values); + me.min = me.min === null ? minVal : Math.min(me.min, minVal); + me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); + }); + me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN; me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX; // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero - this.handleTickRangeOptions(); + me.handleTickRangeOptions(); }, // Returns the maximum number of ticks based on the scale dimension diff --git a/test/fixtures/controller.bar/stacking/order-default.json b/test/fixtures/controller.bar/stacking/order-default.json new file mode 100644 index 00000000000..53f25a93705 --- /dev/null +++ b/test/fixtures/controller.bar/stacking/order-default.json @@ -0,0 +1,42 @@ +{ + "config": { + "type": "bar", + "data": { + "labels": ["2016", "2018", "2020", "2024", "2030"], + "datasets": [{ + "backgroundColor": "#FF6384", + "data": [1, null, 3, 4, 5] + }, { + "backgroundColor": "#36A2EB", + "data": [5, 4, 3, null, 1] + }, { + "backgroundColor": "#FFCE56", + "data": [3, 5, 2, null, 4] + }] + }, + "options": { + "responsive": false, + "legend": false, + "title": false, + "scales": { + "xAxes": [{ + "display": false, + "stacked": true + }], + "yAxes": [{ + "display": false, + "stacked": true, + "ticks": { + "beginAtZero": true + } + }] + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 512 + } + } +} diff --git a/test/fixtures/controller.bar/stacking/order-default.png b/test/fixtures/controller.bar/stacking/order-default.png new file mode 100644 index 00000000000..59e10242bb8 Binary files /dev/null and b/test/fixtures/controller.bar/stacking/order-default.png differ diff --git a/test/fixtures/controller.bar/stacking/order-specified.json b/test/fixtures/controller.bar/stacking/order-specified.json new file mode 100644 index 00000000000..d4f497b0209 --- /dev/null +++ b/test/fixtures/controller.bar/stacking/order-specified.json @@ -0,0 +1,45 @@ +{ + "config": { + "type": "bar", + "data": { + "labels": ["2016", "2018", "2020", "2024", "2030"], + "datasets": [{ + "backgroundColor": "#FF6384", + "data": [1, null, 3, 4, 5], + "order": 20 + }, { + "backgroundColor": "#36A2EB", + "data": [5, 4, 3, null, 1], + "order": 25 + }, { + "backgroundColor": "#FFCE56", + "data": [3, 5, 2, null, 4], + "order": 10 + }] + }, + "options": { + "responsive": false, + "legend": false, + "title": false, + "scales": { + "xAxes": [{ + "display": false, + "stacked": true + }], + "yAxes": [{ + "display": false, + "stacked": true, + "ticks": { + "beginAtZero": true + } + }] + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 512 + } + } +} diff --git a/test/fixtures/controller.bar/stacking/order-specified.png b/test/fixtures/controller.bar/stacking/order-specified.png new file mode 100644 index 00000000000..b927f337ac9 Binary files /dev/null and b/test/fixtures/controller.bar/stacking/order-specified.png differ diff --git a/test/fixtures/controller.line/fill/order.js b/test/fixtures/controller.line/fill/order.js index 1501fcd2112..7d76cf43257 100644 --- a/test/fixtures/controller.line/fill/order.js +++ b/test/fixtures/controller.line/fill/order.js @@ -5,12 +5,11 @@ module.exports = { labels: [0, 1, 2, 3, 4, 5], datasets: [ { - // option in dataset data: [3, 1, 2, 0, 8, 1], - backgroundColor: '#ff0000' + backgroundColor: '#ff0000', + order: 2 }, { - // option in element (fallback) data: [0, 4, 2, 6, 4, 8], backgroundColor: '#00ff00', order: 1 diff --git a/test/fixtures/controller.line/stacking/order-default.js b/test/fixtures/controller.line/stacking/order-default.js new file mode 100644 index 00000000000..d8b292e956d --- /dev/null +++ b/test/fixtures/controller.line/stacking/order-default.js @@ -0,0 +1,45 @@ +module.exports = { + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [3, 1, 2, 0, 8, 1], + backgroundColor: '#ff0000' + }, + { + // option in element (fallback) + data: [0, 4, 2, 6, 4, 8], + backgroundColor: '#00ff00' + } + ] + }, + options: { + legend: false, + title: false, + elements: { + line: { + fill: true + }, + point: { + radius: 0 + } + }, + layout: { + padding: 32 + }, + scales: { + xAxes: [{stacked: true, display: false}], + yAxes: [{stacked: true, display: false}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.line/stacking/order-default.png b/test/fixtures/controller.line/stacking/order-default.png new file mode 100644 index 00000000000..3355da62923 Binary files /dev/null and b/test/fixtures/controller.line/stacking/order-default.png differ diff --git a/test/fixtures/controller.line/stacking/order-specified.js b/test/fixtures/controller.line/stacking/order-specified.js new file mode 100644 index 00000000000..5a95441343d --- /dev/null +++ b/test/fixtures/controller.line/stacking/order-specified.js @@ -0,0 +1,47 @@ +module.exports = { + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [3, 1, 2, 0, 8, 1], + backgroundColor: '#ff0000', + order: 2 + }, + { + // option in element (fallback) + data: [0, 4, 2, 6, 4, 8], + backgroundColor: '#00ff00', + order: 1 + } + ] + }, + options: { + legend: false, + title: false, + elements: { + line: { + fill: true + }, + point: { + radius: 0 + } + }, + layout: { + padding: 32 + }, + scales: { + xAxes: [{stacked: true, display: false}], + yAxes: [{stacked: true, display: false}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.line/stacking/order-specified.png b/test/fixtures/controller.line/stacking/order-specified.png new file mode 100644 index 00000000000..ff3edfaf112 Binary files /dev/null and b/test/fixtures/controller.line/stacking/order-specified.png differ diff --git a/test/specs/scale.linear.tests.js b/test/specs/scale.linear.tests.js index bdf8a9386f9..b0a2e2d5bb8 100644 --- a/test/specs/scale.linear.tests.js +++ b/test/specs/scale.linear.tests.js @@ -1034,7 +1034,7 @@ describe('Linear Scale', function() { chart.update(); }); - expect(chart.scales['x-axis-0'].min).toEqual(0); + expect(chart.scales['x-axis-0'].min).toEqual(-1); expect(chart.scales['x-axis-0'].max).toEqual(1); });