mirror of
https://github.com/nasa/openmct.git
synced 2024-12-19 05:07:52 +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
|
// ensure table header visible
|
||||||
await expect(page.getByRole('searchbox', { name: 'message filter input' })).toBeVisible();
|
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
|
// select second tab
|
||||||
await page.getByLabel(`${notebook.name} tab`).click();
|
await page.getByLabel(`${notebook.name} tab`).click();
|
||||||
|
|
||||||
// ensure notebook visible
|
// ensure notebook visible
|
||||||
await expect(page.locator('.c-notebook__drag-area')).toBeVisible();
|
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
|
// select third tab
|
||||||
await page.getByLabel(`${sineWaveGenerator.name} tab`).click();
|
await page.getByLabel(`${sineWaveGenerator.name} tab`).click();
|
||||||
|
|
||||||
// expect sine wave generator visible
|
// 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
|
// now try to select the first tab again
|
||||||
await page.getByLabel(`${table.name} tab`).click();
|
await page.getByLabel(`${table.name} tab`).click();
|
||||||
// ensure table header visible
|
// ensure table header visible
|
||||||
await expect(page.getByRole('searchbox', { name: 'message filter input' })).toBeVisible();
|
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>
|
<template>
|
||||||
<div ref="chart" class="gl-plot-chart-area">
|
<div ref="chart" class="gl-plot-chart-area">
|
||||||
<canvas :style="canvasStyle"></canvas>
|
<canvas :style="canvasStyle" class="js-overlay-canvas"></canvas>
|
||||||
<canvas :style="canvasStyle"></canvas>
|
<canvas :style="canvasStyle" class="js-main-canvas"></canvas>
|
||||||
<div ref="limitArea" class="js-limit-area">
|
<div ref="limitArea" class="js-limit-area">
|
||||||
<limit-label
|
<limit-label
|
||||||
v-for="(limitLabel, index) in visibleLimitLabels"
|
v-for="(limitLabel, index) in visibleLimitLabels"
|
||||||
@ -197,6 +197,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.chartVisible = true;
|
||||||
|
this.chartContainer = this.$refs.chart;
|
||||||
|
this.visibilityObserver = new IntersectionObserver(this.visibilityChanged);
|
||||||
|
this.visibilityObserver.observe(this.chartContainer);
|
||||||
eventHelpers.extend(this);
|
eventHelpers.extend(this);
|
||||||
this.seriesModels = [];
|
this.seriesModels = [];
|
||||||
this.config = this.getConfig();
|
this.config = this.getConfig();
|
||||||
@ -239,10 +243,8 @@ export default {
|
|||||||
this.seriesElements = new WeakMap();
|
this.seriesElements = new WeakMap();
|
||||||
this.seriesLimits = new WeakMap();
|
this.seriesLimits = new WeakMap();
|
||||||
|
|
||||||
let canvasEls = this.$parent.$refs.chartContainer.querySelectorAll('canvas');
|
const canvasReadyForDrawing = this.readyCanvasForDrawing();
|
||||||
const mainCanvas = canvasEls[1];
|
if (canvasReadyForDrawing) {
|
||||||
const overlayCanvas = canvasEls[0];
|
|
||||||
if (this.initializeCanvas(mainCanvas, overlayCanvas)) {
|
|
||||||
this.draw();
|
this.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,6 +258,7 @@ export default {
|
|||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.destroy();
|
this.destroy();
|
||||||
|
this.visibilityObserver.unobserve(this.chartContainer);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getConfig() {
|
getConfig() {
|
||||||
@ -272,6 +275,26 @@ export default {
|
|||||||
|
|
||||||
return config;
|
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) {
|
reDraw(newXKey, oldXKey, series) {
|
||||||
this.changeInterpolate(newXKey, oldXKey, series);
|
this.changeInterpolate(newXKey, oldXKey, series);
|
||||||
this.changeMarkers(newXKey, oldXKey, series);
|
this.changeMarkers(newXKey, oldXKey, series);
|
||||||
@ -417,13 +440,12 @@ export default {
|
|||||||
this.scheduleDraw();
|
this.scheduleDraw();
|
||||||
},
|
},
|
||||||
destroy() {
|
destroy() {
|
||||||
|
this.destroyCanvas();
|
||||||
this.isDestroyed = true;
|
this.isDestroyed = true;
|
||||||
this.stopListening();
|
|
||||||
this.lines.forEach((line) => line.destroy());
|
this.lines.forEach((line) => line.destroy());
|
||||||
this.limitLines.forEach((line) => line.destroy());
|
this.limitLines.forEach((line) => line.destroy());
|
||||||
this.pointSets.forEach((pointSet) => pointSet.destroy());
|
this.pointSets.forEach((pointSet) => pointSet.destroy());
|
||||||
this.alarmSets.forEach((alarmSet) => alarmSet.destroy());
|
this.alarmSets.forEach((alarmSet) => alarmSet.destroy());
|
||||||
DrawLoader.releaseDrawAPI(this.drawAPI);
|
|
||||||
},
|
},
|
||||||
resetYOffsetAndSeriesDataForYAxis(yAxisId) {
|
resetYOffsetAndSeriesDataForYAxis(yAxisId) {
|
||||||
delete this.offset[yAxisId].y;
|
delete this.offset[yAxisId].y;
|
||||||
@ -477,33 +499,49 @@ export default {
|
|||||||
return this.offset[yAxisId].y(pSeries.getYVal(point));
|
return this.offset[yAxisId].y(pSeries.getYVal(point));
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
},
|
},
|
||||||
|
destroyCanvas() {
|
||||||
initializeCanvas(canvas, overlay) {
|
if (this.isDestroyed) {
|
||||||
this.canvas = canvas;
|
return;
|
||||||
this.overlay = overlay;
|
}
|
||||||
this.drawAPI = DrawLoader.getDrawAPI(canvas, overlay);
|
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) {
|
if (this.drawAPI) {
|
||||||
this.listenTo(this.drawAPI, 'error', this.fallbackToCanvas, this);
|
this.listenTo(this.drawAPI, 'error', this.fallbackToCanvas, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Boolean(this.drawAPI);
|
return Boolean(this.drawAPI);
|
||||||
},
|
},
|
||||||
fallbackToCanvas() {
|
buildCanvasElements() {
|
||||||
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.
|
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.innerHTML = `
|
div.innerHTML = `
|
||||||
<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%;"></canvas>
|
<canvas style="position: absolute; background: none; width: 100%; height: 100%;" class="js-main-canvas"></canvas>
|
||||||
`;
|
`;
|
||||||
const mainCanvas = div.querySelectorAll('canvas')[1];
|
const mainCanvas = div.querySelectorAll('canvas')[1];
|
||||||
const overlayCanvas = div.querySelectorAll('canvas')[0];
|
const overlayCanvas = div.querySelectorAll('canvas')[0];
|
||||||
this.canvas.parentNode.replaceChild(mainCanvas, this.canvas);
|
this.chartContainer.appendChild(mainCanvas, this.canvas);
|
||||||
this.canvas = mainCanvas;
|
this.canvas = mainCanvas;
|
||||||
this.overlay.parentNode.replaceChild(overlayCanvas, this.overlay);
|
this.chartContainer.appendChild(overlayCanvas, this.overlay);
|
||||||
this.overlay = overlayCanvas;
|
this.overlay = overlayCanvas;
|
||||||
|
},
|
||||||
|
fallbackToCanvas() {
|
||||||
|
console.warn(`📈 fallback to 2D canvas`);
|
||||||
|
this.destroyCanvas();
|
||||||
|
this.buildCanvasElements();
|
||||||
this.drawAPI = DrawLoader.getFallbackDrawAPI(this.canvas, this.overlay);
|
this.drawAPI = DrawLoader.getFallbackDrawAPI(this.canvas, this.overlay);
|
||||||
this.$emit('plot-reinitialize-canvas');
|
this.$emit('plot-reinitialize-canvas');
|
||||||
},
|
},
|
||||||
@ -653,7 +691,7 @@ export default {
|
|||||||
},
|
},
|
||||||
draw() {
|
draw() {
|
||||||
this.drawScheduled = false;
|
this.drawScheduled = false;
|
||||||
if (this.isDestroyed) {
|
if (this.isDestroyed || !this.chartVisible) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -681,6 +719,9 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateViewport(yAxisId) {
|
updateViewport(yAxisId) {
|
||||||
|
if (!this.chartVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const mainYAxisId = this.config.yAxis.get('id');
|
const mainYAxisId = this.config.yAxis.get('id');
|
||||||
const xRange = this.config.xAxis.get('displayRange');
|
const xRange = this.config.xAxis.get('displayRange');
|
||||||
let yRange;
|
let yRange;
|
||||||
|
@ -154,14 +154,14 @@ DrawWebGL.prototype.initContext = function () {
|
|||||||
DrawWebGL.prototype.destroy = function () {
|
DrawWebGL.prototype.destroy = function () {
|
||||||
// Lose the context and delete all associated resources
|
// 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
|
// 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?.getExtension('WEBGL_lose_context')?.loseContext();
|
||||||
this.gl.deleteBuffer(this.buffer);
|
this.gl?.deleteBuffer(this.buffer);
|
||||||
this.buffer = undefined;
|
this.buffer = undefined;
|
||||||
this.gl.deleteProgram(this.program);
|
this.gl?.deleteProgram(this.program);
|
||||||
this.program = undefined;
|
this.program = undefined;
|
||||||
this.gl.deleteShader(this.vertexShader);
|
this.gl?.deleteShader(this.vertexShader);
|
||||||
this.vertexShader = undefined;
|
this.vertexShader = undefined;
|
||||||
this.gl.deleteShader(this.fragmentShader);
|
this.gl?.deleteShader(this.fragmentShader);
|
||||||
this.fragmentShader = undefined;
|
this.fragmentShader = undefined;
|
||||||
this.gl = undefined;
|
this.gl = undefined;
|
||||||
|
|
||||||
|
@ -38,8 +38,6 @@ export default class VisibilityObserver {
|
|||||||
if (!element) {
|
if (!element) {
|
||||||
throw new Error(`VisibilityObserver must be created with an 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.#element = element;
|
||||||
this.isIntersecting = true;
|
this.isIntersecting = true;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user