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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 577 additions and 129 deletions

14
API.md
View File

@ -595,9 +595,17 @@ section.
#### Limit Evaluators **draft** #### Limit Evaluators **draft**
Limit evaluators allow a telemetry integrator to define how limits should be Limit evaluators allow a telemetry integrator to define which limits exist for a
applied to telemetry from a given domain object. For an example of a limit telemetry endpoint and how limits should be applied to telemetry from a given domain object.
evaluator, take a look at `examples/generator/SinewaveLimitProvider.js`.
A limit evaluator can implement the `evalute` method which is used to define how limits
should be applied to telemetry and the `getLimits` method which is used to specify
what the limit values are for different limit levels.
Limit levels can be mapped to one of 5 colors for visualization:
`purple`, `red`, `orange`, `yellow` and `cyan`.
For an example of a limit evaluator, take a look at `examples/generator/SinewaveLimitProvider.js`.
### Telemetry Consumer APIs **draft** ### Telemetry Consumer APIs **draft**

View File

@ -26,14 +26,26 @@ define([
) { ) {
var RED = { var PURPLE = {
sin: 2.2,
cos: 2.2
},
RED = {
sin: 0.9, sin: 0.9,
cos: 0.9 cos: 0.9
}, },
ORANGE = {
sin: 0.7,
cos: 0.7
},
YELLOW = { YELLOW = {
sin: 0.5, sin: 0.5,
cos: 0.5 cos: 0.5
}, },
CYAN = {
sin: 0.45,
cos: 0.45
},
LIMITS = { LIMITS = {
rh: { rh: {
cssClass: "is-limit--upr is-limit--red", cssClass: "is-limit--upr is-limit--red",
@ -94,32 +106,66 @@ define([
}; };
SinewaveLimitProvider.prototype.getLimits = function (domainObject) { SinewaveLimitProvider.prototype.getLimits = function (domainObject) {
return { return {
limits: function () { limits: function () {
return { return Promise.resolve({
WATCH: {
low: {
color: "cyan",
sin: -CYAN.sin,
cos: -CYAN.cos
},
high: {
color: "cyan",
...CYAN
}
},
WARNING: { WARNING: {
low: { low: {
cssClass: "is-limit--lwr is-limit--yellow", color: "yellow",
sin: -YELLOW.sin, sin: -YELLOW.sin,
cos: -YELLOW.cos cos: -YELLOW.cos
}, },
high: { high: {
cssClass: "is-limit--upr is-limit--yellow", color: "yellow",
...YELLOW ...YELLOW
} }
}, },
DISTRESS: { DISTRESS: {
low: { low: {
cssClass: "is-limit--lwr is-limit--red", color: "orange",
sin: -ORANGE.sin,
cos: -ORANGE.cos
},
high: {
color: "orange",
...ORANGE
}
},
CRITICAL: {
low: {
color: "red",
sin: -RED.sin, sin: -RED.sin,
cos: -RED.cos cos: -RED.cos
}, },
high: { high: {
cssClass: "is-limit--upr is-limit--red", color: "red",
...RED ...RED
} }
},
SEVERE: {
low: {
color: "purple",
sin: -PURPLE.sin,
cos: -PURPLE.cos
},
high: {
color: "purple",
...PURPLE
} }
}; }
});
} }
}; };
}; };

View File

@ -567,21 +567,24 @@ define([
* @method limits returns a limits object of * @method limits returns a limits object of
* type { * type {
* level1: { * level1: {
* low: { key1: value1, key2: value2 }, * low: { key1: value1, key2: value2, color: <supportedColor> },
* high: { key1: value1, key2: value2 } * high: { key1: value1, key2: value2, color: <supportedColor> }
* }, * },
* level2: { * level2: {
* low: { key1: value1, key2: value2 }, * low: { key1: value1, key2: value2 },
* high: { key1: value1, key2: value2 } * high: { key1: value1, key2: value2 }
* } * }
* } * }
* supported colors are purple, red, orange, yellow and cyan
* @memberof module:openmct.TelemetryAPI~TelemetryProvider# * @memberof module:openmct.TelemetryAPI~TelemetryProvider#
*/ */
TelemetryAPI.prototype.getLimits = function (domainObject) { TelemetryAPI.prototype.getLimits = function (domainObject) {
const provider = this.findLimitEvaluator(domainObject); const provider = this.findLimitEvaluator(domainObject);
if (!provider || !provider.getLimits) { if (!provider || !provider.getLimits) {
return { return {
limits: function () {} limits: function () {
return Promise.resolve(undefined);
}
}; };
} }

View File

@ -21,6 +21,10 @@
margin-left: $interiorMargin; margin-left: $interiorMargin;
} }
&__value {
@include isLimit();
}
.c-frame & { .c-frame & {
@include abs(); @include abs();
border: 1px solid transparent; border: 1px solid transparent;

View File

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

View File

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

View File

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

View File

@ -46,6 +46,7 @@ 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;
const CLEARANCE = 15;
export default { export default {
inject: ['openmct', 'domainObject'], inject: ['openmct', 'domainObject'],
@ -472,13 +473,15 @@ export default {
} }
Array.from(this.$refs.limitArea.children).forEach((el) => el.remove()); Array.from(this.$refs.limitArea.children).forEach((el) => el.remove());
let limitPointOverlap = [];
this.limitLines.forEach((limitLine) => { this.limitLines.forEach((limitLine) => {
let limitContainerEl = this.$refs.limitArea; let limitContainerEl = this.$refs.limitArea;
limitLine.limits.forEach((limit) => { limitLine.limits.forEach((limit) => {
const showLabels = this.showLabels(limit.seriesKey); const showLabels = this.showLabels(limit.seriesKey);
if (showLabels) { 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); limitContainerEl.appendChild(limitLabelEl);
} }
@ -502,14 +505,41 @@ export default {
const component = new LimitLineClass({ const component = new LimitLineClass({
propsData: { propsData: {
point, point,
cssClass: limit.cssClass limit
} }
}); });
component.$mount(); component.$mount();
return component.$el; 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 = { let point = {
left: 0, left: 0,
top: this.drawAPI.y(limit.point.y) top: this.drawAPI.y(limit.point.y)
@ -517,7 +547,7 @@ export default {
let LimitLabelClass = Vue.extend(LimitLabel); let LimitLabelClass = Vue.extend(LimitLabel);
const component = new LimitLabelClass({ const component = new LimitLabelClass({
propsData: { propsData: {
limit, limit: Object.assign({}, overlap, limit),
point 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.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.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.openmct.time.on('bounds', this.updateLimits);
this.on('destroy', this.onDestroy, this); this.on('destroy', this.onDestroy, this);
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -190,6 +190,11 @@
} }
} }
// All tables
td {
@include isLimit();
}
/******************************* SPECIFIC CASE WRAPPERS */ /******************************* SPECIFIC CASE WRAPPERS */
.is-editing { .is-editing {
.c-telemetry-table__headers__labels { .c-telemetry-table__headers__labels {

View File

@ -10,6 +10,7 @@
@import "../../styles/glyphs"; @import "../../styles/glyphs";
@import "../../styles/global"; @import "../../styles/global";
@import "../../styles/status"; @import "../../styles/status";
@import "../../styles/limits";
@import "../../styles/controls"; @import "../../styles/controls";
@import "../../styles/forms"; @import "../../styles/forms";
@import "../../styles/table"; @import "../../styles/table";

View File

@ -10,6 +10,7 @@
@import "../../styles/glyphs"; @import "../../styles/glyphs";
@import "../../styles/global"; @import "../../styles/global";
@import "../../styles/status"; @import "../../styles/status";
@import "../../styles/limits";
@import "../../styles/controls"; @import "../../styles/controls";
@import "../../styles/forms"; @import "../../styles/forms";
@import "../../styles/table"; @import "../../styles/table";

View File

@ -10,6 +10,7 @@
@import "../../styles/glyphs"; @import "../../styles/glyphs";
@import "../../styles/global"; @import "../../styles/global";
@import "../../styles/status"; @import "../../styles/status";
@import "../../styles/limits";
@import "../../styles/controls"; @import "../../styles/controls";
@import "../../styles/forms"; @import "../../styles/forms";
@import "../../styles/table"; @import "../../styles/table";

View File

@ -314,12 +314,21 @@ $colorTelemStale: pushBack($colorBodyFg, 20%);
$styleTelemStale: italic; $styleTelemStale: italic;
// Limits // Limits
$colorLimitYellowBg: #ac7300; $colorLimitYellowBg: #B18B05;
$colorLimitYellowFg: #ffe64d; $colorLimitYellowFg: #FEEEB5;
$colorLimitYellowIc: #ffb607; $colorLimitYellowIc: #FDC707;
$colorLimitOrangeBg: #B36B00;
$colorLimitOrangeFg: #FFE0B2;
$colorLimitOrangeIc: #ff9900;
$colorLimitRedBg: #940000; $colorLimitRedBg: #940000;
$colorLimitRedFg: #ffa489; $colorLimitRedFg: #ffa489;
$colorLimitRedIc: #ff4222; $colorLimitRedIc: #ff4222;
$colorLimitPurpleBg: #891BB3;
$colorLimitPurpleFg: #EDBEFF;
$colorLimitPurpleIc: #c327ff;
$colorLimitCyanBg: #4BA6B3;
$colorLimitCyanFg: #D3FAFF;
$colorLimitCyanIc: #6BEDFF;
// Bubble colors // Bubble colors
$colorInfoBubbleBg: #dddddd; $colorInfoBubbleBg: #dddddd;
@ -359,6 +368,7 @@ $colorPlotAreaBorder: $colorInteriorBorder;
$colorPlotLabelFg: pushBack($colorPlotFg, 20%); $colorPlotLabelFg: pushBack($colorPlotFg, 20%);
$legendHoverValueBg: rgba($colorBodyFg, 0.2); $legendHoverValueBg: rgba($colorBodyFg, 0.2);
$legendTableHeadBg: $colorTabHeaderBg; $legendTableHeadBg: $colorTabHeaderBg;
$colorPlotLimitLineBg: rgba($colorBodyBg, 0.2);
// Tree // Tree
$colorTreeBg: transparent; $colorTreeBg: transparent;

View File

@ -318,12 +318,21 @@ $colorTelemStale: pushBack($colorBodyFg, 20%);
$styleTelemStale: italic; $styleTelemStale: italic;
// Limits // Limits
$colorLimitYellowBg: #ac7300; $colorLimitYellowBg: #B18B05;
$colorLimitYellowFg: #ffe64d; $colorLimitYellowFg: #FEEEB5;
$colorLimitYellowIc: #ffb607; $colorLimitYellowIc: #FDC707;
$colorLimitOrangeBg: #B36B00;
$colorLimitOrangeFg: #FFE0B2;
$colorLimitOrangeIc: #ff9900;
$colorLimitRedBg: #940000; $colorLimitRedBg: #940000;
$colorLimitRedFg: #ffa489; $colorLimitRedFg: #ffa489;
$colorLimitRedIc: #ff4222; $colorLimitRedIc: #ff4222;
$colorLimitPurpleBg: #891BB3;
$colorLimitPurpleFg: #EDBEFF;
$colorLimitPurpleIc: #c327ff;
$colorLimitCyanBg: #4BA6B3;
$colorLimitCyanFg: #D3FAFF;
$colorLimitCyanIc: #6BEDFF;
// Bubble colors // Bubble colors
$colorInfoBubbleBg: #dddddd; $colorInfoBubbleBg: #dddddd;
@ -363,6 +372,7 @@ $colorPlotAreaBorder: $colorInteriorBorder;
$colorPlotLabelFg: pushBack($colorPlotFg, 20%); $colorPlotLabelFg: pushBack($colorPlotFg, 20%);
$legendHoverValueBg: rgba($colorBodyFg, 0.2); $legendHoverValueBg: rgba($colorBodyFg, 0.2);
$legendTableHeadBg: rgba($colorBodyFg, 0.15); $legendTableHeadBg: rgba($colorBodyFg, 0.15);
$colorPlotLimitLineBg: rgba($colorBodyBg, 0.2);
// Tree // Tree
$colorTreeBg: transparent; $colorTreeBg: transparent;

View File

@ -317,9 +317,18 @@ $styleTelemStale: italic;
$colorLimitYellowBg: #ffe64d; $colorLimitYellowBg: #ffe64d;
$colorLimitYellowFg: #7f4f20; $colorLimitYellowFg: #7f4f20;
$colorLimitYellowIc: #e7a115; $colorLimitYellowIc: #e7a115;
$colorLimitOrangeBg: #B36B00;
$colorLimitOrangeFg: #FFE0B2;
$colorLimitOrangeIc: #ff9900;
$colorLimitRedBg: #ff0000; $colorLimitRedBg: #ff0000;
$colorLimitRedFg: #fff; $colorLimitRedFg: #fff;
$colorLimitRedIc: #ffa99a; $colorLimitRedIc: #ffa99a;
$colorLimitPurpleBg: #891BB3;
$colorLimitPurpleFg: #EDBEFF;
$colorLimitPurpleIc: #c327ff;
$colorLimitCyanBg: #4BA6B3;
$colorLimitCyanFg: #D3FAFF;
$colorLimitCyanIc: #1795c0;
// Bubble colors // Bubble colors
$colorInfoBubbleBg: $colorMenuBg; $colorInfoBubbleBg: $colorMenuBg;
@ -359,6 +368,7 @@ $colorPlotAreaBorder: $colorInteriorBorder;
$colorPlotLabelFg: pushBack($colorPlotFg, 20%); $colorPlotLabelFg: pushBack($colorPlotFg, 20%);
$legendHoverValueBg: rgba($colorBodyFg, 0.2); $legendHoverValueBg: rgba($colorBodyFg, 0.2);
$legendTableHeadBg: rgba($colorBodyFg, 0.15); $legendTableHeadBg: rgba($colorBodyFg, 0.15);
$colorPlotLimitLineBg: rgba($colorBodyBg, 0.4);
// Tree // Tree
$colorTreeBg: transparent; $colorTreeBg: transparent;

View File

@ -136,6 +136,7 @@ mct-plot {
left: nth($plotDisplayArea, 4); left: nth($plotDisplayArea, 4);
.gl-plot-display-area { .gl-plot-display-area {
overflow: hidden;
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
@ -538,12 +539,9 @@ mct-plot {
} }
.plot-series-color-swatch { .plot-series-color-swatch {
border-radius: 30%; //$smallCr; @include colorSwatch();
border: 1px solid $colorBodyBg;
display: inline-block; display: inline-block;
flex: 0 0 auto; flex: 0 0 auto;
height: $plotSwatchD;
width: $plotSwatchD;
} }
.plot-series-name { .plot-series-name {
display: inline; display: inline;
@ -552,6 +550,7 @@ mct-plot {
.plot-series-value { .plot-series-value {
@include ellipsize(); @include ellipsize();
@include isLimit();
} }
} }

270
src/styles/_limits.scss Normal file
View File

@ -0,0 +1,270 @@
$plotLimitLineSize: 1px;
$plotLimitDashWidthOffset: 10px;
$lineBlocker: $colorPlotLimitLineBg;
$plotLimitDashWidthSeverity: 50px;
$plotLimitDashWidthCritical: $plotLimitDashWidthSeverity - $plotLimitDashWidthOffset;
$plotLimitDashWidthDistress: $plotLimitDashWidthCritical - $plotLimitDashWidthOffset;
$plotLimitDashWidthWarning: $plotLimitDashWidthDistress - $plotLimitDashWidthOffset;
$plotLimitDashWidthWatch: $plotLimitDashWidthWarning - $plotLimitDashWidthOffset;
@mixin plotLimitLine($c, $breakPerc) {
background: $lineBlocker linear-gradient(
90deg,
$c $breakPerc,
transparent $breakPerc,
transparent 100%
) repeat-x;
}
@mixin plotLimitDirectionGradient($c, $deg: 0deg) {
background: linear-gradient(
$deg,
$c,
transparent
)
}
@mixin plotLimitLineUpper($c) {
$breakPerc: 80%;
@include plotLimitLine($c: $c, $breakPerc: $breakPerc);
}
@mixin plotLimitLineLower($c) {
$breakPerc: 30%;
@include plotLimitLine($c: $c, $breakPerc: $breakPerc);
}
.c-plot-limit-line {
box-shadow: $lineBlocker 0 0 0 2px;
height: $plotLimitLineSize;
width: 100%;
position: absolute;
z-index: 1;
// Colors and directions
&--purple.c-plot-limit-line--upr {
@include plotLimitLineUpper($colorLimitPurpleIc);
}
&--purple.c-plot-limit-line--lwr {
@include plotLimitLineLower($colorLimitPurpleIc);
}
&--red.c-plot-limit-line--upr {
@include plotLimitLineUpper($colorLimitRedIc);
}
&--red.c-plot-limit-line--lwr {
@include plotLimitLineLower($colorLimitRedIc);
}
&--orange.c-plot-limit-line--upr {
@include plotLimitLineUpper($colorLimitOrangeIc);
}
&--orange.c-plot-limit-line--lwr {
@include plotLimitLineLower($colorLimitOrangeIc);
}
&--yellow.c-plot-limit-line--upr {
@include plotLimitLineUpper($colorLimitYellowIc);
}
&--yellow.c-plot-limit-line--lwr {
@include plotLimitLineLower($colorLimitYellowIc);
}
&--cyan.c-plot-limit-line--upr {
@include plotLimitLineUpper($colorLimitCyanIc);
}
&--cyan.c-plot-limit-line--lwr {
@include plotLimitLineLower($colorLimitCyanIc);
}
// Severities
&--severe {
background-size: $plotLimitDashWidthSeverity 100% !important;
}
&--critical {
background-size: $plotLimitDashWidthCritical 100% !important;
}
&--distress {
background-size: $plotLimitDashWidthDistress 100% !important;
}
&--warning {
background-size: $plotLimitDashWidthWarning 100% !important;
}
&--watch {
background-size: $plotLimitDashWidthWatch 100% !important;
}
}
.c-plot-limit {
// Holds both label and directional gradient
$labelCr: $basicCr;
display: flex;
position: absolute;
width: 100%;
z-index: 0;
&__label {
border-width: 1px 1px 0 0;
border-style: solid;
border-radius: 0 $labelCr 0 0;
display: flex;
flex: 0 0 auto;
align-items: center;
padding: 2px 4px;
transform: translateY(-100%);
> * + * {
margin-left: $interiorMarginSm;
}
}
&.--align-label-right {
justify-content: flex-end;
.c-plot-limit__label {
border-radius: $labelCr 0 0 0;
border-width: 1px 0 0 1px;
}
}
&.--align-label-below {
.c-plot-limit__label {
border-radius: 0 0 $labelCr 0;
border-width: 0 1px 1px 0;
transform: translateY(0);
}
&.--align-label-right {
.c-plot-limit__label {
border-radius: 0 0 0 $labelCr;
border-width: 0 0 1px 1px;
}
}
}
[class*='icon'] {
&:before {
display: block;
font-family: symbolsfont;
font-size: 0.9em;
}
}
&__series-color-swatch {
@include colorSwatch();
display: block;
flex: 0 0 auto;
}
&:before {
// Direction gradient
content: "";
display: block;
position: absolute;
left: 0;
right: 0;
height: 100%;
opacity: 0.2;
}
&--upr:before {
transform: translateY(-100%);
}
&--lwr:before {
transform: scaleY(-1); // This inverts the gradient direction
}
// Label styling
&--purple [class*='label'] {
background-color: $colorLimitPurpleBg;
border-color: $colorLimitPurpleIc;
color: $colorLimitPurpleFg;
}
&--red [class*='label'] {
background-color: $colorLimitRedBg;
border-color: $colorLimitRedIc;
color: $colorLimitRedFg;
}
&--orange [class*='label'] {
background-color: $colorLimitOrangeBg;
border-color: $colorLimitOrangeIc;
color: $colorLimitOrangeFg;
}
&--yellow [class*='label'] {
background-color: $colorLimitYellowBg;
border-color: $colorLimitYellowIc;
color: $colorLimitYellowFg;
}
&--cyan [class*='label'] {
background-color: $colorLimitCyanBg;
border-color: $colorLimitCyanIc;
color: $colorLimitCyanFg;
}
// Directional gradients
&--purple:before {
@include plotLimitDirectionGradient($c: $colorLimitPurpleIc);
}
&--red:before {
@include plotLimitDirectionGradient($c: $colorLimitRedIc);
}
&--orange:before {
@include plotLimitDirectionGradient($c: $colorLimitOrangeIc);
}
&--yellow:before {
@include plotLimitDirectionGradient($c: $colorLimitYellowIc);
}
&--cyan:before {
@include plotLimitDirectionGradient($c: $colorLimitCyanIc);
}
}
// Severity icons
.c-plot-limit__label .c-plot-limit__severity-icon:before {
.c-plot-limit--severe & {
content: $glyph-icon-alert-triangle;
}
.c-plot-limit--critical & {
content: $glyph-icon-alert-rect;
}
.c-plot-limit--distress & {
content: $glyph-icon-bell;
}
.c-plot-limit--warning & {
content: $glyph-icon-asterisk;
}
.c-plot-limit--watch & {
content: $glyph-icon-eye-open;
}
}
// Direction icons
.c-plot-limit__label .c-plot-limit__direction-icon:before {
.c-plot-limit--upr & {
content: $glyph-icon-arrow-up;
}
.c-plot-limit--lwr & {
content: $glyph-icon-arrow-down;
}
}

View File

@ -151,6 +151,25 @@
} }
} }
@mixin isLimit() {
&[class*='is-limit'] {
&:before {
display: inline-block;
font-family: symbolsfont;
margin-right: $interiorMarginSm;
}
}
&.is-limit--lwr:before { content: $glyph-icon-arrow-down; }
&.is-limit--upr:before { content: $glyph-icon-arrow-up; }
&.is-limit--purple { background: $colorLimitPurpleBg !important; color: $colorLimitPurpleFg !important; }
&.is-limit--red { background: $colorLimitRedBg !important; color: $colorLimitRedFg !important; }
&.is-limit--orange { background: $colorLimitOrangeBg !important; color: $colorLimitOrangeFg !important; }
&.is-limit--yellow { background: $colorLimitYellowBg !important; color: $colorLimitYellowFg !important; }
&.is-limit--cyan { background: $colorLimitCyanBg !important; color: $colorLimitCyanFg !important; }
}
@mixin bgDiagonalStripes($c: yellow, $a: 0.1, $d: 40px) { @mixin bgDiagonalStripes($c: yellow, $a: 0.1, $d: 40px) {
background-image: linear-gradient(-45deg, background-image: linear-gradient(-45deg,
rgba($c, $a) 25%, transparent 25%, rgba($c, $a) 25%, transparent 25%,
@ -222,6 +241,13 @@
background-size: $bgSize; background-size: $bgSize;
} }
@mixin colorSwatch() {
border-radius: 30%;
border: 1px solid $colorBodyBg;
height: $plotSwatchD;
width: $plotSwatchD;
}
@mixin noColor() { @mixin noColor() {
// A "no fill/stroke" selection option. Used in palettes. // A "no fill/stroke" selection option. Used in palettes.
$c: red; $c: red;

View File

@ -65,15 +65,6 @@
} }
} }
@mixin andUprLwr {
&.is-limit--upr:before { content: $glyph-icon-arrow-up !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;
@ -95,26 +86,9 @@
display: inline-block; display: inline-block;
padding: 2px $interiorMargin; padding: 2px $interiorMargin;
} }
} }
/*************************************************** STYLES */ /*************************************************** STYLES */
*:not(tr) {
&.is-limit--yellow {
@include statusStyle($colorLimitYellowBg, $colorLimitYellowFg, true);
@include statusIcon($colorLimitYellowIc, $glyph-icon-alert-rect);
@include andUprLwr();
@include andLine();
}
&.is-limit--red {
@include statusStyle($colorLimitRedBg, $colorLimitRedFg, true);
@include statusIcon($colorLimitRedIc, $glyph-icon-alert-triangle);
@include andUprLwr();
@include andLine();
}
}
tr { tr {
&.is-limit--yellow { &.is-limit--yellow {
@include statusStyle($colorLimitYellowBg, $colorLimitYellowFg); @include statusStyle($colorLimitYellowBg, $colorLimitYellowFg);