Plots limit lines (#3882)

This commit is contained in:
Shefali Joshi 2021-05-28 14:51:29 -07:00 committed by GitHub
parent 5fafde5f23
commit 8274c23129
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 586 additions and 19 deletions

View File

@ -93,5 +93,36 @@ define([
}; };
}; };
SinewaveLimitProvider.prototype.getLimits = function (domainObject) {
return {
limits: function () {
return {
WARNING: {
low: {
cssClass: "is-limit--lwr is-limit--yellow",
sin: -YELLOW.sin,
cos: -YELLOW.cos
},
high: {
cssClass: "is-limit--upr is-limit--yellow",
...YELLOW
}
},
DISTRESS: {
low: {
cssClass: "is-limit--lwr is-limit--red",
sin: -RED.sin,
cos: -RED.cos
},
high: {
cssClass: "is-limit--upr is-limit--red",
...RED
}
}
};
}
};
};
return SinewaveLimitProvider; return SinewaveLimitProvider;
}); });

View File

@ -161,6 +161,22 @@ define([
evaluate: function (datum, property) { evaluate: function (datum, property) {
return limitEvaluator.evaluate(datum, property && property.key); return limitEvaluator.evaluate(datum, property && property.key);
} }
};
};
LegacyTelemetryProvider.prototype.getLimits = function (domainObject) {
const oldObject = this.instantiate(
utils.toOldFormat(domainObject),
utils.makeKeyString(domainObject.identifier)
);
const limitEvaluator = oldObject.getCapability("limit");
return {
limits: function () {
return limitEvaluator.limits();
}
}; };
}; };

View File

@ -504,6 +504,26 @@ define([
return this.getLimitEvaluator(domainObject); return this.getLimitEvaluator(domainObject);
}; };
/**
* Get a limits for this domain object.
* Limits help you display limits and alarms of
* telemetry for display purposes without having to interact directly
* with the Limit API.
*
* This method is optional.
* If a provider does not implement this method, it is presumed
* that no limits are defined for this domain object's telemetry.
*
* @param {module:openmct.DomainObject} domainObject the domain
* object for which to get limits
* @returns {module:openmct.TelemetryAPI~LimitEvaluator}
* @method limits
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
*/
TelemetryAPI.prototype.limitDefinition = function (domainObject) {
return this.getLimits(domainObject);
};
/** /**
* Get a limit evaluator for this domain object. * Get a limit evaluator for this domain object.
* Limit Evaluators help you evaluate limit and alarm status of individual * Limit Evaluators help you evaluate limit and alarm status of individual
@ -531,5 +551,42 @@ define([
return provider.getLimitEvaluator(domainObject); return provider.getLimitEvaluator(domainObject);
}; };
/**
* Get a limit definitions for this domain object.
* Limit Definitions help you indicate limits and alarms of
* telemetry for display purposes without having to interact directly
* with the Limit API.
*
* This method is optional.
* If a provider does not implement this method, it is presumed
* that no limits are defined for this domain object's telemetry.
*
* @param {module:openmct.DomainObject} domainObject the domain
* object for which to display limits
* @returns {module:openmct.TelemetryAPI~LimitEvaluator}
* @method limits returns a limits object of
* type {
* level1: {
* low: { key1: value1, key2: value2 },
* high: { key1: value1, key2: value2 }
* },
* level2: {
* low: { key1: value1, key2: value2 },
* high: { key1: value1, key2: value2 }
* }
* }
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
*/
TelemetryAPI.prototype.getLimits = function (domainObject) {
const provider = this.findLimitEvaluator(domainObject);
if (!provider) {
return {
limits: function () {}
};
}
return provider.getLimits(domainObject);
};
return TelemetryAPI; return TelemetryAPI;
}); });

View File

@ -43,15 +43,15 @@ export default function LADTableSetViewProvider(openmct) {
components: { components: {
LadTableSet: LadTableSet LadTableSet: LadTableSet
}, },
provide: {
openmct,
objectPath
},
data() { data() {
return { return {
domainObject domainObject
}; };
}, },
provide: {
openmct,
objectPath
},
template: '<lad-table-set :domain-object="domainObject"></lad-table-set>' template: '<lad-table-set :domain-object="domainObject"></lad-table-set>'
}); });
}, },

