Compare commits
	
		
			23 Commits
		
	
	
		
			remove-unu
			...
			mct4102-sh
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | d4ed4b4a01 | ||
|   | a967f27731 | ||
|   | a423fa1a8f | ||
|   | 60e054f9e1 | ||
|   | 123f3b06b5 | ||
|   | 0e2f37e3b2 | ||
|   | 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,56 @@ | ||||
| /***************************************************************************** | ||||
|  * 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(['range']); | ||||
|  | ||||
|         return rangeValues.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,346 @@ | ||||
| /***************************************************************************** | ||||
|  * 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 []; | ||||
|         }, | ||||
|         values: [ | ||||
|             { | ||||
|                 "key": "name", | ||||
|                 "name": "Name", | ||||
|                 "format": "string" | ||||
|             }, | ||||
|             { | ||||
|                 "key": "utc", | ||||
|                 "name": "Time", | ||||
|                 "format": "utc", | ||||
|                 "hints": { | ||||
|                     "domain": 1, | ||||
|                     "priority": 1 | ||||
|                 }, | ||||
|                 "source": "utc" | ||||
|             } | ||||
|         ] | ||||
|     }; | ||||
|     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": "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(); | ||||
|     }); | ||||
|  | ||||
|     xit("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("allows composition for telemetry that contain at least one range", () => { | ||||
|         const mockTypeDef = { | ||||
|             telemetry: mockGoodSpectralMetaData | ||||
|         }; | ||||
|         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(true); | ||||
|     }); | ||||
|  | ||||
|     it("disallows composition for telemetry that don't contain any range hints", () => { | ||||
|         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; | ||||
|                 } | ||||
|             }; | ||||
|         } | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										333
									
								
								src/plugins/plot/spectralAggregatePlot/SpectralAggregateView.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										333
									
								
								src/plugins/plot/spectralAggregatePlot/SpectralAggregateView.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,333 @@ | ||||
| <!-- | ||||
|  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]; | ||||
|             //Exclude 'name' and 'time' based metadata specifically, from the x-Axis values by using range hints only | ||||
|             const xAxisMetadata = metadata.valuesForHints(['range']); | ||||
|  | ||||
|             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 = []; | ||||
|  | ||||
|             //populate X and Y values for plotly | ||||
|             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