diff --git a/e2e/tests/functional/plugins/plot/overlayPlot.e2e.spec.js b/e2e/tests/functional/plugins/plot/overlayPlot.e2e.spec.js index 16a868ada5..6e116228f8 100644 --- a/e2e/tests/functional/plugins/plot/overlayPlot.e2e.spec.js +++ b/e2e/tests/functional/plugins/plot/overlayPlot.e2e.spec.js @@ -140,4 +140,61 @@ test.describe('Overlay Plot', () => { expect(yAxis3Group.getByRole('listitem', { name: swgB.name })).toBeTruthy(); expect(yAxis3Group.getByRole('listitem').nth(0).getByText(swgB.name)).toBeTruthy(); }); + + test('Clicking on an item in the elements pool brings up the plot preview with data points', async ({ page }) => { + const overlayPlot = await createDomainObjectWithDefaults(page, { + type: "Overlay Plot" + }); + + const swgA = await createDomainObjectWithDefaults(page, { + type: "Sine Wave Generator", + parent: overlayPlot.uuid + }); + + await page.goto(overlayPlot.url); + await page.click('button[title="Edit"]'); + + await page.locator(`#inspector-elements-tree >> text=${swgA.name}`).click(); + await page.locator('.js-overlay canvas').nth(1); + const plotPixelSize = await getCanvasPixelsWithData(page); + expect(plotPixelSize).toBeGreaterThan(0); + }); }); + +/** + * @param {import('@playwright/test').Page} page + */ +async function getCanvasPixelsWithData(page) { + const getTelemValuePromise = new Promise(resolve => page.exposeFunction('getCanvasValue', resolve)); + + await page.evaluate(() => { + // The document canvas is where the plot points and lines are drawn. + // The only way to access the canvas is using document (using page.evaluate) + let data; + let canvas; + let ctx; + canvas = document.querySelector('.js-overlay canvas'); + ctx = canvas.getContext('2d'); + data = ctx.getImageData(0, 0, canvas.width, canvas.height).data; + const imageDataValues = Object.values(data); + let plotPixels = []; + // Each pixel consists of four values within the ImageData.data array. The for loop iterates by multiples of four. + // The values associated with each pixel are R (red), G (green), B (blue), and A (alpha), in that order. + for (let i = 0; i < imageDataValues.length;) { + if (imageDataValues[i] > 0) { + plotPixels.push({ + startIndex: i, + endIndex: i + 3, + value: `rgb(${imageDataValues[i]}, ${imageDataValues[i + 1]}, ${imageDataValues[i + 2]}, ${imageDataValues[i + 3]})` + }); + } + + i = i + 4; + + } + + window.getCanvasValue(plotPixels.length); + }); + + return getTelemValuePromise; +} diff --git a/src/api/time/IndependentTimeContext.js b/src/api/time/IndependentTimeContext.js index 6841f03ab0..6856aed7c0 100644 --- a/src/api/time/IndependentTimeContext.js +++ b/src/api/time/IndependentTimeContext.js @@ -32,14 +32,18 @@ class IndependentTimeContext extends TimeContext { this.openmct = openmct; this.unlisteners = []; this.globalTimeContext = globalTimeContext; - this.upstreamTimeContext = undefined; + // We always start with the global time context. + // This upstream context will be undefined when an independent time context is added later. + this.upstreamTimeContext = this.globalTimeContext; this.objectPath = objectPath; this.refreshContext = this.refreshContext.bind(this); this.resetContext = this.resetContext.bind(this); + this.removeIndependentContext = this.removeIndependentContext.bind(this); this.refreshContext(); this.globalTimeContext.on('refreshContext', this.refreshContext); + this.globalTimeContext.on('removeOwnContext', this.removeIndependentContext); } bounds(newBounds) { @@ -202,10 +206,16 @@ class IndependentTimeContext extends TimeContext { } getUpstreamContext() { + // If a view has an independent context, don't return an upstream context + // Be aware that when a new independent time context is created, we assign the global context as default + if (this.hasOwnContext()) { + return undefined; + } + let timeContext = this.globalTimeContext; this.objectPath.some((item, index) => { const key = this.openmct.objects.makeKeyString(item.identifier); - //first index is the view object itself + // we're only interested in parents, not self, so index > 0 const itemContext = this.globalTimeContext.independentContexts.get(key); if (index > 0 && itemContext && itemContext.hasOwnContext()) { //upstream time context @@ -219,6 +229,43 @@ class IndependentTimeContext extends TimeContext { return timeContext; } + + /** + * Set the time context of a view to follow any upstream time contexts as necessary (defaulting to the global context) + * This needs to be separate from refreshContext + */ + removeIndependentContext(viewKey) { + const key = this.openmct.objects.makeKeyString(this.objectPath[0].identifier); + if (viewKey && key === viewKey) { + //this is necessary as the upstream context gets reassigned after this + this.stopFollowingTimeContext(); + + let timeContext = this.globalTimeContext; + + this.objectPath.some((item, index) => { + const objectKey = this.openmct.objects.makeKeyString(item.identifier); + // we're only interested in any parents, not self, so index > 0 + const itemContext = this.globalTimeContext.independentContexts.get(objectKey); + if (index > 0 && itemContext && itemContext.hasOwnContext()) { + //upstream time context + timeContext = itemContext; + + return true; + } + + return false; + }); + + this.upstreamTimeContext = timeContext; + + this.followTimeContext(); + + // Emit bounds so that views that are changing context get the upstream bounds + this.emit('bounds', this.bounds()); + // now that the view's context is set, tell others to check theirs in case they were following this view's context. + this.globalTimeContext.emit('refreshContext', viewKey); + } + } } export default IndependentTimeContext; diff --git a/src/api/time/TimeAPI.js b/src/api/time/TimeAPI.js index d22361d8d5..7006134a1d 100644 --- a/src/api/time/TimeAPI.js +++ b/src/api/time/TimeAPI.js @@ -149,7 +149,7 @@ class TimeAPI extends GlobalTimeContext { return () => { //follow any upstream time context - this.emit('refreshContext'); + this.emit('removeOwnContext', key); }; } diff --git a/src/api/time/independentTimeAPISpec.js b/src/api/time/independentTimeAPISpec.js index 0a4a4cb83f..f552a81e39 100644 --- a/src/api/time/independentTimeAPISpec.js +++ b/src/api/time/independentTimeAPISpec.js @@ -117,22 +117,58 @@ describe("The Independent Time API", function () { }); it("uses an object's independent time context if the parent doesn't have one", () => { + const domainObjectKey2 = `${domainObjectKey}-2`; + const domainObjectKey3 = `${domainObjectKey}-3`; let timeContext = api.getContextForView([{ identifier: { namespace: '', key: domainObjectKey } - }, { + }]); + let timeContext2 = api.getContextForView([{ identifier: { namespace: '', - key: 'blah' + key: domainObjectKey2 } }]); + let timeContext3 = api.getContextForView([{ + identifier: { + namespace: '', + key: domainObjectKey3 + } + }]); + // all bounds follow global time context expect(timeContext.bounds()).toEqual(bounds); + expect(timeContext2.bounds()).toEqual(bounds); + expect(timeContext3.bounds()).toEqual(bounds); + // only first item has own context let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds); expect(timeContext.bounds()).toEqual(independentBounds); + expect(timeContext2.bounds()).toEqual(bounds); + expect(timeContext3.bounds()).toEqual(bounds); + // first and second item have own context + let destroyTimeContext2 = api.addIndependentContext(domainObjectKey2, independentBounds); + expect(timeContext.bounds()).toEqual(independentBounds); + expect(timeContext2.bounds()).toEqual(independentBounds); + expect(timeContext3.bounds()).toEqual(bounds); + // all items have own time context + let destroyTimeContext3 = api.addIndependentContext(domainObjectKey3, independentBounds); + expect(timeContext.bounds()).toEqual(independentBounds); + expect(timeContext2.bounds()).toEqual(independentBounds); + expect(timeContext3.bounds()).toEqual(independentBounds); + //remove own contexts one at a time - should revert to global time context destroyTimeContext(); expect(timeContext.bounds()).toEqual(bounds); + expect(timeContext2.bounds()).toEqual(independentBounds); + expect(timeContext3.bounds()).toEqual(independentBounds); + destroyTimeContext2(); + expect(timeContext.bounds()).toEqual(bounds); + expect(timeContext2.bounds()).toEqual(bounds); + expect(timeContext3.bounds()).toEqual(independentBounds); + destroyTimeContext3(); + expect(timeContext.bounds()).toEqual(bounds); + expect(timeContext2.bounds()).toEqual(bounds); + expect(timeContext3.bounds()).toEqual(bounds); }); it("Allows setting of valid bounds", function () {