Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nasa/openmct.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJamie V <jamie.j.vigliotta@nasa.gov>2022-06-04 05:48:02 +0300
committerGitHub <noreply@github.com>2022-06-04 05:48:02 +0300
commit934073ed51cd590450a2fe759087084678e94ac7 (patch)
tree25ce2b4049c2ecf1f8c3fa8512469ba47b86be40
parentcec50aa0aeefdab314a9e48b2ea69da6f01f1d65 (diff)
parent162cc6bc77d5f68c15d99e32bac7cb1fb21b5da8 (diff)
Merge branch 'master' into fault-managementfault-management
-rw-r--r--example/generator/GeneratorMetadataProvider.js18
-rw-r--r--example/generator/generatorWorker.js30
-rw-r--r--src/api/telemetry/TelemetryMetadataManager.js12
-rw-r--r--src/api/telemetry/TelemetryValueFormatter.js40
-rw-r--r--src/plugins/charts/bar/BarGraphView.vue149
-rw-r--r--src/plugins/charts/bar/inspector/BarGraphOptions.vue346
-rw-r--r--src/plugins/charts/bar/inspector/SeriesOptions.vue25
-rw-r--r--src/plugins/charts/bar/plugin.js7
-rw-r--r--src/plugins/charts/bar/pluginSpec.js173
-rw-r--r--src/ui/color/ColorSwatch.vue2
-rw-r--r--src/ui/inspector/inspector.scss16
11 files changed, 681 insertions, 137 deletions
diff --git a/example/generator/GeneratorMetadataProvider.js b/example/generator/GeneratorMetadataProvider.js
index 7a8cd9832..f274d2d53 100644
--- a/example/generator/GeneratorMetadataProvider.js
+++ b/example/generator/GeneratorMetadataProvider.js
@@ -29,12 +29,12 @@ define([
}
},
{
- key: "cos",
- name: "Cosine",
- unit: "deg",
- formatString: '%0.2f',
+ key: "wavelengths",
+ name: "Wavelength",
+ unit: "nm",
+ format: 'string[]',
hints: {
- domain: 3
+ range: 4
}
},
// Need to enable "LocalTimeSystem" plugin to make use of this
@@ -64,6 +64,14 @@ define([
hints: {
range: 2
}
+ },
+ {
+ key: "intensities",
+ name: "Intensities",
+ format: 'number[]',
+ hints: {
+ range: 3
+ }
}
]
},
diff --git a/example/generator/generatorWorker.js b/example/generator/generatorWorker.js
index 6cf873063..02807e06f 100644
--- a/example/generator/generatorWorker.js
+++ b/example/generator/generatorWorker.js
@@ -77,7 +77,8 @@
utc: nextStep,
yesterday: nextStep - 60 * 60 * 24 * 1000,
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)
}
});
@@ -126,7 +127,8 @@
utc: nextStep,
yesterday: nextStep - 60 * 60 * 24 * 1000,
sin: sin(nextStep, period, amplitude, offset, phase, randomness),
- wavelength: wavelength(start, nextStep),
+ wavelengths: wavelengths(),
+ intensities: intensities(),
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;
}
- function wavelength(start, nextStep) {
- return (nextStep - start) / 10;
+ function wavelengths() {
+ 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) {
diff --git a/src/api/telemetry/TelemetryMetadataManager.js b/src/api/telemetry/TelemetryMetadataManager.js
index 0e21ad079..4d9761e3f 100644
--- a/src/api/telemetry/TelemetryMetadataManager.js
+++ b/src/api/telemetry/TelemetryMetadataManager.js
@@ -121,6 +121,18 @@ define([
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 () {
return this.valueMetadatas.filter(metadatum => metadatum.filters && metadatum.filters.length > 0);
};
diff --git a/src/api/telemetry/TelemetryValueFormatter.js b/src/api/telemetry/TelemetryValueFormatter.js
index eb97fd062..3e8c0b62c 100644
--- a/src/api/telemetry/TelemetryValueFormatter.js
+++ b/src/api/telemetry/TelemetryValueFormatter.js
@@ -43,9 +43,23 @@ define([
};
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.enumerations = valueMetadata.enumerations.reduce(function (vm, e) {
vm.byValue[e.value] = e.string;
@@ -77,13 +91,13 @@ define([
// Check for formatString support once instead of per format call.
if (valueMetadata.formatString) {
const baseFormat = this.formatter.format;
- const formatString = valueMetadata.formatString;
+ const formatString = getNonArrayValue(valueMetadata.formatString);
this.formatter.format = function (value) {
return printj.sprintf(formatString, baseFormat.call(this, value));
};
}
- if (valueMetadata.format === 'string') {
+ if (valueMetadataFormat === 'string') {
this.formatter.parse = function (value) {
if (value === undefined) {
return '';
@@ -108,7 +122,14 @@ define([
TelemetryValueFormatter.prototype.parse = function (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);
@@ -116,7 +137,14 @@ define([
TelemetryValueFormatter.prototype.format = function (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);
diff --git a/src/plugins/charts/bar/BarGraphView.vue b/src/plugins/charts/bar/BarGraphView.vue
index 02ec8f205..da3ef199c 100644
--- a/src/plugins/charts/bar/BarGraphView.vue
+++ b/src/plugins/charts/bar/BarGraphView.vue
@@ -40,14 +40,6 @@ export default {
BarGraph
},
inject: ['openmct', 'domainObject', 'path'],
- props: {
- options: {
- type: Object,
- default() {
- return {};
- }
- }
- },
data() {
this.telemetryObjects = {};
this.telemetryObjectFormats = {};
@@ -75,7 +67,9 @@ export default {
this.setTimeContext();
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() {
this.stopFollowingTimeContext();
@@ -86,8 +80,19 @@ export default {
return;
}
- this.composition.off('add', this.addTelemetryObject);
+ this.composition.off('add', this.addToComposition);
this.composition.off('remove', this.removeTelemetryObject);
+ if (this.unobserveAxes) {
+ this.unobserveAxes();
+ }
+
+ if (this.unobserveInterpolation) {
+ this.unobserveInterpolation();
+ }
+
+ if (this.unobserveBar) {
+ this.unobserveBar();
+ }
},
methods: {
setTimeContext() {
@@ -105,6 +110,42 @@ export default {
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) {
// grab information we need from the added telmetry object
const key = this.openmct.objects.makeKeyString(telemetryObject.identifier);
@@ -165,7 +206,12 @@ export default {
const yAxisMetadata = metadata.valuesForHints(['range'])[0];
//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 {
xAxisMetadata,
@@ -183,13 +229,7 @@ export default {
loadComposition() {
this.composition = this.openmct.composition.get(this.domainObject);
- if (!this.composition) {
- this.addTelemetryObject(this.domainObject);
-
- return;
- }
-
- this.composition.on('add', this.addTelemetryObject);
+ this.composition.on('add', this.addToComposition);
this.composition.on('remove', this.removeTelemetryObject);
this.composition.load();
},
@@ -212,7 +252,10 @@ export default {
},
removeTelemetryObject(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]) {
delete this.telemetryObjectFormats[key];
}
@@ -237,49 +280,72 @@ export default {
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;
}
let xValues = [];
let yValues = [];
+ let xAxisMetadata = axisMetadata.xAxisMetadata.find(metadata => metadata.key === this.domainObject.configuration.axes.xKey);
+ if (xAxisMetadata && xAxisMetadata.isArrayValue) {
+ //populate x and y values
+ let metadataKey = this.domainObject.configuration.axes.xKey;
+ if (data[metadataKey] !== undefined) {
+ xValues = this.parse(key, metadataKey, data);
+ }
- //populate X and Y values for plotly
- axisMetadata.xAxisMetadata.forEach((metadata) => {
- xValues.push(metadata.name);
- if (data[metadata.key]) {
- const formattedValue = this.format(key, metadata.key, 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 = {
key,
name: telemetryObject.name,
x: xValues,
y: yValues,
- text: yValues.map(String),
- xAxisMetadata: axisMetadata.xAxisMetadata,
+ xAxisMetadata: xAxisMetadata,
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: {
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);
},
- isDataInTimeRange(datum, key) {
+ isDataInTimeRange(datum, key, telemetryObject) {
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;
},
@@ -299,7 +365,8 @@ export default {
},
requestDataFor(telemetryObject) {
const axisMetadata = this.getAxisMetadata(telemetryObject);
- this.openmct.telemetry.request(telemetryObject)
+ const options = this.getOptions();
+ this.openmct.telemetry.request(telemetryObject, options)
.then(data => {
data.forEach((datum) => {
this.addDataToGraph(telemetryObject, datum, axisMetadata);
diff --git a/src/plugins/charts/bar/inspector/BarGraphOptions.vue b/src/plugins/charts/bar/inspector/BarGraphOptions.vue
index a17fbc28b..2a6ffcab8 100644
--- a/src/plugins/charts/bar/inspector/BarGraphOptions.vue
+++ b/src/plugins/charts/bar/inspector/BarGraphOptions.vue
@@ -20,18 +20,155 @@
at runtime from the About dialog for additional information.
-->
<template>
-<ul class="c-tree c-bar-graph-options">
- <h2 title="Display properties for this object">Bar Graph Series</h2>
- <li
- v-for="series in domainObject.composition"
- :key="series.key"
- >
- <series-options
- :item="series"
- :color-palette="colorPalette"
- />
- </li>
-</ul>
+<div class="c-bar-graph-options js-bar-plot-option">
+ <ul class="c-tree">
+ <h2 title="Display properties for this object">Bar Graph Series</h2>
+ <li>
+ <series-options
+ v-for="series in plotSeries"
+ :key="series.key"
+ :item="series"
+ :color-palette="colorPalette"
+ />
+ </li>
+ </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>
<script>
@@ -45,8 +182,17 @@ export default {
inject: ['openmct', 'domainObject'],
data() {
return {
+ xKey: this.domainObject.configuration.axes.xKey,
+ yKey: this.domainObject.configuration.axes.yKey,
+ xKeyLabel: '',
+ yKeyLabel: '',
+ plotSeries: [],
+ yKeyOptions: [],
+ xKeyOptions: [],
isEditing: this.openmct.editor.isEditing(),
- colorPalette: this.colorPalette
+ colorPalette: this.colorPalette,
+ useInterpolation: this.domainObject.configuration.useInterpolation,
+ useBar: this.domainObject.configuration.useBar
};
},
computed: {
@@ -59,13 +205,187 @@ export default {
},
mounted() {
this.openmct.editor.on('isEditing', this.setEditState);
+ this.composition = this.openmct.composition.get(this.domainObject);
+ this.registerListeners();
+ this.composition.load();
},
beforeDestroy() {
this.openmct.editor.off('isEditing', this.setEditState);
+ this.stopListening();
},
methods: {
setEditState(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);
}
}
};
diff --git a/src/plugins/charts/bar/inspector/SeriesOptions.vue b/src/plugins/charts/bar/inspector/SeriesOptions.vue
index 29c1f9cc3..8bb892446 100644
--- a/src/plugins/charts/bar/inspector/SeriesOptions.vue
+++ b/src/plugins/charts/bar/inspector/SeriesOptions.vue
@@ -38,16 +38,19 @@
<div class="c-object-label__name">{{ name }}</div>
</div>
</li>
- <ColorSwatch
- v-if="expanded"
- :current-color="currentColor"
- title="Manually set the color for this bar graph series."
- edit-title="Manually set the color for this bar graph series"
- view-title="The color for this bar graph series."
- short-label="Color"
- class="grid-properties"
- @colorSet="setColor"
- />
+ <ul class="grid-properties">
+ <li class="grid-row">
+ <ColorSwatch
+ v-if="expanded"
+ :current-color="currentColor"
+ title="Manually set the color for this bar graph series."
+ edit-title="Manually set the color for this bar graph series."
+ view-title="The color for this bar graph series."
+ short-label="Color"
+ @colorSet="setColor"
+ />
+ </li>
+ </ul>
</ul>
</template>
@@ -109,7 +112,6 @@ export default {
}
},
mounted() {
- this.key = this.openmct.objects.makeKeyString(this.item);
this.initColorAndName();
this.removeBarStylesListener = this.openmct.objects.observe(this.domainObject, `configuration.barStyles.series["${this.key}"]`, this.initColorAndName);
},
@@ -120,6 +122,7 @@ export default {
},
methods: {
initColorAndName() {
+ this.key = this.openmct.objects.makeKeyString(this.item.identifier);
// this is called before the plot is initialized
if (!this.domainObject.configuration.barStyles.series[this.key]) {
const color = this.colorPalette.getNextColor().asHexString();
diff --git a/src/plugins/charts/bar/plugin.js b/src/plugins/charts/bar/plugin.js
index 7a15d1cb6..d96f9e0d0 100644
--- a/src/plugins/charts/bar/plugin.js
+++ b/src/plugins/charts/bar/plugin.js
@@ -28,14 +28,17 @@ export default function () {
return function install(openmct) {
openmct.types.addType(BAR_GRAPH_KEY, {
key: BAR_GRAPH_KEY,
- name: "Bar Graph",
+ name: "Graph (Bar or Line)",
cssClass: "icon-bar-chart",
description: "View data as a bar graph. Can be added to Display Layouts.",
creatable: true,
initialize: function (domainObject) {
domainObject.composition = [];
domainObject.configuration = {
- barStyles: { series: {} }
+ barStyles: { series: {} },
+ axes: {},
+ useInterpolation: 'linear',
+ useBar: true
};
},
priority: 891
diff --git a/src/plugins/charts/bar/pluginSpec.js b/src/plugins/charts/bar/pluginSpec.js
index 56e3577e2..feea71b81 100644
--- a/src/plugins/charts/bar/pluginSpec.js
+++ b/src/plugins/charts/bar/pluginSpec.js
@@ -57,18 +57,18 @@ describe("the plugin", function () {
const testTelemetry = [
{
'utc': 1,
- 'some-key': 'some-value 1',
- 'some-other-key': 'some-other-value 1'
+ 'some-key': ['1.3222'],
+ 'some-other-key': [1]
},
{
'utc': 2,
- 'some-key': 'some-value 2',
- 'some-other-key': 'some-other-value 2'
+ 'some-key': ['2.555'],
+ 'some-other-key': [2]
},
{
'utc': 3,
- 'some-key': 'some-value 3',
- 'some-other-key': 'some-other-value 3'
+ 'some-key': ['3.888'],
+ 'some-other-key': [3]
}
];
@@ -123,7 +123,6 @@ describe("the plugin", function () {
});
describe("The bar graph view", () => {
- let testDomainObject;
let barGraphObject;
// eslint-disable-next-line no-unused-vars
let component;
@@ -135,22 +134,62 @@ describe("the plugin", function () {
namespace: "",
key: "test-plot"
},
+ configuration: {
+ barStyles: {
+ series: {}
+ },
+ axes: {},
+ useInterpolation: 'linear',
+ useBar: true
+ },
type: "telemetry.plot.bar-graph",
name: "Test Bar Graph"
};
- testDomainObject = {
- identifier: {
- namespace: "",
- key: "test-object"
+ 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
},
- configuration: {
- barStyles: {
- series: {}
- }
+ provide: {
+ openmct: openmct,
+ domainObject: barGraphObject,
+ composition: openmct.composition.get(barGraphObject)
},
- type: "test-object",
- name: "Test Object",
+ template: "<BarGraph></BarGraph>"
+ });
+
+ await Vue.nextTick();
+ });
+
+ it("provides a bar graph view", () => {
+ const applicableViews = openmct.objectViews.get(barGraphObject, mockObjectPath);
+ const plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === BAR_GRAPH_VIEW);
+ expect(plotViewProvider).toBeDefined();
+ });
+
+ it("Renders plotly bar graph", () => {
+ let barChartElement = element.querySelectorAll(".plotly");
+ expect(barChartElement.length).toBe(1);
+ });
+
+ it("Handles dots in telemetry id", () => {
+ const dotFullTelemetryObject = {
+ identifier: {
+ namespace: "someNamespace",
+ key: "~OpenMCT~outer.test-object.foo.bar"
+ },
+ type: "test-dotful-object",
+ name: "A Dotful Object",
telemetry: {
values: [{
key: "utc",
@@ -160,14 +199,14 @@ describe("the plugin", function () {
domain: 1
}
}, {
- key: "some-key",
- name: "Some attribute",
+ key: "some-key.foo.name.45",
+ name: "Some dotful attribute",
hints: {
range: 1
}
}, {
- key: "some-other-key",
- name: "Another attribute",
+ key: "some-other-key.bar.344.rad",
+ name: "Another dotful attribute",
hints: {
range: 2
}
@@ -175,11 +214,46 @@ describe("the plugin", function () {
}
};
+ 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);
+ expect(barGraphObject.configuration.barStyles.series["someNamespace:~OpenMCT~outer.test-object.foo.bar"].name).toEqual("A Dotful Object");
+ 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 = () => {
- mockComposition.emit('add', testDomainObject);
-
- return [testDomainObject];
+ return [];
};
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
@@ -202,18 +276,7 @@ describe("the plugin", function () {
await Vue.nextTick();
});
- it("provides a bar graph view", () => {
- const applicableViews = openmct.objectViews.get(barGraphObject, mockObjectPath);
- const plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === BAR_GRAPH_VIEW);
- expect(plotViewProvider).toBeDefined();
- });
-
- it("Renders plotly bar graph", () => {
- let barChartElement = element.querySelectorAll(".plotly");
- expect(barChartElement.length).toBe(1);
- });
-
- it("Handles dots in telemetry id", () => {
+ it("Renders spectral plots", () => {
const dotFullTelemetryObject = {
identifier: {
namespace: "someNamespace",
@@ -230,29 +293,36 @@ describe("the plugin", function () {
domain: 1
}
}, {
- key: "some-key.foo.name.45",
- name: "Some dotful attribute",
+ key: "some-key",
+ name: "Some attribute",
+ formatString: '%0.2f[]',
hints: {
range: 1
- }
+ },
+ source: 'some-key'
}, {
- key: "some-other-key.bar.344.rad",
- name: "Another dotful attribute",
+ 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(testDomainObject, [testDomainObject]);
+ const barGraphView = plotViewProvider.view(barGraphObject, [barGraphObject]);
barGraphView.show(child, true);
- expect(testDomainObject.configuration.barStyles.series["test-object"].name).toEqual("Test Object");
mockComposition.emit('add', dotFullTelemetryObject);
- expect(testDomainObject.configuration.barStyles.series["someNamespace:~OpenMCT~outer.test-object.foo.bar"].name).toEqual("A Dotful Object");
- barGraphView.destroy();
+
+ return Vue.nextTick().then(() => {
+ const plotElement = element.querySelector('.cartesianlayer .scatterlayer .trace .lines');
+ expect(plotElement).not.toBeNull();
+ barGraphView.destroy();
+ });
});
});
@@ -412,7 +482,7 @@ describe("the plugin", function () {
testDomainObject = {
identifier: {
namespace: "",
- key: "test-object"
+ key: "~Some~foo.bar"
},
type: "test-object",
name: "Test Object",
@@ -460,11 +530,16 @@ describe("the plugin", function () {
isAlias: true
}
}
- }
+ },
+ axes: {},
+ useInterpolation: 'linear',
+ useBar: true
},
composition: [
{
- key: '~Some~foo.bar'
+ identifier: {
+ key: '~Some~foo.bar'
+ }
}
]
}
diff --git a/src/ui/color/ColorSwatch.vue b/src/ui/color/ColorSwatch.vue
index 464851b49..e6de34c6c 100644
--- a/src/ui/color/ColorSwatch.vue
+++ b/src/ui/color/ColorSwatch.vue
@@ -20,7 +20,7 @@
at runtime from the About dialog for additional information.
-->
<template>
-<div class="grid-row">
+<div class="grid-row grid-row--pad-label-for-button">
<template v-if="canEdit">
<div
class="grid-cell label"
diff --git a/src/ui/inspector/inspector.scss b/src/ui/inspector/inspector.scss
index 26d1410bf..3aafd98ba 100644
--- a/src/ui/inspector/inspector.scss
+++ b/src/ui/inspector/inspector.scss
@@ -173,12 +173,18 @@
grid-column: 1 / 3;
}
}
+}
- .is-editing & {
- .c-inspect-properties {
- &__value, &__label {
- line-height: 160%; // Prevent buttons/selects from overlapping when wrapping
- }
+.is-editing {
+ .c-inspect-properties {
+ &__value, &__label {
+ 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;
}
}
}