diff options
author | Shefali Joshi <simplyrender@gmail.com> | 2021-09-18 23:00:16 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-18 23:00:16 +0300 |
commit | c4b9be18f100681fe9efbc29d9d6a938c4b7a76b (patch) | |
tree | 276eab37b03cb210de10f62fed42132fbe028eb4 | |
parent | eabdf6cd04403d1376f354ccc5e36323f31041dd (diff) |
Support for Bar Graphs (#4221)1.7.8-rc1
* Adds new types for Bar Graphs (#4168)
* Adds new spectral test data
Co-authored-by: Scott Bell <scott@traclabs.com>
Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
35 files changed, 2242 insertions, 94 deletions
diff --git a/example/generator/GeneratorMetadataProvider.js b/example/generator/GeneratorMetadataProvider.js index 179019b0d..112166117 100644 --- a/example/generator/GeneratorMetadataProvider.js +++ b/example/generator/GeneratorMetadataProvider.js @@ -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 + } + } + ] } }; diff --git a/example/generator/SpectralAggregateGeneratorProvider.js b/example/generator/SpectralAggregateGeneratorProvider.js new file mode 100644 index 000000000..dcd75c89e --- /dev/null +++ b/example/generator/SpectralAggregateGeneratorProvider.js @@ -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; + +}); diff --git a/example/generator/SpectralGeneratorProvider.js b/example/generator/SpectralGeneratorProvider.js new file mode 100644 index 000000000..4011bf90d --- /dev/null +++ b/example/generator/SpectralGeneratorProvider.js @@ -0,0 +1,102 @@ +/***************************************************************************** + * 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' + ]; + + 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; +}); diff --git a/example/generator/generatorWorker.js b/example/generator/generatorWorker.js index d0200650c..563dbda69 100644 --- a/example/generator/generatorWorker.js +++ b/example/generator/generatorWorker.js @@ -54,23 +54,38 @@ var start = Date.now(); var step = 1000 / data.dataRateInHz; var nextStep = start - (start % step) + 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; + let work; + 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; + } + + 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; + }; } subscriptions[message.id] = work; @@ -111,13 +126,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 +154,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, diff --git a/example/generator/plugin.js b/example/generator/plugin.js index a7a027e32..e98572200 100644 --- a/example/generator/plugin.js +++ b/example/generator/plugin.js @@ -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.", diff --git a/package.json b/package.json index 35c6b4a14..c8c96e1aa 100644 --- a/package.json +++ b/package.json @@ -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", @@ -12,16 +11,9 @@ "copy-webpack-plugin": "^4.5.2", "cross-env": "^6.0.3", "css-loader": "^1.0.0", - "d3-array": "1.2.x", "d3-axis": "1.0.x", - "d3-collection": "1.0.x", - "d3-color": "1.0.x", - "d3-format": "1.2.x", - "d3-interpolate": "1.1.x", "d3-scale": "1.0.x", "d3-selection": "1.3.x", - "d3-time": "1.0.x", - "d3-time-format": "2.1.x", "eslint": "7.0.0", "eslint-plugin-vue": "^7.5.0", "eslint-plugin-you-dont-need-lodash-underscore": "^6.10.0", @@ -41,13 +33,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 +54,8 @@ "node-bourbon": "^4.2.3", "node-sass": "^4.14.1", "painterro": "^1.2.56", + "plotly.js-basic-dist": "^2.5.0", + "plotly.js-gl2d-dist": "^2.5.0", "printj": "^1.2.1", "raw-loader": "^0.5.1", "request": "^2.69.0", diff --git a/src/plugins/plot/ColorSwatch.vue b/src/plugins/plot/ColorSwatch.vue new file mode 100644 index 000000000..77b305e92 --- /dev/null +++ b/src/plugins/plot/ColorSwatch.vue @@ -0,0 +1,170 @@ +<!-- + Open MCT, Copyright (c) 2014-2020, 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="u-contents"> + <ul v-if="canEdit" + class="l-inspector-part" + > + <h2 v-if="heading" + :title="heading" + >{{ heading }}</h2> + <li class="grid-row"> + <div class="grid-cell label" + :title="editTitle" + >{{ shortLabel }}</div> + <div class="grid-cell value"> + <div class="c-click-swatch c-click-swatch--menu" + @click="toggleSwatch()" + > + <span class="c-color-swatch" + :style="{ background: currentColor }" + > + </span> + </div> + <div class="c-palette c-palette--color"> + <div v-show="swatchActive" + class="c-palette__items" + > + <div v-for="group in colorPaletteGroups" + :key="group.id" + class="u-contents" + > + <div v-for="color in group" + :key="color.id" + class="c-palette__item" + :class="{ 'selected': currentColor === color.hexString }" + :style="{ background: color.hexString }" + @click="setColor(color)" + > + </div> + </div> + </div> + </div> + </div> + </li> + </ul> + <ul v-else + class="l-inspector-part" + > + <h2 v-if="heading" + :title="heading" + >{{ heading }}</h2> + <li class="grid-row"> + <div class="grid-cell label" + :title="viewTitle" + >{{ shortLabel }}</div> + <div class="grid-cell value"> + <span class="c-color-swatch" + :style="{ + 'background': currentColor + }" + > + </span> + </div> + </li> + </ul> +</div> +</template> + +<script> +import ColorPalette from './lib/ColorPalette'; + +export default { + inject: ['openmct', 'domainObject'], + props: { + currentColor: { + type: String, + default() { + return ''; + } + }, + editTitle: { + type: String, + default() { + return 'Set the color.'; + } + }, + viewTitle: { + type: String, + default() { + return 'The current color.'; + } + }, + shortLabel: { + type: String, + default() { + return 'Color'; + } + }, + heading: { + type: String, + default() { + return ''; + } + } + }, + data() { + return { + swatchActive: false, + colorPaletteGroups: [], + isEditing: this.openmct.editor.isEditing() + }; + }, + computed: { + canEdit() { + return this.isEditing && !this.domainObject.locked; + } + }, + mounted() { + this.colorPalette = new ColorPalette(); + this.openmct.editor.on('isEditing', this.setEditState); + this.initialize(); + }, + beforeDestroy() { + this.openmct.editor.off('isEditing', this.setEditState); + }, + methods: { + initialize() { + const colorPaletteGroups = this.colorPalette.groups(); + colorPaletteGroups.forEach((group, index) => { + let groupId = []; + group.forEach(color => { + color.hexString = color.asHexString(); + color.id = `${color.hexString}-${index}`; + groupId.push(color.id); + }); + group.id = groupId.join('-'); + }); + this.colorPaletteGroups = colorPaletteGroups; + }, + setEditState(isEditing) { + this.isEditing = isEditing; + }, + setColor(chosenColor) { + this.$emit('colorSet', chosenColor); + }, + toggleSwatch() { + this.swatchActive = !this.swatchActive; + } + } +}; +</script> diff --git a/src/plugins/plot/MctPlot.vue b/src/plugins/plot/MctPlot.vue index 16917ad22..d801479be 100644 --- a/src/plugins/plot/MctPlot.vue +++ b/src/plugins/plot/MctPlot.vue @@ -156,7 +156,7 @@ import eventHelpers from './lib/eventHelpers'; import LinearScale from "./LinearScale"; import PlotConfigurationModel from './configuration/PlotConfigurationModel'; -import configStore from './configuration/configStore'; +import configStore from './configuration/ConfigStore'; import PlotLegend from "./legend/PlotLegend.vue"; import MctTicks from "./MctTicks.vue"; diff --git a/src/plugins/plot/MctTicks.vue b/src/plugins/plot/MctTicks.vue index ef7388f6e..fa1a0f398 100644 --- a/src/plugins/plot/MctTicks.vue +++ b/src/plugins/plot/MctTicks.vue @@ -77,7 +77,7 @@ <script> import eventHelpers from "./lib/eventHelpers"; import { ticks, getFormattedTicks } from "./tickUtils"; -import configStore from "./configuration/configStore"; +import configStore from "./configuration/ConfigStore"; export default { inject: ['openmct', 'domainObject'], diff --git a/src/plugins/plot/axis/XAxis.vue b/src/plugins/plot/axis/XAxis.vue index 8e414ca8c..93bcdddcc 100644 --- a/src/plugins/plot/axis/XAxis.vue +++ b/src/plugins/plot/axis/XAxis.vue @@ -54,7 +54,7 @@ <script> import MctTicks from "../MctTicks.vue"; import eventHelpers from '../lib/eventHelpers'; -import configStore from "../configuration/configStore"; +import configStore from "../configuration/ConfigStore"; export default { components: { diff --git a/src/plugins/plot/axis/YAxis.vue b/src/plugins/plot/axis/YAxis.vue index 1a2b1fd97..fec2e3a5f 100644 --- a/src/plugins/plot/axis/YAxis.vue +++ b/src/plugins/plot/axis/YAxis.vue @@ -57,7 +57,7 @@ <script> import MctTicks from "../MctTicks.vue"; -import configStore from "../configuration/configStore"; +import configStore from "../configuration/ConfigStore"; export default { components: { diff --git a/src/plugins/plot/barGraph/BarGraphCompositionPolicy.js b/src/plugins/plot/barGraph/BarGraphCompositionPolicy.js new file mode 100644 index 000000000..9380ef809 --- /dev/null +++ b/src/plugins/plot/barGraph/BarGraphCompositionPolicy.js @@ -0,0 +1,57 @@ +/***************************************************************************** + * 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 { BAR_GRAPH_KEY } from './BarGraphConstants'; + +export default function BarGraphCompositionPolicy(openmct) { + function hasAggregateDomainAndRange(metadata) { + const rangeValues = metadata.valuesForHints(['range']); + + return rangeValues.length > 0; + } + + function hasBarGraphTelemetry(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 === BAR_GRAPH_KEY) + && ((child.type !== 'telemetry.plot.overlay') && (hasBarGraphTelemetry(child) === false)) + ) { + return false; + } + + return true; + } + }; +} diff --git a/src/plugins/plot/barGraph/BarGraphCompositionPolicySpec.js b/src/plugins/plot/barGraph/BarGraphCompositionPolicySpec.js new file mode 100644 index 000000000..eb6baa509 --- /dev/null +++ b/src/plugins/plot/barGraph/BarGraphCompositionPolicySpec.js @@ -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 BarGraphCompositionPolicy from "./BarGraphCompositionPolicy"; +import { createOpenMct } from "utils/testing"; + +describe("The bar graph composition policy", () => { + let openmct; + const mockMetaDataWithNoRangeHints = { + "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 mockMetaDataWithRangeHints = { + "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: mockMetaDataWithRangeHints + }; + const mockTypeService = { + getType: () => { + return { + typeDef: mockTypeDef + }; + } + }; + openmct.$injector = { + get: () => { + return mockTypeService; + } + }; + + openmct.telemetry.isTelemetryObject = function (domainObject) { + return true; + }; + }); + + it("exists", () => { + expect(BarGraphCompositionPolicy(openmct).allow).toBeDefined(); + }); + + xit("allow composition for telemetry that provides/supports bar graph meta data", () => { + const parent = { + "composition": [], + "configuration": {}, + "name": "Some Bar Graph", + "type": "telemetry.plot.bar-graph", + "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(BarGraphCompositionPolicy(openmct).allow(parent, child)).toEqual(true); + }); + + it("allows composition for telemetry that contain at least one range", () => { + const mockTypeDef = { + telemetry: mockMetaDataWithRangeHints + }; + const mockTypeService = { + getType: () => { + return { + typeDef: mockTypeDef + }; + } + }; + openmct.$injector = { + get: () => { + return mockTypeService; + } + }; + const parent = { + "composition": [], + "configuration": {}, + "name": "Some Bar Graph", + "type": "telemetry.plot.bar-graph", + "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(BarGraphCompositionPolicy(openmct).allow(parent, child)).toEqual(true); + }); + + it("disallows composition for telemetry that don't contain any range hints", () => { + const mockTypeDef = { + telemetry: mockMetaDataWithNoRangeHints + }; + const mockTypeService = { + getType: () => { + return { + typeDef: mockTypeDef + }; + } + }; + openmct.$injector = { + get: () => { + return mockTypeService; + } + }; + const parent = { + "composition": [], + "configuration": {}, + "name": "Some Bar Graph", + "type": "telemetry.plot.bar-graph", + "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(BarGraphCompositionPolicy(openmct).allow(parent, child)).toEqual(false); + }); + + it("passthrough for composition for non bar graph 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(BarGraphCompositionPolicy(openmct).allow(parent, child)).toEqual(true); + }); +}); + diff --git a/src/plugins/plot/barGraph/BarGraphConstants.js b/src/plugins/plot/barGraph/BarGraphConstants.js new file mode 100644 index 000000000..ba3f78e73 --- /dev/null +++ b/src/plugins/plot/barGraph/BarGraphConstants.js @@ -0,0 +1,5 @@ +export const BAR_GRAPH_VIEW = 'bar-graph.view'; +export const BAR_GRAPH_KEY = 'telemetry.plot.bar-graph'; +export const BAR_GRAPH_INSPECTOR_KEY = 'telemetry.plot.bar-graph.inspector'; +export const SUBSCRIBE = 'subscribe'; +export const UNSUBSCRIBE = 'unsubscribe'; diff --git a/src/plugins/plot/barGraph/BarGraphPlot.vue b/src/plugins/plot/barGraph/BarGraphPlot.vue new file mode 100644 index 000000000..97cfdf9ee --- /dev/null +++ b/src/plugins/plot/barGraph/BarGraphPlot.vue @@ -0,0 +1,293 @@ +<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-bar-chart" + ></div> + <div v-if="false" + 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-basic-dist'; +import { SUBSCRIBE, UNSUBSCRIBE } from './BarGraphConstants'; + +const MULTI_AXES_X_PADDING_PERCENT = { + LEFT: 8, + RIGHT: 94 +}; + +export default { + inject: ['openmct', 'domainObject'], + props: { + data: { + type: Array, + default() { + return []; + } + }, + plotAxisTitle: { + type: Object, + default() { + return {}; + } + } + }, + data() { + return { + isZoomed: false, + primaryYAxisRange: { + min: '', + max: '' + }, + xAxisRange: { + min: '', + max: '' + } + }; + }, + watch: { + data: { + immediate: false, + handler: 'updateData' + } + }, + 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); + } + + if (this.removeBarColorListener) { + this.removeBarColorListener(); + } + }, + methods: { + getAxisMinMax(axis) { + const min = axis.autoSize + ? '' + : axis.min; + const max = axis.autoSize + ? '' + : axis.max; + + return { + min, + max + }; + }, + getLayout() { + const yAxesMeta = this.getYAxisMeta(); + const primaryYaxis = this.getYaxisLayout(yAxesMeta['1']); + const xAxisDomain = this.getXAxisDomain(yAxesMeta); + + return { + autosize: true, + showlegend: false, + textposition: 'auto', + font: { + family: 'Helvetica Neue, Helvetica, Arial, sans-serif', + size: '12px', + color: '#666' + }, + xaxis: { + domain: xAxisDomain, + range: [this.xAxisRange.min, this.xAxisRange.max], + title: this.plotAxisTitle.xAxisTitle, + automargin: true, + fixedrange: true + }, + yaxis: primaryYaxis, + margin: { + l: 5, + r: 5, + t: 5, + b: 0 + }, + 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 = { + automargin: true, + fixedrange: true, + title + }; + + 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; + }, + registerListeners() { + this.$refs.plot.on('plotly_relayout', this.zoom); + + this.removeBarColorListener = this.openmct.objects.observe( + this.domainObject, + 'configuration.barStyles', + this.barColorChanged + ); + 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); + }, + barColorChanged() { + const colors = []; + const indices = []; + this.data.forEach((item, index) => { + const key = item.key; + const color = this.domainObject.configuration.barStyles[key] && this.domainObject.configuration.barStyles[key].color; + indices.push(index); + if (color) { + colors.push(); + } else { + colors.push(item.marker.color); + } + }); + const plotUpdate = { + 'marker.color': colors + }; + Plotly.restyle(this.$refs.plot, plotUpdate, indices); + }, + 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> + diff --git a/src/plugins/plot/barGraph/BarGraphView.vue b/src/plugins/plot/barGraph/BarGraphView.vue new file mode 100644 index 000000000..a7fce7675 --- /dev/null +++ b/src/plugins/plot/barGraph/BarGraphView.vue @@ -0,0 +1,286 @@ +<!-- + 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> +<BarGraph ref="barGraph" + class="c-plot c-bar-chart-view" + :data="trace" + :plot-axis-title="plotAxisTitle" +/> +</template> + +<script> +import * as SPECTRAL_AGGREGATE from './BarGraphConstants'; +import ColorPalette from '../lib/ColorPalette'; +import BarGraph from './BarGraphPlot.vue'; +import Color from "@/plugins/plot/lib/Color"; + +export default { + components: { + BarGraph + }, + inject: ['openmct', 'domainObject'], + data() { + return { + composition: {}, + currentDomainObject: this.domainObject, + subscriptions: [], + telemetryObjects: {}, + trace: [] + }; + }, + 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}` + }; + } + }, + mounted() { + this.colorPalette = new ColorPalette(); + this.loadComposition(); + + this.openmct.time.on('bounds', this.refreshData); + this.openmct.time.on('clock', this.clockChanged); + + this.$refs.barGraph.$on(SPECTRAL_AGGREGATE.SUBSCRIBE, this.subscribeToAll); + this.$refs.barGraph.$on(SPECTRAL_AGGREGATE.UNSUBSCRIBE, this.removeAllSubscriptions); + + this.unobserve = this.openmct.objects.observe(this.currentDomainObject, '*', this.updateDomainObject); + }, + beforeDestroy() { + this.$refs.barGraph.$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: { + addTelemetryObject(telemetryObject) { + const key = this.openmct.objects.makeKeyString(telemetryObject.identifier); + + if (!this.domainObject.configuration.barStyles) { + this.domainObject.configuration.barStyles = {}; + } + + // check to see if we've set a bar color + if (!this.domainObject.configuration.barStyles[key] || !this.domainObject.configuration.barStyles[key].color) { + const color = this.colorPalette.getNextColor().asHexString(); + this.domainObject.configuration.barStyles[key] = { + name: telemetryObject.name, + color + }; + this.openmct.objects.mutate( + this.domainObject, + `configuration.barStyles[${this.key}]`, + this.domainObject.configuration.barStyles[key] + ); + } else { + let color = this.domainObject.configuration.barStyles[key].color; + if (!(color instanceof Color)) { + color = Color.fromHexString(color); + } + + this.colorPalette.remove(color); + } + + 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]); + }, + clockChanged() { + 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 { + 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) { + const telemetryObjects = Object.values(this.telemetryObjects); + telemetryObjects.forEach(this.requestDataFor); + } + }, + removeAllSubscriptions() { + this.subscriptions.forEach(subscription => subscription.unsubscribe()); + this.subscriptions = []; + }, + removeSubscription(key) { + const found = this.subscriptions.findIndex(subscription => subscription.key === key); + if (found > -1) { + this.subscriptions[found].unsubscribe(); + this.subscriptions.splice(found, 1); + } + }, + removeTelemetryObject(identifier) { + const key = this.openmct.objects.makeKeyString(identifier); + delete this.telemetryObjects[key]; + if (this.domainObject.configuration.barStyles[key]) { + delete this.domainObject.configuration.barStyles[key]; + } + + this.removeSubscription(key); + + this.trace = this.trace.filter(t => t.key !== key); + }, + processData(telemetryObject, data, axisMetadata) { + const key = this.openmct.objects.makeKeyString(telemetryObject.identifier); + + 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]) { + //TODO: Format the data? + yValues.push(data[metadata.key]); + } else { + yValues.push(''); + } + }); + + const trace = { + key, + name: telemetryObject.name, + x: xValues, + y: yValues, + text: yValues.map(String), + xAxisMetadata: axisMetadata.xAxisMetadata, + yAxisMetadata: axisMetadata.yAxisMetadata, + type: 'bar', + marker: { + color: this.domainObject.configuration.barStyles[key].color + }, + hoverinfo: 'skip' + }; + + 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 = this.openmct.objects.makeKeyString(telemetryObject.identifier); + + this.removeSubscription(key); + + 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() { + const telemetryObjects = Object.values(this.telemetryObjects); + telemetryObjects.forEach(this.subscribeToObject); + }, + updateDomainObject(newDomainObject) { + this.currentDomainObject = newDomainObject; + } + } +}; + +</script> diff --git a/src/plugins/plot/barGraph/BarGraphViewProvider.js b/src/plugins/plot/barGraph/BarGraphViewProvider.js new file mode 100644 index 000000000..9ddbb8c9f --- /dev/null +++ b/src/plugins/plot/barGraph/BarGraphViewProvider.js @@ -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 BarGraphView from './BarGraphView.vue'; +import { BAR_GRAPH_KEY, BAR_GRAPH_VIEW } from './BarGraphConstants'; +import Vue from 'vue'; + +export default function BarGraphViewProvider(openmct) { + function isCompactView(objectPath) { + return objectPath.find(object => object.type === 'time-strip'); + } + + return { + key: BAR_GRAPH_VIEW, + name: 'Spectral Aggregate Plot', + cssClass: 'icon-telemetry', + canView(domainObject, objectPath) { + return domainObject && domainObject.type === BAR_GRAPH_KEY; + }, + + canEdit(domainObject, objectPath) { + return domainObject && domainObject.type === BAR_GRAPH_KEY; + }, + + view: function (domainObject, objectPath) { + let component; + + return { + show: function (element) { + let isCompact = isCompactView(objectPath); + component = new Vue({ + el: element, + components: { + BarGraphView + }, + provide: { + openmct, + domainObject + }, + data() { + return { + options: { + compact: isCompact + } + }; + }, + template: '<bar-graph-view :options="options"></bar-graph-view>' + }); + }, + destroy: function () { + component.$destroy(); + component = undefined; + } + }; + } + }; +} diff --git a/src/plugins/plot/barGraph/inspector/BarGraphInspectorViewProvider.js b/src/plugins/plot/barGraph/inspector/BarGraphInspectorViewProvider.js new file mode 100644 index 000000000..6888dc1cd --- /dev/null +++ b/src/plugins/plot/barGraph/inspector/BarGraphInspectorViewProvider.js @@ -0,0 +1,48 @@ +import { BAR_GRAPH_INSPECTOR_KEY, BAR_GRAPH_KEY } from '../BarGraphConstants'; +import Vue from 'vue'; +import Options from "./Options.vue"; + +export default function BarGraphInspectorViewProvider(openmct) { + return { + key: BAR_GRAPH_INSPECTOR_KEY, + name: 'Bar Graph Inspector View', + canView: function (selection) { + if (selection.length === 0 || selection[0].length === 0) { + return false; + } + + let object = selection[0][0].context.item; + + return object + && object.type === BAR_GRAPH_KEY; + }, + view: function (selection) { + let component; + + return { + show: function (element) { + component = new Vue({ + el: element, + components: { + Options + }, + provide: { + openmct, + domainObject: selection[0][0].context.item + }, + template: '<options></options>' + }); + }, + destroy: function () { + if (component) { + component.$destroy(); + component = undefined; + } + } + }; + }, + priority: function () { + return 1; + } + }; +} diff --git a/src/plugins/plot/barGraph/inspector/BarGraphOptions.vue b/src/plugins/plot/barGraph/inspector/BarGraphOptions.vue new file mode 100644 index 000000000..04bd6801d --- /dev/null +++ b/src/plugins/plot/barGraph/inspector/BarGraphOptions.vue @@ -0,0 +1,107 @@ +<!-- + Open MCT, Copyright (c) 2014-2020, 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> +<ul> + <li class="c-tree__item menus-to-left"> + <span class="c-disclosure-triangle is-enabled flex-elem" + :class="expandedCssClass" + @click="expanded = !expanded" + > + </span> + <div> + <div class="c-object-label__name">{{ name }}</div> + </div> + </li> + <ColorSwatch v-if="expanded" + :current-color="currentColor" + title="Manually set the color for this bar graph." + edit-title="Manually set the color for this bar graph" + view-title="The color for this bar graph." + short-label="Color" + class="grid-properties" + @colorSet="setColor" + /> +</ul> +</template> + +<script> +import ColorSwatch from '../../ColorSwatch.vue'; + +export default { + components: { + ColorSwatch + }, + inject: ['openmct', 'domainObject'], + props: { + item: { + type: Object, + required: true + } + }, + data() { + return { + currentColor: undefined, + name: '', + expanded: false + }; + }, + computed: { + expandedCssClass() { + return this.expanded ? 'c-disclosure-triangle--expanded' : ''; + } + }, + watch: { + item: { + handler() { + this.initColor(); + }, + deep: true + } + }, + mounted() { + this.key = this.openmct.objects.makeKeyString(this.item); + this.initColor(); + this.unObserve = this.openmct.objects.observe(this.domainObject, `this.domainObject.configuration.barStyles[${this.key}]`, this.initColor); + }, + beforeDestroy() { + if (this.unObserve) { + this.unObserve(); + } + }, + methods: { + initColor() { + if (this.domainObject.configuration.barStyles && this.domainObject.configuration.barStyles[this.key]) { + this.currentColor = this.domainObject.configuration.barStyles[this.key].color; + this.name = this.domainObject.configuration.barStyles[this.key].name; + } + }, + setColor(chosenColor) { + this.currentColor = chosenColor.asHexString(); + this.openmct.objects.mutate( + this.domainObject, + `configuration.barStyles[${this.key}].color`, + this.currentColor + ); + } + } +}; +</script> diff --git a/src/plugins/plot/barGraph/inspector/Options.vue b/src/plugins/plot/barGraph/inspector/Options.vue new file mode 100644 index 000000000..72528fd39 --- /dev/null +++ b/src/plugins/plot/barGraph/inspector/Options.vue @@ -0,0 +1,63 @@ +<!-- + Open MCT, Copyright (c) 2014-2020, 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> + <ul class="c-tree"> + <li v-for="series in domainObject.composition" + :key="series.key" + > + <bar-graph-options :item="series" /> + </li> + </ul> +</div> +</template> + +<script> +import BarGraphOptions from "./BarGraphOptions.vue"; +export default { + components: { + BarGraphOptions + }, + inject: ['openmct', 'domainObject'], + data() { + return { + isEditing: this.openmct.editor.isEditing() + }; + }, + computed: { + canEdit() { + return this.isEditing && !this.domainObject.locked; + } + }, + mounted() { + this.openmct.editor.on('isEditing', this.setEditState); + }, + beforeDestroy() { + this.openmct.editor.off('isEditing', this.setEditState); + }, + methods: { + setEditState(isEditing) { + this.isEditing = isEditing; + } + } +}; +</script> diff --git a/src/plugins/plot/chart/MctChart.vue b/src/plugins/plot/chart/MctChart.vue index e6db5cd43..a480331f2 100644 --- a/src/plugins/plot/chart/MctChart.vue +++ b/src/plugins/plot/chart/MctChart.vue @@ -38,7 +38,7 @@ import MCTChartLineStepAfter from './MCTChartLineStepAfter'; import MCTChartPointSet from './MCTChartPointSet'; import MCTChartAlarmPointSet from './MCTChartAlarmPointSet'; import MCTChartAlarmLineSet from "./MCTChartAlarmLineSet"; -import configStore from "../configuration/configStore"; +import configStore from "../configuration/ConfigStore"; import PlotConfigurationModel from "../configuration/PlotConfigurationModel"; import LimitLine from "./LimitLine.vue"; import LimitLabel from "./LimitLabel.vue"; diff --git a/src/plugins/plot/configuration/configStore.js b/src/plugins/plot/configuration/ConfigStore.js index c7e07a82e..c7e07a82e 100644 --- a/src/plugins/plot/configuration/configStore.js +++ b/src/plugins/plot/configuration/ConfigStore.js diff --git a/src/plugins/plot/configuration/PlotSeries.js b/src/plugins/plot/configuration/PlotSeries.js index 7863dcdeb..2d33eeaed 100644 --- a/src/plugins/plot/configuration/PlotSeries.js +++ b/src/plugins/plot/configuration/PlotSeries.js @@ -22,7 +22,7 @@ import _ from 'lodash'; import Model from "./Model"; import { MARKER_SHAPES } from '../draw/MarkerShapes'; -import configStore from "../configuration/configStore"; +import configStore from "../configuration/ConfigStore"; /** * Plot series handle interpreting telemetry metadata for a single telemetry diff --git a/src/plugins/plot/inspector/PlotOptionsBrowse.vue b/src/plugins/plot/inspector/PlotOptionsBrowse.vue index acb3bbbe2..2ab893ff6 100644 --- a/src/plugins/plot/inspector/PlotOptionsBrowse.vue +++ b/src/plugins/plot/inspector/PlotOptionsBrowse.vue @@ -115,7 +115,7 @@ <script> import PlotOptionsItem from "./PlotOptionsItem.vue"; -import configStore from "../configuration/configStore"; +import configStore from "../configuration/ConfigStore"; import eventHelpers from "../lib/eventHelpers"; export default { diff --git a/src/plugins/plot/inspector/PlotOptionsEdit.vue b/src/plugins/plot/inspector/PlotOptionsEdit.vue index 5c251c246..3adcc0fb0 100644 --- a/src/plugins/plot/inspector/PlotOptionsEdit.vue +++ b/src/plugins/plot/inspector/PlotOptionsEdit.vue @@ -49,7 +49,7 @@ import SeriesForm from "./forms/SeriesForm.vue"; import YAxisForm from "./forms/YAxisForm.vue"; import LegendForm from "./forms/LegendForm.vue"; import eventHelpers from "../lib/eventHelpers"; -import configStore from "../configuration/configStore"; +import configStore from "../configuration/ConfigStore"; export default { components: { diff --git a/src/plugins/plot/inspector/PlotOptionsItem.vue b/src/plugins/plot/inspector/PlotOptionsItem.vue index 42684ae9d..668900263 100644 --- a/src/plugins/plot/inspector/PlotOptionsItem.vue +++ b/src/plugins/plot/inspector/PlotOptionsItem.vue @@ -68,26 +68,23 @@ {{ limitLines ? "Enabled" : "Disabled" }} </div> </li> - <li class="grid-row"> - <div class="grid-cell label" - title="The plot line and marker color for this series." - >Color</div> - <div class="grid-cell value"> - <span class="c-color-swatch" - :style="{ - 'background': seriesHexColor - }" - > - </span> - </div> - </li> + <ColorSwatch :current-color="seriesHexColor" + edit-title="Manually set the plot line and marker color for this series." + view-title="The plot line and marker color for this series." + short-label="Color" + /> </ul> </li> </ul> </template> <script> +import ColorSwatch from "@/plugins/plot/ColorSwatch.vue"; + export default { + components: { + ColorSwatch + }, inject: ['openmct', 'domainObject', 'path'], props: { series: { diff --git a/src/plugins/plot/inspector/forms/SeriesForm.vue b/src/plugins/plot/inspector/forms/SeriesForm.vue index 5c36ae20a..6a73bfddd 100644 --- a/src/plugins/plot/inspector/forms/SeriesForm.vue +++ b/src/plugins/plot/inspector/forms/SeriesForm.vue @@ -117,49 +117,27 @@ <li v-show="interpolate !== 'none' || markers" class="grid-row" > - <div class="grid-cell label" - title="Manually set the plot line and marker color for this series." - >Color</div> - <div class="grid-cell value"> - <div class="c-click-swatch c-click-swatch--menu" - @click="toggleSwatch()" - > - <span class="c-color-swatch" - :style="{ background: seriesColorAsHex }" - > - </span> - </div> - <div class="c-palette c-palette--color"> - <div v-show="swatchActive" - class="c-palette__items" - > - <div v-for="(group, index) in colorPalette" - :key="index" - class="u-contents" - > - <div v-for="(color, colorIndex) in group" - :key="colorIndex" - class="c-palette__item" - :class="{ 'selected': series.get('color').equalTo(color) }" - :style="{ background: color.asHexString() }" - @click="setColor(color)" - > - </div> - </div> - </div> - </div> - </div> + <ColorSwatch :current-color="currentColor" + edit-title="Manually set the plot line and marker color for this series." + view-title="The plot line and marker color for this series." + short-label="Color" + @colorSet="setColor" + /> </li> </ul> </ul> </template> <script> +import ColorSwatch from '../../ColorSwatch.vue'; import { MARKER_SHAPES } from "../../draw/MarkerShapes"; import { objectPath, validate, coerce } from "./formUtil"; import _ from 'lodash'; export default { + components: { + ColorSwatch + }, inject: ['openmct', 'domainObject', 'path'], props: { series: { @@ -209,7 +187,7 @@ export default { expandedCssClass() { return this.expanded ? 'c-disclosure-triangle--expanded' : ''; }, - seriesColorAsHex() { + currentColor() { return this.series.get('color').asHexString(); } }, diff --git a/src/plugins/plot/lib/ColorPalette.js b/src/plugins/plot/lib/ColorPalette.js index 099ecd8ae..7463a4678 100644 --- a/src/plugins/plot/lib/ColorPalette.js +++ b/src/plugins/plot/lib/ColorPalette.js @@ -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; diff --git a/src/plugins/plot/plugin.js b/src/plugins/plot/plugin.js index 091977d17..53908b06c 100644 --- a/src/plugins/plot/plugin.js +++ b/src/plugins/plot/plugin.js @@ -19,13 +19,18 @@ * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ - +import { BAR_GRAPH_KEY } from './barGraph/BarGraphConstants'; import PlotViewProvider from './PlotViewProvider'; +import SpectralPlotViewProvider from './spectralPlot/SpectralPlotViewProvider'; +import BarGraphViewProvider from './barGraph/BarGraphViewProvider'; import OverlayPlotViewProvider from './overlayPlot/OverlayPlotViewProvider'; import StackedPlotViewProvider from './stackedPlot/StackedPlotViewProvider'; import PlotsInspectorViewProvider from './inspector/PlotsInspectorViewProvider'; +import BarGraphInspectorViewProvider from './barGraph/inspector/BarGraphInspectorViewProvider'; import OverlayPlotCompositionPolicy from './overlayPlot/OverlayPlotCompositionPolicy'; import StackedPlotCompositionPolicy from './stackedPlot/StackedPlotCompositionPolicy'; +import SpectralPlotCompositionPolicy from './spectralPlot/SpectralPlotCompositionPolicy'; +import BarGraphCompositionPolicy from './barGraph/BarGraphCompositionPolicy'; export default function () { return function install(openmct) { @@ -59,13 +64,48 @@ 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.", + //Temporarily disabling spectral plots + creatable: false, + initialize: function (domainObject) { + domainObject.composition = []; + domainObject.configuration = {}; + }, + priority: 890 + }); + + openmct.types.addType(BAR_GRAPH_KEY, { + key: BAR_GRAPH_KEY, + name: "Bar Graph", + cssClass: "icon-bar-chart", + description: "View data as a bar graph. Can be added to Display Layouts.", + creatable: true, + initialize: function (domainObject) { + domainObject.composition = []; + domainObject.configuration = { + 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 BarGraphViewProvider(openmct)); + openmct.inspectorViews.addProvider(new PlotsInspectorViewProvider(openmct)); + openmct.inspectorViews.addProvider(new BarGraphInspectorViewProvider(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 BarGraphCompositionPolicy(openmct).allow); }; } diff --git a/src/plugins/plot/pluginSpec.js b/src/plugins/plot/pluginSpec.js index 96dc95b68..15342bcbe 100644 --- a/src/plugins/plot/pluginSpec.js +++ b/src/plugins/plot/pluginSpec.js @@ -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 configStore from "./configuration/configStore"; +// 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 { BAR_GRAPH_VIEW, BAR_GRAPH_KEY } from './barGraph/BarGraphConstants'; 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: BAR_GRAPH_KEY, + telemetry: { + values: [{ + key: "lots-of-aggregate-telemetry" + }] + } + }; + + const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath); + let plotView = applicableViews.find((viewProvider) => viewProvider.key === BAR_GRAPH_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; @@ -963,7 +1137,7 @@ describe("the plugin", function () { expandControl.dispatchEvent(clickEvent); const plotOptionsProperties = editOptionsEl.querySelectorAll(".js-plot-options-edit-properties .grid-row"); - expect(plotOptionsProperties.length).toEqual(7); + expect(plotOptionsProperties.length).toEqual(8); }); it('shows yKeyOptions', () => { @@ -991,4 +1165,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); + }); + + xit('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: BAR_GRAPH_KEY, + creatable: true + }; + + it('defines a spectral plot object type with the correct key', () => { + const objectDef = openmct.types.get(BAR_GRAPH_KEY).definition; + expect(objectDef.key).toEqual(mockObject.key); + }); + + it('is creatable', () => { + const objectDef = openmct.types.get(BAR_GRAPH_KEY).definition; + expect(objectDef.creatable).toEqual(mockObject.creatable); + }); + }); }); diff --git a/src/plugins/plot/spectralPlot/SpectralPlotCompositionPolicy.js b/src/plugins/plot/spectralPlot/SpectralPlotCompositionPolicy.js new file mode 100644 index 000000000..681e0c140 --- /dev/null +++ b/src/plugins/plot/spectralPlot/SpectralPlotCompositionPolicy.js @@ -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; + } + }; +} diff --git a/src/plugins/plot/spectralPlot/SpectralPlotViewProvider.js b/src/plugins/plot/spectralPlot/SpectralPlotViewProvider.js new file mode 100644 index 000000000..0634ddc7c --- /dev/null +++ b/src/plugins/plot/spectralPlot/SpectralPlotViewProvider.js @@ -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; + } + }; + } + }; +} diff --git a/src/plugins/plot/spectralPlot/SpectralView.vue b/src/plugins/plot/spectralPlot/SpectralView.vue new file mode 100644 index 000000000..d3345fdda --- /dev/null +++ b/src/plugins/plot/spectralPlot/SpectralView.vue @@ -0,0 +1,13 @@ +<template> +<div> + +</div> +</template> + +<script> + +export default { + inject: ['openmct', 'domainObject'] +}; + +</script> diff --git a/src/styles/_legacy-plots.scss b/src/styles/_legacy-plots.scss index 7bf59300a..0a95af0bb 100644 --- a/src/styles/_legacy-plots.scss +++ b/src/styles/_legacy-plots.scss @@ -729,6 +729,12 @@ mct-plot { } +/********************************************************************* BAR CHARTS */ +.c-bar-chart { + flex: 1 1 auto; + overflow: hidden; +} + /***************** CURSOR GUIDES */ [class*='c-cursor-guide'] { box-shadow: $shdwCursorGuide; diff --git a/src/styles/plotly.scss b/src/styles/plotly.scss index 073bb2d40..dbd13f4fb 100644 --- a/src/styles/plotly.scss +++ b/src/styles/plotly.scss @@ -40,6 +40,11 @@ } } + .zerolinelayer { + // Hide unneeded plotly-styled horizontal line + display: none; + } + path.xy2-y { stroke: $colorPlotHash !important; // Using this instead of $colorPlotAreaBorder because that is an rgba opacity: $opacityPlotHash !important; |