Fix stacked plots legend (#6199)

* Add listeners to remove stacked plot series and make keys unique

* don't add overlay plots to stacked plot legends

* Ensure series colors are drawn correctly in the plot legend

* Remove legend from mct plot. Remove series reactivity from stackd plot and add them to the legend instead.

* Clean up stacked plots so that the plot legend needs fewer props
Also make sure that plot selection inside a stacked plot works - this had regressed due to plot annotations

* Fix console error in plot elements pool and plot legend - reset arrays to empty
* Ensure color in the y axis swatch updates correctly

* Fix small issues with removing objects from STacked plots

* Fix selection for annotations and also select stacked plot child items

* fix notebook tagging

* remove unused annotation editor and change selection to single object

* remove reference to deleted css

* fix e2e tests

* Fix small typos into the selection context for Notebooks.

* Add a typ that identifies that an annotation selection is coming from a search result

---------

Co-authored-by: Scott Bell <scott@traclabs.com>
This commit is contained in:
Shefali Joshi
2023-02-01 10:14:02 -08:00
committed by GitHub
parent 393c801426
commit f570424357
25 changed files with 416 additions and 384 deletions

View File

@ -41,6 +41,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
}); });
test('Inspect Notebook Entry Network Requests', async ({ page }) => { test('Inspect Notebook Entry Network Requests', async ({ page }) => {
await page.getByText('Annotations').click();
// Expand sidebar // Expand sidebar
await page.locator('.c-notebook__toggle-nav-button').click(); await page.locator('.c-notebook__toggle-nav-button').click();
@ -162,20 +163,20 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
await page.locator('[aria-label="Notebook Entry Input"] >> nth=2').press('Enter'); await page.locator('[aria-label="Notebook Entry Input"] >> nth=2').press('Enter');
// Add three tags // Add three tags
await page.hover(`button:has-text("Add Tag") >> nth=2`); await page.hover(`button:has-text("Add Tag")`);
await page.locator(`button:has-text("Add Tag") >> nth=2`).click(); await page.locator(`button:has-text("Add Tag")`).click();
await page.locator('[placeholder="Type to select tag"]').click(); await page.locator('[placeholder="Type to select tag"]').click();
await page.locator('[aria-label="Autocomplete Options"] >> text=Science').click(); await page.locator('[aria-label="Autocomplete Options"] >> text=Science').click();
await page.waitForSelector('[aria-label="Tag"]:has-text("Science")'); await page.waitForSelector('[aria-label="Tag"]:has-text("Science")');
await page.hover(`button:has-text("Add Tag") >> nth=2`); await page.hover(`button:has-text("Add Tag")`);
await page.locator(`button:has-text("Add Tag") >> nth=2`).click(); await page.locator(`button:has-text("Add Tag")`).click();
await page.locator('[placeholder="Type to select tag"]').click(); await page.locator('[placeholder="Type to select tag"]').click();
await page.locator('[aria-label="Autocomplete Options"] >> text=Drilling').click(); await page.locator('[aria-label="Autocomplete Options"] >> text=Drilling').click();
await page.waitForSelector('[aria-label="Tag"]:has-text("Drilling")'); await page.waitForSelector('[aria-label="Tag"]:has-text("Drilling")');
await page.hover(`button:has-text("Add Tag") >> nth=2`); await page.hover(`button:has-text("Add Tag")`);
await page.locator(`button:has-text("Add Tag") >> nth=2`).click(); await page.locator(`button:has-text("Add Tag")`).click();
await page.locator('[placeholder="Type to select tag"]').click(); await page.locator('[placeholder="Type to select tag"]').click();
await page.locator('[aria-label="Autocomplete Options"] >> text=Driving').click(); await page.locator('[aria-label="Autocomplete Options"] >> text=Driving').click();
await page.waitForSelector('[aria-label="Tag"]:has-text("Driving")'); await page.waitForSelector('[aria-label="Tag"]:has-text("Driving")');
@ -231,6 +232,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
type: 'issue', type: 'issue',
description: 'https://github.com/akhenry/openmct-yamcs/issues/69' description: 'https://github.com/akhenry/openmct-yamcs/issues/69'
}); });
await page.getByText('Annotations').click();
await page.locator('text=To start a new entry, click here or drag and drop any object').click(); await page.locator('text=To start a new entry, click here or drag and drop any object').click();
await page.locator('[aria-label="Notebook Entry Input"]').click(); await page.locator('[aria-label="Notebook Entry Input"]').click();
await page.locator('[aria-label="Notebook Entry Input"]').fill(`First Entry`); await page.locator('[aria-label="Notebook Entry Input"]').fill(`First Entry`);

View File

@ -381,7 +381,7 @@ export default {
}); });
}, },
updateSelection(selection) { updateSelection(selection) {
if (selection?.[0]?.[1]?.context?.targetDetails?.entryId === undefined) { if (selection?.[0]?.[0]?.context?.targetDetails?.entryId === undefined) {
this.selectedEntryId = ''; this.selectedEntryId = '';
} }
}, },

View File