View File

@ -28,6 +28,7 @@
:series="config.series.models" :series="config.series.models"
:highlights="highlights" :highlights="highlights"
:legend="config.legend" :legend="config.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">
<y-axis v-if="config.series.models.length > 0" <y-axis v-if="config.series.models.length > 0"
@ -67,6 +68,7 @@
> >
<mct-chart :rectangles="rectangles" <mct-chart :rectangles="rectangles"
:highlights="highlights" :highlights="highlights"
:show-limit-line-labels="showLimitLineLabels"
@plotReinitializeCanvas="initCanvas" @plotReinitializeCanvas="initCanvas"
/> />
</div> </div>
@ -213,7 +215,8 @@ export default {
pending: 0, pending: 0,
isRealTime: this.openmct.time.clock() !== undefined, isRealTime: this.openmct.time.clock() !== undefined,
loaded: false, loaded: false,
isTimeOutOfSync: false isTimeOutOfSync: false,
showLimitLineLabels: undefined
}; };
}, },
computed: { computed: {
@ -1018,6 +1021,9 @@ export default {
this.offsetWidth = this.$parent.$refs.plotWrapper.offsetWidth; this.offsetWidth = this.$parent.$refs.plotWrapper.offsetWidth;
this.config.series.models.forEach(this.loadSeriesData, this); this.config.series.models.forEach(this.loadSeriesData, this);
} }
},
legendHoverChanged(data) {
this.showLimitLineLabels = data;
} }
} }
}; };

View File

@ -0,0 +1,46 @@
<template>
<div class="plot-series-limit-label"
:style="styleObj"
:class="limit.cssClass"
>
<span class="plot-series-limit-value">{{ limit.value }}</span>
<span class="plot-series-color-swatch"
:style="{ 'background-color': limit.color }"
></span>
<span class="plot-series-name">{{ limit.name }}</span>
</div>
</template>
<script>
export default {
props: {
limit: {
type: Object,
required: true,
default() {
return {};
}
},
point: {
type: Object,
required: true,
default() {
return {};
}
}
},
computed: {
styleObj() {
const top = `${this.point.top - 10}px`;
const left = `${this.point.left + 5}px`;
return {
'position': 'absolute',
'top': top,
'left': left,
'color': '#fff'
};
}
}
};
</script>

View File

@ -0,0 +1,44 @@
<template>
<hr :style="styleObj"
:class="cssWithoutUprLwr"
>
</template>
<script>
export default {
props: {
point: {
type: Object,
required: true,
default() {
return {};
}
},
cssClass: {
type: String,
default() {
return '';
}
}
},
computed: {
styleObj() {
const top = `${this.point.top}px`;
const left = `${this.point.left}px`;
return {
'position': 'absolute',
'width': '100%',
'top': top,
'left': left
};
},
cssWithoutUprLwr() {
let cssClass = this.cssClass.replace(/is-limit--upr/gi, 'is-limit--line');
cssClass = cssClass.replace(/is-limit--lwr/gi, 'is-limit--line');
return cssClass;
}
}
};
</script>

View File

