mirror of
https://github.com/nasa/openmct.git
synced 2024-12-18 20:57:53 +00:00
Destroy canvas in plots if not visible (#7263)
* first draft * add some more debugging * add test and remove debug * Remove debug function * consolidate destroy * add better canvas name and handle if gl has gone missing * extra check for extension
This commit is contained in:
parent
2dc1388737
commit
e7b9481aa9
@ -54,21 +54,35 @@ test.describe('Tabs View', () => {
|
||||
// ensure table header visible
|
||||
await expect(page.getByRole('searchbox', { name: 'message filter input' })).toBeVisible();
|
||||
|
||||
// no canvas (i.e., sine wave generator) in the document should be visible
|
||||
await expect(page.locator('canvas')).toBeHidden();
|
||||
|
||||
// select second tab
|
||||
await page.getByLabel(`${notebook.name} tab`).click();
|
||||
|
||||
// ensure notebook visible
|
||||
await expect(page.locator('.c-notebook__drag-area')).toBeVisible();
|
||||
|
||||
// no canvas (i.e., sine wave generator) in the document should be visible
|
||||
await expect(page.locator('canvas')).toBeHidden();
|
||||
|
||||
// select third tab
|
||||
await page.getByLabel(`${sineWaveGenerator.name} tab`).click();
|
||||
|
||||
// expect sine wave generator visible
|
||||
expect(await page.locator('.c-plot').isVisible()).toBe(true);
|
||||
await expect(page.locator('.c-plot')).toBeVisible();
|
||||
|
||||
// expect two canvases (i.e., overlay & main canvas for sine wave generator) to be visible
|
||||
await expect(page.locator('canvas')).toHaveCount(2);
|
||||
await expect(page.locator('canvas').nth(0)).toBeVisible();
|
||||
await expect(page.locator('canvas').nth(1)).toBeVisible();
|
||||
|
||||
// now try to select the first tab again
|
||||
await page.getByLabel(`${table.name} tab`).click();
|
||||
// ensure table header visible
|
||||
await expect(page.getByRole('searchbox', { name: 'message filter input' })).toBeVisible();
|
||||
|
||||
// no canvas (i.e., sine wave generator) in the document should be visible
|
||||
await expect(page.locator('canvas')).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
@ -22,8 +22,8 @@
|
||||
|
||||
<template>
|
||||
<div ref="chart" class="gl-plot-chart-area">
|
||||
<canvas :style="canvasStyle"></canvas>
|
||||
<canvas :style="canvasStyle"></canvas>
|
||||
<canvas :style="canvasStyle" class="js-overlay-canvas"></canvas>
|
||||
<canvas :style="canvasStyle" class="js-main-canvas"></canvas>
|
||||
<div ref="limitArea" class="js-limit-area">
|
||||
<limit-label
|
||||
v-for="(limitLabel, index) in visibleLimitLabels"
|
||||
@ -197,6 +197,10 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.chartVisible = true;
|
||||
this.chartContainer = this.$refs.chart;
|
||||
this.visibilityObserver = new IntersectionObserver(this.visibilityChanged);
|
||||
this.visibilityObserver.observe(this.chartContainer);
|
||||
eventHelpers.extend(this);
|
||||
this.seriesModels = [];
|
||||
this.config = this.getConfig();
|
||||
@ -239,10 +243,8 @@ export default {
|
||||
this.seriesElements = new WeakMap();
|
||||
this.seriesLimits = new WeakMap();
|
||||
|
||||
let canvasEls = this.$parent.$refs.chartContainer.querySelectorAll('canvas');
|
||||
const mainCanvas = canvasEls[1];
|
||||
const overlayCanvas = canvasEls[0];
|
||||
if (this.initializeCanvas(mainCanvas, overlayCanvas)) {
|
||||
const canvasReadyForDrawing = this.readyCanvasForDrawing();
|
||||
if (canvasReadyForDrawing) {
|
||||
this.draw();
|
||||
}
|
||||
|
||||
@ -256,6 +258,7 @@ export default {
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.destroy();
|
||||
this.visibilityObserver.unobserve(this.chartContainer);
|
||||
},
|
||||
methods: {
|
||||
getConfig() {
|
||||
@ -272,6 +275,26 @@ export default {
|
||||
|
||||
return config;
|
||||
},
|
||||
visibilityChanged([entry]) {
|
||||
if (entry.target === this.chartContainer) {
|
||||
const wasVisible = this.chartVisible;
|
||||
this.chartVisible = entry.isIntersecting;
|
||||
if (!this.chartVisible) {
|
||||
// destroy the chart
|
||||
this.destroyCanvas();
|
||||
} else if (!wasVisible && this.chartVisible) {
|
||||
// rebuild the chart
|
||||
this.buildCanvasElements();
|
||||
const canvasInitialized = this.readyCanvasForDrawing();
|
||||
if (canvasInitialized) {
|
||||
this.draw();
|
||||
}
|
||||
this.$emit('plot-reinitialize-canvas');
|
||||
} else if (wasVisible && this.chartVisible) {
|
||||
// ignore, moving on
|
||||
}
|
||||
}
|
||||
},
|
||||
reDraw(newXKey, oldXKey, series) {
|
||||
this.changeInterpolate(newXKey, oldXKey, series);
|
||||
this.changeMarkers(newXKey, oldXKey, series);
|
||||
@ -417,13 +440,12 @@ export default {
|
||||
this.scheduleDraw();
|
||||
},
|
||||
destroy() {
|
||||
this.destroyCanvas();
|
||||
this.isDestroyed = true;
|
||||
this.stopListening();
|
||||
this.lines.forEach((line) => line.destroy());
|
||||
this.limitLines.forEach((line) => line.destroy());
|
||||
this.pointSets.forEach((pointSet) => pointSet.destroy());
|
||||
this.alarmSets.forEach((alarmSet) => alarmSet.destroy());
|
||||
DrawLoader.releaseDrawAPI(this.drawAPI);
|
||||
},
|
||||
resetYOffsetAndSeriesDataForYAxis(yAxisId) {
|
||||
delete this.offset[yAxisId].y;
|
||||
@ -477,33 +499,49 @@ export default {
|
||||
return this.offset[yAxisId].y(pSeries.getYVal(point));
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
initializeCanvas(canvas, overlay) {
|
||||
this.canvas = canvas;
|
||||
this.overlay = overlay;
|
||||
this.drawAPI = DrawLoader.getDrawAPI(canvas, overlay);
|
||||
destroyCanvas() {
|
||||
if (this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
this.stopListening(this.drawAPI);
|
||||
DrawLoader.releaseDrawAPI(this.drawAPI);
|
||||
if (this.chartContainer) {
|
||||
const canvasElements = this.chartContainer.querySelectorAll('canvas');
|
||||
canvasElements.forEach((canvas) => {
|
||||
canvas.parentNode.removeChild(canvas);
|
||||
});
|
||||
}
|
||||
},
|
||||
readyCanvasForDrawing() {
|
||||
const canvasEls = this.chartContainer.querySelectorAll('canvas');
|
||||
const mainCanvas = canvasEls[1];
|
||||
const overlayCanvas = canvasEls[0];
|
||||
this.canvas = mainCanvas;
|
||||
this.overlay = overlayCanvas;
|
||||
this.drawAPI = DrawLoader.getDrawAPI(mainCanvas, overlayCanvas);
|
||||
if (this.drawAPI) {
|
||||
this.listenTo(this.drawAPI, 'error', this.fallbackToCanvas, this);
|
||||
}
|
||||
|
||||
return Boolean(this.drawAPI);
|
||||
},
|
||||
fallbackToCanvas() {
|
||||
this.stopListening(this.drawAPI);
|
||||
DrawLoader.releaseDrawAPI(this.drawAPI);
|
||||
// Have to throw away the old canvas elements and replace with new
|
||||
// canvas elements in order to get new drawing contexts.
|
||||
buildCanvasElements() {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = `
|
||||
<canvas style="position: absolute; background: none; width: 100%; height: 100%;"></canvas>
|
||||
<canvas style="position: absolute; background: none; width: 100%; height: 100%;"></canvas>
|
||||
<canvas style="position: absolute; background: none; width: 100%; height: 100%;" class="js-overlay-canvas"></canvas>
|
||||
<canvas style="position: absolute; background: none; width: 100%; height: 100%;" class="js-main-canvas"></canvas>
|
||||
`;
|
||||
const mainCanvas = div.querySelectorAll('canvas')[1];
|
||||
const overlayCanvas = div.querySelectorAll('canvas')[0];
|
||||
this.canvas.parentNode.replaceChild(mainCanvas, this.canvas);
|
||||
this.chartContainer.appendChild(mainCanvas, this.canvas);
|
||||
this.canvas = mainCanvas;
|
||||
this.overlay.parentNode.replaceChild(overlayCanvas, this.overlay);
|
||||
this.chartContainer.appendChild(overlayCanvas, this.overlay);
|
||||
this.overlay = overlayCanvas;
|
||||
},
|
||||
fallbackToCanvas() {
|
||||
console.warn(`📈 fallback to 2D canvas`);
|
||||
this.destroyCanvas();
|
||||
this.buildCanvasElements();
|
||||
this.drawAPI = DrawLoader.getFallbackDrawAPI(this.canvas, this.overlay);
|
||||
this.$emit('plot-reinitialize-canvas');
|
||||
},
|
||||
@ -653,7 +691,7 @@ export default {
|
||||
},
|
||||
draw() {
|
||||
this.drawScheduled = false;
|
||||
if (this.isDestroyed) {
|
||||
if (this.isDestroyed || !this.chartVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -681,6 +719,9 @@ export default {
|
||||
});
|
||||
},
|
||||
updateViewport(yAxisId) {
|
||||
if (!this.chartVisible) {
|
||||
return;
|
||||
}
|
||||
const mainYAxisId = this.config.yAxis.get('id');
|
||||
const xRange = this.config.xAxis.get('displayRange');
|
||||
let yRange;
|
||||
|
@ -154,14 +154,14 @@ DrawWebGL.prototype.initContext = function () {
|
||||
DrawWebGL.prototype.destroy = function () {
|
||||
// Lose the context and delete all associated resources
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#lose_contexts_eagerly
|
||||
this.gl.getExtension('WEBGL_lose_context').loseContext();
|
||||
this.gl.deleteBuffer(this.buffer);
|
||||
this.gl?.getExtension('WEBGL_lose_context')?.loseContext();
|
||||
this.gl?.deleteBuffer(this.buffer);
|
||||
this.buffer = undefined;
|
||||
this.gl.deleteProgram(this.program);
|
||||
this.gl?.deleteProgram(this.program);
|
||||
this.program = undefined;
|
||||
this.gl.deleteShader(this.vertexShader);
|
||||
this.gl?.deleteShader(this.vertexShader);
|
||||
this.vertexShader = undefined;
|
||||
this.gl.deleteShader(this.fragmentShader);
|
||||
this.gl?.deleteShader(this.fragmentShader);
|
||||
this.fragmentShader = undefined;
|
||||
this.gl = undefined;
|
||||
|
||||
|
@ -38,8 +38,6 @@ export default class VisibilityObserver {
|
||||
if (!element) {
|
||||
throw new Error(`VisibilityObserver must be created with an element`);
|
||||
}
|
||||
// set the id to some random 4 letters
|
||||
this.id = Math.random().toString(36).substring(2, 6);
|
||||
this.#element = element;
|
||||
this.isIntersecting = true;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user