]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Early data parsing, stacking by value and support object data (#6576)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Mon, 4 Nov 2019 23:07:01 +0000 (01:07 +0200)
committerEvert Timberg <evert.timberg+github@gmail.com>
Mon, 4 Nov 2019 23:07:01 +0000 (18:07 -0500)
* Early data parsing + stacking by value
* Review comments
* review comments
* Remove reduntant parsing
* Couple CC warnings
* Optimize filterBetween
* More migration info

28 files changed:
docs/developers/axes.md
docs/getting-started/v3-migration.md
src/controllers/controller.bar.js
src/controllers/controller.bubble.js
src/controllers/controller.doughnut.js
src/controllers/controller.line.js
src/controllers/controller.polarArea.js
src/controllers/controller.radar.js
src/core/core.datasetController.js
src/core/core.scale.js
src/core/core.tooltip.js
src/scales/scale.category.js
src/scales/scale.linear.js
src/scales/scale.linearbase.js
src/scales/scale.logarithmic.js
src/scales/scale.radialLinear.js
src/scales/scale.time.js
test/fixtures/controller.bar/data/object.js [new file with mode: 0644]
test/fixtures/controller.bar/data/object.png [new file with mode: 0644]
test/fixtures/controller.bar/floatBar/float-bar-stacked-horizontal.png
test/fixtures/controller.bar/floatBar/float-bar-stacked.png
test/fixtures/controller.bubble/radius-data.js [new file with mode: 0644]
test/fixtures/controller.bubble/radius-data.png [new file with mode: 0644]
test/specs/scale.category.tests.js
test/specs/scale.linear.tests.js
test/specs/scale.logarithmic.tests.js
test/specs/scale.radialLinear.tests.js
test/specs/scale.time.tests.js

index 682e93ea91987ef87eb3851a686664b3c1b5ab0e..4b919815c960f5d82c6b4f4232ce5deece1cf891 100644 (file)
@@ -73,8 +73,8 @@ To work with Chart.js, custom scale types must implement the following interface
     // buildTicks() should create a ticks array on the axis instance, if you intend to use any of the implementations from the base class
     buildTicks: function() {},
 
-    // Get the value to show for the data at the given index of the the given dataset, ie this.chart.data.datasets[datasetIndex].data[index]
-    getLabelForIndex: function(index, datasetIndex) {},
+    // Get the label to show for the given value
+    getLabelForValue: function(value) {},
 
     // Get the pixel (x coordinate for horizontal axis, y coordinate for vertical axis) for a given value
     // @param index: index into the ticks array
index 80f20139379dda2e52c9e24bc867bd228baeadf4..8587554cfa611642cbfa2d7c413b720bcaa95dbb 100644 (file)
@@ -52,11 +52,13 @@ Chart.js is no longer providing the `Chart.bundle.js` and `Chart.bundle.min.js`.
 * `helpers.numberOfLabelLines`
 * `helpers.removeEvent`
 * `helpers.scaleMerge`
+* `scale.getRightValue`
 * `scale.mergeTicksOptions`
 * `scale.ticksAsNumbers`
 * `Chart.Controller`
 * `Chart.chart.chart`
 * `Chart.types`
+* `Line.calculatePointY`
 * Made `scale.handleDirectionalChanges` private
 * Made `scale.tickValues` private
 
@@ -74,13 +76,18 @@ Chart.js is no longer providing the `Chart.bundle.js` and `Chart.bundle.min.js`.
 
 ### Changed
 
-#### Ticks
+#### Scales
+
+* `scale.getLabelForIndex` was replaced by `scale.getLabelForValue`
+* `scale.getPixelForValue` now has only one parameter
+
+##### Ticks
 
 * `scale.ticks` now contains objects instead of strings
 * `buildTicks` is now expected to return tick objects
 * `afterBuildTicks` now has no parameters like the other callbacks
 * `convertTicksToLabels` was renamed to `generateTickLabels`. It is now expected to set the label property on the ticks given as input
 
-#### Time Scale
+##### Time Scale
 
 * `getValueForPixel` now returns milliseconds since the epoch
index f7a4b221650b4d9c333672d46d6c0338bc4f9758..4bf0010eb7f47c3abc60c7007b6b804a75cdccba 100644 (file)
@@ -125,6 +125,57 @@ function computeFlexCategoryTraits(index, ruler, options) {
        };
 }
 
