diff --git a/docs/charts/bar.md b/docs/charts/bar.md index 328b1df0e19..a4e54915ad4 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 order for stacking, tooltip, and legend. | `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..6536395c279 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..0ad87730182 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 order for stacking, tooltip, and legend. | `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 9f83f2ea6d8..5ead1fb343a 100644 --- a/docs/charts/mixed.md +++ b/docs/charts/mixed.md @@ -70,3 +70,29 @@ At this point we have a chart rendering how we'd like. It's important to note th } } {% endchartjs %} + +## Drawing order + + 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, { + type: 'bar', + data: { + datasets: [{ + label: 'Bar Dataset', + data: [10, 20, 30, 40], + // this dataset is drawn below + order: 1 + }, { + label: 'Line Dataset', + data: [10, 10, 10, 10], + type: 'line', + // this dataset is drawn on top + order: 2 + }], + labels: ['January', 'February', 'March', 'April'] + }, + options: options +}); +``` 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 90e05a55e83..caa69a6c20b 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` | Yes | - | `3` | [`fill`](#line-styling) | boolean|string | Yes | - | `true` | [`label`](#general) | `string` | - | - | `''` +| [`order`](#general) | `number` | - | - | `0` | [`lineTension`](#line-styling) | `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)'` @@ -94,6 +96,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 aa8b06a2cbc..ccbbe734df7 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -208,21 +208,27 @@ module.exports = DatasetController.extend({ */ _getStacks: function(last) { var me = this; - var chart = me.chart; var scale = me._getIndexScale(); + var metasets = scale._getMatchingVisibleMetas(me._type); 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) && - (stacked === false || - (stacked === true && stacks.indexOf(meta.stack) === -1) || - (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) { + meta = metasets[i]; + // stacked | meta.stack + // | found | not found | undefined + // false | x | x | x + // true | | x | + // undefined | | x | x + if (stacked === false || stacks.indexOf(meta.stack) === -1 || + (stacked === undefined && meta.stack === undefined)) { stacks.push(meta.stack); } + if (meta.index === last) { + break; + } } return stacks; @@ -297,24 +303,26 @@ module.exports = DatasetController.extend({ var scale = me._getValueScale(); var isHorizontal = scale.isHorizontal(); var datasets = chart.data.datasets; + var metasets = scale._getMatchingVisibleMetas(me._type); var value = scale._parseValue(datasets[datasetIndex].data[index]); var minBarLength = scale.options.minBarLength; var stacked = scale.options.stacked; var stack = meta.stack; var start = value.start === undefined ? 0 : value.max >= 0 && value.min >= 0 ? value.min : value.max; var length = value.start === undefined ? value.end : value.max >= 0 && value.min >= 0 ? value.max - value.min : value.min - value.max; + var ilen = metasets.length; var i, imeta, ivalue, base, head, size, stackLength; 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; + } - stackLength = scale._parseValue(datasets[i].data[index]); + if (imeta.stack === stack) { + stackLength = scale._parseValue(datasets[imeta.index].data[index]); ivalue = stackLength.start === undefined ? stackLength.end : stackLength.min >= 0 && stackLength.max >= 0 ? stackLength.max : stackLength.min; if ((value.min < 0 && ivalue < 0) || (value.max >= 0 && ivalue > 0)) { diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index c2d67549bfa..dc1fc689b24 100644 --- a/src/controllers/controller.line.js +++ b/src/controllers/controller.line.js @@ -183,14 +183,21 @@ module.exports = DatasetController.extend({ var yScale = me._yScale; var sumPos = 0; var sumNeg = 0; - var i, ds, dsMeta; + var rightValue = +yScale.getRightValue(value); + var metasets = chart._getSortedVisibleDatasetMetas(); + var ilen = metasets.length; + var i, ds, dsMeta, stackedRightValue; 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)) { - var stackedRightValue = Number(yScale.getRightValue(ds.data[index])); + 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) { + stackedRightValue = +yScale.getRightValue(ds.data[index]); if (stackedRightValue < 0) { sumNeg += stackedRightValue || 0; } else { @@ -199,14 +206,11 @@ module.exports = DatasetController.extend({ } } - var rightValue = Number(yScale.getRightValue(value)); if (rightValue < 0) { return yScale.getPixelForValue(sumNeg + rightValue); } - return yScale.getPixelForValue(sumPos + rightValue); } - - return yScale.getPixelForValue(value); + return yScale.getPixelForValue(sumPos + rightValue); }, updateBezierControlPoints: function() { diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 5349fb5f72d..d2860ff95c2 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -154,6 +154,14 @@ function positionIsHorizontal(position) { return position === 'top' || position === 'bottom'; } +function compare2Level(l1, l2) { + return function(a, b) { + return a[l1] === b[l1] + ? a[l2] - b[l2] + : a[l1] - b[l1]; + }; +} + var Chart = function(item, config) { this.construct(item, config); return this; @@ -422,6 +430,8 @@ helpers.extend(Chart.prototype, /** @lends Chart */ { meta = me.getDatasetMeta(datasetIndex); } meta.type = type; + meta.order = dataset.order || 0; + meta.index = datasetIndex; if (meta.controller) { meta.controller.updateIndex(datasetIndex); @@ -513,11 +523,7 @@ helpers.extend(Chart.prototype, /** @lends Chart */ { // Do this before render so that any plugins that need final scale updates can use it plugins.notify(me, 'afterUpdate'); - me._layers.sort(function(a, b) { - return a.z === b.z - ? a._idx - b._idx - : a.z - b.z; - }); + me._layers.sort(compare2Level('z', '_idx')); if (me._bufferedRender) { me._bufferedRequest = { @@ -712,6 +718,33 @@ helpers.extend(Chart.prototype, /** @lends Chart */ { me.tooltip.transition(easingValue); }, + /** + * @private + */ + _getSortedDatasetMetas: function(filterVisible) { + var me = this; + var datasets = me.data.datasets || []; + var result = []; + var i, ilen; + + for (i = 0, ilen = datasets.length; i < ilen; ++i) { + if (!filterVisible || me.isDatasetVisible(i)) { + result.push(me.getDatasetMeta(i)); + } + } + + result.sort(compare2Level('order', 'index')); + + return result; + }, + + /** + * @private + */ + _getSortedVisibleDatasetMetas: function() { + return this._getSortedDatasetMetas(true); + }, + /** * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw` * hook, in which case, plugins will not be called on `afterDatasetsDraw`. @@ -719,16 +752,15 @@ helpers.extend(Chart.prototype, /** @lends Chart */ { */ drawDatasets: function(easingValue) { var me = this; + var metasets, i; if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) { return; } - // Draw datasets reversed to support proper line stacking - for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) { - if (me.isDatasetVisible(i)) { - me.drawDataset(i, easingValue); - } + metasets = me._getSortedVisibleDatasetMetas(); + for (i = metasets.length - 1; i >= 0; --i) { + me.drawDataset(metasets[i], easingValue); } plugins.notify(me, 'afterDatasetsDraw', [easingValue]); @@ -739,12 +771,11 @@ helpers.extend(Chart.prototype, /** @lends Chart */ { * hook, in which case, plugins will not be called on `afterDatasetDraw`. * @private */ - drawDataset: function(index, easingValue) { + drawDataset: function(meta, easingValue) { var me = this; - var meta = me.getDatasetMeta(index); var args = { meta: meta, - index: index, + index: meta.index, easingValue: easingValue }; @@ -824,7 +855,9 @@ helpers.extend(Chart.prototype, /** @lends Chart */ { controller: null, hidden: null, // See isDatasetVisible() comment xAxisID: null, - yAxisID: null + yAxisID: null, + order: dataset.order || 0, + index: datasetIndex }; } diff --git a/src/core/core.interaction.js b/src/core/core.interaction.js index e163f1182a3..f5051fbff1c 100644 --- a/src/core/core.interaction.js +++ b/src/core/core.interaction.js @@ -25,17 +25,13 @@ function getRelativePosition(e, chart) { * @param {function} handler - the callback to execute for each visible item */ function parseVisibleItems(chart, handler) { - var datasets = chart.data.datasets; - var meta, i, j, ilen, jlen; + var metasets = chart._getSortedVisibleDatasetMetas(); + var metadata, i, j, ilen, jlen, element; - for (i = 0, ilen = datasets.length; i < ilen; ++i) { - if (!chart.isDatasetVisible(i)) { - continue; - } - - meta = chart.getDatasetMeta(i); - for (j = 0, jlen = meta.data.length; j < jlen; ++j) { - var element = meta.data[j]; + for (i = 0, ilen = metasets.length; i < ilen; ++i) { + metadata = metasets[i].data; + for (j = 0, jlen = metadata.length; j < jlen; ++j) { + element = metadata[j]; if (!element._view.skip) { handler(element); } @@ -120,15 +116,12 @@ function indexMode(chart, e, options) { return []; } - chart.data.datasets.forEach(function(dataset, datasetIndex) { - if (chart.isDatasetVisible(datasetIndex)) { - var meta = chart.getDatasetMeta(datasetIndex); - var element = meta.data[items[0]._index]; + chart._getSortedVisibleDatasetMetas().forEach(function(meta) { + var element = meta.data[items[0]._index]; - // don't count items that are skipped (null data) - if (element && !element._view.skip) { - elements.push(element); - } + // don't count items that are skipped (null data) + if (element && !element._view.skip) { + elements.push(element); } }); diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 298d409aa8e..508fff837a4 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -1183,6 +1183,19 @@ var Scale = Element.extend({ me._drawLabels.apply(me, arguments); } }]; + }, + + /** + * @private + */ + _getMatchingVisibleMetas: function(type) { + var me = this; + var isHorizontal = me.isHorizontal(); + return me.chart._getSortedVisibleDatasetMetas() + .filter(function(meta) { + return (!type || meta.type === type) + && (isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id); + }); } }); diff --git a/src/core/core.tooltip.js b/src/core/core.tooltip.js index 00e97ca3810..102ab71149a 100644 --- a/src/core/core.tooltip.js +++ b/src/core/core.tooltip.js @@ -977,6 +977,9 @@ var exports = Element.extend({ me._active = []; } else { me._active = me._chart.getElementsAtEventForMode(e, options.mode, options); + if (options.reverse) { + me._active.reverse(); + } } // Remember Last Actives diff --git a/src/plugins/plugin.filler.js b/src/plugins/plugin.filler.js index 8acbc40742c..8fed6d9057e 100644 --- a/src/plugins/plugin.filler.js +++ b/src/plugins/plugin.filler.js @@ -354,12 +354,12 @@ module.exports = { }, beforeDatasetsDraw: function(chart) { - var count = (chart.data.datasets || []).length - 1; + var metasets = chart._getSortedVisibleDatasetMetas(); var ctx = chart.ctx; var meta, i, el, view, points, mapper, color; - for (i = count; i >= 0; --i) { - meta = chart.getDatasetMeta(i).$filler; + for (i = metasets.length - 1; i >= 0; --i) { + meta = metasets[i].$filler; if (!meta || !meta.visible) { continue; diff --git a/src/plugins/plugin.legend.js b/src/plugins/plugin.legend.js index 91ff07a20bd..e38fe6b586b 100644 --- a/src/plugins/plugin.legend.js +++ b/src/plugins/plugin.legend.js @@ -48,16 +48,15 @@ defaults._set('global', { // lineJoin : // lineWidth : generateLabels: function(chart) { - var data = chart.data; + var datasets = chart.data.datasets; var options = chart.options.legend || {}; var usePointStyle = options.labels && options.labels.usePointStyle; - return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) { - var meta = chart.getDatasetMeta(i); + return chart._getSortedDatasetMetas().map(function(meta, i) { var style = meta.controller.getStyle(usePointStyle ? 0 : undefined); return { - text: dataset.label, + text: datasets[meta.index].label, fillStyle: style.backgroundColor, hidden: !chart.isDatasetVisible(i), lineCap: style.borderCapStyle, @@ -72,7 +71,7 @@ defaults._set('global', { // Below is extra data used for toggling the datasets datasetIndex: i }; - }, this) : []; + }, this); } } }, diff --git a/src/scales/scale.linear.js b/src/scales/scale.linear.js index 7db13eb46cf..6bf9c4af18c 100644 --- a/src/scales/scale.linear.js +++ b/src/scales/scale.linear.js @@ -11,116 +11,113 @@ var defaultConfig = { } }; +var DEFAULT_MIN = 0; +var DEFAULT_MAX = 1; + +function getOrCreateStack(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 = getOrCreateStack(stacks, stacked, meta); + var pos = stack.pos; + var neg = stack.neg; + var ilen = data.length; + var i, value; + + for (i = 0; i < ilen; ++i) { + value = scale._parseValue(data[i]); + if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) { + continue; + } + + pos[i] = pos[i] || 0; + neg[i] = neg[i] || 0; + + if (opts.relativePoints) { + pos[i] = 100; + } else if (value.min < 0 || value.max < 0) { + neg[i] += value.min; + } else { + pos[i] += value.max; + } + } +} + +function updateMinMax(scale, meta, data) { + var ilen = data.length; + var i, value; + + for (i = 0; i < ilen; ++i) { + value = scale._parseValue(data[i]); + if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) { + continue; + } + + scale.min = Math.min(scale.min, value.min); + scale.max = Math.max(scale.max, value.max); + } +} + module.exports = LinearScaleBase.extend({ determineDataLimits: function() { var me = this; var opts = me.options; var chart = me.chart; var datasets = chart.data.datasets; - var isHorizontal = me.isHorizontal(); - var DEFAULT_MIN = 0; - var DEFAULT_MAX = 1; - var datasetIndex, meta, value, data, i, ilen; - - function IDMatches(datasetMeta) { - return isHorizontal ? datasetMeta.xAxisID === me.id : datasetMeta.yAxisID === me.id; - } + var metasets = me._getMatchingVisibleMetas(); + var hasStacks = opts.stacked; + var stacks = {}; + var ilen = metasets.length; + var i, meta, data, values; - // First Calculate the range me.min = Number.POSITIVE_INFINITY; me.max = Number.NEGATIVE_INFINITY; - var hasStacks = opts.stacked; if (hasStacks === undefined) { - for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) { - meta = chart.getDatasetMeta(datasetIndex); - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && meta.stack !== undefined) { - hasStacks = true; - break; - } + for (i = 0; !hasStacks && i < ilen; ++i) { + meta = metasets[i]; + hasStacks = meta.stack !== undefined; } } - if (opts.stacked || hasStacks) { - var valuesPerStack = {}; - - for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) { - 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)) { - data = datasets[datasetIndex].data; - for (i = 0, ilen = data.length; i < ilen; i++) { - value = me._parseValue(data[i]); - - if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) { - continue; - } - - positiveValues[i] = positiveValues[i] || 0; - negativeValues[i] = negativeValues[i] || 0; - - if (value.min === 0 && !opts.ticks.beginAtZero) { - value.min = value.max; - } - - if (opts.relativePoints) { - positiveValues[i] = 100; - } else if (value.min < 0 || value.max < 0) { - negativeValues[i] += value.min; - } else { - positiveValues[i] += value.max; - } - } - } - } - - helpers.each(valuesPerStack, function(valuesForType) { - var values = valuesForType.positiveValues.concat(valuesForType.negativeValues); - me.min = Math.min(me.min, helpers.min(values)); - me.max = Math.max(me.max, helpers.max(values)); - }); - - } else { - for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) { - meta = chart.getDatasetMeta(datasetIndex); - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { - data = datasets[datasetIndex].data; - for (i = 0, ilen = data.length; i < ilen; i++) { - value = me._parseValue(data[i]); - - if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) { - continue; - } - - me.min = Math.min(value.min, me.min); - me.max = Math.max(value.max, me.max); - } - } + 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) { + values = stackValues.pos.concat(stackValues.neg); + me.min = Math.min(me.min, helpers.min(values)); + me.max = Math.max(me.max, helpers.max(values)); + }); + me.min = helpers.isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN; me.max = helpers.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-default.js b/test/fixtures/controller.line/fill/order-default.js new file mode 100644 index 00000000000..5fcade52611 --- /dev/null +++ b/test/fixtures/controller.line/fill/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: [{display: false}], + yAxes: [{display: false}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.line/fill/order-default.png b/test/fixtures/controller.line/fill/order-default.png new file mode 100644 index 00000000000..6c07bd20b10 Binary files /dev/null and b/test/fixtures/controller.line/fill/order-default.png differ diff --git a/test/fixtures/controller.line/fill/order.js b/test/fixtures/controller.line/fill/order.js new file mode 100644 index 00000000000..7d76cf43257 --- /dev/null +++ b/test/fixtures/controller.line/fill/order.js @@ -0,0 +1,45 @@ +module.exports = { + config: { + type: 'line', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + data: [3, 1, 2, 0, 8, 1], + backgroundColor: '#ff0000', + order: 2 + }, + { + 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: [{display: false}], + yAxes: [{display: false}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.line/fill/order.png b/test/fixtures/controller.line/fill/order.png new file mode 100644 index 00000000000..6ff1a054fe5 Binary files /dev/null and b/test/fixtures/controller.line/fill/order.png differ 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/core.tooltip.tests.js b/test/specs/core.tooltip.tests.js index 29c449a8be4..89898677ad7 100755 --- a/test/specs/core.tooltip.tests.js +++ b/test/specs/core.tooltip.tests.js @@ -581,6 +581,165 @@ describe('Core.Tooltip', function() { expect(tooltip._view.y).toBeCloseToPixel(155); }); + it('Should allow reversing items', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + tooltips: { + mode: 'label', + reverse: true + } + } + }); + + // Trigger an event over top of the + var meta0 = chart.getDatasetMeta(0); + var point0 = meta0.data[1]; + + var node = chart.canvas; + var rect = node.getBoundingClientRect(); + + var evt = new MouseEvent('mousemove', { + view: window, + bubbles: true, + cancelable: true, + clientX: rect.left + point0._model.x, + clientY: rect.top + point0._model.y + }); + + // Manually trigger rather than having an async test + node.dispatchEvent(evt); + + // Check and see if tooltip was displayed + var tooltip = chart.tooltip; + var globalDefaults = Chart.defaults.global; + + expect(tooltip._view).toEqual(jasmine.objectContaining({ + // Positioning + xAlign: 'left', + yAlign: 'center', + + // Text + title: ['Point 2'], + beforeBody: [], + body: [{ + before: [], + lines: ['Dataset 2: 40'], + after: [] + }, { + before: [], + lines: ['Dataset 1: 20'], + after: [] + }], + afterBody: [], + footer: [], + labelColors: [{ + borderColor: globalDefaults.defaultColor, + backgroundColor: globalDefaults.defaultColor + }, { + borderColor: globalDefaults.defaultColor, + backgroundColor: globalDefaults.defaultColor + }] + })); + + expect(tooltip._view.x).toBeCloseToPixel(267); + expect(tooltip._view.y).toBeCloseToPixel(155); + }); + + it('Should follow dataset order', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)', + order: 10 + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)', + order: 5 + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + tooltips: { + mode: 'label' + } + } + }); + + // Trigger an event over top of the + var meta0 = chart.getDatasetMeta(0); + var point0 = meta0.data[1]; + + var node = chart.canvas; + var rect = node.getBoundingClientRect(); + + var evt = new MouseEvent('mousemove', { + view: window, + bubbles: true, + cancelable: true, + clientX: rect.left + point0._model.x, + clientY: rect.top + point0._model.y + }); + + // Manually trigger rather than having an async test + node.dispatchEvent(evt); + + // Check and see if tooltip was displayed + var tooltip = chart.tooltip; + var globalDefaults = Chart.defaults.global; + + expect(tooltip._view).toEqual(jasmine.objectContaining({ + // Positioning + xAlign: 'left', + yAlign: 'center', + + // Text + title: ['Point 2'], + beforeBody: [], + body: [{ + before: [], + lines: ['Dataset 2: 40'], + after: [] + }, { + before: [], + lines: ['Dataset 1: 20'], + after: [] + }], + afterBody: [], + footer: [], + labelColors: [{ + borderColor: globalDefaults.defaultColor, + backgroundColor: globalDefaults.defaultColor + }, { + borderColor: globalDefaults.defaultColor, + backgroundColor: globalDefaults.defaultColor + }] + })); + + expect(tooltip._view.x).toBeCloseToPixel(267); + expect(tooltip._view.y).toBeCloseToPixel(155); + }); + it('should filter items from the tooltip using the callback', function() { var chart = window.acquireChart({ type: 'line', diff --git a/test/specs/plugin.legend.tests.js b/test/specs/plugin.legend.tests.js index 06c8b2cf4c4..1fd4821bb59 100644 --- a/test/specs/plugin.legend.tests.js +++ b/test/specs/plugin.legend.tests.js @@ -163,6 +163,81 @@ describe('Legend block tests', function() { }]); }); + it('should reverse correctly', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'dataset1', + backgroundColor: '#f31', + borderCapStyle: 'round', + borderDash: [2, 2], + borderDashOffset: 5.5, + data: [] + }, { + label: 'dataset2', + hidden: true, + borderJoinStyle: 'round', + data: [] + }, { + label: 'dataset3', + borderWidth: 10, + borderColor: 'green', + pointStyle: 'crossRot', + fill: false, + data: [] + }], + labels: [] + }, + options: { + legend: { + reverse: true + } + } + }); + + expect(chart.legend.legendItems).toEqual([{ + text: 'dataset3', + fillStyle: 'rgba(0,0,0,0)', + hidden: false, + lineCap: 'butt', + lineDash: [], + lineDashOffset: 0, + lineJoin: 'miter', + lineWidth: 10, + strokeStyle: 'green', + pointStyle: undefined, + rotation: undefined, + datasetIndex: 2 + }, { + text: 'dataset2', + fillStyle: 'rgba(0,0,0,0.1)', + hidden: true, + lineCap: 'butt', + lineDash: [], + lineDashOffset: 0, + lineJoin: 'round', + lineWidth: 3, + strokeStyle: 'rgba(0,0,0,0.1)', + pointStyle: undefined, + rotation: undefined, + datasetIndex: 1 + }, { + text: 'dataset1', + fillStyle: '#f31', + hidden: false, + lineCap: 'round', + lineDash: [2, 2], + lineDashOffset: 5.5, + lineJoin: 'miter', + lineWidth: 3, + strokeStyle: 'rgba(0,0,0,0.1)', + pointStyle: undefined, + rotation: undefined, + datasetIndex: 0 + }]); + }); + it('should filter items', function() { var chart = window.acquireChart({ type: 'bar',