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',