]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Improve stacking performance and footprint (#6762)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Mon, 18 Nov 2019 23:51:58 +0000 (01:51 +0200)
committerEvert Timberg <evert.timberg+github@gmail.com>
Mon, 18 Nov 2019 23:51:58 +0000 (18:51 -0500)
* Improve stacking speed and footprint

* Better utilization of caches

* Chore

src/controllers/controller.bar.js
src/controllers/controller.bubble.js
src/controllers/controller.doughnut.js
src/controllers/controller.horizontalBar.js
src/controllers/controller.line.js
src/controllers/controller.polarArea.js
src/controllers/controller.radar.js
src/core/core.datasetController.js

index 8d7e01f71e3c5a0437184d7d2557715aa0d493ed..804746f52432af8afe4bdf8f23ba79d89f323363 100644 (file)
@@ -153,12 +153,12 @@ function parseFloatBar(arr, item, vScale, i) {
 }
 
 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;
+       const iScale = meta.iScale;
+       const vScale = meta.vScale;
+       const labels = iScale._getLabels();
+       const singleScale = iScale === vScale;
+       const parsed = [];
+       let i, ilen, item, entry;
 
        for (i = start, ilen = start + count; i < ilen; ++i) {
                entry = data[i];
@@ -219,11 +219,11 @@ module.exports = DatasetController.extend({
         * @private
         */
        _parseObjectData: function(meta, data, start, count) {
-               var iScale = this._getIndexScale();
-               var vScale = this._getValueScale();
-               var vProp = vScale._getAxis();
-               var parsed = [];
-               var i, ilen, item, obj, value;
+               const iScale = meta.iScale;
+               const vScale = meta.vScale;
+               const vProp = vScale._getAxis();
+               const parsed = [];
+               let i, ilen, item, obj, value;
                for (i = start, ilen = start + count; i < ilen; ++i) {
                        obj = data[i];
                        item = {};
@@ -244,8 +244,9 @@ module.exports = DatasetController.extend({
         */
        _getLabelAndValue: function(index) {
                const me = this;
-               const indexScale = me._getIndexScale();
-               const valueScale = me._getValueScale();
+               const meta = me._cachedMeta;
+               const indexScale = meta.iScale;
+               const valueScale = meta.vScale;
                const parsed = me._getParsed(index);
                const custom = parsed._custom;
                const value = custom
@@ -264,14 +265,14 @@ module.exports = DatasetController.extend({
 
                DatasetController.prototype.initialize.apply(me, arguments);
 
-               meta = me.getMeta();
+               meta = me._cachedMeta;
                meta.stack = me.getDataset().stack;
                meta.bar = true;
        },
 
        update: function(reset) {
                var me = this;
-               var rects = me.getMeta().data;
+               var rects = me._cachedMeta.data;
                var i, ilen;
 
                me._ruler = me.getRuler();
@@ -306,14 +307,15 @@ module.exports = DatasetController.extend({
         * @private
         */
        _updateElementGeometry: function(rectangle, index, reset, options) {
-               var me = this;
-               var model = rectangle._model;
-               var vscale = me._getValueScale();
-               var base = vscale.getBasePixel();
-               var horizontal = vscale.isHorizontal();
-               var ruler = me._ruler || me.getRuler();
-               var vpixels = me.calculateBarValuePixels(index, options);
-               var ipixels = me.calculateBarIndexPixels(index, ruler, options);
+               const me = this;
+               const meta = me._cachedMeta;
+               const model = rectangle._model;
+               const vScale = meta.vScale;
+               const base = vScale.getBasePixel();
+               const horizontal = vScale.isHorizontal();
+               const ruler = me._ruler || me.getRuler();
+               const vpixels = me.calculateBarValuePixels(index, options);
+               const ipixels = me.calculateBarIndexPixels(index, ruler, options);
 
                model.horizontal = horizontal;
                model.base = reset ? base : vpixels.base;
@@ -330,26 +332,27 @@ module.exports = DatasetController.extend({
         * @private
         */
        _getStacks: function(last) {
-               var me = this;
-               var scale = me._getIndexScale();
-               var metasets = scale._getMatchingVisibleMetas(me._type);
-               var stacked = scale.options.stacked;
-               var ilen = metasets.length;
-               var stacks = [];
-               var i, meta;
+               const me = this;
+               const meta = me._cachedMeta;
+               const iScale = meta.iScale;
+               const metasets = iScale._getMatchingVisibleMetas(me._type);
+               const stacked = iScale.options.stacked;
+               const ilen = metasets.length;
+               const stacks = [];
+               let i, item;
 
                for (i = 0; i < ilen; ++i) {
-                       meta = metasets[i];
+                       item = metasets[i];
                        // stacked   | meta.stack
                        //           | found | not found | undefined
                        // false     |   x   |     x     |     x
                        // true      |       |     x     |
                        // undefined |       |     x     |     x
-                       if (stacked === false || stacks.indexOf(meta.stack) === -1 ||
-                               (stacked === undefined && meta.stack === undefined)) {
-                               stacks.push(meta.stack);
+                       if (stacked === false || stacks.indexOf(item.stack) === -1 ||
+                               (stacked === undefined && item.stack === undefined)) {
+                               stacks.push(item.stack);
                        }
-                       if (meta.index === last) {
+                       if (item.index === last) {
                                break;
                        }
                }
@@ -387,21 +390,22 @@ module.exports = DatasetController.extend({
         * @private
         */
        getRuler: function() {
-               var me = this;
-               var scale = me._getIndexScale();
-               var pixels = [];
-               var i, ilen;
+               const me = this;
+               const meta = me._cachedMeta;
+               const iScale = meta.iScale;
+               const pixels = [];
+               let i, ilen;
 
-               for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
-                       pixels.push(scale.getPixelForValue(me._getParsed(i)[scale.id]));
+               for (i = 0, ilen = meta.data.length; i < ilen; ++i) {
+                       pixels.push(iScale.getPixelForValue(me._getParsed(i)[iScale.id]));
                }
 
                return {
-                       pixels: pixels,
-                       start: scale._startPixel,
-                       end: scale._endPixel,
+                       pixels,
+                       start: iScale._startPixel,
+                       end: iScale._endPixel,
                        stackCount: me.getStackCount(),
-                       scale: scale
+                       scale: iScale
                };
        },
 
@@ -410,15 +414,16 @@ module.exports = DatasetController.extend({
         * @private
         */
        calculateBarValuePixels: function(index, options) {
-               var me = this;
-               var valueScale = me._getValueScale();
-               var minBarLength = options.minBarLength;
-               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;
+               const me = this;
+               const meta = me._cachedMeta;
+               const vScale = meta.vScale;
+               const minBarLength = options.minBarLength;
+               const parsed = me._getParsed(index);
+               const custom = parsed._custom;
+               let value = parsed[vScale.id];
+               let start = 0;
+               let length = meta._stacked ? me._applyStack(vScale, parsed) : parsed[vScale.id];
+               let base, head, size;
 
                if (length !== value) {
                        start = length - value;
@@ -435,8 +440,8 @@ module.exports = DatasetController.extend({
                        start += value;
                }
 
-               base = valueScale.getPixelForValue(start);
-               head = valueScale.getPixelForValue(start + length);
+               base = vScale.getPixelForValue(start);
+               head = vScale.getPixelForValue(start + length);
                size = head - base;
 
                if (minBarLength !== undefined && Math.abs(size) < minBarLength) {
@@ -461,7 +466,7 @@ module.exports = DatasetController.extend({
                        ? computeFlexCategoryTraits(index, ruler, options)
                        : computeFitCategoryTraits(index, ruler, options);
 
-               var stackIndex = me.getStackIndex(me.index, me.getMeta().stack);
+               var stackIndex = me.getStackIndex(me.index, me._cachedMeta.stack);
                var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2);
                var size = Math.min(
                        valueOrDefault(options.maxBarThickness, Infinity),
@@ -476,17 +481,18 @@ module.exports = DatasetController.extend({
        },
 
        draw: function() {
-               var me = this;
-               var chart = me.chart;
-               var scale = me._getValueScale();
-               var rects = me.getMeta().data;
-               var ilen = rects.length;
-               var i = 0;
+               const me = this;
+               const chart = me.chart;
+               const meta = me._cachedMeta;
+               const vScale = meta.vScale;
+               const rects = meta.data;
+               const ilen = rects.length;
+               let i = 0;
 
                helpers.canvas.clipArea(chart.ctx, chart.chartArea);
 
                for (; i < ilen; ++i) {
-                       if (!isNaN(me._getParsed(i)[scale.id])) {
+                       if (!isNaN(me._getParsed(i)[vScale.id])) {
                                rects[i].draw();
                        }
                }
index 726f86b81ba624a33921667a0601acd241ea5a80..c1b58f8dd7309adeb1f5b64092653dd1890cb266 100644 (file)
@@ -59,9 +59,9 @@ module.exports = DatasetController.extend({
         * @private
         */
        _parseObjectData: function(meta, data, start, count) {
-               var xScale = this.getScaleForId(meta.xAxisID);
-               var yScale = this.getScaleForId(meta.yAxisID);
-               var parsed = [];
+               const xScale = meta.xScale;
+               const yScale = meta.yScale;
+               const parsed = [];
                var i, ilen, item, obj;
                for (i = start, ilen = start + count; i < ilen; ++i) {
                        obj = data[i];
@@ -95,8 +95,8 @@ module.exports = DatasetController.extend({
        _getLabelAndValue: function(index) {
                const me = this;
                const meta = me._cachedMeta;
-               const xScale = me.getScaleForId(meta.xAxisID);
-               const yScale = me.getScaleForId(meta.yAxisID);
+               const xScale = meta.xScale;
+               const yScale = meta.yScale;
                const parsed = me._getParsed(index);
                const x = xScale.getLabelForValue(parsed[xScale.id]);
                const y = yScale.getLabelForValue(parsed[yScale.id]);
@@ -112,9 +112,8 @@ module.exports = DatasetController.extend({
         * @protected
         */
        update: function(reset) {
-               var me = this;
-               var meta = me.getMeta();
-               var points = meta.data;
+               const me = this;
+               const points = me._cachedMeta.data;
 
                // Update Points
                helpers.each(points, function(point, index) {
@@ -126,14 +125,14 @@ module.exports = DatasetController.extend({
         * @protected
         */
        updateElement: function(point, index, reset) {
-               var me = this;
-               var meta = me.getMeta();
-               var xScale = me.getScaleForId(meta.xAxisID);
-               var yScale = me.getScaleForId(meta.yAxisID);
-               var options = me._resolveDataElementOptions(index);
-               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]);
+               const me = this;
+               const meta = me._cachedMeta;
+               const xScale = meta.xScale;
+               const yScale = meta.yScale;
+               const options = me._resolveDataElementOptions(index);
+               const parsed = !reset && me._getParsed(index);
+               const x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(parsed[xScale.id]);
+               const y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(parsed[yScale.id]);
 
                point._options = options;
                point._model = {
index e3bf0aa231508a08c7639828398e974a7f68b62a..b98331b76adf487c64161460446732448fae5e0c 100644 (file)
@@ -140,7 +140,7 @@ module.exports = DatasetController.extend({
         */
        _parse: function(start, count) {
                var data = this.getDataset().data;
-               var metaData = this.getMeta().data;
+               var metaData = this._cachedMeta.data;
                var i, ilen;
                for (i = start, ilen = start + count; i < ilen; ++i) {
                        metaData[i]._parsed = +data[i];
@@ -169,7 +169,7 @@ module.exports = DatasetController.extend({
                var ratioY = 1;
                var offsetX = 0;
                var offsetY = 0;
-               var meta = me.getMeta();
+               var meta = me._cachedMeta;
                var arcs = meta.data;
                var cutout = opts.cutoutPercentage / 100 || 0;
                var circumference = opts.circumference;
@@ -261,7 +261,7 @@ module.exports = DatasetController.extend({
                        if (index === 0) {
                                model.startAngle = opts.rotation;
                        } else {
-                               model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
+                               model.startAngle = me._cachedMeta.data[index - 1]._model.endAngle;
                        }
 
                        model.endAngle = model.startAngle + model.circumference;
@@ -271,7 +271,7 @@ module.exports = DatasetController.extend({
        },
 
        calculateTotal: function() {
-               var metaData = this.getMeta().data;
+               var metaData = this._cachedMeta.data;
                var total = 0;
                var value;
 
@@ -290,7 +290,7 @@ module.exports = DatasetController.extend({
        },
 
        calculateCircumference: function(value) {
-               var total = this.getMeta().total;
+               var total = this._cachedMeta.total;
                if (total > 0 && !isNaN(value)) {
                        return DOUBLE_PI * (Math.abs(value) / total);
                }
index 7b11bcc6627f68f7f4befbab37232d1abdb4cd21..6ee66000d0f96bcecf8c968312781cc001d289c8 100644 (file)
@@ -51,13 +51,13 @@ module.exports = BarController.extend({
         * @private
         */
        _getValueScaleId: function() {
-               return this.getMeta().xAxisID;
+               return this._cachedMeta.xAxisID;
        },
 
        /**
         * @private
         */
        _getIndexScaleId: function() {
-               return this.getMeta().yAxisID;
+               return this._cachedMeta.yAxisID;
        }
 });
index 7a89514fc3d8bb3562bcb699b024df3eddded274..0d46f369669ccc04472d22649187c884986bbcba 100644 (file)
@@ -69,7 +69,7 @@ module.exports = DatasetController.extend({
 
        update: function(reset) {
                var me = this;
-               var meta = me.getMeta();
+               var meta = me._cachedMeta;
                var line = meta.dataset;
                var points = meta.data || [];
                var options = me.chart.options;
@@ -77,9 +77,6 @@ module.exports = DatasetController.extend({
                var showLine = me._showLine = valueOrDefault(config.showLine, options.showLines);
                var i, ilen;
 
-               me._xScale = me.getScaleForId(meta.xAxisID);
-               me._yScale = me.getScaleForId(meta.yAxisID);
-
                // Update Line
                if (showLine) {
                        // Data
@@ -106,15 +103,15 @@ module.exports = DatasetController.extend({
        },
 
        updateElement: function(point, index, reset) {
-               var me = this;
-               var meta = me.getMeta();
-               var xScale = me._xScale;
-               var yScale = me._yScale;
-               var stacked = meta._stacked;
-               var parsed = me._getParsed(index);
-               var options = me._resolveDataElementOptions(index);
-               var x = xScale.getPixelForValue(parsed[xScale.id]);
-               var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(stacked ? me._applyStack(yScale, parsed) : parsed[yScale.id]);
+               const me = this;
+               const meta = me._cachedMeta;
+               const xScale = meta.xScale;
+               const yScale = meta.yScale;
+               const stacked = meta._stacked;
+               const parsed = me._getParsed(index);
+               const options = me._resolveDataElementOptions(index);
+               const x = xScale.getPixelForValue(parsed[xScale.id]);
+               const y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(stacked ? me._applyStack(yScale, parsed) : parsed[yScale.id]);
 
                // Utility
                point._options = options;
@@ -175,7 +172,7 @@ module.exports = DatasetController.extend({
        updateBezierControlPoints: function() {
                var me = this;
                var chart = me.chart;
-               var meta = me.getMeta();
+               var meta = me._cachedMeta;
                var lineModel = meta.dataset._model;
                var area = chart.chartArea;
                var points = meta.data || [];
@@ -230,7 +227,7 @@ module.exports = DatasetController.extend({
        draw: function() {
                var me = this;
                var chart = me.chart;
-               var meta = me.getMeta();
+               var meta = me._cachedMeta;
                var points = meta.data || [];
                var area = chart.chartArea;
                var i = 0;
index 4bd83c9d97ae6d7c512c308ceb16f4dbe5b70099..a2fd5a1a43b29e79896be006ddbdb810f0e1a52a 100644 (file)
@@ -136,7 +136,7 @@ module.exports = DatasetController.extend({
        update: function(reset) {
                var me = this;
                var dataset = me.getDataset();
-               var meta = me.getMeta();
+               var meta = me._cachedMeta;
                var start = me.chart.options.startAngle || 0;
                var starts = me._starts = [];
                var angles = me._angles = [];
@@ -219,7 +219,7 @@ module.exports = DatasetController.extend({
 
        countVisibleElements: function() {
                var dataset = this.getDataset();
-               var meta = this.getMeta();
+               var meta = this._cachedMeta;
                var count = 0;
 
                helpers.each(meta.data, function(element, index) {
@@ -256,9 +256,9 @@ module.exports = DatasetController.extend({
         */
        _computeAngle: function(index) {
                var me = this;
-               var count = this.getMeta().count;
+               var meta = me._cachedMeta;
+               var count = meta.count;
                var dataset = me.getDataset();
-               var meta = me.getMeta();
 
                if (isNaN(dataset.data[index]) || meta.data[index].hidden) {
                        return 0;
index 99857a17c24d6101ed67409969c911270ae3c3db..6b9d24468b709ee985326c2d8c61340eebf68738 100644 (file)
@@ -82,18 +82,18 @@ module.exports = DatasetController.extend({
         */
        _getLabelAndValue: function(index) {
                const me = this;
-               const scale = me._getValueScale();
+               const vScale = me._cachedMeta.vScale;
                const parsed = me._getParsed(index);
 
                return {
-                       label: scale._getLabels()[index],
-                       value: '' + scale.getLabelForValue(parsed[scale.id])
+                       label: vScale._getLabels()[index],
+                       value: '' + vScale.getLabelForValue(parsed[vScale.id])
                };
        },
 
        update: function(reset) {
                var me = this;
-               var meta = me.getMeta();
+               var meta = me._cachedMeta;
                var line = meta.dataset;
                var points = meta.data || [];
                var animationsDisabled = me.chart._animationsDisabled;
@@ -168,7 +168,7 @@ module.exports = DatasetController.extend({
 
        updateBezierControlPoints: function() {
                var me = this;
-               var meta = me.getMeta();
+               var meta = me._cachedMeta;
                var lineModel = meta.dataset._model;
                var area = me.chart.chartArea;
                var points = meta.data || [];
index 213c1a553c3aae7d4305fe75ea3ad3729796ff0c..79139bfd355e8840166b73fe324bc3f966bccbbc 100644 (file)
@@ -192,6 +192,45 @@ function getUserBounds(scale) {
                max: maxDefined ? max : Number.POSITIVE_INFINITY
        };
 }
+
+function getOrCreateStack(stacks, stackKey, indexValue) {
+       const subStack = stacks[stackKey] || (stacks[stackKey] = {});
+       return subStack[indexValue] || (subStack[indexValue] = {});
+}
+
+function updateStacks(controller, parsed) {
+       const chart = controller.chart;
+       const meta = controller._cachedMeta;
+       const stacks = chart._stacks || (chart._stacks = {}); // map structure is {stackKey: {datasetIndex: value}}
+       const xScale = meta.xScale;
+       const yScale = meta.yScale;
+       const xId = xScale.id;
+       const yId = yScale.id;
+       const xStacked = isStacked(xScale, meta);
+       const yStacked = isStacked(yScale, meta);
+       const xKey = yStacked && getStackKey(xScale, yScale, meta);
+       const yKey = xStacked && getStackKey(yScale, xScale, meta);
+       const ilen = parsed.length;
+       const datasetIndex = meta.index;
+       let stack;
+
+       for (let i = 0; i < ilen; ++i) {
+               const item = parsed[i];
+               const x = item[xId];
+               const y = item[yId];
+               const itemStacks = item._stacks || (item._stacks = {});
+
+               if (yStacked) {
+                       stack = itemStacks[yId] = getOrCreateStack(stacks, xKey, x);
+                       stack[datasetIndex] = y;
+               }
+               if (xStacked) {
+                       stack = itemStacks[xId] = getOrCreateStack(stacks, yKey, y);
+                       stack[datasetIndex] = x;
+               }
+       }
+}
+
 // Base class for all dataset controllers (line, bar, etc)
 var DatasetController = function(chart, datasetIndex) {
        this.initialize(chart, datasetIndex);
@@ -241,14 +280,14 @@ helpers.extend(DatasetController.prototype, {
        ],
 
        initialize: function(chart, datasetIndex) {
-               var me = this;
-               var meta;
+               const me = this;
+               let meta;
                me.chart = chart;
                me.index = datasetIndex;
                me._cachedMeta = meta = me.getMeta();
                me._type = meta.type;
                me.linkScales();
-               meta._stacked = isStacked(me._getValueScale(), meta);
+               meta._stacked = isStacked(meta.vScale, meta);
                me.addElements();
        },
 
@@ -257,12 +296,16 @@ helpers.extend(DatasetController.prototype, {
        },
 
        linkScales: function() {
-               var chart = this.chart;
-               var meta = this._cachedMeta;
-               var dataset = this.getDataset();
-
-               meta.xAxisID = dataset.xAxisID || getFirstScaleId(chart, 'x');
-               meta.yAxisID = dataset.yAxisID || getFirstScaleId(chart, 'y');
+               const me = this;
+               const chart = me.chart;
+               const meta = me._cachedMeta;
+               const dataset = me.getDataset();
+               const xid = meta.xAxisID = dataset.xAxisID || getFirstScaleId(chart, 'x');
+               const yid = meta.yAxisID = dataset.yAxisID || getFirstScaleId(chart, 'y');
+               meta.xScale = me.getScaleForId(xid);
+               meta.yScale = me.getScaleForId(yid);
+               meta.iScale = me._getIndexScale();
+               meta.vScale = me._getValueScale();
        },
 
        getDataset: function() {
@@ -309,9 +352,10 @@ helpers.extend(DatasetController.prototype, {
         * @private
         */
        _getOtherScale: function(scale) {
-               return scale.id === this._getIndexScaleId()
-                       ? this._getValueScale()
-                       : this._getIndexScale();
+               const meta = this._cachedMeta;
+               return scale === meta.iScale
+                       ? meta.vScale
+                       : meta.iScale;
        },
 
        reset: function() {
@@ -378,9 +422,9 @@ helpers.extend(DatasetController.prototype, {
         * @private
         */
        _labelCheck: function() {
-               var me = this;
-               var scale = me._getIndexScale();
-               var labels = scale ? scale._getLabels() : me.chart.data.labels;
+               const me = this;
+               const iScale = me._cachedMeta.iScale;
+               const labels = iScale ? iScale._getLabels() : me.chart.data.labels;
 
                if (me._labels === labels) {
                        return false;
@@ -407,14 +451,14 @@ helpers.extend(DatasetController.prototype, {
        },
 
        buildOrUpdateElements: function() {
-               var me = this;
-               var dataChanged = me._dataCheck();
-               var labelsChanged = me._labelCheck();
-               var scaleChanged = me._scaleCheck();
-               var meta = me._cachedMeta;
+               const me = this;
+               const dataChanged = me._dataCheck();
+               const labelsChanged = me._labelCheck();
+               const scaleChanged = me._scaleCheck();
+               const meta = me._cachedMeta;
 
                // make sure cached _stacked status is current
-               meta._stacked = isStacked(me._getValueScale(), meta);
+               meta._stacked = isStacked(meta.vScale, 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.
@@ -426,7 +470,7 @@ helpers.extend(DatasetController.prototype, {
         * @private
         */
        _configure: function() {
-               var me = this;
+               const me = this;
                me._config = helpers.merge({}, [
                        me.chart.options.datasets[me._type],
                        me.getDataset(),
@@ -444,20 +488,12 @@ helpers.extend(DatasetController.prototype, {
         */
        _parse: function(start, count) {
                const me = this;
-               const chart = me.chart;
                const meta = me._cachedMeta;
                const data = me._data;
-               const stacks = chart._stacks || (chart._stacks = {}); // map structure is {stackKey: {datasetIndex: value}}
-               const xScale = me._getIndexScale();
-               const yScale = me._getValueScale();
-               const xId = xScale.id;
-               const yId = yScale.id;
-               const xStacked = isStacked(xScale, meta);
-               const yStacked = isStacked(yScale, meta);
-               const xKey = yStacked && getStackKey(xScale, yScale, meta);
-               const yKey = xStacked && getStackKey(yScale, xScale, meta);
-               const stacked = xStacked || yStacked;
-               var i, ilen, parsed, stack, item, x, y;
+               const iScale = meta.iScale;
+               const vScale = meta.vScale;
+               const stacked = isStacked(iScale, meta) || isStacked(vScale, meta);
+               let i, ilen, parsed;
 
                if (helpers.isArray(data[start])) {
                        parsed = me._parseArrayData(meta, data, start, count);
@@ -467,34 +503,16 @@ helpers.extend(DatasetController.prototype, {
                        parsed = me._parsePrimitiveData(meta, data, start, count);
                }
 
-               function storeStack(stackKey, indexValue, scaleId, value) {
-                       stackKey += '.' + indexValue;
-                       item._stackKeys[scaleId] = stackKey;
-                       stack = stacks[stackKey] || (stacks[stackKey] = {});
-                       stack[meta.index] = value;
-               }
-
                for (i = 0, ilen = parsed.length; i < ilen; ++i) {
-                       item = parsed[i];
-                       meta.data[start + i]._parsed = item;
-
-                       if (stacked) {
-                               item._stackKeys = {};
-                               x = item[xId];
-                               y = item[yId];
-
-                               if (yStacked) {
-                                       storeStack(xKey, x, yId, y);
-                               }
-                               if (xStacked) {
-                                       storeStack(yKey, y, xId, x);
-                               }
-                       }
+                       meta.data[start + i]._parsed = parsed[i];
+               }
+               if (stacked) {
+                       updateStacks(me, parsed);
                }
 
-               xScale._invalidateCaches();
-               if (yScale !== xScale) {
-                       yScale._invalidateCaches();
+               iScale._invalidateCaches();
+               if (vScale !== iScale) {
+                       vScale._invalidateCaches();
                }
        },
 
@@ -510,12 +528,12 @@ helpers.extend(DatasetController.prototype, {
         * @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;
+               const iScale = meta.iScale;
+               const vScale = meta.vScale;
+               const labels = iScale._getLabels();
+               const singleScale = iScale === vScale;
+               const parsed = [];
+               let i, ilen, item;
 
                for (i = start, ilen = start + count; i < ilen; ++i) {
                        item = {};
@@ -538,10 +556,10 @@ helpers.extend(DatasetController.prototype, {
         * @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;
+               const xScale = meta.xScale;
+               const yScale = meta.yScale;
+               const parsed = [];
+               let i, ilen, item, arr;
                for (i = start, ilen = start + count; i < ilen; ++i) {
                        arr = data[i];
                        item = {};
@@ -564,10 +582,10 @@ helpers.extend(DatasetController.prototype, {
         * @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;
+               const xScale = meta.xScale;
+               const yScale = meta.yScale;
+               const parsed = [];
+               let i, ilen, item, obj;
                for (i = start, ilen = start + count; i < ilen; ++i) {
                        obj = data[i];
                        item = {};
@@ -582,7 +600,7 @@ helpers.extend(DatasetController.prototype, {
         * @private
         */
        _getParsed: function(index) {
-               var data = this._cachedMeta.data;
+               const data = this._cachedMeta.data;
                if (index < 0 || index >= data.length) {
                        return;
                }
@@ -593,12 +611,12 @@ helpers.extend(DatasetController.prototype, {
         * @private
         */
        _applyStack: function(scale, parsed) {
-               var chart = this.chart;
-               var meta = this._cachedMeta;
-               var value = parsed[scale.id];
-               var stack = {
+               const chart = this.chart;
+               const meta = this._cachedMeta;
+               const value = parsed[scale.id];
+               const stack = {
                        keys: getSortedDatasetIndices(chart, true),
-                       values: chart._stacks[parsed._stackKeys[scale.id]]
+                       values: parsed._stacks[scale.id]
                };
                return applyStack(stack, value, meta.index);
        },
@@ -607,17 +625,16 @@ helpers.extend(DatasetController.prototype, {
         * @private
         */
        _getMinMax: function(scale, canStack) {
-               var chart = this.chart;
-               var meta = this._cachedMeta;
-               var metaData = meta.data;
-               var ilen = metaData.length;
-               var stacks = chart._stacks || (chart._stacks = {});
-               var max = Number.NEGATIVE_INFINITY;
-               var stacked = canStack && meta._stacked;
-               var indices = getSortedDatasetIndices(chart, true);
-               var otherScale = this._getOtherScale(scale);
-               var {min: otherMin, max: otherMax} = getUserBounds(otherScale);
-               var i, item, value, parsed, stack, min, minPositive, otherValue;
+               const chart = this.chart;
+               const meta = this._cachedMeta;
+               const metaData = meta.data;
+               const ilen = metaData.length;
+               const stacked = canStack && meta._stacked;
+               const indices = getSortedDatasetIndices(chart, true);
+               const otherScale = this._getOtherScale(scale);
+               let max = Number.NEGATIVE_INFINITY;
+               let {min: otherMin, max: otherMax} = getUserBounds(otherScale);
+               let i, item, value, parsed, stack, min, minPositive, otherValue;
 
                min = minPositive = Number.POSITIVE_INFINITY;
 
@@ -633,7 +650,7 @@ helpers.extend(DatasetController.prototype, {
                        if (stacked) {
                                stack = {
                                        keys: indices,
-                                       values: stacks[parsed._stackKeys[scale.id]]
+                                       values: parsed._stacks[scale.id]
                                };
                                value = applyStack(stack, value, meta.index, true);
                        }
@@ -654,10 +671,10 @@ helpers.extend(DatasetController.prototype, {
         * @private
         */
        _getAllParsedValues: function(scale) {
-               var meta = this._cachedMeta;
-               var metaData = meta.data;
-               var values = [];
-               var i, ilen, value;
+               const meta = this._cachedMeta;
+               const metaData = meta.data;
+               const values = [];
+               let i, ilen, value;
 
                for (i = 0, ilen = metaData.length; i < ilen; ++i) {
                        value = metaData[i]._parsed[scale.id];
@@ -672,13 +689,14 @@ helpers.extend(DatasetController.prototype, {
         * @private
         */
        _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;
+               const me = this;
+               const meta = me._cachedMeta;
+               const iScale = meta.iScale;
+               const vScale = meta.vScale;
+               const cache = me._scaleStacked = {};
+               if (iScale && vScale) {
+                       cache[iScale.id] = iScale.options.stacked;
+                       cache[vScale.id] = vScale.options.stacked;
                }
        },
 
@@ -686,15 +704,16 @@ helpers.extend(DatasetController.prototype, {
         * @private
         */
        _scaleCheck: function() {
-               var me = this;
-               var indexScale = me._getIndexScale();
-               var valueScale = me._getValueScale();
-               var cache = me._scaleStacked;
+               const me = this;
+               const meta = me._cachedMeta;
+               const iScale = meta.iScale;
+               const vScale = meta.vScale;
+               const cache = me._scaleStacked;
                return !cache ||
-                       !indexScale ||
-                       !valueScale ||
-                       cache[indexScale.id] !== indexScale.options.stacked ||
-                       cache[valueScale.id] !== valueScale.options.stacked;
+                       !iScale ||
+                       !vScale ||
+                       cache[iScale.id] !== iScale.options.stacked ||
+                       cache[vScale.id] !== vScale.options.stacked;
        },
 
        /**
@@ -709,12 +728,13 @@ helpers.extend(DatasetController.prototype, {
         */
        _getLabelAndValue: function(index) {
                const me = this;
-               const indexScale = me._getIndexScale();
-               const valueScale = me._getValueScale();
+               const meta = me._cachedMeta;
+               const iScale = meta.iScale;
+               const vScale = meta.vScale;
                const parsed = me._getParsed(index);
                return {
-                       label: indexScale ? '' + indexScale.getLabelForValue(parsed[indexScale.id]) : '',
-                       value: valueScale ? '' + valueScale.getLabelForValue(parsed[valueScale.id]) : ''
+                       label: iScale ? '' + iScale.getLabelForValue(parsed[iScale.id]) : '',
+                       value: vScale ? '' + vScale.getLabelForValue(parsed[vScale.id]) : ''
                };
        },
 
@@ -722,21 +742,22 @@ helpers.extend(DatasetController.prototype, {
         * @private
         */
        _update: function(reset) {
-               var me = this;
+               const me = this;
+               const meta = me._cachedMeta;
                me._configure();
                me._cachedDataOpts = null;
                me.update(reset);
-               me._cachedMeta._clip = toClip(helpers.valueOrDefault(me._config.clip, defaultClip(me._xScale, me._yScale, me._getMaxOverflow())));
+               meta._clip = toClip(helpers.valueOrDefault(me._config.clip, defaultClip(meta.xScale, meta.yScale, me._getMaxOverflow())));
                me._cacheScaleStackStatus();
        },
 
        update: helpers.noop,
 
        transition: function(easingValue) {
-               var meta = this._cachedMeta;
-               var elements = meta.data || [];
-               var ilen = elements.length;
-               var i = 0;
+               const meta = this._cachedMeta;
+               const elements = meta.data || [];
+               const ilen = elements.length;
+               let i = 0;
 
                for (; i < ilen; ++i) {
                        elements[i].transition(easingValue);
@@ -748,10 +769,10 @@ helpers.extend(DatasetController.prototype, {
        },
 
        draw: function() {
-               var meta = this._cachedMeta;
-               var elements = meta.data || [];
-               var ilen = elements.length;
-               var i = 0;
+               const meta = this._cachedMeta;
+               const elements = meta.data || [];
+               const ilen = elements.length;
+               let i = 0;
 
                if (meta.dataset) {
                        meta.dataset.draw();
@@ -769,10 +790,10 @@ helpers.extend(DatasetController.prototype, {
         * @return {IStyleInterface} style object
         */
        getStyle: function(index) {
-               var me = this;
-               var meta = me._cachedMeta;
-               var dataset = meta.dataset;
-               var style;
+               const me = this;
+               const meta = me._cachedMeta;
+               const dataset = meta.dataset;
+               let style;
 
                me._configure();
                if (dataset && index === undefined) {
@@ -793,21 +814,19 @@ helpers.extend(DatasetController.prototype, {
         * @private
         */
        _resolveDatasetElementOptions: function(active) {
-               var me = this;
-               var chart = me.chart;
-               var datasetOpts = me._config;
-               var options = chart.options.elements[me.datasetElementType.prototype._type] || {};
-               var elementOptions = me._datasetElementOptions;
-               var values = {};
-               var i, ilen, key, readKey;
-
-               // Scriptable options
-               var context = {
+               const me = this;
+               const chart = me.chart;
+               const datasetOpts = me._config;
+               const options = chart.options.elements[me.datasetElementType.prototype._type] || {};
+               const elementOptions = me._datasetElementOptions;
+               const values = {};
+               const context = {
                        chart,
                        dataset: me.getDataset(),
                        datasetIndex: me.index,
                        active
                };
+               let i, ilen, key, readKey;
 
                for (i = 0, ilen = elementOptions.length; i < ilen; ++i) {
                        key = elementOptions[i];
@@ -825,29 +844,24 @@ helpers.extend(DatasetController.prototype, {
         * @private
         */
        _resolveDataElementOptions: function(index) {
-               var me = this;
-               var cached = me._cachedDataOpts;
+               const me = this;
+               const cached = me._cachedDataOpts;
                if (cached) {
                        return cached;
                }
-               var chart = me.chart;
-               var datasetOpts = me._config;
-               var options = chart.options.elements[me.dataElementType.prototype._type] || {};
-               var elementOptions = me._dataElementOptions;
-               var values = {};
-
-               // Scriptable options
-               var context = {
+               const chart = me.chart;
+               const datasetOpts = me._config;
+               const options = chart.options.elements[me.dataElementType.prototype._type] || {};
+               const elementOptions = me._dataElementOptions;
+               const values = {};
+               const context = {
                        chart: chart,
                        dataIndex: index,
                        dataset: me.getDataset(),
                        datasetIndex: me.index
                };
-
-               // `resolve` sets cacheable to `false` if any option is indexed or scripted
-               var info = {cacheable: true};
-
-               var keys, i, ilen, key;
+               const info = {cacheable: true};
+               let keys, i, ilen, key;
 
                if (helpers.isArray(elementOptions)) {
                        for (i = 0, ilen = elementOptions.length; i < ilen; ++i) {
@@ -882,9 +896,9 @@ helpers.extend(DatasetController.prototype, {
        },
 
        setHoverStyle: function(element, datasetIndex, index) {
-               var dataset = this.chart.data.datasets[datasetIndex];
-               var model = element._model;
-               var getHoverColor = helpers.getHoverColor;
+               const dataset = this.chart.data.datasets[datasetIndex];
+               const model = element._model;
+               const getHoverColor = helpers.getHoverColor;
 
                element.$previousStyle = {
                        backgroundColor: model.backgroundColor,
@@ -901,7 +915,7 @@ helpers.extend(DatasetController.prototype, {
         * @private
         */
        _removeDatasetHoverStyle: function() {
-               var element = this.getMeta().dataset;
+               const element = this._cachedMeta.dataset;
 
                if (element) {
                        this.removeHoverStyle(element);
@@ -912,9 +926,9 @@ helpers.extend(DatasetController.prototype, {
         * @private
         */
        _setDatasetHoverStyle: function() {
-               var element = this.getMeta().dataset;
-               var prev = {};
-               var i, ilen, key, keys, hoverOptions, model;
+               const element = this._cachedMeta.dataset;
+               const prev = {};
+               let i, ilen, key, keys, hoverOptions, model;
 
                if (!element) {
                        return;
@@ -937,10 +951,10 @@ helpers.extend(DatasetController.prototype, {
         * @private
         */
        resyncElements: function(changed) {
-               var me = this;
-               var meta = me._cachedMeta;
-               var numMeta = meta.data.length;
-               var numData = me._data.length;
+               const me = this;
+               const meta = me._cachedMeta;
+               const numMeta = meta.data.length;
+               const numData = me._data.length;
 
                if (numData > numMeta) {
                        me.insertElements(numMeta, numData - numMeta);
@@ -958,7 +972,7 @@ helpers.extend(DatasetController.prototype, {
        insertElements: function(start, count) {
                const me = this;
                const elements = [];
-               var i;
+               let i;
                for (i = start; i < start + count; ++i) {
                        elements.push(me.createElement(me.dataElementType));
                }
@@ -973,7 +987,7 @@ helpers.extend(DatasetController.prototype, {
         * @private
         */
        onDataPush: function() {
-               var count = arguments.length;
+               const count = arguments.length;
                this.insertElements(this.getDataset().data.length - count, count);
        },