]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Implement `dataset.order` (#6268)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Tue, 22 Oct 2019 23:14:54 +0000 (02:14 +0300)
committerEvert Timberg <evert.timberg+github@gmail.com>
Tue, 22 Oct 2019 23:14:54 +0000 (19:14 -0400)
Allow sorting datasets based on the `order` property

30 files changed:
docs/charts/bar.md
docs/charts/bubble.md
docs/charts/line.md
docs/charts/mixed.md
docs/charts/polar.md
docs/charts/radar.md
docs/charts/scatter.md
src/controllers/controller.bar.js
src/controllers/controller.line.js
src/core/core.controller.js
src/core/core.interaction.js
src/core/core.scale.js
src/core/core.tooltip.js
src/plugins/plugin.filler.js
src/plugins/plugin.legend.js
src/scales/scale.linear.js
test/fixtures/controller.bar/stacking/order-default.json [new file with mode: 0644]
test/fixtures/controller.bar/stacking/order-default.png [new file with mode: 0644]
test/fixtures/controller.bar/stacking/order-specified.json [new file with mode: 0644]
test/fixtures/controller.bar/stacking/order-specified.png [new file with mode: 0644]
test/fixtures/controller.line/fill/order-default.js [new file with mode: 0644]
test/fixtures/controller.line/fill/order-default.png [new file with mode: 0644]
test/fixtures/controller.line/fill/order.js [new file with mode: 0644]
test/fixtures/controller.line/fill/order.png [new file with mode: 0644]
test/fixtures/controller.line/stacking/order-default.js [new file with mode: 0644]
test/fixtures/controller.line/stacking/order-default.png [new file with mode: 0644]
test/fixtures/controller.line/stacking/order-specified.js [new file with mode: 0644]
test/fixtures/controller.line/stacking/order-specified.png [new file with mode: 0644]
test/specs/core.tooltip.tests.js
test/specs/plugin.legend.tests.js

index 328b1df0e1935ab2bfff7411b76f2893ed211c5e..a4e54915ad46e22e022547d0f25172806ba80d2c 100644 (file)
@@ -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.
 
index b94c98c17d5c549b75e2098187d494ff1b96c7df..6536395c279c21fb02c88ede9b1821a9f8f66924 100644 (file)
@@ -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
 
index bd2a24c98d259e836a6c5a7aa54ea7dd943b3c96..0ad8773018211af65e046618c47d9bcdb4f92020 100644 (file)
@@ -54,6 +54,7 @@ The line chart allows a number of properties to be specified for each dataset. T
 | [`fill`](#line-styling) | <code>boolean&#124;string</code> | 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.
 
index 9f83f2ea6d8c37bff5bc8b534d8ba1231ae70759..5ead1fb343a7c904c56a62cd59042bb5b8177921 100644 (file)
@@ -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
+});
+```
index 8403a387230331f17067920ba39b9018a8265b61..84275dd9357844201c37fc15925d44d28ddc5c00 100644 (file)
@@ -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'`
 
index e5f3b313766edb60b12a904d4d873177c6601413..942425ed81896001c06deb2148fef98de308f8b3 100644 (file)
@@ -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) | <code>boolean&#124;string</code> | 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
 
index 0107fd4c238768b130f1046d249d1e6d66eafa4c..8fc7726d3d8eace68a0026e312c8bd18e33e0c6e 100644 (file)
@@ -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
index cd07fdbc1deb8e486ecf952b6e2289f468aa5472..4fa3f4290132bfbd2c5ccebd79ec22db04e676ff 100644 (file)
@@ -207,21 +207,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;
@@ -290,24 +296,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 = me.getMeta().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)) {
index c2d67549bfa7b55635b6711565463b72d46ec4db..dc1fc689b24250071aa6b5dca842100b1a5d776d 100644 (file)
@@ -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() {
index 694e776461117cecfa52feac026a91385e2d541e..04d86a3d97a7a3880159a23e237be1868485132b 100644 (file)
@@ -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 = {
@@ -717,6 +723,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`.
@@ -724,16 +757,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]);
@@ -744,12 +776,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
                };
 
@@ -829,7 +860,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
                        };
                }
 
index e163f1182a31905cf298623bb85ccf356cdad9f4..f5051fbff1c0786a7bbd9a5e5a3ba9f859c1b14a 100644 (file)
@@ -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);
                }
        });
 
index 6a5f7bf012b90c54bae2b38a95009ba2f73c912d..29134180cae5cabc7f6297124c3cc27e743a58b9 100644 (file)
@@ -1416,6 +1416,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);
+                       });
        }
 });
 
index 97f7f16df7596eeafd01f4b19bb7081bb9d86238..f42ede285a02cdf90981affa2e30a3d1253d6795 100644 (file)
@@ -995,6 +995,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
index 8acbc40742ca15d064107add213aa2d4de473fc2..8fed6d9057e9079d1e80e5186ee61bca189da9b3 100644 (file)
@@ -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;
index 78a5efe70769c1eafc46ed243abcf52a9943fb0d..c358ff6804cbbeff4aacb69dfd48212a26e51443 100644 (file)
@@ -49,16 +49,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,
@@ -73,7 +72,7 @@ defaults._set('global', {
                                                // Below is extra data used for toggling the datasets
                                                datasetIndex: i
                                        };
-                               }, this) : [];
+                               }, this);
                        }
                }
        },
index 004b7d8e6ea747c3962851bff5589001cff11264..1c3da726d7d3a84a8063514ff0d77f4b1e3864cb 100644 (file)
@@ -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 (file)
index 0000000..53f25a9
--- /dev/null
@@ -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 (file)
index 0000000..59e1024
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 (file)
index 0000000..d4f497b
--- /dev/null
@@ -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 (file)
index 0000000..b927f33
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 (file)
index 0000000..5fcade5
--- /dev/null
@@ -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 (file)
index 0000000..6c07bd2
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 (file)
index 0000000..7d76cf4
--- /dev/null
@@ -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 (file)
index 0000000..6ff1a05
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 (file)
index 0000000..d8b292e
--- /dev/null
@@ -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 (file)
index 0000000..3355da6
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 (file)
index 0000000..5a95441
--- /dev/null
@@ -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 (file)
index 0000000..ff3edfa
Binary files /dev/null and b/test/fixtures/controller.line/stacking/order-specified.png differ
index 29c449a8be4b39d8aa06323a7aba854444a825bb..89898677ad7528cb5bc0ee157a2c0b0f8d1100d3 100755 (executable)
@@ -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',
index 06c8b2cf4c464ecdf54f4f1bd581c065f33e78cf..1fd4821bb59a0a3d30b3757aa335bb77f40e0c10 100644 (file)
@@ -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',