@ -0,0 +1,105 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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.
*****************************************************************************/
import eventHelpers from '../lib/eventHelpers';
export default class MCTChartAlarmLineSet {
constructor(series, chart, offset, bounds) {
this.series = series;
this.chart = chart;
this.offset = offset;
this.bounds = bounds;
this.limits = [];
eventHelpers.extend(this);
this.listenTo(series, 'limitBounds', this.updateBounds, this);
this.listenTo(series, 'change:xKey', this.getLimitPoints, this);
if (series.limits) {
this.getLimitPoints(series);
}
}
updateBounds(bounds) {
this.bounds = bounds;
this.getLimitPoints(this.series);
}
color() {
return this.series.get('color');
}
name() {
return this.series.get('name');
}
makePoint(point, series) {
if (!this.offset.xVal) {
this.chart.setOffset(point, undefined, series);
}
return {
x: this.offset.xVal(point, series),
y: this.offset.yVal(point, series)
};
}
getLimitPoints(series) {
this.limits = [];
let xKey = series.get('xKey');
Object.keys(series.limits).forEach((key) => {
const limitForLevel = series.limits[key];
if (limitForLevel.high) {
const point = this.makePoint(Object.assign({ [xKey]: this.bounds.start }, limitForLevel.high), series);
this.limits.push({
seriesKey: series.keyString,
value: series.getYVal(limitForLevel.high),
color: this.color().asHexString(),
name: this.name(),
point,
cssClass: limitForLevel.high.cssClass
});
}
if (limitForLevel.low) {
const point = this.makePoint(Object.assign({ [xKey]: this.bounds.start }, limitForLevel.low), series);
this.limits.push({
seriesKey: series.keyString,
value: series.getYVal(limitForLevel.low),
color: this.color().asHexString(),
name: this.name(),
point,
cssClass: limitForLevel.low.cssClass
});
}
}, this);
}
reset() {
this.limits = [];
}
destroy() {
this.stopListening();
}
}

View File

