Plots correctly use configuration set on the parent if they can't their own (#7770)

* For telemetry that cannot have it's own configuration, ensure that it is correct initialized by the parent.

* stacked plot test checks that config properties for immutable telemetry points are applied correctly

* add tab navigation to inspector tabs

* add accessibility metadata to this component

* Update title to be on the correct component. Add expand/collapse logic

* clean up test

* refactor: better a11y for plot forms, fix "expand by default" test, refactor out `plotActions.js`

* a11y: aria label for plotOptionsItem

* refactor(a11y): PlotOptionsBrowse structure to have better a11y

* fixed tests

* address comment

* reverted to match previous commit

---------

Co-authored-by: Hill, John (ARC-TI)[KBR Wyle Services, LLC] <john.c.hill@nasa.gov>
Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
This commit is contained in:
Shefali Joshi 2024-07-16 10:57:12 -07:00 committed by GitHub
parent 6983148aba
commit db808b4d54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 455 additions and 348 deletions

View File

@ -26,7 +26,6 @@ Testsuite for plot autoscale.
import { createDomainObjectWithDefaults } from '../../../../appActions.js'; import { createDomainObjectWithDefaults } from '../../../../appActions.js';
import { expect, test } from '../../../../pluginFixtures.js'; import { expect, test } from '../../../../pluginFixtures.js';
import { setUserDefinedMinAndMax, turnOffAutoscale } from './plotActions.js';
test.use({ test.use({
viewport: { viewport: {
width: 1280, width: 1280,
@ -62,9 +61,12 @@ test.describe('Autoscale', () => {
await page.getByLabel('Edit Object').click(); await page.getByLabel('Edit Object').click();
await page.getByRole('tab', { name: 'Config' }).click(); await page.getByRole('tab', { name: 'Config' }).click();
await turnOffAutoscale(page);
await setUserDefinedMinAndMax(page, '-2', '2'); // turn off autoscale
await page.getByRole('checkbox', { name: 'Auto scale' }).uncheck();
await page.getByLabel('Y Axis 1 Minimum value').fill('-2');
await page.getByLabel('Y Axis 1 Maximum value').fill('2');
// save // save
await page.click('button[title="Save"]'); await page.click('button[title="Save"]');

View File

@ -91,7 +91,7 @@ test.describe('Overlay Plot', () => {
// Assert that the legend is collapsed by default // Assert that the legend is collapsed by default
await expect(page.getByLabel('Plot Legend Collapsed')).toBeVisible(); await expect(page.getByLabel('Plot Legend Collapsed')).toBeVisible();
await expect(page.getByLabel('Plot Legend Expanded')).toBeHidden(); await expect(page.getByLabel('Plot Legend Expanded')).toBeHidden();
await expect(page.getByLabel('Expand by Default')).toHaveText('No'); await expect(page.getByLabel('Expand by Default')).toHaveText(/No/);
expect(await page.getByLabel('Plot Legend Item').count()).toBe(3); expect(await page.getByLabel('Plot Legend Item').count()).toBe(3);
@ -106,7 +106,7 @@ test.describe('Overlay Plot', () => {
await expect(page.getByRole('cell', { name: 'Name' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Name' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Value' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Value' })).toBeVisible();
await expect(page.getByLabel('Expand by Default')).toHaveText('Yes'); await expect(page.getByLabel('Expand by Default')).toHaveText(/Yes/);
await expect(page.getByLabel('Plot Legend Item')).toHaveCount(3); await expect(page.getByLabel('Plot Legend Item')).toHaveCount(3);
// Assert that the legend is expanded on page load // Assert that the legend is expanded on page load
@ -116,7 +116,7 @@ test.describe('Overlay Plot', () => {
await expect(page.getByRole('cell', { name: 'Name' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Name' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Value' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Value' })).toBeVisible();
await expect(page.getByLabel('Expand by Default')).toHaveText('Yes'); await expect(page.getByLabel('Expand by Default')).toHaveText(/Yes/);
await expect(page.getByLabel('Plot Legend Item')).toHaveCount(3); await expect(page.getByLabel('Plot Legend Item')).toHaveCount(3);
}); });
@ -144,15 +144,8 @@ test.describe('Overlay Plot', () => {
// Expand the "Sine Wave Generator" plot series options and enable limit lines // Expand the "Sine Wave Generator" plot series options and enable limit lines
await page.getByRole('tab', { name: 'Config' }).click(); await page.getByRole('tab', { name: 'Config' }).click();
await page await page.getByLabel('Expand Sine Wave Generator:').click();
.getByRole('list', { name: 'Plot Series Properties' }) await page.getByLabel('Limit lines').check();
.locator('span')
.first()
.click();
await page
.getByRole('list', { name: 'Plot Series Properties' })
.locator('[title="Display limit lines"]~div input')
.check();
await assertLimitLinesExistAndAreVisible(page); await assertLimitLinesExistAndAreVisible(page);
@ -215,21 +208,13 @@ test.describe('Overlay Plot', () => {
// Expand the "Sine Wave Generator" plot series options and enable limit lines // Expand the "Sine Wave Generator" plot series options and enable limit lines
await page.getByRole('tab', { name: 'Config' }).click(); await page.getByRole('tab', { name: 'Config' }).click();
await page await page.getByLabel('Expand Sine Wave Generator:').click();
.getByRole('list', { name: 'Plot Series Properties' }) await page.getByLabel('Limit lines').check();
.locator('span')
.first()
.click();
await page
.getByRole('list', { name: 'Plot Series Properties' })
.getByRole('checkbox', { name: 'Limit lines' })
.check();
await assertLimitLinesExistAndAreVisible(page); await assertLimitLinesExistAndAreVisible(page);
// Save (exit edit mode) await page.getByRole('button', { name: 'Save' }).click();
await page.locator('button[title="Save"]').click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
await page.locator('li[title="Save and Finish Editing"]').click();
const initialCoords = await assertLimitLinesExistAndAreVisible(page); const initialCoords = await assertLimitLinesExistAndAreVisible(page);
// Resize the chart container by showing the snapshot pane. // Resize the chart container by showing the snapshot pane.

View File

@ -1,42 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* @param {import('@playwright/test').Page} page
*/
async function turnOffAutoscale(page) {
// uncheck autoscale
await page.getByRole('checkbox', { name: 'Auto scale' }).uncheck();
}
/**
* @param {import('@playwright/test').Page} page
* @param {string} min
* @param {string} max
*/
async function setUserDefinedMinAndMax(page, min, max) {
// set minimum value
await page.getByRole('spinbutton').first().fill(min);
// set maximum value
await page.getByRole('spinbutton').nth(1).fill(max);
}
export { setUserDefinedMinAndMax, turnOffAutoscale };

View File

@ -33,7 +33,6 @@ import {
setStartOffset setStartOffset
} from '../../../../appActions.js'; } from '../../../../appActions.js';
import { expect, test } from '../../../../pluginFixtures.js'; import { expect, test } from '../../../../pluginFixtures.js';
import { setUserDefinedMinAndMax, turnOffAutoscale } from './plotActions.js';
test.describe('Plot Controls', () => { test.describe('Plot Controls', () => {
let overlayPlot; let overlayPlot;
@ -78,9 +77,12 @@ test.describe('Plot Controls', () => {
await page.getByLabel('Edit Object').click(); await page.getByLabel('Edit Object').click();
await page.getByRole('tab', { name: 'Config' }).click(); await page.getByRole('tab', { name: 'Config' }).click();
await turnOffAutoscale(page);
await setUserDefinedMinAndMax(page, '-1', '1'); // turn off autoscale
await page.getByRole('checkbox', { name: 'Auto scale' }).uncheck();
await page.getByLabel('Y Axis 1 Minimum value').fill('-1');
await page.getByLabel('Y Axis 1 Maximum value').fill('1');
// save // save
await page.click('button[title="Save"]'); await page.click('button[title="Save"]');

View File

@ -39,19 +39,23 @@ test.describe('Stacked Plot', () => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
stackedPlot = await createDomainObjectWithDefaults(page, { stackedPlot = await createDomainObjectWithDefaults(page, {
type: 'Stacked Plot' type: 'Stacked Plot',
name: 'Stacked Plot'
}); });
swgA = await createDomainObjectWithDefaults(page, { swgA = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator', type: 'Sine Wave Generator',
name: 'Sine Wave Generator A',
parent: stackedPlot.uuid parent: stackedPlot.uuid
}); });
swgB = await createDomainObjectWithDefaults(page, { swgB = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator', type: 'Sine Wave Generator',
name: 'Sine Wave Generator B',
parent: stackedPlot.uuid parent: stackedPlot.uuid
}); });
swgC = await createDomainObjectWithDefaults(page, { swgC = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator', type: 'Sine Wave Generator',
name: 'Sine Wave Generator C',
parent: stackedPlot.uuid parent: stackedPlot.uuid
}); });
}); });
@ -151,40 +155,80 @@ test.describe('Stacked Plot', () => {
await page.getByRole('tab', { name: 'Config' }).click(); await page.getByRole('tab', { name: 'Config' }).click();
// Click on the 1st plot // Click on the 1st plot
await page.locator(`[aria-label="Stacked Plot Item ${swgA.name}"] canvas`).nth(1).click(); await page
.getByLabel('Stacked Plot Item Sine Wave Generator A')
.getByLabel('Plot Canvas')
.click();
// Assert that the inspector shows the Y Axis properties for swgA // Assert that the inspector shows the Y Axis properties for swgA
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText( await expect(page.getByRole('heading', { name: 'Plot Series' })).toBeVisible();
'Plot Series'
);
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect( await expect(
page.locator('[aria-label="Plot Series Properties"] .c-object-label') page.getByLabel('Inspector Views').getByText('Sine Wave Generator A', { exact: true })
).toContainText(swgA.name); ).toBeVisible();
// Click on the 2nd plot // Click on the 2nd plot
await page.locator(`[aria-label="Stacked Plot Item ${swgB.name}"] canvas`).nth(1).click(); await page
.getByLabel('Stacked Plot Item Sine Wave Generator B')
.getByLabel('Plot Canvas')
.click();
// Assert that the inspector shows the Y Axis properties for swgB // Assert that the inspector shows the Y Axis properties for swgB
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText( await expect(page.getByRole('heading', { name: 'Plot Series' })).toBeVisible();
'Plot Series'
);
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect( await expect(
page.locator('[aria-label="Plot Series Properties"] .c-object-label') page.getByLabel('Inspector Views').getByText('Sine Wave Generator B', { exact: true })
).toContainText(swgB.name); ).toBeVisible();
// Click on the 3rd plot // Click on the 3rd plot
await page.locator(`[aria-label="Stacked Plot Item ${swgC.name}"] canvas`).nth(1).click(); await page
.getByLabel('Stacked Plot Item Sine Wave Generator C')
// Assert that the inspector shows the Y Axis properties for swgC .getByLabel('Plot Canvas')
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText( .click();
'Plot Series' // Assert that the inspector shows the Y Axis properties for swgB
); await expect(page.getByRole('heading', { name: 'Plot Series' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect( await expect(
page.locator('[aria-label="Plot Series Properties"] .c-object-label') page.getByLabel('Inspector Views').getByText('Sine Wave Generator C', { exact: true })
).toContainText(swgC.name); ).toBeVisible();
// Go into edit mode
await page.getByLabel('Edit Object').click();
await page.getByRole('tab', { name: 'Config' }).click();
// Click on the 1st plot
await page.getByLabel('Stacked Plot Item Sine Wave Generator A').click();
// Assert that the inspector shows the Y Axis properties for swgA
await expect(page.getByRole('heading', { name: 'Plot Series' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect(
page.getByLabel('Inspector Views').getByText('Sine Wave Generator A', { exact: true })
).toBeVisible();
// Click on the 2nd plot
await page.getByLabel('Stacked Plot Item Sine Wave Generator B').click();
// Assert that the inspector shows the Y Axis properties for swgB
await expect(page.getByRole('heading', { name: 'Plot Series' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect(
page.getByLabel('Inspector Views').getByText('Sine Wave Generator B', { exact: true })
).toBeVisible();
// Click on the 3rd plot
await page.getByLabel('Stacked Plot Item Sine Wave Generator C').click();
// Assert that the inspector shows the Y Axis properties for swgC
await expect(page.getByRole('heading', { name: 'Plot Series' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect(
page.getByLabel('Inspector Views').getByText('Sine Wave Generator C', { exact: true })
).toBeVisible();
});
test('Changing properties of an immutable child plot are applied correctly', async ({ page }) => {
await page.goto(stackedPlot.url);
// Go into edit mode // Go into edit mode
await page.getByLabel('Edit Object').click(); await page.getByLabel('Edit Object').click();
@ -192,40 +236,35 @@ test.describe('Stacked Plot', () => {
await page.getByRole('tab', { name: 'Config' }).click(); await page.getByRole('tab', { name: 'Config' }).click();
// Click on canvas for the 1st plot // Click on canvas for the 1st plot
await page.locator(`[aria-label="Stacked Plot Item ${swgA.name}"]`).click(); await page.getByLabel(`Stacked Plot Item ${swgA.name}`).click();
// Assert that the inspector shows the Y Axis properties for swgA // Expand config for the series
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText( await page.getByLabel('Expand Sine Wave Generator').click();
'Plot Series'
); // turn off alarm markers
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible(); await page.getByLabel('Alarm Markers').uncheck();
// save
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// reload page and waitForPlotsToRender
await page.reload();
await waitForPlotsToRender(page);
// Click on canvas for the 1st plot
await page.getByLabel(`Stacked Plot Item ${swgA.name}`).click();
// Expand config for the series
//TODO Fix this locator
await page.getByLabel('Expand Sine Wave Generator A generator').click();
// Assert that alarm markers are still turned off
await expect( await expect(
page.locator('[aria-label="Plot Series Properties"] .c-object-label') page
).toContainText(swgA.name); .getByTitle('Display markers visually denoting points in alarm.')
.getByRole('cell', { name: 'Disabled' })
//Click on canvas for the 2nd plot ).toBeVisible();
await page.locator(`[aria-label="Stacked Plot Item ${swgB.name}"]`).click();
// Assert that the inspector shows the Y Axis properties for swgB
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText(
'Plot Series'
);
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect(
page.locator('[aria-label="Plot Series Properties"] .c-object-label')
).toContainText(swgB.name);
//Click on canvas for the 3rd plot
await page.locator(`[aria-label="Stacked Plot Item ${swgC.name}"]`).click();
// Assert that the inspector shows the Y Axis properties for swgC
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText(
'Plot Series'
);
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect(
page.locator('[aria-label="Plot Series Properties"] .c-object-label')
).toContainText(swgC.name);
}); });
test('the legend toggles between aggregate and per child', async ({ page }) => { test('the legend toggles between aggregate and per child', async ({ page }) => {

View File

@ -21,7 +21,7 @@
--> -->
<template> <template>
<ul> <ul>
<li class="c-tree__item menus-to-left" :class="aliasCss"> <li class="c-tree__item menus-to-left" :class="aliasCss" role="treeitem">
<span <span
class="c-disclosure-triangle is-enabled flex-elem" class="c-disclosure-triangle is-enabled flex-elem"
:class="expandedCssClass" :class="expandedCssClass"

View File

@ -20,109 +20,55 @@
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<template> <template>
<div v-if="loaded" class="js-plot-options-browse"> <ul v-if="loaded" class="js-plot-options-browse" aria-label="Plot Configuration">
<ul v-if="!isStackedPlotObject" class="c-tree" aria-label="Plot Series Properties"> <li v-if="showPlotSeries" class="c-tree" aria-labelledby="plot-series-header">
<h2 class="--first" title="Plot series display properties in this object">Plot Series</h2> <h2 id="plot-series-header" class="--first">Plot Series</h2>
<PlotOptionsItem v-for="series in plotSeries" :key="series.keyString" :series="series" /> <ul aria-label="Plot Series Items" class="l-inspector-part">
</ul> <PlotOptionsItem v-for="series in plotSeries" :key="series.keyString" :series="series" />
<div v-if="plotSeries.length && !isStackedPlotObject" class="grid-properties"> </ul>
<ul </li>
<ul v-if="showYAxisProperties" aria-label="Y Axes" class="l-inspector-part js-yaxis-properties">
<li
v-for="(yAxis, index) in yAxesWithSeries" v-for="(yAxis, index) in yAxesWithSeries"
:key="`yAxis-${index}`" :key="`yAxis-${index}`"
class="l-inspector-part js-yaxis-properties" :aria-labelledby="getYAxisHeaderId(index)"
:aria-label="
yAxesWithSeries.length > 1 ? `Y Axis ${yAxis.id} Properties` : 'Y Axis Properties'
"
> >
<h2 title="Y axis settings for this object"> <h2 :id="getYAxisHeaderId(index)">
Y Axis {{ yAxesWithSeries.length > 1 ? yAxis.id : '' }} Y Axis {{ yAxesWithSeries.length > 1 ? yAxis.id : '' }}
</h2> </h2>
<li class="grid-row"> <ul class="grid-properties" :aria-label="`Y Axis ${yAxis.id} Properties`">
<div class="grid-cell label" title="Manually override how the Y axis is labeled."> <li
Label v-for="(prop, key) in yAxisProperties(yAxis)"
</div> :key="key"
<div class="grid-cell value">{{ yAxis.label ? yAxis.label : 'Not defined' }}</div> :aria-labelledby="getYAxisPropId(index, prop.label)"
</li> class="grid-row"
<li class="grid-row"> role="grid"
<div class="grid-cell label" title="Enable log mode.">Log mode</div>
<div class="grid-cell value">
{{ yAxis.logMode ? 'Enabled' : 'Disabled' }}
</div>
</li>
<li class="grid-row">
<div
class="grid-cell label"
title="Automatically scale the Y axis to keep all values in view."
> >
Auto scale <div
</div> :id="getYAxisPropId(index, prop.label)"
<div class="grid-cell value"> class="grid-cell label"
{{ yAxis.autoscale ? 'Enabled: ' + yAxis.autoscalePadding : 'Disabled' }} :title="prop.title"
</div> role="gridcell"
</li> >
<li v-if="!yAxis.autoscale && yAxis.rangeMin !== ''" class="grid-row"> {{ prop.label }}
<div class="grid-cell label" title="Minimum Y axis value.">Minimum value</div> </div>
<div class="grid-cell value">{{ yAxis.rangeMin }}</div> <div class="grid-cell value" role="gridcell">{{ prop.value }}</div>
</li> </li>
<li v-if="!yAxis.autoscale && yAxis.rangeMax !== ''" class="grid-row"> </ul>
<div class="grid-cell label" title="Maximum Y axis value.">Maximum value</div> </li>
<div class="grid-cell value">{{ yAxis.rangeMax }}</div> </ul>
</li> <li v-if="showLegendProperties" class="grid-properties" aria-label="Legend Configuration">
</ul> <ul class="l-inspector-part js-legend-properties" aria-labelledby="legend-header">
</div> <h2 id="legend-header" class="--first">Legend</h2>
<div v-if="isStackedPlotObject || !isNestedWithinAStackedPlot" class="grid-properties"> <li v-for="(prop, key) in legendProperties" :key="key" class="grid-row">
<ul class="l-inspector-part js-legend-properties"> <div class="u-contents" :aria-label="prop.label">
<h2 class="--first" title="Legend settings for this object">Legend</h2> <div class="grid-cell label" :title="prop.title">{{ prop.label }}</div>
<li v-if="isStackedPlotObject" class="grid-row"> <div class="grid-cell value" :class="prop.class">{{ prop.value }}</div>
<div class="grid-cell label" title="Display legends per sub plot.">
Show legend per plot
</div>
<div class="grid-cell value">{{ showLegendsForChildren ? 'Yes' : 'No' }}</div>
</li>
<li v-if="showLegendDetails" class="grid-row">
<div
class="grid-cell label"
title="The position of the legend relative to the plot display area."
>
Position
</div>
<div class="grid-cell value capitalize">{{ position }}</div>
</li>
<li v-if="showLegendDetails" class="grid-row">
<div class="grid-cell label" title="Hide the legend when the plot is small">
Hide when plot small
</div>
<div class="grid-cell value">{{ hideLegendWhenSmall ? 'Yes' : 'No' }}</div>
</li>
<li v-if="showLegendDetails" class="grid-row">
<div class="grid-cell label" title="Show the legend expanded by default">
Expand by Default
</div>
<div aria-label="Expand by Default" class="grid-cell value">
{{ expandByDefault ? 'Yes' : 'No' }}
</div>
</li>
<li v-if="showLegendDetails" class="grid-row">
<div class="grid-cell label" title="What to display in the legend when it's collapsed.">
Show when collapsed:
</div>
<div class="grid-cell value">{{ valueToShowWhenCollapsed.replace('nearest', '') }}</div>
</li>
<li v-if="showLegendDetails" class="grid-row">
<div class="grid-cell label" title="What to display in the legend when it's expanded.">
Show when expanded:
</div>
<div class="grid-cell value comma-list">
<span v-if="showTimestampWhenExpanded">Timestamp</span>
<span v-if="showValueWhenExpanded">Value</span>
<span v-if="showMinimumWhenExpanded">Min</span>
<span v-if="showMaximumWhenExpanded">Max</span>
<span v-if="showUnitsWhenExpanded">Unit</span>
</div> </div>
</li> </li>
</ul> </ul>
</div> </li>
</div> </ul>
</template> </template>
<script> <script>
@ -173,6 +119,60 @@ export default {
}, },
yAxesWithSeries() { yAxesWithSeries() {
return this.yAxes.filter((yAxis) => yAxis.seriesCount > 0); return this.yAxes.filter((yAxis) => yAxis.seriesCount > 0);
},
showPlotSeries() {
return !this.isStackedPlotObject;
},
showYAxisProperties() {
return this.plotSeries.length && !this.isStackedPlotObject;
},
showLegendProperties() {
return this.isStackedPlotObject || !this.isNestedWithinAStackedPlot;
},
legendProperties() {
const props = {};
if (this.isStackedPlotObject) {
props.showLegendsForChildren = {
label: 'Show legend per plot',
title: 'Display legends per sub plot.',
value: this.showLegendsForChildren ? 'Yes' : 'No'
};
}
if (this.showLegendDetails) {
Object.assign(props, {
position: {
label: 'Position',
title: 'The position of the legend relative to the plot display area.',
value: this.position,
class: 'capitalize'
},
hideLegendWhenSmall: {
label: 'Hide when plot small',
title: 'Hide the legend when the plot is small',
value: this.hideLegendWhenSmall ? 'Yes' : 'No'
},
expandByDefault: {
label: 'Expand by Default',
title: 'Show the legend expanded by default',
value: this.expandByDefault ? 'Yes' : 'No'
},
valueToShowWhenCollapsed: {
label: 'Show when collapsed:',
title: "What to display in the legend when it's collapsed.",
value: this.valueToShowWhenCollapsed.replace('nearest', '')
},
expandedValues: {
label: 'Show when expanded:',
title: "What to display in the legend when it's expanded.",
value: this.getExpandedValues(),
class: 'comma-list'
}
});
}
return props;
} }
}, },
mounted() { mounted() {
@ -282,6 +282,76 @@ export default {
if (foundYAxis) { if (foundYAxis) {
foundYAxis.seriesCount = foundYAxis.seriesCount + updateCount; foundYAxis.seriesCount = foundYAxis.seriesCount + updateCount;
} }
},
getYAxisHeaderId(index) {
return `yAxis-${index}-header`;
},
getYAxisPropId(index, label) {
return `y-axis-${index}-${label.toLowerCase().replace(' ', '-')}`;
},
yAxisAriaLabel(yAxis) {
return this.yAxesWithSeries.length > 1
? `Y Axis ${yAxis.id} Properties`
: 'Y Axis Properties';
},
yAxisProperties(yAxis) {
const props = {
label: {
label: 'Label',
title: 'Manually override how the Y axis is labeled.',
value: yAxis.label ? yAxis.label : 'Not defined'
},
logMode: {
label: 'Log mode',
title: 'Enable log mode.',
value: yAxis.logMode ? 'Enabled' : 'Disabled'
},
autoscale: {
label: 'Auto scale',
title: 'Automatically scale the Y axis to keep all values in view.',
value: yAxis.autoscale ? `Enabled: ${yAxis.autoscalePadding}` : 'Disabled'
}
};
if (!yAxis.autoscale) {
if (yAxis.rangeMin !== '') {
props.rangeMin = {
label: 'Minimum value',
title: 'Minimum Y axis value.',
value: yAxis.rangeMin
};
}
if (yAxis.rangeMax !== '') {
props.rangeMax = {
label: 'Maximum value',
title: 'Maximum Y axis value.',
value: yAxis.rangeMax
};
}
}
return props;
},
getExpandedValues() {
const values = [];
if (this.showTimestampWhenExpanded) {
values.push('Timestamp');
}
if (this.showValueWhenExpanded) {
values.push('Value');
}
if (this.showMinimumWhenExpanded) {
values.push('Min');
}
if (this.showMaximumWhenExpanded) {
values.push('Max');
}
if (this.showUnitsWhenExpanded) {
values.push('Unit');
}
return values.join(', ');
} }
} }
}; };

View File

@ -21,8 +21,8 @@
--> -->
<template> <template>
<div v-if="loaded" class="js-plot-options-edit"> <div v-if="loaded" class="js-plot-options-edit">
<ul v-if="!isStackedPlotObject" class="c-tree" aria-label="Plot Series Properties"> <ul v-if="!isStackedPlotObject" class="c-tree" role="tree">
<h2 class="--first" title="Display properties for this object">Plot Series</h2> <h2 class="--first" title="Display properties for this Plot Series object">Plot Series</h2>
<li v-for="series in plotSeries" :key="series.keyString"> <li v-for="series in plotSeries" :key="series.keyString">
<series-form :series="series" @series-updated="updateSeriesConfigForObject" /> <series-form :series="series" @series-updated="updateSeriesConfigForObject" />
</li> </li>
@ -33,15 +33,18 @@
:key="`yAxis-${index}`" :key="`yAxis-${index}`"
class="grid-properties js-yaxis-grid-properties" class="grid-properties js-yaxis-grid-properties"
:y-axis="config.yAxis" :y-axis="config.yAxis"
role="group"
aria-labelledby="y-axis-group"
@series-updated="updateSeriesConfigForObject" @series-updated="updateSeriesConfigForObject"
/> />
<ul <ul
v-if="isStackedPlotObject || !isStackedPlotNestedObject" v-if="isStackedPlotObject || !isStackedPlotNestedObject"
class="l-inspector-part" class="l-inspector-part"
aria-label="Legend Properties" aria-label="Legend Properties"
role="tree"
> >
<h2 class="--first" title="Legend options">Legend</h2> <h2 class="--first" title="Legend options">Legend</h2>
<legend-form class="grid-properties" :legend="config.legend" /> <legend-form role="treeitem" tabindex="0" class="grid-properties" :legend="config.legend" />
</ul> </ul>
</div> </div>
</template> </template>
@ -83,7 +86,10 @@ export default {
); );
}, },
yAxesIds() { yAxesIds() {
return !this.isStackedPlotObject && this.yAxes.filter((yAxis) => yAxis.seriesCount > 0); if (this.isStackedPlotObject) {
return [];
}
return this.yAxes.filter((yAxis) => yAxis.seriesCount > 0);
} }
}, },
created() { created() {

View File

@ -20,98 +20,93 @@
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<template> <template>
<ul> <li class="c-tree__item menus-to-left" :class="isAliasClass" :aria-label="ariaLabel">
<li class="c-tree__item menus-to-left" :class="isAliasClass"> <span
<span class="c-disclosure-triangle is-enabled flex-elem"
class="c-disclosure-triangle is-enabled flex-elem" :class="expandedCssClass"
:class="expandedCssClass" role="button"
@click="toggleExpanded" :aria-label="ariaLabelExpandCollapse"
> tabindex="0"
</span> @click="toggleExpanded"
<div class="c-object-label" :class="statusClass"> @keydown.enter="toggleExpanded"
<div class="c-object-label__type-icon" :class="getSeriesClass"> ></span>
<span class="is-status__indicator" title="This item is missing or suspect"></span> <div class="c-object-label" :class="statusClass">
</div> <div class="c-object-label__type-icon" :class="getSeriesClass">
<div class="c-object-label__name">{{ series.domainObject.name }}</div> <span class="is-status__indicator" title="This item is missing or suspect"></span>
</div> </div>
</li> <div class="c-object-label__name">{{ series.domainObject.name }}</div>
<li v-show="expanded" class="c-tree__item menus-to-left" role="table"> </div>
<ul class="grid-properties js-plot-options-browse-properties" role="rowgroup"> </li>
<li class="grid-row" role="row"> <li v-show="expanded" class="c-tree__item menus-to-left" role="table">
<div <ul class="grid-properties js-plot-options-browse-properties" role="rowgroup">
class="grid-cell label" <li class="grid-row" role="row">
title="The field to be plotted as a value for this series." <div
role="cell" class="grid-cell label"
> title="The field to be plotted as a value for this series."
Value role="cell"
</div> >
<div class="grid-cell value" role="cell"> Value
{{ yKey }} </div>
</div> <div class="grid-cell value" role="cell">
</li> {{ yKey }}
<li class="grid-row" role="row"> </div>
<div </li>
class="grid-cell label" <li class="grid-row" role="row">
title="The rendering method to join lines for this series." <div
role="cell" class="grid-cell label"
> title="The rendering method to join lines for this series."
Line Method role="cell"
</div> >
<div class="grid-cell value" role="cell"> Line Method
{{ </div>
{ <div class="grid-cell value" role="cell">
none: 'None', {{
linear: 'Linear interpolation', {
stepAfter: 'Step After' none: 'None',
}[interpolate] linear: 'Linear interpolation',
}} stepAfter: 'Step After'
</div> }[interpolate]
</li> }}
<li class="grid-row" role="row"> </div>
<div </li>
class="grid-cell label" <li class="grid-row" role="row">
title="Whether markers are displayed, and their size." <div
role="cell" class="grid-cell label"
> title="Whether markers are displayed, and their size."
Markers role="cell"
</div> >
<div class="grid-cell value" role="cell"> Markers
{{ markerOptionsDisplayText }} </div>
</div> <div class="grid-cell value" role="cell">
</li> {{ markerOptionsDisplayText }}
<li class="grid-row" role="row"> </div>
<div </li>
class="grid-cell label" <li class="grid-row" role="row" title="Display markers visually denoting points in alarm.">
title="Display markers visually denoting points in alarm." <div class="grid-cell label" role="cell">Alarm Markers</div>
role="cell" <div class="grid-cell value" role="cell">
> {{ alarmMarkers ? 'Enabled' : 'Disabled' }}
Alarm Markers </div>
</div> </li>
<div class="grid-cell value" role="cell"> <li class="grid-row" role="row">
{{ alarmMarkers ? 'Enabled' : 'Disabled' }} <div
</div> class="grid-cell label"
</li> title="Display lines visually denoting alarm limits."
<li class="grid-row" role="row"> role="cell"
<div >
class="grid-cell label" Limit Lines
title="Display lines visually denoting alarm limits." </div>
role="cell" <div class="grid-cell value" role="cell">
> {{ limitLines ? 'Enabled' : 'Disabled' }}
Limit Lines </div>
</div> </li>
<div class="grid-cell value" role="cell"> <ColorSwatch
{{ limitLines ? 'Enabled' : 'Disabled' }} :current-color="seriesHexColor"
</div> edit-title="Manually set the plot line and marker color for this series."
</li> view-title="The plot line and marker color for this series."
<ColorSwatch short-label="Color"
:current-color="seriesHexColor" />
edit-title="Manually set the plot line and marker color for this series." </ul>
view-title="The plot line and marker color for this series." </li>
short-label="Color"
/>
</ul>
</li>
</ul>
</template> </template>
<script> <script>
@ -137,6 +132,15 @@ export default {
}; };
}, },
computed: { computed: {
ariaLabel() {
return this.series?.domainObject?.name ?? '';
},
ariaLabelExpandCollapse() {
const name = this.series.domainObject.name ? ` ${this.series.domainObject.name}` : '';
const type = this.series.domainObject.type ? ` ${this.series.domainObject.type}` : '';
return `${this.expanded ? 'Collapse' : 'Expand'}${name}${type}`;
},
isAliasClass() { isAliasClass() {
let cssClass = ''; let cssClass = '';
const domainObjectPath = [this.series.domainObject, ...this.path]; const domainObjectPath = [this.series.domainObject, ...this.path];

View File

@ -21,15 +21,19 @@
--> -->
<template> <template>
<ul> <ul>
<li class="c-tree__item menus-to-left" :class="isAliasCss"> <li class="c-tree__item menus-to-left" :class="isAliasCss" role="treeitem">
<span <span
class="c-disclosure-triangle is-enabled flex-elem" class="c-disclosure-triangle is-enabled flex-elem"
:class="expandedCssClass" :class="expandedCssClass"
role="button"
:aria-label="ariaLabelValue"
tabindex="0"
@click="toggleExpanded" @click="toggleExpanded"
@keydown.enter="toggleExpanded"
> >
</span> </span>
<div :class="objectLabelCss"> <div :class="objectLabelCss">
<div class="c-object-label__type-icon" :class="[seriesCss]"> <div class="c-object-label__type-icon" :class="seriesCss">
<span class="is-status__indicator" title="This item is missing or suspect"></span> <span class="is-status__indicator" title="This item is missing or suspect"></span>
</div> </div>
<div class="c-object-label__name">{{ series.domainObject.name }}</div> <div class="c-object-label__name">{{ series.domainObject.name }}</div>
@ -43,12 +47,7 @@
</div> </div>
<div class="grid-cell value"> <div class="grid-cell value">
<select v-model="yKey" @change="updateForm('yKey')"> <select v-model="yKey" @change="updateForm('yKey')">
<option <option v-for="option in yKeyOptions" :key="option.value" :value="option.value">
v-for="option in yKeyOptions"
:key="option.value"
:value="option.value"
:selected="option.value == yKey"
>
{{ option.name }} {{ option.name }}
</option> </option>
</select> </select>
@ -84,10 +83,15 @@
</li> </li>
<li class="grid-row"> <li class="grid-row">
<div class="grid-cell label" title="Display markers visually denoting points in alarm."> <div class="grid-cell label" title="Display markers visually denoting points in alarm.">
Alarm Markers <label for="alarm-markers-checkbox">Alarm Markers</label>
</div> </div>
<div class="grid-cell value"> <div class="grid-cell value">
<input v-model="alarmMarkers" type="checkbox" @change="updateForm('alarmMarkers')" /> <input
id="alarm-markers-checkbox"
v-model="alarmMarkers"
type="checkbox"
@change="updateForm('alarmMarkers')"
/>
</div> </div>
</li> </li>
<li class="grid-row"> <li class="grid-row">
@ -169,18 +173,24 @@ export default {
}; };
}, },
computed: { computed: {
ariaLabelValue() {
const name = this.series.domainObject.name ? ` ${this.series.domainObject.name}` : '';
const type = this.series.domainObject.type ? ` ${this.series.domainObject.type}` : '';
return `${this.expanded ? 'Collapse' : 'Expand'}${name}${type}`;
},
colorPalette() { colorPalette() {
return this.series.collection.palette.groups(); return this.series.collection.palette.groups();
}, },
objectLabelCss() { objectLabelCss() {
return this.status ? `c-object-label is-status--${this.status}'` : 'c-object-label'; return this.status ? `c-object-label is-status--${this.status}` : 'c-object-label';
}, },
seriesCss() { seriesCss() {
let type = this.openmct.types.get(this.series.domainObject.type); const type = this.openmct.types.get(this.series.domainObject.type);
const baseClass = 'c-object-label__type-icon';
const typeClass = type.definition.cssClass || '';
return type.definition.cssClass return `${baseClass} ${typeClass}`.trim();
? `c-object-label__type-icon ${type.definition.cssClass}`
: `c-object-label__type-icon`;
}, },
isAliasCss() { isAliasCss() {
let cssClass = ''; let cssClass = '';

View File

@ -31,25 +31,36 @@
Label Label
</div> </div>
<div class="grid-cell value"> <div class="grid-cell value">
<input v-model="label" class="c-input--flex" type="text" @change="updateForm('label')" /> <label :for="`y-axis-${id}-label`" class="visually-hidden">Y Axis {{ id }} Label</label>
</div>
</li>
<li class="grid-row">
<div id="log-mode-checkbox" class="grid-cell label" title="Enable log mode.">Log mode</div>
<div class="grid-cell value">
<!-- eslint-disable-next-line vue/html-self-closing -->
<input <input
v-model="logMode" :id="`y-axis-${id}-label`"
class="js-log-mode-input" v-model="label"
aria-labelledby="log-mode-checkbox" class="c-input--flex"
type="checkbox" type="text"
@change="updateForm('logMode')" @change="updateForm('label')"
/> />
</div> </div>
</li> </li>
<li class="grid-row">
<div :id="`log-mode-checkbox-${id}`" class="grid-cell label" title="Enable log mode.">
Log mode
</div>
<div class="grid-cell value">
<input
:id="`log-mode-input-${id}`"
v-model="logMode"
class="js-log-mode-input"
type="checkbox"
@change="updateForm('logMode')"
/>
<label :for="`log-mode-input-${id}`" class="visually-hidden"
>Y Axis {{ id }} Log mode</label
>
</div>
</li>
<li class="grid-row"> <li class="grid-row">
<div <div
id="autoscale-checkbox" :id="`autoscale-checkbox-${id}`"
class="grid-cell label" class="grid-cell label"
title="Automatically scale the Y axis to keep all values in view." title="Automatically scale the Y axis to keep all values in view."
> >
@ -57,11 +68,14 @@
</div> </div>
<div class="grid-cell value"> <div class="grid-cell value">
<input <input
:id="`autoscale-input-${id}`"
v-model="autoscale" v-model="autoscale"
type="checkbox" type="checkbox"
aria-labelledby="autoscale-checkbox"
@change="updateForm('autoscale')" @change="updateForm('autoscale')"
/> />
<label :for="`autoscale-input-${id}`" class="visually-hidden"
>Y Axis {{ id }} Auto scale</label
>
</div> </div>
</li> </li>
<li v-show="autoscale" class="grid-row"> <li v-show="autoscale" class="grid-row">
@ -72,7 +86,11 @@
Padding Padding
</div> </div>
<div class="grid-cell value"> <div class="grid-cell value">
<label :for="`autoscale-padding-${id}`" class="visually-hidden"
>Y Axis {{ id }} Autoscale Padding</label
>
<input <input
:id="`autoscale-padding-${id}`"
v-model="autoscalePadding" v-model="autoscalePadding"
class="c-input--flex" class="c-input--flex"
type="text" type="text"
@ -88,8 +106,12 @@
<li class="grid-row force-border"> <li class="grid-row force-border">
<div class="grid-cell label" title="Minimum Y axis value.">Minimum Value</div> <div class="grid-cell label" title="Minimum Y axis value.">Minimum Value</div>
<div class="grid-cell value"> <div class="grid-cell value">
<label :for="`range-min-${id}`" class="visually-hidden"
>Y Axis {{ id }} Minimum value</label
>
<input <input
v-model.lazy="rangeMin" :id="`range-min-${id}`"
v-model="rangeMin"
class="c-input--flex" class="c-input--flex"
type="number" type="number"
@change="updateForm('range')" @change="updateForm('range')"
@ -99,8 +121,12 @@
<li class="grid-row"> <li class="grid-row">
<div class="grid-cell label" title="Maximum Y axis value.">Maximum Value</div> <div class="grid-cell label" title="Maximum Y axis value.">Maximum Value</div>
<div class="grid-cell value"> <div class="grid-cell value">
<label :for="`range-max-${id}`" class="visually-hidden"
>Y Axis {{ id }} Maximum value</label
>
<input <input
v-model.lazy="rangeMax" :id="`range-max-${id}`"
v-model="rangeMax"
class="c-input--flex" class="c-input--flex"
type="number" type="number"
@change="updateForm('range')" @change="updateForm('range')"

View File

@ -122,8 +122,11 @@ export default {
'grid-lines', 'grid-lines',
'plot-y-tick-width' 'plot-y-tick-width'
], ],
mounted() { beforeMount() {
// We must do this before mounted to use any series configuration options set at the stacked plot level
this.updateView(); this.updateView();
},
mounted() {
this.isEditing = this.openmct.editor.isEditing(); this.isEditing = this.openmct.editor.isEditing();
this.openmct.editor.on('isEditing', this.setEditState); this.openmct.editor.on('isEditing', this.setEditState);
this.setupClockChangedEvent((domainObject) => { this.setupClockChangedEvent((domainObject) => {

View File

@ -28,8 +28,10 @@
role="tab" role="tab"
class="c-inspector__tab c-tab" class="c-inspector__tab c-tab"
:class="{ 'is-current': isSelected(tab) }" :class="{ 'is-current': isSelected(tab) }"
tabindex="0"
:title="tab.name" :title="tab.name"
@click="selectTab(tab)" @click="selectTab(tab)"
@keydown.enter="selectTab(tab)"
> >
<span class="c-inspector__tab-name c-tab__name">{{ tab.name }}</span> <span class="c-inspector__tab-name c-tab__name">{{ tab.name }}</span>
</div> </div>