mirror of
https://github.com/nasa/openmct.git
synced 2024-12-19 21:27:52 +00:00
Support for spectral plots via existing bar graphs (#5162)
Spectral plots support Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov> Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com> Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
parent
111b0d0d68
commit
162cc6bc77
@ -29,12 +29,12 @@ define([
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "cos",
|
key: "wavelengths",
|
||||||
name: "Cosine",
|
name: "Wavelength",
|
||||||
unit: "deg",
|
unit: "nm",
|
||||||
formatString: '%0.2f',
|
format: 'string[]',
|
||||||
hints: {
|
hints: {
|
||||||
domain: 3
|
range: 4
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Need to enable "LocalTimeSystem" plugin to make use of this
|
// Need to enable "LocalTimeSystem" plugin to make use of this
|
||||||
@ -64,6 +64,14 @@ define([
|
|||||||
hints: {
|
hints: {
|
||||||
range: 2
|
range: 2
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "intensities",
|
||||||
|
name: "Intensities",
|
||||||
|
format: 'number[]',
|
||||||
|
hints: {
|
||||||
|
range: 3
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -77,7 +77,8 @@
|
|||||||
utc: nextStep,
|
utc: nextStep,
|
||||||
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
||||||
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness),
|
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness),
|
||||||
wavelength: wavelength(start, nextStep),
|
wavelengths: wavelengths(),
|
||||||
|
intensities: intensities(),
|
||||||
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
|
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -126,7 +127,8 @@
|
|||||||
utc: nextStep,
|
utc: nextStep,
|
||||||
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
||||||
sin: sin(nextStep, period, amplitude, offset, phase, randomness),
|
sin: sin(nextStep, period, amplitude, offset, phase, randomness),
|
||||||
wavelength: wavelength(start, nextStep),
|
wavelengths: wavelengths(),
|
||||||
|
intensities: intensities(),
|
||||||
cos: cos(nextStep, period, amplitude, offset, phase, randomness)
|
cos: cos(nextStep, period, amplitude, offset, phase, randomness)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -154,8 +156,28 @@
|
|||||||
* Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
|
* Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
function wavelength(start, nextStep) {
|
function wavelengths() {
|
||||||
return (nextStep - start) / 10;
|
let values = [];
|
||||||
|
while (values.length < 5) {
|
||||||
|
const randomValue = Math.random() * 100;
|
||||||
|
if (!values.includes(randomValue)) {
|
||||||
|
values.push(String(randomValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
function intensities() {
|
||||||
|
let values = [];
|
||||||
|
while (values.length < 5) {
|
||||||
|
const randomValue = Math.random() * 10;
|
||||||
|
if (!values.includes(randomValue)) {
|
||||||
|
values.push(String(randomValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendError(error, message) {
|
function sendError(error, message) {
|
||||||
|
@ -121,6 +121,18 @@ define([
|
|||||||
return _.sortBy(matchingMetadata, ...iteratees);
|
return _.sortBy(matchingMetadata, ...iteratees);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check out of a given metadata has array values
|
||||||
|
*/
|
||||||
|
TelemetryMetadataManager.prototype.isArrayValue = function (metadata) {
|
||||||
|
const regex = /\[\]$/g;
|
||||||
|
if (!metadata.format && !metadata.formatString) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (metadata.format || metadata.formatString).match(regex) !== null;
|
||||||
|
};
|
||||||
|
|
||||||
TelemetryMetadataManager.prototype.getFilterableValues = function () {
|
TelemetryMetadataManager.prototype.getFilterableValues = function () {
|
||||||
return this.valueMetadatas.filter(metadatum => metadatum.filters && metadatum.filters.length > 0);
|
return this.valueMetadatas.filter(metadatum => metadatum.filters && metadatum.filters.length > 0);
|
||||||
};
|
};
|
||||||
|
@ -43,9 +43,23 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.valueMetadata = valueMetadata;
|
this.valueMetadata = valueMetadata;
|
||||||
this.formatter = formatMap.get(valueMetadata.format) || numberFormatter;
|
|
||||||
|
|
||||||
if (valueMetadata.format === 'enum') {
|
function getNonArrayValue(value) {
|
||||||
|
//metadata format could have array formats ex. string[]/number[]
|
||||||
|
const arrayRegex = /\[\]$/g;
|
||||||
|
if (value && value.match(arrayRegex)) {
|
||||||
|
return value.replace(arrayRegex, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
let valueMetadataFormat = getNonArrayValue(valueMetadata.format);
|
||||||
|
|
||||||
|
//Is there an existing formatter for the format specified? If not, default to number format
|
||||||
|
this.formatter = formatMap.get(valueMetadataFormat) || numberFormatter;
|
||||||
|
|
||||||
|
if (valueMetadataFormat === 'enum') {
|
||||||
this.formatter = {};
|
this.formatter = {};
|
||||||
this.enumerations = valueMetadata.enumerations.reduce(function (vm, e) {
|
this.enumerations = valueMetadata.enumerations.reduce(function (vm, e) {
|
||||||
vm.byValue[e.value] = e.string;
|
vm.byValue[e.value] = e.string;
|
||||||
@ -77,13 +91,13 @@ define([
|
|||||||
// Check for formatString support once instead of per format call.
|
// Check for formatString support once instead of per format call.
|
||||||
if (valueMetadata.formatString) {
|
if (valueMetadata.formatString) {
|
||||||
const baseFormat = this.formatter.format;
|
const baseFormat = this.formatter.format;
|
||||||
const formatString = valueMetadata.formatString;
|
const formatString = getNonArrayValue(valueMetadata.formatString);
|
||||||
this.formatter.format = function (value) {
|
this.formatter.format = function (value) {
|
||||||
return printj.sprintf(formatString, baseFormat.call(this, value));
|
return printj.sprintf(formatString, baseFormat.call(this, value));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valueMetadata.format === 'string') {
|
if (valueMetadataFormat === 'string') {
|
||||||
this.formatter.parse = function (value) {
|
this.formatter.parse = function (value) {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return '';
|
return '';
|
||||||
@ -108,7 +122,14 @@ define([
|
|||||||
|
|
||||||
TelemetryValueFormatter.prototype.parse = function (datum) {
|
TelemetryValueFormatter.prototype.parse = function (datum) {
|
||||||
if (_.isObject(datum)) {
|
if (_.isObject(datum)) {
|
||||||
return this.formatter.parse(datum[this.valueMetadata.source]);
|
const objectDatum = datum[this.valueMetadata.source];
|
||||||
|
if (Array.isArray(objectDatum)) {
|
||||||
|
return objectDatum.map((item) => {
|
||||||
|
return this.formatter.parse(item);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return this.formatter.parse(objectDatum);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.formatter.parse(datum);
|
return this.formatter.parse(datum);
|
||||||
@ -116,7 +137,14 @@ define([
|
|||||||
|
|
||||||
TelemetryValueFormatter.prototype.format = function (datum) {
|
TelemetryValueFormatter.prototype.format = function (datum) {
|
||||||
if (_.isObject(datum)) {
|
if (_.isObject(datum)) {
|
||||||
return this.formatter.format(datum[this.valueMetadata.source]);
|
const objectDatum = datum[this.valueMetadata.source];
|
||||||
|
if (Array.isArray(objectDatum)) {
|
||||||
|
return objectDatum.map((item) => {
|
||||||
|
return this.formatter.format(item);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return this.formatter.format(objectDatum);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.formatter.format(datum);
|
return this.formatter.format(datum);
|
||||||
|
@ -40,14 +40,6 @@ export default {
|
|||||||
BarGraph
|
BarGraph
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'domainObject', 'path'],
|
inject: ['openmct', 'domainObject', 'path'],
|
||||||
props: {
|
|
||||||
options: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
this.telemetryObjects = {};
|
this.telemetryObjects = {};
|
||||||
this.telemetryObjectFormats = {};
|
this.telemetryObjectFormats = {};
|
||||||
@ -75,7 +67,9 @@ export default {
|
|||||||
this.setTimeContext();
|
this.setTimeContext();
|
||||||
|
|
||||||
this.loadComposition();
|
this.loadComposition();
|
||||||
|
this.unobserveAxes = this.openmct.objects.observe(this.domainObject, 'configuration.axes', this.refreshData);
|
||||||
|
this.unobserveInterpolation = this.openmct.objects.observe(this.domainObject, 'configuration.useInterpolation', this.refreshData);
|
||||||
|
this.unobserveBar = this.openmct.objects.observe(this.domainObject, 'configuration.useBar', this.refreshData);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.stopFollowingTimeContext();
|
this.stopFollowingTimeContext();
|
||||||
@ -86,8 +80,19 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.composition.off('add', this.addTelemetryObject);
|
this.composition.off('add', this.addToComposition);
|
||||||
this.composition.off('remove', this.removeTelemetryObject);
|
this.composition.off('remove', this.removeTelemetryObject);
|
||||||
|
if (this.unobserveAxes) {
|
||||||
|
this.unobserveAxes();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.unobserveInterpolation) {
|
||||||
|
this.unobserveInterpolation();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.unobserveBar) {
|
||||||
|
this.unobserveBar();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setTimeContext() {
|
setTimeContext() {
|
||||||
@ -105,6 +110,42 @@ export default {
|
|||||||
this.timeContext.off('bounds', this.refreshData);
|
this.timeContext.off('bounds', this.refreshData);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
addToComposition(telemetryObject) {
|
||||||
|
if (Object.values(this.telemetryObjects).length > 0) {
|
||||||
|
this.confirmRemoval(telemetryObject);
|
||||||
|
} else {
|
||||||
|
this.addTelemetryObject(telemetryObject);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmRemoval(telemetryObject) {
|
||||||
|
const dialog = this.openmct.overlays.dialog({
|
||||||
|
iconClass: 'alert',
|
||||||
|
message: 'This action will replace the current telemetry source. Do you want to continue?',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
label: 'Ok',
|
||||||
|
emphasis: true,
|
||||||
|
callback: () => {
|
||||||
|
const oldTelemetryObject = Object.values(this.telemetryObjects)[0];
|
||||||
|
this.removeFromComposition(oldTelemetryObject);
|
||||||
|
this.removeTelemetryObject(oldTelemetryObject.identifier);
|
||||||
|
this.addTelemetryObject(telemetryObject);
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Cancel',
|
||||||
|
callback: () => {
|
||||||
|
this.removeFromComposition(telemetryObject);
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removeFromComposition(telemetryObject) {
|
||||||
|
this.composition.remove(telemetryObject);
|
||||||
|
},
|
||||||
addTelemetryObject(telemetryObject) {
|
addTelemetryObject(telemetryObject) {
|
||||||
// grab information we need from the added telmetry object
|
// grab information we need from the added telmetry object
|
||||||
const key = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
const key = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||||
@ -165,7 +206,12 @@ export default {
|
|||||||
|
|
||||||
const yAxisMetadata = metadata.valuesForHints(['range'])[0];
|
const yAxisMetadata = metadata.valuesForHints(['range'])[0];
|
||||||
//Exclude 'name' and 'time' based metadata specifically, from the x-Axis values by using range hints only
|
//Exclude 'name' and 'time' based metadata specifically, from the x-Axis values by using range hints only
|
||||||
const xAxisMetadata = metadata.valuesForHints(['range']);
|
const xAxisMetadata = metadata.valuesForHints(['range'])
|
||||||
|
.map((metaDatum) => {
|
||||||
|
metaDatum.isArrayValue = metadata.isArrayValue(metaDatum);
|
||||||
|
|
||||||
|
return metaDatum;
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
xAxisMetadata,
|
xAxisMetadata,
|
||||||
@ -183,13 +229,7 @@ export default {
|
|||||||
loadComposition() {
|
loadComposition() {
|
||||||
this.composition = this.openmct.composition.get(this.domainObject);
|
this.composition = this.openmct.composition.get(this.domainObject);
|
||||||
|
|
||||||
if (!this.composition) {
|
this.composition.on('add', this.addToComposition);
|
||||||
this.addTelemetryObject(this.domainObject);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.composition.on('add', this.addTelemetryObject);
|
|
||||||
this.composition.on('remove', this.removeTelemetryObject);
|
this.composition.on('remove', this.removeTelemetryObject);
|
||||||
this.composition.load();
|
this.composition.load();
|
||||||
},
|
},
|
||||||
@ -212,7 +252,10 @@ export default {
|
|||||||
},
|
},
|
||||||
removeTelemetryObject(identifier) {
|
removeTelemetryObject(identifier) {
|
||||||
const key = this.openmct.objects.makeKeyString(identifier);
|
const key = this.openmct.objects.makeKeyString(identifier);
|
||||||
delete this.telemetryObjects[key];
|
if (this.telemetryObjects[key]) {
|
||||||
|
delete this.telemetryObjects[key];
|
||||||
|
}
|
||||||
|
|
||||||
if (this.telemetryObjectFormats && this.telemetryObjectFormats[key]) {
|
if (this.telemetryObjectFormats && this.telemetryObjectFormats[key]) {
|
||||||
delete this.telemetryObjectFormats[key];
|
delete this.telemetryObjectFormats[key];
|
||||||
}
|
}
|
||||||
@ -237,49 +280,72 @@ export default {
|
|||||||
this.openmct.notifications.alert(data.message);
|
this.openmct.notifications.alert(data.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isDataInTimeRange(data, key)) {
|
if (!this.isDataInTimeRange(data, key, telemetryObject)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.domainObject.configuration.axes.xKey === undefined || this.domainObject.configuration.axes.yKey === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let xValues = [];
|
let xValues = [];
|
||||||
let yValues = [];
|
let yValues = [];
|
||||||
|
let xAxisMetadata = axisMetadata.xAxisMetadata.find(metadata => metadata.key === this.domainObject.configuration.axes.xKey);
|
||||||
//populate X and Y values for plotly
|
if (xAxisMetadata && xAxisMetadata.isArrayValue) {
|
||||||
axisMetadata.xAxisMetadata.forEach((metadata) => {
|
//populate x and y values
|
||||||
xValues.push(metadata.name);
|
let metadataKey = this.domainObject.configuration.axes.xKey;
|
||||||
if (data[metadata.key]) {
|
if (data[metadataKey] !== undefined) {
|
||||||
const formattedValue = this.format(key, metadata.key, data);
|
xValues = this.parse(key, metadataKey, data);
|
||||||
yValues.push(formattedValue);
|
|
||||||
} else {
|
|
||||||
yValues.push(null);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
metadataKey = this.domainObject.configuration.axes.yKey;
|
||||||
|
if (data[metadataKey] !== undefined) {
|
||||||
|
yValues = this.parse(key, metadataKey, data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//populate X and Y values for plotly
|
||||||
|
axisMetadata.xAxisMetadata.filter(metadataObj => !metadataObj.isArrayValue).forEach((metadata) => {
|
||||||
|
if (!xAxisMetadata) {
|
||||||
|
//Assign the first metadata to use for any formatting
|
||||||
|
xAxisMetadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
xValues.push(metadata.name);
|
||||||
|
if (data[metadata.key]) {
|
||||||
|
const parsedValue = this.parse(key, metadata.key, data);
|
||||||
|
yValues.push(parsedValue);
|
||||||
|
} else {
|
||||||
|
yValues.push(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let trace = {
|
let trace = {
|
||||||
key,
|
key,
|
||||||
name: telemetryObject.name,
|
name: telemetryObject.name,
|
||||||
x: xValues,
|
x: xValues,
|
||||||
y: yValues,
|
y: yValues,
|
||||||
text: yValues.map(String),
|
xAxisMetadata: xAxisMetadata,
|
||||||
xAxisMetadata: axisMetadata.xAxisMetadata,
|
|
||||||
yAxisMetadata: axisMetadata.yAxisMetadata,
|
yAxisMetadata: axisMetadata.yAxisMetadata,
|
||||||
type: this.options.type ? this.options.type : 'bar',
|
type: this.domainObject.configuration.useBar ? 'bar' : 'scatter',
|
||||||
|
mode: 'lines',
|
||||||
|
line: {
|
||||||
|
shape: this.domainObject.configuration.useInterpolation
|
||||||
|
},
|
||||||
marker: {
|
marker: {
|
||||||
color: this.domainObject.configuration.barStyles.series[key].color
|
color: this.domainObject.configuration.barStyles.series[key].color
|
||||||
},
|
},
|
||||||
hoverinfo: 'skip'
|
hoverinfo: this.domainObject.configuration.useBar ? 'skip' : 'x+y'
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.options.type) {
|
|
||||||
trace.mode = 'markers';
|
|
||||||
trace.hoverinfo = 'x+y';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addTrace(trace, key);
|
this.addTrace(trace, key);
|
||||||
},
|
},
|
||||||
isDataInTimeRange(datum, key) {
|
isDataInTimeRange(datum, key, telemetryObject) {
|
||||||
const timeSystemKey = this.timeContext.timeSystem().key;
|
const timeSystemKey = this.timeContext.timeSystem().key;
|
||||||
let currentTimestamp = this.parse(key, timeSystemKey, datum);
|
const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
||||||
|
let metadataValue = metadata.value(timeSystemKey) || { key: timeSystemKey };
|
||||||
|
|
||||||
|
let currentTimestamp = this.parse(key, metadataValue.key, datum);
|
||||||
|
|
||||||
return currentTimestamp && this.timeContext.bounds().end >= currentTimestamp;
|
return currentTimestamp && this.timeContext.bounds().end >= currentTimestamp;
|
||||||
},
|
},
|
||||||
@ -299,7 +365,8 @@ export default {
|
|||||||
},
|
},
|
||||||
requestDataFor(telemetryObject) {
|
requestDataFor(telemetryObject) {
|
||||||
const axisMetadata = this.getAxisMetadata(telemetryObject);
|
const axisMetadata = this.getAxisMetadata(telemetryObject);
|
||||||
this.openmct.telemetry.request(telemetryObject)
|
const options = this.getOptions();
|
||||||
|
this.openmct.telemetry.request(telemetryObject, options)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
data.forEach((datum) => {
|
data.forEach((datum) => {
|
||||||
this.addDataToGraph(telemetryObject, datum, axisMetadata);
|
this.addDataToGraph(telemetryObject, datum, axisMetadata);
|
||||||
|
@ -20,18 +20,155 @@
|
|||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<ul class="c-tree c-bar-graph-options">
|
<div class="c-bar-graph-options js-bar-plot-option">
|
||||||
<h2 title="Display properties for this object">Bar Graph Series</h2>
|
<ul class="c-tree">
|
||||||
<li
|
<h2 title="Display properties for this object">Bar Graph Series</h2>
|
||||||
v-for="series in domainObject.composition"
|
<li>
|
||||||
:key="series.key"
|
<series-options
|
||||||
>
|
v-for="series in plotSeries"
|
||||||
<series-options
|
:key="series.key"
|
||||||
:item="series"
|
:item="series"
|
||||||
:color-palette="colorPalette"
|
:color-palette="colorPalette"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<div class="grid-properties">
|
||||||
|
<ul class="l-inspector-part">
|
||||||
|
<h2 title="Y axis settings for this object">Axes</h2>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div
|
||||||
|
class="grid-cell label"
|
||||||
|
title="X axis selection."
|
||||||
|
>X Axis</div>
|
||||||
|
<div
|
||||||
|
v-if="isEditing"
|
||||||
|
class="grid-cell value"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
v-model="xKey"
|
||||||
|
@change="updateForm('xKey')"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="option in xKeyOptions"
|
||||||
|
:key="`xKey-${option.value}`"
|
||||||
|
:value="option.value"
|
||||||
|
:selected="option.value === xKey"
|
||||||
|
>
|
||||||
|
{{ option.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="grid-cell value"
|
||||||
|
>{{ xKeyLabel }}</div>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="yKey !== ''"
|
||||||
|
class="grid-row"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="grid-cell label"
|
||||||
|
title="Y axis selection."
|
||||||
|
>Y Axis</div>
|
||||||
|
<div
|
||||||
|
v-if="isEditing"
|
||||||
|
class="grid-cell value"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
v-model="yKey"
|
||||||
|
@change="updateForm('yKey')"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="option in yKeyOptions"
|
||||||
|
:key="`yKey-${option.value}`"
|
||||||
|
:value="option.value"
|
||||||
|
:selected="option.value === yKey"
|
||||||
|
>
|
||||||
|
{{ option.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="grid-cell value"
|
||||||
|
>{{ yKeyLabel }}</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="grid-properties">
|
||||||
|
<ul class="l-inspector-part">
|
||||||
|
<h2 title="Settings for plot">Settings</h2>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div
|
||||||
|
v-if="isEditing"
|
||||||
|
class="grid-cell label"
|
||||||
|
title="Display style for the plot"
|
||||||
|
>Display Style</div>
|
||||||
|
<div
|
||||||
|
v-if="isEditing"
|
||||||
|
class="grid-cell value"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
v-model="useBar"
|
||||||
|
@change="updateBar"
|
||||||
|
>
|
||||||
|
<option :value="true">Bar</option>
|
||||||
|
<option :value="false">Line</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="!isEditing"
|
||||||
|
class="grid-cell label"
|
||||||
|
title="Display style for plot"
|
||||||
|
>Display Style</div>
|
||||||
|
<div
|
||||||
|
v-if="!isEditing"
|
||||||
|
class="grid-cell value"
|
||||||
|
>{{ {
|
||||||
|
'true': 'Bar',
|
||||||
|
'false': 'Line'
|
||||||
|
}[useBar] }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="!useBar"
|
||||||
|
class="grid-row"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="isEditing"
|
||||||
|
class="grid-cell label"
|
||||||
|
title="The rendering method to join lines for this series."
|
||||||
|
>Line Method</div>
|
||||||
|
<div
|
||||||
|
v-if="isEditing"
|
||||||
|
class="grid-cell value"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
v-model="useInterpolation"
|
||||||
|
@change="updateInterpolation"
|
||||||
|
>
|
||||||
|
<option value="linear">Linear interpolate</option>
|
||||||
|
<option value="hv">Step after</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="!isEditing"
|
||||||
|
class="grid-cell label"
|
||||||
|
title="The rendering method to join lines for this series."
|
||||||
|
>Line Method</div>
|
||||||
|
<div
|
||||||
|
v-if="!isEditing"
|
||||||
|
class="grid-cell value"
|
||||||
|
>{{ {
|
||||||
|
'linear': 'Linear interpolation',
|
||||||
|
'hv': 'Step After'
|
||||||
|
}[useInterpolation] }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -45,8 +182,17 @@ export default {
|
|||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'domainObject'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
xKey: this.domainObject.configuration.axes.xKey,
|
||||||
|
yKey: this.domainObject.configuration.axes.yKey,
|
||||||
|
xKeyLabel: '',
|
||||||
|
yKeyLabel: '',
|
||||||
|
plotSeries: [],
|
||||||
|
yKeyOptions: [],
|
||||||
|
xKeyOptions: [],
|
||||||
isEditing: this.openmct.editor.isEditing(),
|
isEditing: this.openmct.editor.isEditing(),
|
||||||
colorPalette: this.colorPalette
|
colorPalette: this.colorPalette,
|
||||||
|
useInterpolation: this.domainObject.configuration.useInterpolation,
|
||||||
|
useBar: this.domainObject.configuration.useBar
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -59,13 +205,187 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.openmct.editor.on('isEditing', this.setEditState);
|
this.openmct.editor.on('isEditing', this.setEditState);
|
||||||
|
this.composition = this.openmct.composition.get(this.domainObject);
|
||||||
|
this.registerListeners();
|
||||||
|
this.composition.load();
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.openmct.editor.off('isEditing', this.setEditState);
|
this.openmct.editor.off('isEditing', this.setEditState);
|
||||||
|
this.stopListening();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setEditState(isEditing) {
|
setEditState(isEditing) {
|
||||||
this.isEditing = isEditing;
|
this.isEditing = isEditing;
|
||||||
|
},
|
||||||
|
registerListeners() {
|
||||||
|
this.composition.on('add', this.addSeries);
|
||||||
|
this.composition.on('remove', this.removeSeries);
|
||||||
|
this.unobserve = this.openmct.objects.observe(this.domainObject, 'configuration.axes', this.setKeysAndSetupOptions);
|
||||||
|
},
|
||||||
|
stopListening() {
|
||||||
|
this.composition.off('add', this.addSeries);
|
||||||
|
this.composition.off('remove', this.removeSeries);
|
||||||
|
if (this.unobserve) {
|
||||||
|
this.unobserve();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addSeries(series, index) {
|
||||||
|
this.$set(this.plotSeries, this.plotSeries.length, series);
|
||||||
|
this.setupOptions();
|
||||||
|
},
|
||||||
|
removeSeries(seriesIdentifier) {
|
||||||
|
const index = this.plotSeries.findIndex(plotSeries => this.openmct.objects.areIdsEqual(seriesIdentifier, plotSeries.identifier));
|
||||||
|
if (index >= 0) {
|
||||||
|
this.$delete(this.plotSeries, index);
|
||||||
|
this.setupOptions();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setKeysAndSetupOptions() {
|
||||||
|
this.xKey = this.domainObject.configuration.axes.xKey;
|
||||||
|
this.yKey = this.domainObject.configuration.axes.yKey;
|
||||||
|
this.setupOptions();
|
||||||
|
},
|
||||||
|
setupOptions() {
|
||||||
|
this.xKeyOptions = [];
|
||||||
|
this.yKeyOptions = [];
|
||||||
|
if (this.plotSeries.length <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let update = false;
|
||||||
|
const series = this.plotSeries[0];
|
||||||
|
const metadata = this.openmct.telemetry.getMetadata(series);
|
||||||
|
const metadataRangeValues = metadata.valuesForHints(['range']).map((metaDatum) => {
|
||||||
|
metaDatum.isArrayValue = metadata.isArrayValue(metaDatum);
|
||||||
|
|
||||||
|
return metaDatum;
|
||||||
|
});
|
||||||
|
const metadataArrayValues = metadataRangeValues.filter(metadataObj => metadataObj.isArrayValue);
|
||||||
|
const metadataValues = metadataRangeValues.filter(metadataObj => !metadataObj.isArrayValue);
|
||||||
|
metadataArrayValues.forEach((metadataValue) => {
|
||||||
|
this.xKeyOptions.push({
|
||||||
|
name: metadataValue.name || metadataValue.key,
|
||||||
|
value: metadataValue.key,
|
||||||
|
isArrayValue: metadataValue.isArrayValue
|
||||||
|
});
|
||||||
|
this.yKeyOptions.push({
|
||||||
|
name: metadataValue.name || metadataValue.key,
|
||||||
|
value: metadataValue.key,
|
||||||
|
isArrayValue: metadataValue.isArrayValue
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//Metadata values that are not array values will be grouped together as x-axis only option.
|
||||||
|
// Here, the y-axis is not relevant.
|
||||||
|
if (metadataValues.length) {
|
||||||
|
this.xKeyOptions.push(
|
||||||
|
metadataValues.reduce((previousValue, currentValue) => {
|
||||||
|
return {
|
||||||
|
name: `${previousValue.name}, ${currentValue.name}`,
|
||||||
|
value: currentValue.key,
|
||||||
|
isArrayValue: currentValue.isArrayValue
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let xKeyOptionIndex;
|
||||||
|
let yKeyOptionIndex;
|
||||||
|
|
||||||
|
if (this.domainObject.configuration.axes.xKey) {
|
||||||
|
xKeyOptionIndex = this.xKeyOptions.findIndex(option => option.value === this.domainObject.configuration.axes.xKey);
|
||||||
|
if (xKeyOptionIndex > -1) {
|
||||||
|
this.xKey = this.xKeyOptions[xKeyOptionIndex].value;
|
||||||
|
this.xKeyLabel = this.xKeyOptions[xKeyOptionIndex].name;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.xKey === undefined) {
|
||||||
|
update = true;
|
||||||
|
xKeyOptionIndex = 0;
|
||||||
|
this.xKey = this.xKeyOptions[xKeyOptionIndex].value;
|
||||||
|
this.xKeyLabel = this.xKeyOptions[xKeyOptionIndex].name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadataRangeValues.length > 1) {
|
||||||
|
if (this.domainObject.configuration.axes.yKey && this.domainObject.configuration.axes.yKey !== 'none') {
|
||||||
|
yKeyOptionIndex = this.yKeyOptions.findIndex(option => option.value === this.domainObject.configuration.axes.yKey);
|
||||||
|
if (yKeyOptionIndex > -1 && yKeyOptionIndex !== xKeyOptionIndex) {
|
||||||
|
this.yKey = this.yKeyOptions[yKeyOptionIndex].value;
|
||||||
|
this.yKeyLabel = this.yKeyOptions[yKeyOptionIndex].name;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.yKey === undefined) {
|
||||||
|
yKeyOptionIndex = this.yKeyOptions.findIndex((option, index) => index !== xKeyOptionIndex);
|
||||||
|
if (yKeyOptionIndex > -1) {
|
||||||
|
update = true;
|
||||||
|
this.yKey = this.yKeyOptions[yKeyOptionIndex].value;
|
||||||
|
this.yKeyLabel = this.yKeyOptions[yKeyOptionIndex].name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.yKeyOptions = this.yKeyOptions.map((option, index) => {
|
||||||
|
if (index === xKeyOptionIndex) {
|
||||||
|
option.name = `${option.name} (swap)`;
|
||||||
|
option.swap = yKeyOptionIndex;
|
||||||
|
} else {
|
||||||
|
option.name = option.name.replace(' (swap)', '');
|
||||||
|
option.swap = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return option;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.xKeyOptions = this.xKeyOptions.map((option, index) => {
|
||||||
|
if (index === yKeyOptionIndex) {
|
||||||
|
option.name = `${option.name} (swap)`;
|
||||||
|
option.swap = xKeyOptionIndex;
|
||||||
|
} else {
|
||||||
|
option.name = option.name.replace(' (swap)', '');
|
||||||
|
option.swap = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return option;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (update === true) {
|
||||||
|
this.saveConfiguration();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateForm(property) {
|
||||||
|
if (property === 'xKey') {
|
||||||
|
const xKeyOption = this.xKeyOptions.find(option => option.value === this.xKey);
|
||||||
|
if (xKeyOption.swap !== undefined) {
|
||||||
|
//swap
|
||||||
|
this.yKey = this.xKeyOptions[xKeyOption.swap].value;
|
||||||
|
} else if (!xKeyOption.isArrayValue) {
|
||||||
|
this.yKey = 'none';
|
||||||
|
} else {
|
||||||
|
this.yKey = undefined;
|
||||||
|
}
|
||||||
|
} else if (property === 'yKey') {
|
||||||
|
const yKeyOption = this.yKeyOptions.find(option => option.value === this.yKey);
|
||||||
|
if (yKeyOption.swap !== undefined) {
|
||||||
|
//swap
|
||||||
|
this.xKey = this.yKeyOptions[yKeyOption.swap].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveConfiguration();
|
||||||
|
},
|
||||||
|
saveConfiguration() {
|
||||||
|
this.openmct.objects.mutate(this.domainObject, `configuration.axes`, {
|
||||||
|
xKey: this.xKey,
|
||||||
|
yKey: this.yKey
|
||||||
|
});
|
||||||
|
},
|
||||||
|
updateInterpolation(event) {
|
||||||
|
this.openmct.objects.mutate(this.domainObject, `configuration.useInterpolation`, this.useInterpolation);
|
||||||
|
},
|
||||||
|
updateBar(event) {
|
||||||
|
this.openmct.objects.mutate(this.domainObject, `configuration.useBar`, this.useBar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -38,16 +38,19 @@
|
|||||||
<div class="c-object-label__name">{{ name }}</div>
|
<div class="c-object-label__name">{{ name }}</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<ColorSwatch
|
<ul class="grid-properties">
|
||||||
v-if="expanded"
|
<li class="grid-row">
|
||||||
:current-color="currentColor"
|
<ColorSwatch
|
||||||
title="Manually set the color for this bar graph series."
|
v-if="expanded"
|
||||||
edit-title="Manually set the color for this bar graph series"
|
:current-color="currentColor"
|
||||||
view-title="The color for this bar graph series."
|
title="Manually set the color for this bar graph series."
|
||||||
short-label="Color"
|
edit-title="Manually set the color for this bar graph series."
|
||||||
class="grid-properties"
|
view-title="The color for this bar graph series."
|
||||||
@colorSet="setColor"
|
short-label="Color"
|
||||||
/>
|
@colorSet="setColor"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -109,7 +112,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.key = this.openmct.objects.makeKeyString(this.item);
|
|
||||||
this.initColorAndName();
|
this.initColorAndName();
|
||||||
this.removeBarStylesListener = this.openmct.objects.observe(this.domainObject, `configuration.barStyles.series["${this.key}"]`, this.initColorAndName);
|
this.removeBarStylesListener = this.openmct.objects.observe(this.domainObject, `configuration.barStyles.series["${this.key}"]`, this.initColorAndName);
|
||||||
},
|
},
|
||||||
@ -120,6 +122,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initColorAndName() {
|
initColorAndName() {
|
||||||
|
this.key = this.openmct.objects.makeKeyString(this.item.identifier);
|
||||||
// this is called before the plot is initialized
|
// this is called before the plot is initialized
|
||||||
if (!this.domainObject.configuration.barStyles.series[this.key]) {
|
if (!this.domainObject.configuration.barStyles.series[this.key]) {
|
||||||
const color = this.colorPalette.getNextColor().asHexString();
|
const color = this.colorPalette.getNextColor().asHexString();
|
||||||
|
@ -28,14 +28,17 @@ export default function () {
|
|||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
openmct.types.addType(BAR_GRAPH_KEY, {
|
openmct.types.addType(BAR_GRAPH_KEY, {
|
||||||
key: BAR_GRAPH_KEY,
|
key: BAR_GRAPH_KEY,
|
||||||
name: "Bar Graph",
|
name: "Graph (Bar or Line)",
|
||||||
cssClass: "icon-bar-chart",
|
cssClass: "icon-bar-chart",
|
||||||
description: "View data as a bar graph. Can be added to Display Layouts.",
|
description: "View data as a bar graph. Can be added to Display Layouts.",
|
||||||
creatable: true,
|
creatable: true,
|
||||||
initialize: function (domainObject) {
|
initialize: function (domainObject) {
|
||||||
domainObject.composition = [];
|
domainObject.composition = [];
|
||||||
domainObject.configuration = {
|
domainObject.configuration = {
|
||||||
barStyles: { series: {} }
|
barStyles: { series: {} },
|
||||||
|
axes: {},
|
||||||
|
useInterpolation: 'linear',
|
||||||
|
useBar: true
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
priority: 891
|
priority: 891
|
||||||
|
@ -57,18 +57,18 @@ describe("the plugin", function () {
|
|||||||
const testTelemetry = [
|
const testTelemetry = [
|
||||||
{
|
{
|
||||||
'utc': 1,
|
'utc': 1,
|
||||||
'some-key': 'some-value 1',
|
'some-key': ['1.3222'],
|
||||||
'some-other-key': 'some-other-value 1'
|
'some-other-key': [1]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'utc': 2,
|
'utc': 2,
|
||||||
'some-key': 'some-value 2',
|
'some-key': ['2.555'],
|
||||||
'some-other-key': 'some-other-value 2'
|
'some-other-key': [2]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'utc': 3,
|
'utc': 3,
|
||||||
'some-key': 'some-value 3',
|
'some-key': ['3.888'],
|
||||||
'some-other-key': 'some-other-value 3'
|
'some-other-key': [3]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -123,7 +123,6 @@ describe("the plugin", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("The bar graph view", () => {
|
describe("The bar graph view", () => {
|
||||||
let testDomainObject;
|
|
||||||
let barGraphObject;
|
let barGraphObject;
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
let component;
|
let component;
|
||||||
@ -135,51 +134,21 @@ describe("the plugin", function () {
|
|||||||
namespace: "",
|
namespace: "",
|
||||||
key: "test-plot"
|
key: "test-plot"
|
||||||
},
|
},
|
||||||
|
configuration: {
|
||||||
|
barStyles: {
|
||||||
|
series: {}
|
||||||
|
},
|
||||||
|
axes: {},
|
||||||
|
useInterpolation: 'linear',
|
||||||
|
useBar: true
|
||||||
|
},
|
||||||
type: "telemetry.plot.bar-graph",
|
type: "telemetry.plot.bar-graph",
|
||||||
name: "Test Bar Graph"
|
name: "Test Bar Graph"
|
||||||
};
|
};
|
||||||
|
|
||||||
testDomainObject = {
|
|
||||||
identifier: {
|
|
||||||
namespace: "",
|
|
||||||
key: "test-object"
|
|
||||||
},
|
|
||||||
configuration: {
|
|
||||||
barStyles: {
|
|
||||||
series: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
type: "test-object",
|
|
||||||
name: "Test Object",
|
|
||||||
telemetry: {
|
|
||||||
values: [{
|
|
||||||
key: "utc",
|
|
||||||
format: "utc",
|
|
||||||
name: "Time",
|
|
||||||
hints: {
|
|
||||||
domain: 1
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
key: "some-key",
|
|
||||||
name: "Some attribute",
|
|
||||||
hints: {
|
|
||||||
range: 1
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
key: "some-other-key",
|
|
||||||
name: "Another attribute",
|
|
||||||
hints: {
|
|
||||||
range: 2
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
mockComposition = new EventEmitter();
|
mockComposition = new EventEmitter();
|
||||||
mockComposition.load = () => {
|
mockComposition.load = () => {
|
||||||
mockComposition.emit('add', testDomainObject);
|
return [];
|
||||||
|
|
||||||
return [testDomainObject];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
|
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
|
||||||
@ -247,15 +216,116 @@ describe("the plugin", function () {
|
|||||||
|
|
||||||
const applicableViews = openmct.objectViews.get(barGraphObject, mockObjectPath);
|
const applicableViews = openmct.objectViews.get(barGraphObject, mockObjectPath);
|
||||||
const plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === BAR_GRAPH_VIEW);
|
const plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === BAR_GRAPH_VIEW);
|
||||||
const barGraphView = plotViewProvider.view(testDomainObject, [testDomainObject]);
|
const barGraphView = plotViewProvider.view(barGraphObject, [barGraphObject]);
|
||||||
barGraphView.show(child, true);
|
barGraphView.show(child, true);
|
||||||
expect(testDomainObject.configuration.barStyles.series["test-object"].name).toEqual("Test Object");
|
|
||||||
mockComposition.emit('add', dotFullTelemetryObject);
|
mockComposition.emit('add', dotFullTelemetryObject);
|
||||||
expect(testDomainObject.configuration.barStyles.series["someNamespace:~OpenMCT~outer.test-object.foo.bar"].name).toEqual("A Dotful Object");
|
expect(barGraphObject.configuration.barStyles.series["someNamespace:~OpenMCT~outer.test-object.foo.bar"].name).toEqual("A Dotful Object");
|
||||||
barGraphView.destroy();
|
barGraphView.destroy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("The spectral plot view for telemetry objects with array values", () => {
|
||||||
|
let barGraphObject;
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
let component;
|
||||||
|
let mockComposition;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
barGraphObject = {
|
||||||
|
identifier: {
|
||||||
|
namespace: "",
|
||||||
|
key: "test-plot"
|
||||||
|
},
|
||||||
|
configuration: {
|
||||||
|
barStyles: {
|
||||||
|
series: {}
|
||||||
|
},
|
||||||
|
axes: {
|
||||||
|
xKey: 'some-key',
|
||||||
|
yKey: 'some-other-key'
|
||||||
|
},
|
||||||
|
useInterpolation: 'linear',
|
||||||
|
useBar: false
|
||||||
|
},
|
||||||
|
type: "telemetry.plot.bar-graph",
|
||||||
|
name: "Test Bar Graph"
|
||||||
|
};
|
||||||
|
|
||||||
|
mockComposition = new EventEmitter();
|
||||||
|
mockComposition.load = () => {
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
|
||||||
|
|
||||||
|
let viewContainer = document.createElement("div");
|
||||||
|
child.append(viewContainer);
|
||||||
|
component = new Vue({
|
||||||
|
el: viewContainer,
|
||||||
|
components: {
|
||||||
|
BarGraph
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct: openmct,
|
||||||
|
domainObject: barGraphObject,
|
||||||
|
composition: openmct.composition.get(barGraphObject)
|
||||||
|
},
|
||||||
|
template: "<BarGraph></BarGraph>"
|
||||||
|
});
|
||||||
|
|
||||||
|
await Vue.nextTick();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Renders spectral plots", () => {
|
||||||
|
const dotFullTelemetryObject = {
|
||||||
|
identifier: {
|
||||||
|
namespace: "someNamespace",
|
||||||
|
key: "~OpenMCT~outer.test-object.foo.bar"
|
||||||
|
},
|
||||||
|
type: "test-dotful-object",
|
||||||
|
name: "A Dotful Object",
|
||||||
|
telemetry: {
|
||||||
|
values: [{
|
||||||
|
key: "utc",
|
||||||
|
format: "utc",
|
||||||
|
name: "Time",
|
||||||
|
hints: {
|
||||||
|
domain: 1
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: "some-key",
|
||||||
|
name: "Some attribute",
|
||||||
|
formatString: '%0.2f[]',
|
||||||
|
hints: {
|
||||||
|
range: 1
|
||||||
|
},
|
||||||
|
source: 'some-key'
|
||||||
|
}, {
|
||||||
|
key: "some-other-key",
|
||||||
|
name: "Another attribute",
|
||||||
|
format: "number[]",
|
||||||
|
hints: {
|
||||||
|
range: 2
|
||||||
|
},
|
||||||
|
source: 'some-other-key'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const applicableViews = openmct.objectViews.get(barGraphObject, mockObjectPath);
|
||||||
|
const plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === BAR_GRAPH_VIEW);
|
||||||
|
const barGraphView = plotViewProvider.view(barGraphObject, [barGraphObject]);
|
||||||
|
barGraphView.show(child, true);
|
||||||
|
mockComposition.emit('add', dotFullTelemetryObject);
|
||||||
|
|
||||||
|
return Vue.nextTick().then(() => {
|
||||||
|
const plotElement = element.querySelector('.cartesianlayer .scatterlayer .trace .lines');
|
||||||
|
expect(plotElement).not.toBeNull();
|
||||||
|
barGraphView.destroy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("the bar graph objects", () => {
|
describe("the bar graph objects", () => {
|
||||||
const mockObject = {
|
const mockObject = {
|
||||||
name: 'A very nice bar graph',
|
name: 'A very nice bar graph',
|
||||||
@ -412,7 +482,7 @@ describe("the plugin", function () {
|
|||||||
testDomainObject = {
|
testDomainObject = {
|
||||||
identifier: {
|
identifier: {
|
||||||
namespace: "",
|
namespace: "",
|
||||||
key: "test-object"
|
key: "~Some~foo.bar"
|
||||||
},
|
},
|
||||||
type: "test-object",
|
type: "test-object",
|
||||||
name: "Test Object",
|
name: "Test Object",
|
||||||
@ -460,11 +530,16 @@ describe("the plugin", function () {
|
|||||||
isAlias: true
|
isAlias: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
axes: {},
|
||||||
|
useInterpolation: 'linear',
|
||||||
|
useBar: true
|
||||||
},
|
},
|
||||||
composition: [
|
composition: [
|
||||||
{
|
{
|
||||||
key: '~Some~foo.bar'
|
identifier: {
|
||||||
|
key: '~Some~foo.bar'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<div class="grid-row">
|
<div class="grid-row grid-row--pad-label-for-button">
|
||||||
<template v-if="canEdit">
|
<template v-if="canEdit">
|
||||||
<div
|
<div
|
||||||
class="grid-cell label"
|
class="grid-cell label"
|
||||||
|
@ -173,12 +173,18 @@
|
|||||||
grid-column: 1 / 3;
|
grid-column: 1 / 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.is-editing & {
|
.is-editing {
|
||||||
.c-inspect-properties {
|
.c-inspect-properties {
|
||||||
&__value, &__label {
|
&__value, &__label {
|
||||||
line-height: 160%; // Prevent buttons/selects from overlapping when wrapping
|
line-height: 160%; // Prevent buttons/selects from overlapping when wrapping
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.grid-row--pad-label-for-button {
|
||||||
|
// Add extra space at the top of the label grid cell because there's a button to the right
|
||||||
|
[class*='label'] {
|
||||||
|
line-height: 1.8em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user