Skip to content

Commit 3b46ca0

Browse files
committed
fix: reattach chart after a number of addpoint calls
1 parent 7cc926f commit 3b46ca0

File tree

2 files changed

+50
-1
lines changed

2 files changed

+50
-1
lines changed

packages/charts/src/vaadin-chart-mixin.js

+34
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,10 @@ export const ChartMixin = (superClass) =>
344344
beta: 15,
345345
depth: 50,
346346
};
347+
348+
// Threshold for the number of "addPoint" calls to detach and re-attach the chart.
349+
this.__addPointThreshold = 200;
350+
this.__addPointCounter = 0;
347351
}
348352

349353
/**
@@ -953,6 +957,30 @@ export const ChartMixin = (superClass) =>
953957
this._jsonConfigurationBuffer = null;
954958
}
955959

960+
/**
961+
* Detaches and re-attaches the chart. This process destroys and recreates the configuration, which prevents the listeners from piling up. This is a workaround for HighCharts memory leak bug on addPoint calls. Should be removed after the underlying issue is resolved.
962+
* @private
963+
*/
964+
__detachReattachChart() {
965+
const parent = this.parentElement;
966+
let index;
967+
if (parent) {
968+
index = Array.prototype.indexOf.call(parent.children, this);
969+
parent.removeChild(this);
970+
}
971+
queueMicrotask(() => {
972+
if (!parent || index == null) {
973+
return;
974+
}
975+
if (index === parent.children.length) {
976+
parent.appendChild(this);
977+
} else {
978+
parent.insertBefore(this, parent.children[index]);
979+
}
980+
});
981+
this.__addPointCounter = 0;
982+
}
983+
956984
/**
957985
* Search for axis with given `id`.
958986
*
@@ -1578,8 +1606,14 @@ export const ChartMixin = (superClass) =>
15781606
const series = this.configuration.series[seriesIndex];
15791607
const functionToCall = series[functionName];
15801608
if (functionToCall && typeof functionToCall === 'function') {
1609+
if (functionName === 'addPoint') {
1610+
this.__addPointCounter += 1;
1611+
}
15811612
args.forEach((arg) => inflateFunctions(arg));
15821613
functionToCall.apply(series, args);
1614+
if (functionName === 'addPoint' && this.__addPointCounter >= this.__addPointThreshold) {
1615+
this.__detachReattachChart();
1616+
}
15831617
}
15841618
}
15851619
}

packages/charts/test/private-api.test.js

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect } from '@vaadin/chai-plugins';
2-
import { fixtureSync, nextFrame, oneEvent } from '@vaadin/testing-helpers';
2+
import { fixtureSync, nextFrame, nextRender, oneEvent } from '@vaadin/testing-helpers';
33
import '../vaadin-chart.js';
44
import { inflateFunctions } from '../src/helpers.js';
55

@@ -102,6 +102,21 @@ describe('vaadin-chart private API', () => {
102102
const { dataLabels } = chart.configuration.series[0].userOptions;
103103
expect(dataLabels.formatter).to.be.a('function');
104104
});
105+
106+
it('should destroy and recreate the chart after a large number of addPoint calls', async () => {
107+
const initialNumberOfPoints = chart.configuration.series[0].data.length;
108+
const oldChartInstance = chart.configuration;
109+
// Use internal threshold
110+
const numberOfPointsToAdd = chart.__addPointThreshold * 2;
111+
for (let i = 0; i < numberOfPointsToAdd; i++) {
112+
chart.__callSeriesFunction('addPoint', 0, i);
113+
await nextRender(chart);
114+
}
115+
expect(chart.configuration).to.exist;
116+
expect(chart.configuration).to.not.equal(oldChartInstance);
117+
expect(chart.configuration.series[0]).to.exist;
118+
expect(chart.configuration.series[0].data.length).to.be.greaterThan(initialNumberOfPoints);
119+
});
105120
});
106121

107122
describe('__callAxisFunction', () => {

0 commit comments

Comments
 (0)