+function parseFloatBar(arr, item, vScale, i) {
+       var startValue = vScale._parse(arr[0], i);
+       var endValue = vScale._parse(arr[1], i);
+       var min = Math.min(startValue, endValue);
+       var max = Math.max(startValue, endValue);
+       var barStart = min;
+       var barEnd = max;
+
+       if (Math.abs(min) > Math.abs(max)) {
+               barStart = max;
+               barEnd = min;
+       }
+
+       // Store `barEnd` (furthest away from origin) as parsed value,
+       // to make stacking straight forward
+       item[vScale.id] = barEnd;
+
+       item._custom = {
+               barStart: barStart,
+               barEnd: barEnd,
+               start: startValue,
+               end: endValue,
+               min: min,
+               max: max
+       };
+}
+
+function parseArrayOrPrimitive(meta, data, start, count) {
+       var iScale = this._getIndexScale();
+       var vScale = this._getValueScale();
+       var labels = iScale._getLabels();
+       var singleScale = iScale === vScale;
+       var parsed = [];
+       var i, ilen, item, entry;
+
+       for (i = start, ilen = start + count; i < ilen; ++i) {
+               entry = data[i];
+               item = {};
+               item[iScale.id] = singleScale || iScale._parse(labels[i], i);
+
+               if (helpers.isArray(entry)) {
+                       parseFloatBar(entry, item, vScale, i);
+               } else {
+                       item[vScale.id] = vScale._parse(entry, i);
+               }
+
+               parsed.push(item);
+       }
+       return parsed;
+}
+
 module.exports = DatasetController.extend({
 
        dataElementType: elements.Rectangle,
@@ -144,6 +195,24 @@ module.exports = DatasetController.extend({
                'minBarLength'
        ],
 
+       /**
+        * Overriding primitive data parsing since we support mixed primitive/array
+        * data for float bars
+        * @private
+        */
+       _parsePrimitiveData: function() {
+               return parseArrayOrPrimitive.apply(this, arguments);
+       },
+
+       /**
+        * Overriding array data parsing since we support mixed primitive/array
+        * data for float bars
+        * @private
+        */
+       _parseArrayData: function() {
+               return parseArrayOrPrimitive.apply(this, arguments);
+       },
+
        initialize: function() {
                var me = this;
                var meta;
@@ -183,7 +252,8 @@ module.exports = DatasetController.extend({
                        label: me.chart.data.labels[index]
                };
 
-               if (helpers.isArray(dataset.data[index])) {
+               // all borders are drawn for floating bar
+               if (me._getParsed(index)._custom) {
                        rectangle._model.borderSkipped = null;
                }
 
@@ -202,8 +272,8 @@ module.exports = DatasetController.extend({
                var base = vscale.getBasePixel();
                var horizontal = vscale.isHorizontal();
                var ruler = me._ruler || me.getRuler();
-               var vpixels = me.calculateBarValuePixels(me.index, index, options);
-               var ipixels = me.calculateBarIndexPixels(me.index, index, ruler, options);
+               var vpixels = me.calculateBarValuePixels(index, options);
+               var ipixels = me.calculateBarIndexPixels(index, ruler, options);
 
                model.horizontal = horizontal;
                model.base = reset ? base : vpixels.base;
@@ -283,7 +353,7 @@ module.exports = DatasetController.extend({
                var i, ilen;
 
                for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
-                       pixels.push(scale.getPixelForValue(null, i, me.index));
+                       pixels.push(scale.getPixelForValue(me._getParsed(i)[scale.id]));
                }
 
                return {
@@ -299,52 +369,39 @@ module.exports = DatasetController.extend({
         * Note: pixel values are not clamped to the scale area.
         * @private
         */
-       calculateBarValuePixels: function(datasetIndex, index, options) {
+       calculateBarValuePixels: function(index, options) {
                var me = this;
-               var chart = me.chart;
-               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 valueScale = me._getValueScale();
                var minBarLength = 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 < ilen; ++i) {
-                               imeta = metasets[i];
-
-                               if (imeta.index === datasetIndex) {
-                                       break;
-                               }
-
-                               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;
+               var start = 0;
+               var parsed = me._getParsed(index);
+               var value = parsed[valueScale.id];
+               var custom = parsed._custom;
+               var length = me._cachedMeta._stacked ? me._applyStack(valueScale, parsed) : parsed[valueScale.id];
+               var base, head, size;
+
+               if (length !== value) {
+                       start = length - value;
+                       length = value;
+               }
 
-                                       if ((value.min < 0 && ivalue < 0) || (value.max >= 0 && ivalue > 0)) {
-                                               start += ivalue;
-                                       }
-                               }
+               if (custom && custom.barStart !== undefined && custom.barEnd !== undefined) {
+                       value = custom.barStart;
+                       length = custom.barEnd - custom.barStart;
+                       // bars crossing origin are not stacked
+                       if (value !== 0 && Math.sign(value) !== Math.sign(custom.barEnd)) {
+                               start = 0;
                        }
+                       start += value;
                }
 
-               base = scale.getPixelForValue(start);
-               head = scale.getPixelForValue(start + length);
+               base = valueScale.getPixelForValue(start);
+               head = valueScale.getPixelForValue(start + length);
                size = head - base;
 
                if (minBarLength !== undefined && Math.abs(size) < minBarLength) {
-                       size = minBarLength;
-                       if (length >= 0 && !isHorizontal || length < 0 && isHorizontal) {
-                               head = base - minBarLength;
-                       } else {
-                               head = base + minBarLength;
-                       }
+                       size = size < 0 ? -minBarLength : minBarLength;
+                       head = base + size;
                }
 
                return {
@@ -358,13 +415,13 @@ module.exports = DatasetController.extend({
        /**
         * @private
         */
-       calculateBarIndexPixels: function(datasetIndex, index, ruler, options) {
+       calculateBarIndexPixels: function(index, ruler, options) {
                var me = this;
                var range = options.barThickness === 'flex'
                        ? computeFlexCategoryTraits(index, ruler, options)
                        : computeFitCategoryTraits(index, ruler, options);
 
-               var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack);
+               var stackIndex = me.getStackIndex(me.index, me.getMeta().stack);
                var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2);
                var size = Math.min(
                        valueOrDefault(options.maxBarThickness, Infinity),
@@ -383,15 +440,13 @@ module.exports = DatasetController.extend({
                var chart = me.chart;
                var scale = me._getValueScale();
                var rects = me.getMeta().data;
-               var dataset = me.getDataset();
                var ilen = rects.length;
                var i = 0;
 
                helpers.canvas.clipArea(chart.ctx, chart.chartArea);
 
                for (; i < ilen; ++i) {
-                       var val = scale._parseValue(dataset.data[i]);
-                       if (!isNaN(val.min) && !isNaN(val.max)) {
+                       if (!isNaN(me._getParsed(i)[scale.id])) {
                                rects[i].draw();
                        }
                }
index 87db319a89a850197384ff463e38b9c466f881a5..48c0a2ee46dff968b8cb8a38e70d1e8edfad2cbd 100644 (file)
@@ -30,7 +30,7 @@ defaults._set('bubble', {
                        },
                        label: function(item, data) {
                                var datasetLabel = data.datasets[item.datasetIndex].label || '';
-                               var dataPoint = data.datasets[item.datasetIndex].data[item.index];
+                               var dataPoint = data.datasets[item.datasetIndex].data[item.index] || {r: '?'};
                                return datasetLabel + ': (' + item.label + ', ' + item.value + ', ' + dataPoint.r + ')';
                        }
                }
@@ -59,6 +59,26 @@ module.exports = DatasetController.extend({
                'rotation'
        ],
 
+       /**
+        * Parse array of objects
+        * @private
+        */
+       _parseObjectData: function(meta, data, start, count) {
+               var xScale = this.getScaleForId(meta.xAxisID);
+               var yScale = this.getScaleForId(meta.yAxisID);
+               var parsed = [];
+               var i, ilen, item, obj;
+               for (i = start, ilen = start + count; i < ilen; ++i) {
+                       obj = data[i];
+                       item = {};
+                       item[xScale.id] = xScale._parseObject(obj, 'x', i);
+                       item[yScale.id] = yScale._parseObject(obj, 'y', i);
+                       item._custom = obj && obj.r && +obj.r;
+                       parsed.push(item);
+               }
+               return parsed;
+       },
+
        /**
         * @protected
         */
@@ -82,14 +102,12 @@ module.exports = DatasetController.extend({
                var xScale = me.getScaleForId(meta.xAxisID);
                var yScale = me.getScaleForId(meta.yAxisID);
                var options = me._resolveDataElementOptions(index);
-               var data = me.getDataset().data[index];
-               var dsIndex = me.index;
-
-               var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex);
-               var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex);
+               var parsed = !reset && me._getParsed(index);
+               var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(parsed[xScale.id]);
+               var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(parsed[yScale.id]);
 
                point._options = options;
-               point._datasetIndex = dsIndex;
+               point._datasetIndex = me.index;
                point._index = index;
                point._model = {
                        backgroundColor: options.backgroundColor,
@@ -135,7 +153,7 @@ module.exports = DatasetController.extend({
                var me = this;
                var chart = me.chart;
                var dataset = me.getDataset();
-               var data = dataset.data[index] || {};
+               var parsed = me._getParsed(index);
                var values = DatasetController.prototype._resolveDataElementOptions.apply(me, arguments);
 
                // Scriptable options
@@ -153,7 +171,7 @@ module.exports = DatasetController.extend({
 
                // Custom radius resolution
                values.radius = resolve([
-                       data.r,
+                       parsed && parsed._custom,
                        me._config.radius,
                        chart.options.elements.point.radius
                ], context, index);
index 7495d15faf63bd99ca93f83c830fc341eae34ed7..099c88b91487c36f3d0d49b9d06aab1cda2f1182 100644 (file)
@@ -134,6 +134,19 @@ module.exports = DatasetController.extend({
                'hoverBorderWidth',
        ],
 
+       /**
+        * Override data parsing, since we are not using scales
+        * @private
+        */
+       _parse: function(start, count) {
+               var data = this.getDataset().data;
+               var metaData = this.getMeta().data;
+               var i, ilen;
+               for (i = start, ilen = start + count; i < ilen; ++i) {
+                       metaData[i]._val = +data[i];
+               }
+       },
+
        // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
        getRingIndex: function(datasetIndex) {
                var ringIndex = 0;
@@ -220,7 +233,7 @@ module.exports = DatasetController.extend({
                var startAngle = opts.rotation; // non reset case handled later
                var endAngle = opts.rotation; // non reset case handled later
                var dataset = me.getDataset();
-               var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / DOUBLE_PI);
+               var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(arc._val * opts.circumference / DOUBLE_PI);
                var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
                var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
                var options = arc._options || {};
@@ -264,14 +277,13 @@ module.exports = DatasetController.extend({
        },
 
        calculateTotal: function() {
-               var dataset = this.getDataset();
-               var meta = this.getMeta();
+               var metaData = this.getMeta().data;
                var total = 0;
                var value;
 
-               helpers.each(meta.data, function(element, index) {
-                       value = dataset.data[index];
-                       if (!isNaN(value) && !element.hidden) {
+               helpers.each(metaData, function(arc) {
+                       value = arc ? arc._val : NaN;
+                       if (!isNaN(value) && !arc.hidden) {
                                total += Math.abs(value);
                        }
                });
index a8e242d3410a65d3c3077af58da41d147655be3c..41066c0118ac5aaebb09dcfd958be9842677fa6b 100644 (file)
@@ -115,16 +115,15 @@ module.exports = DatasetController.extend({
        updateElement: function(point, index, reset) {
                var me = this;
                var meta = me.getMeta();
-               var dataset = me.getDataset();
                var datasetIndex = me.index;
-               var value = dataset.data[index];
+               var xScale = me._xScale;
+               var yScale = me._yScale;
                var lineModel = meta.dataset._model;
-               var x, y;
-
+               var stacked = meta._stacked;
+               var parsed = me._getParsed(index);
                var options = me._resolveDataElementOptions(index);
-
-               x = me._xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex);
-               y = reset ? me._yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
+               var x = xScale.getPixelForValue(parsed[xScale.id]);
+               var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(stacked ? me._applyStack(yScale, parsed) : parsed[yScale.id]);
 
                // Utility
                point._options = options;
@@ -170,43 +169,6 @@ module.exports = DatasetController.extend({
                return values;
        },
 
-       calculatePointY: function(value, index, datasetIndex) {
-               var me = this;
-               var chart = me.chart;
-               var yScale = me._yScale;
-               var sumPos = 0;
-               var sumNeg = 0;
-               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 < 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 {
-                                               sumPos += stackedRightValue || 0;
-                                       }
-                               }
-                       }
-
-                       if (rightValue < 0) {
-                               return yScale.getPixelForValue(sumNeg + rightValue);
-                       }
-                       return yScale.getPixelForValue(sumPos + rightValue);
-               }
-               return yScale.getPixelForValue(value);
-       },
-
        updateBezierControlPoints: function() {
                var me = this;
                var chart = me.chart;
index b73a938bb4101b9f990d47a53fc9e68aa157b512..d2458bd7b2ddbde010a5b70586aaebc977971217 100644 (file)
@@ -108,8 +108,6 @@ module.exports = DatasetController.extend({
 
        dataElementType: elements.Arc,
 
-       linkScales: helpers.noop,
-
        /**
         * @private
         */
index a690c75d05d3595483d4d252aae51486bec31233..1a0bee2fadaa0c1da33c2910791d926942024273 100644 (file)
@@ -24,8 +24,6 @@ module.exports = DatasetController.extend({
 
        dataElementType: elements.Point,
 
-       linkScales: helpers.noop,
-
        /**
         * @private
         */
index d885c53c536f73ae069e56891f4e3115a48e6afb..a6e889d3e3d0fc5a053a83a4a7a2f4b56b20a7f5 100644 (file)
@@ -75,6 +75,85 @@ function unlistenArrayEvents(array, listener) {
        delete array._chartjs;
 }
 
+function getSortedDatasetIndices(chart, filterVisible) {
+       var keys = [];
+       var metasets = chart._getSortedDatasetMetas(filterVisible);
+       var i, ilen;
+
+       for (i = 0, ilen = metasets.length; i < ilen; ++i) {
+               keys.push(metasets[i].index);
+       }
+       return keys;
+}
+
+function applyStack(stack, value, dsIndex, allOther) {
+       var keys = stack.keys;
+       var i, ilen, datasetIndex, otherValue;
+
+       for (i = 0, ilen = keys.length; i < ilen; ++i) {
+               datasetIndex = +keys[i];
+               if (datasetIndex === dsIndex) {
+                       if (allOther) {
+                               continue;
+                       }
+                       break;
+               }
+               otherValue = stack.values[datasetIndex];
+               if (!isNaN(otherValue) && (value === 0 || Math.sign(value) === Math.sign(otherValue))) {
+                       value += otherValue;
+               }
+       }
+       return value;
+}
+
+function convertObjectDataToArray(data) {
+       var keys = Object.keys(data);
+       var adata = [];
+       var i, ilen, key;
+       for (i = 0, ilen = keys.length; i < ilen; ++i) {
+               key = keys[i];
+               adata.push({
+                       x: key,
+                       y: data[key]
+               });
+       }
+       return adata;
+}
+
+function isStacked(scale, meta) {
+       var stacked = scale && scale.options.stacked;
+       return stacked || (stacked === undefined && meta.stack !== undefined);
+}
+
+function getStackKey(xScale, yScale, meta) {
+       return isStacked(yScale, meta) && xScale.id + '.' + yScale.id + '.' + meta.stack + '.' + meta.type;
+}
+
+function arraysEqual(array1, array2) {
+       var ilen = array1.length;
+       var i;
+
+       if (ilen !== array2.length) {
+               return false;
+       }
+
+       for (i = 0; i < ilen; i++) {
+               if (array1[i] !== array2[i]) {
+                       return false;
+               }
+       }
+       return true;
+}
+
+function getFirstScaleId(chart, axis) {
+       var scalesOpts = chart.options.scales;
+       var scale = chart.options.scale;
+       var scaleId = scale && scale.id;
+       var prop = axis + 'Axes';
+
+       return (scalesOpts && scalesOpts[prop] && scalesOpts[prop].length && scalesOpts[prop][0].id) || scaleId;
+}
+
 // Base class for all dataset controllers (line, bar, etc)
 var DatasetController = function(chart, datasetIndex) {
        this.initialize(chart, datasetIndex);
@@ -125,11 +204,14 @@ helpers.extend(DatasetController.prototype, {
 
        initialize: function(chart, datasetIndex) {
                var me = this;
+               var meta;
                me.chart = chart;
                me.index = datasetIndex;
+               me._cachedMeta = meta = me.getMeta();
+               me._type = meta.type;
                me.linkScales();
+               meta._stacked = isStacked(me._getValueScale(), meta);
                me.addElements();
-               me._type = me.getMeta().type;
        },
 
        updateIndex: function(datasetIndex) {
@@ -137,19 +219,12 @@ helpers.extend(DatasetController.prototype, {
        },
 
        linkScales: function() {
-               var me = this;
-               var meta = me.getMeta();
-               var chart = me.chart;
-               var scales = chart.scales;
-               var dataset = me.getDataset();
-               var scalesOpts = chart.options.scales;
+               var chart = this.chart;
+               var meta = this._cachedMeta;
+               var dataset = this.getDataset();
 
-               if (meta.xAxisID === null || !(meta.xAxisID in scales) || dataset.xAxisID) {
-                       meta.xAxisID = dataset.xAxisID || scalesOpts.xAxes[0].id;
-               }
-               if (meta.yAxisID === null || !(meta.yAxisID in scales) || dataset.yAxisID) {
-                       meta.yAxisID = dataset.yAxisID || scalesOpts.yAxes[0].id;
-               }
+               meta.xAxisID = dataset.xAxisID || getFirstScaleId(chart, 'x');
+               meta.yAxisID = dataset.yAxisID || getFirstScaleId(chart, 'y');
        },
 
        getDataset: function() {
@@ -168,14 +243,14 @@ helpers.extend(DatasetController.prototype, {
         * @private
         */
        _getValueScaleId: function() {
-               return this.getMeta().yAxisID;
+               return this._cachedMeta.yAxisID;
        },
 
        /**
         * @private
         */
        _getIndexScaleId: function() {
-               return this.getMeta().xAxisID;
+               return this._cachedMeta.xAxisID;
        },
 
        /**
@@ -220,16 +295,76 @@ helpers.extend(DatasetController.prototype, {
                return type && new type({
                        _ctx: me.chart.ctx,
                        _datasetIndex: me.index,
-                       _index: index
+                       _index: index,
+                       _parsed: {}
                });
        },
 
+       /**
+        * @private
+        */
+       _dataCheck: function() {
+               var me = this;
+               var dataset = me.getDataset();
+               var data = dataset.data || (dataset.data = []);
+
+               // In order to correctly handle data addition/deletion animation (an thus simulate
+               // real-time charts), we need to monitor these data modifications and synchronize
+               // the internal meta data accordingly.
+
+               if (helpers.isObject(data)) {
+                       // Object data is currently monitored for replacement only
+                       if (me._objectData === data) {
+                               return false;
+                       }
+                       me._data = convertObjectDataToArray(data);
+                       me._objectData = data;
+               } else {
+                       if (me._data === data && arraysEqual(data, me._dataCopy)) {
+                               return false;
+                       }
+
+                       if (me._data) {
+                               // This case happens when the user replaced the data array instance.
+                               unlistenArrayEvents(me._data, me);
+                       }
+
+                       // Store a copy to detect direct modifications.
+                       // Note: This is suboptimal, but better than always parsing the data
+                       me._dataCopy = data.slice(0);
+
+                       if (data && Object.isExtensible(data)) {
+                               listenArrayEvents(data, me);
+                       }
+                       me._data = data;
+               }
+               return true;
+       },
+
+       /**
+        * @private
+        */
+       _labelCheck: function() {
+               var me = this;
+               var scale = me._getIndexScale();
+               var labels = scale ? scale._getLabels() : me.chart.data.labels;
+
+               if (me._labels === labels) {
+                       return false;
+               }
+
+               me._labels = labels;
+               return true;
+       },
+
        addElements: function() {
                var me = this;
-               var meta = me.getMeta();
-               var data = me.getDataset().data || [];
+               var meta = me._cachedMeta;
                var metaData = meta.data;
-               var i, ilen;
+               var i, ilen, data;
+
+               me._dataCheck();
+               data = me._data;
 
                for (i = 0, ilen = data.length; i < ilen; ++i) {
                        metaData[i] = metaData[i] || me.createMetaData(i);
@@ -240,33 +375,23 @@ helpers.extend(DatasetController.prototype, {
 
        addElementAndReset: function(index) {
                var element = this.createMetaData(index);
-               this.getMeta().data.splice(index, 0, element);
+               this._cachedMeta.data.splice(index, 0, element);
                this.updateElement(element, index, true);
        },
 
        buildOrUpdateElements: function() {
                var me = this;
-               var dataset = me.getDataset();
-               var data = dataset.data || (dataset.data = []);
+               var dataChanged = me._dataCheck();
+               var labelsChanged = me._labelCheck();
+               var scaleChanged = me._scaleCheck();
+               var meta = me._cachedMeta;
 
-               // In order to correctly handle data addition/deletion animation (an thus simulate
-               // real-time charts), we need to monitor these data modifications and synchronize
-               // the internal meta data accordingly.
-               if (me._data !== data) {
-                       if (me._data) {
-                               // This case happens when the user replaced the data array instance.
-                               unlistenArrayEvents(me._data, me);
-                       }
-
-                       if (data && Object.isExtensible(data)) {
-                               listenArrayEvents(data, me);
-                       }
-                       me._data = data;
-               }
+               // make sure cached _stacked status is current
+               meta._stacked = isStacked(me._getValueScale(), meta);
 
                // Re-sync meta data in case the user replaced the data array or if we missed
                // any updates and so make sure that we handle number of datapoints changing.
-               me.resyncElements();
+               me.resyncElements(dataChanged | labelsChanged | scaleChanged);
        },
 
        /**
@@ -287,17 +412,256 @@ helpers.extend(DatasetController.prototype, {
                });
        },
 
+       /**
+        * @private
+        */
+       _parse: function(start, count) {
+               var me = this;
+               var chart = me.chart;
+               var meta = me._cachedMeta;
+               var data = me._data;
+               var crossRef = chart._xref || (chart._xref = {});
+               var xScale = me._getIndexScale();
+               var yScale = me._getValueScale();
+               var xId = xScale.id;
+               var yId = yScale.id;
+               var xKey = getStackKey(xScale, yScale, meta);
+               var yKey = getStackKey(yScale, xScale, meta);
+               var stacks = xKey || yKey;
+               var i, ilen, parsed, stack, item, x, y;
+
+               if (helpers.isArray(data[start])) {
+                       parsed = me._parseArrayData(meta, data, start, count);
+               } else if (helpers.isObject(data[start])) {
+                       parsed = me._parseObjectData(meta, data, start, count);
+               } else {
+                       parsed = me._parsePrimitiveData(meta, data, start, count);
+               }
+
+               function storeStack(stackKey, indexValue, scaleId, value) {
+                       if (stackKey) {
+                               stackKey += '.' + indexValue;
+                               item._stackKeys[scaleId] = stackKey;
+                               stack = crossRef[stackKey] || (crossRef[stackKey] = {});
+                               stack[meta.index] = value;
+                       }
+               }
+
+               for (i = 0, ilen = parsed.length; i < ilen; ++i) {
+                       item = parsed[i];
+                       meta.data[start + i]._parsed = item;
+
+                       if (stacks) {
+                               item._stackKeys = {};
+                               x = item[xId];
+                               y = item[yId];
+
+                               storeStack(xKey, x, yId, y);
+                               storeStack(yKey, y, xId, x);
+                       }
+               }
+
+               xScale._invalidateCaches();
+               if (yScale !== xScale) {
+                       yScale._invalidateCaches();
+               }
+       },
+
+       /**
+        * Parse array of primitive values
+        * @param {object} meta - dataset meta
+        * @param {array} data - data array. Example [1,3,4]
+        * @param {number} start - start index
+        * @param {number} count - number of items to parse
+        * @returns {object} parsed item - item containing index and a parsed value
+        * for each scale id.
+        * Example: {xScale0: 0, yScale0: 1}
+        * @private
+        */
+       _parsePrimitiveData: function(meta, data, start, count) {
+               var iScale = this._getIndexScale();
+               var vScale = this._getValueScale();
+               var labels = iScale._getLabels();
+               var singleScale = iScale === vScale;
+               var parsed = [];
+               var i, ilen, item;
+
+               for (i = start, ilen = start + count; i < ilen; ++i) {
+                       item = {};
+                       item[iScale.id] = singleScale || iScale._parse(labels[i], i);
+                       item[vScale.id] = vScale._parse(data[i], i);
+                       parsed.push(item);
+               }
+               return parsed;
+       },
+
+       /**
+        * Parse array of arrays
+        * @param {object} meta - dataset meta
+        * @param {array} data - data array. Example [[1,2],[3,4]]
+        * @param {number} start - start index
+        * @param {number} count - number of items to parse
+        * @returns {object} parsed item - item containing index and a parsed value
+        * for each scale id.
+        * Example: {xScale0: 0, yScale0: 1}
+        * @private
+        */
+       _parseArrayData: function(meta, data, start, count) {
+               var xScale = this.getScaleForId(meta.xAxisID);
+               var yScale = this.getScaleForId(meta.yAxisID);
+               var parsed = [];
+               var i, ilen, item, arr;
+               for (i = start, ilen = start + count; i < ilen; ++i) {
+                       arr = data[i];
+                       item = {};
+                       item[xScale.id] = xScale._parse(arr[0], i);
+                       item[yScale.id] = yScale._parse(arr[1], i);
+                       parsed.push(item);
+               }
+               return parsed;
+       },
+
+       /**
+        * Parse array of objects
+        * @param {object} meta - dataset meta
+        * @param {array} data - data array. Example [{x:1, y:5}, {x:2, y:10}]
+        * @param {number} start - start index
+        * @param {number} count - number of items to parse
+        * @returns {object} parsed item - item containing index and a parsed value
+        * for each scale id. _custom is optional
+        * Example: {xScale0: 0, yScale0: 1, _custom: {r: 10, foo: 'bar'}}
+        * @private
+        */
+       _parseObjectData: function(meta, data, start, count) {
+               var xScale = this.getScaleForId(meta.xAxisID);
+               var yScale = this.getScaleForId(meta.yAxisID);
+               var parsed = [];
+               var i, ilen, item, obj;
+               for (i = start, ilen = start + count; i < ilen; ++i) {
+                       obj = data[i];
+                       item = {};
+                       item[xScale.id] = xScale._parseObject(obj, 'x', i);
+                       item[yScale.id] = yScale._parseObject(obj, 'y', i);
+                       parsed.push(item);
+               }
+               return parsed;
+       },
+
+       /**
+        * @private
+        */
+       _getParsed: function(index) {
+               var data = this._cachedMeta.data;
+               if (index < 0 || index >= data.length) {
+                       return;
+               }
+               return data[index]._parsed;
+       },
+
+       /**
+        * @private
+        */
+       _applyStack: function(scale, parsed) {
+               var chart = this.chart;
+               var meta = this._cachedMeta;
+               var value = parsed[scale.id];
+               var stack = {
+                       keys: getSortedDatasetIndices(chart, true),
+                       values: chart._xref[parsed._stackKeys[scale.id]]
+               };
+               return applyStack(stack, value, meta.index);
+       },
+
+       _getMinMax: function(scale, canStack) {
+               var chart = this.chart;
+               var meta = this._cachedMeta;
+               var metaData = meta.data;
+               var ilen = metaData.length;
+               var crossRef = chart._xref || (chart._xref = {});
+               var max = Number.NEGATIVE_INFINITY;
+               var stacked = canStack && meta._stacked;
+               var indices = getSortedDatasetIndices(chart, true);
+               var i, item, value, parsed, stack, min, minPositive;
+
+               min = minPositive = Number.POSITIVE_INFINITY;
+
+               for (i = 0; i < ilen; ++i) {
+                       item = metaData[i];
+                       parsed = item._parsed;
+                       value = parsed[scale.id];
+                       if (item.hidden || isNaN(value)) {
+                               continue;
+                       }
+                       if (stacked) {
+                               stack = {
+                                       keys: indices,
+                                       values: crossRef[parsed._stackKeys[scale.id]]
+                               };
+                               value = applyStack(stack, value, meta.index, true);
+                       }
+                       min = Math.min(min, value);
+                       max = Math.max(max, value);
+                       if (value > 0) {
+                               minPositive = Math.min(minPositive, value);
+                       }
+               }
+               return {
+                       min: min,
+                       max: max,
+                       minPositive: minPositive
+               };
+       },
+
+       _getAllParsedValues: function(scale) {
+               var meta = this._cachedMeta;
+               var metaData = meta.data;
+               var values = [];
+               var i, ilen, value;
+
+               for (i = 0, ilen = metaData.length; i < ilen; ++i) {
+                       value = metaData[i]._parsed[scale.id];
+                       if (!isNaN(value)) {
+                               values.push(value);
+                       }
+               }
+               return values;
+       },
+
+       _cacheScaleStackStatus: function() {
+               var me = this;
+               var indexScale = me._getIndexScale();
+               var valueScale = me._getValueScale();
+               var cache = me._scaleStacked = {};
+               if (indexScale && valueScale) {
+                       cache[indexScale.id] = indexScale.options.stacked;
+                       cache[valueScale.id] = valueScale.options.stacked;
+               }
+       },
+
+       _scaleCheck: function() {
+               var me = this;
+               var indexScale = me._getIndexScale();
+               var valueScale = me._getValueScale();
+               var cache = me._scaleStacked;
+               return !cache ||
+                       !indexScale ||
+                       !valueScale ||
+                       cache[indexScale.id] !== indexScale.options.stacked ||
+                       cache[valueScale.id] !== valueScale.options.stacked;
+       },
+
        _update: function(reset) {
                var me = this;
                me._configure();
                me._cachedDataOpts = null;
                me.update(reset);
+               me._cacheScaleStackStatus();
        },
 
        update: helpers.noop,
 
        transition: function(easingValue) {
-               var meta = this.getMeta();
+               var meta = this._cachedMeta;
                var elements = meta.data || [];
                var ilen = elements.length;
                var i = 0;
@@ -312,7 +676,7 @@ helpers.extend(DatasetController.prototype, {
        },
 
        draw: function() {
-               var meta = this.getMeta();
+               var meta = this._cachedMeta;
                var elements = meta.data || [];
                var ilen = elements.length;
                var i = 0;
@@ -334,7 +698,7 @@ helpers.extend(DatasetController.prototype, {
         */
        getStyle: function(index) {
                var me = this;
-               var meta = me.getMeta();
+               var meta = me._cachedMeta;
                var dataset = meta.dataset;
                var style;
 
@@ -501,17 +865,19 @@ helpers.extend(DatasetController.prototype, {
        /**
         * @private
         */
-       resyncElements: function() {
+       resyncElements: function(changed) {
                var me = this;
-               var meta = me.getMeta();
-               var data = me.getDataset().data;
+               var meta = me._cachedMeta;
                var numMeta = meta.data.length;
-               var numData = data.length;
+               var numData = me._data.length;
 
-               if (numData < numMeta) {
-                       meta.data.splice(numData, numMeta - numData);
-               } else if (numData > numMeta) {
+               if (numData > numMeta) {
                        me.insertElements(numMeta, numData - numMeta);
+               } else if (numData < numMeta) {
+                       meta.data.splice(numData, numMeta - numData);
+                       me._parse(0, numData);
+               } else if (changed) {
+                       me._parse(0, numData);
                }
        },
 
@@ -522,6 +888,7 @@ helpers.extend(DatasetController.prototype, {
                for (var i = 0; i < count; ++i) {
                        this.addElementAndReset(start + i);
                }
+               this._parse(start, count);
        },
 
        /**
@@ -536,21 +903,21 @@ helpers.extend(DatasetController.prototype, {
         * @private
         */
        onDataPop: function() {
-               this.getMeta().data.pop();
+               this._cachedMeta.data.pop();
        },
 
        /**
         * @private
         */
        onDataShift: function() {
-               this.getMeta().data.shift();
+               this._cachedMeta.data.shift();
        },
 
        /**
         * @private
         */
        onDataSplice: function(start, count) {
-               this.getMeta().data.splice(start, count);
+               this._cachedMeta.data.splice(start, count);
                this.insertElements(start, arguments.length - 2);
        },
 
index f3521ee1232194574e266044de80d6cc08904c50..6cc8b3ab31147c38627d9842f985de79899fa5b6 100644 (file)
@@ -327,6 +327,56 @@ var Scale = Element.extend({
 
        zeroLineIndex: 0,
 
+       /**
+        * Parse a supported input value to internal representation.
+        * @param {*} raw
+        * @param {number} index
+        * @private
+        * @since 3.0
+        */
+       _parse: function(raw, index) { // eslint-disable-line no-unused-vars
+               return raw;
+       },
+
+       /**
+        * Parse an object for axis to internal representation.
+        * @param {object} obj
+        * @param {string} axis
+        * @param {number} index
+        * @private
+        * @since 3.0
+        */
+       _parseObject: function(obj, axis, index) {
+               if (obj[axis] !== undefined) {
+                       return this._parse(obj[axis], index);
+               }
+               return null;
+       },
+
+       _getMinMax: function(canStack) {
+               var me = this;
+               var metas = me._getMatchingVisibleMetas();
+               var min = Number.POSITIVE_INFINITY;
+               var max = Number.NEGATIVE_INFINITY;
+               var minPositive = Number.POSITIVE_INFINITY;
+               var i, ilen, minmax;
+
+               for (i = 0, ilen = metas.length; i < ilen; ++i) {
+                       minmax = metas[i].controller._getMinMax(me, canStack);
+                       min = Math.min(min, minmax.min);
+                       max = Math.max(max, minmax.max);
+                       minPositive = Math.min(minPositive, minmax.minPositive);
+               }
+
+               return {
+                       min: min,
+                       max: max,
+                       minPositive: minPositive
+               };
+       },
+
+       _invalidateCaches: helpers.noop,
+
        /**
         * Get the padding needed for the scale
         * @method getPadding
@@ -734,32 +784,6 @@ var Scale = Element.extend({
                return this.options.fullWidth;
        },
 
-       // Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not
-       getRightValue: function(rawValue) {
-               // Null and undefined values first
-               if (isNullOrUndef(rawValue)) {
-                       return NaN;
-               }
-               // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values
-               if ((typeof rawValue === 'number' || rawValue instanceof Number) && !isFinite(rawValue)) {
-                       return NaN;
-               }
-
-               // If it is in fact an object, dive in one more level
-               if (rawValue) {
-                       if (this.isHorizontal()) {
-                               if (rawValue.x !== undefined) {
-                                       return this.getRightValue(rawValue.x);
-                               }
-                       } else if (rawValue.y !== undefined) {
-                               return this.getRightValue(rawValue.y);
-                       }
-               }
-
-               // Value is good, return it
-               return rawValue;
-       },
-
        _convertTicksToLabels: function(ticks) {
                var me = this;
 
@@ -786,51 +810,13 @@ var Scale = Element.extend({
        },
 
        /**
-        * @private
+        * Used to get the label to display in the tooltip for the given value
+        * @param value
         */
-       _parseValue: function(value) {
-               var start, end, min, max;
-
-               if (isArray(value)) {
-                       start = +this.getRightValue(value[0]);
-                       end = +this.getRightValue(value[1]);
-                       min = Math.min(start, end);
-                       max = Math.max(start, end);
-               } else {
-                       value = +this.getRightValue(value);
-                       start = undefined;
-                       end = value;
-                       min = value;
-                       max = value;
-               }
-
-               return {
-                       min: min,
-                       max: max,
-                       start: start,
-                       end: end
-               };
-       },
-
-       /**
-       * @private
-       */
-       _getScaleLabel: function(rawValue) {
-               var v = this._parseValue(rawValue);
-               if (v.start !== undefined) {
-                       return '[' + v.start + ', ' + v.end + ']';
-               }
-
-               return +this.getRightValue(rawValue);
+       getLabelForValue: function(value) {
+               return value;
        },
 
-       /**
-        * Used to get the value to display in the tooltip for the data at the given index
-        * @param index
-        * @param datasetIndex
-        */
-       getLabelForIndex: helpers.noop,
-
        /**
         * Returns the location of the given data point. Value can either be an index or a numerical value
         * The coordinate (0, 0) is at the upper-left corner of the canvas
@@ -963,26 +949,13 @@ var Scale = Element.extend({
         * @private
         */
        _isVisible: function() {
-               var me = this;
-               var chart = me.chart;
-               var display = me.options.display;
-               var i, ilen, meta;
+               var display = this.options.display;
 
                if (display !== 'auto') {
                        return !!display;
                }
 
-               // When 'auto', the scale is visible if at least one associated dataset is visible.
-               for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
-                       if (chart.isDatasetVisible(i)) {
-                               meta = chart.getDatasetMeta(i);
-                               if (meta.xAxisID === me.id || meta.yAxisID === me.id) {
-                                       return true;
-                               }
-                       }
-               }
-
-               return false;
+               return this._getMatchingVisibleMetas().length > 0;
        },
 
        /**
@@ -1402,14 +1375,29 @@ var Scale = Element.extend({
        /**
         * @private
         */
+       _getAxisID: function() {
+               return this.isHorizontal() ? 'xAxisID' : 'yAxisID';
+       },
+
+       /**
+        * Returns visible dataset metas that are attached to this scale
+        * @param {string} [type] - if specified, also filter by dataset type
+        * @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);
-                       });
+               var metas = me.chart._getSortedVisibleDatasetMetas();
+               var axisID = me._getAxisID();
+               var result = [];
+               var i, ilen, meta;
+
+               for (i = 0, ilen = metas.length; i < ilen; ++i) {
+                       meta = metas[i];
+                       if (meta[axisID] === me.id && (!type || meta.type === type)) {
+                               result.push(meta);
+                       }
+               }
+               return result;
        }
 });
 
index f1aeee29894f870c01b8a7161535cc7199e78c6a..38d4e990d7d7557d38724e17bb06c7dc56b8203c 100644 (file)
@@ -210,10 +210,11 @@ function createTooltipItem(chart, element) {
        var controller = chart.getDatasetMeta(datasetIndex).controller;
        var indexScale = controller._getIndexScale();
        var valueScale = controller._getValueScale();
+       var parsed = controller._getParsed(index);
 
        return {
-               label: indexScale ? '' + indexScale.getLabelForIndex(index, datasetIndex) : '',
-               value: valueScale ? '' + valueScale.getLabelForIndex(index, datasetIndex) : '',
+               label: indexScale ? '' + indexScale.getLabelForValue(parsed[indexScale.id]) : '',
+               value: valueScale ? '' + valueScale.getLabelForValue(parsed[valueScale.id]) : '',
                index: index,
                datasetIndex: datasetIndex,
                x: element._model.x,
index 487f4b554ff05b6b1ea84a8a9fcedfb346439bce..cb07a6751b26b39d90547dbea53e3df4ed6b9b08 100644 (file)
@@ -1,15 +1,27 @@
 'use strict';
 
-var helpers = require('../helpers/index');
 var Scale = require('../core/core.scale');
 
-var isNullOrUndef = helpers.isNullOrUndef;
-
 var defaultConfig = {
        position: 'bottom'
 };
 
 module.exports = Scale.extend({
+
+       _parse: function(raw, index) {
+               var labels = this._getLabels();
+               var first = labels.indexOf(raw);
+               var last = labels.lastIndexOf(raw);
+               return first === -1 || first !== last ? index : first;
+       },
+
+       _parseObject: function(obj, axis, index) {
+               if (obj[axis] !== undefined) {
+                       return this._parse(obj[axis], index);
+               }
+               return null;
+       },
+
        determineDataLimits: function() {
                var me = this;
                var labels = me._getLabels();
@@ -55,15 +67,14 @@ module.exports = Scale.extend({
                });
        },
 
-       getLabelForIndex: function(index, datasetIndex) {
+       getLabelForValue: function(value) {
                var me = this;
-               var chart = me.chart;
+               var labels = me._getLabels();
 
-               if (chart.getDatasetMeta(datasetIndex).controller._getValueScaleId() === me.id) {
-                       return me.getRightValue(chart.data.datasets[datasetIndex].data[index]);
+               if (value >= 0 && value < labels.length) {
+                       return labels[value];
                }
-
-               return me._getLabels()[index];
+               return value;
        },
 
        _configure: function() {
@@ -87,36 +98,21 @@ module.exports = Scale.extend({
        },
 
        // Used to get data value locations.  Value can either be an index or a numerical value
-       getPixelForValue: function(value, index, datasetIndex) {
+       getPixelForValue: function(value) {
                var me = this;
-               var valueCategory, labels, idx;
 
-               if (!isNullOrUndef(index) && !isNullOrUndef(datasetIndex)) {
-                       value = me.chart.data.datasets[datasetIndex].data[index];
+               if (typeof value !== 'number') {
+                       value = me._parse(value);
                }
 
-               // If value is a data object, then index is the index in the data array,
-               // not the index of the scale. We need to change that.
-               if (!isNullOrUndef(value)) {
-                       valueCategory = me.isHorizontal() ? value.x : value.y;
-               }
-               if (valueCategory !== undefined || (value !== undefined && isNaN(index))) {
-                       labels = me._getLabels();
-                       value = helpers.valueOrDefault(valueCategory, value);
-                       idx = labels.indexOf(value);
-                       index = idx !== -1 ? idx : index;
-                       if (isNaN(index)) {
-                               index = value;
-                       }
-               }
-               return me.getPixelForDecimal((index - me._startValue) / me._valueRange);
+               return me.getPixelForDecimal((value - me._startValue) / me._valueRange);
        },
 
        getPixelForTick: function(index) {
                var ticks = this.ticks;
                return index < 0 || index > ticks.length - 1
                        ? null
-                       : this.getPixelForValue(ticks[index], index + this.minIndex);
+                       : this.getPixelForValue(index + this.minIndex);
        },
 
        getValueForPixel: function(pixel) {
index 21999c2be0b248b548298f1257555e8a60601f64..84c3b19c5ea8023afb4fa06d6f0bc720509f8a10 100644 (file)
@@ -11,108 +11,23 @@ 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 (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 metasets = me._getMatchingVisibleMetas();
-               var hasStacks = opts.stacked;
-               var stacks = {};
-               var ilen = metasets.length;
-               var i, meta, data, values;
-
-               me.min = Number.POSITIVE_INFINITY;
-               me.max = Number.NEGATIVE_INFINITY;
-
-               if (hasStacks === undefined) {
-                       for (i = 0; !hasStacks && i < ilen; ++i) {
-                               meta = metasets[i];
-                               hasStacks = meta.stack !== undefined;
-                       }
+               var DEFAULT_MIN = 0;
+               var DEFAULT_MAX = 1;
+               var minmax = me._getMinMax(true);
+               var min = minmax.min;
+               var max = minmax.max;
+
+               me.min = helpers.isFinite(min) && !isNaN(min) ? min : DEFAULT_MIN;
+               me.max = helpers.isFinite(max) && !isNaN(max) ? max : DEFAULT_MAX;
+
+               // Backward compatible inconsistent min for stacked
+               if (me.options.stacked && min > 0) {
+                       me.min = 0;
                }
 
-               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);
-                       helpers._setMinAndMax(values, me);
-               });
-
-               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
                me.handleTickRangeOptions();
        },
@@ -138,14 +53,10 @@ module.exports = LinearScaleBase.extend({
                return this.isHorizontal() ? ticks : ticks.reverse();
        },
 
-       getLabelForIndex: function(index, datasetIndex) {
-               return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]);
-       },
-
        // Utils
        getPixelForValue: function(value) {
                var me = this;
-               return me.getPixelForDecimal((+me.getRightValue(value) - me._startValue) / me._valueRange);
+               return me.getPixelForDecimal((value - me._startValue) / me._valueRange);
        },
 
        getValueForPixel: function(pixel) {
index b20820aaf5b2f68bead5dec67c3634afc5805707..c420b601957219be917d826a94a4770bdd4f94c7 100644 (file)
@@ -84,11 +84,15 @@ function generateTicks(generationOptions, dataRange) {
 }
 
 module.exports = Scale.extend({
-       getRightValue: function(value) {
-               if (typeof value === 'string') {
-                       return +value;
+       _parse: function(raw) {
+               if (helpers.isNullOrUndef(raw)) {
+                       return NaN;
                }
-               return Scale.prototype.getRightValue.call(this, value);
+               if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(raw)) {
+                       return NaN;
+               }
+
+               return +raw;
        },
 
        handleTickRangeOptions: function() {
index 43a9d46ab920e773fc1aa5ebe2ee4ac2e2801b8e..f85ed9549d6c87b91d67adec7161ca5cfb41432c 100644 (file)
@@ -3,6 +3,7 @@
 var defaults = require('../core/core.defaults');
 var helpers = require('../helpers/index');
 var Scale = require('../core/core.scale');
+var LinearScaleBase = require('./scale.linearbase');
 var Ticks = require('../core/core.ticks');
 
 var valueOrDefault = helpers.valueOrDefault;
@@ -69,100 +70,20 @@ function nonNegativeOrDefault(value, defaultValue) {
 }
 
 module.exports = Scale.extend({
+       _parse: LinearScaleBase.prototype._parse,
+
        determineDataLimits: function() {
                var me = this;
-               var opts = me.options;
-               var chart = me.chart;
-               var datasets = chart.data.datasets;
-               var isHorizontal = me.isHorizontal();
-               function IDMatches(meta) {
-                       return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
-               }
-               var datasetIndex, meta, value, data, i, ilen;
-
-               // Calculate Range
-               me.min = Number.POSITIVE_INFINITY;
-               me.max = Number.NEGATIVE_INFINITY;
-               me.minNotZero = Number.POSITIVE_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;
-                               }
-                       }
-               }
-
-               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 (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
-                                       if (valuesPerStack[key] === undefined) {
-                                               valuesPerStack[key] = [];
-                                       }
-
-                                       data = datasets[datasetIndex].data;
-                                       for (i = 0, ilen = data.length; i < ilen; i++) {
-                                               var values = valuesPerStack[key];
-                                               value = me._parseValue(data[i]);
-                                               // invalid, hidden and negative values are ignored
-                                               if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) {
-                                                       continue;
-                                               }
-                                               values[i] = values[i] || 0;
-                                               values[i] += value.max;
-                                       }
-                               }
-                       }
-
-                       helpers.each(valuesPerStack, function(valuesForType) {
-                               if (valuesForType.length > 0) {
-                                       helpers._setMinAndMax(valuesForType, me);
-                               }
-                       });
+               var minmax = me._getMinMax(true);
+               var min = minmax.min;
+               var max = minmax.max;
+               var minPositive = minmax.minPositive;
 
-               } 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]);
-                                               // invalid, hidden and negative values are ignored
-                                               if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) {
-                                                       continue;
-                                               }
-
-                                               me.min = Math.min(value.min, me.min);
-                                               me.max = Math.max(value.max, me.max);
-
-                                               if (value.min !== 0) {
-                                                       me.minNotZero = Math.min(value.min, me.minNotZero);
-                                               }
-                                       }
-                               }
-                       }
-               }
-
-               me.min = helpers.isFinite(me.min) ? me.min : null;
-               me.max = helpers.isFinite(me.max) ? me.max : null;
-               me.minNotZero = helpers.isFinite(me.minNotZero) ? me.minNotZero : null;
+               me.min = helpers.isFinite(min) ? Math.max(0, min) : null;
+               me.max = helpers.isFinite(max) ? Math.max(0, max) : null;
+               me.minNotZero = helpers.isFinite(minPositive) ? minPositive : null;
 
-               // Common base implementation to handle ticks.min, ticks.max
-               this.handleTickRangeOptions();
+               me.handleTickRangeOptions();
        },
 
        handleTickRangeOptions: function() {
@@ -237,11 +158,6 @@ module.exports = Scale.extend({
                return Scale.prototype.generateTickLabels.call(this, ticks);
        },
 
-       // Get the correct tooltip label
-       getLabelForIndex: function(index, datasetIndex) {
-               return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]);
-       },
-
        getPixelForTick: function(index) {
                var ticks = this._tickValues;
                if (index < 0 || index > ticks.length - 1) {
@@ -284,8 +200,6 @@ module.exports = Scale.extend({
                var me = this;
                var decimal = 0;
 
-               value = +me.getRightValue(value);
-
                if (value > me.min && value > 0) {
                        decimal = (log10(value) - me._startValue) / me._valueRange + me._valueOffset;
                }
index 18216168da985530b74494891bcf7d5ba46910a5..5ccf9a20d0055a4b2dbd309247cdaa0b5c299fba 100644 (file)
@@ -305,28 +305,12 @@ module.exports = LinearScaleBase.extend({
 
        determineDataLimits: function() {
                var me = this;
-               var chart = me.chart;
-               var min = Number.POSITIVE_INFINITY;
-               var max = Number.NEGATIVE_INFINITY;
-
-               helpers.each(chart.data.datasets, function(dataset, datasetIndex) {
-                       if (chart.isDatasetVisible(datasetIndex)) {
-                               var meta = chart.getDatasetMeta(datasetIndex);
-
-                               helpers.each(dataset.data, function(rawValue, index) {
-                                       var value = +me.getRightValue(rawValue);
-                                       if (isNaN(value) || meta.data[index].hidden) {
-                                               return;
-                                       }
-
-                                       min = Math.min(value, min);
-                                       max = Math.max(value, max);
-                               });
-                       }
-               });
+               var minmax = me._getMinMax(false);
+               var min = minmax.min;
+               var max = minmax.max;
 
-               me.min = (min === Number.POSITIVE_INFINITY ? 0 : min);
-               me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max);
+               me.min = helpers.isFinite(min) && !isNaN(min) ? min : 0;
+               me.max = helpers.isFinite(max) && !isNaN(max) ? max : 0;
 
                // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
                me.handleTickRangeOptions();
@@ -349,10 +333,6 @@ module.exports = LinearScaleBase.extend({
                });
        },
 
-       getLabelForIndex: function(index, datasetIndex) {
-               return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
-       },
-
        fit: function() {
                var me = this;
                var opts = me.options;
index 453d18023d2b97fc6d781485202a71b8d62fe721..36024c1d1647a353b3f6239b94d99f77c0955f43 100644 (file)
@@ -9,7 +9,6 @@ var resolve = helpers.options.resolve;
 var valueOrDefault = helpers.valueOrDefault;
 
 // Integer constants are from the ES6 spec.
-var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
 var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
 
 var INTERVALS = {
@@ -204,7 +203,7 @@ function parse(scale, input) {
        }
 
        var options = scale.options.time;
-       var value = toTimestamp(scale, scale.getRightValue(input));
+       var value = toTimestamp(scale, input);
        if (value === null) {
                return value;
        }
@@ -365,6 +364,89 @@ function ticksFromTimestamps(scale, values, majorUnit) {
        return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
 }
 
+
+function getDataTimestamps(scale) {
+       var timestamps = scale._cache.data || [];
+       var i, ilen, metas;
+
+       if (!timestamps.length) {
+               metas = scale._getMatchingVisibleMetas();
+               for (i = 0, ilen = metas.length; i < ilen; ++i) {
+                       timestamps = timestamps.concat(metas[i].controller._getAllParsedValues(scale));
+               }
+               timestamps = scale._cache.data = arrayUnique(timestamps).sort(sorter);
+       }
+       return timestamps;
+}
+
+function getLabelTimestamps(scale) {
+       var timestamps = scale._cache.labels || [];
+       var i, ilen, labels;
+
+       if (!timestamps.length) {
+               labels = scale._getLabels();
+               for (i = 0, ilen = labels.length; i < ilen; ++i) {
+                       timestamps.push(parse(scale, labels[i]));
+               }
+               timestamps = scale._cache.labels = arrayUnique(timestamps).sort(sorter);
+       }
+       return timestamps;
+}
+
+function getAllTimestamps(scale) {
+       var timestamps = scale._cache.all || [];
+
+       if (!timestamps.length) {
+               timestamps = getDataTimestamps(scale).concat(getLabelTimestamps(scale));
+               timestamps = scale._cache.all = arrayUnique(timestamps).sort(sorter);
+       }
+       return timestamps;
+}
+
+
+function getTimestampsForTicks(scale) {
+       var min = scale.min;
+       var max = scale.max;
+       var options = scale.options;
+       var capacity = scale.getLabelCapacity(min);
+       var source = options.ticks.source;
+       var timestamps;
+
+       if (source === 'data' || (source === 'auto' && options.distribution === 'series')) {
+               timestamps = getAllTimestamps(scale);
+       } else if (source === 'labels') {
+               timestamps = getLabelTimestamps(scale);
+       } else {
+               timestamps = generate(scale, min, max, capacity, options);
+       }
+
+       return timestamps;
+}
+
+/**
+ * Return subset of `timestamps` between `min` and `max`.
+ * Timestamps are assumend to be in sorted order.
+ * @param {int[]} timestamps - array of timestamps
+ * @param {int} min - min value (timestamp)
+ * @param {int} max - max value (timestamp)
+ */
+function filterBetween(timestamps, min, max) {
+       var start = 0;
+       var end = timestamps.length - 1;
+
+       while (start < end && timestamps[start] < min) {
+               start++;
+       }
+       while (end > start && timestamps[end] > max) {
+               end--;
+       }
+       end++; // slice does not include last element
+
+       return start > 0 || end < timestamps.length
+               ? timestamps.slice(start, end)
+               : timestamps;
+}
+
 var defaultConfig = {
        position: 'bottom',
 
@@ -415,158 +497,107 @@ var defaultConfig = {
 };
 
 module.exports = Scale.extend({
+       _parse: function(raw, index) { // eslint-disable-line no-unused-vars
+               if (raw === undefined) {
+                       return NaN;
+               }
+               return toTimestamp(this, raw);
+       },
+
+       _parseObject: function(obj, axis, index) {
+               if (obj && obj.t) {
+                       return this._parse(obj.t, index);
+               }
+               if (obj[axis] !== undefined) {
+                       return this._parse(obj[axis], index);
+               }
+               return null;
+       },
 
-       update: function() {
+       _invalidateCaches: function() {
+               this._cache = {};
+       },
+
+       initialize: function() {
                var me = this;
                var options = me.options;
                var time = options.time || (options.time = {});
                var adapter = me._adapter = new adapters._date(options.adapters.date);
 
+
+               me._cache = {};
+
                // Backward compatibility: before introducing adapter, `displayFormats` was
                // supposed to contain *all* unit/string pairs but this can't be resolved
                // when loading the scale (adapters are loaded afterward), so let's populate
                // missing formats on update
-               helpers.mergeIf(time.displayFormats, adapter.formats());
 
-               return Scale.prototype.update.apply(me, arguments);
-       },
+               helpers.mergeIf(time.displayFormats, adapter.formats());
 
-       /**
-        * Allows data to be referenced via 't' attribute
-        */
-       getRightValue: function(rawValue) {
-               if (rawValue && rawValue.t !== undefined) {
-                       rawValue = rawValue.t;
-               }
-               return Scale.prototype.getRightValue.call(this, rawValue);
+               Scale.prototype.initialize.call(me);
        },
 
        determineDataLimits: function() {
                var me = this;
-               var chart = me.chart;
                var adapter = me._adapter;
                var options = me.options;
                var tickOpts = options.ticks;
                var unit = options.time.unit || 'day';
-               var min = MAX_INTEGER;
-               var max = MIN_INTEGER;
-               var timestamps = [];
-               var datasets = [];
-               var labels = [];
-               var i, j, ilen, jlen, data, timestamp, labelsAdded;
-               var dataLabels = me._getLabels();
-
-               for (i = 0, ilen = dataLabels.length; i < ilen; ++i) {
-                       labels.push(parse(me, dataLabels[i]));
+               var min = Number.POSITIVE_INFINITY;
+               var max = Number.NEGATIVE_INFINITY;
+               var minmax = me._getMinMax(false);
+               var i, ilen, labels;
+
+               min = Math.min(min, minmax.min);
+               max = Math.max(max, minmax.max);
+
+               labels = getLabelTimestamps(me);
+               for (i = 0, ilen = labels.length; i < ilen; ++i) {
+                       min = Math.min(min, labels[i]);
+                       max = Math.max(max, labels[i]);
                }
 
-               for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
-                       if (chart.isDatasetVisible(i)) {
-                               data = chart.data.datasets[i].data;
-
-                               // Let's consider that all data have the same format.
-                               if (helpers.isObject(data[0])) {
-                                       datasets[i] = [];
-
-                                       for (j = 0, jlen = data.length; j < jlen; ++j) {
-                                               timestamp = parse(me, data[j]);
-                                               timestamps.push(timestamp);
-                                               datasets[i][j] = timestamp;
-                                       }
-                               } else {
-                                       datasets[i] = labels.slice(0);
-                                       if (!labelsAdded) {
-                                               timestamps = timestamps.concat(labels);
-                                               labelsAdded = true;
-                                       }
-                               }
-                       } else {
-                               datasets[i] = [];
-                       }
-               }
-
-               if (labels.length) {
-                       min = Math.min(min, labels[0]);
-                       max = Math.max(max, labels[labels.length - 1]);
-               }
-
-               if (timestamps.length) {
-                       timestamps = ilen > 1 ? arrayUnique(timestamps).sort(sorter) : timestamps.sort(sorter);
-                       min = Math.min(min, timestamps[0]);
-                       max = Math.max(max, timestamps[timestamps.length - 1]);
-               }
+               min = helpers.isFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit);
+               max = helpers.isFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1;
 
                min = parse(me, tickOpts.min) || min;
                max = parse(me, tickOpts.max) || max;
 
-               // In case there is no valid min/max, set limits based on unit time option
-               min = min === MAX_INTEGER ? +adapter.startOf(Date.now(), unit) : min;
-               max = max === MIN_INTEGER ? +adapter.endOf(Date.now(), unit) + 1 : max;
-
                // Make sure that max is strictly higher than min (required by the lookup table)
                me.min = Math.min(min, max);
                me.max = Math.max(min + 1, max);
-
-               // PRIVATE
-               me._table = [];
-               me._timestamps = {
-                       data: timestamps,
-                       datasets: datasets,
-                       labels: labels
-               };
        },
 
        buildTicks: function() {
                var me = this;
-               var min = me.min;
-               var max = me.max;
                var options = me.options;
-               var tickOpts = options.ticks;
                var timeOpts = options.time;
-               var timestamps = me._timestamps;
-               var ticks = [];
-               var capacity = me.getLabelCapacity(min);
-               var source = tickOpts.source;
+               var tickOpts = options.ticks;
                var distribution = options.distribution;
-               var i, ilen, timestamp;
+               var ticks = [];
+               var min, max, timestamps;
 
-               if (source === 'data' || (source === 'auto' && distribution === 'series')) {
-                       timestamps = timestamps.data;
-               } else if (source === 'labels') {
-                       timestamps = timestamps.labels;
-               } else {
-                       timestamps = generate(me, min, max, capacity, options);
-               }
+               timestamps = getTimestampsForTicks(me);
 
                if (options.bounds === 'ticks' && timestamps.length) {
-                       min = timestamps[0];
-                       max = timestamps[timestamps.length - 1];
+                       me.min = parse(me, tickOpts.min) || timestamps[0];
+                       me.max = parse(me, tickOpts.max) || timestamps[timestamps.length - 1];
                }
 
-               // Enforce limits with user min/max options
-               min = parse(me, tickOpts.min) || min;
-               max = parse(me, tickOpts.max) || max;
-
-               // Remove ticks outside the min/max range
-               for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
-                       timestamp = timestamps[i];
-                       if (timestamp >= min && timestamp <= max) {
-                               ticks.push(timestamp);
-                       }
-               }
+               min = me.min;
+               max = me.max;
 
-               me.min = min;
-               me.max = max;
+               ticks = filterBetween(timestamps, min, max);
 
                // PRIVATE
                // determineUnitForFormatting relies on the number of ticks so we don't use it when
                // autoSkip is enabled because we don't yet know what the final number of ticks will be
                me._unit = timeOpts.unit || (tickOpts.autoSkip
-                       ? determineUnitForAutoTicks(timeOpts.minUnit, me.min, me.max, capacity)
+                       ? determineUnitForAutoTicks(timeOpts.minUnit, me.min, me.max, me.getLabelCapacity(min))
                        : determineUnitForFormatting(me, ticks.length, timeOpts.minUnit, me.min, me.max));
                me._majorUnit = !tickOpts.major.enabled || me._unit === 'year' ? undefined
                        : determineMajorUnit(me._unit);
-               me._table = buildLookupTable(me._timestamps.data, min, max, distribution);
+               me._table = buildLookupTable(getAllTimestamps(me), min, max, distribution);
                me._offsets = computeOffsets(me._table, ticks, min, max, options);
 
                if (tickOpts.reverse) {
@@ -576,24 +607,15 @@ module.exports = Scale.extend({
                return ticksFromTimestamps(me, ticks, me._majorUnit);
        },
 
-       getLabelForIndex: function(index, datasetIndex) {
+       getLabelForValue: function(value) {
                var me = this;
                var adapter = me._adapter;
-               var data = me.chart.data;
                var timeOpts = me.options.time;
-               var label = data.labels && index < data.labels.length ? data.labels[index] : '';
-               var value = data.datasets[datasetIndex].data[index];
 
-               if (helpers.isObject(value)) {
-                       label = me.getRightValue(value);
-               }
                if (timeOpts.tooltipFormat) {
-                       return adapter.format(toTimestamp(me, label), timeOpts.tooltipFormat);
-               }
-               if (typeof label === 'string') {
-                       return label;
+                       return adapter.format(value, timeOpts.tooltipFormat);
                }
-               return adapter.format(toTimestamp(me, label), timeOpts.displayFormats.datetime);
+               return adapter.format(value, timeOpts.displayFormats.datetime);
        },
 
        /**
@@ -640,20 +662,15 @@ module.exports = Scale.extend({
                return me.getPixelForDecimal((offsets.start + pos) * offsets.factor);
        },
 
-       getPixelForValue: function(value, index, datasetIndex) {
+       getPixelForValue: function(value) {
                var me = this;
-               var time = null;
-
-               if (index !== undefined && datasetIndex !== undefined) {
-                       time = me._timestamps.datasets[datasetIndex][index];
-               }
 
-               if (time === null) {
-                       time = parse(me, value);
+               if (typeof value !== 'number') {
+                       value = parse(me, value);
                }
 
-               if (time !== null) {
-                       return me.getPixelForOffset(time);
+               if (value !== null) {
+                       return me.getPixelForOffset(value);
                }
        },
 
diff --git a/test/fixtures/controller.bar/data/object.js b/test/fixtures/controller.bar/data/object.js
new file mode 100644 (file)
index 0000000..f5b6ac2
--- /dev/null
@@ -0,0 +1,32 @@
+module.exports = {
+       config: {
+               type: 'bar',
+               data: {
+                       labels: ['a', 'b', 'c'],
+                       datasets: [
+                               {
+                                       data: {a: 10, b: 2, c: -5},
+                                       backgroundColor: '#ff0000'
+                               },
+                               {
+                                       data: {a: 8, b: 12, c: 5},
+                                       backgroundColor: '#00ff00'
+                               }
+                       ]
+               },
+               options: {
+                       legend: false,
+                       title: false,
+                       scales: {
+                               xAxes: [{display: false}],
+                               yAxes: [{display: false}]
+                       }
+               }
+       },
+       options: {
+               canvas: {
+                       height: 256,
+                       width: 512
+               }
+       }
+};
diff --git a/test/fixtures/controller.bar/data/object.png b/test/fixtures/controller.bar/data/object.png
new file mode 100644 (file)
index 0000000..c705448
Binary files /dev/null and b/test/fixtures/controller.bar/data/object.png differ
index 5df57310985aac9a184543beed66eb856ac5c229..c3066fd6812b3ece9fe2526b933a1e6cc5e1fef1 100644 (file)
Binary files a/test/fixtures/controller.bar/floatBar/float-bar-stacked-horizontal.png and b/test/fixtures/controller.bar/floatBar/float-bar-stacked-horizontal.png differ
index c9c02ea2f7b4136a9107bebc0b3512fcce66731a..5da257bcd43fdbec720bbd0c36c92ebb67919fb2 100644 (file)
Binary files a/test/fixtures/controller.bar/floatBar/float-bar-stacked.png and b/test/fixtures/controller.bar/floatBar/float-bar-stacked.png differ
diff --git a/test/fixtures/controller.bubble/radius-data.js b/test/fixtures/controller.bubble/radius-data.js
new file mode 100644 (file)
index 0000000..0c90917
--- /dev/null
@@ -0,0 +1,47 @@
+module.exports = {
+       config: {
+               type: 'bubble',
+               data: {
+                       datasets: [{
+                               data: [
+                                       {x: 0, y: 5, r: 1},
+                                       {x: 1, y: 4, r: 2},
+                                       {x: 2, y: 3, r: 6},
+                                       {x: 3, y: 2},
+                                       {x: 4, y: 1, r: 2},
+                                       {x: 5, y: 0, r: NaN},
+                                       {x: 6, y: -1, r: undefined},
+                                       {x: 7, y: -2, r: null},
+                                       {x: 8, y: -3, r: '4'},
+                                       {x: 9, y: -4, r: '4px'},
+                               ]
+                       }]
+               },
+               options: {
+                       legend: false,
+                       title: false,
+                       scales: {
+                               xAxes: [{display: false}],
+                               yAxes: [{display: false}]
+                       },
+                       elements: {
+                               point: {
+                                       backgroundColor: '#444',
+                                       radius: 10
+                               }
+                       },
+                       layout: {
+                               padding: {
+                                       left: 24,
+                                       right: 24
+                               }
+                       }
+               }
+       },
+       options: {
+               canvas: {
+                       height: 128,
+                       width: 256
+               }
+       }
+};
diff --git a/test/fixtures/controller.bubble/radius-data.png b/test/fixtures/controller.bubble/radius-data.png
new file mode 100644 (file)
index 0000000..ac819c2
Binary files /dev/null and b/test/fixtures/controller.bubble/radius-data.png differ
index 337279608597af597df21dadfbb571a9e9732e4e..c8b5957cd3117c99e4f7d930d5c9dfd04898d245 100644 (file)
@@ -192,7 +192,7 @@ describe('Category scale tests', function() {
 
                var scale = chart.scales.xScale0;
 
-               expect(scale.getLabelForIndex(1, 0)).toBe('tick2');
+               expect(scale.getLabelForValue(1)).toBe('tick2');
        });
 
        it('Should get the correct pixel for a value when horizontal', function() {
@@ -222,19 +222,19 @@ describe('Category scale tests', function() {
                });
 
                var xScale = chart.scales.xScale0;
-               expect(xScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(23 + 6); // plus lineHeight
+               expect(xScale.getPixelForValue(0)).toBeCloseToPixel(23 + 6); // plus lineHeight
                expect(xScale.getValueForPixel(23)).toBe(0);
 
-               expect(xScale.getPixelForValue(0, 4, 0)).toBeCloseToPixel(487);
+               expect(xScale.getPixelForValue(4)).toBeCloseToPixel(487);
                expect(xScale.getValueForPixel(487)).toBe(4);
 
                xScale.options.offset = true;
                chart.update();
 
-               expect(xScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(71 + 6); // plus lineHeight
+               expect(xScale.getPixelForValue(0)).toBeCloseToPixel(71 + 6); // plus lineHeight
                expect(xScale.getValueForPixel(69)).toBe(0);
 
-               expect(xScale.getPixelForValue(0, 4, 0)).toBeCloseToPixel(461);
+               expect(xScale.getPixelForValue(4)).toBeCloseToPixel(461);
                expect(xScale.getValueForPixel(417)).toBe(4);
        });
 
@@ -265,8 +265,7 @@ describe('Category scale tests', function() {
                });
 
                var xScale = chart.scales.xScale0;
-               expect(xScale.getPixelForValue('tick_1', 0, 0)).toBeCloseToPixel(23 + 6); // plus lineHeight
-               expect(xScale.getPixelForValue('tick_1', 1, 0)).toBeCloseToPixel(143);
+               expect(xScale.getPixelForValue('tick1')).toBeCloseToPixel(23 + 6); // plus lineHeight
        });
 
        it('Should get the correct pixel for a value when horizontal and zoomed', function() {
@@ -300,14 +299,14 @@ describe('Category scale tests', function() {
                });
 
                var xScale = chart.scales.xScale0;
-               expect(xScale.getPixelForValue(0, 1, 0)).toBeCloseToPixel(23 + 6); // plus lineHeight
-               expect(xScale.getPixelForValue(0, 3, 0)).toBeCloseToPixel(496);
+               expect(xScale.getPixelForValue(1)).toBeCloseToPixel(23 + 6); // plus lineHeight
+               expect(xScale.getPixelForValue(3)).toBeCloseToPixel(496);
 
                xScale.options.offset = true;
                chart.update();
 
-               expect(xScale.getPixelForValue(0, 1, 0)).toBeCloseToPixel(103 + 6); // plus lineHeight
-               expect(xScale.getPixelForValue(0, 3, 0)).toBeCloseToPixel(429);
+               expect(xScale.getPixelForValue(1)).toBeCloseToPixel(103 + 6); // plus lineHeight
+               expect(xScale.getPixelForValue(3)).toBeCloseToPixel(429);
        });
 
        it('should get the correct pixel for a value when vertical', function() {
@@ -339,20 +338,20 @@ describe('Category scale tests', function() {
                });
 
                var yScale = chart.scales.yScale0;
-               expect(yScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(32);
-               expect(yScale.getValueForPixel(32)).toBe(0);
+               expect(yScale.getPixelForValue(0)).toBeCloseToPixel(32);
+               expect(yScale.getValueForPixel(257)).toBe(2);
 
-               expect(yScale.getPixelForValue(0, 4, 0)).toBeCloseToPixel(484);
-               expect(yScale.getValueForPixel(484)).toBe(4);
+               expect(yScale.getPixelForValue(4)).toBeCloseToPixel(481);
+               expect(yScale.getValueForPixel(144)).toBe(1);
 
                yScale.options.offset = true;
                chart.update();
 
-               expect(yScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(77);
-               expect(yScale.getValueForPixel(77)).toBe(0);
+               expect(yScale.getPixelForValue(0)).toBeCloseToPixel(77);
+               expect(yScale.getValueForPixel(256)).toBe(2);
 
-               expect(yScale.getPixelForValue(0, 4, 0)).toBeCloseToPixel(437);
-               expect(yScale.getValueForPixel(437)).toBe(4);
+               expect(yScale.getPixelForValue(4)).toBeCloseToPixel(436);
+               expect(yScale.getValueForPixel(167)).toBe(1);
        });
 
        it('should get the correct pixel for a value when vertical and zoomed', function() {
@@ -389,14 +388,14 @@ describe('Category scale tests', function() {
 
                var yScale = chart.scales.yScale0;
 
-               expect(yScale.getPixelForValue(0, 1, 0)).toBeCloseToPixel(32);
-               expect(yScale.getPixelForValue(0, 3, 0)).toBeCloseToPixel(484);
+               expect(yScale.getPixelForValue(1)).toBeCloseToPixel(32);
+               expect(yScale.getPixelForValue(3)).toBeCloseToPixel(482);
 
                yScale.options.offset = true;
                chart.update();
 
-               expect(yScale.getPixelForValue(0, 1, 0)).toBeCloseToPixel(107);
-               expect(yScale.getPixelForValue(0, 3, 0)).toBeCloseToPixel(407);
+               expect(yScale.getPixelForValue(1)).toBeCloseToPixel(107);
+               expect(yScale.getPixelForValue(3)).toBeCloseToPixel(407);
        });
 
        it('Should get the correct pixel for an object value when horizontal', function() {
@@ -432,9 +431,9 @@ describe('Category scale tests', function() {
                });
 
                var xScale = chart.scales.xScale0;
-               expect(xScale.getPixelForValue({x: 0, y: 10}, 0, 0)).toBeCloseToPixel(29);
-               expect(xScale.getPixelForValue({x: 3, y: 25}, 3, 0)).toBeCloseToPixel(506);
-               expect(xScale.getPixelForValue({x: 0, y: 78}, 4, 0)).toBeCloseToPixel(29);
+               expect(xScale.getPixelForValue(0)).toBeCloseToPixel(29);
+               expect(xScale.getPixelForValue(3)).toBeCloseToPixel(506);
+               expect(xScale.getPixelForValue(4)).toBeCloseToPixel(664);
        });
 
        it('Should get the correct pixel for an object value when vertical', function() {
@@ -472,8 +471,8 @@ describe('Category scale tests', function() {
                });
 
                var yScale = chart.scales.yScale0;
-               expect(yScale.getPixelForValue({x: 0, y: 2}, 0, 0)).toBeCloseToPixel(257);
-               expect(yScale.getPixelForValue({x: 0, y: 1}, 4, 0)).toBeCloseToPixel(144);
+               expect(yScale.getPixelForValue(0)).toBeCloseToPixel(32);
+               expect(yScale.getPixelForValue(4)).toBeCloseToPixel(481);
        });
 
        it('Should get the correct pixel for an object value in a bar chart', function() {
@@ -509,9 +508,9 @@ describe('Category scale tests', function() {
                });
 
                var xScale = chart.scales.xScale0;
-               expect(xScale.getPixelForValue(null, 0, 0)).toBeCloseToPixel(89);
-               expect(xScale.getPixelForValue(null, 3, 0)).toBeCloseToPixel(449);
-               expect(xScale.getPixelForValue(null, 4, 0)).toBeCloseToPixel(89);
+               expect(xScale.getPixelForValue(0)).toBeCloseToPixel(89);
+               expect(xScale.getPixelForValue(3)).toBeCloseToPixel(449);
+               expect(xScale.getPixelForValue(4)).toBeCloseToPixel(569);
        });
 
        it('Should get the correct pixel for an object value in a horizontal bar chart', function() {
@@ -547,8 +546,8 @@ describe('Category scale tests', function() {
                });
 
                var yScale = chart.scales.yScale0;
-               expect(yScale.getPixelForValue(null, 0, 0)).toBeCloseToPixel(88);
-               expect(yScale.getPixelForValue(null, 3, 0)).toBeCloseToPixel(426);
-               expect(yScale.getPixelForValue(null, 4, 0)).toBeCloseToPixel(88);
+               expect(yScale.getPixelForValue(0)).toBeCloseToPixel(88);
+               expect(yScale.getPixelForValue(3)).toBeCloseToPixel(426);
+               expect(yScale.getPixelForValue(4)).toBeCloseToPixel(538);
        });
 });
index 79d7fcd7f706d052b12bd80e6287f1e9b66410f3..2c185178360e01f2c6e251736c554040ed53bd7a 100644 (file)
@@ -323,7 +323,7 @@ describe('Linear Scale', function() {
                });
                chart.update();
 
-               expect(chart.scales.yScale0.getLabelForIndex(3, 0)).toBe(7);
+               expect(chart.scales.yScale0.getLabelForValue(7)).toBe(7);
        });
 
        it('Should correctly determine the min and max data values when stacked mode is turned on', function() {
@@ -857,18 +857,18 @@ describe('Linear Scale', function() {
                });
 
                var xScale = chart.scales.xScale0;
-               expect(xScale.getPixelForValue(1, 0, 0)).toBeCloseToPixel(501); // right - paddingRight
-               expect(xScale.getPixelForValue(-1, 0, 0)).toBeCloseToPixel(31 + 6); // left + paddingLeft + lineSpace
-               expect(xScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(266 + 6 / 2); // halfway*/
+               expect(xScale.getPixelForValue(1)).toBeCloseToPixel(501); // right - paddingRight
+               expect(xScale.getPixelForValue(-1)).toBeCloseToPixel(31 + 6); // left + paddingLeft + lineSpace
+               expect(xScale.getPixelForValue(0)).toBeCloseToPixel(266 + 6 / 2); // halfway*/
 
                expect(xScale.getValueForPixel(501)).toBeCloseTo(1, 1e-2);
                expect(xScale.getValueForPixel(31)).toBeCloseTo(-1, 1e-2);
                expect(xScale.getValueForPixel(266)).toBeCloseTo(0, 1e-2);
 
                var yScale = chart.scales.yScale0;
-               expect(yScale.getPixelForValue(1, 0, 0)).toBeCloseToPixel(32); // right - paddingRight
-               expect(yScale.getPixelForValue(-1, 0, 0)).toBeCloseToPixel(484); // left + paddingLeft
-               expect(yScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(258); // halfway*/
+               expect(yScale.getPixelForValue(1)).toBeCloseToPixel(32); // right - paddingRight
+               expect(yScale.getPixelForValue(-1)).toBeCloseToPixel(484); // left + paddingLeft
+               expect(yScale.getPixelForValue(0)).toBeCloseToPixel(258); // halfway*/
 
                expect(yScale.getValueForPixel(32)).toBeCloseTo(1, 1e-2);
                expect(yScale.getValueForPixel(484)).toBeCloseTo(-1, 1e-2);
index f8f1d9cea007ea5ee6d80d1b5bb177107d3c07fc..fa0dc3caeeb932f879523f7e2c066f2097b0e858 100644 (file)
@@ -747,7 +747,7 @@ describe('Logarithmic Scale tests', function() {
                        }
                });
 
-               expect(chart.scales.yScale0.getLabelForIndex(0, 2)).toBe(150);
+               expect(chart.scales.yScale0.getLabelForValue(150)).toBe(150);
        });
 
        describe('when', function() {
@@ -884,8 +884,8 @@ describe('Logarithmic Scale tests', function() {
                                        var start = chart.chartArea[chartStart];
                                        var end = chart.chartArea[chartEnd];
 
-                                       expect(scale.getPixelForValue(firstTick, 0, 0)).toBeCloseToPixel(start);
-                                       expect(scale.getPixelForValue(lastTick, 0, 0)).toBeCloseToPixel(end);
+                                       expect(scale.getPixelForValue(firstTick)).toBeCloseToPixel(start);
+                                       expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end);
 
                                        expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4);
                                        expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4);
@@ -897,8 +897,8 @@ describe('Logarithmic Scale tests', function() {
                                        start = chart.chartArea[chartEnd];
                                        end = chart.chartArea[chartStart];
 
-                                       expect(scale.getPixelForValue(firstTick, 0, 0)).toBeCloseToPixel(start);
-                                       expect(scale.getPixelForValue(lastTick, 0, 0)).toBeCloseToPixel(end);
+                                       expect(scale.getPixelForValue(firstTick)).toBeCloseToPixel(start);
+                                       expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end);
 
                                        expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4);
                                        expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4);
index ee83eb96cd5abf632e66cf7660a481c9efa77233..a474a5f22b34b8935f287fb3c6d7896c046fc4dd 100644 (file)
@@ -157,25 +157,21 @@ describe('Test the radial linear scale', function() {
        });
 
        it('Should ensure that the scale has a max and min that are not equal', function() {
-               var scaleID = 'myScale';
-
-               var mockData = {
-                       datasets: [],
-                       labels: []
-               };
-
-               var mockContext = window.createMockContext();
-               var Constructor = Chart.scaleService.getScaleConstructor('radialLinear');
-               var scale = new Constructor({
-                       ctx: mockContext,
-                       options: Chart.scaleService.getScaleDefaults('radialLinear'), // use default config for scale
-                       chart: {
-                               data: mockData
+               var chart = window.acquireChart({
+                       type: 'radar',
+                       data: {
+                               datasets: [],
+                               labels: []
                        },
-                       id: scaleID,
+                       options: {
+                               scale: {
+                                       id: 'myScale'
+                               }
+                       }
                });
 
-               scale.update(200, 300);
+               var scale = chart.scales.myScale;
+
                expect(scale.min).toBe(-1);
                expect(scale.max).toBe(1);
        });
@@ -428,7 +424,7 @@ describe('Test the radial linear scale', function() {
                                }
                        }
                });
-               expect(chart.scale.getLabelForIndex(1, 0)).toBe(5);
+               expect(chart.scale.getLabelForValue(5)).toBe(5);
        });
 
        it('should get the correct distance from the center point', function() {
index 604bc9307bf215ecb73090bd62f3b0f41babbc8a..8f84ad0f55498dee2b69cd12dcf11aa354469ac0 100755 (executable)
@@ -1,24 +1,25 @@
 // Time scale tests
 describe('Time scale tests', function() {
        function createScale(data, options, dimensions) {
-               var scaleID = 'myScale';
-               var mockContext = window.createMockContext();
-               var Constructor = Chart.scaleService.getScaleConstructor('time');
                var width = (dimensions && dimensions.width) || 400;
                var height = (dimensions && dimensions.height) || 50;
-               var scale = new Constructor({
-                       ctx: mockContext,
-                       options: options,
-                       chart: {
-                               data: data,
-                               width: width,
-                               height: height
-                       },
-                       id: scaleID
-               });
 
-               scale.update(width, height);
-               return scale;
+               options = options || {};
+               options.type = 'time';
+               options.id = 'xScale0';
+
+               var chart = window.acquireChart({
+                       type: 'line',
+                       data: data,
+                       options: {
+                               scales: {
+                                       xAxes: [options]
+                               }
+                       }
+               }, {canvas: {width: width, height: height}});
+
+
+               return chart.scales.xScale0;
        }
 
        function getLabels(scale) {
@@ -538,7 +539,7 @@ describe('Time scale tests', function() {
                                                xAxes: [{
                                                        id: 'xScale0',
                                                        type: 'time',
-                                                       position: 'bottom'
+                                                       position: 'bottom',
                                                }],
                                        }
                                }
@@ -564,14 +565,14 @@ describe('Time scale tests', function() {
                        var lastPointOffsetMs = moment(chart.config.data.labels[chart.config.data.labels.length - 1]).valueOf() - scale.min;
                        var lastPointPixel = scale.left + lastPointOffsetMs / msPerPix;
 
-                       expect(scale.getPixelForValue('', 0, 0)).toBeCloseToPixel(firstPointPixel);
+                       expect(scale.getPixelForValue('2015-01-01T20:00:00')).toBeCloseToPixel(firstPointPixel);
                        expect(scale.getPixelForValue(chart.data.labels[0])).toBeCloseToPixel(firstPointPixel);
                        expect(scale.getValueForPixel(firstPointPixel)).toBeCloseToTime({
                                value: moment(chart.data.labels[0]),
                                unit: 'hour',
                        });
 
-                       expect(scale.getPixelForValue('', 6, 0)).toBeCloseToPixel(lastPointPixel);
+                       expect(scale.getPixelForValue('2015-01-10T12:00')).toBeCloseToPixel(lastPointPixel);
                        expect(scale.getValueForPixel(lastPointPixel)).toBeCloseToTime({
                                value: moment(chart.data.labels[6]),
                                unit: 'hour'
@@ -654,16 +655,21 @@ describe('Time scale tests', function() {
                                        xAxes: [{
                                                id: 'xScale0',
                                                type: 'time',
-                                               position: 'bottom'
+                                               position: 'bottom',
+                                               ticks: {
+                                                       source: 'labels',
+                                                       autoSkip: false
+                                               }
                                        }],
                                }
                        }
                });
 
                var xScale = chart.scales.xScale0;
-               expect(xScale.getLabelForIndex(0, 0)).toBeTruthy();
-               expect(xScale.getLabelForIndex(0, 0)).toBe('2015-01-01T20:00:00');
-               expect(xScale.getLabelForIndex(6, 0)).toBe('2015-01-10T12:00');
+               var controller = chart.getDatasetMeta(0).controller;
+               expect(xScale.getLabelForValue(controller._getParsed(0)[xScale.id])).toBeTruthy();
+               expect(xScale.getLabelForValue(controller._getParsed(0)[xScale.id])).toBe('Jan 1, 2015, 8:00:00 pm');
+               expect(xScale.getLabelForValue(xScale.getValueForPixel(xScale.getPixelForTick(6)))).toBe('Jan 10, 2015, 12:00:00 pm');
        });
 
        describe('when ticks.callback is specified', function() {
@@ -825,8 +831,10 @@ describe('Time scale tests', function() {
                });
 
                var xScale = chart.scales.xScale0;
-               expect(xScale.getLabelForIndex(0, 0)).toBeTruthy();
-               expect(xScale.getLabelForIndex(0, 0)).toBe('2015-01-01T20:00:00');
+               var controller = chart.getDatasetMeta(0).controller;
+               var value = controller._getParsed(0)[xScale.id];
+               expect(xScale.getLabelForValue(value)).toBeTruthy();
+               expect(xScale.getLabelForValue(value)).toBe('Jan 1, 2015, 8:00:00 pm');
        });
 
        it('should get the correct label for a timestamp', function() {
@@ -853,7 +861,8 @@ describe('Time scale tests', function() {
                });
 
                var xScale = chart.scales.xScale0;
-               var label = xScale.getLabelForIndex(0, 0);
+               var controller = chart.getDatasetMeta(0).controller;
+               var label = xScale.getLabelForValue(controller._getParsed(0)[xScale.id]);
                expect(label).toEqual('Jan 8, 2018, 5:14:23 am');
        });
 
@@ -879,9 +888,9 @@ describe('Time scale tests', function() {
                });
 
                var xScale = chart.scales.xScale0;
-               var pixel = xScale.getPixelForValue('', 0, 0);
+               var pixel = xScale.getPixelForValue('2016-05-27');
 
-               expect(xScale.getValueForPixel(pixel).valueOf()).toEqual(moment(chart.data.labels[0]).valueOf());
+               expect(xScale.getValueForPixel(pixel)).toEqual(moment(chart.data.labels[0]).valueOf());
        });
 
        it('does not create a negative width chart when hidden', function() {