diff --git a/example/generator/GeneratorMetadataProvider.js b/example/generator/GeneratorMetadataProvider.js index 179019b0d9..112166117b 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 0000000000..dcd75c89e0 --- /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 0000000000..4011bf90d8 --- /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 d0200650cc..563dbda690 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; + 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; + } - 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 +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 a7a027e32e..e985722006 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 35c6b4a147..c8c96e1aad 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 0000000000..77b305e925 --- /dev/null +++ b/src/plugins/plot/ColorSwatch.vue @@ -0,0 +1,170 @@ + + + + diff --git a/src/plugins/plot/MctPlot.vue b/src/plugins/plot/MctPlot.vue index 16917ad22e..d801479be5 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 ef7388f6e2..fa1a0f3987 100644 --- a/src/plugins/plot/MctTicks.vue +++ b/src/plugins/plot/MctTicks.vue @@ -77,7 +77,7 @@ + diff --git a/src/plugins/plot/barGraph/BarGraphView.vue b/src/plugins/plot/barGraph/BarGraphView.vue new file mode 100644 index 0000000000..a7fce76754 --- /dev/null +++ b/src/plugins/plot/barGraph/BarGraphView.vue @@ -0,0 +1,286 @@ + + + + + diff --git a/src/plugins/plot/barGraph/BarGraphViewProvider.js b/src/plugins/plot/barGraph/BarGraphViewProvider.js new file mode 100644 index 0000000000..9ddbb8c9fc --- /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: '' + }); + }, + 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 0000000000..6888dc1cd0 --- /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: '' + }); + }, + 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 0000000000..04bd6801d6 --- /dev/null +++ b/src/plugins/plot/barGraph/inspector/BarGraphOptions.vue @@ -0,0 +1,107 @@ + + + + diff --git a/src/plugins/plot/barGraph/inspector/Options.vue b/src/plugins/plot/barGraph/inspector/Options.vue new file mode 100644 index 0000000000..72528fd390 --- /dev/null +++ b/src/plugins/plot/barGraph/inspector/Options.vue @@ -0,0 +1,63 @@ + + + + diff --git a/src/plugins/plot/chart/MctChart.vue b/src/plugins/plot/chart/MctChart.vue index e6db5cd439..a480331f2e 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 similarity index 100% rename from src/plugins/plot/configuration/configStore.js rename to src/plugins/plot/configuration/ConfigStore.js diff --git a/src/plugins/plot/configuration/PlotSeries.js b/src/plugins/plot/configuration/PlotSeries.js index 7863dcdeb5..2d33eeaede 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 acb3bbbe28..2ab893ff60 100644 --- a/src/plugins/plot/inspector/PlotOptionsBrowse.vue +++ b/src/plugins/plot/inspector/PlotOptionsBrowse.vue @@ -115,7 +115,7 @@ diff --git a/src/styles/_legacy-plots.scss b/src/styles/_legacy-plots.scss index 7bf59300ab..0a95af0bb5 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 073bb2d40f..dbd13f4fb9 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;