me._sortedMetasets = metasets.slice(0).sort(compare2Level('order', 'index'));
}
+ /**
+ * @private
+ */
+ _removeUnreferencedMetasets() {
+ const me = this;
+ const datasets = me.data.datasets;
+ me._metasets.forEach((meta, index) => {
+ if (datasets.filter(x => x === meta._dataset).length === 0) {
+ me._destroyDatasetMeta(index);
+ }
+ });
+ }
+
buildOrUpdateControllers() {
const me = this;
const newControllers = [];
const datasets = me.data.datasets;
let i, ilen;
+ me._removeUnreferencedMetasets();
+
for (i = 0, ilen = datasets.length; i < ilen; i++) {
const dataset = datasets[i];
let meta = me.getDatasetMeta(i);
const me = this;
const dataset = me.data.datasets[datasetIndex];
const metasets = me._metasets;
- let meta = metasets.filter(x => x._dataset === dataset).pop();
+ let meta = metasets.filter(x => x && x._dataset === dataset).pop();
if (!meta) {
meta = metasets[datasetIndex] = {
});
}
+function clearStacks(meta, items) {
+ items = items || meta._parsed;
+ items.forEach((parsed) => {
+ delete parsed._stacks[meta.vScale.id][meta.index];
+ });
+}
+
const optionKeys = (optionNames) => isArray(optionNames) ? optionNames : Object.keys(optionNames);
const optionKey = (key, active) => active ? 'hover' + _capitalize(key) : key;
const isDirectUpdateMode = (mode) => mode === 'reset' || mode === 'none';
* @private
*/
_destroy() {
+ const meta = this._cachedMeta;
if (this._data) {
unlistenArrayEvents(this._data, this);
}
+ if (meta._stacked) {
+ clearStacks(meta);
+ }
}
/**
if (meta.stack !== dataset.stack) {
stackChanged = true;
// remove values from old stack
- meta._parsed.forEach((parsed) => {
- delete parsed._stacks[meta.vScale.id][meta.index];
- });
+ clearStacks(meta);
meta.stack = dataset.stack;
}
*/
_resyncElements() {
const me = this;
- const meta = me._cachedMeta;
- const numMeta = meta.data.length;
+ const numMeta = me._cachedMeta.data.length;
const numData = me._data.length;
if (numData > numMeta) {
me._insertElements(numMeta, numData - numMeta);
} else if (numData < numMeta) {
- meta.data.splice(numData, numMeta - numData);
- meta._parsed.splice(numData, numMeta - numData);
+ me._removeElements(numData, numMeta - numData);
}
// Re-parse the old elements (new elements are parsed in _insertElements)
me.parse(0, Math.min(numData, numMeta));
*/
_removeElements(start, count) {
const me = this;
+ const meta = me._cachedMeta;
if (me._parsing) {
- me._cachedMeta._parsed.splice(start, count);
+ const removed = meta._parsed.splice(start, count);
+ if (meta._stacked) {
+ clearStacks(meta, removed);
+ }
}
- me._cachedMeta.data.splice(start, count);
+ meta.data.splice(start, count);
}
});
});
+ it('should re-synchronize stacks when data is removed', function() {
+ var chart = acquireChart({
+ type: 'bar',
+ data: {
+ labels: ['a', 'b'],
+ datasets: [{
+ data: [1, 10],
+ stack: '1'
+ }, {
+ data: [2, 20],
+ stack: '2'
+ }, {
+ data: [3, 30],
+ stack: '1'
+ }]
+ }
+ });
+
+ expect(chart._stacks).toEqual({
+ 'x.y.1.bar': {
+ 0: {0: 1, 2: 3},
+ 1: {0: 10, 2: 30}
+ },
+ 'x.y.2.bar': {
+ 0: {1: 2},
+ 1: {1: 20}
+ }
+ });
+
+ chart.data.datasets[2].data = [4];
+ chart.update();
+
+ expect(chart._stacks).toEqual({
+ 'x.y.1.bar': {
+ 0: {0: 1, 2: 4},
+ 1: {0: 10}
+ },
+ 'x.y.2.bar': {
+ 0: {1: 2},
+ 1: {1: 20}
+ }
+ });
+ });
+
it('should cleanup attached properties when the reference changes or when the chart is destroyed', function() {
var data0 = [0, 1, 2, 3, 4, 5];
var data1 = [6, 7, 8];