From 28c5bc2121ab847a39dfc7d8fd8392869ec2ae25 Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Sat, 8 Mar 2025 17:39:28 +0300 Subject: [PATCH 1/2] fix: reattach chart after a number of addpoint calls --- packages/charts/src/vaadin-chart-mixin.js | 34 +++++++++++++++++++++++ packages/charts/test/private-api.test.js | 17 +++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/charts/src/vaadin-chart-mixin.js b/packages/charts/src/vaadin-chart-mixin.js index a701b584b19..97a942a681f 100644 --- a/packages/charts/src/vaadin-chart-mixin.js +++ b/packages/charts/src/vaadin-chart-mixin.js @@ -344,6 +344,10 @@ export const ChartMixin = (superClass) => beta: 15, depth: 50, }; + + // Threshold for the number of "addPoint" calls to detach and re-attach the chart. + this.__addPointThreshold = 200; + this.__addPointCounter = 0; } /** @@ -953,6 +957,30 @@ export const ChartMixin = (superClass) => this._jsonConfigurationBuffer = null; } + /** + * 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. + * @private + */ + __detachReattachChart() { + const parent = this.parentElement; + let index; + if (parent) { + index = Array.prototype.indexOf.call(parent.children, this); + parent.removeChild(this); + } + queueMicrotask(() => { + if (!parent || index == null) { + return; + } + if (index === parent.children.length) { + parent.appendChild(this); + } else { + parent.insertBefore(this, parent.children[index]); + } + }); + this.__addPointCounter = 0; + } + /** * Search for axis with given `id`. * @@ -1578,8 +1606,14 @@ export const ChartMixin = (superClass) => const series = this.configuration.series[seriesIndex]; const functionToCall = series[functionName]; if (functionToCall && typeof functionToCall === 'function') { + if (functionName === 'addPoint') { + this.__addPointCounter += 1; + } args.forEach((arg) => inflateFunctions(arg)); functionToCall.apply(series, args); + if (functionName === 'addPoint' && this.__addPointCounter >= this.__addPointThreshold) { + this.__detachReattachChart(); + } } } } diff --git a/packages/charts/test/private-api.test.js b/packages/charts/test/private-api.test.js index c7c8260e370..dcd3359f1e6 100644 --- a/packages/charts/test/private-api.test.js +++ b/packages/charts/test/private-api.test.js @@ -1,5 +1,5 @@ import { expect } from '@vaadin/chai-plugins'; -import { fixtureSync, nextFrame, oneEvent } from '@vaadin/testing-helpers'; +import { fixtureSync, nextFrame, nextRender, oneEvent } from '@vaadin/testing-helpers'; import '../vaadin-chart.js'; import { inflateFunctions } from '../src/helpers.js'; @@ -102,6 +102,21 @@ describe('vaadin-chart private API', () => { const { dataLabels } = chart.configuration.series[0].userOptions; expect(dataLabels.formatter).to.be.a('function'); }); + + it('should destroy and recreate the chart after a large number of addPoint calls', async () => { + const initialNumberOfPoints = chart.configuration.series[0].data.length; + const oldChartInstance = chart.configuration; + // Use internal threshold + const numberOfPointsToAdd = chart.__addPointThreshold * 2; + for (let i = 0; i < numberOfPointsToAdd; i++) { + chart.__callSeriesFunction('addPoint', 0, i); + await nextRender(chart); + } + expect(chart.configuration).to.exist; + expect(chart.configuration).to.not.equal(oldChartInstance); + expect(chart.configuration.series[0]).to.exist; + expect(chart.configuration.series[0].data.length).to.be.greaterThan(initialNumberOfPoints); + }); }); describe('__callAxisFunction', () => { From 0e839dc16c5ede83a75b7058cd1b531d756c162f Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Thu, 22 May 2025 15:17:39 +0300 Subject: [PATCH 2/2] test: add missing import --- packages/charts/test/private-api.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/charts/test/private-api.test.js b/packages/charts/test/private-api.test.js index e36491ee16d..4303aa08964 100644 --- a/packages/charts/test/private-api.test.js +++ b/packages/charts/test/private-api.test.js @@ -1,5 +1,5 @@ import { expect } from '@vaadin/chai-plugins'; -import { fixtureSync, nextFrame, oneEvent } from '@vaadin/testing-helpers'; +import { fixtureSync, nextFrame, nextRender, oneEvent } from '@vaadin/testing-helpers'; import '../src/vaadin-chart.js'; import { inflateFunctions } from '../src/helpers.js';