Styling for plot limits (#3917)

* Styling for plot limits and colors
* Updates to limit provider and css
* Change limits related CSS "*--upper" and "*--lower" to "*--upr" and
"*--lwr" for better parity with legacy naming;
* Refactor limit class to util
* Use new classes for sine wave generator for red-low and yellow-high
* Added modifier classes for right and below-aligned labels;
* Prevent label overlap of limits as much as possible
* Add border colors to limit labels for better visual ties to their lines
* Add documentation for limit level specification API change

Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
Charles Hacskaylo
2021-06-21 16:22:28 -07:00
committed by GitHub
parent d56d176aac
commit 2889e88a97
25 changed files with 577 additions and 129 deletions

View File

@ -1,17 +1,23 @@
<template>
<div class="plot-series-limit-label"
<div class="c-plot-limit"
:style="styleObj"
:class="limit.cssClass"
:class="limitClass"
>
<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 class="c-plot-limit__label">
<span class="c-plot-limit__direction-icon"></span>
<span class="c-plot-limit__severity-icon"></span>
<span class="c-plot-limit__limit-value">{{ limit.value }}</span>
<span class="c-plot-limit__series-color-swatch"
:style="{ 'background-color': limit.seriesColor }"
></span>
<span class="c-plot-limit__series-name">{{ limit.name }}</span>
</div>
</div>
</template>
<script>
import { getLimitClass } from "./limitUtil";
export default {
props: {
limit: {
@ -31,15 +37,14 @@ export default {
},
computed: {
styleObj() {
const top = `${this.point.top - 10}px`;
const left = `${this.point.left + 5}px`;
const top = `${this.point.top}px`;
return {
'position': 'absolute',
'top': top,
'left': left,
'color': '#fff'
'top': top
};
},
limitClass() {
return getLimitClass(this.limit, 'c-plot-limit--');
}
}
};

View File

@ -1,10 +1,13 @@
<template>
<hr :style="styleObj"
:class="cssWithoutUprLwr"
>
<div :style="styleObj"
class="c-plot-limit-line js-limit-line"
:class="limitClass"
></div>
</template>
<script>
import { getLimitClass } from "./limitUtil";
export default {
props: {
point: {
@ -14,30 +17,23 @@ export default {
return {};
}
},
cssClass: {
type: String,
limit: {
type: Object,
default() {
return '';
return {};
}
}
},
computed: {
styleObj() {
const top = `${this.point.top}px`;
const left = `${this.point.left}px`;
return {
'position': 'absolute',
'width': '100%',
'top': top,
'left': left
'top': top
};
},
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;
limitClass() {
return getLimitClass(this.limit, 'c-plot-limit-line--');
}
}
};

View File

@ -32,6 +32,7 @@ export default class MCTChartAlarmLineSet {
eventHelpers.extend(this);
this.listenTo(series, 'limitBounds', this.updateBounds, this);
this.listenTo(series, 'limits', this.getLimitPoints, this);
this.listenTo(series, 'change:xKey', this.getLimitPoints, this);
if (series.limits) {
@ -69,26 +70,28 @@ export default class MCTChartAlarmLineSet {
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(),
level: key.toLowerCase(),
name: this.name(),
point,
cssClass: limitForLevel.high.cssClass
seriesColor: series.get('color').asHexString(),
point: this.makePoint(Object.assign({ [xKey]: this.bounds.start }, limitForLevel.high), series),
value: series.getYVal(limitForLevel.high),
color: limitForLevel.high.color,
isUpper: true
});
}
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(),
level: key.toLowerCase(),
name: this.name(),
point,
cssClass: limitForLevel.low.cssClass
seriesColor: series.get('color').asHexString(),
point: this.makePoint(Object.assign({ [xKey]: this.bounds.start }, limitForLevel.low), series),
value: series.getYVal(limitForLevel.low),
color: limitForLevel.low.color,
isUpper: false
});
}
}, this);

View File