@ -23,6 +23,9 @@
<div class="gl-plot-chart-area"> <div class="gl-plot-chart-area">
<span v-html="canvasTemplate"></span> <span v-html="canvasTemplate"></span>
<span v-html="canvasTemplate"></span> <span v-html="canvasTemplate"></span>
<div ref="limitArea"
class="js-limit-area"
></div>
</div> </div>
</template> </template>
@ -34,8 +37,12 @@ import MCTChartLineLinear from './MCTChartLineLinear';
import MCTChartLineStepAfter from './MCTChartLineStepAfter'; import MCTChartLineStepAfter from './MCTChartLineStepAfter';
import MCTChartPointSet from './MCTChartPointSet'; import MCTChartPointSet from './MCTChartPointSet';
import MCTChartAlarmPointSet from './MCTChartAlarmPointSet'; import MCTChartAlarmPointSet from './MCTChartAlarmPointSet';
import MCTChartAlarmLineSet from "./MCTChartAlarmLineSet";
import configStore from "../configuration/configStore"; import configStore from "../configuration/configStore";
import PlotConfigurationModel from "../configuration/PlotConfigurationModel"; import PlotConfigurationModel from "../configuration/PlotConfigurationModel";
import LimitLine from "./LimitLine.vue";
import LimitLabel from "./LimitLabel.vue";
import Vue from 'vue';
const MARKER_SIZE = 6.0; const MARKER_SIZE = 6.0;
const HIGHLIGHT_SIZE = MARKER_SIZE * 2.0; const HIGHLIGHT_SIZE = MARKER_SIZE * 2.0;
@ -54,6 +61,12 @@ export default {
default() { default() {
return []; return [];
} }
},
showLimitLineLabels: {
type: Object,
default() {
return {};
}
} }
}, },
data() { data() {
@ -67,18 +80,22 @@ export default {
}, },
rectangles() { rectangles() {
this.scheduleDraw(); this.scheduleDraw();
},
showLimitLineLabels() {
this.drawLimitLines();
} }
}, },
mounted() { mounted() {
eventHelpers.extend(this); eventHelpers.extend(this);
this.config = this.getConfig(); this.config = this.getConfig();
this.isDestroyed = false; this.isDestroyed = false;
this.lines = []; this.lines = [];
this.limitLines = [];
this.pointSets = []; this.pointSets = [];
this.alarmSets = []; this.alarmSets = [];
this.offset = {}; this.offset = {};
this.seriesElements = new WeakMap(); this.seriesElements = new WeakMap();
this.seriesLimits = new WeakMap();
let canvasEls = this.$parent.$refs.chartContainer.querySelectorAll("canvas"); let canvasEls = this.$parent.$refs.chartContainer.querySelectorAll("canvas");
const mainCanvas = canvasEls[1]; const mainCanvas = canvasEls[1];
@ -90,8 +107,8 @@ export default {
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this); this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this); this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this);
this.listenTo(this.config.yAxis, 'change:key', this.clearOffset, this); this.listenTo(this.config.yAxis, 'change:key', this.clearOffset, this);
this.listenTo(this.config.yAxis, 'change', this.scheduleDraw); this.listenTo(this.config.yAxis, 'change', this.updateLimitsAndDraw);
this.listenTo(this.config.xAxis, 'change', this.scheduleDraw); this.listenTo(this.config.xAxis, 'change', this.updateLimitsAndDraw);
this.config.series.forEach(this.onSeriesAdd, this); this.config.series.forEach(this.onSeriesAdd, this);
}, },
beforeDestroy() { beforeDestroy() {
@ -116,15 +133,18 @@ export default {
this.changeInterpolate(mode, o, series); this.changeInterpolate(mode, o, series);
this.changeMarkers(mode, o, series); this.changeMarkers(mode, o, series);
this.changeAlarmMarkers(mode, o, series); this.changeAlarmMarkers(mode, o, series);
this.changeLimitLines(mode, o, series);
}, },
onSeriesAdd(series) { onSeriesAdd(series) {
this.listenTo(series, 'change:xKey', this.reDraw, this); this.listenTo(series, 'change:xKey', this.reDraw, this);
this.listenTo(series, 'change:interpolate', this.changeInterpolate, this); this.listenTo(series, 'change:interpolate', this.changeInterpolate, this);
this.listenTo(series, 'change:markers', this.changeMarkers, this); this.listenTo(series, 'change:markers', this.changeMarkers, this);
this.listenTo(series, 'change:alarmMarkers', this.changeAlarmMarkers, this); this.listenTo(series, 'change:alarmMarkers', this.changeAlarmMarkers, this);
this.listenTo(series, 'change:limitLines', this.changeLimitLines, this);
this.listenTo(series, 'change', this.scheduleDraw); this.listenTo(series, 'change', this.scheduleDraw);
this.listenTo(series, 'add', this.scheduleDraw); this.listenTo(series, 'add', this.scheduleDraw);
this.makeChartElement(series); this.makeChartElement(series);
this.makeLimitLines(series);
}, },
changeInterpolate(mode, o, series) { changeInterpolate(mode, o, series) {
if (mode === o) { if (mode === o) {
@ -178,6 +198,14 @@ export default {
this.pointSets.push(pointSet); this.pointSets.push(pointSet);
} }
}, },
changeLimitLines(mode, o, series) {
if (mode === o) {
return;
}
this.makeLimitLines(series);
this.updateLimitsAndDraw();
},
onSeriesRemove(series) { onSeriesRemove(series) {
this.stopListening(series); this.stopListening(series);
this.removeChartElement(series); this.removeChartElement(series);
@ -187,6 +215,7 @@ export default {
this.isDestroyed = true; this.isDestroyed = true;
this.stopListening(); this.stopListening();
this.lines.forEach(line => line.destroy()); this.lines.forEach(line => line.destroy());
this.limitLines.forEach(line => line.destroy());
DrawLoader.releaseDrawAPI(this.drawAPI); DrawLoader.releaseDrawAPI(this.drawAPI);
}, },
clearOffset() { clearOffset() {
@ -199,6 +228,9 @@ export default {
this.lines.forEach(function (line) { this.lines.forEach(function (line) {
line.reset(); line.reset();
}); });
this.limitLines.forEach(function (line) {
line.reset();
});
this.pointSets.forEach(function (pointSet) { this.pointSets.forEach(function (pointSet) {
pointSet.reset(); pointSet.reset();
}); });
@ -269,6 +301,8 @@ export default {
} }
this.seriesElements.delete(series); this.seriesElements.delete(series);
this.clearLimitLines(series);
}, },
lineForSeries(series) { lineForSeries(series) {
if (series.get('interpolate') === 'linear') { if (series.get('interpolate') === 'linear') {
@ -287,6 +321,14 @@ export default {
); );
} }
}, },
limitLineForSeries(series) {
return new MCTChartAlarmLineSet(
series,
this,
this.offset,
this.openmct.time.bounds()
);
},
pointSetForSeries(series) { pointSetForSeries(series) {
if (series.get('markers')) { if (series.get('markers')) {
return new MCTChartPointSet( return new MCTChartPointSet(
@ -308,7 +350,8 @@ export default {
makeChartElement(series) { makeChartElement(series) {
const elements = { const elements = {
lines: [], lines: [],
pointSets: [] pointSets: [],
limitLines: []
}; };
const line = this.lineForSeries(series); const line = this.lineForSeries(series);
@ -330,6 +373,37 @@ export default {
this.seriesElements.set(series, elements); this.seriesElements.set(series, elements);
}, },
makeLimitLines(series) {
this.clearLimitLines(series);
if (!series.get('limitLines')) {
return;
}
const limitElements = {
limitLines: []
};
const limitLine = this.limitLineForSeries(series);
if (limitLine) {
limitElements.limitLines.push(limitLine);
this.limitLines.push(limitLine);
}
this.seriesLimits.set(series, limitElements);
},
clearLimitLines(series) {
const seriesLimits = this.seriesLimits.get(series);
if (seriesLimits) {
seriesLimits.limitLines.forEach(function (line) {
this.limitLines.splice(this.limitLines.indexOf(line), 1);
line.destroy();
}, this);
this.seriesLimits.delete(series);
}
},
canDraw() { canDraw() {
if (!this.offset.x || !this.offset.y) { if (!this.offset.x || !this.offset.y) {
return false; return false;
@ -337,6 +411,10 @@ export default {
return true; return true;
}, },
updateLimitsAndDraw() {
this.drawLimitLines();
this.scheduleDraw();
},
scheduleDraw() { scheduleDraw() {
if (!this.drawScheduled) { if (!this.drawScheduled) {
requestAnimationFrame(this.draw); requestAnimationFrame(this.draw);
@ -385,6 +463,68 @@ export default {
this.pointSets.forEach(this.drawPoints, this); this.pointSets.forEach(this.drawPoints, this);
this.alarmSets.forEach(this.drawAlarmPoints, this); this.alarmSets.forEach(this.drawAlarmPoints, this);
}, },
drawLimitLines() {
if (this.canDraw()) {
this.updateViewport();
if (!this.drawAPI.origin) {
return;
}
Array.from(this.$refs.limitArea.children).forEach((el) => el.remove());
this.limitLines.forEach((limitLine) => {
let limitContainerEl = this.$refs.limitArea;
limitLine.limits.forEach((limit) => {
const showLabels = this.showLabels(limit.seriesKey);
if (showLabels) {
let limitLabelEl = this.getLimitLabel(limit);
limitContainerEl.appendChild(limitLabelEl);
}
let limitEl = this.getLimitElement(limit);
limitContainerEl.appendChild(limitEl);
}, this);
});
}
},
showLabels(seriesKey) {
return this.showLimitLineLabels.seriesKey
&& (this.showLimitLineLabels.seriesKey === seriesKey);
},
getLimitElement(limit) {
let point = {
left: 0,
top: this.drawAPI.y(limit.point.y)
};
let LimitLineClass = Vue.extend(LimitLine);
const component = new LimitLineClass({
propsData: {
point,
cssClass: limit.cssClass
}
});
component.$mount();
return component.$el;
},
getLimitLabel(limit) {
let point = {
left: 0,
top: this.drawAPI.y(limit.point.y)
};
let LimitLabelClass = Vue.extend(LimitLabel);
const component = new LimitLabelClass({
propsData: {
limit,
point
}
});
component.$mount();
return component.$el;
},
drawAlarmPoints(alarmSet) { drawAlarmPoints(alarmSet) {
this.drawAPI.drawLimitPoints( this.drawAPI.drawLimitPoints(
alarmSet.points, alarmSet.points,
@ -401,11 +541,12 @@ export default {
chartElement.series.get('markerShape') chartElement.series.get('markerShape')
); );
}, },
drawLine(chartElement) { drawLine(chartElement, disconnected) {
this.drawAPI.drawLine( this.drawAPI.drawLine(
chartElement.getBuffer(), chartElement.getBuffer(),
chartElement.color().asRGBAArray(), chartElement.color().asRGBAArray(),
chartElement.count chartElement.count,
disconnected
); );
}, },
drawHighlights() { drawHighlights() {

View File

@ -97,7 +97,8 @@ export default class PlotSeries extends Model {
markers: true, markers: true,
markerShape: 'point', markerShape: 'point',
markerSize: 2.0, markerSize: 2.0,
alarmMarkers: true alarmMarkers: true,
limitLines: false
}; };
} }
@ -115,9 +116,16 @@ export default class PlotSeries extends Model {
this.domainObject = options.domainObject; this.domainObject = options.domainObject;
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier); this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject); this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject);
this.limitDefinition = this.openmct.telemetry.limitDefinition(options.domainObject);
this.limits = this.limitDefinition.limits();
this.openmct.time.on('bounds', this.updateLimits);
this.on('destroy', this.onDestroy, this); this.on('destroy', this.onDestroy, this);
} }
updateLimits(bounds) {
this.emit('limitBounds', bounds);
}
locateOldObject(oldStyleParent) { locateOldObject(oldStyleParent) {
return oldStyleParent.useCapability('composition') return oldStyleParent.useCapability('composition')
.then(function (children) { .then(function (children) {

View File

@ -60,6 +60,14 @@
{{ alarmMarkers ? "Enabled" : "Disabled" }} {{ alarmMarkers ? "Enabled" : "Disabled" }}
</div> </div>
</li> </li>
<li class="grid-row">
<div class="grid-cell label"
title="Display lines visually denoting alarm limits."
>Limit lines</div>
<div class="grid-cell value">
{{ limitLines ? "Enabled" : "Disabled" }}
</div>
</li>
<li class="grid-row"> <li class="grid-row">
<div class="grid-cell label" <div class="grid-cell label"
title="The plot line and marker color for this series." title="The plot line and marker color for this series."
@ -135,6 +143,9 @@ export default {
alarmMarkers() { alarmMarkers() {
return this.series.get('alarmMarkers'); return this.series.get('alarmMarkers');
}, },
limitLines() {
return this.series.get('limitLines');
},
seriesHexColor() { seriesHexColor() {
return this.series.get('color').asHexString(); return this.series.get('color').asHexString();
} }

View File

@ -91,6 +91,17 @@
> >
</div> </div>
</li> </li>
<li class="grid-row">
<div class="grid-cell label"
title="Display limit lines"
>Limit lines</div>
<div class="grid-cell value">
<input v-model="limitLines"
type="checkbox"
@change="updateForm('limitLines')"
>
</div>
</li>
<li v-show="markers || alarmMarkers" <li v-show="markers || alarmMarkers"
class="grid-row" class="grid-row"
> >
@ -168,6 +179,7 @@ export default {
markers: this.series.get('markers'), markers: this.series.get('markers'),
markerShape: this.series.get('markerShape'), markerShape: this.series.get('markerShape'),
alarmMarkers: this.series.get('alarmMarkers'), alarmMarkers: this.series.get('alarmMarkers'),
limitLines: this.series.get('limitLines'),
markerSize: this.series.get('markerSize'), markerSize: this.series.get('markerSize'),
validation: {}, validation: {},
swatchActive: false swatchActive: false
@ -240,6 +252,11 @@ export default {
modelProp: 'alarmMarkers', modelProp: 'alarmMarkers',
coerce: Boolean, coerce: Boolean,
objectPath: this.dynamicPathForKey('alarmMarkers') objectPath: this.dynamicPathForKey('alarmMarkers')
},
{
modelProp: 'limitLines',
coerce: Boolean,
objectPath: this.dynamicPathForKey('limitLines')
} }
]; ];

View File

@ -48,7 +48,7 @@
:highlights="highlights" :highlights="highlights"
:value-to-show-when-collapsed="legend.get('valueToShowWhenCollapsed')" :value-to-show-when-collapsed="legend.get('valueToShowWhenCollapsed')"
:series-object="seriesObject" :series-object="seriesObject"
:closest="seriesObject.closest" @legendHoverChanged="legendHoverChanged"
/> />
</div> </div>
<!-- EXPANDED PLOT LEGEND --> <!-- EXPANDED PLOT LEGEND -->
@ -89,6 +89,7 @@
:series-object="seriesObject" :series-object="seriesObject"
:highlights="highlights" :highlights="highlights"
:legend="legend" :legend="legend"
@legendHoverChanged="legendHoverChanged"
/> />
</tbody> </tbody>
</table> </table>
@ -160,6 +161,9 @@ export default {
expandLegend() { expandLegend() {
this.isLegendExpanded = !this.isLegendExpanded; this.isLegendExpanded = !this.isLegendExpanded;
this.legend.set('expanded', this.isLegendExpanded); this.legend.set('expanded', this.isLegendExpanded);
},
legendHoverChanged(data) {
this.$emit('legendHoverChanged', data);
} }
} }
}; };

View File

@ -24,6 +24,8 @@
:class="{ :class="{
'is-status--missing': isMissing 'is-status--missing': isMissing
}" }"
@mouseover="toggleHover(true)"
@mouseleave="toggleHover(false)"
> >
<div class="plot-series-swatch-and-name"> <div class="plot-series-swatch-and-name">
<span class="plot-series-color-swatch" <span class="plot-series-color-swatch"
@ -121,6 +123,12 @@ export default {
} else { } else {
this.formattedYValueFromStats = ''; this.formattedYValueFromStats = '';
} }
},
toggleHover(hover) {
this.hover = hover;
this.$emit('legendHoverChanged', {
seriesKey: this.hover ? this.seriesObject.keyString : ''
});
} }
} }
}; };

View File

@ -25,6 +25,8 @@
:class="{ :class="{
'is-status--missing': isMissing 'is-status--missing': isMissing
}" }"
@mouseover="toggleHover(true)"
@mouseleave="toggleHover(false)"
> >
<td class="plot-series-swatch-and-name"> <td class="plot-series-swatch-and-name">
<span class="plot-series-color-swatch" <span class="plot-series-color-swatch"
@ -160,6 +162,12 @@ export default {
this.formattedMinY = ''; this.formattedMinY = '';
this.formattedMaxY = ''; this.formattedMaxY = '';
} }
},
toggleHover(hover) {
this.hover = hover;
this.$emit('legendHoverChanged', {
seriesKey: this.hover ? this.seriesObject.keyString : ''
});
} }
} }
}; };