@ -472,16 +472,11 @@ export default {
targetDomainObjects[keyString] = this.domainObject; targetDomainObjects[keyString] = this.domainObject;
this.openmct.selection.select( this.openmct.selection.select(
[ [
{
element: this.openmct.layout.$refs.browseObject.$el,
context: {
item: this.domainObject
}
},
{ {
element: event.currentTarget, element: event.currentTarget,
context: { context: {
type: 'notebook-entry-selection', type: 'notebook-entry-selection',
item: this.domainObject,
targetDetails, targetDetails,
targetDomainObjects, targetDomainObjects,
annotations: this.notebookAnnotations, annotations: this.notebookAnnotations,

View File

@ -23,16 +23,8 @@
<div <div
v-if="loaded" v-if="loaded"
class="gl-plot" class="gl-plot"
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
> >
<plot-legend <slot></slot>
v-if="!isNestedWithinAStackedPlot"
:cursor-locked="!!lockHighlightPoint"
:series="seriesModels"
:highlights="highlights"
:legend="legend"
@legendHoverChanged="legendHoverChanged"
/>
<div class="plot-wrapper-axis-and-display-area flex-elem grows"> <div class="plot-wrapper-axis-and-display-area flex-elem grows">
<div <div
v-if="seriesModels.length" v-if="seriesModels.length"
@ -94,7 +86,6 @@
:highlights="highlights" :highlights="highlights"
:annotated-points="annotatedPoints" :annotated-points="annotatedPoints"
:annotation-selections="annotationSelections" :annotation-selections="annotationSelections"
:show-limit-line-labels="showLimitLineLabels"
:hidden-y-axis-ids="hiddenYAxisIds" :hidden-y-axis-ids="hiddenYAxisIds"
:annotation-viewing-and-editing-allowed="annotationViewingAndEditingAllowed" :annotation-viewing-and-editing-allowed="annotationViewingAndEditingAllowed"
@plotReinitializeCanvas="initCanvas" @plotReinitializeCanvas="initCanvas"
@ -217,7 +208,6 @@ import LinearScale from "./LinearScale";
import PlotConfigurationModel from './configuration/PlotConfigurationModel'; import PlotConfigurationModel from './configuration/PlotConfigurationModel';
import configStore from './configuration/ConfigStore'; import configStore from './configuration/ConfigStore';
import PlotLegend from "./legend/PlotLegend.vue";
import MctTicks from "./MctTicks.vue"; import MctTicks from "./MctTicks.vue";
import MctChart from "./chart/MctChart.vue"; import MctChart from "./chart/MctChart.vue";
import XAxis from "./axis/XAxis.vue"; import XAxis from "./axis/XAxis.vue";
@ -232,7 +222,6 @@ export default {
components: { components: {
XAxis, XAxis,
YAxis, YAxis,
PlotLegend,
MctTicks, MctTicks,
MctChart MctChart
}, },
@ -296,7 +285,6 @@ export default {
isRealTime: this.openmct.time.clock() !== undefined, isRealTime: this.openmct.time.clock() !== undefined,
loaded: false, loaded: false,
isTimeOutOfSync: false, isTimeOutOfSync: false,
showLimitLineLabels: this.limitLineLabels,
isFrozenOnMouseDown: false, isFrozenOnMouseDown: false,
cursorGuide: this.initCursorGuide, cursorGuide: this.initCursorGuide,
gridLines: this.initGridLines, gridLines: this.initGridLines,
@ -334,23 +322,9 @@ export default {
return this.config.xAxis.get('frozen') === true && this.config.yAxis.get('frozen') === true; return this.config.xAxis.get('frozen') === true && this.config.yAxis.get('frozen') === true;
}, },
annotationViewingAndEditingAllowed() { annotationViewingAndEditingAllowed() {
// only allow annotations viewing/editing if plot is paused or in fixed time mode // only allow annotations viewing/editing if plot is paused or in fixed time mode
return this.isFrozen || !this.isRealTime; return this.isFrozen || !this.isRealTime;
}, },
plotLegendPositionClass() {
return !this.isNestedWithinAStackedPlot ? `plot-legend-${this.config.legend.get('position')}` : '';
},
plotLegendExpandedStateClass() {
if (this.isNestedWithinAStackedPlot) {
return '';
}
if (this.config.legend.get('expanded')) {
return 'plot-legend-expanded';
} else {
return 'plot-legend-collapsed';
}
},
plotLeftTickWidth() { plotLeftTickWidth() {
let leftTickWidth = 0; let leftTickWidth = 0;
this.yAxes.forEach((yAxis) => { this.yAxes.forEach((yAxis) => {
@ -365,12 +339,6 @@ export default {
} }
}, },
watch: { watch: {
limitLineLabels: {
handler(limitLineLabels) {
this.legendHoverChanged(limitLineLabels);
},
deep: true
},
initGridLines(newGridLines) { initGridLines(newGridLines) {
this.gridLines = newGridLines; this.gridLines = newGridLines;
}, },
@ -406,8 +374,7 @@ export default {
})); }));
} }
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier); this.$emit('configLoaded', true);
this.$emit('configLoaded', configId);
this.listenTo(this.config.series, 'add', this.addSeries, this); this.listenTo(this.config.series, 'add', this.addSeries, this);
this.listenTo(this.config.series, 'remove', this.removeSeries, this); this.listenTo(this.config.series, 'remove', this.removeSeries, this);
@ -439,15 +406,20 @@ export default {
methods: { methods: {
updateSelection(selection) { updateSelection(selection) {
const selectionContext = selection?.[0]?.[0]?.context?.item; const selectionContext = selection?.[0]?.[0]?.context?.item;
if (!selectionContext // on clicking on a search result we highlight the annotation and zoom - we know it's an annotation result when isAnnotationSearchResult === true
|| this.openmct.objects.areIdsEqual(selectionContext.identifier, this.domainObject.identifier)) { // We shouldn't zoom when we're selecting existing annotations to view them or creating new annotations.
// Selection changed, but it's us, so ignoring it const selectionType = selection?.[0]?.[0]?.context?.type;
const validSelectionTypes = ['clicked-on-plot-selection', 'plot-annotation-search-result'];
const isAnnotationSearchResult = selectionType === 'plot-annotation-search-result';
if (!validSelectionTypes.includes(selectionType)) {
// wrong type of selection
return; return;
} }
const selectionType = selection?.[0]?.[1]?.context?.type; if (selectionContext
if (selectionType !== 'plot-points-selection') { && (!isAnnotationSearchResult)
// wrong type of selection && this.openmct.objects.areIdsEqual(selectionContext.identifier, this.domainObject.identifier)) {
return; return;
} }
@ -460,7 +432,18 @@ export default {
return; return;
} }
const selectedAnnotations = selection?.[0]?.[1]?.context?.annotations; const selectedAnnotations = selection?.[0]?.[0]?.context?.annotations;
//This section is only for the annotations search results entry to displaying annotations
if (isAnnotationSearchResult) {
this.showAnnotationsFromSearchResults(selectedAnnotations);
}
//This section is common to all entry points for annotation display
this.prepareExistingAnnotationSelection(selectedAnnotations);
},
showAnnotationsFromSearchResults(selectedAnnotations) {
//Start section
if (selectedAnnotations?.length) { if (selectedAnnotations?.length) {
// just use first annotation // just use first annotation
const boundingBoxes = Object.values(selectedAnnotations[0].targets); const boundingBoxes = Object.values(selectedAnnotations[0].targets);
@ -494,10 +477,9 @@ export default {
min: minY, min: minY,
max: maxY max: maxY
}); });
//Zoom out just a touch so that the highlighted section for annotations doesn't take over the whole view - which is not a nice look.
this.zoom('out', 0.2); this.zoom('out', 0.2);
} }
this.prepareExistingAnnotationSelection(selectedAnnotations);
}, },
handleKeyDown(event) { handleKeyDown(event) {
if (event.key === 'Alt') { if (event.key === 'Alt') {
@ -688,9 +670,15 @@ export default {
series.reset(); series.reset();
}); });
}, },
shareCommonParent(domainObjectToFind) {
return false;
},
compositionPathContainsId(domainObjectToFind) {
if (!domainObjectToFind.composition) {
return false;
}
compositionPathContainsId(domainObjectToClear) { return domainObjectToFind.composition.some((compositionIdentifier) => {
return domainObjectToClear.composition.some((compositionIdentifier) => {
return this.openmct.objects.areIdsEqual(compositionIdentifier, this.domainObject.identifier); return this.openmct.objects.areIdsEqual(compositionIdentifier, this.domainObject.identifier);
}); });
}, },
@ -1044,8 +1032,6 @@ export default {
highlightValues(point) { highlightValues(point) {
this.highlightPoint = point; this.highlightPoint = point;
// TODO: used in StackedPlotController
this.$emit('plotHighlightUpdate', point);
if (this.lockHighlightPoint) { if (this.lockHighlightPoint) {
return; return;
} }
@ -1157,7 +1143,7 @@ export default {
endPixels: this.positionOverElement, endPixels: this.positionOverElement,
start: this.positionOverPlot, start: this.positionOverPlot,
end: this.positionOverPlot, end: this.positionOverPlot,
color: [1, 1, 1, 0.5] color: [1, 1, 1, 0.25]
}; };
if (annotationEvent) { if (annotationEvent) {
this.marquee.annotationEvent = true; this.marquee.annotationEvent = true;
@ -1168,13 +1154,21 @@ export default {
} }
}, },
selectNearbyAnnotations(event) { selectNearbyAnnotations(event) {
// need to stop propagation right away to prevent selecting the plot itself
event.stopPropagation(); event.stopPropagation();
if (!this.annotationViewingAndEditingAllowed || this.annotationSelections.length) { if (!this.annotationViewingAndEditingAllowed || this.annotationSelections.length) {
return; return;
} }
const nearbyAnnotations = this.gatherNearbyAnnotations(); const nearbyAnnotations = this.gatherNearbyAnnotations();
if (!nearbyAnnotations.length) {
const emptySelection = this.createPathSelection();
this.openmct.selection.select(emptySelection, true);
// should show plot itself if we didn't find any annotations
return;
}
const { targetDomainObjects, targetDetails } = this.prepareExistingAnnotationSelection(nearbyAnnotations); const { targetDomainObjects, targetDetails } = this.prepareExistingAnnotationSelection(nearbyAnnotations);
this.selectPlotAnnotations({ this.selectPlotAnnotations({
targetDetails, targetDetails,
@ -1182,33 +1176,50 @@ export default {
annotations: nearbyAnnotations annotations: nearbyAnnotations
}); });
}, },
createPathSelection() {
let selection = [];
this.path.forEach((pathObject, index) => {
selection.push({
element: this.openmct.layout.$refs.browseObject.$el,
context: {
item: pathObject
}
});
});
return selection;
},
selectPlotAnnotations({targetDetails, targetDomainObjects, annotations}) { selectPlotAnnotations({targetDetails, targetDomainObjects, annotations}) {
const selection = const annotationContext = {
[ type: 'clicked-on-plot-selection',
{ targetDetails,
element: this.openmct.layout.$refs.browseObject.$el, targetDomainObjects,
context: { annotations,
item: this.domainObject annotationType: this.openmct.annotation.ANNOTATION_TYPES.PLOT_SPATIAL,
} onAnnotationChange: this.onAnnotationChange
}, };
{ const selection = this.createPathSelection();
element: this.$el, if (selection.length && this.openmct.objects.areIdsEqual(selection[0].context.item.identifier, this.domainObject.identifier)) {
context: { selection[0].context = {
type: 'plot-points-selection', ...selection[0].context,
targetDetails, ...annotationContext
targetDomainObjects, };
annotations, } else {
annotationType: this.openmct.annotation.ANNOTATION_TYPES.PLOT_SPATIAL, selection.unshift({
onAnnotationChange: this.onAnnotationChange element: this.$el,
} context: {
} item: this.domainObject,
]; ...annotationContext
}
});
}
this.openmct.selection.select(selection, true); this.openmct.selection.select(selection, true);
}, },
selectNewPlotAnnotations(boundingBoxPerYAxis, pointsInBox, event) { selectNewPlotAnnotations(boundingBoxPerYAxis, pointsInBox, event) {
let targetDomainObjects = {}; let targetDomainObjects = {};
let targetDetails = {}; let targetDetails = {};
let annotations = {}; let annotations = [];
pointsInBox.forEach(pointInBox => { pointsInBox.forEach(pointInBox => {
if (pointInBox.length) { if (pointInBox.length) {
const seriesID = pointInBox[0].series.keyString; const seriesID = pointInBox[0].series.keyString;
@ -1752,9 +1763,6 @@ export default {
this.config.series.models.forEach(this.loadSeriesData, this); this.config.series.models.forEach(this.loadSeriesData, this);
} }
}, },
legendHoverChanged(data) {
this.showLimitLineLabels = data;
},
toggleCursorGuide() { toggleCursorGuide() {
this.cursorGuide = !this.cursorGuide; this.cursorGuide = !this.cursorGuide;
this.$emit('cursorGuide', this.cursorGuide); this.$emit('cursorGuide', this.cursorGuide);

View File

@ -36,12 +36,26 @@
:model="{progressPerc: undefined}" :model="{progressPerc: undefined}"
/> />
<mct-plot <mct-plot
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
:init-grid-lines="gridLines" :init-grid-lines="gridLines"
:init-cursor-guide="cursorGuide" :init-cursor-guide="cursorGuide"
:options="options" :options="options"
:limit-line-labels="limitLineLabels"
@loadingUpdated="loadingUpdated" @loadingUpdated="loadingUpdated"
@statusUpdated="setStatus" @statusUpdated="setStatus"
/> @configLoaded="updateReady"
@lockHighlightPoint="lockHighlightPointUpdated"
@highlights="highlightsUpdated"
>
<plot-legend
v-if="configReady"
:cursor-locked="lockHighlightPoint"
:highlights="highlights"
@legendHoverChanged="legendHoverChanged"
@expanded="updateExpanded"
@position="updatePosition"
/>
</mct-plot>
</div> </div>
</div> </div>
</template> </template>
@ -50,13 +64,15 @@
import eventHelpers from './lib/eventHelpers'; import eventHelpers from './lib/eventHelpers';
import ImageExporter from '../../exporters/ImageExporter'; import ImageExporter from '../../exporters/ImageExporter';
import MctPlot from './MctPlot.vue'; import MctPlot from './MctPlot.vue';
import PlotLegend from "./legend/PlotLegend.vue";
import ProgressBar from "../../ui/components/ProgressBar.vue"; import ProgressBar from "../../ui/components/ProgressBar.vue";
import StalenessUtils from '@/utils/staleness'; import StalenessUtils from '@/utils/staleness';
export default { export default {
components: { components: {
MctPlot, MctPlot,
ProgressBar ProgressBar,
PlotLegend
}, },
inject: ['openmct', 'domainObject', 'path'], inject: ['openmct', 'domainObject', 'path'],
props: { props: {
@ -77,7 +93,13 @@ export default {
gridLines: !this.options.compact, gridLines: !this.options.compact,
loading: false, loading: false,
status: '', status: '',
staleObjects: [] staleObjects: [],
limitLineLabels: undefined,
lockHighlightPoint: false,
highlights: [],
expanded: false,
position: undefined,
configReady: false
}; };
}, },
computed: { computed: {
@ -87,6 +109,16 @@ export default {
} }
return ''; return '';
},
plotLegendPositionClass() {
return this.position ? `plot-legend-${this.position}` : '';
},
plotLegendExpandedStateClass() {
if (this.expanded) {
return 'plot-legend-expanded';
} else {
return 'plot-legend-collapsed';
}
} }
}, },
mounted() { mounted() {
@ -183,6 +215,24 @@ export default {
exportPNG: this.exportPNG, exportPNG: this.exportPNG,
exportJPG: this.exportJPG exportJPG: this.exportJPG
}; };
},
lockHighlightPointUpdated(data) {
this.lockHighlightPoint = data;
},
highlightsUpdated(data) {
this.highlights = data;
},
legendHoverChanged(data) {
this.limitLineLabels = data;
},
updateExpanded(expanded) {
this.expanded = expanded;
},
updatePosition(position) {
this.position = position;
},
updateReady(ready) {
this.configReady = ready;
} }
} }
}; };

View File

@ -202,6 +202,7 @@ export default {
} }
this.listenTo(series, 'change:yAxisId', this.addOrRemoveSeries.bind(this, series), this); this.listenTo(series, 'change:yAxisId', this.addOrRemoveSeries.bind(this, series), this);
this.listenTo(series, 'change:color', this.updateSeriesColors.bind(this, series), this);
}, },
removeSeries(plotSeries) { removeSeries(plotSeries) {
const seriesIndex = this.seriesModels.findIndex(model => this.openmct.objects.areIdsEqual(model.get('identifier'), plotSeries.get('identifier'))); const seriesIndex = this.seriesModels.findIndex(model => this.openmct.objects.areIdsEqual(model.get('identifier'), plotSeries.get('identifier')));
@ -216,6 +217,9 @@ export default {
return model.get('yKey') === this.seriesModels[0].get('yKey'); return model.get('yKey') === this.seriesModels[0].get('yKey');
}); });
this.singleSeries = this.seriesModels.length === 1; this.singleSeries = this.seriesModels.length === 1;
this.updateSeriesColors();
},
updateSeriesColors() {
this.seriesColors = this.seriesModels.map(model => { this.seriesColors = this.seriesModels.map(model => {
return model.get('color').asHexString(); return model.get('color').asHexString();
}); });

View File

@ -533,7 +533,6 @@ export default {
}, },
updateLimitsAndDraw() { updateLimitsAndDraw() {
this.drawLimitLines(); this.drawLimitLines();
this.scheduleDraw();
}, },
scheduleDraw() { scheduleDraw() {
if (!this.drawScheduled) { if (!this.drawScheduled) {

View File

@ -67,6 +67,10 @@ export default class SeriesCollection extends Collection {
}, this); }, this);
} }
watchTelemetryContainer(domainObject) { watchTelemetryContainer(domainObject) {
if (domainObject.type === 'telemetry.plot.stacked') {
return;
}
const composition = this.openmct.composition.get(domainObject); const composition = this.openmct.composition.get(domainObject);
this.listenTo(composition, 'add', this.addTelemetryObject, this); this.listenTo(composition, 'add', this.addTelemetryObject, this);
this.listenTo(composition, 'remove', this.removeTelemetryObject, this); this.listenTo(composition, 'remove', this.removeTelemetryObject, this);

View File

@ -93,7 +93,7 @@
</ul> </ul>
</div> </div>
<div <div
v-if="plotSeries.length && (isStackedPlotObject || !isNestedWithinAStackedPlot)" v-if="isStackedPlotObject || !isNestedWithinAStackedPlot"
class="grid-properties" class="grid-properties"
> >
<ul <ul
@ -190,10 +190,13 @@ export default {
mounted() { mounted() {
eventHelpers.extend(this); eventHelpers.extend(this);
this.config = this.getConfig(); this.config = this.getConfig();
this.initYAxesConfiguration(); if (!this.isStackedPlotObject) {
this.initYAxesConfiguration();
this.registerListeners();
} else {
this.initLegendConfiguration();
}
this.registerListeners();
this.initLegendConfiguration();
this.loaded = true; this.loaded = true;
}, },
@ -245,9 +248,9 @@ export default {
} }
}, },
getConfig() { getConfig() {
this.configId = this.openmct.objects.makeKeyString(this.domainObject.identifier); const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
return configStore.get(this.configId); return configStore.get(configId);
}, },
registerListeners() { registerListeners() {
if (this.config) { if (this.config) {

View File

@ -53,7 +53,6 @@
> >
<h2 title="Legend options">Legend</h2> <h2 title="Legend options">Legend</h2>
<legend-form <legend-form
v-if="plotSeries.length"
class="grid-properties" class="grid-properties"
:legend="config.legend" :legend="config.legend"
/> />
@ -97,20 +96,23 @@ export default {
mounted() { mounted() {
eventHelpers.extend(this); eventHelpers.extend(this);
this.config = this.getConfig(); this.config = this.getConfig();
this.yAxes = [{ if (!this.isStackedPlotObject) {
id: this.config.yAxis.id, this.yAxes = [{
seriesCount: 0 id: this.config.yAxis.id,
}]; seriesCount: 0
if (this.config.additionalYAxes) { }];
this.yAxes = this.yAxes.concat(this.config.additionalYAxes.map(yAxis => { if (this.config.additionalYAxes) {
return { this.yAxes = this.yAxes.concat(this.config.additionalYAxes.map(yAxis => {
id: yAxis.id, return {
seriesCount: 0 id: yAxis.id,
}; seriesCount: 0
})); };
}));
}
this.registerListeners();
} }
this.registerListeners();
this.loaded = true; this.loaded = true;
}, },
beforeDestroy() { beforeDestroy() {

View File

@ -12,11 +12,12 @@ export default function PlotsInspectorViewProvider(openmct) {
} }
let object = selection[0][0].context.item; let object = selection[0][0].context.item;
let parent = selection[0].length > 1 && selection[0][1].context.item;
const isOverlayPlotObject = object && object.type === 'telemetry.plot.overlay'; const isOverlayPlotObject = object && object.type === 'telemetry.plot.overlay';
const isStackedPlotObject = object && object.type === 'telemetry.plot.stacked'; const isParentStackedPlotObject = parent && parent.type === 'telemetry.plot.stacked';
return isStackedPlotObject || isOverlayPlotObject; return isOverlayPlotObject || isParentStackedPlotObject;
}, },
view: function (selection) { view: function (selection) {
let component; let component;

View File

@ -12,12 +12,10 @@ export default function StackedPlotsInspectorViewProvider(openmct) {
} }
const object = selection[0][0].context.item; const object = selection[0][0].context.item;
const parent = selection[0].length > 1 && selection[0][1].context.item;
const isOverlayPlotObject = object && object.type === 'telemetry.plot.overlay'; const isStackedPlotObject = object && object.type === 'telemetry.plot.stacked';
const isParentStackedPlotObject = parent && parent.type === 'telemetry.plot.stacked';
return !isOverlayPlotObject && isParentStackedPlotObject; return isStackedPlotObject;
}, },
view: function (selection) { view: function (selection) {
let component; let component;

View File

@ -49,10 +49,10 @@
title="Cursor is point locked. Click anywhere in the plot to unlock." title="Cursor is point locked. Click anywhere in the plot to unlock."
></div> ></div>
<plot-legend-item-collapsed <plot-legend-item-collapsed
v-for="(seriesObject, seriesIndex) in series" v-for="(seriesObject, seriesIndex) in seriesModels"
:key="`${seriesObject.keyString}-${seriesIndex}`" :key="`${seriesObject.keyString}-${seriesIndex}-collapsed`"
:highlights="highlights" :highlights="highlights"
:value-to-show-when-collapsed="legend.get('valueToShowWhenCollapsed')" :value-to-show-when-collapsed="valueToShowWhenCollapsed"
:series-object="seriesObject" :series-object="seriesObject"
@legendHoverChanged="legendHoverChanged" @legendHoverChanged="legendHoverChanged"
/> />
@ -95,11 +95,10 @@
</thead> </thead>
<tbody> <tbody>
<plot-legend-item-expanded <plot-legend-item-expanded
v-for="(seriesObject, seriesIndex) in series" v-for="(seriesObject, seriesIndex) in seriesModels"
:key="`${seriesObject.keyString}-${seriesIndex}-expanded`" :key="`${seriesObject.keyString}-${seriesIndex}-expanded`"
:series-object="seriesObject" :series-object="seriesObject"
:highlights="highlights" :highlights="highlights"
:legend="legend"
@legendHoverChanged="legendHoverChanged" @legendHoverChanged="legendHoverChanged"
/> />
</tbody> </tbody>
@ -111,6 +110,9 @@
<script> <script>
import PlotLegendItemCollapsed from "./PlotLegendItemCollapsed.vue"; import PlotLegendItemCollapsed from "./PlotLegendItemCollapsed.vue";
import PlotLegendItemExpanded from "./PlotLegendItemExpanded.vue"; import PlotLegendItemExpanded from "./PlotLegendItemExpanded.vue";
import configStore from "../configuration/ConfigStore";
import eventHelpers from "../lib/eventHelpers";
export default { export default {
components: { components: {
PlotLegendItemExpanded, PlotLegendItemExpanded,
@ -124,57 +126,113 @@ export default {
return false; return false;
} }
}, },
series: {
type: Array,
default() {
return [];
}
},
highlights: { highlights: {
type: Array, type: Array,
default() { default() {
return []; return [];
} }
},
legend: {
type: Object,
default() {
return {};
}
} }
}, },
data() { data() {
return { return {
isLegendExpanded: this.legend.get('expanded') === true isLegendExpanded: false,
seriesModels: [],
loaded: false
}; };
}, },
computed: { computed: {
showUnitsWhenExpanded() { showUnitsWhenExpanded() {
return this.legend.get('showUnitsWhenExpanded') === true; return this.loaded && this.legend.get('showUnitsWhenExpanded') === true;
}, },
showMinimumWhenExpanded() { showMinimumWhenExpanded() {
return this.legend.get('showMinimumWhenExpanded') === true; return this.loaded && this.legend.get('showMinimumWhenExpanded') === true;
}, },
showMaximumWhenExpanded() { showMaximumWhenExpanded() {
return this.legend.get('showMaximumWhenExpanded') === true; return this.loaded && this.legend.get('showMaximumWhenExpanded') === true;
}, },
showValueWhenExpanded() { showValueWhenExpanded() {
return this.legend.get('showValueWhenExpanded') === true; return this.loaded && this.legend.get('showValueWhenExpanded') === true;
}, },
showTimestampWhenExpanded() { showTimestampWhenExpanded() {
return this.legend.get('showTimestampWhenExpanded') === true; return this.loaded && this.legend.get('showTimestampWhenExpanded') === true;
}, },
isLegendHidden() { isLegendHidden() {
return this.legend.get('hideLegendWhenSmall') === true; return this.loaded && this.legend.get('hideLegendWhenSmall') === true;
},
valueToShowWhenCollapsed() {
return this.loaded && this.legend.get('valueToShowWhenCollapsed');
} }
}, },
mounted() {
this.seriesModels = [];
eventHelpers.extend(this);
this.config = this.getConfig();
this.legend = this.config.legend;
this.loaded = true;
this.isLegendExpanded = this.legend.get('expanded') === true;
this.listenTo(this.config.legend, 'change:position', this.updatePosition, this);
this.updatePosition();
this.initialize();
},
beforeDestroy() {
if (this.objectComposition) {
this.objectComposition.off('add', this.addTelemetryObject);
this.objectComposition.off('remove', this.removeTelemetryObject);
}
this.stopListening();
},
methods: { methods: {
initialize() {
if (this.domainObject.type === 'telemetry.plot.stacked') {
this.objectComposition = this.openmct.composition.get(this.domainObject);
this.objectComposition.on('add', this.addTelemetryObject);
this.objectComposition.on('remove', this.removeTelemetryObject);
this.objectComposition.load();
} else {
this.registerListeners(this.config);
}
},
getConfig() {
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
return configStore.get(configId);
},
addTelemetryObject(object) {
//get the config for each child
const configId = this.openmct.objects.makeKeyString(object.identifier);
const config = configStore.get(configId);
if (config) {
this.registerListeners(config);
}
},
registerListeners(config) {
//listen to any changes to the telemetry endpoints that are associated with the child
this.listenTo(config.series, 'add', this.addSeries, this);
this.listenTo(config.series, 'remove', this.removeSeries, this);
config.series.forEach(this.addSeries, this);
},
addSeries(series) {
this.$set(this.seriesModels, this.seriesModels.length, series);
},
removeSeries(plotSeries) {
this.stopListening(plotSeries);
const seriesIndex = this.seriesModels.findIndex(series => series.keyString === plotSeries.keyString);
this.seriesModels.splice(seriesIndex, 1);
},
expandLegend() { expandLegend() {
this.isLegendExpanded = !this.isLegendExpanded; this.isLegendExpanded = !this.isLegendExpanded;
this.legend.set('expanded', this.isLegendExpanded); this.legend.set('expanded', this.isLegendExpanded);
this.$emit('expanded', this.isLegendExpanded);
}, },
legendHoverChanged(data) { legendHoverChanged(data) {
this.$emit('legendHoverChanged', data); this.$emit('legendHoverChanged', data);
},
updatePosition() {
this.$emit('position', this.legend.get('position'));
} }
} }
}; };

View File

@ -57,15 +57,12 @@
import {getLimitClass} from "@/plugins/plot/chart/limitUtil"; import {getLimitClass} from "@/plugins/plot/chart/limitUtil";
import eventHelpers from "../lib/eventHelpers"; import eventHelpers from "../lib/eventHelpers";
import stalenessMixin from '@/ui/mixins/staleness-mixin'; import stalenessMixin from '@/ui/mixins/staleness-mixin';
import configStore from "../configuration/ConfigStore";
export default { export default {
mixins: [stalenessMixin], mixins: [stalenessMixin],
inject: ['openmct', 'domainObject'], inject: ['openmct', 'domainObject'],
props: { props: {
valueToShowWhenCollapsed: {
type: String,
required: true
},
seriesObject: { seriesObject: {
type: Object, type: Object,
required: true, required: true,
@ -88,10 +85,14 @@ export default {
formattedYValue: '', formattedYValue: '',
formattedXValue: '', formattedXValue: '',
mctLimitStateClass: '', mctLimitStateClass: '',
formattedYValueFromStats: '' formattedYValueFromStats: '',
loaded: false
}; };
}, },
computed: { computed: {
valueToShowWhenCollapsed() {
return this.loaded ? this.legend.get('valueToShowWhenCollapsed') : [];
},
valueToDisplayWhenCollapsedClass() { valueToDisplayWhenCollapsedClass() {
return `value-to-display-${ this.valueToShowWhenCollapsed }`; return `value-to-display-${ this.valueToShowWhenCollapsed }`;
}, },
@ -109,6 +110,9 @@ export default {
}, },
mounted() { mounted() {
eventHelpers.extend(this); eventHelpers.extend(this);
this.config = this.getConfig();
this.legend = this.config.legend;
this.loaded = true;
this.listenTo(this.seriesObject, 'change:color', (newColor) => { this.listenTo(this.seriesObject, 'change:color', (newColor) => {
this.updateColor(newColor); this.updateColor(newColor);
}, this); }, this);
@ -122,8 +126,13 @@ export default {
this.stopListening(); this.stopListening();
}, },
methods: { methods: {
getConfig() {
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
return configStore.get(configId);
},
initialize(highlightedObject) { initialize(highlightedObject) {
const seriesObject = highlightedObject ? highlightedObject.series : this.seriesObject; const seriesObject = highlightedObject?.series || this.seriesObject;
this.isMissing = seriesObject.domainObject.status === 'missing'; this.isMissing = seriesObject.domainObject.status === 'missing';
this.colorAsHexString = seriesObject.get('color').asHexString(); this.colorAsHexString = seriesObject.get('color').asHexString();

View File

@ -83,6 +83,7 @@
import {getLimitClass} from "@/plugins/plot/chart/limitUtil"; import {getLimitClass} from "@/plugins/plot/chart/limitUtil";
import eventHelpers from "@/plugins/plot/lib/eventHelpers"; import eventHelpers from "@/plugins/plot/lib/eventHelpers";
import stalenessMixin from '@/ui/mixins/staleness-mixin'; import stalenessMixin from '@/ui/mixins/staleness-mixin';
import configStore from "../configuration/ConfigStore";
export default { export default {
mixins: [stalenessMixin], mixins: [stalenessMixin],
@ -100,10 +101,6 @@ export default {
default() { default() {
return []; return [];
} }
},
legend: {
type: Object,
required: true
} }
}, },
data() { data() {
@ -116,24 +113,25 @@ export default {
formattedXValue: '', formattedXValue: '',
formattedMinY: '', formattedMinY: '',
formattedMaxY: '', formattedMaxY: '',
mctLimitStateClass: '' mctLimitStateClass: '',
loaded: false
}; };
}, },
computed: { computed: {
showUnitsWhenExpanded() { showUnitsWhenExpanded() {
return this.legend.get('showUnitsWhenExpanded') === true; return this.loaded && this.legend.get('showUnitsWhenExpanded') === true;
}, },
showMinimumWhenExpanded() { showMinimumWhenExpanded() {
return this.legend.get('showMinimumWhenExpanded') === true; return this.loaded && this.legend.get('showMinimumWhenExpanded') === true;
}, },
showMaximumWhenExpanded() { showMaximumWhenExpanded() {
return this.legend.get('showMaximumWhenExpanded') === true; return this.loaded && this.legend.get('showMaximumWhenExpanded') === true;
}, },
showValueWhenExpanded() { showValueWhenExpanded() {
return this.legend.get('showValueWhenExpanded') === true; return this.loaded && this.legend.get('showValueWhenExpanded') === true;
}, },
showTimestampWhenExpanded() { showTimestampWhenExpanded() {
return this.legend.get('showTimestampWhenExpanded') === true; return this.loaded && this.legend.get('showTimestampWhenExpanded') === true;
} }
}, },
watch: { watch: {
@ -146,6 +144,9 @@ export default {
}, },
mounted() { mounted() {
eventHelpers.extend(this); eventHelpers.extend(this);
this.config = this.getConfig();
this.legend = this.config.legend;
this.loaded = true;
this.listenTo(this.seriesObject, 'change:color', (newColor) => { this.listenTo(this.seriesObject, 'change:color', (newColor) => {
this.updateColor(newColor); this.updateColor(newColor);
}, this); }, this);
@ -159,8 +160,13 @@ export default {
this.stopListening(); this.stopListening();
}, },
methods: { methods: {
getConfig() {
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
return configStore.get(configId);
},
initialize(highlightedObject) { initialize(highlightedObject) {
const seriesObject = highlightedObject ? highlightedObject.series : this.seriesObject; const seriesObject = highlightedObject?.series || this.seriesObject;
this.isMissing = seriesObject.domainObject.status === 'missing'; this.isMissing = seriesObject.domainObject.status === 'missing';
this.colorAsHexString = seriesObject.get('color').asHexString(); this.colorAsHexString = seriesObject.get('color').asHexString();

View File

@ -27,13 +27,16 @@
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]" :class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
> >
<plot-legend <plot-legend
v-if="compositionObjectsConfigLoaded"
:cursor-locked="!!lockHighlightPoint" :cursor-locked="!!lockHighlightPoint"
:series="seriesModels"
:highlights="highlights" :highlights="highlights"
:legend="legend"
@legendHoverChanged="legendHoverChanged" @legendHoverChanged="legendHoverChanged"
@expanded="updateExpanded"
@position="updatePosition"
/> />
<div class="l-view-section"> <div
class="l-view-section"
>
<stacked-plot-item <stacked-plot-item
v-for="objectWrapper in compositionObjects" v-for="objectWrapper in compositionObjects"
:key="objectWrapper.keyString" :key="objectWrapper.keyString"
@ -51,7 +54,7 @@
@gridLines="onGridLinesChange" @gridLines="onGridLinesChange"
@lockHighlightPoint="lockHighlightPointUpdated" @lockHighlightPoint="lockHighlightPointUpdated"
@highlights="highlightsUpdated" @highlights="highlightsUpdated"
@configLoaded="registerSeriesListeners" @configLoaded="configLoadedForObject(objectWrapper.keyString)"
/> />
</div> </div>
</div> </div>
@ -66,14 +69,13 @@ import ColorPalette from "@/ui/color/ColorPalette";
import PlotLegend from "../legend/PlotLegend.vue"; import PlotLegend from "../legend/PlotLegend.vue";
import StackedPlotItem from './StackedPlotItem.vue'; import StackedPlotItem from './StackedPlotItem.vue';
import ImageExporter from '../../../exporters/ImageExporter'; import ImageExporter from '../../../exporters/ImageExporter';
import eventHelpers from "@/plugins/plot/lib/eventHelpers";
export default { export default {
components: { components: {
StackedPlotItem, StackedPlotItem,
PlotLegend PlotLegend
}, },
inject: ['openmct', 'domainObject', 'composition', 'path'], inject: ['openmct', 'domainObject', 'path'],
props: { props: {
options: { options: {
type: Object, type: Object,
@ -87,24 +89,25 @@ export default {
hideExportButtons: false, hideExportButtons: false,
cursorGuide: false, cursorGuide: false,
gridLines: true, gridLines: true,
loading: false, configLoaded: {},
compositionObjects: [], compositionObjects: [],
tickWidthMap: {}, tickWidthMap: {},
legend: {},
loaded: false, loaded: false,
lockHighlightPoint: false, lockHighlightPoint: false,
highlights: [], highlights: [],
seriesModels: [],
showLimitLineLabels: undefined, showLimitLineLabels: undefined,
colorPalette: new ColorPalette() colorPalette: new ColorPalette(),
compositionObjectsConfigLoaded: false,
position: 'top',
expanded: false
}; };
}, },
computed: { computed: {
plotLegendPositionClass() { plotLegendPositionClass() {
return `plot-legend-${this.config.legend.get('position')}`; return `plot-legend-${this.position}`;
}, },
plotLegendExpandedStateClass() { plotLegendExpandedStateClass() {
if (this.config.legend.get('expanded')) { if (this.expanded) {
return 'plot-legend-expanded'; return 'plot-legend-expanded';
} else { } else {
return 'plot-legend-collapsed'; return 'plot-legend-collapsed';
@ -118,17 +121,14 @@ export default {
this.destroy(); this.destroy();
}, },
mounted() { mounted() {
eventHelpers.extend(this); //We only need to initialize the stacked plot config for legend properties
this.seriesConfig = {};
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier); const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.config = this.getConfig(configId); this.config = this.getConfig(configId);
this.legend = this.config.legend;
this.loaded = true; this.loaded = true;
this.imageExporter = new ImageExporter(this.openmct); this.imageExporter = new ImageExporter(this.openmct);
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addChild); this.composition.on('add', this.addChild);
this.composition.on('remove', this.removeChild); this.composition.on('remove', this.removeChild);
this.composition.on('reorder', this.compositionReorder); this.composition.on('reorder', this.compositionReorder);
@ -142,7 +142,6 @@ export default {
id: configId, id: configId,
domainObject: this.domainObject, domainObject: this.domainObject,
openmct: this.openmct, openmct: this.openmct,
palette: this.colorPalette,
callback: (data) => { callback: (data) => {
this.data = data; this.data = data;
} }
@ -155,10 +154,19 @@ export default {
loadingUpdated(loaded) { loadingUpdated(loaded) {
this.loading = loaded; this.loading = loaded;
}, },
destroy() { configLoadedForObject(childObjIdentifier) {
this.stopListening(); const childObjId = this.openmct.objects.makeKeyString(childObjIdentifier);
configStore.deleteStore(this.config.id); this.configLoaded[childObjId] = true;
this.setConfigLoadedForComposition();
},
setConfigLoadedForComposition() {
this.compositionObjectsConfigLoaded = this.compositionObjects.length && this.compositionObjects.every(childObject => {
const id = childObject.keyString;
return this.configLoaded[id] === true;
});
},
destroy() {
this.composition.off('add', this.addChild); this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild); this.composition.off('remove', this.removeChild);
this.composition.off('reorder', this.compositionReorder); this.composition.off('reorder', this.compositionReorder);
@ -173,6 +181,7 @@ export default {
object: child, object: child,
keyString: id keyString: id
}); });
this.setConfigLoadedForComposition();
}, },
removeChild(childIdentifier) { removeChild(childIdentifier) {
@ -180,23 +189,36 @@ export default {
this.$delete(this.tickWidthMap, id); this.$delete(this.tickWidthMap, id);
const configIndex = this.domainObject.configuration.series.findIndex((seriesConfig) => { const childObj = this.compositionObjects.filter((c) => {
return this.openmct.objects.areIdsEqual(seriesConfig.identifier, childIdentifier); const identifier = c.keyString;
});
if (configIndex > -1) { return identifier === id;
this.domainObject.configuration.series.splice(configIndex, 1); })[0];
if (childObj) {
if (childObj.object.type !== 'telemetry.plot.overlay') {
const config = this.getConfig(childObj.keyString);
if (config) {
config.series.remove(config.series.at(0));
}
}
} }
this.removeSeries({
keyString: id
});
this.compositionObjects = this.compositionObjects.filter((c) => { this.compositionObjects = this.compositionObjects.filter((c) => {
const identifier = c.keyString; const identifier = c.keyString;
return identifier !== id; return identifier !== id;
}); });
const configIndex = this.domainObject.configuration.series.findIndex((seriesConfig) => {
return this.openmct.objects.areIdsEqual(seriesConfig.identifier, childIdentifier);
});
if (configIndex > -1) {
const cSeries = this.domainObject.configuration.series.slice();
this.openmct.objects.mutate(this.domainObject, 'configuration.series', cSeries);
}
this.setConfigLoadedForComposition();
}, },
compositionReorder(reorderPlan) { compositionReorder(reorderPlan) {
@ -245,39 +267,18 @@ export default {
lockHighlightPointUpdated(data) { lockHighlightPointUpdated(data) {
this.lockHighlightPoint = data; this.lockHighlightPoint = data;
}, },
updateExpanded(expanded) {
this.expanded = expanded;
},
updatePosition(position) {
this.position = position;
},
updateReady(ready) {
this.configReady = ready;
},
highlightsUpdated(data) { highlightsUpdated(data) {
this.highlights = data; this.highlights = data;
}, },
registerSeriesListeners(configId) {
const config = this.getConfig(configId);
this.seriesConfig[configId] = config;
const childObject = config.get('domainObject');
//TODO differentiate between objects with composition and those without
if (childObject.type === 'telemetry.plot.overlay') {
this.listenTo(config.series, 'add', this.addSeries, this);
this.listenTo(config.series, 'remove', this.removeSeries, this);
}
config.series.models.forEach(this.addSeries, this);
},
addSeries(series) {
const childObject = series.domainObject;
//don't add the series if it can have child series this will happen in registerSeriesListeners
if (childObject.type !== 'telemetry.plot.overlay') {
const index = this.seriesModels.length;
this.$set(this.seriesModels, index, series);
}
},
removeSeries(plotSeries) {
const index = this.seriesModels.findIndex(seriesModel => seriesModel.keyString === plotSeries.keyString);
if (index > -1) {
this.$delete(this.seriesModels, index);
}
this.stopListening(plotSeries);
},
onCursorGuideChange(cursorGuide) { onCursorGuideChange(cursorGuide) {
this.cursorGuide = cursorGuide === true; this.cursorGuide = cursorGuide === true;
}, },

View File

@ -100,10 +100,6 @@ export default {
this.updateView(); this.updateView();
}, },
beforeDestroy() { beforeDestroy() {
if (this.removeSelectable) {
this.removeSelectable();
}
if (this.component) { if (this.component) {
this.component.$destroy(); this.component.$destroy();
} }
@ -180,8 +176,6 @@ export default {
}, },
template: '<div v-if="!isMissing" ref="plotWrapper" class="l-view-section u-style-receiver js-style-receiver" :class="{\'s-status-timeconductor-unsynced\': status && status === \'timeconductor-unsynced\', \'is-stale\': isStale}"><progress-bar v-show="loading !== false" class="c-telemetry-table__progress-bar" :model="{progressPerc: undefined}" /><mct-plot :init-grid-lines="gridLines" :init-cursor-guide="cursorGuide" :plot-tick-width="plotTickWidth" :limit-line-labels="limitLineLabels" :color-palette="colorPalette" :options="options" @plotTickWidth="onTickWidthChange" @lockHighlightPoint="onLockHighlightPointUpdated" @highlights="onHighlightsUpdated" @configLoaded="onConfigLoaded" @cursorGuide="onCursorGuideChange" @gridLines="onGridLinesChange" @statusUpdated="setStatus" @loadingUpdated="loadingUpdated"/></div>' template: '<div v-if="!isMissing" ref="plotWrapper" class="l-view-section u-style-receiver js-style-receiver" :class="{\'s-status-timeconductor-unsynced\': status && status === \'timeconductor-unsynced\', \'is-stale\': isStale}"><progress-bar v-show="loading !== false" class="c-telemetry-table__progress-bar" :model="{progressPerc: undefined}" /><mct-plot :init-grid-lines="gridLines" :init-cursor-guide="cursorGuide" :plot-tick-width="plotTickWidth" :limit-line-labels="limitLineLabels" :color-palette="colorPalette" :options="options" @plotTickWidth="onTickWidthChange" @lockHighlightPoint="onLockHighlightPointUpdated" @highlights="onHighlightsUpdated" @configLoaded="onConfigLoaded" @cursorGuide="onCursorGuideChange" @gridLines="onGridLinesChange" @statusUpdated="setStatus" @loadingUpdated="loadingUpdated"/></div>'
}); });
this.setSelection();
}, },
onLockHighlightPointUpdated() { onLockHighlightPointUpdated() {
this.$emit('lockHighlightPoint', ...arguments); this.$emit('lockHighlightPoint', ...arguments);
@ -205,17 +199,6 @@ export default {
this.status = status; this.status = status;
this.updateComponentProp('status', status); this.updateComponentProp('status', status);
}, },
setSelection() {
let childContext = {};
childContext.item = this.childObject;
this.context = childContext;
if (this.removeSelectable) {
this.removeSelectable();
}
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context);
},
getProps() { getProps() {
return { return {
limitLineLabels: this.showLimitLineLabels, limitLineLabels: this.showLimitLineLabels,
@ -230,7 +213,7 @@ export default {
}, },
getPlotObject() { getPlotObject() {
if (this.childObject.configuration && this.childObject.configuration.series) { if (this.childObject.configuration && this.childObject.configuration.series) {
//If the object has a configuration, allow initialization of the config from it's persisted config //If the object has a configuration (like an overlay plot), allow initialization of the config from it's persisted config
return this.childObject; return this.childObject;
} else { } else {
//If object is missing, warn and return object //If object is missing, warn and return object

View File

@ -57,7 +57,6 @@ export default function StackedPlotViewProvider(openmct) {
provide: { provide: {
openmct, openmct,
domainObject, domainObject,
composition: openmct.composition.get(domainObject),
path: objectPath path: objectPath
}, },
data() { data() {

View File

@ -173,7 +173,7 @@ describe("the plugin", function () {
let testTelemetryObject2; let testTelemetryObject2;
let config; let config;
let component; let component;
let mockComposition; let mockCompositionList = [];
let plotViewComponentObject; let plotViewComponentObject;
afterAll(() => { afterAll(() => {
@ -271,14 +271,34 @@ describe("the plugin", function () {
} }
}; };
mockComposition = new EventEmitter(); stackedPlotObject.composition = [{
mockComposition.load = () => { identifier: testTelemetryObject.identifier
mockComposition.emit('add', testTelemetryObject); }];
return [testTelemetryObject]; mockCompositionList = [];
}; spyOn(openmct.composition, 'get').and.callFake((domainObject) => {
//We need unique compositions here - one for the StackedPlot view and one for the PlotLegend view
const numObjects = domainObject.composition.length;
const mockComposition = new EventEmitter();
mockComposition.load = () => {
if (numObjects === 1) {
mockComposition.emit('add', testTelemetryObject);
spyOn(openmct.composition, 'get').and.returnValue(mockComposition); return [testTelemetryObject];
} else if (numObjects === 2) {
mockComposition.emit('add', testTelemetryObject);
mockComposition.emit('add', testTelemetryObject2);
return [testTelemetryObject, testTelemetryObject2];
} else {
return [];
}
};
mockCompositionList.push(mockComposition);
return mockComposition;
});
let viewContainer = document.createElement("div"); let viewContainer = document.createElement("div");
child.append(viewContainer); child.append(viewContainer);
@ -290,7 +310,6 @@ describe("the plugin", function () {
provide: { provide: {
openmct: openmct, openmct: openmct,
domainObject: stackedPlotObject, domainObject: stackedPlotObject,
composition: openmct.composition.get(stackedPlotObject),
path: [stackedPlotObject] path: [stackedPlotObject]
}, },
template: "<stacked-plot></stacked-plot>" template: "<stacked-plot></stacked-plot>"
@ -321,7 +340,7 @@ describe("the plugin", function () {
expect(legend.length).toBe(6); expect(legend.length).toBe(6);
}); });
it("Renders X-axis ticks for the telemetry object", (done) => { it("Renders X-axis ticks for the telemetry object", () => {
let xAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper"); let xAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper");
expect(xAxisElement.length).toBe(1); expect(xAxisElement.length).toBe(1);
@ -329,13 +348,8 @@ describe("the plugin", function () {
min: 0, min: 0,
max: 4 max: 4
}); });
let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
Vue.nextTick(() => { expect(ticks.length).toBe(9);
let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
expect(ticks.length).toBe(9);
done();
});
}); });
it("Renders Y-axis ticks for the telemetry object", (done) => { it("Renders Y-axis ticks for the telemetry object", (done) => {
@ -401,17 +415,22 @@ describe("the plugin", function () {
}); });
it('plots a new series when a new telemetry object is added', (done) => { it('plots a new series when a new telemetry object is added', (done) => {
mockComposition.emit('add', testTelemetryObject2); //setting composition here so that any new triggers to composition.load with correctly load the mockComposition in the beforeEach
stackedPlotObject.composition = [testTelemetryObject, testTelemetryObject2];
mockCompositionList[0].emit('add', testTelemetryObject2);
Vue.nextTick(() => { Vue.nextTick(() => {
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name"); let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
expect(legend.length).toBe(2); expect(legend.length).toBe(2);
expect(legend[1].innerHTML).toEqual("Test Object2"); expect(legend[1].innerHTML).toEqual("Test Object2");
done(); done();
}); });
}); });
it('removes plots from series when a telemetry object is removed', (done) => { it('removes plots from series when a telemetry object is removed', (done) => {
mockComposition.emit('remove', testTelemetryObject.identifier); stackedPlotObject.composition = [];
mockCompositionList[0].emit('remove', testTelemetryObject.identifier);
Vue.nextTick(() => { Vue.nextTick(() => {
expect(plotViewComponentObject.compositionObjects.length).toBe(0); expect(plotViewComponentObject.compositionObjects.length).toBe(0);
done(); done();
@ -429,16 +448,6 @@ describe("the plugin", function () {
}); });
}); });
it("Renders a new series when added to one of the plots", (done) => {
mockComposition.emit('add', testTelemetryObject2);
Vue.nextTick(() => {
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
expect(legend.length).toBe(2);
expect(legend[1].innerHTML).toEqual("Test Object2");
done();
});
});
it("Adds a new point to the plot", (done) => { it("Adds a new point to the plot", (done) => {
let originalLength = config.series.models[0].getSeriesData().length; let originalLength = config.series.models[0].getSeriesData().length;
config.series.models[0].add({ config.series.models[0].add({
@ -459,7 +468,7 @@ describe("the plugin", function () {
max: 10 max: 10
}); });
Vue.nextTick(() => { Vue.nextTick(() => {
expect(plotViewComponentObject.$children[1].component.$children[1].xScale.domain()).toEqual({ expect(plotViewComponentObject.$children[0].component.$children[1].xScale.domain()).toEqual({
min: 0, min: 0,
max: 10 max: 10
}); });
@ -476,7 +485,7 @@ describe("the plugin", function () {
}); });
}); });
Vue.nextTick(() => { Vue.nextTick(() => {
const yAxesScales = plotViewComponentObject.$children[1].component.$children[1].yScale; const yAxesScales = plotViewComponentObject.$children[0].component.$children[1].yScale;
yAxesScales.forEach((yAxisScale) => { yAxesScales.forEach((yAxisScale) => {
expect(yAxisScale.scale.domain()).toEqual({ expect(yAxisScale.scale.domain()).toEqual({
min: 10, min: 10,

View File

@ -42,7 +42,6 @@
@import "../ui/inspector/elements.scss"; @import "../ui/inspector/elements.scss";
@import "../ui/inspector/inspector.scss"; @import "../ui/inspector/inspector.scss";
@import "../ui/inspector/location.scss"; @import "../ui/inspector/location.scss";
@import "../ui/inspector/annotations/annotation-inspector.scss";
@import "../ui/layout/app-logo.scss"; @import "../ui/layout/app-logo.scss";
@import "../ui/layout/create-button.scss"; @import "../ui/layout/create-button.scss";
@import "../ui/layout/layout.scss"; @import "../ui/layout/layout.scss";

View File

@ -145,7 +145,7 @@ export default {
this.unlistenComposition(); this.unlistenComposition();
if (this.parentObject) { if (this.parentObject && this.parentObject.type === 'telemetry.plot.overlay') {
this.setYAxisIds(); this.setYAxisIds();
this.composition = this.openmct.composition.get(this.parentObject); this.composition = this.openmct.composition.get(this.parentObject);
@ -175,6 +175,7 @@ export default {
setYAxisIds() { setYAxisIds() {
const configId = this.openmct.objects.makeKeyString(this.parentObject.identifier); const configId = this.openmct.objects.makeKeyString(this.parentObject.identifier);
this.config = configStore.get(configId); this.config = configStore.get(configId);
this.yAxes = [];
this.yAxes.push({ this.yAxes.push({
id: this.config.yAxis.id, id: this.config.yAxis.id,
elements: this.parentObject.configuration.series.filter( elements: this.parentObject.configuration.series.filter(

View File

@ -1,83 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, 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.
*****************************************************************************/
<template>
<div class="c-annotation__row">
<textarea
v-model="contentModel"
class="c-annotation__text_area"
type="text"
></textarea>
<div>
<span>{{ modifiedOnDate }}</span>
<span>{{ modifiedOnTime }}</span>
</div>
</div>
</template>
<script>
import Moment from 'moment';
export default {
inject: ['openmct'],
props: {
annotation: {
type: Object,
default() {
return {};
}
}
},
data() {
return {
};
},
computed: {
contentModel: {
get() {
return this.annotation.contentText;
},
set(contentText) {
console.debug(`Set tag called with ${contentText}`);
}
},
modifiedOnDate() {
return this.formatTime(this.annotation.modified, 'YYYY-MM-DD');
},
modifiedOnTime() {
return this.formatTime(this.annotation.modified, 'HH:mm:ss');
}
},
mounted() {
},
methods: {
getAvailableTagByID(tagID) {
return this.openmct.annotation.getAvailableTags().find(tag => {
return tag.id === tagID;
});
},
formatTime(unixTime, timeFormat) {
return Moment.utc(unixTime).format(timeFormat);
}
}
};
</script>

View File

@ -111,25 +111,31 @@ export default {
return this?.selection?.[0]?.[0]?.context?.item; return this?.selection?.[0]?.[0]?.context?.item;
}, },
targetDetails() { targetDetails() {
return this?.selection?.[0]?.[1]?.context?.targetDetails ?? {}; return this?.selection?.[0]?.[0]?.context?.targetDetails ?? {};
}, },
shouldShowTagsEditor() { shouldShowTagsEditor() {
return Object.keys(this.targetDetails).length > 0; const showingTagsEditor = Object.keys(this.targetDetails).length > 0;
if (showingTagsEditor) {
return true;
}
return false;
}, },
targetDomainObjects() { targetDomainObjects() {
return this?.selection?.[0]?.[1]?.context?.targetDomainObjects ?? {}; return this?.selection?.[0]?.[0]?.context?.targetDomainObjects ?? {};
}, },
selectedAnnotations() { selectedAnnotations() {
return this?.selection?.[0]?.[1]?.context?.annotations; return this?.selection?.[0]?.[0]?.context?.annotations;
}, },
annotationType() { annotationType() {
return this?.selection?.[0]?.[1]?.context?.annotationType; return this?.selection?.[0]?.[0]?.context?.annotationType;
}, },
annotationFilter() { annotationFilter() {
return this?.selection?.[0]?.[1]?.context?.annotationFilter; return this?.selection?.[0]?.[0]?.context?.annotationFilter;
}, },
onAnnotationChange() { onAnnotationChange() {
return this?.selection?.[0]?.[1]?.context?.onAnnotationChange; return this?.selection?.[0]?.[0]?.context?.onAnnotationChange;
} }
}, },
async mounted() { async mounted() {
@ -195,6 +201,7 @@ export default {
} }
}, },
async loadAnnotationForTargetObject(target) { async loadAnnotationForTargetObject(target) {
console.debug(`📝 Loading annotations for target`, target);
const targetID = this.openmct.objects.makeKeyString(target.identifier); const targetID = this.openmct.objects.makeKeyString(target.identifier);
const allAnnotationsForTarget = await this.openmct.annotation.getAnnotations(target.identifier); const allAnnotationsForTarget = await this.openmct.annotation.getAnnotations(target.identifier);
const filteredAnnotationsForSelection = allAnnotationsForTarget.filter(annotation => { const filteredAnnotationsForSelection = allAnnotationsForTarget.filter(annotation => {

View File

@ -1,18 +0,0 @@
.c-inspect-annotations {
> * + * {
margin-top: $interiorMargin;
}
&__content{
> * + * {
margin-top: $interiorMargin;
}
}
&__content {
display: flex;
flex-direction: column;
}
}

View File

@ -150,16 +150,11 @@ export default {
}); });
const selection = const selection =
[ [
{
element: this.openmct.layout.$refs.browseObject.$el,
context: {
item: this.result
}
},
{ {
element: this.$el, element: this.$el,
context: { context: {
type: 'plot-points-selection', item: this.result.targetModels[0],
type: 'plot-annotation-search-result',
targetDetails, targetDetails,
targetDomainObjects, targetDomainObjects,
annotations: [this.result], annotations: [this.result],