@ -46,6 +46,7 @@ import Vue from 'vue';
const MARKER_SIZE = 6.0;
const HIGHLIGHT_SIZE = MARKER_SIZE * 2.0;
const CLEARANCE = 15;
export default {
inject: ['openmct', 'domainObject'],
@ -472,13 +473,15 @@ export default {
}
Array.from(this.$refs.limitArea.children).forEach((el) => el.remove());
let limitPointOverlap = [];
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);
const overlap = this.getLimitOverlap(limit, limitPointOverlap);
limitPointOverlap.push(overlap);
let limitLabelEl = this.getLimitLabel(limit, overlap);
limitContainerEl.appendChild(limitLabelEl);
}
@ -502,14 +505,41 @@ export default {
const component = new LimitLineClass({
propsData: {
point,
cssClass: limit.cssClass
limit
}
});
component.$mount();
return component.$el;
},
getLimitLabel(limit) {
getLimitOverlap(limit, overlapMap) {
//calculate if limit lines are too close to each other
let limitTop = this.drawAPI.y(limit.point.y);
const needsVerticalAdjustment = limitTop - CLEARANCE <= 0;
let needsHorizontalAdjustment = false;
overlapMap.forEach(value => {
let diffTop;
if (limitTop > value.overlapTop) {
diffTop = limitTop - value.overlapTop;
} else {
diffTop = value.overlapTop - limitTop;
}
//need to compare +ves to +ves and -ves to -ves
if (!needsHorizontalAdjustment
&& Math.abs(diffTop) <= CLEARANCE
&& value.needsHorizontalAdjustment !== true) {
needsHorizontalAdjustment = true;
}
});
return {
needsHorizontalAdjustment,
needsVerticalAdjustment,
overlapTop: limitTop
};
},
getLimitLabel(limit, overlap) {
let point = {
left: 0,
top: this.drawAPI.y(limit.point.y)
@ -517,7 +547,7 @@ export default {
let LimitLabelClass = Vue.extend(LimitLabel);
const component = new LimitLabelClass({
propsData: {
limit,
limit: Object.assign({}, overlap, limit),
point
}
});

View File

@ -0,0 +1,32 @@
export function getLimitClass(limit, prefix) {
let cssClass = '';
//If color exists then use it, fall back to the cssClass
if (limit.color) {
cssClass = `${cssClass} ${prefix}${limit.color}`;
} else if (limit.cssClass) {
cssClass = `${cssClass}${limit.cssClass}`;
}
// If we applied the cssClass then skip these classes
if (limit.cssClass === undefined) {
if (limit.isUpper) {
cssClass = `${cssClass} ${prefix}upr`;
} else {
cssClass = `${cssClass} ${prefix}lwr`;
}
if (limit.level) {
cssClass = `${cssClass} ${prefix}${limit.level}`;
}
if (limit.needsHorizontalAdjustment) {
cssClass = `${cssClass} --align-label-right`;
}
if (limit.needsVerticalAdjustment) {
cssClass = `${cssClass} --align-label-below`;
}
}
return cssClass;
}

View File

@ -117,7 +117,17 @@ export default class PlotSeries extends Model {
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject);
this.limitDefinition = this.openmct.telemetry.limitDefinition(options.domainObject);
this.limits = this.limitDefinition.limits();
this.limits = [];
this.limitDefinition.limits().then(response => {
this.limits = [];
if (response) {
this.limits = response;
}
this.emit('limits', this);
});
this.openmct.time.on('bounds', this.updateLimits);
this.on('destroy', this.onDestroy, this);
}

View File

@ -49,6 +49,8 @@
</template>
<script>
import {getLimitClass} from "@/plugins/plot/chart/limitUtil";
export default {
props: {
valueToShowWhenCollapsed: {
@ -110,7 +112,7 @@ export default {
if (closest) {
this.formattedYValue = seriesObject.formatY(closest);
this.formattedXValue = seriesObject.formatX(closest);
this.mctLimitStateClass = closest.mctLimitState ? `${closest.mctLimitState.cssClass}` : '';
this.mctLimitStateClass = closest.mctLimitState ? getLimitClass(closest.mctLimitState, 'c-plot-limit--') : '';
} else {
this.formattedYValue = '';
this.formattedXValue = '';

View File

@ -74,6 +74,8 @@
</template>
<script>
import {getLimitClass} from "@/plugins/plot/chart/limitUtil";
export default {
props: {
seriesObject: {
@ -147,7 +149,7 @@ export default {
if (closest) {
this.formattedYValue = seriesObject.formatY(closest);
this.formattedXValue = seriesObject.formatX(closest);
this.mctLimitStateClass = seriesObject.closest.mctLimitState ? seriesObject.closest.mctLimitState.cssClass : '';
this.mctLimitStateClass = seriesObject.closest.mctLimitState ? getLimitClass(seriesObject.closest.mctLimitState, 'c-plot-limit--') : '';
} else {
this.formattedYValue = '';
this.formattedXValue = '';

View File

@ -21,36 +21,36 @@
*****************************************************************************/
export const COLOR_PALETTE = [
[0x20, 0xB2, 0xAA],
[0x9A, 0xCD, 0x32],
[0xFF, 0x8C, 0x00],
[0xD2, 0xB4, 0x8C],
[0x40, 0xE0, 0xD0],
[0x41, 0x69, 0xFF],
[0xFF, 0xD7, 0x00],
[0x6A, 0x5A, 0xCD],
[0xEE, 0x82, 0xEE],
[0xCC, 0x99, 0x66],
[0x99, 0xCC, 0xCC],
[0x66, 0xCC, 0x33],
[0xFF, 0xCC, 0x00],
[0xFF, 0x66, 0x33],
[0xCC, 0x66, 0xFF],
[0xFF, 0x00, 0x66],
[0xFF, 0xFF, 0x00],
[0x80, 0x00, 0x80],
[0x00, 0x86, 0x8B],
[0x00, 0x8A, 0x00],
[0xFF, 0x00, 0x00],
[0x00, 0x00, 0xFF],
[0xF5, 0xDE, 0xB3],
[0xBC, 0x8F, 0x8F],
[0x46, 0x82, 0xB4],
[0xFF, 0xAF, 0xAF],
[0x43, 0xCD, 0x80],
[0xCD, 0xC1, 0xC5],
[0xA0, 0x52, 0x2D],
[0x64, 0x95, 0xED]
[0x00, 0x37, 0xFF],
[0xF0, 0x60, 0x00],
[0x00, 0x70, 0x40],
[0xFB, 0x49, 0x49],
[0xC8, 0x00, 0xCF],
[0x55, 0x77, 0xF2],
[0xFF, 0xA6, 0x3D],
[0x05, 0xA3, 0x00],
[0xF0, 0x00, 0x6C],
[0x77, 0x17, 0x7A],
[0x23, 0xA9, 0xDB],
[0xFA, 0xF0, 0x6F],
[0x4E, 0xF0, 0x48],
[0xAD, 0x50, 0x72],
[0x94, 0x25, 0xEA],
[0x21, 0x87, 0x82],
[0x8F, 0x6E, 0x47],
[0xf0, 0x59, 0xcb],
[0x34, 0xB6, 0x7D],
[0x6A, 0x36, 0xFF],
[0x56, 0xF0, 0xE8],
[0xA1, 0x8C, 0x1C],
[0xCB, 0xE1, 0x44],
[0xFF, 0x84, 0x9E],
[0xB7, 0x79, 0xE7],
[0x8C, 0xC9, 0xFD],
[0xDB, 0xAA, 0x6E],
[0xB8, 0xDF, 0x97],
[0xFF, 0xBC, 0xDA],
[0xD3, 0xB6, 0xDE]
];
export function isDefaultColor(color) {

View File

@ -97,7 +97,7 @@ describe("the plugin", function () {
telemetrylimitProvider.supportsLimits.and.returnValue(true);
telemetrylimitProvider.getLimits.and.returnValue({
limits: function () {
return {
return Promise.resolve({
WARNING: {
low: {
cssClass: "is-limit--lwr is-limit--yellow",
@ -118,7 +118,7 @@ describe("the plugin", function () {
'some-key': 0.9
}
}
};
});
}
});
telemetrylimitProvider.getLimitEvaluator.and.returnValue({
@ -709,7 +709,7 @@ describe("the plugin", function () {
config.series.models[0].set('limitLines', true);
Vue.nextTick(() => {
let limitEl = element.querySelectorAll(".js-limit-area hr");
let limitEl = element.querySelectorAll(".js-limit-area .js-limit-line");
expect(limitEl.length).toBe(4);
done();
});