Compare commits
18 Commits
master
...
mct4102_v2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ac9eebab3 | ||
|
|
04ece72679 | ||
|
|
80b4ccd562 | ||
|
|
880c3a8850 | ||
|
|
72b609f527 | ||
|
|
7c883aea96 | ||
|
|
a7ed38b7ee | ||
|
|
1049ac38ff | ||
|
|
2f435facd4 | ||
|
|
121eeac27c | ||
|
|
b67b13912e | ||
|
|
0cb884563a | ||
|
|
4942e27a89 | ||
|
|
e84a3bfe97 | ||
|
|
fdc1499339 | ||
|
|
ae224ae567 | ||
|
|
48322d46fd | ||
|
|
7616610f2c |
@@ -28,6 +28,15 @@ define([
|
||||
domain: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "cos",
|
||||
name: "Cosine",
|
||||
unit: "deg",
|
||||
formatString: '%0.2f',
|
||||
hints: {
|
||||
domain: 3
|
||||
}
|
||||
},
|
||||
// Need to enable "LocalTimeSystem" plugin to make use of this
|
||||
// {
|
||||
// key: "local",
|
||||
@@ -109,6 +118,100 @@ define([
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
'example.spectral-generator': {
|
||||
values: [
|
||||
{
|
||||
key: "name",
|
||||
name: "Name",
|
||||
format: "string"
|
||||
},
|
||||
{
|
||||
key: "utc",
|
||||
name: "Time",
|
||||
format: "utc",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "wavelength",
|
||||
name: "Wavelength",
|
||||
unit: "Hz",
|
||||
formatString: '%0.2f',
|
||||
hints: {
|
||||
domain: 2,
|
||||
spectralAttribute: true
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "cos",
|
||||
name: "Cosine",
|
||||
unit: "deg",
|
||||
formatString: '%0.2f',
|
||||
hints: {
|
||||
range: 2,
|
||||
spectralAttribute: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
'example.spectral-aggregate-generator': {
|
||||
values: [
|
||||
{
|
||||
key: "name",
|
||||
name: "Name",
|
||||
format: "string"
|
||||
},
|
||||
{
|
||||
key: "utc",
|
||||
name: "Time",
|
||||
format: "utc",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "ch1",
|
||||
name: "Channel 1",
|
||||
format: "string",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "ch2",
|
||||
name: "Channel 2",
|
||||
format: "string",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "ch3",
|
||||
name: "Channel 3",
|
||||
format: "string",
|
||||
hints: {
|
||||
range: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "ch4",
|
||||
name: "Channel 4",
|
||||
format: "string",
|
||||
hints: {
|
||||
range: 4
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "ch5",
|
||||
name: "Channel 5",
|
||||
format: "string",
|
||||
hints: {
|
||||
range: 5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
86
example/generator/SpectralAggregateGeneratorProvider.js
Normal file
86
example/generator/SpectralAggregateGeneratorProvider.js
Normal file
@@ -0,0 +1,86 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
|
||||
function SpectralAggregateGeneratorProvider() {
|
||||
|
||||
}
|
||||
|
||||
function pointForTimestamp(timestamp, count, name) {
|
||||
return {
|
||||
name: name,
|
||||
utc: String(Math.floor(timestamp / count) * count),
|
||||
ch1: String(Math.floor(timestamp / count) % 1),
|
||||
ch2: String(Math.floor(timestamp / count) % 2),
|
||||
ch3: String(Math.floor(timestamp / count) % 3),
|
||||
ch4: String(Math.floor(timestamp / count) % 4),
|
||||
ch5: String(Math.floor(timestamp / count) % 5)
|
||||
};
|
||||
}
|
||||
|
||||
SpectralAggregateGeneratorProvider.prototype.supportsSubscribe = function (domainObject) {
|
||||
return domainObject.type === 'example.spectral-aggregate-generator';
|
||||
};
|
||||
|
||||
SpectralAggregateGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
|
||||
var count = 5000;
|
||||
|
||||
var interval = setInterval(function () {
|
||||
var now = Date.now();
|
||||
var datum = pointForTimestamp(now, count, domainObject.name);
|
||||
callback(datum);
|
||||
}, count);
|
||||
|
||||
return function () {
|
||||
clearInterval(interval);
|
||||
};
|
||||
};
|
||||
|
||||
SpectralAggregateGeneratorProvider.prototype.supportsRequest = function (domainObject, options) {
|
||||
return domainObject.type === 'example.spectral-aggregate-generator';
|
||||
};
|
||||
|
||||
SpectralAggregateGeneratorProvider.prototype.request = function (domainObject, options) {
|
||||
var start = options.start;
|
||||
var end = Math.min(Date.now(), options.end); // no future values
|
||||
var count = 5000;
|
||||
if (options.strategy === 'latest' || options.size === 1) {
|
||||
start = end;
|
||||
}
|
||||
|
||||
var data = [];
|
||||
while (start <= end && data.length < 5000) {
|
||||
data.push(pointForTimestamp(start, count, domainObject.name));
|
||||
start += count;
|
||||
}
|
||||
|
||||
return Promise.resolve(data);
|
||||
};
|
||||
|
||||
return SpectralAggregateGeneratorProvider;
|
||||
|
||||
});
|
||||
104
example/generator/SpectralGeneratorProvider.js
Normal file
104
example/generator/SpectralGeneratorProvider.js
Normal file
@@ -0,0 +1,104 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./WorkerInterface'
|
||||
], function (
|
||||
WorkerInterface
|
||||
) {
|
||||
|
||||
var REQUEST_DEFAULTS = {
|
||||
amplitude: 1,
|
||||
wavelength: 1,
|
||||
period: 10,
|
||||
offset: 0,
|
||||
dataRateInHz: 1,
|
||||
randomness: 0,
|
||||
phase: 0
|
||||
};
|
||||
|
||||
function SpectralGeneratorProvider() {
|
||||
this.workerInterface = new WorkerInterface();
|
||||
}
|
||||
|
||||
SpectralGeneratorProvider.prototype.canProvideTelemetry = function (domainObject) {
|
||||
return domainObject.type === 'example.spectral-generator';
|
||||
};
|
||||
|
||||
SpectralGeneratorProvider.prototype.supportsRequest =
|
||||
SpectralGeneratorProvider.prototype.supportsSubscribe =
|
||||
SpectralGeneratorProvider.prototype.canProvideTelemetry;
|
||||
|
||||
SpectralGeneratorProvider.prototype.makeWorkerRequest = function (domainObject, request) {
|
||||
var props = [
|
||||
'amplitude',
|
||||
'wavelength',
|
||||
'period',
|
||||
'offset',
|
||||
'dataRateInHz',
|
||||
'phase',
|
||||
'randomness'
|
||||
];
|
||||
|
||||
request = request || {};
|
||||
|
||||
var workerRequest = {};
|
||||
|
||||
props.forEach(function (prop) {
|
||||
if (domainObject.telemetry && Object.prototype.hasOwnProperty.call(domainObject.telemetry, prop)) {
|
||||
workerRequest[prop] = domainObject.telemetry[prop];
|
||||
}
|
||||
|
||||
if (request && Object.prototype.hasOwnProperty.call(request, prop)) {
|
||||
workerRequest[prop] = request[prop];
|
||||
}
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(workerRequest, prop)) {
|
||||
workerRequest[prop] = REQUEST_DEFAULTS[prop];
|
||||
}
|
||||
|
||||
workerRequest[prop] = Number(workerRequest[prop]);
|
||||
});
|
||||
|
||||
workerRequest.name = domainObject.name;
|
||||
|
||||
return workerRequest;
|
||||
};
|
||||
|
||||
SpectralGeneratorProvider.prototype.request = function (domainObject, request) {
|
||||
var workerRequest = this.makeWorkerRequest(domainObject, request);
|
||||
workerRequest.start = request.start;
|
||||
workerRequest.end = request.end;
|
||||
workerRequest.spectra = true;
|
||||
|
||||
return this.workerInterface.request(workerRequest);
|
||||
};
|
||||
|
||||
SpectralGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
|
||||
var workerRequest = this.makeWorkerRequest(domainObject, {});
|
||||
workerRequest.spectra = true;
|
||||
|
||||
return this.workerInterface.subscribe(workerRequest, callback);
|
||||
};
|
||||
|
||||
return SpectralGeneratorProvider;
|
||||
});
|
||||
@@ -54,23 +54,39 @@
|
||||
var start = Date.now();
|
||||
var step = 1000 / data.dataRateInHz;
|
||||
var nextStep = start - (start % step) + step;
|
||||
let work;
|
||||
console.log('dataRate', data);
|
||||
if (data.spectra) {
|
||||
work = function (now) {
|
||||
while (nextStep < now) {
|
||||
const messageCopy = Object.create(message);
|
||||
message.data.start = nextStep - (60 * 1000);
|
||||
message.data.end = nextStep;
|
||||
onRequest(messageCopy);
|
||||
nextStep += step;
|
||||
}
|
||||
|
||||
function work(now) {
|
||||
while (nextStep < now) {
|
||||
self.postMessage({
|
||||
id: message.id,
|
||||
data: {
|
||||
name: data.name,
|
||||
utc: nextStep,
|
||||
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
||||
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness),
|
||||
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
|
||||
}
|
||||
});
|
||||
nextStep += step;
|
||||
}
|
||||
return nextStep;
|
||||
};
|
||||
} else {
|
||||
work = function (now) {
|
||||
while (nextStep < now) {
|
||||
self.postMessage({
|
||||
id: message.id,
|
||||
data: {
|
||||
name: data.name,
|
||||
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),
|
||||
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
|
||||
}
|
||||
});
|
||||
nextStep += step;
|
||||
}
|
||||
|
||||
return nextStep;
|
||||
return nextStep;
|
||||
};
|
||||
}
|
||||
|
||||
subscriptions[message.id] = work;
|
||||
@@ -111,13 +127,21 @@
|
||||
utc: nextStep,
|
||||
yesterday: nextStep - 60 * 60 * 24 * 1000,
|
||||
sin: sin(nextStep, period, amplitude, offset, phase, randomness),
|
||||
wavelength: wavelength(start, nextStep),
|
||||
cos: cos(nextStep, period, amplitude, offset, phase, randomness)
|
||||
});
|
||||
}
|
||||
|
||||
self.postMessage({
|
||||
id: message.id,
|
||||
data: data
|
||||
data: request.spectra ? {
|
||||
wavelength: data.map((item) => {
|
||||
return item.wavelength;
|
||||
}),
|
||||
cos: data.map((item) => {
|
||||
return item.cos;
|
||||
})
|
||||
} : data
|
||||
});
|
||||
}
|
||||
|
||||
@@ -131,6 +155,10 @@
|
||||
* Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
|
||||
}
|
||||
|
||||
function wavelength(start, nextStep) {
|
||||
return (nextStep - start) / 10;
|
||||
}
|
||||
|
||||
function sendError(error, message) {
|
||||
self.postMessage({
|
||||
error: error.name + ': ' + error.message,
|
||||
|
||||
@@ -24,11 +24,15 @@ define([
|
||||
"./GeneratorProvider",
|
||||
"./SinewaveLimitProvider",
|
||||
"./StateGeneratorProvider",
|
||||
"./SpectralGeneratorProvider",
|
||||
"./SpectralAggregateGeneratorProvider",
|
||||
"./GeneratorMetadataProvider"
|
||||
], function (
|
||||
GeneratorProvider,
|
||||
SinewaveLimitProvider,
|
||||
StateGeneratorProvider,
|
||||
SpectralGeneratorProvider,
|
||||
SpectralAggregateGeneratorProvider,
|
||||
GeneratorMetadataProvider
|
||||
) {
|
||||
|
||||
@@ -61,6 +65,37 @@ define([
|
||||
|
||||
openmct.telemetry.addProvider(new StateGeneratorProvider());
|
||||
|
||||
openmct.types.addType("example.spectral-generator", {
|
||||
name: "Spectral Generator",
|
||||
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
||||
cssClass: "icon-generator-telemetry",
|
||||
creatable: true,
|
||||
initialize: function (object) {
|
||||
object.telemetry = {
|
||||
period: 10,
|
||||
amplitude: 1,
|
||||
wavelength: 1,
|
||||
frequency: 1,
|
||||
offset: 0,
|
||||
dataRateInHz: 1,
|
||||
phase: 0,
|
||||
randomness: 0
|
||||
};
|
||||
}
|
||||
});
|
||||
openmct.telemetry.addProvider(new SpectralGeneratorProvider());
|
||||
|
||||
openmct.types.addType("example.spectral-aggregate-generator", {
|
||||
name: "Spectral Aggregate Generator",
|
||||
description: "For development use. Generates example streaming telemetry data using a simple state algorithm.",
|
||||
cssClass: "icon-generator-telemetry",
|
||||
creatable: true,
|
||||
initialize: function (object) {
|
||||
object.telemetry = {};
|
||||
}
|
||||
});
|
||||
openmct.telemetry.addProvider(new SpectralAggregateGeneratorProvider());
|
||||
|
||||
openmct.types.addType("generator", {
|
||||
name: "Sine Wave Generator",
|
||||
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"name": "openmct",
|
||||
"version": "1.7.8-SNAPSHOT",
|
||||
"description": "The Open MCT core platform",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"angular": ">=1.8.0",
|
||||
"angular-route": "1.4.14",
|
||||
@@ -41,13 +40,13 @@
|
||||
"jsdoc": "^3.3.2",
|
||||
"karma": "6.3.4",
|
||||
"karma-chrome-launcher": "3.1.0",
|
||||
"karma-firefox-launcher": "2.1.1",
|
||||
"karma-cli": "2.0.0",
|
||||
"karma-coverage": "2.0.3",
|
||||
"karma-coverage-istanbul-reporter": "3.0.3",
|
||||
"karma-junit-reporter": "2.0.1",
|
||||
"karma-firefox-launcher": "2.1.1",
|
||||
"karma-html-reporter": "0.2.7",
|
||||
"karma-jasmine": "4.0.1",
|
||||
"karma-junit-reporter": "2.0.1",
|
||||
"karma-sourcemap-loader": "0.3.8",
|
||||
"karma-webpack": "4.0.2",
|
||||
"location-bar": "^3.0.1",
|
||||
@@ -62,6 +61,7 @@
|
||||
"node-bourbon": "^4.2.3",
|
||||
"node-sass": "^4.14.1",
|
||||
"painterro": "^1.2.56",
|
||||
"plotly.js": "^2.5.0",
|
||||
"printj": "^1.2.1",
|
||||
"raw-loader": "^0.5.1",
|
||||
"request": "^2.69.0",
|
||||
|
||||
@@ -89,13 +89,4 @@ ColorPalette.prototype.getNextColor = function () {
|
||||
return this.availableColors.shift();
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} index the index of the color to return. An index
|
||||
* value larger than the size of the index will wrap around.
|
||||
* @returns {Color}
|
||||
*/
|
||||
ColorPalette.prototype.getColor = function (index) {
|
||||
return this.colors[index % this.colors.length];
|
||||
};
|
||||
|
||||
export default ColorPalette;
|
||||
|
||||
@@ -19,13 +19,17 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import { SPECTRAL_AGGREGATE_KEY } from './spectralAggregatePlot/SpectralAggregateConstants';
|
||||
import PlotViewProvider from './PlotViewProvider';
|
||||
import SpectralPlotViewProvider from './spectralPlot/SpectralPlotViewProvider';
|
||||
import SpectralAggregatePlotViewProvider from './spectralAggregatePlot/SpectralAggregatePlotViewProvider';
|
||||
import OverlayPlotViewProvider from './overlayPlot/OverlayPlotViewProvider';
|
||||
import StackedPlotViewProvider from './stackedPlot/StackedPlotViewProvider';
|
||||
import PlotsInspectorViewProvider from './inspector/PlotsInspectorViewProvider';
|
||||
import OverlayPlotCompositionPolicy from './overlayPlot/OverlayPlotCompositionPolicy';
|
||||
import StackedPlotCompositionPolicy from './stackedPlot/StackedPlotCompositionPolicy';
|
||||
import SpectralPlotCompositionPolicy from './spectralPlot/SpectralPlotCompositionPolicy';
|
||||
import SpectralAggregatePlotCompositionPolicy from './spectralAggregatePlot/SpectralAggregatePlotCompositionPolicy';
|
||||
|
||||
export default function () {
|
||||
return function install(openmct) {
|
||||
@@ -59,13 +63,46 @@ export default function () {
|
||||
},
|
||||
priority: 890
|
||||
});
|
||||
openmct.types.addType('telemetry.plot.spectral', {
|
||||
key: "telemetry.plot.spectral",
|
||||
name: "Spectral Plot",
|
||||
cssClass: "icon-plot-stacked",
|
||||
description: "View Spectra on Y Axes with non-time domain on the X axis. Can be added to Display Layouts.",
|
||||
creatable: true,
|
||||
initialize: function (domainObject) {
|
||||
domainObject.composition = [];
|
||||
domainObject.configuration = {};
|
||||
},
|
||||
priority: 890
|
||||
});
|
||||
|
||||
openmct.types.addType(SPECTRAL_AGGREGATE_KEY, {
|
||||
key: SPECTRAL_AGGREGATE_KEY,
|
||||
name: "Spectral Aggregate Plot",
|
||||
cssClass: "icon-plot-stacked",
|
||||
description: "View Spectra on Y Axes with non-time domain on the X axis. Can be added to Display Layouts.",
|
||||
creatable: true,
|
||||
initialize: function (domainObject) {
|
||||
domainObject.composition = [];
|
||||
domainObject.configuration = {
|
||||
plotType: 'bar'
|
||||
};
|
||||
},
|
||||
priority: 891
|
||||
});
|
||||
|
||||
openmct.objectViews.addProvider(new StackedPlotViewProvider(openmct));
|
||||
openmct.objectViews.addProvider(new OverlayPlotViewProvider(openmct));
|
||||
openmct.objectViews.addProvider(new PlotViewProvider(openmct));
|
||||
openmct.objectViews.addProvider(new SpectralPlotViewProvider(openmct));
|
||||
openmct.objectViews.addProvider(new SpectralAggregatePlotViewProvider(openmct));
|
||||
|
||||
openmct.inspectorViews.addProvider(new PlotsInspectorViewProvider(openmct));
|
||||
|
||||
openmct.composition.addPolicy(new OverlayPlotCompositionPolicy(openmct).allow);
|
||||
openmct.composition.addPolicy(new StackedPlotCompositionPolicy(openmct).allow);
|
||||
openmct.composition.addPolicy(new SpectralPlotCompositionPolicy(openmct).allow);
|
||||
openmct.composition.addPolicy(new SpectralAggregatePlotCompositionPolicy(openmct).allow);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -24,10 +24,12 @@ import {createMouseEvent, createOpenMct, resetApplicationState, spyOnBuiltins} f
|
||||
import PlotVuePlugin from "./plugin";
|
||||
import Vue from "vue";
|
||||
import StackedPlot from "./stackedPlot/StackedPlot.vue";
|
||||
// import SpectralPlot from "./spectralPlot/SpectralPlot.vue";
|
||||
import configStore from "./configuration/configStore";
|
||||
import EventEmitter from "EventEmitter";
|
||||
import PlotOptions from "./inspector/PlotOptions.vue";
|
||||
import PlotConfigurationModel from "./configuration/PlotConfigurationModel";
|
||||
import { SPECTRAL_AGGREGATE_VIEW, SPECTRAL_AGGREGATE_KEY } from './spectralAggregatePlot/SpectralAggregateConstants';
|
||||
|
||||
describe("the plugin", function () {
|
||||
let element;
|
||||
@@ -312,6 +314,38 @@ describe("the plugin", function () {
|
||||
let plotView = applicableViews.find((viewProvider) => viewProvider.key === "plot-stacked");
|
||||
expect(plotView).toBeDefined();
|
||||
});
|
||||
|
||||
it("provides a spectral plot view for objects with telemetry", () => {
|
||||
const testTelemetryObject = {
|
||||
id: "test-object",
|
||||
type: "telemetry.plot.spectral",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "a-very-fine-key"
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
|
||||
let plotView = applicableViews.find((viewProvider) => viewProvider.key === "plot-spectral");
|
||||
expect(plotView).toBeDefined();
|
||||
});
|
||||
|
||||
it("provides a spectral aggregate plot view for objects with telemetry", () => {
|
||||
const testTelemetryObject = {
|
||||
id: "test-object",
|
||||
type: SPECTRAL_AGGREGATE_KEY,
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "lots-of-aggregate-telemetry"
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
|
||||
let plotView = applicableViews.find((viewProvider) => viewProvider.key === SPECTRAL_AGGREGATE_VIEW);
|
||||
expect(plotView).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("The single plot view", () => {
|
||||
@@ -462,6 +496,146 @@ describe("the plugin", function () {
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* disabling this until we develop the plot view
|
||||
describe("The spectral plot view", () => {
|
||||
let testTelemetryObject;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let testTelemetryObject2;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let config;
|
||||
let spectralPlotObject;
|
||||
let component;
|
||||
let mockComposition;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let plotViewComponentObject;
|
||||
|
||||
beforeEach(() => {
|
||||
const getFunc = openmct.$injector.get;
|
||||
spyOn(openmct.$injector, "get")
|
||||
.withArgs("exportImageService").and.returnValue({
|
||||
exportPNG: () => {},
|
||||
exportJPG: () => {}
|
||||
})
|
||||
.and.callFake(getFunc);
|
||||
|
||||
spectralPlotObject = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "test-spectral-plot"
|
||||
},
|
||||
type: "telemetry.plot.spectral",
|
||||
name: "Test Spectral Plot"
|
||||
};
|
||||
|
||||
testTelemetryObject = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "test-object"
|
||||
},
|
||||
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
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
testTelemetryObject2 = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "test-object2"
|
||||
},
|
||||
type: "test-object",
|
||||
name: "Test Object2",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "utc",
|
||||
format: "utc",
|
||||
name: "Time",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}, {
|
||||
key: "wavelength",
|
||||
name: "Wavelength",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-other-key2",
|
||||
name: "Another attribute2",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
mockComposition = new EventEmitter();
|
||||
mockComposition.load = () => {
|
||||
mockComposition.emit('add', testTelemetryObject);
|
||||
|
||||
return [testTelemetryObject];
|
||||
};
|
||||
|
||||
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
|
||||
|
||||
let viewContainer = document.createElement("div");
|
||||
child.append(viewContainer);
|
||||
component = new Vue({
|
||||
el: viewContainer,
|
||||
components: {
|
||||
SpectralPlot
|
||||
},
|
||||
provide: {
|
||||
openmct: openmct,
|
||||
domainObject: spectralPlotObject,
|
||||
composition: openmct.composition.get(spectralPlotObject)
|
||||
},
|
||||
template: "<spectral-plot></spectral-plot>"
|
||||
});
|
||||
|
||||
cleanupFirst.push(() => {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
});
|
||||
|
||||
return telemetryPromise
|
||||
.then(Vue.nextTick())
|
||||
.then(() => {
|
||||
plotViewComponentObject = component.$root.$children[0];
|
||||
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
|
||||
config = configStore.get(configId);
|
||||
});
|
||||
});
|
||||
|
||||
it("Renders a collapsed legend for every telemetry", () => {
|
||||
let legend = element.querySelectorAll(".plot-wrapper-collapsed-legend .plot-series-name");
|
||||
expect(legend.length).toBe(1);
|
||||
expect(legend[0].innerHTML).toEqual("Test Object");
|
||||
});
|
||||
|
||||
}); */
|
||||
|
||||
describe("The stacked plot view", () => {
|
||||
let testTelemetryObject;
|
||||
let testTelemetryObject2;
|
||||
@@ -990,4 +1164,39 @@ describe("the plugin", function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe("the spectral plot", () => {
|
||||
const mockObject = {
|
||||
name: 'A Very Nice Spectral Plot',
|
||||
key: 'telemetry.plot.spectral',
|
||||
creatable: true
|
||||
};
|
||||
|
||||
it('defines a spectral plot object type with the correct key', () => {
|
||||
const objectDef = openmct.types.get('telemetry.plot.spectral').definition;
|
||||
expect(objectDef.key).toEqual(mockObject.key);
|
||||
});
|
||||
|
||||
it('is creatable', () => {
|
||||
const objectDef = openmct.types.get('telemetry.plot.spectral').definition;
|
||||
expect(objectDef.creatable).toEqual(mockObject.creatable);
|
||||
});
|
||||
});
|
||||
|
||||
describe("the aggregate spectral plot", () => {
|
||||
const mockObject = {
|
||||
name: 'An Even Nicer Aggregate Spectral Plot',
|
||||
key: SPECTRAL_AGGREGATE_KEY,
|
||||
creatable: true
|
||||
};
|
||||
|
||||
it('defines a spectral plot object type with the correct key', () => {
|
||||
const objectDef = openmct.types.get(SPECTRAL_AGGREGATE_KEY).definition;
|
||||
expect(objectDef.key).toEqual(mockObject.key);
|
||||
});
|
||||
|
||||
it('is creatable', () => {
|
||||
const objectDef = openmct.types.get(SPECTRAL_AGGREGATE_KEY).definition;
|
||||
expect(objectDef.creatable).toEqual(mockObject.creatable);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export const CONFIGURATION_CHANGED = 'configurationChanged';
|
||||
export const HOVER_VALUES_CHANGED = 'hoverValuesChanged';
|
||||
export const HOVER_VALUES_CLEARED = 'hoverValuesCleared';
|
||||
export const LEGEND_EXPANDED = 'plot-legends-expanded';
|
||||
export const REALTIME_POLL_INTERVAL_IN_MS = 5000;
|
||||
|
||||
export const SPECTRAL_AGGREGATE_VIEW = 'plot.spectral.aggregate.view';
|
||||
export const SPECTRAL_AGGREGATE_KEY = 'telemetry.plot.spectral.aggregate';
|
||||
export const SUBSCRIBE = 'subscribe';
|
||||
export const UNSUBSCRIBE = 'unsubscribe';
|
||||
289
src/plugins/plot/spectralAggregatePlot/SpectralAggregatePlot.vue
Normal file
289
src/plugins/plot/spectralAggregatePlot/SpectralAggregatePlot.vue
Normal file
@@ -0,0 +1,289 @@
|
||||
<template>
|
||||
<div ref="plotWrapper"
|
||||
class="has-local-controls"
|
||||
:class="{ 's-unsynced' : isZoomed }"
|
||||
>
|
||||
<div v-if="isZoomed"
|
||||
class="l-state-indicators"
|
||||
>
|
||||
<span class="l-state-indicators__alert-no-lad t-object-alert t-alert-unsynced icon-alert-triangle"
|
||||
title="This plot is not currently displaying the latest data. Reset pan/zoom to view latest data."
|
||||
></span>
|
||||
</div>
|
||||
<div ref="plot"
|
||||
class="c-spectral-aggregate-plot__plot"
|
||||
></div>
|
||||
<div ref="localControl"
|
||||
class="gl-plot__local-controls h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover"
|
||||
>
|
||||
<button v-if="data.length"
|
||||
class="c-button icon-reset"
|
||||
:disabled="!isZoomed"
|
||||
title="Reset pan/zoom"
|
||||
@click="reset()"
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Plotly from 'plotly.js/dist/plotly';
|
||||
import { HOVER_VALUES_CLEARED, HOVER_VALUES_CHANGED, SUBSCRIBE, UNSUBSCRIBE } from './SpectralAggregateConstants';
|
||||
|
||||
const PLOT_PADDING_IN_PERCENT = 1;
|
||||
const MULTI_AXES_X_PADDING_PERCENT = {
|
||||
LEFT: 8,
|
||||
RIGHT: 94
|
||||
};
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
props: [
|
||||
'data',
|
||||
'legendExpanded',
|
||||
'plotAxisTitle'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
isZoomed: false,
|
||||
primaryYAxisRange: {
|
||||
min: '',
|
||||
max: ''
|
||||
},
|
||||
xAxisRange: {
|
||||
min: '',
|
||||
max: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
data: {
|
||||
immediate: false,
|
||||
handler: 'updateData'
|
||||
},
|
||||
legendExpanded: {
|
||||
immediate: false,
|
||||
handler: 'updatePlot'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
Plotly.newPlot(this.$refs.plot, Array.from(this.data), this.getLayout(), {
|
||||
responsive: true,
|
||||
displayModeBar: false
|
||||
});
|
||||
this.registerListeners();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$refs.plot.removeAllListeners();
|
||||
|
||||
if (this.plotResizeObserver) {
|
||||
this.plotResizeObserver.unobserve(this.$refs.plotWrapper);
|
||||
clearTimeout(this.resizeTimer);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getAxisMinMax(axis) {
|
||||
const min = axis.autoSize
|
||||
? ''
|
||||
: axis.min;
|
||||
const max = axis.autoSize
|
||||
? ''
|
||||
: axis.max;
|
||||
|
||||
return {
|
||||
min,
|
||||
max
|
||||
};
|
||||
},
|
||||
getAxisPadding(min, max) {
|
||||
return (Math.abs(max - min) * PLOT_PADDING_IN_PERCENT / 100) || 0;
|
||||
},
|
||||
getLayout() {
|
||||
const yAxesMeta = this.getYAxisMeta();
|
||||
const primaryYaxis = this.getYaxisLayout(yAxesMeta['1']);
|
||||
const xAxisDomain = this.getXAxisDomain(yAxesMeta);
|
||||
|
||||
return {
|
||||
// hovermode: 'closest',
|
||||
// hoverdistance: -1,
|
||||
autosize: true,
|
||||
showlegend: false,
|
||||
font: {
|
||||
family: 'Helvetica Neue, Helvetica, Arial, sans-serif',
|
||||
size: '12px',
|
||||
color: '#666'
|
||||
},
|
||||
xaxis: {
|
||||
domain: xAxisDomain,
|
||||
// hoverformat: '.2r',
|
||||
range: [this.xAxisRange.min, this.xAxisRange.max],
|
||||
title: this.plotAxisTitle.xAxisTitle
|
||||
// zeroline: false
|
||||
},
|
||||
yaxis: primaryYaxis,
|
||||
margin: {
|
||||
l: 40,
|
||||
r: 10,
|
||||
b: 40,
|
||||
t: 20
|
||||
},
|
||||
paper_bgcolor: 'transparent',
|
||||
plot_bgcolor: 'transparent'
|
||||
};
|
||||
},
|
||||
getYAxisMeta() {
|
||||
const yAxisMeta = {};
|
||||
|
||||
this.data.forEach(d => {
|
||||
const yAxisMetadata = d.yAxisMetadata;
|
||||
const range = '1';
|
||||
const side = 'left';
|
||||
const name = '';
|
||||
const unit = yAxisMetadata.units;
|
||||
|
||||
yAxisMeta[range] = {
|
||||
range,
|
||||
side,
|
||||
name,
|
||||
unit
|
||||
};
|
||||
});
|
||||
|
||||
return yAxisMeta;
|
||||
},
|
||||
getXAxisDomain(yAxisMeta) {
|
||||
let leftPaddingPerc = 0;
|
||||
let rightPaddingPerc = 100;
|
||||
let rightSide = yAxisMeta && Object.values(yAxisMeta).filter((axisMeta => axisMeta.side === 'right'));
|
||||
let leftSide = yAxisMeta && Object.values(yAxisMeta).filter((axisMeta => axisMeta.side === 'left'));
|
||||
if (yAxisMeta && rightSide.length > 1) {
|
||||
rightPaddingPerc = MULTI_AXES_X_PADDING_PERCENT.RIGHT;
|
||||
}
|
||||
|
||||
if (yAxisMeta && leftSide.length > 1) {
|
||||
leftPaddingPerc = MULTI_AXES_X_PADDING_PERCENT.LEFT;
|
||||
}
|
||||
|
||||
return [leftPaddingPerc / 100, rightPaddingPerc / 100];
|
||||
},
|
||||
getYaxisLayout(yAxisMeta) {
|
||||
if (!yAxisMeta) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const { name, range, side = 'left', unit } = yAxisMeta;
|
||||
const title = `${name} ${unit ? '(' + unit + ')' : ''}`;
|
||||
const yaxis = {
|
||||
// hoverformat: '.2r',
|
||||
// showgrid: true,
|
||||
title
|
||||
// zeroline: false
|
||||
};
|
||||
|
||||
let yAxistype = this.primaryYAxisRange;
|
||||
if (range === '1') {
|
||||
yaxis.range = [yAxistype.min, yAxistype.max];
|
||||
|
||||
return yaxis;
|
||||
}
|
||||
|
||||
yaxis.range = [yAxistype.min, yAxistype.max];
|
||||
yaxis.anchor = side.toLowerCase() === 'left'
|
||||
? 'free'
|
||||
: 'x';
|
||||
yaxis.showline = side.toLowerCase() === 'left';
|
||||
yaxis.side = side.toLowerCase();
|
||||
yaxis.overlaying = 'y';
|
||||
yaxis.position = 0.01;
|
||||
|
||||
return yaxis;
|
||||
},
|
||||
handleHover(isHovered, itemValues) {
|
||||
return () => {
|
||||
this.updateLocalControlPosition(isHovered);
|
||||
|
||||
const eventName = isHovered ? HOVER_VALUES_CHANGED : HOVER_VALUES_CLEARED;
|
||||
this.$emit(eventName, { itemValues });
|
||||
};
|
||||
},
|
||||
registerListeners() {
|
||||
this.$refs.plot.on('plotly_hover', this.handleHover.bind(this, true));
|
||||
this.$refs.plot.on('plotly_unhover', this.handleHover.bind(this, false));
|
||||
this.$refs.plot.on('plotly_relayout', this.zoom);
|
||||
this.resizeTimer = false;
|
||||
if (window.ResizeObserver) {
|
||||
this.plotResizeObserver = new ResizeObserver(() => {
|
||||
// debounce and trigger window resize so that plotly can resize the plot
|
||||
clearTimeout(this.resizeTimer);
|
||||
this.resizeTimer = setTimeout(() => {
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
}, 250);
|
||||
});
|
||||
this.plotResizeObserver.observe(this.$refs.plotWrapper);
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
this.updatePlot();
|
||||
|
||||
this.isZoomed = false;
|
||||
this.$emit(SUBSCRIBE);
|
||||
},
|
||||
updateData() {
|
||||
this.updatePlot();
|
||||
},
|
||||
updateLocalControlPosition() {
|
||||
const localControl = this.$refs.localControl;
|
||||
localControl.style.display = 'none';
|
||||
|
||||
const plot = this.$refs.plot;
|
||||
const bgLayer = this.$el.querySelector('.bglayer');
|
||||
|
||||
const plotBoundingRect = plot.getBoundingClientRect();
|
||||
const bgLayerBoundingRect = bgLayer.getBoundingClientRect();
|
||||
|
||||
const top = bgLayerBoundingRect.top - plotBoundingRect.top + 5;
|
||||
const left = bgLayerBoundingRect.left - plotBoundingRect.left + 5;
|
||||
|
||||
localControl.style.top = `${top}px`;
|
||||
localControl.style.left = `${left}px`;
|
||||
localControl.style.display = 'block';
|
||||
},
|
||||
updatePlot() {
|
||||
if (!this.$refs || !this.$refs.plot) {
|
||||
return;
|
||||
}
|
||||
|
||||
Plotly.react(this.$refs.plot, Array.from(this.data), this.getLayout());
|
||||
},
|
||||
zoom(eventData) {
|
||||
const autorange = eventData['xaxis.autorange'];
|
||||
const { autosize } = eventData;
|
||||
|
||||
if (autosize || autorange) {
|
||||
this.isZoomed = false;
|
||||
this.reset();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.isZoomed = true;
|
||||
this.$emit(UNSUBSCRIBE);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.c-spectral-aggregate-plot__plot {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.c-spectral-aggregate-plot-view .gl-plot__local-controls {
|
||||
top: 25px;
|
||||
}
|
||||
|
||||
.has-local-controls {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import { SPECTRAL_AGGREGATE_KEY } from './SpectralAggregateConstants';
|
||||
export default function SpectralAggregatePlotCompositionPolicy(openmct) {
|
||||
function hasAggregateDomainAndRange(metadata) {
|
||||
const rangeValues = metadata.valuesForHints(['domain']);
|
||||
const domainValues = metadata.valuesForHints(['range']);
|
||||
|
||||
return rangeValues.length > 0
|
||||
|| domainValues.length > 0;
|
||||
}
|
||||
|
||||
function hasSpectralAggregateTelemetry(domainObject) {
|
||||
if (!Object.prototype.hasOwnProperty.call(domainObject, 'telemetry')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let metadata = openmct.telemetry.getMetadata(domainObject);
|
||||
|
||||
return metadata.values().length > 0 && hasAggregateDomainAndRange(metadata);
|
||||
}
|
||||
|
||||
function hasNoChildren(parentObject) {
|
||||
return parentObject.composition && parentObject.composition.length < 1;
|
||||
}
|
||||
|
||||
return {
|
||||
allow: function (parent, child) {
|
||||
if ((parent.type === SPECTRAL_AGGREGATE_KEY)
|
||||
&& ((child.type !== 'telemetry.plot.overlay') && (hasSpectralAggregateTelemetry(child) === false) || (hasNoChildren(parent) === false))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import SpectralAggregatePlotCompositionPolicy from "./SpectralAggregatePlotCompositionPolicy";
|
||||
import { createOpenMct } from "utils/testing";
|
||||
|
||||
describe("The spectral aggregation plot composition policy", () => {
|
||||
let openmct;
|
||||
const mockNonSpectralMetaData = {
|
||||
"period": 10,
|
||||
"amplitude": 1,
|
||||
"offset": 0,
|
||||
"dataRateInHz": 1,
|
||||
"phase": 0,
|
||||
"randomness": 0,
|
||||
valuesForHints: () => {
|
||||
return [
|
||||
{
|
||||
"key": "sin",
|
||||
"name": "Sine",
|
||||
"unit": "Hz",
|
||||
"formatString": "%0.2f",
|
||||
"hints": {
|
||||
"range": 1,
|
||||
"priority": 4
|
||||
},
|
||||
"source": "sin"
|
||||
},
|
||||
{
|
||||
"key": "cos",
|
||||
"name": "Cosine",
|
||||
"unit": "deg",
|
||||
"formatString": "%0.2f",
|
||||
"hints": {
|
||||
"range": 2,
|
||||
"priority": 5
|
||||
},
|
||||
"source": "cos"
|
||||
}
|
||||
];
|
||||
},
|
||||
values: [
|
||||
{
|
||||
"key": "name",
|
||||
"name": "Name",
|
||||
"format": "string",
|
||||
"source": "name",
|
||||
"hints": {
|
||||
"priority": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "utc",
|
||||
"name": "Time",
|
||||
"format": "utc",
|
||||
"hints": {
|
||||
"domain": 1,
|
||||
"priority": 1
|
||||
},
|
||||
"source": "utc"
|
||||
},
|
||||
{
|
||||
"key": "yesterday",
|
||||
"name": "Yesterday",
|
||||
"format": "utc",
|
||||
"hints": {
|
||||
"domain": 2,
|
||||
"priority": 2
|
||||
},
|
||||
"source": "yesterday"
|
||||
},
|
||||
{
|
||||
"key": "cos",
|
||||
"name": "Cosine",
|
||||
"unit": "deg",
|
||||
"formatString": "%0.2f",
|
||||
"hints": {
|
||||
"domain": 3,
|
||||
"priority": 3
|
||||
},
|
||||
"source": "cos"
|
||||
},
|
||||
{
|
||||
"key": "sin",
|
||||
"name": "Sine",
|
||||
"unit": "Hz",
|
||||
"formatString": "%0.2f",
|
||||
"hints": {
|
||||
"range": 1,
|
||||
"priority": 4
|
||||
},
|
||||
"source": "sin"
|
||||
},
|
||||
{
|
||||
"key": "cos",
|
||||
"name": "Cosine",
|
||||
"unit": "deg",
|
||||
"formatString": "%0.2f",
|
||||
"hints": {
|
||||
"range": 2,
|
||||
"priority": 5
|
||||
},
|
||||
"source": "cos"
|
||||
}
|
||||
]
|
||||
};
|
||||
const mockGoodSpectralMetaData = {
|
||||
"period": 10,
|
||||
"amplitude": 1,
|
||||
"offset": 0,
|
||||
"dataRateInHz": 1,
|
||||
"phase": 0,
|
||||
"randomness": 0,
|
||||
"wavelength": 0,
|
||||
valuesForHints: () => {
|
||||
return [
|
||||
{
|
||||
"key": "sin",
|
||||
"name": "Sine",
|
||||
"unit": "Hz",
|
||||
"formatString": "%0.2f",
|
||||
"hints": {
|
||||
"range": 1,
|
||||
"priority": 4
|
||||
},
|
||||
"source": "sin"
|
||||
},
|
||||
{
|
||||
"key": "cos",
|
||||
"name": "Cosine",
|
||||
"unit": "deg",
|
||||
"formatString": "%0.2f",
|
||||
"hints": {
|
||||
"range": 2,
|
||||
"priority": 5
|
||||
},
|
||||
"source": "cos"
|
||||
}
|
||||
];
|
||||
},
|
||||
values: [
|
||||
{
|
||||
"key": "name",
|
||||
"name": "Name",
|
||||
"format": "string",
|
||||
"source": "name",
|
||||
"hints": {
|
||||
"priority": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "utc",
|
||||
"name": "Time",
|
||||
"format": "utc",
|
||||
"hints": {
|
||||
"domain": 1,
|
||||
"priority": 1
|
||||
},
|
||||
"source": "utc"
|
||||
},
|
||||
{
|
||||
"key": "yesterday",
|
||||
"name": "Yesterday",
|
||||
"format": "utc",
|
||||
"hints": {
|
||||
"domain": 2,
|
||||
"priority": 2
|
||||
},
|
||||
"source": "yesterday"
|
||||
},
|
||||
{
|
||||
"key": "cos",
|
||||
"name": "Cosine",
|
||||
"unit": "deg",
|
||||
"formatString": "%0.2f",
|
||||
"hints": {
|
||||
"domain": 3,
|
||||
"priority": 3
|
||||
},
|
||||
"source": "cos"
|
||||
},
|
||||
{
|
||||
"key": "sin",
|
||||
"name": "Sine",
|
||||
"unit": "Hz",
|
||||
"formatString": "%0.2f",
|
||||
"hints": {
|
||||
"range": 1,
|
||||
"spectralAttribute": true
|
||||
},
|
||||
"source": "sin"
|
||||
},
|
||||
{
|
||||
"key": "cos",
|
||||
"name": "Cosine",
|
||||
"unit": "deg",
|
||||
"formatString": "%0.2f",
|
||||
"hints": {
|
||||
"range": 2,
|
||||
"priority": 5
|
||||
},
|
||||
"source": "cos"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
openmct = createOpenMct();
|
||||
const mockTypeDef = {
|
||||
telemetry: mockGoodSpectralMetaData
|
||||
};
|
||||
const mockTypeService = {
|
||||
getType: () => {
|
||||
return {
|
||||
typeDef: mockTypeDef
|
||||
};
|
||||
}
|
||||
};
|
||||
openmct.$injector = {
|
||||
get: () => {
|
||||
return mockTypeService;
|
||||
}
|
||||
};
|
||||
|
||||
openmct.telemetry.isTelemetryObject = function (domainObject) {
|
||||
return true;
|
||||
};
|
||||
});
|
||||
|
||||
it("exists", () => {
|
||||
expect(SpectralAggregatePlotCompositionPolicy(openmct).allow).toBeDefined();
|
||||
});
|
||||
|
||||
it("allow composition only for telemetry that provides/supports spectral data", () => {
|
||||
const parent = {
|
||||
"composition": [],
|
||||
"configuration": {},
|
||||
"name": "Some Spectral Aggregate Plot",
|
||||
"type": "telemetry.plot.spectral.aggregate",
|
||||
"location": "mine",
|
||||
"modified": 1631005183584,
|
||||
"persisted": 1631005183502,
|
||||
"identifier": {
|
||||
"namespace": "",
|
||||
"key": "b78e7e23-f2b8-4776-b1f0-3ff778f5c8a9"
|
||||
}
|
||||
};
|
||||
const child = {
|
||||
"telemetry": {
|
||||
"period": 10,
|
||||
"amplitude": 1,
|
||||
"offset": 0,
|
||||
"dataRateInHz": 1,
|
||||
"phase": 0,
|
||||
"randomness": 0
|
||||
},
|
||||
"name": "Unnamed Sine Wave Generator",
|
||||
"type": "generator",
|
||||
"location": "mine",
|
||||
"modified": 1630399715531,
|
||||
"persisted": 1630399715531,
|
||||
"identifier": {
|
||||
"namespace": "",
|
||||
"key": "21d61f2d-6d2d-4bea-8b0a-7f59fd504c6c"
|
||||
}
|
||||
};
|
||||
expect(SpectralAggregatePlotCompositionPolicy(openmct).allow(parent, child)).toEqual(true);
|
||||
});
|
||||
|
||||
it("disallows composition for telemetry that contain anything else", () => {
|
||||
const mockTypeDef = {
|
||||
telemetry: mockNonSpectralMetaData
|
||||
};
|
||||
const mockTypeService = {
|
||||
getType: () => {
|
||||
return {
|
||||
typeDef: mockTypeDef
|
||||
};
|
||||
}
|
||||
};
|
||||
openmct.$injector = {
|
||||
get: () => {
|
||||
return mockTypeService;
|
||||
}
|
||||
};
|
||||
const parent = {
|
||||
"composition": [],
|
||||
"configuration": {},
|
||||
"name": "Some Spectral Aggregate Plot",
|
||||
"type": "telemetry.plot.spectral.aggregate",
|
||||
"location": "mine",
|
||||
"modified": 1631005183584,
|
||||
"persisted": 1631005183502,
|
||||
"identifier": {
|
||||
"namespace": "",
|
||||
"key": "b78e7e23-f2b8-4776-b1f0-3ff778f5c8a9"
|
||||
}
|
||||
};
|
||||
const child = {
|
||||
"telemetry": {
|
||||
"period": 10,
|
||||
"amplitude": 1,
|
||||
"offset": 0,
|
||||
"dataRateInHz": 1,
|
||||
"phase": 0,
|
||||
"randomness": 0
|
||||
},
|
||||
"name": "Unnamed Sine Wave Generator",
|
||||
"type": "generator",
|
||||
"location": "mine",
|
||||
"modified": 1630399715531,
|
||||
"persisted": 1630399715531,
|
||||
"identifier": {
|
||||
"namespace": "",
|
||||
"key": "21d61f2d-6d2d-4bea-8b0a-7f59fd504c6c"
|
||||
}
|
||||
};
|
||||
expect(SpectralAggregatePlotCompositionPolicy(openmct).allow(parent, child)).toEqual(false);
|
||||
});
|
||||
|
||||
it("passthrough for composition for non spectral aggregate plots", () => {
|
||||
const parent = {
|
||||
"composition": [],
|
||||
"configuration": {},
|
||||
"name": "Some Stacked Plot",
|
||||
"type": "telemetry.plot.stacked",
|
||||
"location": "mine",
|
||||
"modified": 1631005183584,
|
||||
"persisted": 1631005183502,
|
||||
"identifier": {
|
||||
"namespace": "",
|
||||
"key": "b78e7e23-f2b8-4776-b1f0-3ff778f5c8a9"
|
||||
}
|
||||
};
|
||||
const child = {
|
||||
"telemetry": {
|
||||
"period": 10,
|
||||
"amplitude": 1,
|
||||
"offset": 0,
|
||||
"dataRateInHz": 1,
|
||||
"phase": 0,
|
||||
"randomness": 0
|
||||
},
|
||||
"name": "Unnamed Sine Wave Generator",
|
||||
"type": "generator",
|
||||
"location": "mine",
|
||||
"modified": 1630399715531,
|
||||
"persisted": 1630399715531,
|
||||
"identifier": {
|
||||
"namespace": "",
|
||||
"key": "21d61f2d-6d2d-4bea-8b0a-7f59fd504c6c"
|
||||
}
|
||||
};
|
||||
expect(SpectralAggregatePlotCompositionPolicy(openmct).allow(parent, child)).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import SpectralAggregateView from './SpectralAggregateView.vue';
|
||||
import { SPECTRAL_AGGREGATE_KEY, SPECTRAL_AGGREGATE_VIEW } from './SpectralAggregateConstants';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default function SpectralAggregatePlotViewProvider(openmct) {
|
||||
function isCompactView(objectPath) {
|
||||
return objectPath.find(object => object.type === 'time-strip');
|
||||
}
|
||||
|
||||
return {
|
||||
key: SPECTRAL_AGGREGATE_VIEW,
|
||||
name: 'Spectral Aggregate Plot',
|
||||
cssClass: 'icon-telemetry',
|
||||
canView(domainObject, objectPath) {
|
||||
return domainObject && domainObject.type === SPECTRAL_AGGREGATE_KEY;
|
||||
},
|
||||
|
||||
canEdit(domainObject, objectPath) {
|
||||
return domainObject && domainObject.type === SPECTRAL_AGGREGATE_KEY;
|
||||
},
|
||||
|
||||
view: function (domainObject, objectPath) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
let isCompact = isCompactView(objectPath);
|
||||
component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
SpectralAggregateView
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: {
|
||||
compact: isCompact
|
||||
}
|
||||
};
|
||||
},
|
||||
template: '<spectral-aggregate-view :options="options"></spectral-aggregate-view>'
|
||||
});
|
||||
},
|
||||
destroy: function () {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
332
src/plugins/plot/spectralAggregatePlot/SpectralAggregateView.vue
Normal file
332
src/plugins/plot/spectralAggregatePlot/SpectralAggregateView.vue
Normal file
@@ -0,0 +1,332 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
as represented by the Administrator of the National Aeronautics and Space
|
||||
Administration. All rights reserved.
|
||||
|
||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
Open MCT includes source code licensed under additional open source
|
||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="c-spectral-aggregate-plot-view gl-plot plot-legend-bottom"
|
||||
:class="{'plot-legend-expanded': legendExpanded, 'plot-legend-collapsed': !legendExpanded }"
|
||||
>
|
||||
<SpectralAggregatePlot ref="spectralAggregatePlot"
|
||||
class="c-spectral-aggregate-plot__plot-wrapper"
|
||||
:data="visibleData"
|
||||
:plot-axis-title="plotAxisTitle"
|
||||
:legend-expanded="legendExpanded"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as SPECTRAL_AGGREGATE from './SpectralAggregateConstants';
|
||||
import ColorPalette from '../lib/ColorPalette';
|
||||
import objectUtils from 'objectUtils';
|
||||
import SpectralAggregatePlot from './SpectralAggregatePlot.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SpectralAggregatePlot
|
||||
},
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data() {
|
||||
return {
|
||||
colorMapping: {},
|
||||
composition: {},
|
||||
currentDomainObject: this.domainObject,
|
||||
isRealTime: (this.openmct.time.clock() !== undefined),
|
||||
spectralAggregateTypes: {},
|
||||
subscriptions: [],
|
||||
telemetryObjects: {},
|
||||
trace: [],
|
||||
legendExpanded: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
activeClock() {
|
||||
return this.openmct.time.activeClock;
|
||||
},
|
||||
plotAxisTitle() {
|
||||
const { xAxisMetadata = {}, yAxisMetadata = {} } = this.trace[0] || {};
|
||||
const xAxisUnit = xAxisMetadata.units ? `(${xAxisMetadata.units})` : '';
|
||||
const yAxisUnit = yAxisMetadata.units ? `(${yAxisMetadata.units})` : '';
|
||||
|
||||
return {
|
||||
xAxisTitle: `${xAxisMetadata.name || ''} ${xAxisUnit}`,
|
||||
yAxisTitle: `${yAxisMetadata.name || ''} ${yAxisUnit}`
|
||||
};
|
||||
},
|
||||
visibleData() {
|
||||
return this.trace.filter((trace) => trace.hidden !== true);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.colorPalette = new ColorPalette();
|
||||
this.loadComposition();
|
||||
|
||||
this.openmct.time.on('bounds', this.refreshData);
|
||||
this.openmct.time.on('clock', this.clockChanged);
|
||||
|
||||
this.$refs.spectralAggregatePlot.$on(SPECTRAL_AGGREGATE.SUBSCRIBE, this.subscribeToAll);
|
||||
this.$refs.spectralAggregatePlot.$on(SPECTRAL_AGGREGATE.UNSUBSCRIBE, this.removeAllSubscriptions);
|
||||
|
||||
this.unobserve = this.openmct.objects.observe(this.currentDomainObject, '*', this.updateDomainObject);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$refs.spectralAggregatePlot.$off();
|
||||
this.openmct.time.off('bounds', this.refreshData);
|
||||
this.openmct.time.off('clock', this.clockChanged);
|
||||
|
||||
this.removeAllSubscriptions();
|
||||
this.unobserve();
|
||||
|
||||
if (!this.composition) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.composition.off('add', this.addTelemetryObject);
|
||||
this.composition.off('remove', this.removeTelemetryObject);
|
||||
},
|
||||
methods: {
|
||||
addColorForTelemetry(key) {
|
||||
const color = this.colorPalette.getNextColor().asHexString();
|
||||
this.colorMapping[key] = color;
|
||||
|
||||
return color;
|
||||
},
|
||||
addSpectralAggregateType(key, name, timestamp, color) {
|
||||
this.$set(this.spectralAggregateTypes, key, {
|
||||
name,
|
||||
timestamp,
|
||||
color
|
||||
});
|
||||
},
|
||||
addTelemetryObject(telemetryObject) {
|
||||
const key = objectUtils.makeKeyString(telemetryObject.identifier);
|
||||
|
||||
if (!this.colorMapping[key]) {
|
||||
this.addColorForTelemetry(key);
|
||||
}
|
||||
|
||||
this.telemetryObjects[key] = telemetryObject;
|
||||
|
||||
this.requestDataFor(telemetryObject);
|
||||
this.subscribeToObject(telemetryObject);
|
||||
},
|
||||
addTrace(trace, key) {
|
||||
if (!this.trace.length) {
|
||||
this.trace = this.trace.concat([trace]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let isInTrace = false;
|
||||
const newTrace = this.trace.map((currentTrace, index) => {
|
||||
if (currentTrace.key !== key) {
|
||||
return currentTrace;
|
||||
}
|
||||
|
||||
isInTrace = true;
|
||||
|
||||
return trace;
|
||||
});
|
||||
|
||||
this.trace = isInTrace ? newTrace : newTrace.concat([trace]);
|
||||
this.updateTraceVisibility();
|
||||
},
|
||||
clockChanged() {
|
||||
this.isRealTime = this.openmct.time.clock() !== undefined;
|
||||
|
||||
this.removeAllSubscriptions();
|
||||
this.subscribeToAll();
|
||||
},
|
||||
getAxisMetadata(telemetryObject) {
|
||||
const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
||||
const yAxisMetadata = metadata.valuesForHints(['range'])[0];
|
||||
const xAxisMetadata = metadata.valueMetadatas.filter((valueMetadata => {
|
||||
return valueMetadata.key !== 'name' && !this.openmct.time.getAllTimeSystems().find((timeSystem) => timeSystem.key === valueMetadata.key);
|
||||
}));
|
||||
|
||||
return {
|
||||
xAxisMetadata,
|
||||
yAxisMetadata
|
||||
};
|
||||
},
|
||||
getOptions(telemetryObject) {
|
||||
const { start, end } = this.openmct.time.bounds();
|
||||
|
||||
return {
|
||||
averages: 0,
|
||||
end,
|
||||
start,
|
||||
startTime: null,
|
||||
spectra: true
|
||||
};
|
||||
},
|
||||
loadComposition() {
|
||||
this.composition = this.openmct.composition.get(this.currentDomainObject);
|
||||
|
||||
if (!this.composition) {
|
||||
this.addTelemetryObject(this.currentDomainObject);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.composition.on('add', this.addTelemetryObject);
|
||||
this.composition.on('remove', this.removeTelemetryObject);
|
||||
this.composition.load();
|
||||
},
|
||||
refreshData(bounds, isTick) {
|
||||
if (!isTick) {
|
||||
this.colorPalette.reset();
|
||||
const telemetryObjects = Object.values(this.telemetryObjects);
|
||||
telemetryObjects.forEach(this.requestDataFor);
|
||||
}
|
||||
},
|
||||
removeAllSubscriptions() {
|
||||
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||
this.subscriptions = [];
|
||||
},
|
||||
removeTelemetryObject(identifier) {
|
||||
const key = objectUtils.makeKeyString(identifier);
|
||||
delete this.telemetryObjects[key];
|
||||
this.$delete(this.spectralAggregateTypes, key);
|
||||
|
||||
this.subscriptions.forEach(subscription => {
|
||||
if (subscription.key !== key) {
|
||||
return;
|
||||
}
|
||||
|
||||
subscription.unsubscribe();
|
||||
delete this.subscriptions[key];
|
||||
});
|
||||
|
||||
this.trace = this.trace.filter(t => t.key !== key);
|
||||
},
|
||||
processData(telemetryObject, data, axisMetadata) {
|
||||
const key = objectUtils.makeKeyString(telemetryObject.identifier);
|
||||
|
||||
const formattedTimestamp = 'N/A';
|
||||
|
||||
const color = this.colorMapping[key];
|
||||
const spectralAggregateValue = this.spectralAggregateTypes[key];
|
||||
if (!spectralAggregateValue) {
|
||||
this.addSpectralAggregateType(key, telemetryObject.name, formattedTimestamp, color);
|
||||
}
|
||||
|
||||
if (data.message) {
|
||||
this.openmct.notifications.alert(data.message);
|
||||
}
|
||||
|
||||
let xValues = [];
|
||||
let yValues = [];
|
||||
axisMetadata.xAxisMetadata.forEach((metadata) => {
|
||||
xValues.push(metadata.name);
|
||||
if (data[metadata.key]) {
|
||||
yValues.push(data[metadata.key]);
|
||||
} else {
|
||||
yValues.push('');
|
||||
}
|
||||
});
|
||||
|
||||
const trace = {
|
||||
key,
|
||||
name: telemetryObject.name,
|
||||
x: xValues,
|
||||
y: yValues,
|
||||
xAxisMetadata: axisMetadata.xAxisMetadata,
|
||||
yAxisMetadata: axisMetadata.yAxisMetadata,
|
||||
type: 'bar'
|
||||
};
|
||||
|
||||
this.addTrace(trace, key);
|
||||
},
|
||||
requestDataFor(telemetryObject) {
|
||||
const axisMetadata = this.getAxisMetadata(telemetryObject);
|
||||
this.openmct.telemetry.request(telemetryObject, this.getOptions(telemetryObject))
|
||||
.then(data => {
|
||||
data.forEach((datum) => {
|
||||
this.processData(telemetryObject, datum, axisMetadata);
|
||||
});
|
||||
});
|
||||
},
|
||||
subscribeToObject(telemetryObject) {
|
||||
const key = objectUtils.makeKeyString(telemetryObject.identifier);
|
||||
const found = Object.values(this.subscriptions).findIndex(objectKey => objectKey === key);
|
||||
if (found > -1) {
|
||||
this.subscriptions[found].unsubscribe();
|
||||
delete this.subscriptions[found];
|
||||
}
|
||||
|
||||
const options = this.getOptions(telemetryObject);
|
||||
const axisMetadata = this.getAxisMetadata(telemetryObject);
|
||||
const unsubscribe = this.openmct.telemetry.subscribe(telemetryObject,
|
||||
data => this.processData(telemetryObject, data, axisMetadata)
|
||||
, options);
|
||||
|
||||
this.subscriptions.push({
|
||||
key,
|
||||
unsubscribe
|
||||
});
|
||||
},
|
||||
subscribeToAll() {
|
||||
this.colorPalette.reset();
|
||||
const telemetryObjects = Object.values(this.telemetryObjects);
|
||||
telemetryObjects.forEach(this.subscribeToObject);
|
||||
},
|
||||
updateDomainObject(newDomainObject) {
|
||||
this.currentDomainObject = newDomainObject;
|
||||
},
|
||||
updateTraceVisibility() {
|
||||
// We create a copy here to improve performance since visibleData - a computed property - is dependent on this.trace.
|
||||
this.trace = this.trace.map((currentTrace, index) => {
|
||||
currentTrace.hidden = this.spectralAggregateTypes[currentTrace.key].hidden === true;
|
||||
|
||||
return currentTrace;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.c-spectral-aggregate-plot {
|
||||
> * + * {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
&-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__plot-wrapper {
|
||||
flex: 1 1 auto;
|
||||
min-height: 300px;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
&__legend-wrapper {
|
||||
flex: 0 1 auto;
|
||||
overflow: auto;
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,36 @@
|
||||
export default function SpectralPlotCompositionPolicy(openmct) {
|
||||
function hasSpectralDomainAndRange(metadata) {
|
||||
const rangeValues = metadata.valuesForHints(['range']);
|
||||
const domainValues = metadata.valuesForHints(['domain']);
|
||||
const containsSomeSpectralData = domainValues.some(value => {
|
||||
return ((value.key === 'wavelength') || (value.key === 'frequency'));
|
||||
});
|
||||
|
||||
return rangeValues.length > 0
|
||||
&& domainValues.length > 0
|
||||
&& containsSomeSpectralData;
|
||||
}
|
||||
|
||||
function hasSpectralTelemetry(domainObject) {
|
||||
if (!Object.prototype.hasOwnProperty.call(domainObject, 'telemetry')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let metadata = openmct.telemetry.getMetadata(domainObject);
|
||||
|
||||
return metadata.values().length > 0 && hasSpectralDomainAndRange(metadata);
|
||||
}
|
||||
|
||||
return {
|
||||
allow: function (parent, child) {
|
||||
|
||||
if ((parent.type === 'telemetry.plot.spectral')
|
||||
&& ((child.type !== 'telemetry.plot.overlay') && (hasSpectralTelemetry(child) === false))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
75
src/plugins/plot/spectralPlot/SpectralPlotViewProvider.js
Normal file
75
src/plugins/plot/spectralPlot/SpectralPlotViewProvider.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import SpectralView from './SpectralView.vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default function SpectralPlotViewProvider(openmct) {
|
||||
function isCompactView(objectPath) {
|
||||
return objectPath.find(object => object.type === 'time-strip');
|
||||
}
|
||||
|
||||
return {
|
||||
key: 'plot-spectral',
|
||||
name: 'Spectral Plot',
|
||||
cssClass: 'icon-telemetry',
|
||||
canView(domainObject, objectPath) {
|
||||
return domainObject && domainObject.type === 'telemetry.plot.spectral';
|
||||
},
|
||||
|
||||
canEdit(domainObject, objectPath) {
|
||||
return domainObject && domainObject.type === 'telemetry.plot.spectral';
|
||||
},
|
||||
|
||||
view: function (domainObject, objectPath) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
let isCompact = isCompactView(objectPath);
|
||||
component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
SpectralView
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: {
|
||||
compact: isCompact
|
||||
}
|
||||
};
|
||||
},
|
||||
template: '<spectral-view :options="options"></spectral-view>'
|
||||
});
|
||||
},
|
||||
destroy: function () {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
13
src/plugins/plot/spectralPlot/SpectralView.vue
Normal file
13
src/plugins/plot/spectralPlot/SpectralView.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject']
|
||||
};
|
||||
|
||||
</script>
|
||||
Reference in New Issue
Block a user