View File

@ -36,6 +36,7 @@ describe("the plugin", function () {
let telemetryPromise; let telemetryPromise;
let cleanupFirst; let cleanupFirst;
let mockObjectPath; let mockObjectPath;
let telemetrylimitProvider;
beforeEach((done) => { beforeEach((done) => {
mockObjectPath = [ mockObjectPath = [
@ -88,6 +89,45 @@ describe("the plugin", function () {
return telemetryPromise; return telemetryPromise;
}); });
telemetrylimitProvider = jasmine.createSpyObj('telemetrylimitProvider', [
'supportsLimits',
'getLimits',
'getLimitEvaluator'
]);
telemetrylimitProvider.supportsLimits.and.returnValue(true);
telemetrylimitProvider.getLimits.and.returnValue({
limits: function () {
return {
WARNING: {
low: {
cssClass: "is-limit--lwr is-limit--yellow",
'some-key': -0.5
},
high: {
cssClass: "is-limit--upr is-limit--yellow",
'some-key': 0.5
}
},
DISTRESS: {
low: {
cssClass: "is-limit--lwr is-limit--red",
'some-key': -0.9
},
high: {
cssClass: "is-limit--upr is-limit--red",
'some-key': 0.9
}
}
};
}
});
telemetrylimitProvider.getLimitEvaluator.and.returnValue({
evaluate: function () {
return {};
}
});
openmct.telemetry.addProvider(telemetrylimitProvider);
openmct.install(new PlotVuePlugin()); openmct.install(new PlotVuePlugin());
element = document.createElement("div"); element = document.createElement("div");
@ -657,6 +697,25 @@ describe("the plugin", function () {
done(); done();
}); });
}); });
describe('limits', () => {
it('lines are not displayed by default', () => {
let limitEl = element.querySelectorAll(".js-limit-area hr");
expect(limitEl.length).toBe(0);
});
it('lines are displayed when configuration is set to true', (done) => {
config.series.models[0].set('limitLines', true);
Vue.nextTick(() => {
let limitEl = element.querySelectorAll(".js-limit-area hr");
expect(limitEl.length).toBe(4);
done();
});
});
});
}); });
describe('the inspector view', () => { describe('the inspector view', () => {
@ -803,7 +862,7 @@ describe("the plugin", function () {
expandControl.dispatchEvent(clickEvent); expandControl.dispatchEvent(clickEvent);
const plotOptionsProperties = browseOptionsEl.querySelectorAll('.js-plot-options-browse-properties .grid-row'); const plotOptionsProperties = browseOptionsEl.querySelectorAll('.js-plot-options-browse-properties .grid-row');
expect(plotOptionsProperties.length).toEqual(5); expect(plotOptionsProperties.length).toEqual(6);
}); });
}); });
@ -845,7 +904,7 @@ describe("the plugin", function () {
expandControl.dispatchEvent(clickEvent); expandControl.dispatchEvent(clickEvent);
const plotOptionsProperties = editOptionsEl.querySelectorAll(".js-plot-options-edit-properties .grid-row"); const plotOptionsProperties = editOptionsEl.querySelectorAll(".js-plot-options-edit-properties .grid-row");
expect(plotOptionsProperties.length).toEqual(6); expect(plotOptionsProperties.length).toEqual(7);
}); });
it('shows yKeyOptions', () => { it('shows yKeyOptions', () => {

View File

@ -83,9 +83,9 @@
@update="timePopUpdate" @update="timePopUpdate"
/> />
<button <button
@click="showTimePopupStart"
class="c-button c-conductor__delta-button"
ref="startOffset" ref="startOffset"
class="c-button c-conductor__delta-button"
@click="showTimePopupStart"
> >
{{ offsets.start }} {{ offsets.start }}
</button> </button>
@ -135,7 +135,7 @@
class="c-button c-conductor__delta-button" class="c-button c-conductor__delta-button"
@click="showTimePopupEnd" @click="showTimePopupEnd"
> >
{{offsets.end}} {{ offsets.end }}
</button> </button>
</div> </div>

View File

@ -531,7 +531,7 @@ mct-plot {
} }
/***************** GENERAL STYLES, ALL STATES */ /***************** GENERAL STYLES, ALL STATES */
.plot-legend-item { .plot-legend-item, .plot-series-limit-label {
// General styles for legend items, both expanded and collapsed legend states // General styles for legend items, both expanded and collapsed legend states
> * + * { > * + * {
margin-left: $interiorMarginSm; margin-left: $interiorMarginSm;

View File

@ -70,6 +70,10 @@
&.is-limit--lwr:before { content: $glyph-icon-arrow-down !important; } &.is-limit--lwr:before { content: $glyph-icon-arrow-down !important; }
} }
@mixin andLine {
&.is-limit--line:before { content: '' !important; }
}
@mixin uIndicator($bg, $fg, $glyph) { @mixin uIndicator($bg, $fg, $glyph) {
background: $bg; background: $bg;
color: $fg; color: $fg;
@ -100,12 +104,14 @@
@include statusStyle($colorLimitYellowBg, $colorLimitYellowFg, true); @include statusStyle($colorLimitYellowBg, $colorLimitYellowFg, true);
@include statusIcon($colorLimitYellowIc, $glyph-icon-alert-rect); @include statusIcon($colorLimitYellowIc, $glyph-icon-alert-rect);
@include andUprLwr(); @include andUprLwr();
@include andLine();
} }
&.is-limit--red { &.is-limit--red {
@include statusStyle($colorLimitRedBg, $colorLimitRedFg, true); @include statusStyle($colorLimitRedBg, $colorLimitRedFg, true);
@include statusIcon($colorLimitRedIc, $glyph-icon-alert-triangle); @include statusIcon($colorLimitRedIc, $glyph-icon-alert-triangle);
@include andUprLwr(); @include andUprLwr();
@include andLine();
} }
} }