From 5726fe6313113b38ec22101efa761603acb08d1d Mon Sep 17 00:00:00 2001 From: Pete Richards Date: Fri, 2 Mar 2018 14:29:34 -0800 Subject: [PATCH] new-plot import (#1557) Merge of new plot * Introduces new Plot object and view * Removes Old Plot * Add LAD support and state type to generators * Removes Telemetry Panel Type * Telemetry API Updates * UTCFormat.parse: passthrough numbers * TelemetryAPI: default request arguments * TelemetryAPI: fix enum formatting * Markup and styling to support new plots --- example/generator/GeneratorProvider.js | 10 +- example/generator/StateGeneratorProvider.js | 80 ++++ example/generator/generatorWorker.js | 41 +- example/generator/plugin.js | 93 +++- example/imagery/plugin.js | 15 +- .../res/sass/_style-guide-base.scss | 6 +- karma.conf.js | 3 +- openmct.js | 1 + .../commonUI/general/res/sass/_constants.scss | 2 +- .../commonUI/general/res/sass/_global.scss | 8 +- .../commonUI/general/res/sass/_icons.scss | 19 +- .../commonUI/general/res/sass/_inspector.scss | 108 ++++- platform/commonUI/general/res/sass/_main.scss | 1 + .../commonUI/general/res/sass/_views.scss | 1 + .../general/res/sass/controls/_controls.scss | 20 + .../general/res/sass/controls/_messages.scss | 4 - .../general/res/sass/controls/_palette.scss | 105 +++-- .../general/res/sass/mobile/_tree.scss | 13 +- .../general/res/sass/plots/_legend.scss | 208 +++++++++ .../general/res/sass/plots/_plots-main.scss | 97 ++-- .../commonUI/general/res/sass/tree/_tree.scss | 10 +- .../commonUI/general/src/ui/TreeNodeView.js | 4 +- .../themes/espresso/res/sass/_constants.scss | 2 + .../themes/snow/res/sass/_constants.scss | 2 + platform/features/layout/bundle.js | 40 -- platform/features/plot/README.md | 37 -- platform/features/plot/bundle.js | 157 ------- .../res/templates/plot-options-browse.html | 70 --- .../features/plot/res/templates/plot.html | 165 ------- platform/features/plot/src/Canvas2DChart.js | 117 ----- platform/features/plot/src/GLChart.js | 160 ------- platform/features/plot/src/MCTChart.js | 250 ---------- platform/features/plot/src/PlotController.js | 437 ------------------ .../plot/src/PlotOptionsController.js | 195 -------- platform/features/plot/src/PlotOptionsForm.js | 150 ------ platform/features/plot/src/SubPlot.js | 415 ----------------- platform/features/plot/src/SubPlotFactory.js | 59 --- .../features/plot/src/elements/PlotAxis.js | 134 ------ .../plot/src/elements/PlotLimitTracker.js | 78 ---- .../features/plot/src/elements/PlotLine.js | 118 ----- .../plot/src/elements/PlotLineBuffer.js | 268 ----------- .../features/plot/src/elements/PlotPalette.js | 133 ------ .../plot/src/elements/PlotPanZoomStack.js | 141 ------ .../src/elements/PlotPanZoomStackGroup.js | 167 ------- .../plot/src/elements/PlotPosition.js | 95 ---- .../plot/src/elements/PlotPreparer.js | 153 ------ .../plot/src/elements/PlotSeriesWindow.js | 80 ---- .../src/elements/PlotTelemetryFormatter.js | 76 --- .../plot/src/elements/PlotTickGenerator.js | 102 ---- .../features/plot/src/elements/PlotUpdater.js | 353 -------------- .../plot/src/modes/PlotModeOptions.js | 155 ------- .../plot/src/modes/PlotOverlayMode.js | 88 ---- .../features/plot/src/modes/PlotStackMode.js | 104 ----- .../features/plot/test/Canvas2DChartSpec.js | 95 ---- platform/features/plot/test/GLChartSpec.js | 143 ------ platform/features/plot/test/MCTChartSpec.js | 216 --------- .../features/plot/test/PlotControllerSpec.js | 403 ---------------- .../plot/test/PlotOptionsControllerSpec.js | 147 ------ .../features/plot/test/SubPlotFactorySpec.js | 66 --- platform/features/plot/test/SubPlotSpec.js | 208 --------- .../plot/test/elements/PlotAxisSpec.js | 107 ----- .../test/elements/PlotLimitTrackerSpec.js | 100 ---- .../plot/test/elements/PlotLineBufferSpec.js | 167 ------- .../plot/test/elements/PlotLineSpec.js | 133 ------ .../plot/test/elements/PlotPaletteSpec.js | 123 ----- .../elements/PlotPanZoomStackGroupSpec.js | 126 ----- .../test/elements/PlotPanZoomStackSpec.js | 99 ---- .../plot/test/elements/PlotPositionSpec.js | 67 --- .../plot/test/elements/PlotPreparerSpec.js | 93 ---- .../test/elements/PlotSeriesWindowSpec.js | 93 ---- .../elements/PlotTelemetryFormatterSpec.js | 71 --- .../test/elements/PlotTickGeneratorSpec.js | 73 --- .../plot/test/elements/PlotUpdaterSpec.js | 237 ---------- .../plot/test/modes/PlotModeOptionsSpec.js | 87 ---- .../plot/test/modes/PlotOverlayModeSpec.js | 184 -------- .../plot/test/modes/PlotStackModeSpec.js | 179 ------- .../plot/test/policies/PlotViewPolicySpec.js | 123 ----- .../test/services/ExportImageServiceSpec.js | 146 ------ platform/telemetry/src/TelemetryCapability.js | 3 + src/api/telemetry/LegacyTelemetryProvider.js | 4 +- src/api/telemetry/TelemetryMetadataManager.js | 12 + src/api/telemetry/TelemetryValueFormatter.js | 13 +- src/defaultRegistry.js | 2 - src/plugins/plot/plugin.js | 250 ++++++++++ src/plugins/plot/res/templates/mct-plot.html | 210 +++++++++ .../res/templates/plot-options-browse.html | 130 ++++++ .../plot/res/templates/plot-options-edit.html | 229 +++++++++ .../plot/res/templates/plot-options.html | 31 ++ src/plugins/plot/res/templates/plot.html | 50 ++ .../plot/res/templates/stacked-plot.html | 53 +++ .../plugins/plot/src}/PlotViewPolicy.js | 4 +- .../plot/src/chart/MCTChartAlarmPointSet.js | 77 +++ .../plot/src/chart/MCTChartController.js | 400 ++++++++++++++++ .../plot/src/chart/MCTChartDirective.js | 67 +++ .../plot/src/chart/MCTChartLineLinear.js | 40 ++ .../plot/src/chart/MCTChartLineStepAfter.js | 78 ++++ .../plot/src/chart/MCTChartPointSet.js | 40 ++ .../plot/src/chart/MCTChartSeriesElement.js | 162 +++++++ .../plot/src/configuration/Collection.js | 133 ++++++ .../plot/src/configuration/LegendModel.js | 61 +++ src/plugins/plot/src/configuration/Model.js | 102 ++++ .../configuration/PlotConfigurationModel.js | 134 ++++++ .../plot/src/configuration/PlotSeries.js | 357 ++++++++++++++ .../src/configuration/SeriesCollection.js | 158 +++++++ .../plot/src/configuration/XAxisModel.js | 89 ++++ .../plot/src/configuration/YAxisModel.js | 219 +++++++++ .../plot/src/configuration/configStore.js | 61 ++- src/plugins/plot/src/draw/Draw2D.js | 160 +++++++ src/plugins/plot/src/draw/DrawLoader.js | 95 ++++ src/plugins/plot/src/draw/DrawWebGL.js | 226 +++++++++ .../src/inspector/HideElementPoolDirective.js | 55 +++ .../plot/src/inspector/InspectorRegion.js | 68 +++ .../plot/src/inspector/PlotBrowseRegion.js | 40 ++ .../plot/src/inspector/PlotEditRegion.js | 18 + .../plot/src/inspector/PlotInspector.js | 39 ++ .../src/inspector/PlotOptionsController.js | 258 +++++++++++ .../plot/src/inspector/PlotOptionsForm.js | 177 +++++++ src/plugins/plot/src/inspector/Region.js | 100 ++++ src/plugins/plot/src/lib/color.js | 205 ++++++++ src/plugins/plot/src/lib/eventHelpers.js | 97 ++++ src/plugins/plot/src/lib/extend.js | 67 +++ src/plugins/plot/src/plot/LinearScale.js | 78 ++++ .../plot/src/plot/MCTPlotController.js | 339 ++++++++++++++ src/plugins/plot/src/plot/MCTPlotDirective.js | 46 ++ .../plot/src/plot/MCTTicksController.js | 248 ++++++++++ .../plot/src/plot/MCTTicksDirective.js | 43 ++ .../plot/src/services/ExportImageService.js | 105 ++--- .../plot/src/telemetry/MCTOverlayPlot.js | 37 ++ .../plot/src/telemetry/PlotController.js | 239 ++++++++++ .../src/telemetry/StackedPlotController.js | 146 ++++++ src/plugins/plugins.js | 8 +- 131 files changed, 6652 insertions(+), 8247 deletions(-) create mode 100644 example/generator/StateGeneratorProvider.js create mode 100644 platform/commonUI/general/res/sass/plots/_legend.scss delete mode 100644 platform/features/plot/README.md delete mode 100644 platform/features/plot/bundle.js delete mode 100644 platform/features/plot/res/templates/plot-options-browse.html delete mode 100644 platform/features/plot/res/templates/plot.html delete mode 100644 platform/features/plot/src/Canvas2DChart.js delete mode 100644 platform/features/plot/src/GLChart.js delete mode 100644 platform/features/plot/src/MCTChart.js delete mode 100644 platform/features/plot/src/PlotController.js delete mode 100644 platform/features/plot/src/PlotOptionsController.js delete mode 100644 platform/features/plot/src/PlotOptionsForm.js delete mode 100644 platform/features/plot/src/SubPlot.js delete mode 100644 platform/features/plot/src/SubPlotFactory.js delete mode 100644 platform/features/plot/src/elements/PlotAxis.js delete mode 100644 platform/features/plot/src/elements/PlotLimitTracker.js delete mode 100644 platform/features/plot/src/elements/PlotLine.js delete mode 100644 platform/features/plot/src/elements/PlotLineBuffer.js delete mode 100644 platform/features/plot/src/elements/PlotPalette.js delete mode 100644 platform/features/plot/src/elements/PlotPanZoomStack.js delete mode 100644 platform/features/plot/src/elements/PlotPanZoomStackGroup.js delete mode 100644 platform/features/plot/src/elements/PlotPosition.js delete mode 100644 platform/features/plot/src/elements/PlotPreparer.js delete mode 100644 platform/features/plot/src/elements/PlotSeriesWindow.js delete mode 100644 platform/features/plot/src/elements/PlotTelemetryFormatter.js delete mode 100644 platform/features/plot/src/elements/PlotTickGenerator.js delete mode 100644 platform/features/plot/src/elements/PlotUpdater.js delete mode 100644 platform/features/plot/src/modes/PlotModeOptions.js delete mode 100644 platform/features/plot/src/modes/PlotOverlayMode.js delete mode 100644 platform/features/plot/src/modes/PlotStackMode.js delete mode 100644 platform/features/plot/test/Canvas2DChartSpec.js delete mode 100644 platform/features/plot/test/GLChartSpec.js delete mode 100644 platform/features/plot/test/MCTChartSpec.js delete mode 100644 platform/features/plot/test/PlotControllerSpec.js delete mode 100644 platform/features/plot/test/PlotOptionsControllerSpec.js delete mode 100644 platform/features/plot/test/SubPlotFactorySpec.js delete mode 100644 platform/features/plot/test/SubPlotSpec.js delete mode 100644 platform/features/plot/test/elements/PlotAxisSpec.js delete mode 100644 platform/features/plot/test/elements/PlotLimitTrackerSpec.js delete mode 100644 platform/features/plot/test/elements/PlotLineBufferSpec.js delete mode 100644 platform/features/plot/test/elements/PlotLineSpec.js delete mode 100644 platform/features/plot/test/elements/PlotPaletteSpec.js delete mode 100644 platform/features/plot/test/elements/PlotPanZoomStackGroupSpec.js delete mode 100644 platform/features/plot/test/elements/PlotPanZoomStackSpec.js delete mode 100644 platform/features/plot/test/elements/PlotPositionSpec.js delete mode 100644 platform/features/plot/test/elements/PlotPreparerSpec.js delete mode 100644 platform/features/plot/test/elements/PlotSeriesWindowSpec.js delete mode 100644 platform/features/plot/test/elements/PlotTelemetryFormatterSpec.js delete mode 100644 platform/features/plot/test/elements/PlotTickGeneratorSpec.js delete mode 100644 platform/features/plot/test/elements/PlotUpdaterSpec.js delete mode 100644 platform/features/plot/test/modes/PlotModeOptionsSpec.js delete mode 100644 platform/features/plot/test/modes/PlotOverlayModeSpec.js delete mode 100644 platform/features/plot/test/modes/PlotStackModeSpec.js delete mode 100644 platform/features/plot/test/policies/PlotViewPolicySpec.js delete mode 100644 platform/features/plot/test/services/ExportImageServiceSpec.js create mode 100644 src/plugins/plot/plugin.js create mode 100644 src/plugins/plot/res/templates/mct-plot.html create mode 100644 src/plugins/plot/res/templates/plot-options-browse.html create mode 100644 src/plugins/plot/res/templates/plot-options-edit.html create mode 100644 src/plugins/plot/res/templates/plot-options.html create mode 100644 src/plugins/plot/res/templates/plot.html create mode 100644 src/plugins/plot/res/templates/stacked-plot.html rename {platform/features/plot/src/policies => src/plugins/plot/src}/PlotViewPolicy.js (95%) create mode 100644 src/plugins/plot/src/chart/MCTChartAlarmPointSet.js create mode 100644 src/plugins/plot/src/chart/MCTChartController.js create mode 100644 src/plugins/plot/src/chart/MCTChartDirective.js create mode 100644 src/plugins/plot/src/chart/MCTChartLineLinear.js create mode 100644 src/plugins/plot/src/chart/MCTChartLineStepAfter.js create mode 100644 src/plugins/plot/src/chart/MCTChartPointSet.js create mode 100644 src/plugins/plot/src/chart/MCTChartSeriesElement.js create mode 100644 src/plugins/plot/src/configuration/Collection.js create mode 100644 src/plugins/plot/src/configuration/LegendModel.js create mode 100644 src/plugins/plot/src/configuration/Model.js create mode 100644 src/plugins/plot/src/configuration/PlotConfigurationModel.js create mode 100644 src/plugins/plot/src/configuration/PlotSeries.js create mode 100644 src/plugins/plot/src/configuration/SeriesCollection.js create mode 100644 src/plugins/plot/src/configuration/XAxisModel.js create mode 100644 src/plugins/plot/src/configuration/YAxisModel.js rename platform/features/plot/test/PlotOptionsFormSpec.js => src/plugins/plot/src/configuration/configStore.js (55%) create mode 100644 src/plugins/plot/src/draw/Draw2D.js create mode 100644 src/plugins/plot/src/draw/DrawLoader.js create mode 100644 src/plugins/plot/src/draw/DrawWebGL.js create mode 100644 src/plugins/plot/src/inspector/HideElementPoolDirective.js create mode 100644 src/plugins/plot/src/inspector/InspectorRegion.js create mode 100644 src/plugins/plot/src/inspector/PlotBrowseRegion.js create mode 100644 src/plugins/plot/src/inspector/PlotEditRegion.js create mode 100644 src/plugins/plot/src/inspector/PlotInspector.js create mode 100644 src/plugins/plot/src/inspector/PlotOptionsController.js create mode 100644 src/plugins/plot/src/inspector/PlotOptionsForm.js create mode 100644 src/plugins/plot/src/inspector/Region.js create mode 100644 src/plugins/plot/src/lib/color.js create mode 100644 src/plugins/plot/src/lib/eventHelpers.js create mode 100644 src/plugins/plot/src/lib/extend.js create mode 100644 src/plugins/plot/src/plot/LinearScale.js create mode 100644 src/plugins/plot/src/plot/MCTPlotController.js create mode 100644 src/plugins/plot/src/plot/MCTPlotDirective.js create mode 100644 src/plugins/plot/src/plot/MCTTicksController.js create mode 100644 src/plugins/plot/src/plot/MCTTicksDirective.js rename {platform/features => src/plugins}/plot/src/services/ExportImageService.js (75%) create mode 100644 src/plugins/plot/src/telemetry/MCTOverlayPlot.js create mode 100644 src/plugins/plot/src/telemetry/PlotController.js create mode 100644 src/plugins/plot/src/telemetry/StackedPlotController.js diff --git a/example/generator/GeneratorProvider.js b/example/generator/GeneratorProvider.js index d053c68f7c..1ca0b75155 100644 --- a/example/generator/GeneratorProvider.js +++ b/example/generator/GeneratorProvider.js @@ -30,7 +30,8 @@ define([ amplitude: 1, period: 10, offset: 0, - dataRateInHz: 1 + dataRateInHz: 1, + phase: 0 }; function GeneratorProvider() { @@ -50,9 +51,12 @@ define([ 'amplitude', 'period', 'offset', - 'dataRateInHz' + 'dataRateInHz', + 'phase', ]; + request = request || {}; + var workerRequest = {}; props.forEach(function (prop) { @@ -67,7 +71,7 @@ define([ } workerRequest[prop] = Number(workerRequest[prop]); }); - + workerRequest.name = domainObject.name; return workerRequest; }; diff --git a/example/generator/StateGeneratorProvider.js b/example/generator/StateGeneratorProvider.js new file mode 100644 index 0000000000..7784bb10cc --- /dev/null +++ b/example/generator/StateGeneratorProvider.js @@ -0,0 +1,80 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2017, 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 StateGeneratorProvider() { + + } + + function pointForTimestamp(timestamp, duration, name) { + return { + name: name, + utc: Math.floor(timestamp / duration) * duration, + value: Math.floor(timestamp / duration) % 2 + }; + } + + StateGeneratorProvider.prototype.supportsSubscribe = function (domainObject) { + return domainObject.type === 'example.state-generator'; + }; + + StateGeneratorProvider.prototype.subscribe = function (domainObject, callback) { + var duration = domainObject.telemetry.duration * 1000; + + var interval = setInterval(function () { + var now = Date.now(); + callback(pointForTimestamp(now, duration, domainObject.name)); + }, duration); + + return function () { + clearInterval(interval); + }; + }; + + + StateGeneratorProvider.prototype.supportsRequest = function (domainObject, options) { + return domainObject.type === 'example.state-generator'; + }; + + StateGeneratorProvider.prototype.request = function (domainObject, options) { + var start = options.start; + var end = options.end; + var duration = domainObject.telemetry.duration * 1000; + if (options.strategy === 'latest' || options.size === 1) { + start = end; + } + var data = []; + while (start <= end && data.length < 5000) { + data.push(pointForTimestamp(start, duration, domainObject.name)); + start += 5000; + } + return Promise.resolve(data); + }; + + return StateGeneratorProvider; + +}); diff --git a/example/generator/generatorWorker.js b/example/generator/generatorWorker.js index 5b45363b1d..32e9e4c5e1 100644 --- a/example/generator/generatorWorker.js +++ b/example/generator/generatorWorker.js @@ -62,10 +62,11 @@ 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), - cos: cos(nextStep, data.period, data.amplitude, data.offset) + sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase), + cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase) } }); nextStep += step; @@ -82,21 +83,22 @@ } function onRequest(message) { - var data = message.data; - if (data.end == undefined) { - data.end = Date.now(); + var request = message.data; + if (request.end == undefined) { + request.end = Date.now(); } - if (data.start == undefined){ - data.start = data.end - FIFTEEN_MINUTES; + if (request.start == undefined){ + request.start = request.end - FIFTEEN_MINUTES; } var now = Date.now(); - var start = data.start; - var end = data.end > now ? now : data.end; - var amplitude = data.amplitude; - var period = data.period; - var offset = data.offset; - var dataRateInHz = data.dataRateInHz; + var start = request.start; + var end = request.end > now ? now : request.end; + var amplitude = request.amplitude; + var period = request.period; + var offset = request.offset; + var dataRateInHz = request.dataRateInHz; + var phase = request.phase; var step = 1000 / dataRateInHz; var nextStep = start - (start % step) + step; @@ -105,10 +107,11 @@ for (; nextStep < end && data.length < 5000; nextStep += step) { data.push({ + name: request.name, utc: nextStep, yesterday: nextStep - 60*60*24*1000, - sin: sin(nextStep, period, amplitude, offset), - cos: cos(nextStep, period, amplitude, offset) + sin: sin(nextStep, period, amplitude, offset, phase), + cos: cos(nextStep, period, amplitude, offset, phase) }); } self.postMessage({ @@ -117,14 +120,14 @@ }); } - function cos(timestamp, period, amplitude, offset) { + function cos(timestamp, period, amplitude, offset, phase) { return amplitude * - Math.cos(timestamp / period / 1000 * Math.PI * 2) + offset; + Math.cos(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset; } - function sin(timestamp, period, amplitude, offset) { + function sin(timestamp, period, amplitude, offset, phase) { return amplitude * - Math.sin(timestamp / period / 1000 * Math.PI * 2) + offset; + Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset; } function sendError(error, message) { diff --git a/example/generator/plugin.js b/example/generator/plugin.js index 6a99448f5a..1039e61039 100644 --- a/example/generator/plugin.js +++ b/example/generator/plugin.js @@ -23,10 +23,12 @@ define([ "./GeneratorProvider", - "./SinewaveLimitCapability" + "./SinewaveLimitCapability", + "./StateGeneratorProvider" ], function ( GeneratorProvider, - SinewaveLimitCapability + SinewaveLimitCapability, + StateGeneratorProvider ) { var legacyExtensions = { @@ -46,6 +48,75 @@ define([ openmct.legacyExtension(type, extension) }) }); + + openmct.types.addType("example.state-generator", { + name: "State Generator", + description: "For development use. Generates test enumerated telemetry by cycling through a given set of states", + cssClass: "icon-telemetry", + creatable: true, + form: [ + { + name: "State Duration (seconds)", + control: "textfield", + cssClass: "l-input-sm l-numeric", + key: "duration", + required: true, + property: [ + "telemetry", + "duration" + ], + pattern: "^\\d*(\\.\\d*)?$" + } + ], + initialize: function (object) { + object.telemetry = { + duration: 5, + values: [ + { + key: "name", + name: "Name" + }, + { + key: "utc", + name: "Time", + format: "utc", + hints: { + domain: 1 + } + }, + { + key: "state", + source: "value", + name: "State", + format: "enum", + enumerations: [ + { + value: 0, + string: "OFF" + }, + { + value: 1, + string: "ON" + } + ], + hints: { + range: 1 + } + }, + { + key: "value", + name: "Value", + hints: { + range: 2 + } + } + ] + } + } + }); + + openmct.telemetry.addProvider(new StateGeneratorProvider()); + openmct.types.addType("generator", { name: "Sine Wave Generator", description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.", @@ -99,6 +170,18 @@ define([ "dataRateInHz" ], pattern: "^\\d*(\\.\\d*)?$" + }, + { + name: "Phase (radians)", + control: "textfield", + cssClass: "l-input-sm l-numeric", + key: "phase", + required: true, + property: [ + "telemetry", + "phase" + ], + pattern: "^\\d*(\\.\\d*)?$" } ], initialize: function (object) { @@ -107,7 +190,12 @@ define([ amplitude: 1, offset: 0, dataRateInHz: 1, + phase: 0, values: [ + { + key: "name", + name: "Name" + }, { key: "utc", name: "Time", @@ -142,6 +230,7 @@ define([ }; } }); + openmct.telemetry.addProvider(new GeneratorProvider()); }; diff --git a/example/imagery/plugin.js b/example/imagery/plugin.js index 5e30ebfc85..21d2cd7c2c 100644 --- a/example/imagery/plugin.js +++ b/example/imagery/plugin.js @@ -48,8 +48,9 @@ define([ "https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg" ]; - function pointForTimestamp(timestamp) { + function pointForTimestamp(timestamp, name) { return { + name: name, utc: Math.floor(timestamp / 5000) * 5000, url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length] }; @@ -61,7 +62,7 @@ define([ }, subscribe: function (domainObject, callback) { var interval = setInterval(function () { - callback(pointForTimestamp(Date.now())); + callback(pointForTimestamp(Date.now(), domainObject.name)); }, 5000); return function (interval) { @@ -79,8 +80,8 @@ define([ var start = options.start; var end = options.end; var data = []; - while (start < end && data.length < 5000) { - data.push(pointForTimestamp(start)); + while (start <= end && data.length < 5000) { + data.push(pointForTimestamp(start, domainObject.name)); start += 5000; } return Promise.resolve(data); @@ -93,7 +94,7 @@ define([ options.strategy === 'latest'; }, request: function (domainObject, options) { - return Promise.resolve([pointForTimestamp(Date.now())]); + return Promise.resolve([pointForTimestamp(Date.now(), domainObject.name)]); } }; @@ -109,6 +110,10 @@ define([ initialize: function (object) { object.telemetry = { values: [ + { + name: 'Name', + key: 'name' + }, { name: 'Time', key: 'utc', diff --git a/example/styleguide/res/sass/_style-guide-base.scss b/example/styleguide/res/sass/_style-guide-base.scss index 5c6cbd56c4..f64681d876 100644 --- a/example/styleguide/res/sass/_style-guide-base.scss +++ b/example/styleguide/res/sass/_style-guide-base.scss @@ -58,11 +58,7 @@ position: relative; } - .w-mct-example { - div { - margin-bottom: $interiorMarginLg; - } - } + .w-mct-example > div { margin-bottom: $interiorMarginLg; } code, pre { diff --git a/karma.conf.js b/karma.conf.js index 533ebbff3d..486ee66010 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -89,7 +89,8 @@ module.exports = function(config) { "dist/reports/coverage", check: { global: { - lines: 80 + lines: 80, + excludes: ['src/plugins/plot/**/*.js'] } } }, diff --git a/openmct.js b/openmct.js index 0dd3a54e2a..645e005201 100644 --- a/openmct.js +++ b/openmct.js @@ -101,6 +101,7 @@ define([ var openmct = new MCT(); openmct.legacyRegistry = defaultRegistry; + openmct.install(openmct.plugins.Plot()); if (typeof BUILD_CONSTANTS !== 'undefined') { openmct.install(buildInfo(BUILD_CONSTANTS)); diff --git a/platform/commonUI/general/res/sass/_constants.scss b/platform/commonUI/general/res/sass/_constants.scss index 7ab95e48bd..a50dadb31b 100644 --- a/platform/commonUI/general/res/sass/_constants.scss +++ b/platform/commonUI/general/res/sass/_constants.scss @@ -99,7 +99,7 @@ $plotXBarH: 32px; $plotLegendH: 20px; $plotSwatchD: 8px; // 1: Top, 2: right, 3: bottom, 4: left -$plotDisplayArea: ($plotLegendH + $interiorMargin, 0, $plotXBarH, $plotYBarW); +$plotDisplayArea: (0, 0, $plotXBarH, $plotYBarW); /* min plot height is based on user testing to find minimum useful height */ $plotMinH: 95px; /*************** Bubbles */ diff --git a/platform/commonUI/general/res/sass/_global.scss b/platform/commonUI/general/res/sass/_global.scss index dc4b9ee735..8bcfeab8af 100644 --- a/platform/commonUI/general/res/sass/_global.scss +++ b/platform/commonUI/general/res/sass/_global.scss @@ -40,7 +40,7 @@ * Use https://icomoon.io/app with icomoon-project-openmct-symbols-12px.json * to generate font files */ - font-family: 'symbolsfont 12px'; + font-family: 'symbolsfont-12px'; src: url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.eot'); src: url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.eot?#iefix') format('embedded-opentype'), url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.woff') format('woff'), @@ -248,6 +248,12 @@ a.disabled { color: rgba(#fff, 0.2); } +.comma-list span { + &:not(:first-child) { + &:before { content: ', '; } + } +} + .test-stripes { @include bgDiagonalStripes(); } diff --git a/platform/commonUI/general/res/sass/_icons.scss b/platform/commonUI/general/res/sass/_icons.scss index 0bb55c6744..e7326ba606 100644 --- a/platform/commonUI/general/res/sass/_icons.scss +++ b/platform/commonUI/general/res/sass/_icons.scss @@ -44,6 +44,12 @@ } } + +.t-alert-unsynced { + @extend .icon-alert-triangle; + color: $colorPausedBg; +} + .bar .ui-symbol { display: inline-block; } @@ -81,18 +87,5 @@ @include transform(scale(0.3)); z-index: 2; } - -/* .t-item-icon-glyph { - &:after { - color: $colorIconLink; - content: '\e921'; //$glyph-icon-link; - height: auto; width: auto; - position: absolute; - left: 0; top: 0; right: 0; bottom: 20%; - @include transform-origin(bottom left); - @include transform(scale(0.3)); - z-index: 2; - } - }*/ } } diff --git a/platform/commonUI/general/res/sass/_inspector.scss b/platform/commonUI/general/res/sass/_inspector.scss index 79644a78b7..1e9e8d8e26 100644 --- a/platform/commonUI/general/res/sass/_inspector.scss +++ b/platform/commonUI/general/res/sass/_inspector.scss @@ -53,6 +53,7 @@ .l-inspector-part { box-sizing: border-box; padding-right: $interiorMargin; + .tree .form { margin-left: $treeVCW + $interiorMarginLg; } @@ -78,6 +79,7 @@ } } .form-row { + // To be replaced with .inspector-config, see below. @include align-items(center); border: none !important; margin-bottom: 0 !important; @@ -99,15 +101,12 @@ position: relative; } - ul li { - margin-bottom: $interiorMarginLg; - } - em.t-inspector-part-header { border-radius: $basicCr; background-color: $colorInspectorSectionHeaderBg; color: $colorInspectorSectionHeaderFg; - margin-bottom: $interiorMargin; + margin-top: $interiorMarginLg; + //margin-bottom: $interiorMargin; padding: floor($formTBPad * .75) $formLRPad; text-transform: uppercase; } @@ -201,3 +200,102 @@ mct-representation:not(.s-status-editing) .l-inspect { pointer-events: inherit; } } + +// NEW COMPACT FORM, FOR USE IN INSPECTOR +// ul > li > label, control +// Make a new UL for each form section +// Allow control-first, controls-below + +.l-inspect .tree ul li, +.inspector-config ul li { + padding: 2px 0; +} + + +.inspector-config { + $labelW: 40%; + $minW: $labelW; + ul { + margin-bottom: $interiorMarginLg; + li { + @include display(flex); + @include flex-wrap(wrap); + @include align-items(center); + label, + .control { + @include display(flex); + min-width: $minW; + } + label { + line-height: inherit; + padding: $interiorMarginSm 0; + width: $labelW; + } + .control { + @include flex-grow(1); + } + + &:not(.section-header) { + &:not(.connects-to-previous) { + //border-top: 1px solid $colorFormLines; + } + } + + &.connects-to-previous { + padding-top: 0 !important; + } + + &.section-header { + margin-top: $interiorMarginLg; + border-top: 1px solid $colorFormLines; + } + + &.controls-first { + .control { + @include flex-grow(0); + margin-right: $interiorMargin; + min-width: 0; + order: 1; + width: auto; + } + label { + @include flex-grow(1); + order: 2; + width: auto; + } + } + &.controls-under { + display: block; + .control, label { + display: block; + width: auto; + } + + ul li { + border-top: none !important; + padding: 0; + } + } + } + } + + .form-error { + // Block element that visually flags an error and contains a message + background-color: $colorFormFieldErrorBg; + color: $colorFormFieldErrorFg; + border-radius: $basicCr; + display: block; + padding: 1px 6px; + &:before { + content: $glyph-icon-alert-triangle; + display: inline; + font-family: symbolsfont; + margin-right: $interiorMarginSm; + } + } +} + +.tree .inspector-config { + margin-left: $treeVCW + $interiorMarginLg; +} + diff --git a/platform/commonUI/general/res/sass/_main.scss b/platform/commonUI/general/res/sass/_main.scss index 28465cf49f..395b54eb6c 100644 --- a/platform/commonUI/general/res/sass/_main.scss +++ b/platform/commonUI/general/res/sass/_main.scss @@ -70,6 +70,7 @@ @import "fixed-position"; @import "lists/tabular"; @import "plots/plots-main"; +@import "plots/legend"; @import "iframe"; @import "views"; @import "items/item"; diff --git a/platform/commonUI/general/res/sass/_views.scss b/platform/commonUI/general/res/sass/_views.scss index 32094db89d..5ba1862eb3 100644 --- a/platform/commonUI/general/res/sass/_views.scss +++ b/platform/commonUI/general/res/sass/_views.scss @@ -5,6 +5,7 @@ } .l-view-section { + //@include test(orange, 0.1); @include absPosDefault(0); h2 { color: #fff; diff --git a/platform/commonUI/general/res/sass/controls/_controls.scss b/platform/commonUI/general/res/sass/controls/_controls.scss index f553324ed6..dbc5868868 100644 --- a/platform/commonUI/general/res/sass/controls/_controls.scss +++ b/platform/commonUI/general/res/sass/controls/_controls.scss @@ -150,6 +150,26 @@ } } +/******************************************************** VIEW CONTROLS */ +// Expand/collapse > and v arrows, used in tree and plot legend +// Moved this over from a tree-only context 5/18/17 + +.view-control { + @extend .ui-symbol; + cursor: pointer; + height: 1em; width: 1em; + line-height: inherit; + &:before { + position: absolute; + @include trans-prop-nice(transform, 100ms); + content: $glyph-icon-arrow-right; + @include transform-origin(center); + } + &.expanded:before { + @include transform(rotate(90deg)); + } +} + /******************************************************** CUSTOM CHECKBOXES */ label.checkbox.custom, label.radio.custom { diff --git a/platform/commonUI/general/res/sass/controls/_messages.scss b/platform/commonUI/general/res/sass/controls/_messages.scss index c528f26703..6d362ffff7 100644 --- a/platform/commonUI/general/res/sass/controls/_messages.scss +++ b/platform/commonUI/general/res/sass/controls/_messages.scss @@ -398,10 +398,6 @@ body.desktop .t-message-list { .object-header { .t-object-alert { display: inline; - &.t-alert-unsynced { - @extend .icon-alert-triangle; - color: $colorPausedBg; - } } } } diff --git a/platform/commonUI/general/res/sass/controls/_palette.scss b/platform/commonUI/general/res/sass/controls/_palette.scss index dd3ce6d2e9..8b8542b821 100644 --- a/platform/commonUI/general/res/sass/controls/_palette.scss +++ b/platform/commonUI/general/res/sass/controls/_palette.scss @@ -20,53 +20,70 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ .l-palette { - $d: 16px; - $colorsPerRow: 10; - $m: 1; - box-sizing: border-box; padding: $interiorMargin !important; +} - .l-palette-row { - @include clearfix; - line-height: $d; - width: ($d * $colorsPerRow) + ($m * $colorsPerRow); +.l-palette-row { + $d: 16px; + $m: 1; + $colorsPerRow: 10; + display: flex; + flex-wrap: wrap; + line-height: $d; + width: ($d * $colorsPerRow) + ($m * $colorsPerRow); - &.l-option-row { - margin-bottom: $interiorMargin; - .s-palette-item { - border-color: $colorPaletteFg; + &.l-option-row { + margin-bottom: $interiorMargin; + .s-palette-item { + border-color: $colorPaletteFg; + } + } + + .l-palette-item { + box-sizing: border-box; + display: block; + height: $d; width: $d; + min-width: $d; + line-height: $d * 0.9; + margin: 0 ($m * 1px) ($m * 1px) 0; + position: relative; + text-align: center; + } +} + +.s-palette-item { + border: 1px solid transparent; + color: $colorPaletteFg; + text-shadow: $shdwPaletteFg; + @include trans-prop-nice-fade(0.25s); + &:hover { + @include trans-prop-nice-fade(0); + border-color: $colorPaletteSelected !important; + } + &.selected { + border-color: $colorPaletteSelected; + box-shadow: $shdwPaletteSelected; //Needed to see selection rect on light colored swatches + } +} + +.l-palette-item-label { + margin-left: $interiorMargin; +} + +.l-inline-palette { + .l-palette-row { + width: 100%; + .l-palette-item { + //@include display(flex); + @include flex(1 0 auto); + margin: 1px; + min-width: auto; + width: auto; + &:before { + content: ''; + padding-top: 75%; } } - - .l-palette-item { - box-sizing: border-box; - display: block; - float: left; - height: $d; width: $d; - line-height: $d * 0.9; - margin: 0 ($m * 1px) ($m * 1px) 0; - position: relative; - text-align: center; - } - - .s-palette-item { - border: 1px solid transparent; - color: $colorPaletteFg; - text-shadow: $shdwPaletteFg; - @include trans-prop-nice-fade(0.25s); - &:hover { - @include trans-prop-nice-fade(0); - border-color: $colorPaletteSelected !important; - } - &.selected { - border-color: $colorPaletteSelected; - box-shadow: $shdwPaletteSelected; //Needed to see selection rect on light colored swatches - } - } - - .l-palette-item-label { - margin-left: $interiorMargin; - } - } -} \ No newline at end of file + } +} diff --git a/platform/commonUI/general/res/sass/mobile/_tree.scss b/platform/commonUI/general/res/sass/mobile/_tree.scss index 7995f9eb96..891dd0a17e 100644 --- a/platform/commonUI/general/res/sass/mobile/_tree.scss +++ b/platform/commonUI/general/res/sass/mobile/_tree.scss @@ -34,18 +34,7 @@ body.touch { line-height: $mobileTreeItemH !important; .view-control { font-size: 1em; - margin-right: $interiorMargin; - width: ceil($mobileTreeItemH * 0.75); - &.has-children { - &:before { - content: $glyph-icon-arrow-down; - left: 50%; - @include transform(translateX(-50%) rotate(-90deg)); - } - &.expanded:before { - @include transform(translateX(-50%) rotate(0deg)); - } - } + width: ceil($mobileTreeItemH * 0.5); } .t-object-label { line-height: inherit; diff --git a/platform/commonUI/general/res/sass/plots/_legend.scss b/platform/commonUI/general/res/sass/plots/_legend.scss new file mode 100644 index 0000000000..9522868510 --- /dev/null +++ b/platform/commonUI/general/res/sass/plots/_legend.scss @@ -0,0 +1,208 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2017, 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. + *****************************************************************************/ +.gl-plot { + .gl-plot-legend { + min-height: $plotLegendH; + + .view-control { + font-size: 1em; + margin-right: $interiorMarginSm; + } + + table { + table-layout: fixed; + tr { + display: table-row; + } + th, + td { + @include ellipsize(); // Note: this won't work if table-layout uses anything other than fixed. + display: table-cell; + padding: 1px 3px; // Tighter than standard tabular padding + } + } + + &.hover-on-plot { + // User is hovering over the plot to get a value at a point + .hover-value-enabled { + background-color: $legendHoverValueBg; + border-radius: $smallCr; + padding: 0 $interiorMarginSm; + &:before { + opacity: 0.5; + } + &.cursor-hover, + .value-to-display-nearestTimestamp, + .value-to-display-nearestValue + { + @extend .icon-crosshair-12px; + &:before { + font-size: 9px; + } + } + + &.value-to-display-min:before { + content: 'MIN '; + } + &.value-to-display-max:before { + content: 'MAX '; + } + } + } + } + + &.plot-legend-collapsed .plot-wrapper-expanded-legend { display: none; } + &.plot-legend-expanded .plot-wrapper-collapsed-legend { display: none; } + + /***************** GENERAL STYLES, ALL STATES */ + .plot-legend-item { + // General styles for legend items, both expanded and collapsed legend states + .plot-series-color-swatch { + border-radius: $smallCr; + border: 1px solid $colorBodyBg; + display: inline-block; + height: $plotSwatchD; + width: $plotSwatchD; + } + .plot-series-name { + display: inline; + } + + .plot-series-value { + @include ellipsize(); + } + } + + /***************** GENERAL STYLES, COLLAPSED */ + &.plot-legend-collapsed { + // .plot-legend-item is a span of spans. + &.plot-legend-top .gl-plot-legend { margin-bottom: $interiorMargin; } + &.plot-legend-bottom .gl-plot-legend { margin-top: $interiorMargin; } + &.plot-legend-right .gl-plot-legend { margin-left: $interiorMargin; } + &.plot-legend-left .gl-plot-legend { margin-right: $interiorMargin; } + + .plot-legend-item { + display: flex; + align-items: center; + &:not(:first-child) { + margin-left: $interiorMarginLg; + } + .plot-series-swatch-and-name, + .plot-series-value { + @include ellipsize(); + flex: 1 1 auto; + } + + .plot-series-swatch-and-name { + margin-right: $interiorMarginSm; + } + + .plot-series-value { + text-align: left; + width: 170px; + } + } + } + + /***************** GENERAL STYLES, EXPANDED */ + &.plot-legend-expanded { + .gl-plot-legend { + max-height: 70%; + } + + .plot-wrapper-expanded-legend { + overflow-y: auto; + } + + &.plot-legend-top .gl-plot-legend { + margin-bottom: $interiorMargin; + } + &.plot-legend-bottom .gl-plot-legend { + margin-top: $interiorMargin; + } + } + + /***************** TOP OR BOTTOM */ + &.plot-legend-top, + &.plot-legend-bottom { + // General styles when legend is on the top or bottom + @extend .l-flex-col; + &.plot-legend-collapsed { + // COLLAPSED ON TOP OR BOTTOM + .plot-wrapper-collapsed-legend { + display: flex; + flex: 1 1 auto; + overflow: hidden; + } + } + } + + /***************** EITHER SIDE */ + &.plot-legend-left, + &.plot-legend-right { + @extend .l-flex-row; + // If the legend is expanded, use flex-col instead so that the legend gets the width it needs. + &.plot-legend-expanded { + // EXPANDED, ON EITHER SIDE + @extend .l-flex-col; + } + + &.plot-legend-collapsed { + // COLLAPSED, ON EITHER SIDE + .gl-plot-legend { + max-height: inherit; + width: 25%; + } + .plot-wrapper-collapsed-legend { + display: flex; + flex-flow: column nowrap; + min-width: 0; + flex: 1 1 auto; + overflow-y: auto; + } + .plot-legend-item { + margin-bottom: 1px; + margin-left: 0; + flex-wrap: wrap; + .plot-series-swatch-and-name { + flex: 0 1 auto; + min-width: 20%; + } + .plot-series-value { + flex: 0 1 auto; + width: auto; + } + } + } + } + + /***************** ON BOTTOM OR RIGHT */ + &.plot-legend-right:not(.plot-legend-expanded), + &.plot-legend-bottom { + .gl-plot-legend { + order: 2; + } + .plot-wrapper-axis-and-display-area { + order: 1; + } + } +} diff --git a/platform/commonUI/general/res/sass/plots/_plots-main.scss b/platform/commonUI/general/res/sass/plots/_plots-main.scss index deac69ad88..887decba17 100644 --- a/platform/commonUI/general/res/sass/plots/_plots-main.scss +++ b/platform/commonUI/general/res/sass/plots/_plots-main.scss @@ -20,18 +20,64 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ .abs.holder-plot { - // Fend off the scrollbar when less than min-height; - right: $interiorMargin; + right: $interiorMargin; // Fend off the scrollbar when less than min-height; + .t-object-alert.t-alert-unsynced { + display: none; + } } +/********************************************* STACKED PLOT LAYOUT */ +.t-plot-stacked { + .l-view-section { + // Make this a flex container + display: flex; + flex-flow: column nowrap; + .gl-plot.child-frame { + mct-plot { + display: flex; + flex: 1 1 auto; + height: 100%; + position: relative; + } + flex: 1 1 auto; + &:not(:first-child) { + margin-top: $interiorMargin; + } + } + } + + .s-status-timeconductor-unsynced .holder-plot { + .t-object-alert.t-alert-unsynced { + display: block; + } + } + +} + + + .gl-plot { color: $colorPlotFg; + display: flex; font-size: 0.7rem; position: relative; width: 100%; height: 100%; min-height: $plotMinH; + /********************************************* AXIS AND DISPLAY AREA */ + .plot-wrapper-axis-and-display-area { + margin-top: $interiorMargin; // Keep the top tick label from getting clipped + position: relative; + flex: 1 1 auto; + .t-object-alert { + position: absolute; + display: block; + font-size: 1.5em; + top: $interiorMarginSm; left: $interiorMarginSm; + } + } + .gl-plot-wrapper-display-area-and-x-axis { // Holds the plot area and the X-axis only position: absolute; @@ -49,7 +95,6 @@ } .gl-plot-axis-area.gl-plot-x { - //@include test(green); top: auto; right: 0; bottom: 0; @@ -63,7 +108,7 @@ .gl-plot-axis-area { position: absolute; &.gl-plot-y { - top: $plotLegendH + $interiorMargin; + top: nth($plotDisplayArea, 1); right: auto; bottom: nth($plotDisplayArea, 3); left: 0; @@ -158,17 +203,6 @@ } } - .gl-plot-legend { - position: absolute; - top: 0; - right: 0; - bottom: auto; - left: 0; - height: $plotLegendH; - overflow-x: hidden; - overflow-y: auto; - } - /****************************** Limits and Out-of-Bounds data */ .l-limit-bar, @@ -235,39 +269,6 @@ border: 1px solid $colorPlotAreaBorder; } -.gl-plot-legend, -.legend { - .plot-legend-item, - .legend-item { - display: inline-block; - margin-right: $interiorMarginLg; - margin-bottom: $interiorMarginSm; - span { - vertical-align: middle; - } - .plot-color-swatch, - .color-swatch { - border-radius: 2px; - display: inline-block; - height: $plotSwatchD; - width: $plotSwatchD; - } - } -} - -.gl-plot-legend { - .plot-legend-item { - border-radius: $smallCr; - line-height: 1.5em; - padding: 0px $itemPadLR; - .plot-color-swatch { - border: 1px solid $colorBodyBg; - height: $plotSwatchD + 1; - width: $plotSwatchD + 1; - } - } -} - .tick { position: absolute; border: 0 $colorPlotHash solid; diff --git a/platform/commonUI/general/res/sass/tree/_tree.scss b/platform/commonUI/general/res/sass/tree/_tree.scss index 24e4bd0904..ba8be980a9 100644 --- a/platform/commonUI/general/res/sass/tree/_tree.scss +++ b/platform/commonUI/general/res/sass/tree/_tree.scss @@ -23,7 +23,7 @@ ul.tree { @include menuUlReset(); @include user-select(none); - li { + > li { display: block; position: relative; } @@ -53,12 +53,10 @@ ul.tree { .view-control { color: $colorItemTreeVC; margin-right: $interiorMargin; - height: 100%; - line-height: inherit; width: $treeVCW; - &:before { display: none; } - &.has-children { - &:before { display: block; } + &:before { display: block; } + &.no-children { + &:before { display: none; } } } diff --git a/platform/commonUI/general/src/ui/TreeNodeView.js b/platform/commonUI/general/src/ui/TreeNodeView.js index 7c43d0d5f0..1faacc711f 100644 --- a/platform/commonUI/general/src/ui/TreeNodeView.js +++ b/platform/commonUI/general/src/ui/TreeNodeView.js @@ -83,9 +83,9 @@ define([ this.activeObject = domainObject; if (domainObject && domainObject.hasCapability('composition')) { - $(this.toggleView.elements()).addClass('has-children'); + $(this.toggleView.elements()).removeClass('no-children'); } else { - $(this.toggleView.elements()).removeClass('has-children'); + $(this.toggleView.elements()).addClass('no-children'); } if (domainObject && domainObject.hasCapability('status')) { diff --git a/platform/commonUI/themes/espresso/res/sass/_constants.scss b/platform/commonUI/themes/espresso/res/sass/_constants.scss index c1186c117f..0bc2948982 100644 --- a/platform/commonUI/themes/espresso/res/sass/_constants.scss +++ b/platform/commonUI/themes/espresso/res/sass/_constants.scss @@ -181,6 +181,8 @@ $colorPlotHash: $colorTick; $stylePlotHash: dashed; $colorPlotAreaBorder: $colorInteriorBorder; $colorPlotLabelFg: pushBack($colorPlotFg, 20%); +$legendCollapsedNameMaxW: 50%; +$legendHoverValueBg: rgba($colorBodyFg, 0.1); // Tree $colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent); diff --git a/platform/commonUI/themes/snow/res/sass/_constants.scss b/platform/commonUI/themes/snow/res/sass/_constants.scss index 674886c829..69141eef32 100644 --- a/platform/commonUI/themes/snow/res/sass/_constants.scss +++ b/platform/commonUI/themes/snow/res/sass/_constants.scss @@ -181,6 +181,8 @@ $colorPlotHash: $colorTick; $stylePlotHash: dashed; $colorPlotAreaBorder: $colorInteriorBorder; $colorPlotLabelFg: pushBack($colorPlotFg, 20%); +$legendCollapsedNameMaxW: 50%; +$legendHoverValueBg: rgba($colorBodyFg, 0.2); // Tree $colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent); diff --git a/platform/features/layout/bundle.js b/platform/features/layout/bundle.js index d7bb565d08..543aee19d5 100644 --- a/platform/features/layout/bundle.js +++ b/platform/features/layout/bundle.js @@ -337,46 +337,6 @@ define([ "conversion": "number[]" } ] - }, - { - "key": "telemetry.panel", - "name": "Telemetry Panel", - "cssClass": "icon-telemetry-panel", - "description": "A panel for collecting telemetry elements.", - "priority": 899, - "delegates": [ - "telemetry" - ], - "features": "creation", - "contains": [ - { - "has": "telemetry" - } - ], - "model": { - "composition": [] - }, - "properties": [ - { - "name": "Layout Grid", - "control": "composite", - "items": [ - { - "name": "Horizontal grid (px)", - "control": "textfield", - "cssClass": "l-input-sm l-numeric" - }, - { - "name": "Vertical grid (px)", - "control": "textfield", - "cssClass": "l-input-sm l-numeric" - } - ], - "pattern": "^(\\d*[1-9]\\d*)?$", - "property": "layoutGrid", - "conversion": "number[]" - } - ] } ] } diff --git a/platform/features/plot/README.md b/platform/features/plot/README.md deleted file mode 100644 index a4a6537fe1..0000000000 --- a/platform/features/plot/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Plot README - -## Chart - -The `mct-chart` directive is used to support drawing of simple charts. It is -present to support the Plot view, and its functionality is limited to the -functionality that is relevant for that view. - -This directive is used at the element level and takes one attribute, `draw` -which is an Angular expression which will should evaluate to a drawing object. -This drawing object should contain the following properties: - -* `dimensions`: The size, in logical coordinates, of the chart area. A -two-element array or numbers. -* `origin`: The position, in logical coordinates, of the lower-left corner of -the chart area. A two-element array or numbers. -* `lines`: An array of lines (e.g. as a plot line) to draw, where each line is -expressed as an object containing: - * `buffer`: A Float32Array containing points in the line, in logical - coordinates, in sequential x,y pairs. - * `color`: The color of the line, as a four-element RGBA array, where - each element is a number in the range of 0.0-1.0. - * `points`: The number of points in the line. -* `boxes`: An array of rectangles to draw in the chart area. Each is an object -containing: - * `start`: The first corner of the rectangle, as a two-element array of - numbers, in logical coordinates. - * `end`: The opposite corner of the rectangle, as a two-element array of - numbers, in logical coordinates. color : The color of the line, as a - four-element RGBA array, where each element is a number in the range of - 0.0-1.0. - -While `mct-chart` is intended to support plots specifically, it does perform -some useful management of canvas objects (e.g. choosing between WebGL and Canvas -2D APIs for drawing based on browser support) so its usage is recommended when -its supported drawing primitives are sufficient for other charting tasks. - diff --git a/platform/features/plot/bundle.js b/platform/features/plot/bundle.js deleted file mode 100644 index 7309197c0a..0000000000 --- a/platform/features/plot/bundle.js +++ /dev/null @@ -1,157 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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([ - "./src/MCTChart", - "./src/PlotController", - "./src/policies/PlotViewPolicy", - "./src/PlotOptionsController", - "./src/services/ExportImageService", - "text!./res/templates/plot.html", - "text!./res/templates/plot-options-browse.html", - 'legacyRegistry' -], function ( - MCTChart, - PlotController, - PlotViewPolicy, - PlotOptionsController, - exportImageService, - plotTemplate, - plotOptionsBrowseTemplate, - legacyRegistry -) { - - legacyRegistry.register("platform/features/plot", { - "name": "Plot view for telemetry", - "extensions": { - "views": [ - { - "name": "Plot", - "key": "plot", - "cssClass": "icon-sine", - "template": plotTemplate, - "needs": [ - "telemetry" - ], - "priority": "preferred", - "delegation": true - } - ], - "directives": [ - { - "key": "mctChart", - "implementation": MCTChart, - "depends": [ - "$interval", - "$log" - ] - } - ], - "controllers": [ - { - "key": "PlotController", - "implementation": PlotController, - "depends": [ - "$scope", - "$element", - "exportImageService", - "telemetryFormatter", - "telemetryHandler", - "throttle", - "PLOT_FIXED_DURATION", - "openmct" - ] - }, - { - "key": "PlotOptionsController", - "implementation": PlotOptionsController, - "depends": [ - "$scope" - ] - } - ], - "services": [ - { - "key": "exportImageService", - "implementation": exportImageService, - "depends": [ - "$q", - "$timeout", - "$log", - "EXPORT_IMAGE_TIMEOUT" - ] - - } - ], - "constants": [ - { - "key": "PLOT_FIXED_DURATION", - "value": 900000, - "priority": "fallback", - "comment": "Fifteen minutes." - }, - { - "key": "EXPORT_IMAGE_TIMEOUT", - "value": 500, - "priority": "fallback" - } - ], - "policies": [ - { - "category": "view", - "implementation": PlotViewPolicy, - "depends": [ - "openmct" - ] - } - ], - "representations": [ - { - "key": "plot-options-browse", - "template": plotOptionsBrowseTemplate - } - ], - "licenses": [ - { - "name": "FileSaver.js", - "version": "0.0.2", - "author": "Eli Grey", - "description": "File download initiator (for file exports)", - "website": "https://github.com/eligrey/FileSaver.js/", - "copyright": "Copyright © 2015 Eli Grey.", - "license": "license-mit", - "link": "https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md" - }, - { - "name": "html2canvas", - "version": "0.4.1", - "author": "Niklas von Hertzen", - "description": "JavaScript HTML renderer", - "website": "https://github.com/niklasvh/html2canvas", - "copyright": "Copyright © 2012 Niklas von Hertzen.", - "license": "license-mit", - "link": "https://github.com/niklasvh/html2canvas/blob/master/LICENSE" - } - ] - } - }); -}); diff --git a/platform/features/plot/res/templates/plot-options-browse.html b/platform/features/plot/res/templates/plot-options-browse.html deleted file mode 100644 index 9dd407cb2d..0000000000 --- a/platform/features/plot/res/templates/plot-options-browse.html +++ /dev/null @@ -1,70 +0,0 @@ - -
- Plot Options - - - - -
-
- Plot Series -
-
    -
      -
    • - - - - - - - - - - - - -
    • -
    -
-
-
diff --git a/platform/features/plot/res/templates/plot.html b/platform/features/plot/res/templates/plot.html deleted file mode 100644 index 0001afe8b4..0000000000 --- a/platform/features/plot/res/templates/plot.html +++ /dev/null @@ -1,165 +0,0 @@ - - - -
-
-
- - - - {{telemetryObject.getModel().name}} - -
- -
-
- {{axes[1].active.name}} -
-
- {{tick.label | reverse}} -
-
-
- -
-
-
- -
- - -
- {{subplot.getHoverCoordinates()}} -
- -
- - - -
-
-
-
-
-
- - - -
- - - - - -
-
-
-
- {{tick.label | reverse}} -
-
- {{axes[0].active.name}} -
-
-
- -
-
- -
-
-
-
-
diff --git a/platform/features/plot/src/Canvas2DChart.js b/platform/features/plot/src/Canvas2DChart.js deleted file mode 100644 index d31155f0b1..0000000000 --- a/platform/features/plot/src/Canvas2DChart.js +++ /dev/null @@ -1,117 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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 () { - - /** - * Create a new chart which uses Canvas's 2D API for rendering. - * - * @memberof platform/features/plot - * @constructor - * @implements {platform/features/plot.Chart} - * @param {CanvasElement} canvas the canvas object to render upon - * @throws {Error} an error is thrown if Canvas's 2D API is unavailable. - */ - function Canvas2DChart(canvas) { - this.canvas = canvas; - this.c2d = canvas.getContext('2d'); - this.width = canvas.width; - this.height = canvas.height; - this.dimensions = [this.width, this.height]; - this.origin = [0, 0]; - - if (!this.c2d) { - throw new Error("Canvas 2d API unavailable."); - } - } - - // Convert from logical to physical x coordinates - Canvas2DChart.prototype.x = function (v) { - return ((v - this.origin[0]) / this.dimensions[0]) * this.width; - }; - - // Convert from logical to physical y coordinates - Canvas2DChart.prototype.y = function (v) { - return this.height - - ((v - this.origin[1]) / this.dimensions[1]) * this.height; - }; - - // Set the color to be used for drawing operations - Canvas2DChart.prototype.setColor = function (color) { - var mappedColor = color.map(function (c, i) { - return i < 3 ? Math.floor(c * 255) : (c); - }).join(','); - this.c2d.strokeStyle = "rgba(" + mappedColor + ")"; - this.c2d.fillStyle = "rgba(" + mappedColor + ")"; - }; - - - Canvas2DChart.prototype.clear = function () { - var canvas = this.canvas; - this.width = canvas.width; - this.height = canvas.height; - this.c2d.clearRect(0, 0, this.width, this.height); - }; - - Canvas2DChart.prototype.setDimensions = function (newDimensions, newOrigin) { - this.dimensions = newDimensions; - this.origin = newOrigin; - }; - - Canvas2DChart.prototype.drawLine = function (buf, color, points) { - var i; - - this.setColor(color); - - // Configure context to draw two-pixel-thick lines - this.c2d.lineWidth = 2; - - // Start a new path... - if (buf.length > 1) { - this.c2d.beginPath(); - this.c2d.moveTo(this.x(buf[0]), this.y(buf[1])); - } - - // ...and add points to it... - for (i = 2; i < points * 2; i = i + 2) { - this.c2d.lineTo(this.x(buf[i]), this.y(buf[i + 1])); - } - - // ...before finally drawing it. - this.c2d.stroke(); - }; - - Canvas2DChart.prototype.drawSquare = function (min, max, color) { - var x1 = this.x(min[0]), - y1 = this.y(min[1]), - w = this.x(max[0]) - x1, - h = this.y(max[1]) - y1; - - this.setColor(color); - this.c2d.fillRect(x1, y1, w, h); - }; - - return Canvas2DChart; - } -); diff --git a/platform/features/plot/src/GLChart.js b/platform/features/plot/src/GLChart.js deleted file mode 100644 index c7fd83752f..0000000000 --- a/platform/features/plot/src/GLChart.js +++ /dev/null @@ -1,160 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * Module defining GLPlot. Created by vwoeltje on 11/12/14. - */ -define( - [], - function () { - - // WebGL shader sources (for drawing plain colors) - var FRAGMENT_SHADER = [ - "precision mediump float;", - "uniform vec4 uColor;", - "void main(void) {", - "gl_FragColor = uColor;", - "}" - ].join('\n'), - VERTEX_SHADER = [ - "attribute vec2 aVertexPosition;", - "uniform vec2 uDimensions;", - "uniform vec2 uOrigin;", - "void main(void) {", - "gl_Position = vec4(2.0 * ((aVertexPosition - uOrigin) / uDimensions) - vec2(1,1), 0, 1);", - "}" - ].join('\n'); - - /** - * Create a new chart which uses WebGL for rendering. - * - * @memberof platform/features/plot - * @constructor - * @implements {platform/features/plot.Chart} - * @param {CanvasElement} canvas the canvas object to render upon - * @throws {Error} an error is thrown if WebGL is unavailable. - */ - function GLChart(canvas) { - var gl = canvas.getContext("webgl", { preserveDrawingBuffer: true }) || - canvas.getContext("experimental-webgl", { preserveDrawingBuffer: true }), - vertexShader, - fragmentShader, - program, - aVertexPosition, - uColor, - uDimensions, - uOrigin; - - // Ensure a context was actually available before proceeding - if (!gl) { - throw new Error("WebGL unavailable."); - } - - // Initialize shaders - vertexShader = gl.createShader(gl.VERTEX_SHADER); - gl.shaderSource(vertexShader, VERTEX_SHADER); - gl.compileShader(vertexShader); - fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); - gl.shaderSource(fragmentShader, FRAGMENT_SHADER); - gl.compileShader(fragmentShader); - - // Assemble vertex/fragment shaders into programs - program = gl.createProgram(); - gl.attachShader(program, vertexShader); - gl.attachShader(program, fragmentShader); - gl.linkProgram(program); - gl.useProgram(program); - - // Get locations for attribs/uniforms from the - // shader programs (to pass values into shaders at draw-time) - aVertexPosition = gl.getAttribLocation(program, "aVertexPosition"); - uColor = gl.getUniformLocation(program, "uColor"); - uDimensions = gl.getUniformLocation(program, "uDimensions"); - uOrigin = gl.getUniformLocation(program, "uOrigin"); - gl.enableVertexAttribArray(aVertexPosition); - - // Create a buffer to holds points which will be drawn - this.buffer = gl.createBuffer(); - - // Use a line width of 2.0 for legibility - gl.lineWidth(2.0); - - // Enable blending, for smoothness - gl.enable(gl.BLEND); - gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); - - this.gl = gl; - this.aVertexPosition = aVertexPosition; - this.uColor = uColor; - this.uDimensions = uDimensions; - this.uOrigin = uOrigin; - } - - // Utility function to handle drawing of a buffer; - // drawType will determine whether this is a box, line, etc. - GLChart.prototype.doDraw = function (drawType, buf, color, points) { - var gl = this.gl; - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, buf, gl.DYNAMIC_DRAW); - gl.vertexAttribPointer(this.aVertexPosition, 2, gl.FLOAT, false, 0, 0); - gl.uniform4fv(this.uColor, color); - gl.drawArrays(drawType, 0, points); - }; - - GLChart.prototype.clear = function () { - var gl = this.gl; - - // Set the viewport size; note that we use the width/height - // that our WebGL context reports, which may be lower - // resolution than the canvas we requested. - gl.viewport( - 0, - 0, - gl.drawingBufferWidth, - gl.drawingBufferHeight - ); - gl.clear(gl.COLOR_BUFFER_BIT + gl.DEPTH_BUFFER_BIT); - }; - - - GLChart.prototype.setDimensions = function (dimensions, origin) { - var gl = this.gl; - if (dimensions && dimensions.length > 0 && - origin && origin.length > 0) { - gl.uniform2fv(this.uDimensions, dimensions); - gl.uniform2fv(this.uOrigin, origin); - } - }; - - GLChart.prototype.drawLine = function (buf, color, points) { - this.doDraw(this.gl.LINE_STRIP, buf, color, points); - }; - - GLChart.prototype.drawSquare = function (min, max, color) { - this.doDraw(this.gl.TRIANGLE_FAN, new Float32Array( - min.concat([min[0], max[1]]).concat(max).concat([max[0], min[1]]) - ), color, 4); - }; - - return GLChart; - } -); diff --git a/platform/features/plot/src/MCTChart.js b/platform/features/plot/src/MCTChart.js deleted file mode 100644 index 7dbc3d5c3e..0000000000 --- a/platform/features/plot/src/MCTChart.js +++ /dev/null @@ -1,250 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * Module defining MCTChart. Created by vwoeltje on 11/12/14. - */ -define( - ["./GLChart", "./Canvas2DChart"], - function (GLChart, Canvas2DChart) { - - var TEMPLATE = ""; - - /** - * The mct-chart directive provides a canvas element which can be - * drawn upon, to support Plot view and similar visualizations. - * - * This directive takes one attribute, "draw", which is an Angular - * expression which will be two-way bound to a drawing object. This - * drawing object should contain: - * - * * `dimensions`: An object describing the logical bounds of the - * drawable area, containing two fields: - * * `origin`: The position, in logical coordinates, of the - * lower-left corner of the chart area. A two-element array. - * * `dimensions`: A two-element array containing the width - * and height of the chart area, in logical coordinates. - * * `lines`: An array of lines to be drawn, where each line is - * expressed as an object containing: - * * `buffer`: A Float32Array containing points in the line, - * in logical coordinate, in sequential x/y pairs. - * * `color`: The color of the line, as a four-element RGBA - * array, where each element is in the range of 0.0-1.0 - * * `points`: The number of points in the line. - * * `boxes`: An array of rectangles to draw in the chart area - * (used for marquee zoom). Each is an object containing: - * * `start`: The first corner of the rectangle (as a two-element - * array, logical coordinates) - * * `end`: The opposite corner of the rectangle (again, as a - * two-element array) - * * `color`: The color of the box, as a four-element RGBA - * array, where each element is in the range of 0.0-1.0 - * - * @memberof platform/features/plot - * @constructor - */ - function MCTChart($interval, $log) { - // Get an underlying chart implementation - function getChart(Charts, canvas) { - // Try the first available option... - var Chart = Charts[0]; - - // This function recursively try-catches all options; - // if these all fail, issue a warning. - if (!Chart) { - $log.warn("Cannot initialize mct-chart."); - return undefined; - } - - // Try first option; if it fails, try remaining options - try { - return new Chart(canvas); - } catch (e) { - $log.warn([ - "Could not instantiate chart", - Chart.name, - ";", - e.message - ].join(" ")); - - return getChart(Charts.slice(1), canvas); - } - } - - function linkChart(scope, element) { - var canvas = element.find("canvas")[0], - activeInterval, - chart; - - // Handle drawing, based on contents of the "draw" object - // in scope - function doDraw(draw) { - // Ensure canvas context has same resolution - // as canvas element - canvas.width = canvas.offsetWidth; - canvas.height = canvas.offsetHeight; - - // Clear previous contents - chart.clear(); - - // Nothing to draw if no draw object defined - if (!draw) { - return; - } - - // Set logical boundaries for the chart - chart.setDimensions( - draw.dimensions || [1, 1], - draw.origin || [0, 0] - ); - - // Draw line segments - (draw.lines || []).forEach(function (line) { - chart.drawLine( - line.buffer, - line.color, - line.points - ); - }); - - // Draw boxes (e.g. marquee zoom rect) - (draw.boxes || []).forEach(function (box) { - chart.drawSquare( - box.start, - box.end, - box.color - ); - }); - - } - - // Issue a drawing call, if-and-only-if canvas size - // has changed. This will be called on a timer, since - // there is no event to depend on. - function drawIfResized() { - if (canvas.width !== canvas.offsetWidth || - canvas.height !== canvas.offsetHeight) { - doDraw(scope.draw); - scope.$apply(); - } - } - - // Stop watching for changes to size (scope destroyed) - function releaseInterval() { - if (activeInterval) { - $interval.cancel(activeInterval); - } - } - - // Switch from WebGL to plain 2D if context is lost - function fallbackFromWebGL() { - element.html(TEMPLATE); - canvas = element.find("canvas")[0]; - chart = getChart([Canvas2DChart], canvas); - if (chart) { - doDraw(scope.draw); - } - } - - // Try to initialize a chart. - chart = getChart([GLChart, Canvas2DChart], canvas); - - // If that failed, there's nothing more we can do here. - // (A warning will already have been issued) - if (!chart) { - return; - } - - // WebGL is a bit of a special case; it may work, then fail - // later for various reasons, so we need to listen for this - // and fall back to plain canvas drawing when it occurs. - canvas.addEventListener("webglcontextlost", fallbackFromWebGL); - - // Check for resize, on a timer - activeInterval = $interval(drawIfResized, 1000, 0, false); - - // Watch "draw" for external changes to the set of - // things to be drawn. - scope.$watchCollection("draw", doDraw); - - // Stop checking for resize when scope is destroyed - scope.$on("$destroy", releaseInterval); - } - - return { - // Apply directive only to elements - restrict: "E", - - // Template to use (a canvas element) - template: TEMPLATE, - - // Link function; set up scope - link: linkChart, - - // Initial, isolate scope for the directive - scope: { draw: "=" } - }; - } - - /** - * @interface platform/features/plot.Chart - * @private - */ - - /** - * Clear the chart. - * @method platform/features/plot.Chart#clear - */ - /** - * Set the logical boundaries of the chart. - * @param {number[]} dimensions the horizontal and - * vertical dimensions of the chart - * @param {number[]} origin the horizontal/vertical - * origin of the chart - * @memberof platform/features/plot.Chart#setDimensions - */ - /** - * Draw the supplied buffer as a line strip (a sequence - * of line segments), in the chosen color. - * @param {Float32Array} buf the line strip to draw, - * in alternating x/y positions - * @param {number[]} color the color to use when drawing - * the line, as an RGBA color where each element - * is in the range of 0.0-1.0 - * @param {number} points the number of points to draw - * @memberof platform/features/plot.Chart#drawLine - */ - /** - * Draw a rectangle extending from one corner to another, - * in the chosen color. - * @param {number[]} min the first corner of the rectangle - * @param {number[]} max the opposite corner - * @param {number[]} color the color to use when drawing - * the rectangle, as an RGBA color where each element - * is in the range of 0.0-1.0 - * @memberof platform/features/plot.Chart#drawSquare - */ - - return MCTChart; - } -); - diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js deleted file mode 100644 index e1d1e35e7f..0000000000 --- a/platform/features/plot/src/PlotController.js +++ /dev/null @@ -1,437 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * This bundle adds a "Plot" view for numeric telemetry data. - * @namespace platform/features/plot - */ -define( - [ - "./elements/PlotUpdater", - "./elements/PlotPalette", - "./elements/PlotAxis", - "./elements/PlotLimitTracker", - "./elements/PlotTelemetryFormatter", - "./modes/PlotModeOptions", - "./SubPlotFactory" - ], - function ( - PlotUpdater, - PlotPalette, - PlotAxis, - PlotLimitTracker, - PlotTelemetryFormatter, - PlotModeOptions, - SubPlotFactory - ) { - - var AXIS_DEFAULTS = [ - { "name": "Time" }, - { "name": "Value" } - ]; - - /** - * The PlotController is responsible for any computation/logic - * associated with displaying the plot view. Specifically, these - * responsibilities include: - * - * * Describing axes and labeling. - * * Handling user interactions. - * * Deciding what needs to be drawn in the chart area. - * - * @memberof platform/features/plot - * @constructor - */ - function PlotController( - $scope, - $element, - exportImageService, - telemetryFormatter, - telemetryHandler, - throttle, - PLOT_FIXED_DURATION, - openmct - ) { - var self = this, - plotTelemetryFormatter = - new PlotTelemetryFormatter(telemetryFormatter), - subPlotFactory = - new SubPlotFactory(plotTelemetryFormatter), - cachedObjects = [], - updater, - lastBounds, - lastRange, - lastDomain, - handle; - var timeAPI = openmct.time; - - // Populate the scope with axis information (specifically, options - // available for each axis.) - function setupAxes(metadatas) { - $scope.axes.forEach(function (axis) { - axis.updateMetadata(metadatas); - }); - } - - // Trigger an update of a specific subplot; - // used in a loop to update all subplots. - function updateSubplot(subplot) { - subplot.update(); - } - - // Set up available modes (stacked/overlaid), based on the - // set of telemetry objects in this plot view. - function setupModes(telemetryObjects) { - if (cachedObjects !== telemetryObjects) { - cachedObjects = telemetryObjects; - self.modeOptions = new PlotModeOptions( - telemetryObjects || [], - subPlotFactory - ); - } - } - - // Change the displayable bounds - function setBasePanZoom(bounds) { - var start = bounds.start, - end = bounds.end; - if (updater) { - updater.setDomainBounds(start, end); - self.update(); - } - lastBounds = bounds; - } - - // Reinstantiate the plot updater (e.g. because we have a - // new subscription.) This will clear the plot. - function recreateUpdater() { - var domain = $scope.axes[0].active.key, - range = $scope.axes[1].active.key, - duration = PLOT_FIXED_DURATION; - - updater = new PlotUpdater(handle, domain, range, duration); - lastDomain = domain; - lastRange = range; - - self.limitTracker = new PlotLimitTracker(handle, range); - - // Keep any externally-provided bounds - if (lastBounds) { - setBasePanZoom(lastBounds); - } - } - - function getUpdater() { - if (!updater) { - recreateUpdater(); - } - return updater; - } - - // Handle new telemetry data in this plot - function updateValues() { - self.pending = false; - if (handle) { - setupModes(handle.getTelemetryObjects()); - setupAxes(handle.getMetadata()); - getUpdater().update(); - self.modeOptions.getModeHandler().plotTelemetry(updater); - self.limitTracker.update(); - self.update(); - } - } - - // Display new historical data as it becomes available - function addHistoricalData(domainObject, series) { - self.pending = false; - getUpdater().addHistorical(domainObject, series); - self.modeOptions.getModeHandler().plotTelemetry(updater); - self.update(); - } - - // Issue a new request for historical telemetry - function requestTelemetry() { - if (handle) { - handle.request({}, addHistoricalData); - } - } - - // Requery for data entirely - function replot() { - if (handle) { - updater = undefined; - requestTelemetry(); - } - } - - function changeTimeOfInterest(timeOfInterest) { - if (timeOfInterest !== undefined) { - var bounds = timeAPI.bounds(); - var range = bounds.end - bounds.start; - $scope.toiPerc = ((timeOfInterest - bounds.start) / range) * 100; - $scope.toiPinned = true; - } else { - $scope.toiPerc = undefined; - $scope.toiPinned = false; - } - } - - // Create a new subscription; telemetrySubscriber gets - // to do the meaningful work here. - function subscribe(domainObject) { - if (handle) { - handle.unsubscribe(); - } - handle = domainObject && telemetryHandler.handle( - domainObject, - updateValues, - true // Lossless - ); - replot(); - - changeTimeOfInterest(timeAPI.timeOfInterest()); - timeAPI.on("timeOfInterest", changeTimeOfInterest); - } - - // Release the current subscription (called when scope is destroyed) - function releaseSubscription() { - if (handle) { - handle.unsubscribe(); - handle = undefined; - } - timeAPI.off("timeOfInterest", changeTimeOfInterest); - } - - function requery() { - self.pending = true; - releaseSubscription(); - subscribe($scope.domainObject); - } - - function updateDomainFormat() { - var domainAxis = $scope.axes[0]; - plotTelemetryFormatter - .setDomainFormat(domainAxis.active.format); - } - - function domainRequery(newDomain) { - if (newDomain !== lastDomain) { - updateDomainFormat(); - requery(); - } - } - - function rangeRequery(newRange) { - if (newRange !== lastRange) { - requery(); - } - } - - // Respond to a display bounds change (requery for data) - function changeDisplayBounds(event, bounds, follow) { - //'hack' for follow mode - if (follow === true) { - setBasePanZoom(bounds); - } else { - var domainAxis = $scope.axes[0]; - - if (bounds.domain) { - domainAxis.chooseOption(bounds.domain); - } - updateDomainFormat(); - setBasePanZoom(bounds); - requery(); - } - self.setUnsynchedStatus($scope.domainObject, follow && self.isZoomed()); - changeTimeOfInterest(timeAPI.timeOfInterest()); - } - - this.modeOptions = new PlotModeOptions([], subPlotFactory); - this.updateValues = updateValues; - - // Create a throttled update function - this.scheduleUpdate = throttle(function () { - self.modeOptions.getModeHandler().getSubPlots() - .forEach(updateSubplot); - }); - - self.pending = true; - self.$element = $element; - self.exportImageService = exportImageService; - - // Initialize axes; will get repopulated when telemetry - // metadata becomes available. - $scope.axes = [ - new PlotAxis("domains", [], AXIS_DEFAULTS[0]), - new PlotAxis("ranges", [], AXIS_DEFAULTS[1]) - ]; - - //Are some initialized bounds defined? - var bounds = timeAPI.bounds(); - if (bounds && - bounds.start !== undefined && - bounds.end !== undefined) { - changeDisplayBounds(undefined, timeAPI.bounds(), timeAPI.clock() !== undefined); - } - - // Watch for changes to the selected axis - $scope.$watch("axes[0].active.key", domainRequery); - $scope.$watch("axes[1].active.key", rangeRequery); - - // Subscribe to telemetry when a domain object becomes available - $scope.$watch('domainObject', subscribe); - - // Respond to external bounds changes - $scope.$on("telemetry:display:bounds", changeDisplayBounds); - - // Unsubscribe when the plot is destroyed - $scope.$on("$destroy", releaseSubscription); - } - - /** - * Get the color (as a style-friendly string) to use - * for plotting the trace at the specified index. - * @param {number} index the index of the trace - * @returns {string} the color, in #RRGGBB form - */ - PlotController.prototype.getColor = function (index) { - return PlotPalette.getStringColor(index); - }; - - /** - * Check if the plot is zoomed or panned out - * of its default state (to determine whether back/unzoom - * controls should be shown) - * @returns {boolean} true if not in default state - */ - PlotController.prototype.isZoomed = function () { - return this.modeOptions.getModeHandler().isZoomed(); - }; - - /** - * Undo the most recent pan/zoom change and restore - * the prior state. - */ - PlotController.prototype.stepBackPanZoom = function () { - return this.modeOptions.getModeHandler().stepBackPanZoom(); - }; - - /** - * Undo all pan/zoom changes and restore the initial state. - */ - PlotController.prototype.unzoom = function () { - return this.modeOptions.getModeHandler().unzoom(); - }; - - /** - * Get the mode options (Stacked/Overlaid) that are applicable - * for this plot. - */ - PlotController.prototype.getModeOptions = function () { - return this.modeOptions.getModeOptions(); - }; - - /** - * Get the current mode that is applicable to this plot. This - * will include key, name, and cssClass fields. - */ - PlotController.prototype.getMode = function () { - return this.modeOptions.getMode(); - }; - - /** - * Set the mode which should be active in this plot. - * @param mode one of the mode options returned from - * getModeOptions() - */ - PlotController.prototype.setMode = function (mode) { - this.modeOptions.setMode(mode); - this.updateValues(); - }; - - /** - * Get all individual plots contained within this Plot view. - * (Multiple may be contained when in Stacked mode). - * @returns {SubPlot[]} all subplots in this Plot view - */ - PlotController.prototype.getSubPlots = function () { - return this.modeOptions.getModeHandler().getSubPlots(); - }; - - /** - * Get the CSS class to apply to the legend for this domain - * object; this will reflect limit state. - * @returns {string} the CSS class - */ - PlotController.prototype.getLegendClass = function (telemetryObject) { - return this.limitTracker && - this.limitTracker.getLegendClass(telemetryObject); - }; - - /** - * Explicitly update all plots. - */ - PlotController.prototype.update = function () { - this.scheduleUpdate(); - }; - - /** - * Check if a request is pending (to show the wait spinner) - */ - PlotController.prototype.isRequestPending = function () { - // Placeholder; this should reflect request state - // when requesting historical telemetry - return this.pending; - }; - - PlotController.prototype.setUnsynchedStatus = function (domainObject, status) { - if (domainObject.hasCapability('status')) { - domainObject.getCapability('status').set('timeconductor-unsynced', status); - } - }; - - /** - * Export the plot to PNG - */ - PlotController.prototype.exportPNG = function () { - var self = this; - self.hideExportButtons = true; - self.exportImageService.exportPNG(self.$element[0], "plot.png", 'white').finally(function () { - self.hideExportButtons = false; - }); - }; - - /** - * Export the plot to JPG - */ - PlotController.prototype.exportJPG = function () { - var self = this; - self.hideExportButtons = true; - self.exportImageService.exportJPG(self.$element[0], "plot.jpg", 'white').finally(function () { - self.hideExportButtons = false; - }); - }; - - return PlotController; - } -); - diff --git a/platform/features/plot/src/PlotOptionsController.js b/platform/features/plot/src/PlotOptionsController.js deleted file mode 100644 index 38fa8c96e3..0000000000 --- a/platform/features/plot/src/PlotOptionsController.js +++ /dev/null @@ -1,195 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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( - ['./PlotOptionsForm'], - function (PlotOptionsForm) { - - /** - * Notes on implementation of plot options - * - * Multiple y-axes will have to be handled with multiple forms as - * they will need to be stored on distinct model object - * - * Likewise plot series options per-child will need to be separate - * forms. - */ - - /** - * The LayoutController is responsible for supporting the - * Layout view. It arranges frames according to saved configuration - * and provides methods for updating these based on mouse - * movement. - * @memberof platform/features/plot - * @constructor - * @param {Scope} $scope the controller's Angular scope - */ - function PlotOptionsController($scope) { - - var self = this; - this.$scope = $scope; - this.domainObject = $scope.domainObject; - this.configuration = this.domainObject.getModel().configuration || {}; - this.plotOptionsForm = new PlotOptionsForm(); - this.composition = []; - this.watches = []; - - /* - Listen for changes to the domain object and update the object's - children. - */ - this.mutationListener = this.domainObject.getCapability('mutation').listen(function (model) { - if (self.hasCompositionChanged(self.composition, model.composition)) { - self.updateChildren(); - } - }); - - /* - Set form structures on scope - */ - $scope.plotSeriesForm = this.plotOptionsForm.plotSeriesForm; - $scope.xAxisForm = this.plotOptionsForm.xAxisForm; - $scope.yAxisForm = this.plotOptionsForm.yAxisForm; - - $scope.$on("$destroy", function () { - //Clean up any listeners on destruction of controller - self.mutationListener(); - }); - - this.defaultConfiguration(); - this.updateChildren(); - - /* - * Setup a number of watches for changes to form values. On - * change, update the model configuration via mutation - */ - $scope.$watchCollection('configuration.plot.yAxis', function (newValue, oldValue) { - self.updateConfiguration(newValue, oldValue); - }); - $scope.$watchCollection('configuration.plot.xAxis', function (newValue, oldValue) { - self.updateConfiguration(newValue, oldValue); - }); - - this.watchSeries(); - - } - - /** - * Unregister all watches for series data (ie. the configuration for - * child objects) - * @private - */ - PlotOptionsController.prototype.clearSeriesWatches = function () { - this.watches.forEach(function (watch) { - watch(); - }); - this.watches = []; - }; - - /** - * Attach watches for each object in the plot's composition - * @private - */ - PlotOptionsController.prototype.watchSeries = function () { - var self = this; - - this.clearSeriesWatches(); - - (self.$scope.children || []).forEach(function (child, index) { - self.watches.push( - self.$scope.$watchCollection( - 'configuration.plot.series[' + index + ']', - function (newValue, oldValue) { - self.updateConfiguration(newValue, oldValue); - } - ) - ); - }); - }; - - /** - * Determine whether the changes to the model that triggered a - * mutation event were purely compositional. - * - * @private - */ - PlotOptionsController.prototype.hasCompositionChanged = function (oldComposition, newComposition) { - // Framed slightly strangely, but the boolean logic is - // easier to follow for the unchanged case. - var isUnchanged = oldComposition === newComposition || - ( - oldComposition.length === newComposition.length && - oldComposition.every(function (currentValue, index) { - return newComposition[index] && currentValue === newComposition[index]; - }) - ); - return !isUnchanged; - }; - - /** - * Default the plot options model - * - * @private - */ - PlotOptionsController.prototype.defaultConfiguration = function () { - this.configuration.plot = this.configuration.plot || {}; - this.configuration.plot.xAxis = this.configuration.plot.xAxis || {}; - this.configuration.plot.yAxis = this.configuration.plot.yAxis || {}; // y-axes will be associative array keyed on axis key - this.configuration.plot.series = this.configuration.plot.series || []; // series will be associative array keyed on sub-object id - this.$scope.configuration = this.configuration; - }; - - /** - * When a child is added to, or removed from a plot, update the - * plot options model - * @private - */ - PlotOptionsController.prototype.updateChildren = function () { - var self = this; - this.domainObject.useCapability('composition').then(function (children) { - self.$scope.children = children; - self.composition = self.domainObject.getModel().composition; - children.forEach(function (child, index) { - self.configuration.plot.series[index] = - self.configuration.plot.series[index] || {'id': child.getId()}; - }); - self.watchSeries(); - }); - }; - - /** - * On changes to the form, update the configuration on the domain - * object - * @private - */ - PlotOptionsController.prototype.updateConfiguration = function () { - var self = this; - this.domainObject.useCapability('mutation', function (model) { - model.configuration = model.configuration || {}; - model.configuration.plot = self.configuration.plot; - }); - }; - - return PlotOptionsController; - } -); - diff --git a/platform/features/plot/src/PlotOptionsForm.js b/platform/features/plot/src/PlotOptionsForm.js deleted file mode 100644 index 224ec8530b..0000000000 --- a/platform/features/plot/src/PlotOptionsForm.js +++ /dev/null @@ -1,150 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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 () { - - /** - * A class for encapsulating structure and behaviour of the plot - * options form - * @memberOf platform/features/plot - * @param topic - * @constructor - */ - function PlotOptionsForm() { - - /* - Defined below are the form structures for the plot options. - */ - this.xAxisForm = { - 'name': 'x-axis', - 'sections': [{ - 'name': 'x-axis', - 'rows': [ - { - 'name': 'Domain', - 'control': 'select', - 'key': 'key', - 'options': [ - {'name': 'SCET', 'value': 'scet'}, - {'name': 'SCLK', 'value': 'sclk'}, - {'name': 'LST', 'value': 'lst'} - ] - } - ] - }]}; - - this.yAxisForm = { - 'name': 'y-axis', - 'sections': [{ - // Will need to be repeated for each y-axis, with a - // distinct name for each. Ideally the name of the axis - // itself. - 'name': 'y-axis', - 'rows': [ - { - 'name': 'Range', - 'control': 'select', - 'key': 'key', - 'options': [ - {'name': 'EU', 'value': 'eu'}, - {'name': 'DN', 'value': 'dn'}, - {'name': 'Status', 'value': 'status'} - ] - }, - { - 'name': 'Autoscale', - 'control': 'checkbox', - 'key': 'autoscale' - }, - { - 'name': 'Min', - 'control': 'textfield', - 'key': 'min', - 'pattern': '[0-9]', - 'inputsize' : 'sm' - }, - { - 'name': 'Max', - 'control': 'textfield', - 'key': 'max', - 'pattern': '[0-9]', - 'inputsize' : 'sm' - } - ] - }] - }; - this.plotSeriesForm = { - 'name': 'Series Options', - 'sections': [ - { - rows: [ - { - 'name': 'Color', - 'control': 'color', - 'key': 'color' - }] - }, - { - 'rows': [ - { - 'name': 'Markers', - 'control': 'checkbox', - 'key': 'markers', - 'layout': 'control-first' - } - ] - }, - { - 'rows': [ - { - 'name': 'No Line', - 'control': 'radio', - 'key': 'lineType', - 'value': 'noLine', - 'layout': 'control-first' - }, - { - 'name': 'Step Line', - 'control': 'radio', - 'key': 'lineType', - 'value': 'stepLine', - 'layout': 'control-first' - }, - { - 'name': 'Linear Line', - 'control': 'radio', - 'key': 'lineType', - 'value': 'linearLine', - 'layout': 'control-first' - } - ] - } - ] - }; - } - - return PlotOptionsForm; - } -); - diff --git a/platform/features/plot/src/SubPlot.js b/platform/features/plot/src/SubPlot.js deleted file mode 100644 index a943f1ce70..0000000000 --- a/platform/features/plot/src/SubPlot.js +++ /dev/null @@ -1,415 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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( - [ - './elements/PlotPosition', - './elements/PlotTickGenerator' - ], - function (PlotPosition, PlotTickGenerator) { - - var DOMAIN_TICKS = 5, - RANGE_TICKS = 7; - - /** - * A SubPlot is an individual plot within a Plot View (which - * may contain multiple plots, specifically when in Stacked - * plot mode.) - * @memberof platform/features/plot - * @constructor - * @param {DomainObject[]} telemetryObjects the domain objects - * which will be plotted in this sub-plot - * @param {PlotPanZoomStack} panZoomStack the stack of pan-zoom - * states which is applicable to this sub-plot - * @param {TelemetryFormatter} telemetryFormatter the telemetry - * formatting service; used to convert domain/range values - * from telemetry data sets to a human-readable form. - */ - function SubPlot(telemetryObjects, panZoomStack, telemetryFormatter) { - // We are used from a template often, so maintain - // state in local variables to allow for fast look-up, - // as is normal for controllers. - this.telemetryObjects = telemetryObjects; - this.domainTicks = []; - this.rangeTicks = []; - this.formatter = telemetryFormatter; - this.draw = {}; - this.hovering = false; - this.panZoomStack = panZoomStack; - - // Start with the right initial drawing bounds, - // tick marks - this.updateDrawingBounds(); - this.updateTicks(); - } - - /** - * Tests whether this subplot has domain data to show for the current pan/zoom level. Absence of domain data - * implies that there is no range data displayed either - * @returns {boolean} true if domain data exists for the current pan/zoom level - */ - SubPlot.prototype.hasDomainData = function () { - return this.panZoomStack && - this.panZoomStack.getDimensions()[0] > 0; - }; - - // Utility function for filtering out empty strings. - function isNonEmpty(v) { - return typeof v === 'string' && v !== ""; - } - - // Converts from pixel coordinates to domain-range, - // to interpret mouse gestures. - SubPlot.prototype.mousePositionToDomainRange = function (mousePosition) { - return new PlotPosition( - mousePosition.x, - mousePosition.y, - mousePosition.width, - mousePosition.height, - this.panZoomStack - ).getPosition(); - }; - - // Utility function to get the mouse position (in x,y - // pixel coordinates in the canvas area) from a mouse - // event object. - SubPlot.prototype.toMousePosition = function ($event) { - var bounds = this.subPlotBounds; - - return { - x: $event.clientX - bounds.left, - y: $event.clientY - bounds.top, - width: bounds.width, - height: bounds.height - }; - }; - - // Convert a domain-range position to a displayable - // position. This will subtract the domain offset, which - // is used to bias domain values to minimize loss-of-precision - // associated with conversion to a 32-bit floating point - // format (which is needed in the chart area itself, by WebGL.) - SubPlot.prototype.toDisplayable = function (position) { - return [position[0] - this.domainOffset, position[1]]; - }; - - // Update the current hover coordinates - SubPlot.prototype.updateHoverCoordinates = function () { - var formatter = this.formatter; - - // Utility, for map/forEach loops. Index 0 is domain, - // index 1 is range. - function formatValue(v, i) { - return i ? - formatter.formatRangeValue(v) : - formatter.formatDomainValue(v); - } - - this.hoverCoordinates = this.mousePosition && - this.mousePositionToDomainRange(this.mousePosition) - .map(formatValue) - .filter(isNonEmpty) - .join(", "); - }; - - // Update the drawable marquee area to reflect current - // mouse position (or don't show it at all, if no marquee - // zoom is in progress) - SubPlot.prototype.updateMarqueeBox = function () { - // Express this as a box in the draw object, which - // is passed to an mct-chart in the template for rendering. - this.draw.boxes = this.marqueeStart ? - [{ - start: this.toDisplayable( - this.mousePositionToDomainRange(this.marqueeStart) - ), - end: this.toDisplayable( - this.mousePositionToDomainRange(this.mousePosition) - ), - color: [1, 1, 1, 0.5] - }] : undefined; - }; - - // Update the bounds (origin and dimensions) of the drawing area. - SubPlot.prototype.updateDrawingBounds = function () { - var panZoom = this.panZoomStack.getPanZoom(); - - // Communicate pan-zoom state from stack to the draw object - // which is passed to mct-chart in the template. - this.draw.dimensions = panZoom.dimensions; - this.draw.origin = [ - panZoom.origin[0] - this.domainOffset, - panZoom.origin[1] - ]; - }; - - // Update tick marks in scope. - SubPlot.prototype.updateTicks = function () { - var tickGenerator = - new PlotTickGenerator(this.panZoomStack, this.formatter); - - this.domainTicks = - tickGenerator.generateDomainTicks(DOMAIN_TICKS); - this.rangeTicks = - tickGenerator.generateRangeTicks(RANGE_TICKS); - }; - - SubPlot.prototype.updatePan = function () { - var start, current, delta, nextOrigin; - - // Clear the previous panning pan-zoom state - this.panZoomStack.popPanZoom(); - - // Calculate what the new resulting pan-zoom should be - start = this.mousePositionToDomainRange( - this.panStart, - this.panZoomStack - ); - current = this.mousePositionToDomainRange( - this.mousePosition, - this.panZoomStack - ); - - delta = [current[0] - start[0], current[1] - start[1]]; - nextOrigin = [ - this.panStartBounds.origin[0] - delta[0], - this.panStartBounds.origin[1] - delta[1] - ]; - - // ...and push a new one at the current mouse position - this.panZoomStack - .pushPanZoom(nextOrigin, this.panStartBounds.dimensions); - }; - - /** - * Get the set of domain objects which are being - * represented in this sub-plot. - * @returns {DomainObject[]} the domain objects which - * will have data plotted in this sub-plot - */ - SubPlot.prototype.getTelemetryObjects = function () { - return this.telemetryObjects; - }; - - /** - * Get ticks mark information appropriate for using in the - * template for this sub-plot's domain axis, as prepared - * by the PlotTickGenerator. - * @returns {Array} tick marks for the domain axis - */ - SubPlot.prototype.getDomainTicks = function () { - return this.domainTicks; - }; - - /** - * Get ticks mark information appropriate for using in the - * template for this sub-plot's range axis, as prepared - * by the PlotTickGenerator. - * @returns {Array} tick marks for the range axis - */ - SubPlot.prototype.getRangeTicks = function () { - return this.rangeTicks; - }; - - /** - * Get the drawing object associated with this sub-plot; - * this object will be passed to the mct-chart in which - * this sub-plot's lines will be plotted, as its "draw" - * attribute, and should have the same internal format - * expected by that directive. - * @return {object} the drawing object - */ - SubPlot.prototype.getDrawingObject = function () { - return this.draw; - }; - - /** - * Get the coordinates (as displayable text) for the - * current mouse position. - * @returns {string[]} the displayable domain and range - * coordinates over which the mouse is hovered - */ - SubPlot.prototype.getHoverCoordinates = function () { - return this.hoverCoordinates; - }; - - /** - * Handle mouse movement over the chart area. - * @param $event the mouse event - * @memberof platform/features/plot.SubPlot# - */ - SubPlot.prototype.hover = function ($event) { - this.hovering = true; - this.subPlotBounds = $event.target.getBoundingClientRect(); - this.mousePosition = this.toMousePosition($event); - //If there is a domain to display, show hover coordinates, otherwise hover coordinates are meaningless - if (this.hasDomainData()) { - this.updateHoverCoordinates(); - } - if (this.marqueeStart) { - this.updateMarqueeBox(); - } - if (this.panStart) { - this.updatePan(); - this.updateDrawingBounds(); - this.updateTicks(); - } - }; - - /** - * Continue a previously-start pan or zoom gesture. - * @param $event the mouse event - * @memberof platform/features/plot.SubPlot# - */ - SubPlot.prototype.continueDrag = function ($event) { - this.mousePosition = this.toMousePosition($event); - if (this.marqueeStart) { - this.updateMarqueeBox(); - } - if (this.panStart) { - this.updatePan(); - this.updateDrawingBounds(); - this.updateTicks(); - } - }; - - /** - * Initiate a marquee zoom action. - * @param $event the mouse event - */ - SubPlot.prototype.startDrag = function ($event) { - this.subPlotBounds = $event.target.getBoundingClientRect(); - this.mousePosition = this.toMousePosition($event); - // Treat any modifier key as a pan - if ($event.altKey || $event.shiftKey || $event.ctrlKey) { - // Start panning - this.panStart = this.mousePosition; - this.panStartBounds = this.panZoomStack.getPanZoom(); - // We're starting a pan, so add this back as a - // state on the stack; it will get replaced - // during the pan. - this.panZoomStack.pushPanZoom( - this.panStartBounds.origin, - this.panStartBounds.dimensions - ); - $event.preventDefault(); - } else { - // Start marquee zooming - this.marqueeStart = this.mousePosition; - this.updateMarqueeBox(); - } - }; - - /** - * Complete a marquee zoom action. - * @param $event the mouse event - */ - SubPlot.prototype.endDrag = function ($event) { - var self = this; - - // Perform a marquee zoom. - function marqueeZoom(start, end) { - // Determine what boundary is described by the marquee, - // in domain-range values. Use the minima for origin, so that - // it doesn't matter what direction the user marqueed in. - var a = self.mousePositionToDomainRange(start), - b = self.mousePositionToDomainRange(end), - origin = [ - Math.min(a[0], b[0]), - Math.min(a[1], b[1]) - ], - dimensions = [ - Math.max(a[0], b[0]) - origin[0], - Math.max(a[1], b[1]) - origin[1] - ]; - - // Proceed with zoom if zoom dimensions are non zeros - if (!(dimensions[0] === 0 && dimensions[1] === 0)) { - // Push the new state onto the pan-zoom stack - self.panZoomStack.pushPanZoom(origin, dimensions); - - // Make sure tick marks reflect new bounds - self.updateTicks(); - } - } - - this.mousePosition = this.toMousePosition($event); - this.subPlotBounds = undefined; - if (this.marqueeStart) { - marqueeZoom(this.marqueeStart, this.mousePosition); - this.marqueeStart = undefined; - this.updateMarqueeBox(); - this.updateDrawingBounds(); - this.updateTicks(); - } - if (this.panStart) { - // End panning - this.panStart = undefined; - this.panStartBounds = undefined; - } - }; - - /** - * Update the drawing bounds, marquee box, and - * tick marks for this subplot. - */ - SubPlot.prototype.update = function () { - this.updateDrawingBounds(); - this.updateMarqueeBox(); - this.updateTicks(); - }; - - /** - * Set the domain offset associated with this sub-plot. - * A domain offset is subtracted from all domain - * before lines are drawn to avoid artifacts associated - * with the use of 32-bit floats when domain values - * are often timestamps (due to insufficient precision.) - * A SubPlot will be drawing boxes (for marquee zoom) in - * the same offset coordinate space, so it needs to know - * the value of this to position that marquee box - * correctly. - * @param {number} value the domain offset - */ - SubPlot.prototype.setDomainOffset = function (value) { - this.domainOffset = value; - }; - - /** - * When used with no argument, check whether or not the user - * is currently hovering over this subplot. When used with - * an argument, set that state. - * @param {boolean} [state] the new hovering state - * @returns {boolean} the hovering state - */ - SubPlot.prototype.isHovering = function (state) { - if (state !== undefined) { - this.hovering = state; - } - return this.hovering; - }; - - return SubPlot; - - } -); - diff --git a/platform/features/plot/src/SubPlotFactory.js b/platform/features/plot/src/SubPlotFactory.js deleted file mode 100644 index d367bd4f39..0000000000 --- a/platform/features/plot/src/SubPlotFactory.js +++ /dev/null @@ -1,59 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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( - ["./SubPlot"], - function (SubPlot) { - - /** - * Utility factory; wraps the SubPlot constructor and adds - * in a reference to the telemetryFormatter, which will be - * used to represent telemetry values (timestamps or data - * values) as human-readable strings. - * @memberof platform/features/plot - * @constructor - */ - function SubPlotFactory(telemetryFormatter) { - this.telemetryFormatter = telemetryFormatter; - } - - /** - * Instantiate a new sub-plot. - * @param {DomainObject[]} telemetryObjects the domain objects - * which will be plotted in this sub-plot - * @param {PlotPanZoomStack} panZoomStack the stack of pan-zoom - * states which is applicable to this sub-plot - * @returns {SubPlot} the instantiated sub-plot - * @method - */ - SubPlotFactory.prototype.createSubPlot = function (telemetryObjects, panZoomStack) { - return new SubPlot( - telemetryObjects, - panZoomStack, - this.telemetryFormatter - ); - }; - - return SubPlotFactory; - - } -); diff --git a/platform/features/plot/src/elements/PlotAxis.js b/platform/features/plot/src/elements/PlotAxis.js deleted file mode 100644 index 6147c4f134..0000000000 --- a/platform/features/plot/src/elements/PlotAxis.js +++ /dev/null @@ -1,134 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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 () { - - /** - * A PlotAxis provides a template-ready set of options - * for the domain or range axis, sufficient to populate - * selectors. - * - * @memberof platform/features/plot - * @constructor - * @param {string} axisType the field in metadatas to - * look at for axis options; usually one of - * "domains" or "ranges" - * @param {object[]} metadatas metadata objects, as - * returned by the `getMetadata()` method of - * a `telemetry` capability. - * @param {object} defaultValue the value to use for the - * active state in the event that no options are - * found; should contain "name" and "key" at - * minimum. - * - */ - function PlotAxis(axisType, metadatas, defaultValue) { - this.axisType = axisType; - this.defaultValue = defaultValue; - this.optionKeys = {}; - - /** - * The currently chosen option for this axis. An - * initial value is provided; this will be updated - * directly form the plot template. - * @memberof platform/features/plot.PlotAxis# - */ - this.active = defaultValue; - - /** - * The set of options applicable for this axis; - * an array of objects, where each object contains a - * "key" field and a "name" field (for machine- and - * human-readable names respectively) - * @memberof platform/features/plot.PlotAxis# - */ - this.options = []; - - // Initialize options from metadata objects - this.updateMetadata(metadatas); - } - - - /** - * Update axis options to reflect current metadata. - * @param {TelemetryMetadata[]} metadata objects describing - * applicable telemetry - */ - PlotAxis.prototype.updateMetadata = function (metadatas) { - var axisType = this.axisType, - optionKeys = this.optionKeys, - newOptions = {}, - toAdd = []; - - function isValid(option) { - return option && optionKeys[option.key]; - } - - metadatas.forEach(function (m) { - (m[axisType] || []).forEach(function (option) { - var key = option.key; - if (!optionKeys[key] && !newOptions[key]) { - toAdd.push(option); - } - newOptions[key] = true; - }); - }); - - optionKeys = this.optionKeys = newOptions; - - // General approach here is to avoid changing object - // instances unless something has really changed, since - // Angular is watching; don't want to trigger extra digests. - if (!this.options.every(isValid)) { - this.options = this.options.filter(isValid); - } - - if (toAdd.length > 0) { - this.options = this.options.concat(toAdd); - } - - if (!isValid(this.active)) { - this.active = this.options[0] || this.defaultValue; - } - }; - - /** - * Change the domain/range selection for this axis. If the - * provided `key` is not recognized as an option, no change - * will occur. - * @param {string} key the identifier for the domain/range - */ - PlotAxis.prototype.chooseOption = function (key) { - var self = this; - this.options.forEach(function (option) { - if (option.key === key) { - self.active = option; - } - }); - }; - - return PlotAxis; - - } -); diff --git a/platform/features/plot/src/elements/PlotLimitTracker.js b/platform/features/plot/src/elements/PlotLimitTracker.js deleted file mode 100644 index f89759b4a2..0000000000 --- a/platform/features/plot/src/elements/PlotLimitTracker.js +++ /dev/null @@ -1,78 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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 () { - - /** - * Tracks the limit state of telemetry objects being plotted. - * @memberof platform/features/plot - * @constructor - * @param {platform/telemetry.TelemetryHandle} handle the handle - * to telemetry access - * @param {string} range the key to use when looking up range values - */ - function PlotLimitTracker(handle, range) { - this.handle = handle; - this.range = range; - this.legendClasses = {}; - } - - /** - * Update limit states to reflect the latest data. - */ - PlotLimitTracker.prototype.update = function () { - var legendClasses = {}, - range = this.range, - handle = this.handle; - - function updateLimit(telemetryObject) { - var limit = telemetryObject.getCapability('limit'), - datum = handle.getDatum(telemetryObject); - - if (limit && datum) { - legendClasses[telemetryObject.getId()] = - (limit.evaluate(datum, range) || {}).cssClass; - } - } - - handle.getTelemetryObjects().forEach(updateLimit); - - this.legendClasses = legendClasses; - }; - - /** - * Get the CSS class associated with any limit violations for this - * telemetry object. - * @param {DomainObject} domainObject the telemetry object to check - * @returns {string} the CSS class name, if any - */ - PlotLimitTracker.prototype.getLegendClass = function (domainObject) { - var id = domainObject && domainObject.getId(); - return id && this.legendClasses[id]; - }; - - return PlotLimitTracker; - - } -); diff --git a/platform/features/plot/src/elements/PlotLine.js b/platform/features/plot/src/elements/PlotLine.js deleted file mode 100644 index d98ac53333..0000000000 --- a/platform/features/plot/src/elements/PlotLine.js +++ /dev/null @@ -1,118 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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( - ['./PlotSeriesWindow'], - function (PlotSeriesWindow) { - - - /** - * Represents a single line or trace of a plot. - * @param {{PlotLineBuffer}} buffer the plot buffer - * @constructor - */ - function PlotLine(buffer) { - this.buffer = buffer; - } - - /** - * Add a point to this plot line. - * @param {number} domainValue the domain value - * @param {number} rangeValue the range value - */ - PlotLine.prototype.addPoint = function (domainValue, rangeValue) { - var buffer = this.buffer, - index; - - // Make sure we got real/useful values here... - if (domainValue !== undefined && rangeValue !== undefined) { - index = buffer.findInsertionIndex(domainValue); - - // Already in the buffer? Skip insertion - if (index < 0) { - return; - } - - // Insert the point - if (!buffer.insertPoint(domainValue, rangeValue, index)) { - // If insertion failed, trim from the beginning... - buffer.trim(1); - // ...and try again. - buffer.insertPoint(domainValue, rangeValue, index); - } - } - }; - - /** - * Add a series of telemetry data to this plot line. - * @param {TelemetrySeries} series the data series - * @param {string} [domain] the key indicating which domain - * to use when looking up data from this series - * @param {string} [range] the key indicating which range - * to use when looking up data from this series - */ - PlotLine.prototype.addSeries = function (series, domain, range) { - var buffer = this.buffer; - - // Insert a time-windowed data series into the buffer - function insertSeriesWindow(seriesWindow) { - var count = seriesWindow.getPointCount(); - - function doInsert() { - var firstTimestamp = seriesWindow.getDomainValue(0), - lastTimestamp = seriesWindow.getDomainValue(count - 1), - startIndex = buffer.findInsertionIndex(firstTimestamp), - endIndex = buffer.findInsertionIndex(lastTimestamp); - - // Does the whole series fit in between two adjacent indexes? - if ((startIndex === endIndex) && startIndex > -1) { - // Insert it in between - buffer.insert(seriesWindow, startIndex); - } else { - // Split it up, and add the two halves - seriesWindow.split().forEach(insertSeriesWindow); - } - } - - // Only insert if there are points to insert - if (count > 0) { - doInsert(); - } - } - - // Should try to add via insertion if a - // clear insertion point is available; - // if not, should split and add each half. - // Insertion operation also needs to factor out - // redundant timestamps, for overlapping data - insertSeriesWindow(new PlotSeriesWindow( - series, - domain, - range, - 0, - series.getPointCount() - )); - }; - - return PlotLine; - } -); diff --git a/platform/features/plot/src/elements/PlotLineBuffer.js b/platform/features/plot/src/elements/PlotLineBuffer.js deleted file mode 100644 index b46537e44b..0000000000 --- a/platform/features/plot/src/elements/PlotLineBuffer.js +++ /dev/null @@ -1,268 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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 () { - - /** - * Contains the buffer used to draw a plot. - * @param {number} domainOffset number to subtract from domain values - * @param {number} initialSize initial buffer size - * @param {number} maxSize maximum buffer size - * @memberof platform/features/plot - * @constructor - */ - function PlotLineBuffer(domainOffset, initialSize, maxSize) { - this.buffer = new Float32Array(initialSize * 2); - this.rangeExtrema = - [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY]; - this.length = 0; - this.domainOffset = domainOffset; - this.initialSize = initialSize; - this.maxSize = maxSize; - } - - // Binary search for an insertion index - PlotLineBuffer.prototype.binSearch = function (value, min, max) { - var mid = Math.floor((min + max) / 2), - found = this.buffer[mid * 2]; - - // On collisions, insert at same index - if (found === value) { - return mid; - } - - // Otherwise, if we're down to a single index, - // we've found our insertion point - if (min >= max) { - // Compare the found timestamp with the search - // value to decide if we'll insert after or before. - return min + ((found < value) ? 1 : 0); - } - - // Finally, do the recursive step - if (found < value) { - return this.binSearch(value, mid + 1, max); - } else { - return this.binSearch(value, min, mid - 1); - } - }; - - // Increase the size of the buffer - PlotLineBuffer.prototype.doubleBufferSize = function () { - var sz = Math.min(this.maxSize * 2, this.buffer.length * 2), - canDouble = sz > this.buffer.length, - doubled = canDouble && new Float32Array(sz); - - if (canDouble) { - doubled.set(this.buffer); // Copy contents of original - this.buffer = doubled; - } - - return canDouble; - }; - - // Decrease the size of the buffer - PlotLineBuffer.prototype.halveBufferSize = function () { - var sz = Math.max(this.initialSize * 2, this.buffer.length / 2), - canHalve = sz < this.buffer.length; - - if (canHalve) { - this.buffer = new Float32Array(this.buffer.subarray(0, sz)); - } - - return canHalve; - }; - - // Set a value in the buffer - PlotLineBuffer.prototype.setValue = function (index, domainValue, rangeValue) { - this.buffer[index * 2] = domainValue - this.domainOffset; - this.buffer[index * 2 + 1] = rangeValue; - // Track min/max of range values (min/max for - // domain values can be read directly from buffer) - this.rangeExtrema[0] = Math.min(this.rangeExtrema[0], rangeValue); - this.rangeExtrema[1] = Math.max(this.rangeExtrema[1], rangeValue); - }; - - /** - * Get the WebGL-displayable buffer of points to plot. - * @returns {Float32Array} displayable buffer for this line - */ - PlotLineBuffer.prototype.getBuffer = function () { - return this.buffer; - }; - - /** - * Get the number of points stored in this buffer. - * @returns {number} the number of points stored - */ - PlotLineBuffer.prototype.getLength = function () { - return this.length; - }; - - /** - * Get the min/max range values that are currently in this - * buffer. Unlike range extrema, these will change as the - * buffer gets trimmed. - * @returns {number[]} min, max domain values - */ - PlotLineBuffer.prototype.getDomainExtrema = function () { - // Since these are ordered in the buffer, assume - // these are the values at the first and last index - return [ - this.buffer[0] + this.domainOffset, - this.buffer[this.length * 2 - 2] + this.domainOffset - ]; - }; - - /** - * Get the min/max range values that have been observed for this - * buffer. Note that these values may have been trimmed out at - * some point. - * @returns {number[]} min, max range values - */ - PlotLineBuffer.prototype.getRangeExtrema = function () { - return this.rangeExtrema; - }; - - /** - * Remove values from this buffer. - * Normally, values are removed from the start - * of the buffer; a truthy value in the second argument - * will cause values to be removed from the end. - * @param {number} count number of values to remove - * @param {boolean} [fromEnd] true if the most recent - * values should be removed - */ - PlotLineBuffer.prototype.trim = function (count, fromEnd) { - // If we're removing values from the start... - if (!fromEnd) { - // ...do so by shifting buffer contents over - this.buffer.set(this.buffer.subarray(2 * count)); - } - // Reduce used buffer size accordingly - this.length -= count; - // Finally, if less than half of the buffer is being - // used, free up some memory. - if (this.length < this.buffer.length / 4) { - this.halveBufferSize(); - } - }; - - /** - * Insert data from the provided series at the specified - * index. If this would exceed the buffer's maximum capacity, - * this operation fails and the buffer is unchanged. - * @param {TelemetrySeries} series the series to insert - * @param {number} index the index at which to insert this - * series - * @returns {boolean} true if insertion succeeded; otherwise - * false - */ - PlotLineBuffer.prototype.insert = function (series, index) { - var sz = series.getPointCount(), - i; - - // Don't allow append after the end; that doesn't make sense - index = Math.min(index, this.length); - - // Resize if necessary - while (sz > ((this.buffer.length / 2) - this.length)) { - if (!this.doubleBufferSize()) { - // Can't make room for this, insertion fails - return false; - } - } - - // Shift data over if necessary - if (index < this.length) { - this.buffer.set( - this.buffer.subarray(index * 2, this.length * 2), - (index + sz) * 2 - ); - } - - // Insert data into the set - for (i = 0; i < sz; i += 1) { - this.setValue( - i + index, - series.getDomainValue(i), - series.getRangeValue(i) - ); - } - - // Increase the length - this.length += sz; - - // Indicate that insertion was successful - return true; - }; - - /** - * Append a single data point. - * @memberof platform/features/plot.PlotLineBuffer# - */ - PlotLineBuffer.prototype.insertPoint = function (domainValue, rangeValue) { - // Ensure there is space for this point - if (this.length >= (this.buffer.length / 2)) { - if (!this.doubleBufferSize()) { - return false; - } - } - - // Put the data in the buffer - this.setValue(this.length, domainValue, rangeValue); - - // Update length - this.length += 1; - - // Indicate that this was successful - return true; - }; - - /** - * Find an index for inserting data with this - * timestamp. The second argument indicates whether - * we are searching for insert-before or insert-after - * positions. - * Timestamps are meant to be unique, so if a collision - * occurs, this will return -1. - * @param {number} timestamp timestamp to insert - * @returns {number} the index for insertion (or -1) - */ - PlotLineBuffer.prototype.findInsertionIndex = function (timestamp) { - var value = timestamp - this.domainOffset; - - // Handle empty buffer case and check for an - // append opportunity (which is most common case for - // real-time data so is optimized-for) before falling - // back to a binary search for the insertion point. - return (this.length < 1) ? 0 : - (value > this.buffer[this.length * 2 - 2]) ? this.length : - this.binSearch(value, 0, this.length - 1); - }; - - return PlotLineBuffer; - } -); - diff --git a/platform/features/plot/src/elements/PlotPalette.js b/platform/features/plot/src/elements/PlotPalette.js deleted file mode 100644 index 4eaf833f3c..0000000000 --- a/platform/features/plot/src/elements/PlotPalette.js +++ /dev/null @@ -1,133 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * Plot palette. Defines colors for various plot lines. - */ -define( - function () { - - // Prepare different forms of the palette, since we wish to - // describe colors in several ways (as RGB 0-255, as - // RGB 0.0-1.0, or as stylesheet-appropriate #-prefixed colors). - var integerPalette = [ - [0x20, 0xB2, 0xAA], - [0x9A, 0xCD, 0x32], - [0xFF, 0x8C, 0x00], - [0xD2, 0xB4, 0x8C], - [0x40, 0xE0, 0xD0], - [0x41, 0x69, 0xFF], - [0xFF, 0xD7, 0x00], - [0x6A, 0x5A, 0xCD], - [0xEE, 0x82, 0xEE], - [0xCC, 0x99, 0x66], - [0x99, 0xCC, 0xCC], - [0x66, 0xCC, 0x33], - [0xFF, 0xCC, 0x00], - [0xFF, 0x66, 0x33], - [0xCC, 0x66, 0xFF], - [0xFF, 0x00, 0x66], - [0xFF, 0xFF, 0x00], - [0x80, 0x00, 0x80], - [0x00, 0x86, 0x8B], - [0x00, 0x8A, 0x00], - [0xFF, 0x00, 0x00], - [0x00, 0x00, 0xFF], - [0xF5, 0xDE, 0xB3], - [0xBC, 0x8F, 0x8F], - [0x46, 0x82, 0xB4], - [0xFF, 0xAF, 0xAF], - [0x43, 0xCD, 0x80], - [0xCD, 0xC1, 0xC5], - [0xA0, 0x52, 0x2D], - [0x64, 0x95, 0xED] - ], stringPalette = integerPalette.map(function (arr) { - // Convert to # notation for use in styles - return '#' + arr.map(function (c) { - return (c < 16 ? '0' : '') + c.toString(16); - }).join(''); - }), floatPalette = integerPalette.map(function (arr) { - return arr.map(function (c) { - return c / 255.0; - }).concat([1]); // RGBA - }); - - /** - * PlotPalette allows a consistent set of colors to be retrieved - * by index, in various color formats. All PlotPalette methods are - * static, so there is no need for a constructor call; using - * this will simply return PlotPalette itself. - * @memberof platform/features/plot - * @constructor - */ - function PlotPalette() { - return PlotPalette; - } - - /** - * Look up a color in the plot's palette, by index. - * This will be returned as a three element array of RGB - * values, as integers in the range of 0-255. - * @param {number} i the index of the color to look up - * @return {number[]} the color, as integer RGB values - */ - PlotPalette.getIntegerColor = function (i) { - return integerPalette[Math.floor(i) % integerPalette.length]; - }; - - - /** - * Look up a color in the plot's palette, by index. - * This will be returned as a three element array of RGB - * values, in the range of 0.0-1.0. - * - * This format is present specifically to support use with - * WebGL, which expects colors of that form. - * - * @param {number} i the index of the color to look up - * @return {number[]} the color, as floating-point RGB values - */ - PlotPalette.getFloatColor = function (i) { - return floatPalette[Math.floor(i) % floatPalette.length]; - }; - - - /** - * Look up a color in the plot's palette, by index. - * This will be returned as a string using #-prefixed - * six-digit RGB hex notation (e.g. #FF0000) - * See http://www.w3.org/TR/css3-color/#rgb-color. - * - * This format is useful for representing colors in in-line - * styles. - * - * @param {number} i the index of the color to look up - * @return {string} the color, as a style-friendly string - */ - PlotPalette.getStringColor = function (i) { - return stringPalette[Math.floor(i) % stringPalette.length]; - }; - - return PlotPalette; - - } -); diff --git a/platform/features/plot/src/elements/PlotPanZoomStack.js b/platform/features/plot/src/elements/PlotPanZoomStack.js deleted file mode 100644 index d2c763234f..0000000000 --- a/platform/features/plot/src/elements/PlotPanZoomStack.js +++ /dev/null @@ -1,141 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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 () { - - /** - * The PlotPanZoomStack is responsible for maintaining the - * pan-zoom state of a plot (expressed as a boundary starting - * at an origin and extending to certain dimensions) in a - * stack, to support the back and unzoom buttons in plot controls. - * - * Dimensions and origins are here described each by two-element - * arrays, where the first element describes a value or quantity - * along the domain axis, and the second element describes the same - * along the range axis. - * - * @memberof platform/features/plot - * @constructor - * @param {number[]} origin the plot's origin, initially - * @param {number[]} dimensions the plot's dimensions, initially - */ - function PlotPanZoomStack(origin, dimensions) { - // Use constructor parameters as the stack's initial state - this.stack = [{ origin: origin, dimensions: dimensions }]; - } - - // Various functions which follow are simply wrappers for - // normal stack-like array methods, with the exception that - // they prevent undesired modification and enforce that this - // stack must remain non-empty. - // See JSDoc for specific methods below for more detail. - - /** - * Get the current stack depth; that is, the number - * of items on the stack. A depth of one means that no - * panning or zooming relative to the base value has - * been applied. - * @returns {number} the depth of the stack - */ - PlotPanZoomStack.prototype.getDepth = function getDepth() { - return this.stack.length; - }; - - /** - * Push a new pan-zoom state onto the stack; this will - * become the active pan-zoom state. - * @param {number[]} origin the new origin - * @param {number[]} dimensions the new dimensions - */ - PlotPanZoomStack.prototype.pushPanZoom = function (origin, dimensions) { - this.stack.push({ origin: origin, dimensions: dimensions }); - }; - - /** - * Pop a pan-zoom state from the stack. Whatever pan-zoom - * state was previously present will become current. - * If called when there is only one pan-zoom state on the - * stack, this acts as a no-op (that is, the lowest - * pan-zoom state on the stack cannot be popped, to ensure - * that some pan-zoom state is always available.) - */ - PlotPanZoomStack.prototype.popPanZoom = function popPanZoom() { - if (this.stack.length > 1) { - this.stack.pop(); - } - }; - - /** - * Set the base pan-zoom state; that is, the state at the - * bottom of the stack. This allows the "unzoomed" state of - * a plot to be updated (e.g. as new data comes in) without - * interfering with the user's chosen zoom level. - * @param {number[]} origin the base origin - * @param {number[]} dimensions the base dimensions - * @memberof platform/features/plot.PlotPanZoomStack# - */ - PlotPanZoomStack.prototype.setBasePanZoom = function (origin, dimensions) { - this.stack[0] = { origin: origin, dimensions: dimensions }; - }; - - /** - * Clear the pan-zoom stack down to its bottom element; - * in effect, pop all elements but the last, e.g. to remove - * any temporary user modifications to pan-zoom state. - */ - PlotPanZoomStack.prototype.clearPanZoom = function clearPanZoom() { - this.stack = [this.stack[0]]; - }; - - /** - * Get the current pan-zoom state (the state at the top - * of the stack), expressed as an object with "origin" and - * "dimensions" fields. - * @returns {object} the current pan-zoom state - */ - PlotPanZoomStack.prototype.getPanZoom = function getPanZoom() { - return this.stack[this.stack.length - 1]; - }; - - /** - * Get the current origin, as represented on the top of the - * stack. - * @returns {number[]} the current plot origin - */ - PlotPanZoomStack.prototype.getOrigin = function getOrigin() { - return this.getPanZoom().origin; - }; - - /** - * Get the current dimensions, as represented on the top of - * the stack. - * @returns {number[]} the current plot dimensions - */ - PlotPanZoomStack.prototype.getDimensions = function getDimensions() { - return this.getPanZoom().dimensions; - }; - - return PlotPanZoomStack; - } -); diff --git a/platform/features/plot/src/elements/PlotPanZoomStackGroup.js b/platform/features/plot/src/elements/PlotPanZoomStackGroup.js deleted file mode 100644 index 5083978167..0000000000 --- a/platform/features/plot/src/elements/PlotPanZoomStackGroup.js +++ /dev/null @@ -1,167 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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( - ['./PlotPanZoomStack'], - function (PlotPanZoomStack) { - - /** - * A plot pan zoom stack group provides a collection of individual - * pan-zoom stacks that synchronize upon the domain axis, but - * remain independent upon the range axis. This supports panning - * and zooming in stacked-plot mode (and, importantly, - * stepping back through those states.) - * @memberof platform/features/plot - * @constructor - * @param {number} count the number of stacks to include in this - * group - */ - function PlotPanZoomStackGroup(count) { - var self = this; - - // Push a pan-zoom state; the index argument identifies - // which stack originated the request (all other stacks - // will ignore the range part of the change.) - function pushPanZoom(origin, dimensions, index) { - self.stacks.forEach(function (stack, i) { - if (i === index) { - // Do a normal push for the specified stack - stack.pushPanZoom(origin, dimensions); - } else { - // For other stacks, do a push, but repeat - // their current range axis bounds. - stack.pushPanZoom( - [origin[0], stack.getOrigin()[1]], - [dimensions[0], stack.getDimensions()[1]] - ); - } - }); - } - - - // Decorate a pan-zoom stack; returns an object with - // the same interface, but whose stack-mutation methods - // effect all items in the group. - function decorateStack(stack, index) { - var result = Object.create(stack); - - // Use the methods defined above - result.pushPanZoom = function (origin, dimensions) { - pushPanZoom(origin, dimensions, index); - }; - result.setBasePanZoom = function () { - self.setBasePanZoom.apply(self, arguments); - }; - result.popPanZoom = function () { - self.popPanZoom.apply(self, arguments); - }; - result.clearPanZoom = function () { - self.clearPanZoom.apply(self, arguments); - }; - - return result; - } - - // Create the stacks in this group ... - this.stacks = []; - while (this.stacks.length < count) { - this.stacks.push(new PlotPanZoomStack([], [])); - } - // ... and their decorated-to-synchronize versions. - this.decoratedStacks = this.stacks.map(decorateStack); - } - - /** - * Pop a pan-zoom state from all stacks in the group. - * If called when there is only one pan-zoom state on each - * stack, this acts as a no-op (that is, the lowest - * pan-zoom state on the stack cannot be popped, to ensure - * that some pan-zoom state is always available.) - */ - PlotPanZoomStackGroup.prototype.popPanZoom = function () { - this.stacks.forEach(function (stack) { - stack.popPanZoom(); - }); - }; - - /** - * Set the base pan-zoom state for all stacks in this group. - * This changes the state at the bottom of each stack. - * This allows the "unzoomed" state of plots to be updated - * (e.g. as new data comes in) without - * interfering with the user's chosen pan/zoom states. - * @param {number[]} origin the base origin - * @param {number[]} dimensions the base dimensions - */ - PlotPanZoomStackGroup.prototype.setBasePanZoom = function (origin, dimensions) { - this.stacks.forEach(function (stack) { - stack.setBasePanZoom(origin, dimensions); - }); - }; - - /** - * Clear all pan-zoom stacks in this group down to - * their bottom element; in effect, pop all elements - * but the last, e.g. to remove any temporary user - * modifications to pan-zoom state. - */ - PlotPanZoomStackGroup.prototype.clearPanZoom = function () { - this.stacks.forEach(function (stack) { - stack.clearPanZoom(); - }); - }; - - /** - * Get the current stack depth; that is, the number - * of items on each stack in the group. - * A depth of one means that no - * panning or zooming relative to the base value has - * been applied. - * @returns {number} the depth of the stacks in this group - */ - PlotPanZoomStackGroup.prototype.getDepth = function () { - // All stacks are kept in sync, so look up depth - // from the first one. - return this.stacks.length > 0 ? this.stacks[0].getDepth() : 0; - }; - - /** - * Get a specific pan-zoom stack in this group. - * Stacks are specified by index; this index must be less - * than the count provided at construction time, and must - * not be less than zero. - * The stack returned by this function will be synchronized - * to other stacks in this group; that is, mutating that - * stack directly will result in other stacks in this group - * undergoing similar updates to ensure that domain bounds - * remain the same. - * @param {number} index the index of the stack to get - * @returns {PlotPanZoomStack} the pan-zoom stack in the - * group identified by that index - */ - PlotPanZoomStackGroup.prototype.getPanZoomStack = function (index) { - return this.decoratedStacks[index]; - }; - - return PlotPanZoomStackGroup; - } -); diff --git a/platform/features/plot/src/elements/PlotPosition.js b/platform/features/plot/src/elements/PlotPosition.js deleted file mode 100644 index a649f29946..0000000000 --- a/platform/features/plot/src/elements/PlotPosition.js +++ /dev/null @@ -1,95 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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 () { - - /** - * A PlotPosition converts from pixel coordinates to domain-range - * coordinates, based on the current plot boundary as described on - * the pan-zoom stack. - * - * These coordinates are not updated after construction; that is, - * they represent the result of the conversion at the time the - * PlotPosition was instantiated. Care should be taken when retaining - * PlotPosition objects across changes to the pan-zoom stack. - * - * @memberof platform/features/plot - * @constructor - * @param {number} x the horizontal pixel position in the plot area - * @param {number} y the vertical pixel position in the plot area - * @param {number} width the width of the plot area - * @param {number} height the height of the plot area - * @param {PanZoomStack} panZoomStack the applicable pan-zoom stack, - * used to determine the plot's domain-range boundaries. - */ - function PlotPosition(x, y, width, height, panZoomStack) { - var panZoom = panZoomStack.getPanZoom(), - origin = panZoom.origin, - dimensions = panZoom.dimensions; - - function convert(v, i) { - return v * dimensions[i] + origin[i]; - } - - if (!dimensions || !origin) { - // We need both dimensions and origin to compute a position - this.position = []; - } else { - // Convert from pixel to domain-range space. - // Note that range is reversed from the y-axis in pixel space - //(positive range points up, positive pixel-y points down) - this.position = - [x / width, (height - y) / height].map(convert); - } - } - - /** - * Get the domain value corresponding to this pixel position. - * @returns {number} the domain value - */ - PlotPosition.prototype.getDomain = function () { - return this.position[0]; - }; - - /** - * Get the range value corresponding to this pixel position. - * @returns {number} the range value - */ - PlotPosition.prototype.getRange = function () { - return this.position[1]; - }; - - /** - * Get the domain and values corresponding to this - * pixel position. - * @returns {number[]} an array containing the domain and - * the range value, in that order - */ - PlotPosition.prototype.getPosition = function () { - return this.position; - }; - - return PlotPosition; - } -); diff --git a/platform/features/plot/src/elements/PlotPreparer.js b/platform/features/plot/src/elements/PlotPreparer.js deleted file mode 100644 index e6da435229..0000000000 --- a/platform/features/plot/src/elements/PlotPreparer.js +++ /dev/null @@ -1,153 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * Prepares data to be rendered in a GL Plot. Handles - * the conversion from data API to displayable buffers. - */ -define( - function () { - - function identity(x) { - return x; - } - - /** - * The PlotPreparer is responsible for handling data sets and - * preparing them to be rendered. It creates a WebGL-plottable - * Float32Array for each trace, and tracks the boundaries of the - * data sets (since this is convenient to do during the same pass). - * @memberof platform/features/plot - * @constructor - * @param {Telemetry[]} datas telemetry data objects - * @param {string} domain the key to use when looking up domain values - * @param {string} range the key to use when looking up range values - */ - function PlotPreparer(datas, domain, range) { - var index, - vertices = [], - max = [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY], - min = [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY], - x, - y, - domainOffset = Number.POSITIVE_INFINITY; - - // Remove any undefined data sets - datas = (datas || []).filter(identity); - - // Do a first pass to determine the domain offset. - // This will be use to reduce the magnitude of domain values - // in the buffer, to minimize loss-of-precision when - // converting to a 32-bit float. - datas.forEach(function (data) { - domainOffset = Math.min(data.getDomainValue(0, domain), domainOffset); - }); - - // Assemble buffers, and track bounds of the data present - datas.forEach(function (data, i) { - vertices.push([]); - for (index = 0; index < data.getPointCount(); index += 1) { - x = data.getDomainValue(index, domain); - y = data.getRangeValue(index, range); - vertices[i].push(x - domainOffset); - vertices[i].push(y); - min[0] = Math.min(min[0], x); - min[1] = Math.min(min[1], y); - max[0] = Math.max(max[0], x); - max[1] = Math.max(max[1], y); - } - }); - - // If range is empty, add some padding - if (max[1] === min[1]) { - max[1] = max[1] + 1.0; - min[1] = min[1] - 1.0; - } - - // Convert to Float32Array - this.buffers = vertices.map(function (v) { - return new Float32Array(v); - }); - - this.min = min; - this.max = max; - this.domainOffset = domainOffset; - } - - /** - * Get the dimensions which bound all data in the provided - * data sets. This is given as a two-element array where the - * first element is domain, and second is range. - * @returns {number[]} the dimensions which bound this data set - */ - PlotPreparer.prototype.getDimensions = function () { - var max = this.max, min = this.min; - return [max[0] - min[0], max[1] - min[1]]; - }; - - /** - * Get the origin of this data set's boundary. - * This is given as a two-element array where the - * first element is domain, and second is range. - * The domain value here is not adjusted by the domain offset. - * @returns {number[]} the origin of this data set's boundary - */ - PlotPreparer.prototype.getOrigin = function () { - return this.min; - }; - - /** - * Get the domain offset; this offset will have been subtracted - * from all domain values in all buffers returned by this - * preparer, in order to minimize loss-of-precision due to - * conversion to the 32-bit float format needed by WebGL. - * @returns {number} the domain offset - */ - PlotPreparer.prototype.getDomainOffset = function () { - return this.domainOffset; - }; - - /** - * Get all renderable buffers for this data set. This will - * be returned as an array which can be correlated back to - * the provided telemetry data objects (from the constructor - * call) by index. - * - * Internally, these are flattened; each buffer contains a - * sequence of alternating domain and range values. - * - * All domain values in all buffers will have been adjusted - * from their original values by subtraction of the domain - * offset; this minimizes loss-of-precision resulting from - * the conversion to 32-bit floats, which may otherwise - * cause aliasing artifacts (particularly for timestamps) - * - * @returns {Float32Array[]} the buffers for these traces - */ - PlotPreparer.prototype.getBuffers = function () { - return this.buffers; - }; - - return PlotPreparer; - - } -); diff --git a/platform/features/plot/src/elements/PlotSeriesWindow.js b/platform/features/plot/src/elements/PlotSeriesWindow.js deleted file mode 100644 index 4907254c9c..0000000000 --- a/platform/features/plot/src/elements/PlotSeriesWindow.js +++ /dev/null @@ -1,80 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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 () { - - /** - * Provides a window on a telemetry data series, to support - * insertion into a plot line. - * @constructor - * @memberof platform/features/plot - * @implements {TelemetrySeries} - */ - function PlotSeriesWindow(series, domain, range, start, end) { - this.series = series; - this.domain = domain; - this.range = range; - this.start = start; - this.end = end; - } - - PlotSeriesWindow.prototype.getPointCount = function () { - return this.end - this.start; - }; - - PlotSeriesWindow.prototype.getDomainValue = function (index) { - return this.series.getDomainValue(index + this.start, this.domain); - }; - - PlotSeriesWindow.prototype.getRangeValue = function (index) { - return this.series.getRangeValue(index + this.start, this.range); - }; - - /** - * Split this series into two series of equal (or nearly-equal) size. - * @returns {PlotSeriesWindow[]} two series - */ - PlotSeriesWindow.prototype.split = function () { - var mid = Math.floor((this.end + this.start) / 2); - return ((this.end - this.start) > 1) ? - [ - new PlotSeriesWindow( - this.series, - this.domain, - this.range, - this.start, - mid - ), - new PlotSeriesWindow( - this.series, - this.domain, - this.range, - mid, - this.end - ) - ] : []; - }; - - return PlotSeriesWindow; - } -); diff --git a/platform/features/plot/src/elements/PlotTelemetryFormatter.js b/platform/features/plot/src/elements/PlotTelemetryFormatter.js deleted file mode 100644 index d4baaa7061..0000000000 --- a/platform/features/plot/src/elements/PlotTelemetryFormatter.js +++ /dev/null @@ -1,76 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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 () { - - var DIGITS = 3; - - /** - * Wraps a `TelemetryFormatter` to provide formats for domain and - * range values; provides a single place to track domain/range - * formats within a plot, allowing other plot elements to simply - * request that values be formatted. - * @constructor - * @memberof platform/features/plot - * @implements {platform/telemetry.TelemetryFormatter} - * @param {TelemetryFormatter} telemetryFormatter the formatter - * to wrap. - */ - function PlotTelemetryFormatter(telemetryFormatter) { - this.telemetryFormatter = telemetryFormatter; - } - - /** - * Specify the format to use for domain values. - * @param {string} key the format's identifier - */ - PlotTelemetryFormatter.prototype.setDomainFormat = function (key) { - this.domainFormat = key; - }; - - /** - * Specify the format to use for range values. - * @param {string} key the format's identifier - */ - PlotTelemetryFormatter.prototype.setRangeFormat = function (key) { - this.rangeFormat = key; - }; - - PlotTelemetryFormatter.prototype.formatDomainValue = function (value) { - return this.telemetryFormatter - .formatDomainValue(value, this.domainFormat); - }; - - PlotTelemetryFormatter.prototype.formatRangeValue = function (value) { - if (typeof value === 'number') { - return value.toFixed(DIGITS); - } - - return this.telemetryFormatter - .formatRangeValue(value, this.rangeFormat); - }; - - return PlotTelemetryFormatter; - } -); diff --git a/platform/features/plot/src/elements/PlotTickGenerator.js b/platform/features/plot/src/elements/PlotTickGenerator.js deleted file mode 100644 index c467ca0c76..0000000000 --- a/platform/features/plot/src/elements/PlotTickGenerator.js +++ /dev/null @@ -1,102 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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 () { - - /** - * The PlotTickGenerator provides labels for ticks along the - * domain and range axes of the plot, to support the plot - * template. - * - * @memberof platform/features/plot - * @constructor - * @param {PlotPanZoomStack} panZoomStack the pan-zoom stack for - * this plot, used to determine plot boundaries - * @param {TelemetryFormatter} formatter used to format (for display) - * domain and range values. - */ - function PlotTickGenerator(panZoomStack, formatter) { - this.panZoomStack = panZoomStack; - this.formatter = formatter; - } - - // For phantomjs compatibility, for headless testing - // (Function.prototype.bind unsupported) - function bind(fn, thisObj) { - return fn.bind ? fn.bind(thisObj) : function () { - return fn.apply(thisObj, arguments); - }; - } - - // Generate ticks; interpolate from start up to - // start + span in count steps, using the provided - // formatter to represent each value. - PlotTickGenerator.prototype.generateTicks = function (start, span, count, format) { - var step = span / (count - 1), - result = [], - i; - - for (i = 0; i < count; i += 1) { - result.push({ - //If data to show, display label for each tick line, otherwise show lines but suppress labels. - label: span > 0 ? format(i * step + start) : '' - }); - } - - return result; - }; - - /** - * Generate tick marks for the domain axis. - * @param {number} count the number of ticks - * @returns {string[]} labels for those ticks - */ - PlotTickGenerator.prototype.generateDomainTicks = function (count) { - var panZoom = this.panZoomStack.getPanZoom(); - return this.generateTicks( - panZoom.origin[0], - panZoom.dimensions[0], - count, - bind(this.formatter.formatDomainValue, this.formatter) - ); - }; - - /** - * Generate tick marks for the range axis. - * @param {number} count the number of ticks - * @returns {string[]} labels for those ticks - */ - PlotTickGenerator.prototype.generateRangeTicks = function (count) { - var panZoom = this.panZoomStack.getPanZoom(); - return this.generateTicks( - panZoom.origin[1], - panZoom.dimensions[1], - count, - bind(this.formatter.formatRangeValue, this.formatter) - ); - }; - - return PlotTickGenerator; - } -); diff --git a/platform/features/plot/src/elements/PlotUpdater.js b/platform/features/plot/src/elements/PlotUpdater.js deleted file mode 100644 index 633b9ea9d5..0000000000 --- a/platform/features/plot/src/elements/PlotUpdater.js +++ /dev/null @@ -1,353 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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( - ['./PlotLine', './PlotLineBuffer'], - function (PlotLine, PlotLineBuffer) { - - var MAX_POINTS = 86400, - PADDING_RATIO = 0.10, // Padding percentage for top & bottom - INITIAL_SIZE = 675; // 1/128 of MAX_POINTS - - /** - * The PlotPreparer is responsible for handling data sets and - * preparing them to be rendered. It creates a WebGL-plottable - * Float32Array for each trace, and tracks the boundaries of the - * data sets (since this is convenient to do during the same pass). - * @memberof platform/features/plot - * @constructor - * @param {TelemetryHandle} handle the handle to telemetry access - * @param {string} domain the key to use when looking up domain values - * @param {string} range the key to use when looking up range values - * @param {number} fixedDuration maximum plot duration to display - * @param {number} maxPoints maximum number of points to display - */ - function PlotUpdater(handle, domain, range, fixedDuration, maxPoints) { - this.handle = handle; - this.domain = domain; - this.range = range; - this.fixedDuration = fixedDuration; - this.maxPoints = maxPoints; - - this.ids = []; - this.lines = {}; - this.buffers = {}; - this.bufferArray = []; - - // Use a default MAX_POINTS if none is provided - this.maxPoints = maxPoints !== undefined ? maxPoints : MAX_POINTS; - this.dimensions = [0, 0]; - this.origin = [0, 0]; - - // Initially prepare state for these objects. - // Note that this may be an empty array at this time, - // so we also need to check during update cycles. - this.update(); - } - - // Look up a domain object's id (for mapping, below) - function getId(domainObject) { - return domainObject.getId(); - } - - // Used in the reduce step of updateExtrema - function reduceExtrema(a, b) { - return [Math.min(a[0], b[0]), Math.max(a[1], b[1])]; - } - - // Convert a domain/range extrema to plot dimensions - function dimensionsOf(extrema) { - return extrema[1] - extrema[0]; - } - - // Convert a domain/range extrema to a plot origin - function originOf(extrema) { - return extrema[0]; - } - - // Check if this set of ids matches the current set of ids - // (used to detect if line preparation can be skipped) - PlotUpdater.prototype.idsMatch = function (nextIds) { - var ids = this.ids; - return ids.length === nextIds.length && - nextIds.every(function (id, index) { - return ids[index] === id; - }); - }; - - // Prepare plot lines for this group of telemetry objects - PlotUpdater.prototype.prepareLines = function (telemetryObjects) { - var nextIds = telemetryObjects.map(getId), - next = {}, - self = this; - - // Detect if we already have everything we need prepared - if (this.idsMatch(nextIds)) { - // Nothing to prepare, move on - return; - } - - // Built up a set of ids. Note that we can only - // create plot lines after our domain offset has - // been determined. - if (this.domainOffset !== undefined) { - // Update list of ids in use - this.ids = nextIds; - - // Create buffers for these objects - this.bufferArray = this.ids.map(function (id) { - self.buffers[id] = self.buffers[id] || new PlotLineBuffer( - self.domainOffset, - INITIAL_SIZE, - self.maxPoints - ); - next[id] = - self.lines[id] || new PlotLine(self.buffers[id]); - return self.buffers[id]; - }); - } - - // If there are no more lines, clear the domain offset - if (Object.keys(next).length < 1) { - this.domainOffset = undefined; - } - - // Update to the current set of lines - this.lines = next; - }; - - // Initialize the domain offset, based on these observed values - PlotUpdater.prototype.initializeDomainOffset = function (values) { - this.domainOffset = - ((this.domainOffset === undefined) && (values.length > 0)) ? - (values.reduce(function (a, b) { - return (a || 0) + (b || 0); - }, 0) / values.length) : - this.domainOffset; - }; - - // Expand range slightly so points near edges are visible - PlotUpdater.prototype.expandRange = function () { - var padding = PADDING_RATIO * this.dimensions[1], - top; - padding = Math.max(padding, 1.0); - top = Math.ceil(this.origin[1] + this.dimensions[1] + padding / 2); - this.origin[1] = Math.floor(this.origin[1] - padding / 2); - this.dimensions[1] = top - this.origin[1]; - }; - - // Update dimensions and origin based on extrema of plots - PlotUpdater.prototype.updateBounds = function () { - var bufferArray = this.bufferArray.filter(function (lineBuffer) { - return lineBuffer.getLength() > 0; // Ignore empty lines - }), - priorDomainOrigin = this.origin[0], - priorDomainDimensions = this.dimensions[0]; - - if (bufferArray.length > 0) { - this.domainExtrema = bufferArray.map(function (lineBuffer) { - return lineBuffer.getDomainExtrema(); - }).reduce(reduceExtrema); - - this.rangeExtrema = bufferArray.map(function (lineBuffer) { - return lineBuffer.getRangeExtrema(); - }).reduce(reduceExtrema); - - // Calculate best-fit dimensions - this.dimensions = [this.domainExtrema, this.rangeExtrema] - .map(dimensionsOf); - this.origin = [this.domainExtrema, this.rangeExtrema] - .map(originOf); - - // Enforce some minimum visible area - this.expandRange(); - - // Suppress domain changes when pinned - if (this.hasSpecificDomainBounds) { - this.origin[0] = priorDomainOrigin; - this.dimensions[0] = priorDomainDimensions; - if (this.following) { - this.origin[0] = Math.max( - this.domainExtrema[1] - this.dimensions[0], - this.origin[0] - ); - } - } - - // ...then enforce a fixed duration if needed - if (this.fixedDuration !== undefined) { - this.origin[0] = this.origin[0] + this.dimensions[0] - - this.fixedDuration; - this.dimensions[0] = this.fixedDuration; - } - } - }; - - // Add latest data for this domain object - PlotUpdater.prototype.addPointFor = function (domainObject) { - var line = this.lines[domainObject.getId()]; - if (line) { - line.addPoint( - this.handle.getDomainValue(domainObject, this.domain), - this.handle.getRangeValue(domainObject, this.range) - ); - } - }; - - /** - * Update with latest data. - */ - PlotUpdater.prototype.update = function update() { - var objects = this.handle.getTelemetryObjects(), - self = this; - - // Initialize domain offset if necessary - if (this.domainOffset === undefined) { - this.initializeDomainOffset(objects.map(function (obj) { - return self.handle.getDomainValue(obj, self.domain); - }).filter(function (value) { - return typeof value === 'number'; - })); - } - - // Make sure lines are available - this.prepareLines(objects); - - // Add new data - objects.forEach(function (domainObject, index) { - self.addPointFor(domainObject, index); - }); - - // Then, update extrema - this.updateBounds(); - }; - - /** - * Get the dimensions which bound all data in the provided - * data sets. This is given as a two-element array where the - * first element is domain, and second is range. - * @returns {number[]} the dimensions which bound this data set - */ - PlotUpdater.prototype.getDimensions = function () { - return this.dimensions; - }; - - /** - * Get the origin of this data set's boundary. - * This is given as a two-element array where the - * first element is domain, and second is range. - * The domain value here is not adjusted by the domain offset. - * @returns {number[]} the origin of this data set's boundary - */ - PlotUpdater.prototype.getOrigin = function () { - return this.origin; - }; - - /** - * Get the domain offset; this offset will have been subtracted - * from all domain values in all buffers returned by this - * preparer, in order to minimize loss-of-precision due to - * conversion to the 32-bit float format needed by WebGL. - * @returns {number} the domain offset - * @memberof platform/features/plot.PlotUpdater# - */ - PlotUpdater.prototype.getDomainOffset = function () { - return this.domainOffset; - }; - - /** - * Get all renderable buffers for this data set. This will - * be returned as an array which can be correlated back to - * the provided telemetry data objects (from the constructor - * call) by index. - * - * Internally, these are flattened; each buffer contains a - * sequence of alternating domain and range values. - * - * All domain values in all buffers will have been adjusted - * from their original values by subtraction of the domain - * offset; this minimizes loss-of-precision resulting from - * the conversion to 32-bit floats, which may otherwise - * cause aliasing artifacts (particularly for timestamps) - * - * @returns {Float32Array[]} the buffers for these traces - * @memberof platform/features/plot.PlotUpdater# - */ - PlotUpdater.prototype.getLineBuffers = function () { - return this.bufferArray; - }; - - /** - * Set the start and end boundaries (usually time) for the - * domain axis of this updater. - */ - PlotUpdater.prototype.setDomainBounds = function (start, end) { - this.fixedDuration = end - start; - this.origin[0] = start; - this.dimensions[0] = this.fixedDuration; - - // Suppress follow behavior if we have windowed in on the past - this.hasSpecificDomainBounds = true; - this.following = - !this.domainExtrema || (end >= this.domainExtrema[1]); - }; - - /** - * Fill in historical data. - */ - PlotUpdater.prototype.addHistorical = function (domainObject, series) { - var count = series ? series.getPointCount() : 0, - line; - - // Nothing to do if it's an empty series - if (count < 1) { - return; - } - - // Initialize domain offset if necessary - if (this.domainOffset === undefined) { - this.initializeDomainOffset([ - series.getDomainValue(0, this.domain), - series.getDomainValue(count - 1, this.domain) - ]); - } - - // Make sure lines are available - this.prepareLines(this.handle.getTelemetryObjects()); - - // Look up the line for this domain object - line = this.lines[domainObject.getId()]; - - // ...and put the data into it. - if (line) { - line.addSeries(series, this.domain, this.range); - } - - // Update extrema - this.updateBounds(); - }; - - return PlotUpdater; - - } -); - diff --git a/platform/features/plot/src/modes/PlotModeOptions.js b/platform/features/plot/src/modes/PlotModeOptions.js deleted file mode 100644 index 671adab6d4..0000000000 --- a/platform/features/plot/src/modes/PlotModeOptions.js +++ /dev/null @@ -1,155 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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( - ["./PlotOverlayMode", "./PlotStackMode"], - function (PlotOverlayMode, PlotStackMode) { - - var STACKED = { - key: "stacked", - name: "Stacked", - cssClass: "icon-plot-stacked", - Constructor: PlotStackMode - }, - OVERLAID = { - key: "overlaid", - name: "Overlaid", - cssClass: "icon-plot-overlay", - Constructor: PlotOverlayMode - }; - - /** - * Handles distinct behavior associated with different - * plot modes. - * - * @interface platform/features/plot.PlotModeHandler - * @private - */ - - /** - * Plot telemetry to the sub-plot(s) managed by this mode. - * @param {platform/features/plot.PlotUpdater} updater a source - * of data that is ready to plot - * @method platform/features/plot.PlotModeHandler#plotTelemetry - */ - /** - * Get all sub-plots to be displayed in this mode; used - * to populate the plot template. - * @return {platform/features/plot.SubPlot[]} all sub-plots to - * display in this mode - * @method platform/features/plot.PlotModeHandler#getSubPlots - */ - /** - * Check if we are not in our base pan-zoom state (that is, - * there are some temporary user modifications to the - * current pan-zoom state.) - * @returns {boolean} true if not in the base pan-zoom state - * @method platform/features/plot.PlotModeHandler#isZoomed - */ - /** - * Undo the most recent pan/zoom change and restore - * the prior state. - * @method platform/features/plot.PlotModeHandler#stepBackPanZoom - */ - /** - * Undo all pan/zoom change and restore the base state. - * @method platform/features/plot.PlotModeHandler#unzoom - */ - - /** - * Determines which plotting modes (stacked/overlaid) - * are applicable in a given plot view, maintains current - * selection state thereof, and provides handlers for the - * different behaviors associated with these modes. - * @memberof platform/features/plot - * @constructor - * @param {DomainObject[]} telemetryObjects the telemetry objects being - * represented in this plot view - * @param {platform/features/plot.SubPlotFactory} subPlotFactory a - * factory for creating sub-plots - */ - function PlotModeOptions(telemetryObjects, subPlotFactory) { - this.options = telemetryObjects.length > 1 ? - [OVERLAID, STACKED] : [OVERLAID]; - this.mode = this.options[0]; // Initial selection (overlaid) - this.telemetryObjects = telemetryObjects; - this.subPlotFactory = subPlotFactory; - } - - /** - * Get a handler for the current mode. This will handle - * plotting telemetry, providing subplots for the template, - * and view-level interactions with pan-zoom state. - * @returns {PlotOverlayMode|PlotStackMode} a handler - * for the current mode - */ - PlotModeOptions.prototype.getModeHandler = function () { - // Lazily initialize - if (!this.modeHandler) { - this.modeHandler = new this.mode.Constructor( - this.telemetryObjects, - this.subPlotFactory - ); - } - return this.modeHandler; - }; - - /** - * Get all mode options available for each plot. Each - * mode contains a `name` and `cssClass` field suitable - * for display in a template. - * @return {Array} the available modes - */ - PlotModeOptions.prototype.getModeOptions = function () { - return this.options; - }; - - /** - * Get the plotting mode option currently in use. - * This will be one of the elements returned from - * `getModeOptions`. - * @return {*} the current mode - */ - PlotModeOptions.prototype.getMode = function () { - return this.mode; - }; - - /** - * Set the plotting mode option to use. - * The passed argument must be one of the options - * returned by `getModeOptions`. - * @param {object} option one of the plot mode options - * from `getModeOptions` - */ - PlotModeOptions.prototype.setMode = function (option) { - if (this.mode !== option) { - this.mode = option; - // Clear the existing mode handler, so it - // can be instantiated next time it's needed. - this.modeHandler = undefined; - } - }; - - - return PlotModeOptions; - } -); diff --git a/platform/features/plot/src/modes/PlotOverlayMode.js b/platform/features/plot/src/modes/PlotOverlayMode.js deleted file mode 100644 index 993a58d376..0000000000 --- a/platform/features/plot/src/modes/PlotOverlayMode.js +++ /dev/null @@ -1,88 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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( - ["../SubPlot", "../elements/PlotPalette", "../elements/PlotPanZoomStack"], - function (SubPlot, PlotPalette, PlotPanZoomStack) { - - /** - * Handles plotting in Overlaid mode. In overlaid mode, there - * is one sub-plot which contains all plotted objects. - * @memberof platform/features/plot - * @constructor - * @implements {platform/features/plot.PlotModeHandler} - * @param {DomainObject[]} the domain objects to be plotted - */ - function PlotOverlayMode(telemetryObjects, subPlotFactory) { - this.panZoomStack = new PlotPanZoomStack([], []); - this.subplot = subPlotFactory.createSubPlot( - telemetryObjects, - this.panZoomStack - ); - this.subplots = [this.subplot]; - } - - PlotOverlayMode.prototype.plotTelemetry = function (updater) { - // Fit to the boundaries of the data, but don't - // override any user-initiated pan-zoom changes. - this.panZoomStack.setBasePanZoom( - updater.getOrigin(), - updater.getDimensions() - ); - - // Track the domain offset, used to bias domain values - // to minimize loss of precision when converted to 32-bit - // floating point values for display. - this.subplot.setDomainOffset(updater.getDomainOffset()); - - // Draw the buffers. Select color by index. - this.subplot.getDrawingObject().lines = - updater.getLineBuffers().map(function (buf, i) { - return { - buffer: buf.getBuffer(), - color: PlotPalette.getFloatColor(i), - points: buf.getLength() - }; - }); - }; - - PlotOverlayMode.prototype.getSubPlots = function () { - return this.subplots; - }; - - PlotOverlayMode.prototype.isZoomed = function () { - return this.panZoomStack.getDepth() > 1; - }; - - PlotOverlayMode.prototype.stepBackPanZoom = function () { - this.panZoomStack.popPanZoom(); - this.subplot.update(); - }; - - PlotOverlayMode.prototype.unzoom = function () { - this.panZoomStack.clearPanZoom(); - this.subplot.update(); - }; - - return PlotOverlayMode; - } -); diff --git a/platform/features/plot/src/modes/PlotStackMode.js b/platform/features/plot/src/modes/PlotStackMode.js deleted file mode 100644 index 8363c032bc..0000000000 --- a/platform/features/plot/src/modes/PlotStackMode.js +++ /dev/null @@ -1,104 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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( - ["../SubPlot", "../elements/PlotPalette", "../elements/PlotPanZoomStackGroup"], - function (SubPlot, PlotPalette, PlotPanZoomStackGroup) { - - /** - * Handles plotting in Stacked mode. In stacked mode, there - * is one sub-plot for each plotted object. - * @memberof platform/features/plot - * @constructor - * @implements {platform/features/plot.PlotModeHandler} - * @param {DomainObject[]} the domain objects to be plotted - */ - function PlotStackMode(telemetryObjects, subPlotFactory) { - var self = this; - - this.panZoomStackGroup = - new PlotPanZoomStackGroup(telemetryObjects.length); - - this.subplots = telemetryObjects.map(function (telemetryObject, i) { - return subPlotFactory.createSubPlot( - [telemetryObject], - self.panZoomStackGroup.getPanZoomStack(i) - ); - }); - } - - PlotStackMode.prototype.plotTelemetryTo = function (subplot, prepared, index) { - var buffer = prepared.getLineBuffers()[index]; - - // Track the domain offset, used to bias domain values - // to minimize loss of precision when converted to 32-bit - // floating point values for display. - subplot.setDomainOffset(prepared.getDomainOffset()); - - // Draw the buffers. Always use the 0th color, because there - // is one line per plot. - subplot.getDrawingObject().lines = [{ - buffer: buffer.getBuffer(), - color: PlotPalette.getFloatColor(0), - points: buffer.getLength() - }]; - }; - - PlotStackMode.prototype.plotTelemetry = function (prepared) { - var self = this; - // Fit to the boundaries of the data, but don't - // override any user-initiated pan-zoom changes. - this.panZoomStackGroup.setBasePanZoom( - prepared.getOrigin(), - prepared.getDimensions() - ); - - this.subplots.forEach(function (subplot, index) { - self.plotTelemetryTo(subplot, prepared, index); - }); - }; - - PlotStackMode.prototype.getSubPlots = function () { - return this.subplots; - }; - - PlotStackMode.prototype.isZoomed = function () { - return this.panZoomStackGroup.getDepth() > 1; - }; - - PlotStackMode.prototype.stepBackPanZoom = function () { - this.panZoomStackGroup.popPanZoom(); - this.subplots.forEach(function (subplot) { - subplot.update(); - }); - }; - - PlotStackMode.prototype.unzoom = function () { - this.panZoomStackGroup.clearPanZoom(); - this.subplots.forEach(function (subplot) { - subplot.update(); - }); - }; - - return PlotStackMode; - } -); diff --git a/platform/features/plot/test/Canvas2DChartSpec.js b/platform/features/plot/test/Canvas2DChartSpec.js deleted file mode 100644 index 28fe85b088..0000000000 --- a/platform/features/plot/test/Canvas2DChartSpec.js +++ /dev/null @@ -1,95 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../src/Canvas2DChart"], - function (Canvas2DChart) { - - describe("A canvas 2d chart", function () { - var mockCanvas, - mock2d, - chart; - - beforeEach(function () { - mockCanvas = jasmine.createSpyObj("canvas", ["getContext"]); - mock2d = jasmine.createSpyObj( - "2d", - [ - "clearRect", - "beginPath", - "moveTo", - "lineTo", - "stroke", - "fillRect" - ] - ); - mockCanvas.getContext.andReturn(mock2d); - - chart = new Canvas2DChart(mockCanvas); - }); - - // Note that tests below are less specific than they - // could be, esp. w.r.t. arguments to drawing calls; - // this is a fallback option so is a lower test priority. - - it("allows the canvas to be cleared", function () { - chart.clear(); - expect(mock2d.clearRect).toHaveBeenCalled(); - }); - - it("does not construct if 2D is unavailable", function () { - mockCanvas.getContext.andReturn(undefined); - expect(function () { - return new Canvas2DChart(mockCanvas); - }).toThrow(); - }); - - it("allows dimensions to be set", function () { - // No return value, just verify API is present - chart.setDimensions([120, 120], [0, 10]); - }); - - it("allows lines to be drawn", function () { - var testBuffer = [0, 1, 3, 8], - testColor = [0.25, 0.33, 0.66, 1.0], - testPoints = 2; - chart.drawLine(testBuffer, testColor, testPoints); - expect(mock2d.beginPath).toHaveBeenCalled(); - expect(mock2d.lineTo.calls.length).toEqual(1); - expect(mock2d.stroke).toHaveBeenCalled(); - }); - - it("allows squares to be drawn", function () { - var testMin = [0, 1], - testMax = [10, 10], - testColor = [0.25, 0.33, 0.66, 1.0]; - - chart.drawSquare(testMin, testMax, testColor); - expect(mock2d.fillRect).toHaveBeenCalled(); - }); - - }); - } -); diff --git a/platform/features/plot/test/GLChartSpec.js b/platform/features/plot/test/GLChartSpec.js deleted file mode 100644 index cd9afcb91f..0000000000 --- a/platform/features/plot/test/GLChartSpec.js +++ /dev/null @@ -1,143 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../src/GLChart"], - function (GLChart) { - - describe("A WebGL chart", function () { - var mockCanvas, - mockGL, - glChart; - - beforeEach(function () { - mockCanvas = jasmine.createSpyObj("canvas", ["getContext"]); - mockGL = jasmine.createSpyObj( - "gl", - [ - "createShader", - "compileShader", - "shaderSource", - "attachShader", - "createProgram", - "linkProgram", - "useProgram", - "enableVertexAttribArray", - "getAttribLocation", - "getUniformLocation", - "createBuffer", - "lineWidth", - "enable", - "blendFunc", - "viewport", - "clear", - "uniform2fv", - "uniform4fv", - "bufferData", - "bindBuffer", - "vertexAttribPointer", - "drawArrays" - ] - ); - mockGL.ARRAY_BUFFER = "ARRAY_BUFFER"; - mockGL.DYNAMIC_DRAW = "DYNAMIC_DRAW"; - mockGL.TRIANGLE_FAN = "TRIANGLE_FAN"; - mockGL.LINE_STRIP = "LINE_STRIP"; - - // Echo back names for uniform locations, so we can - // test which of these are set for certain operations. - mockGL.getUniformLocation.andCallFake(function (a, name) { - return name; - }); - - mockCanvas.getContext.andReturn(mockGL); - - glChart = new GLChart(mockCanvas); - }); - - it("allows the canvas to be cleared", function () { - glChart.clear(); - expect(mockGL.clear).toHaveBeenCalled(); - }); - - it("does not construct if WebGL is unavailable", function () { - mockCanvas.getContext.andReturn(undefined); - expect(function () { - return new GLChart(mockCanvas); - }).toThrow(); - }); - - it("allows dimensions to be set", function () { - glChart.setDimensions([120, 120], [0, 10]); - expect(mockGL.uniform2fv) - .toHaveBeenCalledWith("uDimensions", [120, 120]); - expect(mockGL.uniform2fv) - .toHaveBeenCalledWith("uOrigin", [0, 10]); - }); - - it("allows lines to be drawn", function () { - var testBuffer = [0, 1, 3, 8], - testColor = [0.25, 0.33, 0.66, 1.0], - testPoints = 2; - glChart.drawLine(testBuffer, testColor, testPoints); - expect(mockGL.bufferData).toHaveBeenCalledWith( - mockGL.ARRAY_BUFFER, - testBuffer, - mockGL.DYNAMIC_DRAW - ); - expect(mockGL.uniform4fv) - .toHaveBeenCalledWith("uColor", testColor); - expect(mockGL.drawArrays) - .toHaveBeenCalledWith("LINE_STRIP", 0, testPoints); - }); - - it("allows squares to be drawn", function () { - var testMin = [0, 1], - testMax = [10, 10], - testColor = [0.25, 0.33, 0.66, 1.0]; - - glChart.drawSquare(testMin, testMax, testColor); - - expect(mockGL.uniform4fv) - .toHaveBeenCalledWith("uColor", testColor); - expect(mockGL.drawArrays) - .toHaveBeenCalledWith("TRIANGLE_FAN", 0, 4); - }); - - it("uses buffer sizes reported by WebGL", function () { - // Make sure that GLChart uses the GL buffer size, which may - // differ from what canvas requested. WTD-852 - mockCanvas.width = 300; - mockCanvas.height = 150; - mockGL.drawingBufferWidth = 200; - mockGL.drawingBufferHeight = 175; - - glChart.clear(); - - expect(mockGL.viewport).toHaveBeenCalledWith(0, 0, 200, 175); - }); - }); - } -); diff --git a/platform/features/plot/test/MCTChartSpec.js b/platform/features/plot/test/MCTChartSpec.js deleted file mode 100644 index 2beb5f2a6a..0000000000 --- a/platform/features/plot/test/MCTChartSpec.js +++ /dev/null @@ -1,216 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../src/MCTChart"], - function (MCTChart) { - - describe("The mct-chart directive", function () { - var mockInterval, - mockLog, - mockScope, - mockElement, - mockCanvas, - mockGL, - mockC2d, - mockPromise, - mctChart; - - beforeEach(function () { - mockInterval = - jasmine.createSpy("$interval"); - mockLog = - jasmine.createSpyObj("$log", ["warn", "info", "debug"]); - mockScope = jasmine.createSpyObj( - "$scope", - ["$watchCollection", "$on", "$apply"] - ); - mockElement = - jasmine.createSpyObj("element", ["find", "html"]); - mockInterval.cancel = jasmine.createSpy("cancelInterval"); - mockPromise = jasmine.createSpyObj("promise", ["then"]); - - - // mct-chart uses GLChart, so it needs WebGL API - mockCanvas = - jasmine.createSpyObj("canvas", ["getContext", "addEventListener"]); - mockGL = jasmine.createSpyObj( - "gl", - [ - "createShader", - "compileShader", - "shaderSource", - "attachShader", - "createProgram", - "linkProgram", - "useProgram", - "enableVertexAttribArray", - "getAttribLocation", - "getUniformLocation", - "createBuffer", - "lineWidth", - "enable", - "blendFunc", - "viewport", - "clear", - "uniform2fv", - "uniform4fv", - "bufferData", - "bindBuffer", - "vertexAttribPointer", - "drawArrays" - ] - ); - mockC2d = jasmine.createSpyObj('c2d', ['clearRect']); - mockGL.ARRAY_BUFFER = "ARRAY_BUFFER"; - mockGL.DYNAMIC_DRAW = "DYNAMIC_DRAW"; - mockGL.TRIANGLE_FAN = "TRIANGLE_FAN"; - mockGL.LINE_STRIP = "LINE_STRIP"; - - // Echo back names for uniform locations, so we can - // test which of these are set for certain operations. - mockGL.getUniformLocation.andCallFake(function (a, name) { - return name; - }); - - mockElement.find.andReturn([mockCanvas]); - mockCanvas.getContext.andCallFake(function (type) { - return { webgl: mockGL, '2d': mockC2d }[type]; - }); - mockInterval.andReturn(mockPromise); - - mctChart = new MCTChart(mockInterval, mockLog); - }); - - it("is applicable at the element level", function () { - expect(mctChart.restrict).toEqual("E"); - }); - - it("places a 'draw' attribute in-scope", function () { - // Should ask Angular for the draw attribute - expect(mctChart.scope.draw).toEqual("="); - }); - - it("watches for changes in the drawn object", function () { - mctChart.link(mockScope, mockElement); - expect(mockScope.$watchCollection) - .toHaveBeenCalledWith("draw", jasmine.any(Function)); - }); - - it("issues one draw call per line", function () { - mctChart.link(mockScope, mockElement); - mockScope.$watchCollection.mostRecentCall.args[1]({ - lines: [{}, {}, {}] - }); - expect(mockGL.drawArrays.calls.length).toEqual(3); - }); - - it("issues one draw call per box", function () { - mctChart.link(mockScope, mockElement); - mockScope.$watchCollection.mostRecentCall.args[1]({ - boxes: [ - { start: [0, 0], end: [1, 1] }, - { start: [0, 0], end: [1, 1] }, - { start: [0, 0], end: [1, 1] }, - { start: [0, 0], end: [1, 1] } - ] - }); - expect(mockGL.drawArrays.calls.length).toEqual(4); - }); - - it("does not fail if no draw object is in scope", function () { - mctChart.link(mockScope, mockElement); - expect(mockScope.$watchCollection.mostRecentCall.args[1]) - .not.toThrow(); - }); - - it("draws on canvas resize", function () { - mctChart.link(mockScope, mockElement); - - // Should track canvas size in an interval - expect(mockInterval).toHaveBeenCalledWith( - jasmine.any(Function), - jasmine.any(Number), - 0, - false - ); - - // Verify pre-condition - expect(mockGL.clear).not.toHaveBeenCalled(); - - mockCanvas.width = 100; - mockCanvas.offsetWidth = 150; - mockCanvas.height = 200; - mockCanvas.offsetHeight = 200; - mockInterval.mostRecentCall.args[0](); - - // Use clear as an indication that drawing has occurred - expect(mockGL.clear).toHaveBeenCalled(); - }); - - it("warns if no WebGL context is available", function () { - mockCanvas.getContext.andReturn(undefined); - mctChart.link(mockScope, mockElement); - expect(mockLog.warn).toHaveBeenCalled(); - }); - - it("falls back to Canvas 2d API if WebGL context is lost", function () { - mctChart.link(mockScope, mockElement); - expect(mockCanvas.addEventListener) - .toHaveBeenCalledWith("webglcontextlost", jasmine.any(Function)); - expect(mockCanvas.getContext).not.toHaveBeenCalledWith('2d'); - mockCanvas.addEventListener.mostRecentCall.args[1](); - expect(mockCanvas.getContext).toHaveBeenCalledWith('2d'); - }); - - it("logs nothing in nominal situations (WebGL available)", function () { - // Complement the previous test - mctChart.link(mockScope, mockElement); - expect(mockLog.warn).not.toHaveBeenCalled(); - }); - - // Avoid resource leaks - it("stops polling for size changes on destroy", function () { - mctChart.link(mockScope, mockElement); - - // Should be listening for a destroy event - expect(mockScope.$on).toHaveBeenCalledWith( - "$destroy", - jasmine.any(Function) - ); - - // Precondition - interval still active - expect(mockInterval.cancel).not.toHaveBeenCalled(); - - // Broadcast a $destroy - mockScope.$on.mostRecentCall.args[1](); - - // Should have stopped the interval - expect(mockInterval.cancel).toHaveBeenCalledWith(mockPromise); - }); - - }); - } -); diff --git a/platform/features/plot/test/PlotControllerSpec.js b/platform/features/plot/test/PlotControllerSpec.js deleted file mode 100644 index c8480622db..0000000000 --- a/platform/features/plot/test/PlotControllerSpec.js +++ /dev/null @@ -1,403 +0,0 @@ -/*global angular*/ - -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../src/PlotController"], - function (PlotController) { - - describe("The plot controller", function () { - var mockScope, - mockElement, - mockExportImageService, - mockFormatter, - mockHandler, - mockThrottle, - mockHandle, - mockDomainObject, - mockSeries, - mockStatusCapability, - controller, - mockConductor; - - function bind(method, thisObj) { - return function () { - return method.apply(thisObj, arguments); - }; - } - - function fireEvent(name, args) { - mockScope.$on.calls.forEach(function (call) { - if (call.args[0] === name) { - call.args[1].apply(null, args || []); - } - }); - } - - function fireWatch(expr, value) { - mockScope.$watch.calls.forEach(function (call) { - if (call.args[0] === expr) { - call.args[1].apply(null, [value]); - } - }); - } - - - beforeEach(function () { - mockScope = jasmine.createSpyObj( - "$scope", - ["$watch", "$on", "$emit"] - ); - mockElement = angular.element('
'); - mockExportImageService = jasmine.createSpyObj( - "ExportImageService", - ["exportJPG", "exportPNG"] - ); - mockFormatter = jasmine.createSpyObj( - "formatter", - ["formatDomainValue", "formatRangeValue"] - ); - mockDomainObject = jasmine.createSpyObj( - "domainObject", - ["getId", "getModel", "getCapability", "hasCapability"] - ); - mockHandler = jasmine.createSpyObj( - "telemetrySubscriber", - ["handle"] - ); - mockThrottle = jasmine.createSpy("throttle"); - mockHandle = jasmine.createSpyObj( - "subscription", - [ - "unsubscribe", - "getTelemetryObjects", - "getMetadata", - "getDomainValue", - "getRangeValue", - "getDatum", - "request" - ] - ); - mockSeries = jasmine.createSpyObj( - 'series', - ['getPointCount', 'getDomainValue', 'getRangeValue'] - ); - - mockStatusCapability = jasmine.createSpyObj( - "statusCapability", - ["set"] - ); - - mockHandler.handle.andReturn(mockHandle); - mockThrottle.andCallFake(function (fn) { - return fn; - }); - mockHandle.getTelemetryObjects.andReturn([mockDomainObject]); - mockHandle.getMetadata.andReturn([{}]); - mockHandle.getDomainValue.andReturn(123); - mockHandle.getRangeValue.andReturn(42); - mockScope.domainObject = mockDomainObject; - - mockConductor = jasmine.createSpyObj('conductor', [ - 'on', - 'off', - 'bounds', - 'timeSystem', - 'timeOfInterest', - 'follow' - ]); - - mockConductor.bounds.andReturn({}); - - controller = new PlotController( - mockScope, - mockElement, - mockExportImageService, - mockFormatter, - mockHandler, - mockThrottle, - undefined, - {time: mockConductor} - ); - }); - - it("provides plot colors", function () { - // PlotPalette will have its own tests - expect(controller.getColor(0)) - .toEqual(jasmine.any(String)); - - // Colors should be unique - expect(controller.getColor(0)) - .not.toEqual(controller.getColor(1)); - }); - - it("subscribes to telemetry when a domain object appears in scope", function () { - // Make sure we're using the right watch here - expect(mockScope.$watch.mostRecentCall.args[0]) - .toEqual("domainObject"); - // Make an object available - mockScope.$watch.mostRecentCall.args[1](mockDomainObject); - // Should have subscribed - expect(mockHandler.handle).toHaveBeenCalledWith( - mockDomainObject, - jasmine.any(Function), - true // Lossless - ); - }); - - it("draws lines when data becomes available", function () { - // Make an object available - mockScope.$watch.mostRecentCall.args[1](mockDomainObject); - - // Verify precondition - controller.getSubPlots().forEach(function (subplot) { - expect(subplot.getDrawingObject().lines) - .not.toBeDefined(); - }); - - // Make sure there actually are subplots being verified - expect(controller.getSubPlots().length > 0).toBeTruthy(); - - // Broadcast data - mockHandler.handle.mostRecentCall.args[1](); - - controller.getSubPlots().forEach(function (subplot) { - expect(subplot.getDrawingObject().lines) - .toBeDefined(); - }); - }); - - it("unsubscribes when domain object changes", function () { - // Make an object available - mockScope.$watch.mostRecentCall.args[1](mockDomainObject); - // Verify precondition - shouldn't unsubscribe yet - expect(mockHandle.unsubscribe).not.toHaveBeenCalled(); - // Remove the domain object - mockScope.$watch.mostRecentCall.args[1](undefined); - // Should have unsubscribed - expect(mockHandle.unsubscribe).toHaveBeenCalled(); - }); - - - it("changes modes depending on number of objects", function () { - // Act like one object is available - mockHandle.getTelemetryObjects.andReturn([ - mockDomainObject - ]); - - // Make an object available; invoke handler's callback - mockScope.$watch.mostRecentCall.args[1](mockDomainObject); - mockHandler.handle.mostRecentCall.args[1](); - - expect(controller.getModeOptions().length).toEqual(1); - - // Act like one object is available - mockHandle.getTelemetryObjects.andReturn([ - mockDomainObject, - mockDomainObject, - mockDomainObject - ]); - - // Make an object available; invoke handler's callback - mockScope.$watch.mostRecentCall.args[1](mockDomainObject); - mockHandler.handle.mostRecentCall.args[1](); - - expect(controller.getModeOptions().length).toEqual(2); - }); - - // Interface tests follow; these will be delegated (mostly - // to PlotModeOptions, which is tested separately). - it("provides access to available plot mode options", function () { - expect(Array.isArray(controller.getModeOptions())) - .toBeTruthy(); - }); - - it("provides a current plot mode", function () { - expect(controller.getMode().name) - .toEqual(jasmine.any(String)); - }); - - it("allows plot mode to be changed", function () { - expect(function () { - controller.setMode(controller.getMode()); - }).not.toThrow(); - }); - - it("provides an array of sub-plots", function () { - expect(Array.isArray(controller.getSubPlots())) - .toBeTruthy(); - }); - - it("allows plots to be updated", function () { - expect(bind(controller.update, controller)).not.toThrow(); - }); - - it("allows changing pan-zoom state", function () { - expect(bind(controller.isZoomed, controller)).not.toThrow(); - expect(bind(controller.stepBackPanZoom, controller)).not.toThrow(); - expect(bind(controller.unzoom, controller)).not.toThrow(); - }); - - it("sets status when plot becomes detached from time conductor", function () { - mockScope.$watch.mostRecentCall.args[1](mockDomainObject); - - function boundsEvent() { - fireEvent("telemetry:display:bounds", [ - {}, - { start: 10, end: 100 }, - true - ]); - } - - mockDomainObject.hasCapability.andCallFake(function (name) { - return name === "status"; - }); - mockDomainObject.getCapability.andReturn(mockStatusCapability); - spyOn(controller, "isZoomed"); - - //Mock zoomed in state - controller.isZoomed.andReturn(true); - boundsEvent(); - expect(mockStatusCapability.set).toHaveBeenCalledWith("timeconductor-unsynced", true); - - //"Reset" zoom - controller.isZoomed.andReturn(false); - boundsEvent(); - expect(mockStatusCapability.set).toHaveBeenCalledWith("timeconductor-unsynced", false); - }); - - it("indicates if a request is pending", function () { - mockScope.$watch.mostRecentCall.args[1](mockDomainObject); - expect(controller.isRequestPending()).toBeTruthy(); - mockHandle.request.mostRecentCall.args[1]( - mockDomainObject, - mockSeries - ); - expect(controller.isRequestPending()).toBeFalsy(); - }); - - it("requests historical telemetry", function () { - mockScope.$watch.mostRecentCall.args[1](mockDomainObject); - expect(mockHandle.request).toHaveBeenCalled(); - mockHandle.request.mostRecentCall.args[1]( - mockDomainObject, - mockSeries - ); - }); - - it("unsubscribes when destroyed", function () { - // Make an object available - mockScope.$watch.mostRecentCall.args[1](mockDomainObject); - // Make sure $destroy is what's listened for - expect(mockScope.$on.mostRecentCall.args[0]).toEqual('$destroy'); - // Also verify precondition - expect(mockHandle.unsubscribe).not.toHaveBeenCalled(); - // Destroy the scope - fireEvent("$destroy"); - // Should have unsubscribed - expect(mockHandle.unsubscribe).toHaveBeenCalled(); - }); - - it("requeries when displayable bounds change", function () { - mockScope.$watch.mostRecentCall.args[1](mockDomainObject); - expect(mockHandle.request.calls.length).toEqual(1); - fireEvent("telemetry:display:bounds", [ - {}, - { start: 10, end: 100 } - ]); - expect(mockHandle.request.calls.length).toEqual(2); - }); - - it("requeries when user changes domain selection", function () { - mockScope.$watch.mostRecentCall.args[1](mockDomainObject); - expect(mockHandle.request.calls.length).toEqual(1); - fireWatch("axes[0].active.key", 'someNewKey'); - expect(mockHandle.request.calls.length).toEqual(2); - }); - - it("requeries when user changes range selection", function () { - mockScope.$watch.mostRecentCall.args[1](mockDomainObject); - expect(mockHandle.request.calls.length).toEqual(1); - fireWatch("axes[1].active.key", 'someNewKey'); - expect(mockHandle.request.calls.length).toEqual(2); - }); - - it("maintains externally-provided domain axis bounds after data is received", function () { - mockSeries.getPointCount.andReturn(3); - mockSeries.getRangeValue.andReturn(42); - mockSeries.getDomainValue.andCallFake(function (i) { - return 2500 + i * 2500; - }); - - mockScope.$watch.mostRecentCall.args[1](mockDomainObject); - fireEvent("telemetry:display:bounds", [ - {}, - {start: 0, end: 10000} - ]); - mockHandle.request.mostRecentCall.args[1]( - mockDomainObject, - mockSeries - ); - - // Pan-zoom state should reflect bounds set externally; - // domain axis should not have shrunk to fit data. - expect( - controller.getSubPlots()[0].panZoomStack.getOrigin()[0] - ).toEqual(0); - expect( - controller.getSubPlots()[0].panZoomStack.getDimensions()[0] - ).toEqual(10000); - }); - - it("provides classes for legends based on limit state", function () { - var mockTelemetryObjects = mockHandle.getTelemetryObjects(); - - mockHandle.getDatum.andReturn({}); - mockTelemetryObjects.forEach(function (mockObject, i) { - var id = 'object-' + i, - mockLimitCapability = - jasmine.createSpyObj('limit-' + id, ['evaluate']); - - mockObject.getId.andReturn(id); - mockObject.getCapability.andCallFake(function (key) { - return (key === 'limit') && mockLimitCapability; - }); - - mockLimitCapability.evaluate - .andReturn({ cssClass: 'alarm-' + id }); - }); - - mockScope.$watch.mostRecentCall.args[1](mockDomainObject); - mockHandler.handle.mostRecentCall.args[1](); - - mockTelemetryObjects.forEach(function (mockTelemetryObject) { - expect(controller.getLegendClass(mockTelemetryObject)) - .toEqual('alarm-' + mockTelemetryObject.getId()); - }); - }); - }); - } -); diff --git a/platform/features/plot/test/PlotOptionsControllerSpec.js b/platform/features/plot/test/PlotOptionsControllerSpec.js deleted file mode 100644 index 9a409f4cf9..0000000000 --- a/platform/features/plot/test/PlotOptionsControllerSpec.js +++ /dev/null @@ -1,147 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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( - ['../src/PlotOptionsController'], - function (PlotOptionsController) { - - describe("The Plot Options controller", function () { - var plotOptionsController, - mockDomainObject, - mockMutationCapability, - mockUseCapabilities, - mockCompositionCapability, - mockComposition, - mockUnlisten, - mockChildOne, - mockChildTwo, - model, - mockScope; - - beforeEach(function () { - model = { - composition: ['childOne'] - }; - - mockChildOne = jasmine.createSpyObj('domainObject', [ - 'getId' - ]); - mockChildOne.getId.andReturn('childOne'); - - mockChildTwo = jasmine.createSpyObj('childTwo', [ - 'getId' - ]); - mockChildOne.getId.andReturn('childTwo'); - - mockCompositionCapability = jasmine.createSpyObj('compositionCapability', [ - 'then' - ]); - mockComposition = [ - mockChildOne - ]; - mockCompositionCapability.then.andCallFake(function (callback) { - callback(mockComposition); - }); - - mockUseCapabilities = jasmine.createSpyObj('useCapabilities', [ - 'composition', - 'mutation' - ]); - mockUseCapabilities.composition.andReturn(mockCompositionCapability); - - mockMutationCapability = jasmine.createSpyObj('mutationCapability', [ - 'listen' - ]); - mockUnlisten = jasmine.createSpy('unlisten'); - mockMutationCapability.listen.andReturn(mockUnlisten); - - mockDomainObject = jasmine.createSpyObj('domainObject', [ - 'getModel', - 'useCapability', - 'getCapability' - ]); - mockDomainObject.useCapability.andCallFake(function (capability) { - return mockUseCapabilities[capability](); - }); - mockDomainObject.getCapability.andReturn(mockMutationCapability); - mockDomainObject.getModel.andReturn(model); - - mockScope = jasmine.createSpyObj('scope', [ - '$on', - '$watchCollection' - ]); - mockScope.domainObject = mockDomainObject; - - function noop() {} - mockScope.$watchCollection.andReturn(noop); - - plotOptionsController = new PlotOptionsController(mockScope); - }); - - it("sets form definitions on scope", function () { - expect(mockScope.xAxisForm).toBeDefined(); - expect(mockScope.yAxisForm).toBeDefined(); - expect(mockScope.plotSeriesForm).toBeDefined(); - }); - - it("sets object children on scope", function () { - expect(mockScope.children).toBe(mockComposition); - }); - - it("on changes in object composition, updates the form", function () { - expect(mockMutationCapability.listen).toHaveBeenCalled(); - expect(mockScope.children).toBe(mockComposition); - expect(mockScope.children.length).toBe(1); - mockComposition.push(mockChildTwo); - model.composition.push('childTwo'); - mockMutationCapability.listen.mostRecentCall.args[0](model); - expect(mockScope.children).toBe(mockComposition); - expect(mockScope.children.length).toBe(2); - }); - - it("on changes in form values, updates the object model", function () { - var scopeConfiguration = mockScope.configuration, - objModel = mockDomainObject.getModel(); - - scopeConfiguration.plot.yAxis.autoScale = true; - scopeConfiguration.plot.yAxis.key = 'eu'; - scopeConfiguration.plot.xAxis.key = 'lst'; - - expect(mockScope.$watchCollection).toHaveBeenCalled(); - mockScope.$watchCollection.calls[0].args[1](); - expect(mockDomainObject.useCapability).toHaveBeenCalledWith('mutation', jasmine.any(Function)); - - mockDomainObject.useCapability.mostRecentCall.args[1](objModel); - expect(objModel.configuration.plot.yAxis.autoScale).toBe(true); - expect(objModel.configuration.plot.yAxis.key).toBe('eu'); - expect(objModel.configuration.plot.xAxis.key).toBe('lst'); - - }); - - it("cleans up listeners on destruction of the controller", function () { - mockScope.$on.mostRecentCall.args[1](); - expect(mockUnlisten).toHaveBeenCalled(); - }); - - }); - } -); diff --git a/platform/features/plot/test/SubPlotFactorySpec.js b/platform/features/plot/test/SubPlotFactorySpec.js deleted file mode 100644 index 67798f9e07..0000000000 --- a/platform/features/plot/test/SubPlotFactorySpec.js +++ /dev/null @@ -1,66 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../src/SubPlotFactory"], - function (SubPlotFactory) { - - describe("The sub-plot factory", function () { - var mockDomainObject, - mockPanZoomStack, - mockFormatter, - factory; - - beforeEach(function () { - mockDomainObject = jasmine.createSpyObj( - "domainObject", - ["getId", "getModel", "getCapability"] - ); - mockPanZoomStack = jasmine.createSpyObj( - "panZoomStack", - ["getPanZoom"] - ); - mockFormatter = jasmine.createSpyObj( - "formatter", - ["formatDomainValue", "formatRangeValue"] - ); - - mockPanZoomStack.getPanZoom.andReturn({ - origin: [0, 0], - dimensions: [100, 100] - }); - - factory = new SubPlotFactory(mockFormatter); - }); - - it("creates sub-plots", function () { - expect(factory.createSubPlot( - [mockDomainObject], - mockPanZoomStack - ).getTelemetryObjects()).toEqual([mockDomainObject]); - }); - }); - } -); diff --git a/platform/features/plot/test/SubPlotSpec.js b/platform/features/plot/test/SubPlotSpec.js deleted file mode 100644 index ff38f9b2ca..0000000000 --- a/platform/features/plot/test/SubPlotSpec.js +++ /dev/null @@ -1,208 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../src/SubPlot"], - function (SubPlot) { - - describe("A sub-plot", function () { - var mockDomainObject, - mockPanZoomStack, - mockFormatter, - mockElement, - testDomainObjects, - testOrigin, - testDimensions, - subplot; - - beforeEach(function () { - mockDomainObject = jasmine.createSpyObj( - "domainObject", - ["getId", "getModel", "getCapability"] - ); - mockPanZoomStack = jasmine.createSpyObj( - "panZoomStack", - [ - "getDepth", - "pushPanZoom", - "popPanZoom", - "setBasePanZoom", - "clearPanZoom", - "getPanZoom", - "getOrigin", - "getDimensions" - ] - ); - mockFormatter = jasmine.createSpyObj( - "formatter", - ["formatDomainValue", "formatRangeValue"] - ); - mockElement = jasmine.createSpyObj( - "element", - ["getBoundingClientRect"] - ); - - testOrigin = [5, 10]; - testDimensions = [3000, 1000]; - testDomainObjects = [mockDomainObject, mockDomainObject]; - - mockPanZoomStack.getOrigin.andReturn(testOrigin); - mockPanZoomStack.getDimensions.andReturn(testDimensions); - mockPanZoomStack.getPanZoom.andReturn( - { origin: testOrigin, dimensions: testDimensions } - ); - mockElement.getBoundingClientRect.andReturn( - { left: 10, top: 20, width: 100, height: 100 } - ); - - subplot = new SubPlot( - testDomainObjects, - mockPanZoomStack, - mockFormatter - ); - }); - - - it("provides a getter for its plotted objects", function () { - expect(subplot.getTelemetryObjects()) - .toEqual(testDomainObjects); - }); - - it("exposes tick marks", function () { - // Just test availability; details are tested - // in PlotTickFormatter - expect(Array.isArray(subplot.getDomainTicks())) - .toBeTruthy(); - expect(Array.isArray(subplot.getRangeTicks())) - .toBeTruthy(); - }); - - it("allows hovering state to be tracked", function () { - expect(subplot.isHovering()).toBeFalsy(); - expect(subplot.isHovering(true)).toBeTruthy(); - expect(subplot.isHovering()).toBeTruthy(); - expect(subplot.isHovering(false)).toBeFalsy(); - expect(subplot.isHovering()).toBeFalsy(); - }); - - it("provides hovering coordinates", function () { - // Should be empty when not hovering - expect(subplot.getHoverCoordinates()) - .toBeUndefined(); - - // Start hovering - subplot.hover({ target: mockElement }); - - // Should now have coordinates to display - expect(subplot.getHoverCoordinates()) - .toEqual(jasmine.any(String)); - }); - - it("supports marquee zoom", function () { - expect(mockPanZoomStack.pushPanZoom).not.toHaveBeenCalled(); - - // Simulate a marquee zoom. Note that the mockElement - // is 100 by 100 and starts at 10,20 - subplot.startDrag({ - target: mockElement, - clientX: 60, - clientY: 45 - }); - subplot.hover({ - target: mockElement, - clientX: 75, - clientY: 85 - }); - subplot.endDrag({ - target: mockElement, - clientX: 80, - clientY: 95 - }); - // ... so the origin should be 50%,25% into current dimensions, - // and new dimensions should be 20%,50% thereof - - expect(mockPanZoomStack.pushPanZoom).toHaveBeenCalledWith( - [ - testOrigin[0] + testDimensions[0] * 0.50, - testOrigin[1] + testDimensions[1] * 0.25 - ], - [ - testDimensions[0] * 0.20, - testDimensions[1] * 0.50 - ] - ); - }); - - it ("indicates when there is domain data shown", function () { - expect(subplot.hasDomainData()).toEqual(true); - }); - - it ("indicates when there is no domain data shown", function () { - mockPanZoomStack.getDimensions.andReturn([0,0]); - expect(subplot.hasDomainData()).toEqual(false); - }); - - it("disallows marquee zoom when start and end Marquee is at the same position", function () { - expect(mockPanZoomStack.pushPanZoom).not.toHaveBeenCalled(); - - // Simulate a marquee zoom. Note that the mockElement - // is 100 by 100 and starts at 10,20 - subplot.startDrag({ - target: mockElement, - clientX: 60, - clientY: 45 - }); - subplot.hover({ - target: mockElement, - clientX: 75, - clientY: 85 - }); - subplot.endDrag({ - target: mockElement, - clientX: 60, - clientY: 45 - }); - - expect(mockPanZoomStack.pushPanZoom).not.toHaveBeenCalled(); - }); - - it("provides access to a drawable object", function () { - expect(typeof subplot.getDrawingObject()).toEqual('object'); - }); - - it("allows a domain offset to be provided", function () { - // Domain object is needed to adjust canvas coordinates - // to avoid loss-of-precision associated with converting - // to 32 bit floats. - subplot.setDomainOffset(3); - subplot.update(); - // Should have adjusted the origin accordingly - expect(subplot.getDrawingObject().origin[0]) - .toEqual(2); - }); - - }); - } -); diff --git a/platform/features/plot/test/elements/PlotAxisSpec.js b/platform/features/plot/test/elements/PlotAxisSpec.js deleted file mode 100644 index 72426ad8ef..0000000000 --- a/platform/features/plot/test/elements/PlotAxisSpec.js +++ /dev/null @@ -1,107 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/elements/PlotAxis"], - function (PlotAxis) { - - describe("A plot axis", function () { - var testMetadatas, - testDefault, - axis; - - beforeEach(function () { - testMetadatas = [ - { - tests: [ - { key: "t0", name: "T0" }, - { key: "t1", name: "T1" } - ], - someKey: "some value" - }, - { - tests: [ - { key: "t0", name: "T0" }, - { key: "t2", name: "T2" } - ] - }, - { - tests: [ - { key: "t3", name: "T3" }, - { key: "t4", name: "T4" }, - { key: "t5", name: "T5" }, - { key: "t6", name: "T6" } - ] - } - ]; - testDefault = { key: "test", name: "Test" }; - axis = new PlotAxis("tests", testMetadatas, testDefault); - }); - - it("pulls out a list of domain or range options", function () { - // Should have filtered out duplicates, etc - expect(axis.options).toEqual([ - { key: "t0", name: "T0" }, - { key: "t1", name: "T1" }, - { key: "t2", name: "T2" }, - { key: "t3", name: "T3" }, - { key: "t4", name: "T4" }, - { key: "t5", name: "T5" }, - { key: "t6", name: "T6" } - ]); - }); - - it("chooses the first option as a default", function () { - expect(axis.active).toEqual({ key: "t0", name: "T0" }); - }); - - it("falls back to a provided default if no options are present", function () { - expect(new PlotAxis("tests", [{}], testDefault).active) - .toEqual(testDefault); - }); - - it("allows options to be chosen by key", function () { - axis.chooseOption("t3"); - expect(axis.active).toEqual({ key: "t3", name: "T3" }); - }); - - it("reflects changes to applicable metadata", function () { - axis.updateMetadata([testMetadatas[1]]); - expect(axis.options).toEqual([ - { key: "t0", name: "T0" }, - { key: "t2", name: "T2" } - ]); - }); - - it("returns the same array instance for unchanged metadata", function () { - // ...to avoid triggering extra digest cycles. - var oldInstance = axis.options; - axis.updateMetadata(testMetadatas); - expect(axis.options).toBe(oldInstance); - }); - - }); - } -); diff --git a/platform/features/plot/test/elements/PlotLimitTrackerSpec.js b/platform/features/plot/test/elements/PlotLimitTrackerSpec.js deleted file mode 100644 index dc1169cb6e..0000000000 --- a/platform/features/plot/test/elements/PlotLimitTrackerSpec.js +++ /dev/null @@ -1,100 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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( - ["../../src/elements/PlotLimitTracker"], - function (PlotLimitTracker) { - - describe("A plot's limit tracker", function () { - var mockHandle, - testRange, - mockTelemetryObjects, - testData, - tracker; - - beforeEach(function () { - testRange = "some-range"; - testData = {}; - mockHandle = jasmine.createSpyObj( - 'handle', - ['getTelemetryObjects', 'getDatum'] - ); - mockTelemetryObjects = ['a', 'b', 'c'].map(function (id, i) { - var mockTelemetryObject = jasmine.createSpyObj( - 'object-' + id, - ['getId', 'getCapability', 'getModel'] - ), - mockLimitCapability = jasmine.createSpyObj( - 'limit-' + id, - ['evaluate'] - ); - testData[id] = { id: id, value: i }; - mockTelemetryObject.getId.andReturn(id); - mockTelemetryObject.getCapability.andCallFake(function (key) { - return key === 'limit' && mockLimitCapability; - }); - mockLimitCapability.evaluate - .andReturn({ cssClass: 'alarm-' + id}); - return mockTelemetryObject; - }); - mockHandle.getTelemetryObjects.andReturn(mockTelemetryObjects); - mockHandle.getDatum.andCallFake(function (telemetryObject) { - return testData[telemetryObject.getId()]; - }); - - tracker = new PlotLimitTracker(mockHandle, testRange); - }); - - it("initially provides no limit state", function () { - mockTelemetryObjects.forEach(function (mockTelemetryObject) { - expect(tracker.getLegendClass(mockTelemetryObject)) - .toBeUndefined(); - }); - }); - - describe("when asked to update", function () { - beforeEach(function () { - tracker.update(); - }); - - it("evaluates limits using the limit capability", function () { - mockTelemetryObjects.forEach(function (mockTelemetryObject) { - var id = mockTelemetryObject.getId(), - mockLimit = - mockTelemetryObject.getCapability('limit'); - expect(mockLimit.evaluate) - .toHaveBeenCalledWith(testData[id], testRange); - }); - }); - - it("exposes legend classes returned by the limit capability", function () { - mockTelemetryObjects.forEach(function (mockTelemetryObject) { - var id = mockTelemetryObject.getId(); - expect(tracker.getLegendClass(mockTelemetryObject)) - .toEqual('alarm-' + id); - }); - }); - }); - - }); - } -); diff --git a/platform/features/plot/test/elements/PlotLineBufferSpec.js b/platform/features/plot/test/elements/PlotLineBufferSpec.js deleted file mode 100644 index 0868c1dfd0..0000000000 --- a/platform/features/plot/test/elements/PlotLineBufferSpec.js +++ /dev/null @@ -1,167 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/elements/PlotLineBuffer"], - function (PlotLineBuffer) { - - var TEST_INITIAL_SIZE = 10, - TEST_MAX_SIZE = 40, - TEST_DOMAIN_OFFSET = 42; - - describe("A plot line buffer", function () { - var mockSeries, - testDomainValues, - testRangeValues, - buffer; - - beforeEach(function () { - testDomainValues = [1, 3, 7, 9, 14, 15]; - testRangeValues = [8, 0, 3, 9, 8, 11]; - mockSeries = jasmine.createSpyObj( - "series", - ['getPointCount', 'getDomainValue', 'getRangeValue'] - ); - mockSeries.getPointCount.andCallFake(function () { - return testDomainValues.length; - }); - mockSeries.getDomainValue.andCallFake(function (i) { - return testDomainValues[i]; - }); - mockSeries.getRangeValue.andCallFake(function (i) { - return testRangeValues[i]; - }); - - buffer = new PlotLineBuffer( - TEST_DOMAIN_OFFSET, - TEST_INITIAL_SIZE, - TEST_MAX_SIZE - ); - - // Start with some data in there - buffer.insert(mockSeries, 0); - }); - - it("allows insertion of series data", function () { - // Convert to a regular array for checking. - // Verify that domain/ranges were interleaved and - // that domain offset was adjusted for. - expect( - Array.prototype.slice.call(buffer.getBuffer()).slice(0, 12) - ).toEqual([-41, 8, -39, 0, -35, 3, -33, 9, -28, 8, -27, 11]); - expect(buffer.getLength()).toEqual(6); - }); - - it("finds insertion indexes", function () { - expect(buffer.findInsertionIndex(0)).toEqual(0); - expect(buffer.findInsertionIndex(2)).toEqual(1); - expect(buffer.findInsertionIndex(5)).toEqual(2); - expect(buffer.findInsertionIndex(10)).toEqual(4); - expect(buffer.findInsertionIndex(14.5)).toEqual(5); - expect(buffer.findInsertionIndex(20)).toEqual(6); - }); - - it("allows insertion in the middle", function () { - var head = [-41, 8, -39, 0, -35, 3], - tail = [-33, 9, -28, 8, -27, 11]; - buffer.insert(mockSeries, 3); - expect( - Array.prototype.slice.call(buffer.getBuffer()).slice(0, 24) - ).toEqual(head.concat(head).concat(tail).concat(tail)); - expect(buffer.getLength()).toEqual(12); - }); - - it("allows values to be trimmed from the start", function () { - buffer.trim(2); - expect(buffer.getLength()).toEqual(4); - expect( - Array.prototype.slice.call(buffer.getBuffer()).slice(0, 8) - ).toEqual([-35, 3, -33, 9, -28, 8, -27, 11]); - }); - - it("expands buffer when needed to accommodate more data", function () { - var i; - - // Initial underlying buffer should be twice initial size... - // (Since each pair will take up two elements) - expect(buffer.getBuffer().length).toEqual(20); - - // Should be able to insert 6 series of 6 points each - // (After that, we'll hit the test max of 40) - for (i = 1; i < 15; i += 1) { - expect(buffer.insertPoint(i * 10, Math.sin(i), i)) - .toBeTruthy(); - } - - // Buffer should have expanded in the process - expect(buffer.getBuffer().length).toEqual(40); - - // Push to maximum size just to make sure... - for (i = 1; i < 150; i += 1) { - buffer.insertPoint(i * 10, Math.sin(i), i); - } - - expect(buffer.getBuffer().length).toEqual(80); - }); - - it("ensures a maximum size", function () { - var i; - - // Should be able to insert 6 series of 6 points each - // (After that, we'll hit the test max of 40) - for (i = 1; i < 6; i += 1) { - expect(buffer.getLength()).toEqual(6 * i); - expect(buffer.insert(mockSeries, Number.POSITIVE_INFINITY)) - .toBeTruthy(); - } - - // Should be maxed out now - expect(buffer.getLength()).toEqual(36); - expect(buffer.insert(mockSeries, Number.POSITIVE_INFINITY)) - .toBeFalsy(); - expect(buffer.getLength()).toEqual(36); - - }); - - it("reduces buffer size when space is no longer needed", function () { - // Check that actual buffer is sized to the initial size - // (double TEST_INITIAL_SIZE, since two elements are needed per - // point; one for domain, one for range) - expect(buffer.getBuffer().length).toEqual(20); - // Should have 6 elements now... grow to 24 - buffer.insert(mockSeries, Number.POSITIVE_INFINITY); - buffer.insert(mockSeries, Number.POSITIVE_INFINITY); - buffer.insert(mockSeries, Number.POSITIVE_INFINITY); - // This should have doubled the actual buffer size - expect(buffer.getBuffer().length).toEqual(80); - // Remove some values - buffer.trim(20); - // Actual buffer size should have been reduced accordingly - expect(buffer.getBuffer().length).toBeLessThan(80); - }); - - }); - } -); diff --git a/platform/features/plot/test/elements/PlotLineSpec.js b/platform/features/plot/test/elements/PlotLineSpec.js deleted file mode 100644 index e8f92bdc7c..0000000000 --- a/platform/features/plot/test/elements/PlotLineSpec.js +++ /dev/null @@ -1,133 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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( - ["../../src/elements/PlotLine"], - function (PlotLine) { - - describe("A plot line", function () { - var mockBuffer, - mockSeries, - testDomainBuffer, - testRangeBuffer, - testSeries, - line; - - beforeEach(function () { - testDomainBuffer = []; - testRangeBuffer = []; - testSeries = []; - - mockBuffer = jasmine.createSpyObj( - 'buffer', - ['findInsertionIndex', 'insert', 'insertPoint', 'trim'] - ); - mockSeries = jasmine.createSpyObj( - 'series', - ['getPointCount', 'getDomainValue', 'getRangeValue'] - ); - - mockSeries.getPointCount.andCallFake(function () { - return testSeries.length; - }); - mockSeries.getDomainValue.andCallFake(function (i) { - return (testSeries[i] || [])[0]; - }); - mockSeries.getRangeValue.andCallFake(function (i) { - return (testSeries[i] || [])[1]; - }); - - // Function like PlotLineBuffer, to aid in testability - mockBuffer.findInsertionIndex.andCallFake(function (v) { - var index = 0; - if (testDomainBuffer.indexOf(v) !== -1) { - return -1; - } - while ((index < testDomainBuffer.length) && - (testDomainBuffer[index] < v)) { - index += 1; - } - return index; - }); - mockBuffer.insert.andCallFake(function (series, index) { - var domains = [], ranges = [], i; - for (i = 0; i < series.getPointCount(); i += 1) { - domains.push(series.getDomainValue(i)); - ranges.push(series.getRangeValue(i)); - } - testDomainBuffer = testDomainBuffer.slice(0, index) - .concat(domains) - .concat(testDomainBuffer.slice(index)); - testRangeBuffer = testRangeBuffer.slice(0, index) - .concat(ranges) - .concat(testRangeBuffer.slice(index)); - return true; - }); - mockBuffer.insertPoint.andCallFake(function (dv, rv, index) { - testDomainBuffer.splice(index, 0, dv); - testRangeBuffer.splice(index, 0, rv); - return true; - }); - - line = new PlotLine(mockBuffer); - }); - - it("allows single point insertion", function () { - line.addPoint(100, 200); - line.addPoint(50, 42); - line.addPoint(150, 12321); - // Should have managed insertion index choices to get to... - expect(testDomainBuffer).toEqual([50, 100, 150]); - expect(testRangeBuffer).toEqual([42, 200, 12321]); - }); - - it("allows series insertion", function () { - testSeries = [[50, 42], [100, 200], [150, 12321]]; - line.addSeries(mockSeries); - // Should have managed insertion index choices to get to... - expect(testDomainBuffer).toEqual([50, 100, 150]); - expect(testRangeBuffer).toEqual([42, 200, 12321]); - }); - - it("splits series insertion when necessary", function () { - testSeries = [[50, 42], [100, 200], [150, 12321]]; - line.addPoint(75, 1); - line.addSeries(mockSeries); - // Should have managed insertion index choices to get to... - expect(testDomainBuffer).toEqual([50, 75, 100, 150]); - expect(testRangeBuffer).toEqual([42, 1, 200, 12321]); - }); - - it("attempts to remove points when insertion fails", function () { - // Verify precondition - normally doesn't try to trim - line.addPoint(1, 2); - expect(mockBuffer.trim).not.toHaveBeenCalled(); - - // But if insertPoint fails, it should trim - mockBuffer.insertPoint.andReturn(false); - line.addPoint(2, 3); - expect(mockBuffer.trim).toHaveBeenCalled(); - }); - - }); - } -); diff --git a/platform/features/plot/test/elements/PlotPaletteSpec.js b/platform/features/plot/test/elements/PlotPaletteSpec.js deleted file mode 100644 index 7bfff18f49..0000000000 --- a/platform/features/plot/test/elements/PlotPaletteSpec.js +++ /dev/null @@ -1,123 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/elements/PlotPalette"], - function (PlotPalette) { - - describe("The plot palette", function () { - it("can be used as a constructor", function () { - // PlotPalette has all static methods, so make - // sure it returns itself if used as a constructor. - expect(new PlotPalette()).toBe(PlotPalette); - }); - - it("has 30 unique colors in an integer format", function () { - // Integer format may be useful internal to the application. - // RGB 0-255 - var i, j; - - // Used to verify one of R, G, B in loop below - function verifyChannel(c) { - expect(typeof c).toEqual("number"); - expect(c <= 255).toBeTruthy(); - expect(c >= 0).toBeTruthy(); - } - - for (i = 0; i < 30; i += 1) { - // Verify that we got an array of numbers - expect(Array.isArray(PlotPalette.getIntegerColor(i))) - .toBeTruthy(); - expect(PlotPalette.getIntegerColor(i).length).toEqual(3); - - // Verify all three channels for type and range - PlotPalette.getIntegerColor(i).forEach(verifyChannel); - - // Verify uniqueness - for (j = i + 1; j < 30; j += 1) { - expect(PlotPalette.getIntegerColor(i)).not.toEqual( - PlotPalette.getIntegerColor(j) - ); - } - } - }); - - - it("has 30 unique colors in a floating-point format", function () { - // Float format is useful to WebGL. - // RGB 0.0-1.1 - var i, j; - - // Used to verify one of R, G, B in loop below - function verifyChannel(c) { - expect(typeof c).toEqual("number"); - expect(c <= 1.0).toBeTruthy(); - expect(c >= 0.0).toBeTruthy(); - } - - for (i = 0; i < 30; i += 1) { - // Verify that we got an array of numbers - expect(Array.isArray(PlotPalette.getFloatColor(i))) - .toBeTruthy(); - expect(PlotPalette.getFloatColor(i).length).toEqual(4); - - // Verify all three channels for type and range - PlotPalette.getFloatColor(i).forEach(verifyChannel); - - // Verify uniqueness - for (j = i + 1; j < 30; j += 1) { - expect(PlotPalette.getFloatColor(i)).not.toEqual( - PlotPalette.getFloatColor(j) - ); - } - } - }); - - - it("has 30 unique colors in a string format", function () { - // String format is useful in stylesheets - // #RRGGBB in hex - var i, j, c; - - - for (i = 0; i < 30; i += 1) { - c = PlotPalette.getStringColor(i); - - // Verify that we #-style color strings - expect(typeof c).toEqual('string'); - expect(c.length).toEqual(7); - expect(/^#[0-9a-fA-F]+$/.test(c)).toBeTruthy(); - - // Verify uniqueness - for (j = i + 1; j < 30; j += 1) { - expect(PlotPalette.getStringColor(i)).not.toEqual( - PlotPalette.getStringColor(j) - ); - } - } - }); - }); - } -); diff --git a/platform/features/plot/test/elements/PlotPanZoomStackGroupSpec.js b/platform/features/plot/test/elements/PlotPanZoomStackGroupSpec.js deleted file mode 100644 index 6c4ae03776..0000000000 --- a/platform/features/plot/test/elements/PlotPanZoomStackGroupSpec.js +++ /dev/null @@ -1,126 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/elements/PlotPanZoomStackGroup"], - function (PlotPanZoomStackGroup) { - - var COUNT = 8; - - describe("A plot pan-zoom stack group", function () { - var stacks, - group; - - beforeEach(function () { - group = new PlotPanZoomStackGroup(COUNT); - stacks = []; - while (stacks.length < COUNT) { - stacks.push(group.getPanZoomStack(stacks.length)); - } - }); - - it("creates a number of separate stacks", function () { - expect(group.getPanZoomStack(0)).toBeDefined(); - expect(group.getPanZoomStack(COUNT - 1)).toBeDefined(); - expect(group.getPanZoomStack(COUNT)).toBeUndefined(); - }); - - it("synchronizes pan-zoom stack depth", function () { - expect(group.getDepth()).toEqual(1); - group.getPanZoomStack(1).pushPanZoom([10, 20], [30, 40]); - stacks.forEach(function (stack) { - expect(stack.getDepth()).toEqual(2); - }); - }); - - it("synchronizes domain but not range", function () { - // Set up different initial states - stacks.forEach(function (stack, i) { - stack.pushPanZoom([i, i], [i, i]); - }); - - // Push a new pan-zoom state onto one of the stacks - group.getPanZoomStack(1).pushPanZoom([99, 99], [42, 42]); - - // Should changed domain values for all stacks, but - // only changed range values for stack 1 - stacks.forEach(function (stack, i) { - expect(stack.getOrigin()) - .toEqual([99, i === 1 ? 99 : i]); - expect(stack.getDimensions()) - .toEqual([42, i === 1 ? 42 : i]); - }); - }); - - it("synchronizes base pan-zoom", function () { - group.setBasePanZoom([10, 9], [8, 7]); - stacks.forEach(function (stack) { - expect(stack.getOrigin()).toEqual([10, 9]); - expect(stack.getDimensions()).toEqual([8, 7]); - }); - }); - - it("clears pan-zoom on request", function () { - // Set up different initial states - stacks.forEach(function (stack, i) { - stack.pushPanZoom([i, i], [i, i]); - }); - - // Verify that we have a greater depth - expect(group.getDepth() > 1).toBeTruthy(); - - // Clear the pan-zoom state - group.clearPanZoom(); - - // Should be back down to our initial state - expect(group.getDepth()).toEqual(1); - stacks.forEach(function (stack) { - expect(stack.getDepth()).toEqual(1); - }); - }); - - it("pops pan-zoom on request", function () { - // Set up different initial states - stacks.forEach(function (stack, i) { - stack.pushPanZoom([i, i], [i, i]); - }); - - // Verify that we have a greater depth - expect(group.getDepth()).toEqual(COUNT + 1); - - // Clear the pan-zoom state - group.popPanZoom(); - - // Should be back down to our initial state - expect(group.getDepth()).toEqual(COUNT); - stacks.forEach(function (stack) { - expect(stack.getDepth()).toEqual(COUNT); - }); - }); - - - }); - } -); diff --git a/platform/features/plot/test/elements/PlotPanZoomStackSpec.js b/platform/features/plot/test/elements/PlotPanZoomStackSpec.js deleted file mode 100644 index e4522faff2..0000000000 --- a/platform/features/plot/test/elements/PlotPanZoomStackSpec.js +++ /dev/null @@ -1,99 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/elements/PlotPanZoomStack"], - function (PlotPanZoomStack) { - - describe("A plot pan-zoom stack", function () { - var panZoomStack, - initialOrigin, - initialDimensions, - otherOrigins, - otherDimensions; - - // Shorthand for verifying getOrigin, getDimensions, and getPanZoom, - // which should always agree. - function verifyPanZoom(origin, dimensions) { - expect(panZoomStack.getOrigin()).toEqual(origin); - expect(panZoomStack.getDimensions()).toEqual(dimensions); - expect(panZoomStack.getPanZoom()).toEqual({ - origin: origin, - dimensions: dimensions - }); - } - - beforeEach(function () { - initialOrigin = [4, 2]; - initialDimensions = [600, 400]; - otherOrigins = [[8, 6], [12, 9]]; - otherDimensions = [[400, 300], [200, 300]]; - panZoomStack = - new PlotPanZoomStack(initialOrigin, initialDimensions); - }); - - it("starts off reporting its initial values", function () { - verifyPanZoom(initialOrigin, initialDimensions); - }); - - it("allows origin/dimensions pairs to be pushed/popped", function () { - panZoomStack.pushPanZoom(otherOrigins[0], otherDimensions[0]); - verifyPanZoom(otherOrigins[0], otherDimensions[0]); - panZoomStack.pushPanZoom(otherOrigins[1], otherDimensions[1]); - verifyPanZoom(otherOrigins[1], otherDimensions[1]); - panZoomStack.popPanZoom(); - verifyPanZoom(otherOrigins[0], otherDimensions[0]); - panZoomStack.popPanZoom(); - verifyPanZoom(initialOrigin, initialDimensions); - }); - - it("reports current stack depth", function () { - expect(panZoomStack.getDepth()).toEqual(1); - panZoomStack.pushPanZoom(otherOrigins[0], otherDimensions[0]); - expect(panZoomStack.getDepth()).toEqual(2); - panZoomStack.pushPanZoom(otherOrigins[1], otherDimensions[1]); - expect(panZoomStack.getDepth()).toEqual(3); - }); - - it("allows base pan zoom to be restored", function () { - panZoomStack.pushPanZoom(otherOrigins[0], otherDimensions[0]); - panZoomStack.pushPanZoom(otherOrigins[1], otherDimensions[1]); - panZoomStack.clearPanZoom(); - verifyPanZoom(initialOrigin, initialDimensions); - }); - - it("allows base pan zoom to be changed", function () { - panZoomStack.pushPanZoom(otherOrigins[0], otherDimensions[0]); - panZoomStack.setBasePanZoom(otherOrigins[1], otherDimensions[1]); - // Should not have changed current top-of-stack - verifyPanZoom(otherOrigins[0], otherDimensions[0]); - - // Clear the stack - should be at our new base pan-zoom state - panZoomStack.clearPanZoom(); - verifyPanZoom(otherOrigins[1], otherDimensions[1]); - }); - }); - } -); diff --git a/platform/features/plot/test/elements/PlotPositionSpec.js b/platform/features/plot/test/elements/PlotPositionSpec.js deleted file mode 100644 index 47ef9dc939..0000000000 --- a/platform/features/plot/test/elements/PlotPositionSpec.js +++ /dev/null @@ -1,67 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/elements/PlotPosition"], - function (PlotPosition) { - - describe("A plot position", function () { - var mockPanZoom, - testOrigin = [10, 20], - testDimensions = [800, 10]; - - beforeEach(function () { - mockPanZoom = jasmine.createSpyObj( - "panZoomStack", - ["getPanZoom"] - ); - mockPanZoom.getPanZoom.andReturn({ - origin: testOrigin, - dimensions: testDimensions - }); - }); - - it("transforms pixel coordinates to domain-range", function () { - var position = new PlotPosition(42, 450, 100, 1000, mockPanZoom); - // Domain: .42 * 800 + 10 = 346 - // Range: .55 * 10 + 20 = 25.5 - // Notably, y-axis is reversed between pixel space and range - expect(position.getPosition()).toEqual([346, 25.5]); - expect(position.getDomain()).toEqual(346); - expect(position.getRange()).toEqual(25.5); - }); - - it("treats a position as undefined if no pan-zoom state is present", function () { - var position; - - mockPanZoom.getPanZoom.andReturn({}); - position = new PlotPosition(1, 2, 100, 100, mockPanZoom); - expect(position.getDomain()).toBeUndefined(); - expect(position.getRange()).toBeUndefined(); - expect(position.getPosition()).toEqual([]); - }); - }); - } -); diff --git a/platform/features/plot/test/elements/PlotPreparerSpec.js b/platform/features/plot/test/elements/PlotPreparerSpec.js deleted file mode 100644 index 0e3a0c5bd0..0000000000 --- a/platform/features/plot/test/elements/PlotPreparerSpec.js +++ /dev/null @@ -1,93 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/elements/PlotPreparer"], - function (PlotPreparer) { - - var START = 123456; - - describe("A plot preparer", function () { - - function makeMockData(scale) { - var mockData = jasmine.createSpyObj( - "data" + scale, - ["getPointCount", "getDomainValue", "getRangeValue"] - ); - mockData.getPointCount.andReturn(1000); - mockData.getDomainValue.andCallFake(function (i) { - return START + i * 1000; - }); - mockData.getRangeValue.andCallFake(function (i) { - return Math.sin(i / 100) * scale; - }); - return mockData; - } - - it("fits to provided data sets", function () { - var datas = [1, 2, 3].map(makeMockData), - preparer = new PlotPreparer(datas); - - expect(preparer.getDomainOffset()).toEqual(START); - expect(preparer.getOrigin()[0]).toBeCloseTo(START, 3); - expect(preparer.getOrigin()[1]).toBeCloseTo(-3, 3); - expect(preparer.getDimensions()[0]).toBeCloseTo(999000, 3); - expect(preparer.getDimensions()[1]).toBeCloseTo(6, 3); - }); - - it("looks up values using a specified domain and range", function () { - var datas = [makeMockData(1)], - preparer = new PlotPreparer(datas, "testDomain", "testRange"); - - expect(preparer).toBeDefined(); - - expect(datas[0].getDomainValue).toHaveBeenCalledWith( - jasmine.any(Number), - "testDomain" - ); - - expect(datas[0].getRangeValue).toHaveBeenCalledWith( - jasmine.any(Number), - "testRange" - ); - }); - - it("provides a default range if data set is flat", function () { - var datas = [makeMockData(0)], - preparer = new PlotPreparer(datas); - - expect(preparer.getDimensions[1]).not.toEqual(0); - }); - - it("provides buffers", function () { - var datas = [makeMockData(0)], - preparer = new PlotPreparer(datas); - expect(preparer.getBuffers()[0] instanceof Float32Array) - .toBeTruthy(); - }); - - }); - } -); diff --git a/platform/features/plot/test/elements/PlotSeriesWindowSpec.js b/platform/features/plot/test/elements/PlotSeriesWindowSpec.js deleted file mode 100644 index 1bd339994f..0000000000 --- a/platform/features/plot/test/elements/PlotSeriesWindowSpec.js +++ /dev/null @@ -1,93 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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( - ["../../src/elements/PlotSeriesWindow"], - function (PlotSeriesWindow) { - - describe("A plot's window on a telemetry series", function () { - var mockSeries, - testSeries, - window; - - beforeEach(function () { - testSeries = [ - [0, 42], - [10, 1], - [20, 4], - [30, 9], - [40, 3] - ]; - - mockSeries = jasmine.createSpyObj( - 'series', - ['getPointCount', 'getDomainValue', 'getRangeValue'] - ); - - mockSeries.getPointCount.andCallFake(function () { - return testSeries.length; - }); - mockSeries.getDomainValue.andCallFake(function (i) { - return testSeries[i][0]; - }); - mockSeries.getRangeValue.andCallFake(function (i) { - return testSeries[i][1]; - }); - - window = new PlotSeriesWindow( - mockSeries, - "testDomain", - "testRange", - 1, - testSeries.length - ); - }); - - it("provides a window upon a data series", function () { - expect(window.getPointCount()).toEqual(4); - expect(window.getDomainValue(0)).toEqual(10); - expect(window.getRangeValue(0)).toEqual(1); - }); - - it("looks up using specific domain/range keys", function () { - window.getDomainValue(0); - window.getRangeValue(0); - expect(mockSeries.getDomainValue) - .toHaveBeenCalledWith(1, 'testDomain'); - expect(mockSeries.getRangeValue) - .toHaveBeenCalledWith(1, 'testRange'); - }); - - it("can be split into smaller windows", function () { - var windows = window.split(); - expect(windows.length).toEqual(2); - expect(windows[0].getPointCount()).toEqual(2); - expect(windows[1].getPointCount()).toEqual(2); - expect(windows[0].getDomainValue(0)).toEqual(10); - expect(windows[1].getDomainValue(0)).toEqual(30); - expect(windows[0].getRangeValue(0)).toEqual(1); - expect(windows[1].getRangeValue(0)).toEqual(9); - }); - - }); - } -); diff --git a/platform/features/plot/test/elements/PlotTelemetryFormatterSpec.js b/platform/features/plot/test/elements/PlotTelemetryFormatterSpec.js deleted file mode 100644 index 13c0729966..0000000000 --- a/platform/features/plot/test/elements/PlotTelemetryFormatterSpec.js +++ /dev/null @@ -1,71 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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( - ["../../src/elements/PlotTelemetryFormatter"], - function (PlotTelemetryFormatter) { - - describe("The PlotTelemetryFormatter", function () { - var mockFormatter, - formatter; - - beforeEach(function () { - mockFormatter = jasmine.createSpyObj( - 'telemetryFormatter', - ['formatDomainValue', 'formatRangeValue'] - ); - formatter = new PlotTelemetryFormatter(mockFormatter); - }); - - describe("using domain & range format keys", function () { - var rangeFormat = "someRangeFormat", - domainFormat = "someDomainFormat"; - - beforeEach(function () { - formatter.setRangeFormat(rangeFormat); - formatter.setDomainFormat(domainFormat); - }); - - it("includes format in formatDomainValue calls", function () { - mockFormatter.formatDomainValue.andReturn("formatted!"); - expect(formatter.formatDomainValue(12321)) - .toEqual("formatted!"); - expect(mockFormatter.formatDomainValue) - .toHaveBeenCalledWith(12321, domainFormat); - }); - - it("includes format in formatRangeValue calls for strings", function () { - mockFormatter.formatRangeValue.andReturn("formatted!"); - expect(formatter.formatRangeValue('foo')) - .toEqual("formatted!"); - expect(mockFormatter.formatRangeValue) - .toHaveBeenCalledWith('foo', rangeFormat); - }); - - it("formats numeric values with three fixed digits", function () { - expect(formatter.formatRangeValue(10)).toEqual("10.000"); - }); - }); - - }); - } -); diff --git a/platform/features/plot/test/elements/PlotTickGeneratorSpec.js b/platform/features/plot/test/elements/PlotTickGeneratorSpec.js deleted file mode 100644 index 7d94fca140..0000000000 --- a/platform/features/plot/test/elements/PlotTickGeneratorSpec.js +++ /dev/null @@ -1,73 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/elements/PlotTickGenerator"], - function (PlotTickGenerator) { - - describe("A plot tick generator", function () { - var mockPanZoomStack, - mockFormatter, - generator; - - beforeEach(function () { - mockPanZoomStack = jasmine.createSpyObj( - "panZoomStack", - ["getPanZoom"] - ); - mockFormatter = jasmine.createSpyObj( - "formatter", - ["formatDomainValue", "formatRangeValue"] - ); - - mockPanZoomStack.getPanZoom.andReturn({ - origin: [0, 0], - dimensions: [100, 100] - }); - - generator = - new PlotTickGenerator(mockPanZoomStack, mockFormatter); - }); - - it("provides tick marks for range", function () { - expect(generator.generateRangeTicks(11).length).toEqual(11); - - // Should have used range formatter - expect(mockFormatter.formatRangeValue).toHaveBeenCalled(); - expect(mockFormatter.formatDomainValue).not.toHaveBeenCalled(); - - }); - - it("provides tick marks for domain", function () { - expect(generator.generateDomainTicks(11).length).toEqual(11); - - // Should have used domain formatter - expect(mockFormatter.formatRangeValue).not.toHaveBeenCalled(); - expect(mockFormatter.formatDomainValue).toHaveBeenCalled(); - }); - - }); - } -); diff --git a/platform/features/plot/test/elements/PlotUpdaterSpec.js b/platform/features/plot/test/elements/PlotUpdaterSpec.js deleted file mode 100644 index cd01a78ff2..0000000000 --- a/platform/features/plot/test/elements/PlotUpdaterSpec.js +++ /dev/null @@ -1,237 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/elements/PlotUpdater"], - function (PlotUpdater) { - - describe("A plot updater", function () { - var mockSubscription, - testDomain, - testRange, - testDomainValues, - testRangeValues, - mockSeries, - updater; - - function makeMockDomainObject(id) { - var mockDomainObject = jasmine.createSpyObj( - "object-" + id, - ["getId", "getCapability", "getModel"] - ); - mockDomainObject.getId.andReturn(id); - return mockDomainObject; - } - - beforeEach(function () { - var ids = ['a', 'b', 'c'], - mockObjects = ids.map(makeMockDomainObject); - - mockSubscription = jasmine.createSpyObj( - "subscription", - ["getDomainValue", "getRangeValue", "getTelemetryObjects"] - ); - mockSeries = jasmine.createSpyObj( - 'series', - ['getPointCount', 'getDomainValue', 'getRangeValue'] - ); - testDomain = "testDomain"; - testRange = "testRange"; - testDomainValues = { a: 3, b: 7, c: 13 }; - testRangeValues = { a: 123, b: 456, c: 789 }; - - mockSubscription.getTelemetryObjects.andReturn(mockObjects); - mockSubscription.getDomainValue.andCallFake(function (mockObject) { - return testDomainValues[mockObject.getId()]; - }); - mockSubscription.getRangeValue.andCallFake(function (mockObject) { - return testRangeValues[mockObject.getId()]; - }); - - updater = new PlotUpdater( - mockSubscription, - testDomain, - testRange, - 1350 // Smaller max size for easier testing - ); - }); - - it("provides one buffer per telemetry object", function () { - expect(updater.getLineBuffers().length).toEqual(3); - }); - - it("changes buffer count if telemetry object counts change", function () { - mockSubscription.getTelemetryObjects - .andReturn([makeMockDomainObject('a')]); - updater.update(); - expect(updater.getLineBuffers().length).toEqual(1); - }); - - it("can handle delayed telemetry object availability", function () { - // The case can occur where getTelemetryObjects() returns an - // empty array - specifically, while objects are still being - // loaded. The updater needs to be able to cope with that - // case. - var tmp = mockSubscription.getTelemetryObjects(); - mockSubscription.getTelemetryObjects.andReturn([]); - - // Reinstantiate with the empty subscription - updater = new PlotUpdater( - mockSubscription, - testDomain, - testRange - ); - - // Should have 0 buffers for 0 objects - expect(updater.getLineBuffers().length).toEqual(0); - - // Restore the three objects the test subscription would - // normally have. - mockSubscription.getTelemetryObjects.andReturn(tmp); - updater.update(); - - // Should have 3 buffers for 3 objects - expect(updater.getLineBuffers().length).toEqual(3); - }); - - it("accepts historical telemetry updates", function () { - var mockObject = mockSubscription.getTelemetryObjects()[0]; - - mockSeries.getPointCount.andReturn(3); - mockSeries.getDomainValue.andCallFake(function (i) { - return 1000 + i * 1000; - }); - mockSeries.getRangeValue.andReturn(10); - - // PlotLine & PlotLineBuffer are tested for most of the - // details here, so just check for some expected side - // effect; in this case, should see more points in the buffer - expect(updater.getLineBuffers()[0].getLength()).toEqual(1); - updater.addHistorical(mockObject, mockSeries); - expect(updater.getLineBuffers()[0].getLength()).toEqual(4); - }); - - it("clears the domain offset if no objects are present", function () { - mockSubscription.getTelemetryObjects.andReturn([]); - updater.update(); - expect(updater.getDomainOffset()).toBeUndefined(); - }); - - it("handles empty historical telemetry updates", function () { - // General robustness check for when a series is empty - var mockObject = mockSubscription.getTelemetryObjects()[0]; - - mockSeries.getPointCount.andReturn(0); - mockSeries.getDomainValue.andCallFake(function (i) { - return 1000 + i * 1000; - }); - mockSeries.getRangeValue.andReturn(10); - - // PlotLine & PlotLineBuffer are tested for most of the - // details here, so just check for some expected side - // effect; in this case, should see more points in the buffer - expect(updater.getLineBuffers()[0].getLength()).toEqual(1); - updater.addHistorical(mockObject, mockSeries); - expect(updater.getLineBuffers()[0].getLength()).toEqual(1); - }); - - it("can initialize domain offset from historical telemetry", function () { - var tmp = mockSubscription.getTelemetryObjects(); - - mockSubscription.getTelemetryObjects.andReturn([]); - - // Reinstantiate with the empty subscription - updater = new PlotUpdater( - mockSubscription, - testDomain, - testRange - ); - - // Restore subscription, provide some historical data - mockSubscription.getTelemetryObjects.andReturn(tmp); - mockSeries.getPointCount.andReturn(3); - mockSeries.getDomainValue.andCallFake(function (i) { - return 1000 + i * 1000; - }); - mockSeries.getRangeValue.andReturn(10); - - // PlotLine & PlotLineBuffer are tested for most of the - // details here, so just check for some expected side - // effect; in this case, should see more points in the buffer - expect(updater.getDomainOffset()).toBeUndefined(); - updater.addHistorical(tmp[0], mockSeries); - expect(updater.getDomainOffset()).toBeDefined(); - }); - - it("provides some margin for the range", function () { - var mockObject = mockSubscription.getTelemetryObjects()[0]; - - mockSeries.getPointCount.andReturn(3); - mockSeries.getDomainValue.andCallFake(function (i) { - return 1000 + i * 1000; - }); - mockSeries.getRangeValue.andCallFake(function (i) { - return 10 + i; // 10, 20, 30 - }); - updater.addHistorical(mockObject, mockSeries); - expect(updater.getOrigin()[1]).toBeLessThan(10); - expect(updater.getDimensions()[1]).toBeGreaterThan(20); - }); - - describe("when no data is initially available", function () { - beforeEach(function () { - testDomainValues = {}; - testRangeValues = {}; - updater = new PlotUpdater( - mockSubscription, - testDomain, - testRange, - 1350 // Smaller max size for easier testing - ); - }); - - it("has no line data", function () { - // Either no lines, or empty lines are fine - expect(updater.getLineBuffers().map(function (lineBuffer) { - return lineBuffer.getLength(); - }).reduce(function (a, b) { - return a + b; - }, 0)).toEqual(0); - }); - - it("determines initial domain bounds from first available data", function () { - testDomainValues.a = 123; - testRangeValues.a = 456; - updater.update(); - expect(updater.getOrigin()[0]).toEqual(jasmine.any(Number)); - expect(updater.getOrigin()[1]).toEqual(jasmine.any(Number)); - expect(isNaN(updater.getOrigin()[0])).toBeFalsy(); - expect(isNaN(updater.getOrigin()[1])).toBeFalsy(); - }); - }); - - }); - } -); diff --git a/platform/features/plot/test/modes/PlotModeOptionsSpec.js b/platform/features/plot/test/modes/PlotModeOptionsSpec.js deleted file mode 100644 index ce792fd1b6..0000000000 --- a/platform/features/plot/test/modes/PlotModeOptionsSpec.js +++ /dev/null @@ -1,87 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/modes/PlotModeOptions"], - function (PlotModeOptions) { - - describe("Plot mode options", function () { - var mockDomainObject, - mockSubPlotFactory; - - beforeEach(function () { - mockDomainObject = jasmine.createSpyObj( - "domainObject", - ["getId", "getModel", "getCapability"] - ); - mockSubPlotFactory = jasmine.createSpyObj( - "subPlotFactory", - ["createSubPlot"] - ); - }); - - it("offers only one option when one object is present", function () { - expect( - new PlotModeOptions([mockDomainObject], mockSubPlotFactory) - .getModeOptions().length - ).toEqual(1); - }); - - it("offers two options when multiple objects are present", function () { - var objects = [ - mockDomainObject, - mockDomainObject, - mockDomainObject, - mockDomainObject - ]; - expect( - new PlotModeOptions(objects, mockSubPlotFactory) - .getModeOptions().length - ).toEqual(2); - }); - - it("allows modes to be changed", function () { - var plotModeOptions = new PlotModeOptions([ - mockDomainObject, - mockDomainObject, - mockDomainObject, - mockDomainObject - ], mockSubPlotFactory), - initialHandler = plotModeOptions.getModeHandler(); - - // Change the mode - plotModeOptions.getModeOptions().forEach(function (option) { - if (option !== plotModeOptions.getMode()) { - plotModeOptions.setMode(option); - } - }); - - // Mode should be different now - expect(plotModeOptions.getModeHandler()) - .not.toBe(initialHandler); - }); - }); - } -); diff --git a/platform/features/plot/test/modes/PlotOverlayModeSpec.js b/platform/features/plot/test/modes/PlotOverlayModeSpec.js deleted file mode 100644 index cf1e0ce875..0000000000 --- a/platform/features/plot/test/modes/PlotOverlayModeSpec.js +++ /dev/null @@ -1,184 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/modes/PlotOverlayMode"], - function (PlotOverlayMode) { - - describe("Overlaid plot mode", function () { - var mockDomainObject, - mockSubPlotFactory, - mockPrepared, - testBuffers, - testDrawingObjects, - mode; - - function createMockSubPlot() { - var mockSubPlot = jasmine.createSpyObj( - "subPlot", - [ - "setDomainOffset", - "hover", - "startMarquee", - "endMarquee", - "getDrawingObject", - "update" - ] - ), - testDrawingObject = {}; - - // Track drawing objects in order of creation - testDrawingObjects.push(testDrawingObject); - mockSubPlot.getDrawingObject.andReturn(testDrawingObject); - return mockSubPlot; - } - - beforeEach(function () { - mockDomainObject = jasmine.createSpyObj( - "domainObject", - ["getId", "getModel", "getCapability"] - ); - mockSubPlotFactory = jasmine.createSpyObj( - "subPlotFactory", - ["createSubPlot"] - ); - // Prepared telemetry data - mockPrepared = jasmine.createSpyObj( - "prepared", - [ - "getDomainOffset", - "getOrigin", - "getDimensions", - "getLineBuffers" - ] - ); - - mockSubPlotFactory.createSubPlot.andCallFake(createMockSubPlot); - - // Act as if we have three buffers full of data - testBuffers = ['a', 'b', 'c'].map(function (id) { - var mockBuffer = jasmine.createSpyObj( - 'buffer-' + id, - ['getBuffer', 'getLength'] - ); - mockBuffer.getBuffer.andReturn([id]); - mockBuffer.getLength.andReturn(3); - return mockBuffer; - }); - mockPrepared.getLineBuffers.andReturn(testBuffers); - mockPrepared.getDomainOffset.andReturn(1234); - mockPrepared.getOrigin.andReturn([10, 10]); - mockPrepared.getDimensions.andReturn([500, 500]); - - // Clear out drawing objects - testDrawingObjects = []; - - mode = new PlotOverlayMode([ - mockDomainObject, - mockDomainObject, - mockDomainObject - ], mockSubPlotFactory); - }); - - it("creates one sub-plot for all domain objects", function () { - expect(mode.getSubPlots().length).toEqual(1); - }); - - it("draws telemetry to subplots", function () { - // Verify precondition - mode.getSubPlots().forEach(function (subplot) { - // Either empty list or undefined is fine; - // just want to make sure there are no lines. - expect(subplot.getDrawingObject().lines || []) - .toEqual([]); - }); - - mode.plotTelemetry(mockPrepared); - - // Should have one sub-plot with three lines - testDrawingObjects.forEach(function (testDrawingObject) { - // Either empty list or undefined is fine; - // just want to make sure there are no lines. - expect(testDrawingObject.lines.length) - .toEqual(3); - // Make sure the right buffer was drawn to the - // right subplot. - testDrawingObject.lines.forEach(function (line, j) { - expect(line.buffer).toEqual(testBuffers[j].getBuffer()); - }); - }); - }); - - it("tracks zoomed state of subplots", function () { - // Should start out unzoomed - expect(mode.isZoomed()).toBeFalsy(); - - // Trigger some zoom changes - mockSubPlotFactory.createSubPlot.calls.forEach(function (c) { - // Second argument to the factory was pan-zoom stack - c.args[1].pushPanZoom([1, 2], [3, 4]); - }); - - // Should start out unzoomed - expect(mode.isZoomed()).toBeTruthy(); - }); - - it("supports unzooming", function () { - // Trigger some zoom changes - mockSubPlotFactory.createSubPlot.calls.forEach(function (c) { - // Second argument to the factory was pan-zoom stack - c.args[1].pushPanZoom([1, 2], [3, 4]); - }); - // Verify that we are indeed zoomed now - expect(mode.isZoomed()).toBeTruthy(); - - // Unzoom - mode.unzoom(); - - // Should no longer be zoomed - expect(mode.isZoomed()).toBeFalsy(); - }); - - it("supports stepping back through zoom states", function () { - // Trigger some zoom changes - mockSubPlotFactory.createSubPlot.calls.forEach(function (c) { - // Second argument to the factory was pan-zoom stack - c.args[1].pushPanZoom([1, 2], [3, 4]); - }); - - // Step back the same number of zoom changes - mockSubPlotFactory.createSubPlot.calls.forEach(function () { - // Should still be zoomed at start of each iteration - expect(mode.isZoomed()).toBeTruthy(); - // Step back one of the zoom changes. - mode.stepBackPanZoom(); - }); - - // Should no longer be zoomed - expect(mode.isZoomed()).toBeFalsy(); - }); - }); - } -); diff --git a/platform/features/plot/test/modes/PlotStackModeSpec.js b/platform/features/plot/test/modes/PlotStackModeSpec.js deleted file mode 100644 index cac4e18b82..0000000000 --- a/platform/features/plot/test/modes/PlotStackModeSpec.js +++ /dev/null @@ -1,179 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/modes/PlotStackMode"], - function (PlotStackMode) { - - describe("Stacked plot mode", function () { - var mockDomainObject, - mockSubPlotFactory, - mockPrepared, - testBuffers, - testDrawingObjects, - mode; - - function createMockSubPlot() { - var mockSubPlot = jasmine.createSpyObj( - "subPlot", - [ - "setDomainOffset", - "hover", - "startMarquee", - "endMarquee", - "getDrawingObject", - "update" - ] - ), - testDrawingObject = {}; - - // Track drawing objects in order of creation - testDrawingObjects.push(testDrawingObject); - mockSubPlot.getDrawingObject.andReturn(testDrawingObject); - return mockSubPlot; - } - - beforeEach(function () { - mockDomainObject = jasmine.createSpyObj( - "domainObject", - ["getId", "getModel", "getCapability"] - ); - mockSubPlotFactory = jasmine.createSpyObj( - "subPlotFactory", - ["createSubPlot"] - ); - // Prepared telemetry data - mockPrepared = jasmine.createSpyObj( - "prepared", - ["getDomainOffset", "getOrigin", "getDimensions", "getLineBuffers"] - ); - - mockSubPlotFactory.createSubPlot.andCallFake(createMockSubPlot); - - // Act as if we have three buffers full of data - testBuffers = ['a', 'b', 'c'].map(function (id) { - var mockBuffer = jasmine.createSpyObj( - 'buffer-' + id, - ['getBuffer', 'getLength'] - ); - mockBuffer.getBuffer.andReturn([id]); - mockBuffer.getLength.andReturn(3); - return mockBuffer; - }); - mockPrepared.getLineBuffers.andReturn(testBuffers); - mockPrepared.getDomainOffset.andReturn(1234); - mockPrepared.getOrigin.andReturn([10, 10]); - mockPrepared.getDimensions.andReturn([500, 500]); - - // Objects that will be drawn to in sub-plots - testDrawingObjects = []; - - mode = new PlotStackMode([ - mockDomainObject, - mockDomainObject, - mockDomainObject - ], mockSubPlotFactory); - }); - - it("creates one sub-plot per domain object", function () { - expect(mode.getSubPlots().length).toEqual(3); - }); - - it("draws telemetry to subplots", function () { - // Verify precondition - mode.getSubPlots().forEach(function (subplot) { - // Either empty list or undefined is fine; - // just want to make sure there are no lines. - expect(subplot.getDrawingObject().lines || []) - .toEqual([]); - }); - - mode.plotTelemetry(mockPrepared); - - // Should all each have one line - testDrawingObjects.forEach(function (testDrawingObject, i) { - // Either empty list or undefined is fine; - // just want to make sure there are no lines. - expect(testDrawingObject.lines.length) - .toEqual(1); - // Make sure the right buffer was drawn to the - // right subplot. - expect(testDrawingObject.lines[0].buffer) - .toEqual(testBuffers[i].getBuffer()); - }); - }); - - it("tracks zoomed state of subplots", function () { - // Should start out unzoomed - expect(mode.isZoomed()).toBeFalsy(); - - // Trigger some zoom changes - mockSubPlotFactory.createSubPlot.calls.forEach(function (c) { - // Second argument to the factory was pan-zoom stack - c.args[1].pushPanZoom([1, 2], [3, 4]); - }); - - // Should start out unzoomed - expect(mode.isZoomed()).toBeTruthy(); - }); - - it("supports unzooming", function () { - // Trigger some zoom changes - mockSubPlotFactory.createSubPlot.calls.forEach(function (c) { - // Second argument to the factory was pan-zoom stack - c.args[1].pushPanZoom([1, 2], [3, 4]); - }); - // Verify that we are indeed zoomed now - expect(mode.isZoomed()).toBeTruthy(); - - // Unzoom - mode.unzoom(); - - // Should no longer be zoomed - expect(mode.isZoomed()).toBeFalsy(); - }); - - it("supports stepping back through zoom states", function () { - // Trigger some zoom changes - mockSubPlotFactory.createSubPlot.calls.forEach(function (c) { - // Second argument to the factory was pan-zoom stack - c.args[1].pushPanZoom([1, 2], [3, 4]); - }); - - // Step back the same number of zoom changes - mockSubPlotFactory.createSubPlot.calls.forEach(function () { - // Should still be zoomed at start of each iteration - expect(mode.isZoomed()).toBeTruthy(); - // Step back - mode.stepBackPanZoom(); - }); - - // Should no longer be zoomed - expect(mode.isZoomed()).toBeFalsy(); - }); - - }); - } -); diff --git a/platform/features/plot/test/policies/PlotViewPolicySpec.js b/platform/features/plot/test/policies/PlotViewPolicySpec.js deleted file mode 100644 index fc1b2232aa..0000000000 --- a/platform/features/plot/test/policies/PlotViewPolicySpec.js +++ /dev/null @@ -1,123 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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( - ["../../src/policies/PlotViewPolicy"], - function (PlotViewPolicy) { - - describe("Plot view policy", function () { - var testView, - mockDomainObject, - testAdaptedObject, - openmct, - telemetryMetadata, - policy; - - beforeEach(function () { - testView = { key: "plot" }; - testAdaptedObject = { telemetry: {} }; - mockDomainObject = jasmine.createSpyObj( - 'domainObject', - ['useCapability', 'hasCapability', 'getCapability'] - ); - mockDomainObject.useCapability.andReturn(testAdaptedObject); - openmct = { - telemetry: jasmine.createSpyObj('telemetryAPI', [ - 'getMetadata' - ]) - }; - telemetryMetadata = jasmine.createSpyObj('telemetryMetadata', [ - 'valuesForHints' - ]); - telemetryMetadata.valuesForHints.andReturn([]); - openmct.telemetry.getMetadata.andReturn(telemetryMetadata); - policy = new PlotViewPolicy(openmct); - }); - - it('fetches metadata from telem api', function () { - policy.allow(testView, mockDomainObject); - expect(mockDomainObject.useCapability) - .toHaveBeenCalledWith('adapter'); - expect(openmct.telemetry.getMetadata) - .toHaveBeenCalledWith(testAdaptedObject); - expect(telemetryMetadata.valuesForHints) - .toHaveBeenCalledWith(['range']); - }); - - it('returns false if no ranges exist', function () { - telemetryMetadata.valuesForHints.andReturn([]); - expect(policy.allow(testView, mockDomainObject)).toBe(false); - }); - - it('returns true if any ranges exist', function () { - telemetryMetadata.valuesForHints.andReturn([{}]); - expect(policy.allow(testView, mockDomainObject)).toBe(true); - }); - - it('returns false if all ranges are strings', function () { - telemetryMetadata.valuesForHints.andReturn([{ - format: 'string' - }, { - format: 'string' - }]); - expect(policy.allow(testView, mockDomainObject)).toBe(false); - }); - - it('returns true if only some ranges are strings', function () { - telemetryMetadata.valuesForHints.andReturn([{ - format: 'string' - }, {}]); - expect(policy.allow(testView, mockDomainObject)).toBe(true); - }); - - it('returns true for telemetry delegators', function () { - delete testAdaptedObject.telemetry; - mockDomainObject.hasCapability.andCallFake(function (c) { - return c === 'delegation'; - }); - mockDomainObject.getCapability.andReturn( - jasmine.createSpyObj('delegation', [ - 'doesDelegateCapability' - ]) - ); - mockDomainObject.getCapability('delegation') - .doesDelegateCapability.andCallFake(function (c) { - return c === 'telemetry'; - }); - expect(policy.allow(testView, mockDomainObject)).toBe(true); - expect(openmct.telemetry.getMetadata).not.toHaveBeenCalled(); - }); - - it('returns true for non-telemetry non-delegators', function () { - delete testAdaptedObject.telemetry; - mockDomainObject.hasCapability.andReturn(false); - expect(policy.allow(testView, mockDomainObject)).toBe(false); - }); - - it("allows other views", function () { - testView.key = "somethingElse"; - expect(policy.allow(testView, mockDomainObject)).toBe(true); - }); - - }); - } -); diff --git a/platform/features/plot/test/services/ExportImageServiceSpec.js b/platform/features/plot/test/services/ExportImageServiceSpec.js deleted file mode 100644 index a898a9ad6e..0000000000 --- a/platform/features/plot/test/services/ExportImageServiceSpec.js +++ /dev/null @@ -1,146 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, 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. - *****************************************************************************/ - -/** - * ExportImageServiceSpec. Created by hudsonfoo on 09/03/16. - */ -define( - ["../../src/services/ExportImageService"], - function (ExportImageService) { - var mockQ, - mockDeferred, - mockPromise, - mockTimeout, - mockLog, - mockHtml2Canvas, - mockCanvas, - mockSaveAs, - mockFileReader, - mockExportTimeoutConstant, - testElement, - exportImageService, - mockChangeBackgroundColor; - - describe("ExportImageService", function () { - beforeEach(function () { - mockDeferred = jasmine.createSpyObj( - "deferred", - ["reject", "resolve"] - ); - mockPromise = jasmine.createSpyObj( - "promise", - ["then", "finally"] - ); - mockPromise.then = function (callback) { - callback(); - }; - mockQ = { - "defer": function () { - return { - "resolve": mockDeferred.resolve, - "reject": mockDeferred.reject, - "promise": mockPromise - }; - } - }; - mockTimeout = function (fn, time) { - return { - "cancel": function () {} - }; - }; - mockLog = jasmine.createSpyObj( - "$log", - ["warn"] - ); - mockHtml2Canvas = jasmine.createSpy("html2canvas").andCallFake(function (element, opts) { - opts.onrendered(mockCanvas); - }); - mockCanvas = jasmine.createSpyObj( - "canvas", - ["toBlob"] - ); - mockSaveAs = jasmine.createSpy("saveAs"); - mockFileReader = jasmine.createSpyObj( - "FileReader", - ["readAsDataURL", "onloadend"] - ); - mockExportTimeoutConstant = 0; - testElement = {style: {backgroundColor: 'black'}}; - - mockChangeBackgroundColor = jasmine.createSpy('changeBackgroundColor'); - - exportImageService = new ExportImageService( - mockQ, - mockTimeout, - mockLog, - mockExportTimeoutConstant, - mockHtml2Canvas, - mockSaveAs, - mockFileReader, - mockChangeBackgroundColor - ); - }); - - it("runs html2canvas and tries to save a png", function () { - exportImageService.exportPNG(testElement, "plot.png"); - - expect(mockHtml2Canvas).toHaveBeenCalledWith(testElement, { onrendered: jasmine.any(Function) }); - expect(mockCanvas.toBlob).toHaveBeenCalledWith(mockDeferred.resolve, "image/png"); - expect(mockDeferred.reject).not.toHaveBeenCalled(); - expect(mockSaveAs).toHaveBeenCalled(); - expect(mockPromise.finally).toHaveBeenCalled(); - }); - - it("runs html2canvas and tries to save a jpg", function () { - exportImageService.exportJPG(testElement, "plot.png"); - - expect(mockHtml2Canvas).toHaveBeenCalledWith(testElement, { onrendered: jasmine.any(Function) }); - expect(mockCanvas.toBlob).toHaveBeenCalledWith(mockDeferred.resolve, "image/jpeg"); - expect(mockDeferred.reject).not.toHaveBeenCalled(); - expect(mockSaveAs).toHaveBeenCalled(); - expect(mockPromise.finally).toHaveBeenCalled(); - }); - - it("changes background color to white and returns color back to original after snapshot, for better visibility of plot lines on print", function () { - exportImageService.exportPNG(testElement, "plot.png", 'white'); - - expect(mockChangeBackgroundColor).toHaveBeenCalledWith(testElement, 'white'); - expect(mockChangeBackgroundColor).toHaveBeenCalledWith(testElement, 'black'); - - exportImageService.exportJPG(testElement, "plot.jpg", 'white'); - - expect(mockChangeBackgroundColor).toHaveBeenCalledWith(testElement, 'white'); - expect(mockChangeBackgroundColor).toHaveBeenCalledWith(testElement, 'black'); - }); - - it("does not change background color when color is not specified in parameters", function () { - exportImageService.exportPNG(testElement, "plot.png"); - - expect(mockChangeBackgroundColor).not.toHaveBeenCalled(); - - exportImageService.exportJPG(testElement, "plot.jpg"); - - expect(mockChangeBackgroundColor).not.toHaveBeenCalled(); - }); - }); - } -); diff --git a/platform/telemetry/src/TelemetryCapability.js b/platform/telemetry/src/TelemetryCapability.js index 38258cdb71..097925a2aa 100644 --- a/platform/telemetry/src/TelemetryCapability.js +++ b/platform/telemetry/src/TelemetryCapability.js @@ -205,6 +205,9 @@ define( }, getPointCount: function () { return telemetry.length; + }, + getData: function () { + return telemetry; } }; } diff --git a/src/api/telemetry/LegacyTelemetryProvider.js b/src/api/telemetry/LegacyTelemetryProvider.js index c0f40eb85a..05f1d66ba0 100644 --- a/src/api/telemetry/LegacyTelemetryProvider.js +++ b/src/api/telemetry/LegacyTelemetryProvider.js @@ -148,7 +148,9 @@ define([ var limitEvaluator = oldObject.getCapability("limit"); if (!limitEvaluator) { - return; + return { + evaluate: function () {} + }; } return { diff --git a/src/api/telemetry/TelemetryMetadataManager.js b/src/api/telemetry/TelemetryMetadataManager.js index e8a41806e7..52b152b741 100644 --- a/src/api/telemetry/TelemetryMetadataManager.js +++ b/src/api/telemetry/TelemetryMetadataManager.js @@ -100,6 +100,18 @@ define([ delete valueMetadata.hints.y; } + if (valueMetadata.format === 'enum') { + if (!valueMetadata.values) { + valueMetadata.values = _.pluck(valueMetadata.enumerations, 'value'); + } + if (!valueMetadata.hasOwnProperty('max')) { + valueMetadata.max = _.max(valueMetadata.values) + 1; + } + if (!valueMetadata.hasOwnProperty('min')) { + valueMetadata.min = _.min(valueMetadata.values) - 1; + } + } + if (!valueMetadata.hints.hasOwnProperty('priority')) { valueMetadata.hints.priority = index; } diff --git a/src/api/telemetry/TelemetryValueFormatter.js b/src/api/telemetry/TelemetryValueFormatter.js index c4db2560f3..2153b479c8 100644 --- a/src/api/telemetry/TelemetryValueFormatter.js +++ b/src/api/telemetry/TelemetryValueFormatter.js @@ -49,7 +49,7 @@ define([ this.formatter = numberFormatter; } - if (valueMetadata.type === 'enum') { + if (valueMetadata.format === 'enum') { this.formatter = {}; this.enumerations = valueMetadata.enumerations.reduce(function (vm, e) { vm.byValue[e.value] = e.string; @@ -57,11 +57,16 @@ define([ return vm; }, {byValue: {}, byString: {}}); this.formatter.format = function (value) { - return this.enumerations.byValue[value]; + if (typeof value === "number") { + return this.enumerations.byValue[value] || value; + } + return value; }.bind(this); this.formatter.parse = function (string) { - if (typeof string === "string" && this.enumerations.hasOwnProperty(string)) { - return this.enumerations.byString[string]; + if (typeof string === "string") { + if (this.enumerations.byString.hasOwnProperty(string)) { + return this.enumerations.byString[string]; + } } return Number(string); }.bind(this); diff --git a/src/defaultRegistry.js b/src/defaultRegistry.js index 239571e3b3..d138998fa9 100644 --- a/src/defaultRegistry.js +++ b/src/defaultRegistry.js @@ -73,7 +73,6 @@ define([ '../platform/features/my-items/bundle', '../platform/features/pages/bundle', '../platform/features/hyperlink/bundle', - '../platform/features/plot/bundle', '../platform/features/static-markup/bundle', '../platform/features/table/bundle', '../platform/features/timeline/bundle', @@ -120,7 +119,6 @@ define([ 'platform/features/listview', 'platform/features/pages', 'platform/features/hyperlink', - 'platform/features/plot', 'platform/features/timeline', 'platform/features/table', 'platform/forms', diff --git a/src/plugins/plot/plugin.js b/src/plugins/plot/plugin.js new file mode 100644 index 0000000000..d217e2f47d --- /dev/null +++ b/src/plugins/plot/plugin.js @@ -0,0 +1,250 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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. + *****************************************************************************/ + +/*global define*/ + +define([ + "./src/chart/MCTChartDirective", + "./src/plot/MCTPlotDirective", + './src/plot/MCTTicksDirective', + "./src/telemetry/MCTOverlayPlot", + "./src/telemetry/PlotController", + "./src/telemetry/StackedPlotController", + "./src/inspector/PlotInspector", + "./src/inspector/PlotOptionsController", + "./src/inspector/HideElementPoolDirective", + "./src/services/ExportImageService", + './src/PlotViewPolicy', + "text!./res/templates/plot-options.html", + "text!./res/templates/plot-options-browse.html", + "text!./res/templates/plot-options-edit.html", + "text!./res/templates/stacked-plot.html", + "text!./res/templates/plot.html" +], function ( + MCTChartDirective, + MCTPlotDirective, + MCTTicksDirective, + MCTOverlayPlot, + PlotController, + StackedPlotController, + PlotInspector, + PlotOptionsController, + HideElementPool, + ExportImageService, + PlotViewPolicy, + plotOptionsTemplate, + plotOptionsBrowseTemplate, + plotOptionsEditTemplate, + StackedPlotTemplate, + PlotTemplate +) { + + var installed = false; + + function PlotPlugin() { + return function install(openmct) { + if (installed) { + return; + } + installed = true; + + openmct.legacyRegistry.register("openmct/plot", { + "name": "Plot view for telemetry, reborn", + "extensions": { + "policies": [ + { + "category": "view", + "implementation": PlotViewPolicy, + "depends": [ + "openmct" + ] + } + ], + "views": [ + { + "name": "Plot", + "key": "plot-single", + "cssClass": "icon-telemetry", + "template": PlotTemplate, + "needs": [ + "telemetry" + ], + "delegation": false, + "priority": "mandatory" + }, + { + "name": "Overlay Plot", + "key": "overlayPlot", + "cssClass": "icon-plot-overlay", + "type": "telemetry.plot.overlay", + "template": PlotTemplate, + "editable": true + }, + { + "name": "Stacked Plot", + "key": "stackedPlot", + "cssClass": "icon-plot-stacked", + "type": "telemetry.plot.stacked", + "template": StackedPlotTemplate, + "editable": true + } + ], + "directives": [ + { + "key": "mctTicks", + "implementation": MCTTicksDirective, + "depends": [] + }, + { + "key": "mctChart", + "implementation": MCTChartDirective, + "depends": [ + "$interval", + "$log" + ] + }, + { + "key": "mctPlot", + "implementation": MCTPlotDirective, + "depends": [], + "templateUrl": "templates/mct-plot.html" + }, + { + "key": "mctOverlayPlot", + "implementation": MCTOverlayPlot, + "depends": [] + }, + { + "key": "hideElementPool", + "implementation": HideElementPool, + "depends": [] + } + ], + "controllers": [ + { + "key": "PlotController", + "implementation": PlotController, + "depends": [ + "$scope", + "$element", + "formatService", + "openmct", + "objectService", + "exportImageService" + ] + }, + { + "key": "StackedPlotController", + "implementation": StackedPlotController, + "depends": [ + "$scope", + "openmct", + "objectService", + "$element", + "exportImageService" + ] + }, + { + "key": "PlotOptionsController", + "implementation": PlotOptionsController, + "depends": [ + "$scope", + "openmct", + "$timeout" + ] + } + ], + "services": [ + { + "key": "exportImageService", + "implementation": ExportImageService, + "depends": [ + "$q", + "$timeout", + "$log" + ] + } + ], + "types": [ + { + "key": "telemetry.plot.overlay", + "name": "Overlay Plot", + "cssClass": "icon-plot-overlay", + "description": "Combine multiple telemetry elements and view them together as a plot with common X and Y axes. Can be added to Display Layouts.", + "features": "creation", + "contains": [ + { + "has": "telemetry" + } + ], + "model": { + composition: [], + configuration: { + series: [], + yAxis: {}, + xAxis: {} + } + }, + "properties": [], + "inspector": "plot-options", + "priority": 891 + }, + { + "key": "telemetry.plot.stacked", + "name": "Stacked Plot", + "cssClass": "icon-plot-stacked", + "description": "Combine multiple telemetry elements and view them together as a plot with a common X axis and individual Y axes. Can be added to Display Layouts.", + "features": "creation", + "contains": [ + "telemetry.plot.overlay", + {"has": "telemetry"} + ], + "model": { + "composition": [] + }, + "properties": [], + "priority": 890 + } + ], + "representations": [ + { + "key": "plot-options", + "template": plotOptionsTemplate + }, + { + "key": "plot-options-browse", + "template": plotOptionsBrowseTemplate + }, + { + "key": "plot-options-edit", + "template": plotOptionsEditTemplate + } + ] + } + }); + + openmct.legacyRegistry.enable("openmct/plot"); + }; + } + + return PlotPlugin; +}); diff --git a/src/plugins/plot/res/templates/mct-plot.html b/src/plugins/plot/res/templates/mct-plot.html new file mode 100644 index 0000000000..8f764ace3d --- /dev/null +++ b/src/plugins/plot/res/templates/mct-plot.html @@ -0,0 +1,210 @@ + +
+
+ + + + +
+
+
+ + + {{ series.get('name') }} +
+
+ {{ legend.get('valueToShowWhenCollapsed') === 'nearestValue' ? + series.formatY(series.closest) : + legend.get('valueToShowWhenCollapsed') === 'nearestTimestamp' ? + series.closest && series.formatX(series.closest) : + series.formatY(series.get('stats')[legend.get('valueToShowWhenCollapsed') + 'Point']); + }} +
+
+
+ + +
+ + + + + + + + + + + + + + + + + + +
Name + Timestamp + + Value + + Min + + Max +
+ + + {{ series.get('name') }} + + + {{ series.closest && series.formatX(series.closest) }} + + + + {{ series.formatY(series.closest) }} + + + + {{ series.formatY(series.get('stats').minPoint) }} + + + + {{ series.formatY(series.get('stats').maxPoint) }} + +
+
+
+ +
+
+ +
+ {{ yAxis.get('label') }} +
+ + +
+ {{:: tick.text}} +
+
+
+
+ +
+ +
+
+
+ + + +
+
+
+ + + + + +
+ + + + + + + +
+ + + + +
+ +
+ + +
+ {{:: tick.text | reverse}} +
+
+ +
+ {{ xAxis.get('label') }} +
+
+ +
+
+ +
diff --git a/src/plugins/plot/res/templates/plot-options-browse.html b/src/plugins/plot/res/templates/plot-options-browse.html new file mode 100644 index 0000000000..74699cdba2 --- /dev/null +++ b/src/plugins/plot/res/templates/plot-options-browse.html @@ -0,0 +1,130 @@ + +
+
  • + Plot Series +
      +
        +
      • + + + + + + +
        +
        +
        Line Style
        +
        {{ { + 'none': 'None', + 'linear': 'Linear interpolation', + 'stepAfter': 'Step After' + }[series.get('interpolate')] }}
        +
        +
        +
        Markers
        +
        + {{series.get('markers') ? "On, " + series.get('markerSize') + "px" : "Off"}} +
        +
        + +
        +
        Color
        +
        + + +
        +
        +
        +
      • +
      +
    +
  • +
  • + Y Axis +
    +
    Label
    +
    {{ config.yAxis.get('label') }}
    +
    +
    +
    Autoscale
    +
    + {{ config.yAxis.get('autoscale') ? "On" : "Off" }} + {{ config.yAxis.get('autoscale') ? (config.yAxis.get('autoscalePadding') * 100) + "%" : ""}} +
    +
    +
    +
    Min
    +
    {{ config.yAxis.get('range').min }}
    +
    +
    +
    Max
    +
    {{ config.yAxis.get('range').max }}
    +
    +
  • +
  • + Legend +
    +
    Position
    +
    {{ config.legend.get('position') }}
    +
    +
    +
    Expand by Default
    +
    {{ config.legend.get('expandByDefault') ? "Yes" : "No" }}
    +
    +
    +
    Show when collapsed:
    +
    {{ + config.legend.get('valueToShowWhenCollapsed').replace('nearest', '') + }}
    +
    +
    +
    Show when expanded:
    +
    + Timestamp + Value + Min + Max +
    +
    +
  • +
+
diff --git a/src/plugins/plot/res/templates/plot-options-edit.html b/src/plugins/plot/res/templates/plot-options-edit.html new file mode 100644 index 0000000000..4df65887c1 --- /dev/null +++ b/src/plugins/plot/res/templates/plot-options-edit.html @@ -0,0 +1,229 @@ + +
+ + Series Options + +
    +
      +
    • + + + + + + +
      +
        +
      • + + +
        + +
        +
        +
      • +
      • + + +
        + +
        +
        +
      • +
      • + + +
      • +
      • + + +
      • +
      • + + +
      • +
          +
        • + + +
          + + +
          +
          +
        • +
        • +
          +
          +
          +
          +
        • + +
        +
      +
      +
    • +
    +
+
+ + Y Axis + +
    +
  • + + +
  • +
+
    +
  • Scaling
  • +
  • + + + + +
  • +
  • + {{ validation['form.yAxis.range'] }} +
  • +
  • + + + + +
  • +
  • + + + + +
  • +
  • + + + + +
  • +
+ + + Legend + + +
    +
  • + + +
    + +
    +
    +
  • +
  • + + + + +
  • +
  • + + +
    + +
    +
    +
  • +
  • +
      +
    • +
    • + + + + +
    • +
    • + + + + +
    • +
    • + + + + +
    • +
    • + + + + +
    • +
    +
  • +
+
+
diff --git a/src/plugins/plot/res/templates/plot-options.html b/src/plugins/plot/res/templates/plot-options.html new file mode 100644 index 0000000000..c34594db4d --- /dev/null +++ b/src/plugins/plot/res/templates/plot-options.html @@ -0,0 +1,31 @@ + +
+ + +
+
+ + +
diff --git a/src/plugins/plot/res/templates/plot.html b/src/plugins/plot/res/templates/plot.html new file mode 100644 index 0000000000..3b585a19e5 --- /dev/null +++ b/src/plugins/plot/res/templates/plot.html @@ -0,0 +1,50 @@ + + + + +
+ + +
+
diff --git a/src/plugins/plot/res/templates/stacked-plot.html b/src/plugins/plot/res/templates/stacked-plot.html new file mode 100644 index 0000000000..237ed9590c --- /dev/null +++ b/src/plugins/plot/res/templates/stacked-plot.html @@ -0,0 +1,53 @@ + + + + +
+
+ +
+
+
diff --git a/platform/features/plot/src/policies/PlotViewPolicy.js b/src/plugins/plot/src/PlotViewPolicy.js similarity index 95% rename from platform/features/plot/src/policies/PlotViewPolicy.js rename to src/plugins/plot/src/PlotViewPolicy.js index 0d6e082386..ca23502cbe 100644 --- a/platform/features/plot/src/policies/PlotViewPolicy.js +++ b/src/plugins/plot/src/PlotViewPolicy.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, United States Government + * Open MCT, Copyright (c) 2014-2018, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -54,7 +54,7 @@ define( }; PlotViewPolicy.prototype.allow = function (view, domainObject) { - if (view.key === 'plot') { + if (view.key === 'plot-single') { return this.hasNumericTelemetry(domainObject); } diff --git a/src/plugins/plot/src/chart/MCTChartAlarmPointSet.js b/src/plugins/plot/src/chart/MCTChartAlarmPointSet.js new file mode 100644 index 0000000000..2282cb4130 --- /dev/null +++ b/src/plugins/plot/src/chart/MCTChartAlarmPointSet.js @@ -0,0 +1,77 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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. + *****************************************************************************/ +/*global define*/ + +define([ + '../lib/extend', + '../lib/eventHelpers' +], function ( + extend, + eventHelpers +) { + + function MCTChartAlarmPointSet(series, chart, offset) { + this.series = series; + this.chart = chart; + this.offset = offset; + this.points = []; + + this.listenTo(series, 'add', this.append, this); + this.listenTo(series, 'remove', this.remove, this); + this.listenTo(series, 'reset', this.reset, this); + this.listenTo(series, 'destroy', this.destroy, this); + series.data.forEach(function (point, index) { + this.append(point, index, series); + }, this); + } + + MCTChartAlarmPointSet.prototype.append = function (datum) { + if (datum.mctLimitState) { + this.points.push({ + x: this.offset.xVal(datum, this.series), + y: this.offset.yVal(datum, this.series), + datum: datum + }); + } + }; + + MCTChartAlarmPointSet.prototype.remove = function (datum) { + this.points = this.points.filter(function (p) { + return p.datum !== datum; + }); + }; + + MCTChartAlarmPointSet.prototype.reset = function () { + this.points = []; + }; + + MCTChartAlarmPointSet.prototype.destroy = function () { + this.stopListening(); + }; + + + + eventHelpers.extend(MCTChartAlarmPointSet.prototype); + + return MCTChartAlarmPointSet; + +}); diff --git a/src/plugins/plot/src/chart/MCTChartController.js b/src/plugins/plot/src/chart/MCTChartController.js new file mode 100644 index 0000000000..417e60c8b5 --- /dev/null +++ b/src/plugins/plot/src/chart/MCTChartController.js @@ -0,0 +1,400 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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. + *****************************************************************************/ +/*global define,requestAnimationFrame,Float32Array*/ + +/** + * Module defining MCTChart. Created by vwoeltje on 11/12/14. + */ +define([ + './MCTChartLineLinear', + './MCTChartLineStepAfter', + './MCTChartPointSet', + './MCTChartAlarmPointSet', + '../draw/DrawLoader', + '../lib/eventHelpers', + 'lodash' +], +function ( + MCTChartLineLinear, + MCTChartLineStepAfter, + MCTChartPointSet, + MCTChartAlarmPointSet, + DrawLoader, + eventHelpers, + _ +) { + + var MARKER_SIZE = 6.0, + HIGHLIGHT_SIZE = MARKER_SIZE * 2.0; + + /** + * Offsetter adjusts x and y values by a fixed amount, + * generally increasing the precision of the 32 bit float representation + * required for plotting. + * + * @constructor + */ + function MCTChartController($scope) { + this.$scope = $scope; + this.isDestroyed = false; + this.lines = []; + this.pointSets = []; + this.alarmSets = []; + this.offset = {}; + this.config = $scope.config; + this.listenTo(this.$scope, '$destoy', this.destroy, this); + this.draw = this.draw.bind(this); + this.scheduleDraw = this.scheduleDraw.bind(this); + this.seriesElements = new WeakMap(); + + this.listenTo(this.config.series, 'add', this.onSeriesAdd, this); + this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this); + this.listenTo(this.config.yAxis, 'change:key', this.clearOffset, this); + this.listenTo(this.config.xAxis, 'change:key', this.clearOffset, this); + this.listenTo(this.config.yAxis, 'change', this.scheduleDraw); + this.listenTo(this.config.xAxis, 'change', this.scheduleDraw); + this.$scope.$watch('highlights', this.scheduleDraw); + this.$scope.$watch('rectangles', this.scheduleDraw); + this.config.series.forEach(this.onSeriesAdd, this); + } + + eventHelpers.extend(MCTChartController.prototype); + + MCTChartController.$inject = ['$scope']; + + MCTChartController.prototype.onSeriesAdd = function (series) { + this.listenTo(series, 'change:interpolate', this.changeInterpolate, this); + this.listenTo(series, 'change:markers', this.changeMarkers, this); + this.listenTo(series, 'change:alarmMarkers', this.changeAlarmMarkers, this); + this.listenTo(series, 'change', this.scheduleDraw); + this.listenTo(series, 'add', this.scheduleDraw); + this.makeChartElement(series); + }; + + MCTChartController.prototype.changeInterpolate = function (mode, o, series) { + if (mode === o) { + return; + } + var elements = this.seriesElements.get(series); + elements.lines.forEach(function (line) { + this.lines.splice(this.lines.indexOf(line), 1); + line.destroy(); + }, this); + elements.lines = []; + + var newLine = this.lineForSeries(series); + if (newLine) { + elements.lines.push(newLine); + this.lines.push(newLine); + } + }; + + MCTChartController.prototype.changeAlarmMarkers = function (mode, o, series) { + if (mode === o) { + return; + } + var elements = this.seriesElements.get(series); + if (elements.alarmSet) { + elements.alarmSet.destroy(); + this.alarmSets.splice(this.alarmSets.indexOf(elements.alarmSet), 1); + } + elements.alarmSet = this.alarmPointSetForSeries(series); + if (elements.alarmSet) { + this.alarmSets.push(elements.alarmSet); + } + }; + + MCTChartController.prototype.changeMarkers = function (mode, o, series) { + if (mode === o) { + return; + } + var elements = this.seriesElements.get(series); + elements.pointSets.forEach(function (pointSet) { + this.pointSets.splice(this.pointSets.indexOf(pointSet), 1); + pointSet.destroy(); + }, this); + elements.pointSets = []; + + var pointSet = this.pointSetForSeries(series); + if (pointSet) { + elements.pointSets.push(pointSet); + this.pointSets.push(pointSet); + } + }; + + MCTChartController.prototype.onSeriesRemove = function (series) { + this.stopListening(series); + this.removeChartElement(series); + this.scheduleDraw(); + }; + + MCTChartController.prototype.destroy = function () { + this.isDestroyed = true; + this.stopListening(); + _.invoke(this.lines, 'destroy'); + DrawLoader.releaseDrawAPI(this.drawAPI); + }; + + MCTChartController.prototype.clearOffset = function () { + delete this.offset.x; + delete this.offset.y; + delete this.offset.xVal; + delete this.offset.yVal; + delete this.offset.xKey; + delete this.offset.yKey; + this.lines.forEach(function (line) { + line.reset(); + }); + this.pointSets.forEach(function (pointSet) { + pointSet.reset(); + }); + }; + + MCTChartController.prototype.setOffset = function (offsetPoint, index, series) { + if (this.offset.x && this.offset.y) { + return; + } + + var offsets = { + x: series.getXVal(offsetPoint), + y: series.getYVal(offsetPoint) + }; + + this.offset.x = function (x) { + return x - offsets.x; + }.bind(this); + this.offset.y = function (y) { + return y - offsets.y; + }.bind(this); + this.offset.xVal = function (point, pSeries) { + return this.offset.x(pSeries.getXVal(point)); + }.bind(this); + this.offset.yVal = function (point, pSeries) { + return this.offset.y(pSeries.getYVal(point)); + }.bind(this); + }; + + MCTChartController.prototype.initializeCanvas = function (canvas, overlay) { + this.canvas = canvas; + this.overlay = overlay; + this.drawAPI = DrawLoader.getDrawAPI(canvas, overlay); + return !!this.drawAPI; + }; + + MCTChartController.prototype.removeChartElement = function (series) { + var elements = this.seriesElements.get(series); + + elements.lines.forEach(function (line) { + this.lines.splice(this.lines.indexOf(line), 1); + line.destroy(); + }, this); + elements.pointSets.forEach(function (pointSet) { + this.pointSets.splice(this.pointSets.indexOf(pointSet), 1); + pointSet.destroy(); + }, this); + this.seriesElements.delete(series); + }; + + MCTChartController.prototype.lineForSeries = function (series) { + if (series.get('interpolate') === 'linear') { + return new MCTChartLineLinear( + series, + this, + this.offset + ); + } + if (series.get('interpolate') === 'stepAfter') { + return new MCTChartLineStepAfter( + series, + this, + this.offset + ); + } + }; + + MCTChartController.prototype.pointSetForSeries = function (series) { + if (series.get('markers')) { + return new MCTChartPointSet( + series, + this, + this.offset + ); + } + }; + + MCTChartController.prototype.alarmPointSetForSeries = function (series) { + if (series.get('alarmMarkers')) { + return new MCTChartAlarmPointSet( + series, + this, + this.offset + ); + } + }; + + MCTChartController.prototype.makeChartElement = function (series) { + var elements = { + lines: [], + pointSets: [] + }; + + var line = this.lineForSeries(series); + if (line) { + elements.lines.push(line); + this.lines.push(line); + } + + var pointSet = this.pointSetForSeries(series); + if (pointSet) { + elements.pointSets.push(pointSet); + this.pointSets.push(pointSet); + } + + elements.alarmSet = this.alarmPointSetForSeries(series); + if (elements.alarmSet) { + this.alarmSets.push(elements.alarmSet); + } + + this.seriesElements.set(series, elements); + }; + + MCTChartController.prototype.canDraw = function () { + if (!this.offset.x || !this.offset.y) { + return false; + } + return true; + }; + + MCTChartController.prototype.scheduleDraw = function () { + if (!this.drawScheduled) { + requestAnimationFrame(this.draw); + this.drawScheduled = true; + } + }; + + MCTChartController.prototype.draw = function () { + this.drawScheduled = false; + if (this.isDestroyed) { + return; + } + this.drawAPI.clear(); + if (this.canDraw()) { + this.updateViewport(); + this.drawSeries(); + this.drawRectangles(); + this.drawHighlights(); + } + }; + + MCTChartController.prototype.updateViewport = function () { + var xRange = this.config.xAxis.get('displayRange'), + yRange = this.config.yAxis.get('displayRange'); + + if (!xRange || !yRange) { + return; + } + + var dimensions = [ + xRange.max - xRange.min, + yRange.max - yRange.min + ], + origin = [ + this.offset.x(xRange.min), + this.offset.y(yRange.min) + ]; + + this.drawAPI.setDimensions( + dimensions, + origin + ); + }; + + MCTChartController.prototype.drawSeries = function () { + this.lines.forEach(this.drawLine, this); + this.pointSets.forEach(this.drawPoints, this); + this.alarmSets.forEach(this.drawAlarmPoints, this); + }; + + MCTChartController.prototype.drawAlarmPoints = function (alarmSet) { + this.drawAPI.drawLimitPoints( + alarmSet.points, + alarmSet.series.get('color').asRGBAArray(), + alarmSet.series.get('markerSize') + ); + }; + + MCTChartController.prototype.drawPoints = function (chartElement) { + this.drawAPI.drawPoints( + chartElement.getBuffer(), + chartElement.color().asRGBAArray(), + chartElement.count, + chartElement.series.get('markerSize') + ); + }; + + MCTChartController.prototype.drawLine = function (chartElement) { + this.drawAPI.drawLine( + chartElement.getBuffer(), + chartElement.color().asRGBAArray(), + chartElement.count + ); + }; + + MCTChartController.prototype.drawHighlights = function () { + if (this.$scope.highlights && this.$scope.highlights.length) { + this.$scope.highlights.forEach(this.drawHighlight, this); + } + }; + + MCTChartController.prototype.drawHighlight = function (highlight) { + var points = new Float32Array([ + this.offset.xVal(highlight.point, highlight.series), + this.offset.yVal(highlight.point, highlight.series) + ]), + color = highlight.series.get('color').asRGBAArray(), + pointCount = 1; + + this.drawAPI.drawPoints(points, color, pointCount, HIGHLIGHT_SIZE); + }; + + MCTChartController.prototype.drawRectangles = function () { + if (this.$scope.rectangles) { + this.$scope.rectangles.forEach(this.drawRectangle, this); + } + }; + + MCTChartController.prototype.drawRectangle = function (rect) { + this.drawAPI.drawSquare( + [ + this.offset.x(rect.start.x), + this.offset.y(rect.start.y) + ], + [ + this.offset.x(rect.end.x), + this.offset.y(rect.end.y) + ], + rect.color + ); + }; + + return MCTChartController; +}); diff --git a/src/plugins/plot/src/chart/MCTChartDirective.js b/src/plugins/plot/src/chart/MCTChartDirective.js new file mode 100644 index 0000000000..5662465878 --- /dev/null +++ b/src/plugins/plot/src/chart/MCTChartDirective.js @@ -0,0 +1,67 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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. + *****************************************************************************/ +/*global define*/ + +/** + * Module defining MCTChart. Created by vwoeltje on 11/12/14. + */ +define([ + './MCTChartController' +], function ( + MCTChartController +) { + + var TEMPLATE = ""; + TEMPLATE += TEMPLATE; + + /** + * MCTChart draws charts utilizing a drawAPI. + * + * @constructor + */ + function MCTChart() { + return { + restrict: "E", + template: TEMPLATE, + link: function ($scope, $element, attrs, ctrl) { + var mainCanvas = $element.find("canvas")[1]; + var overlayCanvas = $element.find("canvas")[0]; + + if (ctrl.initializeCanvas(mainCanvas, overlayCanvas)) { + ctrl.draw(); + } + }, + controller: MCTChartController, + scope: { + config: "=", + draw: "=", + rectangles: "=", + series: "=", + xAxis: "=theXAxis", + yAxis: "=theYAxis", + highlights: "=?" + } + }; + } + + return MCTChart; +}); diff --git a/src/plugins/plot/src/chart/MCTChartLineLinear.js b/src/plugins/plot/src/chart/MCTChartLineLinear.js new file mode 100644 index 0000000000..1db828adfd --- /dev/null +++ b/src/plugins/plot/src/chart/MCTChartLineLinear.js @@ -0,0 +1,40 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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. + *****************************************************************************/ +/*global define*/ + +define([ + './MCTChartSeriesElement' +], function ( + MCTChartSeriesElement +) { + + var MCTChartLineLinear = MCTChartSeriesElement.extend({ + addPoint: function (point, start, count) { + this.buffer[start] = point.x; + this.buffer[start + 1] = point.y; + } + }); + + return MCTChartLineLinear; + +}); + diff --git a/src/plugins/plot/src/chart/MCTChartLineStepAfter.js b/src/plugins/plot/src/chart/MCTChartLineStepAfter.js new file mode 100644 index 0000000000..447c68dffe --- /dev/null +++ b/src/plugins/plot/src/chart/MCTChartLineStepAfter.js @@ -0,0 +1,78 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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. + *****************************************************************************/ +/*global define*/ + +define([ + './MCTChartSeriesElement' +], function ( + MCTChartSeriesElement +) { + + var MCTChartLineStepAfter = MCTChartSeriesElement.extend({ + removePoint: function (point, index, count) { + if (index > 0 && index / 2 < this.count) { + this.buffer[index + 1] = this.buffer[index - 1]; + } + }, + vertexCountForPointAtIndex: function (index) { + if (index === 0 && this.count === 0) { + return 2; + } + return 4; + }, + startIndexForPointAtIndex: function (index) { + if (index === 0) { + return 0; + } + return 2 + ((index - 1) * 4); + }, + addPoint: function (point, start, count) { + if (start === 0 && this.count === 0) { + // First point is easy. + this.buffer[start] = point.x; + this.buffer[start + 1] = point.y; // one point + } else if (start === 0 && this.count > 0) { + // Unshifting requires adding an extra point. + this.buffer[start] = point.x; + this.buffer[start + 1] = point.y; + this.buffer[start + 2] = this.buffer[start + 4]; + this.buffer[start + 3] = point.y; + } else { + // Appending anywhere in line, insert standard two points. + this.buffer[start] = point.x; + this.buffer[start + 1] = this.buffer[start - 1]; + this.buffer[start + 2] = point.x; + this.buffer[start + 3] = point.y; + + if (start < this.count * 2) { + // Insert into the middle, need to update the following + // point. + this.buffer[start + 5] = point.y; + } + } + } + }); + + return MCTChartLineStepAfter; + +}); + diff --git a/src/plugins/plot/src/chart/MCTChartPointSet.js b/src/plugins/plot/src/chart/MCTChartPointSet.js new file mode 100644 index 0000000000..92913fa92f --- /dev/null +++ b/src/plugins/plot/src/chart/MCTChartPointSet.js @@ -0,0 +1,40 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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. + *****************************************************************************/ +/*global define*/ + +define([ + './MCTChartSeriesElement' +], function ( + MCTChartSeriesElement +) { + + var MCTChartPointSet = MCTChartSeriesElement.extend({ + addPoint: function (point, start, count) { + this.buffer[start] = point.x; + this.buffer[start + 1] = point.y; + } + }); + + return MCTChartPointSet; + +}); + diff --git a/src/plugins/plot/src/chart/MCTChartSeriesElement.js b/src/plugins/plot/src/chart/MCTChartSeriesElement.js new file mode 100644 index 0000000000..8940d98497 --- /dev/null +++ b/src/plugins/plot/src/chart/MCTChartSeriesElement.js @@ -0,0 +1,162 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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. + *****************************************************************************/ +/*global define,Float32Array*/ + +define([ + '../lib/extend', + '../lib/eventHelpers' +], function ( + extend, + eventHelpers +) { + + function MCTChartSeriesElement(series, chart, offset) { + this.series = series; + this.chart = chart; + this.offset = offset; + this.buffer = new Float32Array(20000); + this.count = 0; + this.listenTo(series, 'add', this.append, this); + this.listenTo(series, 'remove', this.remove, this); + this.listenTo(series, 'reset', this.reset, this); + this.listenTo(series, 'destroy', this.destroy, this); + series.data.forEach(function (point, index) { + this.append(point, index, series); + }, this); + } + + MCTChartSeriesElement.extend = extend; + + eventHelpers.extend(MCTChartSeriesElement.prototype); + + MCTChartSeriesElement.prototype.getBuffer = function () { + if (this.isTempBuffer) { + this.buffer = new Float32Array(this.buffer); + this.isTempBuffer = false; + } + return this.buffer; + }; + + MCTChartSeriesElement.prototype.color = function () { + return this.series.get('color'); + }; + + MCTChartSeriesElement.prototype.vertexCountForPointAtIndex = function (index) { + return 2; + }; + + MCTChartSeriesElement.prototype.startIndexForPointAtIndex = function (index) { + return 2 * index; + }; + + MCTChartSeriesElement.prototype.removeSegments = function (index, count) { + var target = index, + start = index + count, + end = this.count * 2; + this.buffer.copyWithin(target, start, end); + for (var zero = end - count; zero < end; zero++) { + this.buffer[zero] = 0; + } + }; + + MCTChartSeriesElement.prototype.removePoint = function (point, index, count) { + // by default, do nothing. + }; + + MCTChartSeriesElement.prototype.remove = function (point, index, series) { + var vertexCount = this.vertexCountForPointAtIndex(index); + var removalPoint = this.startIndexForPointAtIndex(index); + + this.removeSegments(removalPoint, vertexCount); + + this.removePoint( + this.makePoint(point, series), + removalPoint, + vertexCount + ); + this.count -= (vertexCount / 2); + }; + + MCTChartSeriesElement.prototype.makePoint = function (point, series) { + if (!this.offset.xVal) { + this.chart.setOffset(point, undefined, series); + } + return { + x: this.offset.xVal(point, series), + y: this.offset.yVal(point, series) + }; + }; + + MCTChartSeriesElement.prototype.append = function (point, index, series) { + var pointsRequired = this.vertexCountForPointAtIndex(index); + var insertionPoint = this.startIndexForPointAtIndex(index); + this.growIfNeeded(pointsRequired); + this.makeInsertionPoint(insertionPoint, pointsRequired); + this.addPoint( + this.makePoint(point, series), + insertionPoint, + pointsRequired + ); + this.count += (pointsRequired / 2); + }; + + MCTChartSeriesElement.prototype.makeInsertionPoint = function (insertionPoint, pointsRequired) { + if (this.count * 2 > insertionPoint) { + if (!this.isTempBuffer) { + this.buffer = Array.prototype.slice.apply(this.buffer); + this.isTempBuffer = true; + } + var target = insertionPoint + pointsRequired, + start = insertionPoint; + for (; start < target; start++) { + this.buffer.splice(start, 0, 0); + } + } + }; + + MCTChartSeriesElement.prototype.reset = function () { + this.buffer = new Float32Array(20000); + this.count = 0; + if (this.offset.x) { + this.series.data.forEach(function (point, index) { + this.append(point, index, this.series); + }, this); + } + }; + + MCTChartSeriesElement.prototype.growIfNeeded = function (pointsRequired) { + var remainingPoints = this.buffer.length - this.count * 2; + var temp; + + if (remainingPoints <= pointsRequired) { + temp = new Float32Array(this.buffer.length + 20000); + temp.set(this.buffer); + this.buffer = temp; + } + }; + + MCTChartSeriesElement.prototype.destroy = function () { + this.stopListening(); + }; + + return MCTChartSeriesElement; +}); diff --git a/src/plugins/plot/src/configuration/Collection.js b/src/plugins/plot/src/configuration/Collection.js new file mode 100644 index 0000000000..d12cb45cb5 --- /dev/null +++ b/src/plugins/plot/src/configuration/Collection.js @@ -0,0 +1,133 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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. + *****************************************************************************/ +/*global define*/ + +define([ + 'lodash', + 'EventEmitter', + './Model', + '../lib/extend', + '../lib/eventHelpers' +], function ( + _, + EventEmitter, + Model, + extend, + eventHelpers +) { + + function Collection(options) { + if (options.models) { + this.models = options.models.map(this.modelFn, this); + } else { + this.models = []; + } + this.initialize(options); + } + + _.extend(Collection.prototype, EventEmitter.prototype); + eventHelpers.extend(Collection.prototype); + + Collection.extend = extend; + + Collection.prototype.initialize = function (options) { + + }; + + Collection.prototype.modelClass = Model; + + Collection.prototype.modelFn = function (model) { + if (model instanceof this.modelClass) { + model.collection = this; + return model; + + } + return new this.modelClass({ + collection: this, + model: model + }); + }; + + Collection.prototype.first = function () { + return this.at(0); + }; + + Collection.prototype.forEach = function (iteree, context) { + this.models.forEach(iteree, context); + }; + + Collection.prototype.map = function (iteree, context) { + return this.models.map(iteree, context); + }; + + Collection.prototype.filter = function (iteree, context) { + return this.models.filter(iteree, context); + }; + + Collection.prototype.size = function () { + return this.models.length; + }; + + Collection.prototype.at = function (index) { + return this.models[index]; + }; + + Collection.prototype.add = function (model) { + model = this.modelFn(model); + var index = this.models.length; + this.models.push(model); + this.emit('add', model, index); + }; + + Collection.prototype.insert = function (model, index) { + model = this.modelFn(model); + this.models.splice(index, 0, model); + this.emit('add', model, index + 1); + }; + + Collection.prototype.indexOf = function (model) { + return _.findIndex( + this.models, + function (m) { + return m === model; + } + ); + }; + + Collection.prototype.remove = function (model) { + var index = this.indexOf(model); + if (index === -1) { + throw new Error('model not found in collection.'); + } + this.models.splice(index, 1); + this.emit('remove', model, index); + }; + + Collection.prototype.destroy = function (model) { + this.forEach(function (m) { + m.destroy(); + }); + this.stopListening(); + }; + + return Collection; +}); diff --git a/src/plugins/plot/src/configuration/LegendModel.js b/src/plugins/plot/src/configuration/LegendModel.js new file mode 100644 index 0000000000..fd13073292 --- /dev/null +++ b/src/plugins/plot/src/configuration/LegendModel.js @@ -0,0 +1,61 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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([ + './Model' +], function ( + Model +) { + + /** + * TODO: doc strings. + */ + var LegendModel = Model.extend({ + listenToSeriesCollection: function (seriesCollection) { + this.seriesCollection = seriesCollection; + this.listenTo(this.seriesCollection, 'add', this.setHeight, this); + this.listenTo(this.seriesCollection, 'remove', this.setHeight, this); + this.listenTo(this, 'change:expanded', this.setHeight, this); + this.set('expanded', this.get('expandByDefault')); + }, + setHeight: function () { + var expanded = this.get('expanded'); + if (this.get('position') !== 'top') { + this.set('height', '0px'); + } else { + this.set('height', expanded ? (20 * (this.seriesCollection.size() + 1) + 40) + 'px' : '21px'); + } + }, + defaults: function (options) { + return { + position: 'top', + expandByDefault: false, + valueToShowWhenCollapsed: 'nearestValue', + showTimestampWhenExpanded: true, + showValueWhenExpanded: true, + showMaximumWhenExpanded: true, + showMinimumWhenExpanded: true + }; + } + }); + + return LegendModel; +}); diff --git a/src/plugins/plot/src/configuration/Model.js b/src/plugins/plot/src/configuration/Model.js new file mode 100644 index 0000000000..683b040a09 --- /dev/null +++ b/src/plugins/plot/src/configuration/Model.js @@ -0,0 +1,102 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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. + *****************************************************************************/ +/*global define*/ + +define([ + 'lodash', + 'EventEmitter', + '../lib/extend', + '../lib/eventHelpers' +], function ( + _, + EventEmitter, + extend, + eventHelpers +) { + + function Model(options) { + if (!options) { + options = {}; + } + this.id = options.id; + this.model = options.model; + this.collection = options.collection; + var defaults = this.defaults(options); + if (!this.model) { + this.model = options.model = defaults; + } else { + _.defaultsDeep(this.model, defaults); + } + this.initialize(options); + } + + _.extend(Model.prototype, EventEmitter.prototype); + eventHelpers.extend(Model.prototype); + + Model.extend = extend; + + Model.prototype.idAttr = 'id'; + + Model.prototype.defaults = function (options) { + return {}; + }; + + Model.prototype.initialize = function (model) { + + }; + + /** + * Destroy the model, removing all listeners and subscriptions. + */ + Model.prototype.destroy = function () { + this.emit('destroy'); + this.removeAllListeners(); + }; + + Model.prototype.id = function () { + return this.get(this.idAttr); + }; + + Model.prototype.get = function (attribute) { + return this.model[attribute]; + }; + + Model.prototype.has = function (attribute) { + return _.has(this.model, attribute); + }; + + Model.prototype.set = function (attribute, value) { + var oldValue = this.model[attribute]; + this.model[attribute] = value; + this.emit('change', attribute, value, oldValue, this); + this.emit('change:' + attribute, value, oldValue, this); + }; + + Model.prototype.unset = function (attribute) { + var oldValue = this.model[attribute]; + delete this.model[attribute]; + this.emit('change', attribute, undefined, oldValue, this); + this.emit('change:' + attribute, undefined, oldValue, this); + }; + + return Model; +}); diff --git a/src/plugins/plot/src/configuration/PlotConfigurationModel.js b/src/plugins/plot/src/configuration/PlotConfigurationModel.js new file mode 100644 index 0000000000..e00614ca5b --- /dev/null +++ b/src/plugins/plot/src/configuration/PlotConfigurationModel.js @@ -0,0 +1,134 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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. + *****************************************************************************/ +/*global define*/ + +define([ + './Collection', + './Model', + './SeriesCollection', + './XAxisModel', + './YAxisModel', + './LegendModel', + 'lodash' +], function ( + Collection, + Model, + SeriesCollection, + XAxisModel, + YAxisModel, + LegendModel, + _ +) { + + /** + * PlotConfiguration model stores the configuration of a plot and some + * limited state. The indiidual parts of the plot configuration model + * handle setting defaults and updating in response to various changes. + * + */ + var PlotConfigurationModel = Model.extend({ + + /** + * Initializes all sub models and then passes references to submodels + * to those that need it. + */ + initialize: function (options) { + this.openmct = options.openmct; + + this.xAxis = new XAxisModel({ + model: options.model.xAxis, + plot: this, + openmct: options.openmct + }); + this.yAxis = new YAxisModel({ + model: options.model.yAxis, + plot: this, + openmct: options.openmct + }); + this.legend = new LegendModel({ + model: options.model.legend, + plot: this, + openmct: options.openmct + }); + this.series = new SeriesCollection({ + models: options.model.series, + plot: this, + openmct: options.openmct + }); + + this.removeMutationListener = this.openmct.objects.observe( + this.get('domainObject'), + '*', + this.updateDomainObject.bind(this) + ); + this.yAxis.listenToSeriesCollection(this.series); + this.legend.listenToSeriesCollection(this.series); + + this.listenTo(this, 'destroy', this.onDestroy, this); + }, + /** + * Retrieve the persisted series config for a given identifier. + */ + getPersistedSeriesConfig: function (identifier) { + var domainObject = this.get('domainObject'); + if (!domainObject.configuration || !domainObject.configuration.series) { + return; + } + return domainObject.configuration.series.filter(function (seriesConfig) { + return seriesConfig.identifier.key === identifier.key && + seriesConfig.identifier.namespace === identifier.namespace; + })[0]; + }, + /** + * Update the domain object with the given value. + */ + updateDomainObject: function (domainObject) { + this.set('domainObject', domainObject); + }, + /** + * Clean up all objects and remove all listeners. + */ + onDestroy: function () { + this.xAxis.destroy(); + this.yAxis.destroy(); + this.series.destroy(); + this.legend.destroy(); + this.removeMutationListener(); + }, + /** + * Return defaults, which are extracted from the passed in domain + * object. + */ + defaults: function (options) { + return { + series: [], + domainObject: options.domainObject, + xAxis: { + }, + yAxis: _.cloneDeep(_.get(options.domainObject, 'configuration.yAxis', {})), + legend: _.cloneDeep(_.get(options.domainObject, 'configuration.legend', {})) + }; + } + }); + + return PlotConfigurationModel; +}); diff --git a/src/plugins/plot/src/configuration/PlotSeries.js b/src/plugins/plot/src/configuration/PlotSeries.js new file mode 100644 index 0000000000..145bbc564e --- /dev/null +++ b/src/plugins/plot/src/configuration/PlotSeries.js @@ -0,0 +1,357 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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. + *****************************************************************************/ +/*global define*/ + +define([ + 'lodash', + '../configuration/Model', + '../lib/extend', + 'EventEmitter' +], function ( + _, + Model, + extend, + EventEmitter +) { + + /** + * Plot series handle interpreting telemetry metadata for a single telemetry + * object, querying for that data, and formatting it for display purposes. + * + * Plot series emit both collection events and model events: + * `change` when any property changes + * `change:` when a specific property changes. + * `destroy`: when series is destroyed + * `add`: whenever a data point is added to a series + * `remove`: whenever a data point is removed from a series. + * `reset`: whenever the collection is emptied. + * + * Plot series have the following Model properties: + * + * `name`: name of series. + * `identifier`: the Open MCT identifier for the telemetry source for this + * series. + * `xKey`: the telemetry value key for x values fetched from this series. + * `yKey`: the telemetry value key for y values fetched from this series. + * `interpolate`: interpolate method, either `undefined` (no interpolation), + * `linear` (points are connected via straight lines), or + * `stepAfter` (points are connected by steps). + * `markers`: boolean, whether or not this series should render with markers. + * `markerSize`: number, size in pixels of markers for this series. + * `alarmMarkers`: whether or not to display alarm markers for this series. + * `stats`: An object that tracks the min and max y values observed in this + * series. This property is checked and updated whenever data is + * added. + * + * Plot series have the following instance properties: + * + * `metadata`: the Open MCT Telemetry Metadata Manager for the associated + * telemetry point. + * `formats`: the Open MCT format map for this telemetry point. + */ + var PlotSeries = Model.extend({ + constructor: function (options) { + this.metadata = options + .openmct + .telemetry + .getMetadata(options.domainObject); + this.formats = options + .openmct + .telemetry + .getFormatMap(this.metadata); + + this.data = []; + + this.listenTo(this, 'change:xKey', this.onXKeyChange, this); + this.listenTo(this, 'change:yKey', this.onYKeyChange, this); + + Model.apply(this, arguments); + this.onXKeyChange(this.get('xKey')); + this.onYKeyChange(this.get('yKey')); + }, + + /** + * Set defaults for telemetry series. + */ + defaults: function (options) { + var range = this.metadata.valuesForHints(['range'])[0]; + return { + name: options.domainObject.name, + xKey: options.collection.plot.xAxis.get('key'), + yKey: range.key, + markers: true, + markerSize: 2.0, + alarmMarkers: true + }; + }, + + /** + * Remove real-time subscription when destroyed. + */ + onDestroy: function (model) { + if (this.unsubscribe) { + this.unsubscribe(); + } + }, + + initialize: function (options) { + this.openmct = options.openmct; + this.domainObject = options.domainObject; + this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject); + this.on('destroy', this.onDestroy, this); + }, + /** + * Fetch historical data and establish a realtime subscription. Returns + * a promise that is resolved when all connections have been successfully + * established. + * + * @returns {Promise} + */ + fetch: function (options) { + options = _.extend({}, {size: 1000, strategy: 'minmax'}, options || {}); + if (!this.unsubscribe) { + this.unsubscribe = this.openmct + .telemetry + .subscribe( + this.domainObject, + this.add.bind(this) + ); + } + + return this.openmct + .telemetry + .request(this.domainObject, options) + .then(function (points) { + var newPoints = _(this.data) + .concat(points) + .sortBy(this.getXVal) + .uniq(true, this.getXVal) + .value(); + this.reset(newPoints); + }.bind(this)); + }, + /** + * Update x formatter on x change. + */ + onXKeyChange: function (xKey) { + var format = this.formats[xKey]; + this.getXVal = format.parse.bind(format); + }, + /** + * Update y formatter on change, default to stepAfter interpolation if + * y range is an enumeration. + */ + onYKeyChange: function (newKey, oldKey) { + if (newKey === oldKey) { + return; + } + var valueMetadata = this.metadata.value(newKey); + var persistedConfig = this.get('persistedConfiguration'); + if (!persistedConfig || !persistedConfig.interpolate) { + if (valueMetadata.format === 'enum') { + this.set('interpolate', 'stepAfter'); + } else { + this.set('interpolate', 'linear'); + } + } + this.evaluate = function (datum) { + return this.limitEvaluator.evaluate(datum, valueMetadata); + }.bind(this); + var format = this.formats[newKey]; + this.getYVal = format.parse.bind(format); + }, + + formatX: function (point) { + return this.formats[this.get('xKey')].format(point); + }, + + formatY: function (point) { + return this.formats[this.get('yKey')].format(point); + }, + + /** + * Clear stats and recalculate from existing data. + */ + resetStats: function () { + this.unset('stats'); + this.data.forEach(this.updateStats, this); + }, + + /** + * Reset plot series. If new data is provided, will add that + * data to series after reset. + */ + reset: function (newData) { + this.data = []; + this.resetStats(); + this.emit('reset'); + if (newData) { + newData.forEach(function (point) { + this.add(point, true); + }, this); + } + }, + /** + * Return the point closest to a given x value. + */ + nearestPoint: function (xValue) { + var insertIndex = this.sortedIndex(xValue), + lowPoint = this.data[insertIndex - 1], + highPoint = this.data[insertIndex], + indexVal = this.getXVal(xValue), + lowDistance = lowPoint ? + indexVal - this.getXVal(lowPoint) : + Number.POSITIVE_INFINITY, + highDistance = highPoint ? + this.getXVal(highPoint) - indexVal : + Number.POSITIVE_INFINITY, + nearestPoint = highDistance < lowDistance ? highPoint : lowPoint; + + return nearestPoint; + }, + /** + * Override this to implement plot series loading functionality. Must return + * a promise that is resolved when loading is completed. + * + * @private + * @returns {Promise} + */ + load: function (options) { + return this.fetch(options) + .then(function (res) { + this.emit('load'); + return res; + }.bind(this)); + }, + + /** + * Find the insert index for a given point to maintain sort order. + * @private + */ + sortedIndex: function (point) { + return _.sortedIndex(this.data, point, this.getXVal); + }, + /** + * Update min/max stats for the series. + * @private + */ + updateStats: function (point) { + var value = this.getYVal(point); + var stats = this.get('stats'); + var changed = false; + if (!stats) { + stats = { + minValue: value, + minPoint: point, + maxValue: value, + maxPoint: point + }; + changed = true; + } else { + if (stats.maxValue < value) { + stats.maxValue = value; + stats.maxPoint = point; + changed = true; + } + if (stats.minValue > value) { + stats.minValue = value; + stats.minPoint = point; + changed = true; + } + } + if (changed) { + this.set('stats', { + minValue: stats.minValue, + minPoint: stats.minPoint, + maxValue: stats.maxValue, + maxPoint: stats.maxPoint + }); + } + }, + /** + * Add a point to the data array while maintaining the sort order of + * the array and preventing insertion of points with a duplicate x + * value. Can provide an optional argument to append a point without + * maintaining sort order and dupe checks, which improves performance + * when adding an array of points that are already properly sorted. + * + * @private + * @param {Object} point a telemetry datum. + * @param {Boolean} [appendOnly] default false, if true will append + * a point to the end without dupe checking. + */ + add: function (point, appendOnly) { + var insertIndex = this.data.length; + if (!appendOnly) { + insertIndex = this.sortedIndex(point); + if (this.getXVal(this.data[insertIndex]) === this.getXVal(point)) { + return; + } + if (this.getXVal(this.data[insertIndex - 1]) === this.getXVal(point)) { + return; + } + } + this.updateStats(point); + point.mctLimitState = this.evaluate(point); + this.data.splice(insertIndex, 0, point); + this.emit('add', point, insertIndex, this); + }, + /** + * Remove a point from the data array and notify listeners. + * @private + */ + remove: function (point) { + var index = this.data.indexOf(point); + this.data.splice(index, 1); + this.emit('remove', point, index, this); + }, + /** + * Purges records outside a given x range. Changes removal method based + * on number of records to remove: for large purge, reset data and + * rebuild array. for small purge, removes points and emits updates. + * + * @public + * @param {Object} range + * @param {number} range.min minimum x value to keep + * @param {number} range.max maximum x value to keep. + */ + purgeRecordsOutsideRange: function (range) { + var startIndex = this.sortedIndex(range.min); + var endIndex = this.sortedIndex(range.max) + 1; + var pointsToRemove = startIndex + (this.data.length - endIndex + 1); + if (pointsToRemove > 0) { + if (pointsToRemove < 1000) { + this.data.slice(0, startIndex).forEach(this.remove, this); + this.data.slice(endIndex, this.data.length).forEach(this.remove, this); + this.resetStats(); + } else { + var newData = this.data.slice(startIndex, endIndex); + this.reset(newData); + } + } + + } + }); + + return PlotSeries; + +}); diff --git a/src/plugins/plot/src/configuration/SeriesCollection.js b/src/plugins/plot/src/configuration/SeriesCollection.js new file mode 100644 index 0000000000..845663f05f --- /dev/null +++ b/src/plugins/plot/src/configuration/SeriesCollection.js @@ -0,0 +1,158 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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([ + './PlotSeries', + './Collection', + './Model', + '../lib/color', + 'lodash' +], function ( + PlotSeries, + Collection, + Model, + color, + _ +) { + + var SeriesCollection = Collection.extend({ + modelClass: PlotSeries, + initialize: function (options) { + this.plot = options.plot; + this.openmct = options.openmct; + this.palette = new color.ColorPalette(); + this.listenTo(this, 'add', this.onSeriesAdd, this); + this.listenTo(this, 'remove', this.onSeriesRemove, this); + this.listenTo(this.plot, 'change:domainObject', this.trackPersistedConfig, this); + + var domainObject = this.plot.get('domainObject'); + if (domainObject.telemetry) { + this.addTelemetryObject(domainObject); + } else { + this.watchTelemetryContainer(domainObject); + } + }, + trackPersistedConfig: function (domainObject) { + domainObject.configuration.series.forEach(function (seriesConfig) { + var series = this.byIdentifier(seriesConfig.identifier); + if (series) { + series.set('persistedConfiguration', seriesConfig); + } + }, this); + }, + watchTelemetryContainer: function (domainObject) { + var composition = this.openmct.composition.get(domainObject); + this.listenTo(composition, 'add', this.addTelemetryObject, this); + this.listenTo(composition, 'remove', this.removeTelemetryObject, this); + composition.load(); + }, + addTelemetryObject: function (domainObject) { + var seriesConfig = { + identifier: domainObject.identifier + }; + + var plotObject = this.plot.get('domainObject'); + if (plotObject.type === 'telemetry.plot.overlay') { + var index = this.size(); + if (!plotObject.configuration.series[index]) { + this.openmct.objects.mutate( + plotObject, + 'configuration.series[' + index + ']', + seriesConfig + ); + } + seriesConfig = this.plot.get('domainObject').configuration.series[index]; + // Clone to prevent accidental mutation by ref. + seriesConfig = JSON.parse(JSON.stringify(seriesConfig)); + } + + seriesConfig.persistedConfiguration = + this.plot.getPersistedSeriesConfig(domainObject.identifier); + + this.add(new PlotSeries({ + model: seriesConfig, + domainObject: domainObject, + collection: this, + openmct: this.openmct + })); + }, + removeTelemetryObject: function (identifier) { + // TODO: properly locate in self (and parent configuration) + // Instead of binding via index, which is not guaranteed because + // edits could occur when plotcontroller is not instantiated. + // This bug also extends to the plotOptions form which currently + // relies on indexes that match. + var plotObject = this.plot.get('domainObject'); + if (plotObject.type === 'telemetry.plot.overlay') { + var index = _.findIndex(plotObject.configuration.series, function (s) { + return _.isEqual(identifier, s.identifier); + }); + this.remove(this.at(index)); + // Because this is triggered by a composition change, we have + // to defer mutation of our plot object, otherwise we might + // mutate an outdated version of the plotObject. + setTimeout(function () { + var newPlotObject = this.plot.get('domainObject'); + var cSeries = newPlotObject.configuration.series.slice(); + cSeries.splice(index, 1); + this.openmct.objects.mutate(newPlotObject, 'configuration.series', cSeries); + }.bind(this)); + } + }, + onSeriesAdd: function (series) { + var seriesColor = series.get('color'); + if (seriesColor) { + if (!(seriesColor instanceof color.Color)) { + seriesColor = color.Color.fromHexString(seriesColor); + series.set('color', seriesColor); + } + this.palette.remove(seriesColor); + } else { + series.set('color', this.palette.getNextColor()); + } + this.listenTo(series, 'change:color', this.updateColorPalette, this); + }, + onSeriesRemove: function (series) { + this.palette.return(series.get('color')); + this.stopListening(series); + series.destroy(); + }, + updateColorPalette: function (newColor, oldColor) { + this.palette.remove(newColor); + var seriesWithColor = this.filter(function (series) { + return series.get('color') === newColor; + })[0]; + if (!seriesWithColor) { + this.palette.return(oldColor); + } + }, + byIdentifier: function (identifier) { + return this.filter(function (series) { + var seriesIdentifier = series.get('identifier'); + return seriesIdentifier.namespace === identifier.namespace && + seriesIdentifier.key === identifier.key; + })[0]; + } + }); + + return SeriesCollection; + +}); diff --git a/src/plugins/plot/src/configuration/XAxisModel.js b/src/plugins/plot/src/configuration/XAxisModel.js new file mode 100644 index 0000000000..e924991aef --- /dev/null +++ b/src/plugins/plot/src/configuration/XAxisModel.js @@ -0,0 +1,89 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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([ + './Model' +], function ( + Model +) { + + /** + * TODO: doc strings. + */ + var XAxisModel = Model.extend({ + initialize: function (options) { + this.plot = options.plot; + this.on('change:range', function (newValue, oldValue, model) { + if (!model.get('frozen')) { + model.set('displayRange', newValue); + } + }); + + this.on('change:frozen', function (frozen, oldValue, model) { + if (!frozen) { + model.set('range', this.get('range')); + } + }); + + if (this.get('range')) { + this.set('range', this.get('range')); + } + this.listenTo(this, 'change:key', this.changeKey, this); + }, + changeKey: function (newKey) { + var series = this.plot.series.first(); + if (series) { + var xMetadata = series.metadata.value(newKey); + var xFormat = series.formats[newKey]; + this.set('label', xMetadata.name); + this.set('format', xFormat.format.bind(xFormat)); + } else { + this.set('format', function (x) { + return x; + }); + this.set('label', newKey); + } + this.plot.series.forEach(function (plotSeries) { + plotSeries.set('xKey', newKey); + plotSeries.reset(); + }); + }, + defaults: function (options) { + var bounds = options.openmct.time.bounds(); + var timeSystem = options.openmct.time.timeSystem(); + var format = options.openmct.$injector.get('formatService') + .getFormat(timeSystem.timeFormat); + + return { + name: timeSystem.name, + key: timeSystem.key, + format: format.format.bind(format), + range: { + min: bounds.start, + max: bounds.end + }, + frozen: false + }; + } + }); + + return XAxisModel; +}); diff --git a/src/plugins/plot/src/configuration/YAxisModel.js b/src/plugins/plot/src/configuration/YAxisModel.js new file mode 100644 index 0000000000..8660b38d39 --- /dev/null +++ b/src/plugins/plot/src/configuration/YAxisModel.js @@ -0,0 +1,219 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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([ + './Model', + 'lodash' +], function ( + Model, + _ +) { + + /** + * YAxis model + * + * TODO: docstrings. + * + * has the following Model properties: + * + * `autoscale`: boolean, whether or not to autoscale. + * `autoscalePadding`: float, percent of padding to display in plots. + * `displayRange`: the current display range for the x Axis. + * `format`: the formatter for the axis. + * `frozen`: boolean, if true, displayRange will not be updated automatically. + * Used to temporarily disable automatic updates during user interaction. + * `label`: label to display on axis. + * `stats`: Min and Max Values of data, automatically updated by observing + * plot series. + * `values`: for enumerated types, an array of possible display values. + * `range`: the user-configured range to use for display, when autoscale is + * disabled. + * + */ + var YAxisModel = Model.extend({ + initialize: function (options) { + this.plot = options.plot; + this.listenTo(this, 'change:stats', this.calculateAutoscaleExtents, this); + this.listenTo(this, 'change:autoscale', this.toggleAutoscale, this); + this.listenTo(this, 'change:autoscalePadding', this.updatePadding, this); + this.listenTo(this, 'change:frozen', this.toggleFreeze, this); + this.listenTo(this, 'change:range', this.updateDisplayRange, this); + this.updateDisplayRange(this.get('range')); + }, + listenToSeriesCollection: function (seriesCollection) { + this.seriesCollection = seriesCollection; + this.listenTo(this.seriesCollection, 'add', function (series) { + this.trackSeries(series); + this.updateFromSeries(this.seriesCollection); + }, this); + this.listenTo(this.seriesCollection, 'remove', function (series) { + this.untrackSeries(series); + this.updateFromSeries(this.seriesCollection); + }, this); + this.seriesCollection.forEach(this.trackSeries, this); + this.updateFromSeries(this.seriesCollection); + }, + updateDisplayRange: function (range) { + if (!this.get('autoscale')) { + this.set('displayRange', range); + } + }, + toggleFreeze: function (frozen) { + if (!frozen) { + this.toggleAutoscale(this.get('autoscale')); + } + }, + applyPadding: function (range) { + var padding = Math.abs(range.max - range.min) * this.get('autoscalePadding'); + if (padding === 0) { + padding = 1; + } + return { + min: range.min - padding, + max: range.max + padding + }; + }, + updatePadding: function (newPadding) { + if (this.get('autoscale') && !this.get('frozen') && this.has('stats')) { + this.set('displayRange', this.applyPadding(this.get('stats'))); + } + }, + calculateAutoscaleExtents: function (newStats) { + if (this.get('autoscale') && !this.get('frozen')) { + if (!newStats) { + this.unset('displayRange'); + } else { + this.set('displayRange', this.applyPadding(newStats)); + } + } + }, + updateStats: function (seriesStats) { + if (!this.has('stats')) { + this.set('stats', { + min: seriesStats.minValue, + max: seriesStats.maxValue + }); + return; + } + var stats = this.get('stats'); + var changed = false; + if (stats.min > seriesStats.minValue) { + changed = true; + stats.min = seriesStats.minValue; + } + if (stats.max < seriesStats.maxValue) { + changed = true; + stats.max = seriesStats.maxValue; + } + if (changed) { + this.set('stats', { + min: stats.min, + max: stats.max + }); + } + }, + resetStats: function () { + this.unset('stats'); + this.seriesCollection.forEach(function (series) { + if (series.has('stats')) { + this.updateStats(series.get('stats')); + } + }, this); + }, + trackSeries: function (series) { + this.listenTo(series, 'change:stats', function (seriesStats) { + if (!seriesStats) { + this.resetStats(); + } else { + this.updateStats(seriesStats); + } + }, this); + }, + untrackSeries: function (series) { + this.stopListening(series); + this.resetStats(); + }, + toggleAutoscale: function (autoscale) { + if (autoscale) { + this.set('displayRange', this.applyPadding(this.get('stats'))); + } else { + this.set('displayRange', this.get('range')); + } + }, + /** + * Update yAxis format, values, and label from known series. + */ + updateFromSeries: function (series) { + var sampleSeries = series.first(); + if (!sampleSeries) { + return; + } + + var yKey = sampleSeries.get('yKey'); + var yMetadata = sampleSeries.metadata.value(yKey); + var yFormat = sampleSeries.formats[yKey]; + this.set('format', yFormat.format.bind(yFormat)); + this.set('values', yMetadata.values); + + var plotModel = this.plot.get('domainObject'); + var label = _.get(plotModel, 'configuration.xAxis.label'); + if (!label) { + var labelUnits = series.map(function (s) { + return s.metadata.value(s.get('yKey')).units; + }).reduce(function (a, b) { + if (a === undefined) { + return b; + } + if (a === b) { + return a; + } + return ''; + }, undefined); + if (labelUnits) { + this.set('label', labelUnits); + return; + } + var labelName = series.map(function (s) { + return s.metadata.value(s.get('yKey')).name; + }).reduce(function (a, b) { + if (a === undefined) { + return b; + } + if (a === b) { + return a; + } + return ''; + }, undefined); + this.set('label', labelName); + } + }, + defaults: function (options) { + return { + frozen: false, + autoscale: true, + autoscalePadding: 0.1 + }; + } + }); + + return YAxisModel; + +}); diff --git a/platform/features/plot/test/PlotOptionsFormSpec.js b/src/plugins/plot/src/configuration/configStore.js similarity index 55% rename from platform/features/plot/test/PlotOptionsFormSpec.js rename to src/plugins/plot/src/configuration/configStore.js index 41811e8be7..c34bed8304 100644 --- a/platform/features/plot/test/PlotOptionsFormSpec.js +++ b/src/plugins/plot/src/configuration/configStore.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, United States Government + * Open MCT, Copyright (c) 2014-2018, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -19,29 +19,40 @@ * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ +define([ +], function ( +) { -define( - ['../src/PlotOptionsForm'], - function (PlotOptionsForm) { - - describe("The Plot Options form", function () { - var plotOptionsForm; - - beforeEach(function () { - - plotOptionsForm = new PlotOptionsForm(); - }); - - it("defines form specs for x-axis, y-axis, and series data", function () { - expect(plotOptionsForm.xAxisForm).toBeDefined(); - expect(plotOptionsForm.xAxisForm.sections).toBeDefined(); - expect(plotOptionsForm.xAxisForm.sections[0].rows).toBeDefined(); - expect(plotOptionsForm.xAxisForm.sections[0].rows.length).toBeGreaterThan(0); - - expect(plotOptionsForm.yAxisForm).toBeDefined(); - expect(plotOptionsForm.plotSeriesForm).toBeDefined(); - }); - - }); + function ConfigStore() { + this.store = {}; + this.tracking = {}; } -); + + ConfigStore.prototype.track = function (id) { + if (!this.tracking[id]) { + this.tracking[id] = 0; + } + this.tracking[id] += 1; + }; + + ConfigStore.prototype.untrack = function (id) { + this.tracking[id] -= 1; + if (this.tracking[id] <= 0) { + delete this.tracking[id]; + this.store[id].destroy(); + delete this.store[id]; + } + }; + + ConfigStore.prototype.add = function (id, config) { + this.store[id] = config; + }; + + ConfigStore.prototype.get = function (id) { + return this.store[id]; + }; + + var STORE = new ConfigStore(); + + return STORE; +}); diff --git a/src/plugins/plot/src/draw/Draw2D.js b/src/plugins/plot/src/draw/Draw2D.js new file mode 100644 index 0000000000..a333529062 --- /dev/null +++ b/src/plugins/plot/src/draw/Draw2D.js @@ -0,0 +1,160 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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 ( + +) { + + /** + * Create a new draw API utilizing the Canvas's 2D API for rendering. + * + * @constructor + * @param {CanvasElement} canvas the canvas object to render upon + * @throws {Error} an error is thrown if Canvas's 2D API is unavailab + */ + function Draw2D(canvas) { + this.canvas = canvas; + this.c2d = canvas.getContext('2d'); + this.width = canvas.width; + this.height = canvas.height; + this.dimensions = [this.width, this.height]; + this.origin = [0, 0]; + + if (!this.c2d) { + throw new Error("Canvas 2d API unavailable."); + } + } + + // Convert from logical to physical x coordinates + Draw2D.prototype.x = function (v) { + return ((v - this.origin[0]) / this.dimensions[0]) * this.width; + }; + + // Convert from logical to physical y coordinates + Draw2D.prototype.y = function (v) { + return this.height - + ((v - this.origin[1]) / this.dimensions[1]) * this.height; + }; + + // Set the color to be used for drawing operations + Draw2D.prototype.setColor = function (color) { + var mappedColor = color.map(function (c, i) { + return i < 3 ? Math.floor(c * 255) : (c); + }).join(','); + this.c2d.strokeStyle = "rgba(" + mappedColor + ")"; + this.c2d.fillStyle = "rgba(" + mappedColor + ")"; + }; + + + Draw2D.prototype.clear = function () { + this.width = this.canvas.width = this.canvas.offsetWidth; + this.height = this.canvas.height = this.canvas.offsetHeight; + this.c2d.clearRect(0, 0, this.width, this.height); + }; + + Draw2D.prototype.setDimensions = function (newDimensions, newOrigin) { + this.dimensions = newDimensions; + this.origin = newOrigin; + }; + + Draw2D.prototype.drawLine = function (buf, color, points) { + var i; + + this.setColor(color); + + // Configure context to draw two-pixel-thick lines + this.c2d.lineWidth = 1; + + // Start a new path... + if (buf.length > 1) { + this.c2d.beginPath(); + this.c2d.moveTo(this.x(buf[0]), this.y(buf[1])); + } + + // ...and add points to it... + for (i = 2; i < points * 2; i = i + 2) { + this.c2d.lineTo(this.x(buf[i]), this.y(buf[i + 1])); + } + + // ...before finally drawing it. + this.c2d.stroke(); + }; + + Draw2D.prototype.drawSquare = function (min, max, color) { + var x1 = this.x(min[0]), + y1 = this.y(min[1]), + w = this.x(max[0]) - x1, + h = this.y(max[1]) - y1; + + this.setColor(color); + this.c2d.fillRect(x1, y1, w, h); + }; + + Draw2D.prototype.drawPoints = function ( + buf, + color, + points, + pointSize + ) { + var i = 0, + offset = pointSize / 2; + + this.setColor(color); + + for (; i < points; i++) { + this.c2d.fillRect( + this.x(buf[i * 2]) - offset, + this.y(buf[i * 2 + 1]) - offset, + pointSize, + pointSize + ); + } + }; + + Draw2D.prototype.drawLimitPoint = function (x, y, size) { + this.c2d.fillRect(x + size, y, size, size); + this.c2d.fillRect(x, y + size, size, size); + this.c2d.fillRect(x - size, y, size, size); + this.c2d.fillRect(x, y - size, size, size); + }; + + Draw2D.prototype.drawLimitPoints = function (points, color, pointSize) { + var limitSize = pointSize * 2; + var offset = limitSize / 2; + + this.setColor(color); + + for (var i = 0; i < points.length; i++) { + this.drawLimitPoint( + this.x(points[i].x) - offset, + this.y(points[i].y) - offset, + limitSize + ); + } + }; + + + return Draw2D; +}); diff --git a/src/plugins/plot/src/draw/DrawLoader.js b/src/plugins/plot/src/draw/DrawLoader.js new file mode 100644 index 0000000000..c4eb4e1fe6 --- /dev/null +++ b/src/plugins/plot/src/draw/DrawLoader.js @@ -0,0 +1,95 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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. + *****************************************************************************/ +/* global console */ + +define( + [ + './DrawWebGL', + './Draw2D' + ], + function (DrawWebGL, Draw2D) { + + var CHARTS = [ + { + MAX_INSTANCES: 16, + API: DrawWebGL, + ALLOCATIONS: [] + }, + { + MAX_INSTANCES: Number.MAX_INFINITY, + API: Draw2D, + ALLOCATIONS: [] + } + ]; + + /** + * Draw loader attaches a draw API to a canvas element and returns the + * draw API. + */ + return { + /** + * Return the first draw API available. Returns + * `undefined` if a draw API could not be constructed. + *. + * @param {CanvasElement} canvas - The canvas eelement to attach + the draw API to. + */ + getDrawAPI: function (canvas, overlay) { + var api; + + CHARTS.forEach(function (CHART_TYPE) { + if (api) { + return; + } + if (CHART_TYPE.ALLOCATIONS.length >= + CHART_TYPE.MAX_INSTANCES) { + return; + } + try { + api = new CHART_TYPE.API(canvas, overlay); + CHART_TYPE.ALLOCATIONS.push(api); + } catch (e) { + console.warn([ + "Could not instantiate chart", + CHART_TYPE.API.name, + ";", + e.message + ].join(" ")); + } + }); + + if (!api) { + console.warn("Cannot initialize mct-chart."); + } + return api; + }, + + releaseDrawAPI: function (api) { + CHARTS.forEach(function (CHART_TYPE) { + if (api instanceof CHART_TYPE.API) { + CHART_TYPE.ALLOCATIONS.splice(CHART_TYPE.ALLOCATIONS.indexOf(api), 1); + } + }); + } + }; + } +); diff --git a/src/plugins/plot/src/draw/DrawWebGL.js b/src/plugins/plot/src/draw/DrawWebGL.js new file mode 100644 index 0000000000..1e44a60a6a --- /dev/null +++ b/src/plugins/plot/src/draw/DrawWebGL.js @@ -0,0 +1,226 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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 ( + +) { + + // WebGL shader sources (for drawing plain colors) + var FRAGMENT_SHADER = [ + "precision mediump float;", + "uniform vec4 uColor;", + "void main(void) {", + "gl_FragColor = uColor;", + "}" + ].join('\n'), + VERTEX_SHADER = [ + "attribute vec2 aVertexPosition;", + "uniform vec2 uDimensions;", + "uniform vec2 uOrigin;", + "uniform float uPointSize;", + "void main(void) {", + "gl_Position = vec4(2.0 * ((aVertexPosition - uOrigin) / uDimensions) - vec2(1,1), 0, 1);", + "gl_PointSize = uPointSize;", + "}" + ].join('\n'); + + /** + * Create a draw api utilizing WebGL. + * + * @constructor + * @param {CanvasElement} canvas the canvas object to render upon + * @throws {Error} an error is thrown if WebGL is unavailable. + */ + function DrawWebGL(canvas, overlay) { + this.canvas = canvas; + this.gl = this.canvas.getContext("webgl", { preserveDrawingBuffer: true }) || + this.canvas.getContext("experimental-webgl", { preserveDrawingBuffer: true }); + + this.overlay = overlay; + this.c2d = overlay.getContext('2d'); + if (!this.c2d) { + throw new Error("No canvas 2d!"); + } + + // Ensure a context was actually available before proceeding + if (!this.gl) { + throw new Error("WebGL unavailable."); + } + + // Initialize shaders + this.vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER); + this.gl.shaderSource(this.vertexShader, VERTEX_SHADER); + this.gl.compileShader(this.vertexShader); + this.fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER); + this.gl.shaderSource(this.fragmentShader, FRAGMENT_SHADER); + this.gl.compileShader(this.fragmentShader); + + // Assemble vertex/fragment shaders into programs + this.program = this.gl.createProgram(); + this.gl.attachShader(this.program, this.vertexShader); + this.gl.attachShader(this.program, this.fragmentShader); + this.gl.linkProgram(this.program); + this.gl.useProgram(this.program); + + // Get locations for attribs/uniforms from the + // shader programs (to pass values into shaders at draw-time) + this.aVertexPosition = this.gl.getAttribLocation(this.program, "aVertexPosition"); + this.uColor = this.gl.getUniformLocation(this.program, "uColor"); + this.uDimensions = this.gl.getUniformLocation(this.program, "uDimensions"); + this.uOrigin = this.gl.getUniformLocation(this.program, "uOrigin"); + this.uPointSize = this.gl.getUniformLocation(this.program, "uPointSize"); + + this.gl.enableVertexAttribArray(this.aVertexPosition); + + // Create a buffer to holds points which will be drawn + this.buffer = this.gl.createBuffer(); + + // Use a line width of 2.0 for legibility + this.gl.lineWidth(2.0); + + // Enable blending, for smoothness + this.gl.enable(this.gl.BLEND); + this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA); + } + + // Convert from logical to physical x coordinates + DrawWebGL.prototype.x = function (v) { + return ((v - this.origin[0]) / this.dimensions[0]) * this.width; + }; + + // Convert from logical to physical y coordinates + DrawWebGL.prototype.y = function (v) { + return this.height - + ((v - this.origin[1]) / this.dimensions[1]) * this.height; + }; + + DrawWebGL.prototype.doDraw = function (drawType, buf, color, points) { + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, buf, this.gl.DYNAMIC_DRAW); + this.gl.vertexAttribPointer(this.aVertexPosition, 2, this.gl.FLOAT, false, 0, 0); + this.gl.uniform4fv(this.uColor, color); + this.gl.drawArrays(drawType, 0, points); + }; + + DrawWebGL.prototype.clear = function () { + this.height = this.canvas.height = this.canvas.offsetHeight; + this.width = this.canvas.width = this.canvas.offsetWidth; + this.overlay.height = this.overlay.offsetHeight; + this.overlay.width = this.overlay.offsetWidth; + // Set the viewport size; note that we use the width/height + // that our WebGL context reports, which may be lower + // resolution than the canvas we requested. + this.gl.viewport( + 0, + 0, + this.gl.drawingBufferWidth, + this.gl.drawingBufferHeight + ); + this.gl.clear(this.gl.COLOR_BUFFER_BIT + this.gl.DEPTH_BUFFER_BIT); + }; + + /** + * Set the logical boundaries of the chart. + * @param {number[]} dimensions the horizontal and + * vertical dimensions of the chart + * @param {number[]} origin the horizontal/vertical + * origin of the chart + */ + DrawWebGL.prototype.setDimensions = function (dimensions, origin) { + this.dimensions = dimensions; + this.origin = origin; + if (dimensions && dimensions.length > 0 && + origin && origin.length > 0) { + this.gl.uniform2fv(this.uDimensions, dimensions); + this.gl.uniform2fv(this.uOrigin, origin); + } + }; + + /** + * Draw the supplied buffer as a line strip (a sequence + * of line segments), in the chosen color. + * @param {Float32Array} buf the line strip to draw, + * in alternating x/y positions + * @param {number[]} color the color to use when drawing + * the line, as an RGBA color where each element + * is in the range of 0.0-1.0 + * @param {number} points the number of points to draw + */ + DrawWebGL.prototype.drawLine = function (buf, color, points) { + this.doDraw(this.gl.LINE_STRIP, buf, color, points); + }; + + /** + * Draw the buffer as points. + * + */ + DrawWebGL.prototype.drawPoints = function (buf, color, points, pointSize) { + this.gl.uniform1f(this.uPointSize, pointSize); + this.doDraw(this.gl.POINTS, buf, color, points); + }; + + /** + * Draw a rectangle extending from one corner to another, + * in the chosen color. + * @param {number[]} min the first corner of the rectangle + * @param {number[]} max the opposite corner + * @param {number[]} color the color to use when drawing + * the rectangle, as an RGBA color where each element + * is in the range of 0.0-1.0 + */ + DrawWebGL.prototype.drawSquare = function (min, max, color) { + this.doDraw(this.gl.TRIANGLE_FAN, new Float32Array( + min.concat([min[0], max[1]]).concat(max).concat([max[0], min[1]]) + ), color, 4); + }; + + DrawWebGL.prototype.drawLimitPoint = function (x, y, size) { + this.c2d.fillRect(x + size, y, size, size); + this.c2d.fillRect(x, y + size, size, size); + this.c2d.fillRect(x - size, y, size, size); + this.c2d.fillRect(x, y - size, size, size); + }; + + DrawWebGL.prototype.drawLimitPoints = function (points, color, pointSize) { + var limitSize = pointSize * 2; + var offset = limitSize / 2; + + var mappedColor = color.map(function (c, i) { + return i < 3 ? Math.floor(c * 255) : (c); + }).join(','); + this.c2d.strokeStyle = "rgba(" + mappedColor + ")"; + this.c2d.fillStyle = "rgba(" + mappedColor + ")"; + + for (var i = 0; i < points.length; i++) { + this.drawLimitPoint( + this.x(points[i].x) - offset, + this.y(points[i].y) - offset, + limitSize + ); + } + }; + + return DrawWebGL; +}); diff --git a/src/plugins/plot/src/inspector/HideElementPoolDirective.js b/src/plugins/plot/src/inspector/HideElementPoolDirective.js new file mode 100644 index 0000000000..60932dc66f --- /dev/null +++ b/src/plugins/plot/src/inspector/HideElementPoolDirective.js @@ -0,0 +1,55 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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 () { + /** + * Simple directive that removes the elements pool when used in the + * inspector region. Workaround until we have better control of screen + * regions. + */ + return function HideElementPoolDirective() { + return { + restrict: "A", + link: function ($scope, $element) { + var splitter = $element.parent(); + + while (splitter[0].tagName !== 'MCT-SPLIT-PANE') { + splitter = splitter.parent(); + } + + [ + '.split-pane-component.pane.bottom', + 'mct-splitter' + ].forEach(function (selector) { + var element = splitter[0].querySelectorAll(selector)[0]; + element.style.maxHeight = '0px'; + element.style.minHeight = '0px'; + }); + + splitter[0] + .querySelectorAll('.split-pane-component.pane.top')[0] + .style + .bottom = '0px'; + } + }; + }; +}); diff --git a/src/plugins/plot/src/inspector/InspectorRegion.js b/src/plugins/plot/src/inspector/InspectorRegion.js new file mode 100644 index 0000000000..a0fd87806a --- /dev/null +++ b/src/plugins/plot/src/inspector/InspectorRegion.js @@ -0,0 +1,68 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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( + [ + './Region' + ], + function (Region) { + + /** + * Defines the a default Inspector region. Captured in a class to + * allow for modular extension and customization of regions based on + * the typical case. + * @memberOf platform/commonUI/regions + * @constructor + */ + function InspectorRegion() { + Region.call(this, {'name': 'Inspector'}); + + this.buildRegion(); + } + + InspectorRegion.prototype = Object.create(Region.prototype); + InspectorRegion.prototype.constructor = Region; + + /** + * @private + */ + InspectorRegion.prototype.buildRegion = function () { + var metadataRegion = { + name: 'metadata', + title: 'Metadata Region', + // Which modes should the region part be visible in? If + // nothing provided here, then assumed that part is visible + // in both. The visibility or otherwise of a region part + // should be decided by a policy. In this case, 'modes' is a + // shortcut that is used by the EditableRegionPolicy. + modes: ['browse', 'edit'], + content: { + key: 'object-properties' + } + }; + this.addRegion(new Region(metadataRegion), 0); + }; + + return InspectorRegion; + } +); diff --git a/src/plugins/plot/src/inspector/PlotBrowseRegion.js b/src/plugins/plot/src/inspector/PlotBrowseRegion.js new file mode 100644 index 0000000000..98985efe60 --- /dev/null +++ b/src/plugins/plot/src/inspector/PlotBrowseRegion.js @@ -0,0 +1,40 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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([ + './Region' +], function ( + Region +) { + + var PlotBrowseRegion = new Region({ + name: "plot-options", + title: "Plot Options", + modes: ['browse'], + content: { + key: "plot-options-browse" + } + }); + + return PlotBrowseRegion; + +}); diff --git a/src/plugins/plot/src/inspector/PlotEditRegion.js b/src/plugins/plot/src/inspector/PlotEditRegion.js new file mode 100644 index 0000000000..8ba27ff0ed --- /dev/null +++ b/src/plugins/plot/src/inspector/PlotEditRegion.js @@ -0,0 +1,18 @@ +/*global define*/ +define([ + './Region' +], function ( + Region +) { + + var PlotEditRegion = new Region({ + name: "plot-options", + title: "Plot Options", + modes: ['edit'], + content: { + key: "plot-options-edit" + } + }); + + return PlotEditRegion; +}); diff --git a/src/plugins/plot/src/inspector/PlotInspector.js b/src/plugins/plot/src/inspector/PlotInspector.js new file mode 100644 index 0000000000..faa615af1e --- /dev/null +++ b/src/plugins/plot/src/inspector/PlotInspector.js @@ -0,0 +1,39 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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([ + './InspectorRegion', + './PlotBrowseRegion', + './PlotEditRegion' +], function ( + InspectorRegion, + PlotBrowseRegion, + PlotEditRegion +) { + + var plotInspector = new InspectorRegion(); + + plotInspector.addRegion(PlotBrowseRegion); + plotInspector.addRegion(PlotEditRegion); + + return plotInspector; +}); diff --git a/src/plugins/plot/src/inspector/PlotOptionsController.js b/src/plugins/plot/src/inspector/PlotOptionsController.js new file mode 100644 index 0000000000..e57a929000 --- /dev/null +++ b/src/plugins/plot/src/inspector/PlotOptionsController.js @@ -0,0 +1,258 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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([ + '../configuration/configStore', + '../lib/eventHelpers', + '../../../../api/objects/object-utils', + 'lodash' +], function ( + configStore, + eventHelpers, + objectUtils, + _ +) { + + /** + * The LayoutController is responsible for supporting the + * Layout view. It arranges frames according to saved configuration + * and provides methods for updating these based on mouse + * movement. + * @memberof platform/features/plot + * @constructor + * @param {Scope} $scope the controller's Angular scope + */ + function PlotOptionsController($scope, openmct, $timeout) { + this.$scope = $scope; + this.openmct = openmct; + this.$timeout = $timeout; + + this.configId = $scope.domainObject.getId(); + this.setUpScope(); + } + + eventHelpers.extend(PlotOptionsController.prototype); + + PlotOptionsController.prototype.setColor = function (series, color) { + var seriesWithColor = this.config.series.filter(function (s) { + return s.get('color') === color; + })[0]; + var oldColor = series.get('color'); + series.set('color', color); + + var seriesIndex = this.config.series.indexOf(series); + this.openmct.objects.mutate( + this.domainObject, + 'configuration.series[' + seriesIndex + '].color', + color.asHexString() + ); + + if (seriesWithColor) { + seriesWithColor.set('color', oldColor); + var oldSeriesIndex = this.config.series.indexOf(seriesWithColor); + this.openmct.objects.mutate( + this.domainObject, + 'configuration.series[' + oldSeriesIndex + '].color', + oldColor.asHexString() + ); + } + }; + + PlotOptionsController.prototype.updateDomainObject = function (domainObject) { + this.domainObject = domainObject; + }; + + PlotOptionsController.prototype.destroy = function () { + configStore.untrack(this.configId); + this.stopListening(); + this.unlisten(); + }; + + PlotOptionsController.prototype.setUpScope = function () { + var config = configStore.get(this.configId); + if (!config) { + this.$timeout(this.setUpScope.bind(this)); + return; + } + configStore.track(this.configId); + + this.config = this.$scope.config = config; + this.$scope.setColor = this.setColor.bind(this); + this.$scope.form = {series: []}; + this.$scope.validation = {}; + + this.domainObject = this.config.get('domainObject'); + this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject.bind(this)); + + this.listenTo(this.$scope, '$destroy', this.destroy, this); + this.listenTo(config.series, 'add', this.addSeries, this); + this.listenTo(config.series, 'remove', this.resetAllSeries, this); + config.series.forEach(this.addSeries, this); + + this.linkFields(config.yAxis, 'label', 'form.yAxis.label', undefined, undefined, 'configuration.yAxis.label'); + this.linkFields(config.yAxis, 'autoscale', 'form.yAxis.autoscale', Boolean, undefined, 'configuration.yAxis.autoscale'); + this.linkFields(config.yAxis, 'autoscalePadding', 'form.yAxis.autoscalePadding', Number, undefined, 'configuration.yAxis.autoscalePadding'); + this.linkFields(config.yAxis, 'range', 'form.yAxis.range', function coerceRange(range) { + if (!range) { + return { + min: 0, + max: 0 + }; + } + var newRange = {}; + if (typeof range.min !== undefined) { + newRange.min = Number(range.min); + } + if (typeof range.max !== undefined) { + newRange.max = Number(range.max); + } + return newRange; + }, function validateRange(range) { + if (!range) { + return 'Need range'; + } + if (!range.min) { + return 'Must specify Minimum'; + } + if (!range.max) { + return 'Must specify Maximum'; + } + if (_.isNaN(Number(range.min))) { + return 'Minimum must be a number.'; + } + if (_.isNaN(Number(range.max))) { + return 'Maximum must be a number.'; + } + if (Number(range.min) > Number(range.max)) { + return 'Minimum must be less than Maximum.'; + } + if (config.yAxis.get('autoscale')) { + return false; + } + return true; + }, 'configuration.yAxis.range'); + + this.linkFields(config.legend, 'position', 'form.legend.position', undefined, undefined, 'configuration.legend.position'); + this.linkFields(config.legend, 'expandByDefault', 'form.legend.expandByDefault', Boolean, undefined, 'configuration.legend.expandByDefault'); + this.linkFields(config.legend, 'valueToShowWhenCollapsed', 'form.legend.valueToShowWhenCollapsed', undefined, undefined, 'configuration.legend.valueToShowWhenCollapsed'); + this.linkFields(config.legend, 'showValueWhenExpanded', 'form.legend.showValueWhenExpanded', Boolean, undefined, 'configuration.legend.showValueWhenExpanded'); + this.linkFields(config.legend, 'showTimestampWhenExpanded', 'form.legend.showTimestampWhenExpanded', Boolean, undefined, 'configuration.legend.showTimestampWhenExpanded'); + this.linkFields(config.legend, 'showMaximumWhenExpanded', 'form.legend.showMaximumWhenExpanded', Boolean, undefined, 'configuration.legend.showMaximumWhenExpanded'); + this.linkFields(config.legend, 'showMinimumWhenExpanded', 'form.legend.showMinimumWhenExpanded', Boolean, undefined, 'configuration.legend.showMinimumWhenExpanded'); + }; + + PlotOptionsController.prototype.addSeries = function (series, index) { + if (this.$scope.form.series[index]) { + // the way listeners work, this will blow up. At this point, it + // can't technically occur, but if we added some sort of reordering + // at a later date, it would not be clear why this doesn't work. + // So here's to hoping this helps debugging. + throw new Error('Plot options does not support insert at index.'); + } + var metadata = series.metadata; + this.$scope.form.series[index] = { + yAxisOptions: metadata.valuesForHints(['range']).map(function (o) { + return { + name: o.key, + value: o.key + }; + }) + }; + var seriesObject = series.domainObject; + var seriesId = objectUtils.makeKeyString(seriesObject.identifier); + var configPath = 'configuration.series[' + index + '].'; + var path = 'form.series[' + index + '].'; + this.$scope.domainObject.useCapability('composition') + .then(function (children) { + children.forEach(function (child) { + if (child.getId() === seriesId) { + series.oldObject = child; + } + }); + }.bind(this)); + + this.linkFields(series, 'yKey', path + 'yKey', undefined, undefined, configPath + 'yKey'); + this.linkFields(series, 'interpolate', path + 'interpolate', undefined, undefined, configPath + 'interpolate'); + this.linkFields(series, 'markers', path + 'markers', undefined, undefined, configPath + 'markers'); + this.linkFields(series, 'markerSize', path + 'markerSize', Number, undefined, configPath + 'markerSize'); + this.linkFields(series, 'alarmMarkers', path + 'alarmMarkers', Boolean, undefined, configPath + 'alarmMarkers'); + }; + + PlotOptionsController.prototype.resetAllSeries = function (series, index) { + this.removeSeries(series); + this.config.series.forEach(this.removeSeries, this); + this.$scope.form.series = []; + this.config.series.forEach(this.addSeries, this); + }; + + PlotOptionsController.prototype.removeSeries = function (series) { + this.stopListening(series); + series.stopListening(this.$scope); + }; + + PlotOptionsController.prototype.linkFields = function ( + model, + prop, + scopePath, + coerce, + validate, + objectPath + ) { + if (!coerce) { + coerce = function (v) { + return v; + }; + } + if (!validate) { + validate = function () { + return true; + }; + } + this.listenTo(model, 'change:' + prop, function (newVal, oldVal) { + if (!_.isEqual(coerce(_.get(this.$scope, scopePath)), coerce(newVal))) { + _.set(this.$scope, scopePath, coerce(newVal)); + } + }, this); + model.listenTo(this.$scope, 'change:' + scopePath, function (newVal, oldVal) { + if (_.isEqual(coerce(newVal), coerce(model.get(prop)))) { + return; // Don't trigger excessive mutations. + } + var validationResult = validate(newVal); + if (validationResult === true) { + delete this.$scope.validation[scopePath]; + } else { + this.$scope.validation[scopePath] = validationResult; + return; + } + if (!_.isEqual(coerce(newVal), coerce(oldVal))) { + model.set(prop, coerce(newVal)); + if (objectPath && this.$scope.domainObject.getCapability('editor').isEditContextRoot()) { + this.openmct.objects.mutate(this.domainObject, objectPath, coerce(newVal)); + } + } + }, this); + _.set(this.$scope, scopePath, coerce(model.get(prop))); + }; + + return PlotOptionsController; +}); + diff --git a/src/plugins/plot/src/inspector/PlotOptionsForm.js b/src/plugins/plot/src/inspector/PlotOptionsForm.js new file mode 100644 index 0000000000..2c377cf88c --- /dev/null +++ b/src/plugins/plot/src/inspector/PlotOptionsForm.js @@ -0,0 +1,177 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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 () { + + + /** + * A class for encapsulating structure and behaviour of the plot + * options form + * @memberOf platform/features/plot + * @param topic + * @constructor + */ + function PlotOptionsForm() { + + /* + Defined below are the form structures for the plot options. + */ + this.xAxisForm = { + name: 'x-axis', + sections: [{ + name: 'x-axis', + rows: [ + { + name: 'Domain', + control: 'select', + key: 'key', + options: [ + { + name: 'SCET', + value: 'scet' + }, + { + name: 'ERT', + value: 'ert' + }, + { + name: 'SCLK', + value: 'sclk' + }, + { + name: 'LST', + value: 'lst' + } + ] + } + ] + }]}; + + this.yAxisForm = { + name: 'y-axis', + sections: [{ + // Will need to be repeated for each y-axis, with a + // distinct name for each. Ideally the name of the axis + // itself. + name: 'y-axis', + rows: [ + { + name: 'Range', + control: 'select', + key: 'key', + options: [ + { + name: 'Autoselect', + value: 'auto' + }, + { + name: 'EU', + value: 'eu' + }, + { + name: 'DN', + value: 'dn' + }, + { + name: 'Status', + value: 'enum' + } + ] + }, + { + name: 'Autoscale', + control: 'checkbox', + key: 'autoscale' + }, + { + name: 'Min', + control: 'textfield', + key: 'min', + pattern: '[0-9]', + inputsize: 'sm' + }, + { + name: 'Max', + control: 'textfield', + key: 'max', + pattern: '[0-9]', + inputsize: 'sm' + } + ] + }] + }; + this.plotSeriesForm = { + name: 'Series Options', + sections: [ + { + rows: [ + { + name: 'Color', + control: 'color', + key: 'color' + }] + }, + { + rows: [ + { + name: 'Markers', + control: 'checkbox', + key: 'markers', + layout: 'control-first' + } + ] + }, + { + rows: [ + { + name: 'No Line', + control: 'radio', + key: 'interpolate', + value: 'none', + layout: 'control-first' + }, + { + name: 'Step Line', + control: 'radio', + key: 'interpolate', + value: 'stepAfter', + layout: 'control-first' + }, + { + name: 'Linear Line', + control: 'radio', + key: 'interpolate', + value: 'linear', + layout: 'control-first' + } + ] + } + ] + }; + } + + return PlotOptionsForm; +}); + diff --git a/src/plugins/plot/src/inspector/Region.js b/src/plugins/plot/src/inspector/Region.js new file mode 100644 index 0000000000..f9364a892c --- /dev/null +++ b/src/plugins/plot/src/inspector/Region.js @@ -0,0 +1,100 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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 () { + + /** + * @typeDef {object} PartContents + * @property {string} key If the part is defined as a + * representation, the key corresponding to the representation. + * @memberOf platform/commonUI/regions + */ + + /** + * @typeDef {object} RegionConfiguration + * @property {string} name A unique name for this region part + * @property {PartContents} [content] the details of the region being + * defined + * @property {Array} [modes] the modes that this region + * should be included in. Options are 'edit' and 'browse'. By + * default, will be included in both. Inclusion of regions is + * determined by policies of category 'region'. By default, the + * {EditableRegionPolicy} will be applied. + * @memberOf platform/commonUI/regions + */ + + /** + * Defines the interface for a screen region. A screen region is a + * section of the browse an edit screens for an object. Regions are + * declared in object type definitions. + * @memberOf platform/commonUI/regions + * @abstract + * @constructor + */ + function Region(configuration) { + configuration = configuration || {}; + this.name = configuration.name; + this.content = configuration.content; + this.modes = configuration.modes; + + this.regions = []; + } + + /** + * Adds a sub-region to this region. + * @param {Region} region the part to add + * @param {number} [index] the position to insert the region. By default + * will add to the end + */ + Region.prototype.addRegion = function (region, index) { + if (index) { + this.regions.splice(index, 0, region); + } else { + this.regions.push(region); + } + }; + + /** + * Removes a sub-region from this region. + * @param {Region | number | strnig} region The region to + * remove. If a number, will remove the region at that index. If a + * string, will remove the region with the matching name. If an + * object, will attempt to remove that object from the Region + */ + Region.prototype.removeRegion = function (region) { + if (typeof region === 'number') { + this.regions.splice(region, 1); + } else if (typeof region === 'string') { + this.regions = this.regions.filter(function (thisRegion) { + return thisRegion.name !== region; + }); + } else { + this.regions.splice(this.regions.indexOf(region), 1); + } + }; + + return Region; + } +); diff --git a/src/plugins/plot/src/lib/color.js b/src/plugins/plot/src/lib/color.js new file mode 100644 index 0000000000..55e8967010 --- /dev/null +++ b/src/plugins/plot/src/lib/color.js @@ -0,0 +1,205 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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. + *****************************************************************************/ +/* global console */ + +define(function () { + + var COLOR_PALETTE = [ + [0x20, 0xB2, 0xAA], + [0x9A, 0xCD, 0x32], + [0xFF, 0x8C, 0x00], + [0xD2, 0xB4, 0x8C], + [0x40, 0xE0, 0xD0], + [0x41, 0x69, 0xFF], + [0xFF, 0xD7, 0x00], + [0x6A, 0x5A, 0xCD], + [0xEE, 0x82, 0xEE], + [0xCC, 0x99, 0x66], + [0x99, 0xCC, 0xCC], + [0x66, 0xCC, 0x33], + [0xFF, 0xCC, 0x00], + [0xFF, 0x66, 0x33], + [0xCC, 0x66, 0xFF], + [0xFF, 0x00, 0x66], + [0xFF, 0xFF, 0x00], + [0x80, 0x00, 0x80], + [0x00, 0x86, 0x8B], + [0x00, 0x8A, 0x00], + [0xFF, 0x00, 0x00], + [0x00, 0x00, 0xFF], + [0xF5, 0xDE, 0xB3], + [0xBC, 0x8F, 0x8F], + [0x46, 0x82, 0xB4], + [0xFF, 0xAF, 0xAF], + [0x43, 0xCD, 0x80], + [0xCD, 0xC1, 0xC5], + [0xA0, 0x52, 0x2D], + [0x64, 0x95, 0xED] + ]; + + function isDefaultColor(color) { + var a = color.asIntegerArray(); + return COLOR_PALETTE.some(function (b) { + return a[0] === b[0] && a[1] === b[1] && a[2] === b[2]; + }); + } + + /** + * A representation of a color that allows conversions between different + * formats. + * + * @constructor + */ + function Color(integerArray) { + this.integerArray = integerArray; + } + + Color.fromHexString = function (hexString) { + if (!/#([0-9a-fA-F]{2}){2}/.test(hexString)) { + throw new Error( + 'Invalid input "' + + hexString + + '". Hex string must be in CSS format e.g. #00FF00' + ); + } + return new Color([ + parseInt(hexString.slice(1, 3), 16), + parseInt(hexString.slice(3, 5), 16), + parseInt(hexString.slice(5, 7), 16) + ]); + }; + + /** + * Return color as a three element array of RGB values, where each value + * is a integer in the range of 0-255. + * + * @return {number[]} the color, as integer RGB values + */ + Color.prototype.asIntegerArray = function () { + return this.integerArray.map(function (c) { + return c; + }); + }; + + /** + * Return color as a string using #-prefixed six-digit RGB hex notation + * (e.g. #FF0000). See http://www.w3.org/TR/css3-color/#rgb-color. + * + * @return {string} the color, as a style-friendly string + */ + + Color.prototype.asHexString = function () { + return '#' + this.integerArray.map(function (c) { + return (c < 16 ? '0' : '') + c.toString(16); + }).join(''); + }; + + /** + * Return color as a RGBA float array. + * + * This format is present specifically to support use with + * WebGL, which expects colors of that form. + * + * @return {number[]} the color, as floating-point RGBA values + */ + Color.prototype.asRGBAArray = function () { + return this.integerArray.map(function (c) { + return c / 255.0; + }).concat([1]); + }; + + Color.prototype.equalTo = function (otherColor) { + return this.asHexString() === otherColor.asHexString(); + }; + + /** + * A color palette stores a set of colors and allows for different + * methods of color allocation. + * + * @constructor + */ + function ColorPalette() { + var allColors = this.allColors = COLOR_PALETTE.map(function (color) { + return new Color(color); + }); + this.colorGroups = [[], [], []]; + for (var i = 0; i < allColors.length; i++) { + this.colorGroups[i % 3].push(allColors[i]); + } + this.reset(); + } + + /** + * + */ + ColorPalette.prototype.groups = function () { + return this.colorGroups; + }; + + ColorPalette.prototype.reset = function () { + this.availableColors = this.allColors.slice(); + }; + + ColorPalette.prototype.remove = function (color) { + this.availableColors = this.availableColors.filter(function (c) { + return !c.equalTo(color); + }); + }; + + ColorPalette.prototype.return = function (color) { + if (isDefaultColor(color)) { + this.availableColors.unshift(color); + } + }; + + ColorPalette.prototype.getByHexString = function (hexString) { + var color = Color.fromHexString(hexString); + return color; + }; + + /** + * @returns {Color} the next unused color in the palette. If all colors + * have been allocated, it will wrap around. + */ + ColorPalette.prototype.getNextColor = function () { + if (!this.availableColors.length) { + console.warn('Color Palette empty, reusing colors!'); + this.reset(); + } + 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]; + }; + + + return { + Color: Color, + ColorPalette: ColorPalette + }; +}); diff --git a/src/plugins/plot/src/lib/eventHelpers.js b/src/plugins/plot/src/lib/eventHelpers.js new file mode 100644 index 0000000000..e464a8e33f --- /dev/null +++ b/src/plugins/plot/src/lib/eventHelpers.js @@ -0,0 +1,97 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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. + *****************************************************************************/ +/*jscs:disable disallowDanglingUnderscores */ + +define([ + +], function ( + +) { + + var helperFunctions = { + listenTo: function (object, event, callback, context) { + if (!this._listeningTo) { + this._listeningTo = []; + } + var listener = { + object: object, + event: event, + callback: callback, + context: context, + _cb: !!context ? callback.bind(context) : callback + }; + if (object.$watch && event.indexOf('change:') === 0) { + var scopePath = event.replace('change:', ''); + listener.unlisten = object.$watch(scopePath, listener._cb, true); + } else if (object.$on) { + listener.unlisten = object.$on(event, listener._cb); + } else if (object.addEventListener) { + object.addEventListener(event, listener._cb); + } else { + object.on(event, listener._cb); + } + this._listeningTo.push(listener); + }, + + stopListening: function (object, event, callback, context) { + if (!this._listeningTo) { + this._listeningTo = []; + } + + this._listeningTo.filter(function (listener) { + if (object && object !== listener.object) { + return false; + } + if (event && event !== listener.event) { + return false; + } + if (callback && callback !== listener.callback) { + return false; + } + if (context && context !== listener.context) { + return false; + } + return true; + }) + .map(function (listener) { + if (listener.unlisten) { + listener.unlisten(); + } else if (listener.object.removeEventListener) { + listener.object.removeEventListener(listener.event, listener._cb); + } else { + listener.object.off(listener.event, listener._cb); + } + return listener; + }) + .forEach(function (listener) { + this._listeningTo.splice(this._listeningTo.indexOf(listener), 1); + }, this); + }, + + extend: function (object) { + object.listenTo = helperFunctions.listenTo; + object.stopListening = helperFunctions.stopListening; + } + }; + + return helperFunctions; +}); diff --git a/src/plugins/plot/src/lib/extend.js b/src/plugins/plot/src/lib/extend.js new file mode 100644 index 0000000000..a8b7da3ccb --- /dev/null +++ b/src/plugins/plot/src/lib/extend.js @@ -0,0 +1,67 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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. + *****************************************************************************/ +/*jscs:disable disallowDanglingUnderscores */ + +define([ + +], function ( + +) { + + function extend(props) { + /*jshint validthis: true*/ + var parent = this, + child, + Surrogate; + + if (props && props.hasOwnProperty('constructor')) { + child = props.constructor; + } else { + child = function () { + return parent.apply(this, arguments); + }; + } + + Object.keys(parent).forEach(function copyStaticProperties(propKey) { + child[propKey] = parent[propKey]; + }); + + // Surrogate allows inheriting from parent without invoking constructor. + Surrogate = function () { + this.constructor = child; + }; + Surrogate.prototype = parent.prototype; + child.prototype = new Surrogate(); + + if (props) { + Object.keys(props).forEach(function copyInstanceProperties(key) { + child.prototype[key] = props[key]; + }); + } + + child.__super__ = parent.prototype; + + return child; + } + + return extend; +}); diff --git a/src/plugins/plot/src/plot/LinearScale.js b/src/plugins/plot/src/plot/LinearScale.js new file mode 100644 index 0000000000..1fa7a85d11 --- /dev/null +++ b/src/plugins/plot/src/plot/LinearScale.js @@ -0,0 +1,78 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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. + *****************************************************************************/ +/*jscs:disable disallowDanglingUnderscores */ + +define([ + +], function ( + +) { + + /** + * A scale has an input domain and an output range. It provides functions + * `scale` return the range value associated with a domain value. + * `invert` return the domain value associated with range value. + */ + function LinearScale(domain) { + this.domain(domain); + } + + LinearScale.prototype.domain = function (newDomain) { + if (newDomain) { + this._domain = newDomain; + this._domainDenominator = newDomain.max - newDomain.min; + } + return this._domain; + }; + LinearScale.prototype.range = function (newRange) { + if (newRange) { + this._range = newRange; + this._rangeDenominator = newRange.max - newRange.min; + } + return this._range; + }; + LinearScale.prototype.scale = function (domainValue) { + if (!this._domain || !this._range) { + return; + } + var domainOffset = domainValue - this._domain.min, + rangeFraction = domainOffset - this._domainDenominator, + rangeOffset = rangeFraction * this._rangeDenominator, + rangeValue = rangeOffset + this._range.min; + return rangeValue; + }; + LinearScale.prototype.invert = function (rangeValue) { + if (!this._domain || !this._range) { + return; + } + var rangeOffset = rangeValue - this._range.min, + domainFraction = rangeOffset / this._rangeDenominator, + domainOffset = domainFraction * this._domainDenominator, + domainValue = domainOffset + this._domain.min; + return domainValue; + }; + return LinearScale; +}); + +/** + * + */ diff --git a/src/plugins/plot/src/plot/MCTPlotController.js b/src/plugins/plot/src/plot/MCTPlotController.js new file mode 100644 index 0000000000..27268c62f2 --- /dev/null +++ b/src/plugins/plot/src/plot/MCTPlotController.js @@ -0,0 +1,339 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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([ + './LinearScale', + '../lib/eventHelpers' +], function ( + LinearScale, + eventHelpers +) { + + /** + * MCTPlotController handles user interactions with the plot canvas. + * It supports pan and zoom, implements zoom history, and supports locating + * values near the cursor. + */ + function MCTPlotController($scope, $element, $window) { + this.$scope = $scope; + this.$scope.config = this.config; + this.$scope.plot = this; + this.$element = $element; + this.$window = $window; + + this.xScale = new LinearScale(this.config.xAxis.get('displayRange')); + this.yScale = new LinearScale(this.config.yAxis.get('displayRange')); + + this.pan = undefined; + this.marquee = undefined; + + this.chartElementBounds = undefined; + this.tickUpdate = false; + + this.$scope.plotHistory = this.plotHistory = []; + this.listenTo(this.$scope, 'plot:clearHistory', this.clear, this); + + this.initialize(); + } + + MCTPlotController.$inject = ['$scope', '$element', '$window']; + + eventHelpers.extend(MCTPlotController.prototype); + + MCTPlotController.prototype.initialize = function () { + this.$canvas = this.$element.find('canvas'); + + this.listenTo(this.$canvas, 'mousemove', this.trackMousePosition, this); + this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this); + this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this); + + this.watchForMarquee(); + + this.listenTo(this.$window, 'keydown', this.toggleInteractionMode, this); + this.listenTo(this.$window, 'keyup', this.resetInteractionMode, this); + + this.$scope.rectangles = []; + this.$scope.tickWidth = 0; + + this.$scope.xAxis = this.config.xAxis; + this.$scope.yAxis = this.config.yAxis; + this.$scope.series = this.config.series.models; + this.$scope.legend = this.config.legend; + + this.listenTo(this.$scope, '$destroy', this.destroy, this); + this.listenTo(this.$scope, 'plot:tickWidth', this.onTickWidthChange, this); + this.listenTo(this.$scope, 'plot:highlight:set', this.onPlotHighlightSet, this); + + this.listenTo(this.config.xAxis, 'change:displayRange', this.onXAxisChange, this); + this.listenTo(this.config.yAxis, 'change:displayRange', this.onYAxisChange, this); + }; + + MCTPlotController.prototype.onXAxisChange = function (displayBounds) { + if (displayBounds) { + this.xScale.domain(displayBounds); + } + }; + + MCTPlotController.prototype.onYAxisChange = function (displayBounds) { + if (displayBounds) { + this.yScale.domain(displayBounds); + } + }; + + MCTPlotController.prototype.onTickWidthChange = function ($event, width) { + if ($event.targetScope.domainObject !== this.$scope.domainObject) { + // Always accept tick width if it comes from a different object. + this.$scope.tickWidth = width; + } else { + // Otherwise, only accept tick with if it's larger. + var newWidth = Math.max(width, this.$scope.tickWidth); + if (newWidth !== this.$scope.tickWidth) { + this.$scope.tickWidth = newWidth; + this.$scope.$digest(); + } + } + }; + + MCTPlotController.prototype.trackMousePosition = function ($event) { + this.trackChartElementBounds($event); + this.xScale.range({min: 0, max: this.chartElementBounds.width}); + this.yScale.range({min: 0, max: this.chartElementBounds.height}); + + this.positionOverElement = { + x: $event.clientX - this.chartElementBounds.left, + y: this.chartElementBounds.height - + ($event.clientY - this.chartElementBounds.top) + }; + + this.positionOverPlot = { + x: this.xScale.invert(this.positionOverElement.x), + y: this.yScale.invert(this.positionOverElement.y) + }; + + this.highlightValues(this.positionOverPlot.x); + this.updateMarquee(); + this.updatePan(); + this.$scope.$digest(); + $event.preventDefault(); + }; + + MCTPlotController.prototype.trackChartElementBounds = function ($event) { + if ($event.target === this.$canvas[1]) { + this.chartElementBounds = $event.target.getBoundingClientRect(); + } + }; + + MCTPlotController.prototype.onPlotHighlightSet = function ($e, point) { + if (point === this.highlightPoint) { + return; + } + this.highlightValues(point); + }; + + MCTPlotController.prototype.highlightValues = function (point) { + this.highlightPoint = point; + this.$scope.$emit('plot:highlight:update', point); + if (!point) { + this.$scope.highlights = []; + this.$scope.series.map(function (series) { + delete series.closest; + }); + } else { + this.$scope.highlights = this.$scope.series + .filter(function (series) { + return series.data.length > 0; + }).map(function (series) { + series.closest = series.nearestPoint(point); + return { + series: series, + point: series.closest + }; + }, this); + } + this.$scope.$digest(); + }; + + MCTPlotController.prototype.untrackMousePosition = function () { + this.positionOverElement = undefined; + this.positionOverPlot = undefined; + this.highlightValues(); + }; + + MCTPlotController.prototype.onMouseDown = function ($event) { + this.listenTo(this.$window, 'mouseup', this.onMouseUp, this); + this.listenTo(this.$window, 'mousemove', this.trackMousePosition, this); + if (this.allowPan) { + return this.startPan($event); + } + if (this.allowMarquee) { + return this.startMarquee($event); + } + }; + + MCTPlotController.prototype.onMouseUp = function ($event) { + this.stopListening(this.$window, 'mouseup', this.onMouseUp, this); + this.stopListening(this.$window, 'mousemove', this.trackMousePosition, this); + if (this.pan) { + this.endPan($event); + } + if (this.marquee) { + this.endMarquee($event); + } + this.$scope.$apply(); + }; + + MCTPlotController.prototype.updateMarquee = function () { + if (!this.marquee) { + return; + } + this.marquee.end = this.positionOverPlot; + }; + + MCTPlotController.prototype.startMarquee = function ($event) { + this.trackMousePosition($event); + if (this.positionOverPlot) { + this.freeze(); + this.marquee = { + start: this.positionOverPlot, + end: this.positionOverPlot, + color: [1, 1, 1, 0.5] + }; + this.$scope.rectangles.push(this.marquee); + this.trackHistory(); + } + }; + + MCTPlotController.prototype.endMarquee = function () { + if (this.marquee.start.x !== this.marquee.end.x && + this.marquee.start.y !== this.marquee.end.y) { + this.$scope.xAxis.set('displayRange', { + min: Math.min(this.marquee.start.x, this.marquee.end.x), + max: Math.max(this.marquee.start.x, this.marquee.end.x) + }); + this.$scope.yAxis.set('displayRange', { + min: Math.min(this.marquee.start.y, this.marquee.end.y), + max: Math.max(this.marquee.start.y, this.marquee.end.y) + }); + this.$scope.$emit('user:viewport:change:end'); + } + this.$scope.rectangles = []; + this.marquee = undefined; + }; + + MCTPlotController.prototype.startPan = function ($event) { + this.trackMousePosition($event); + this.freeze(); + this.pan = { + start: this.positionOverPlot + }; + $event.preventDefault(); + this.trackHistory(); + return false; + }; + + MCTPlotController.prototype.updatePan = function () { + // calculate offset between points. Apply that offset to viewport. + if (!this.pan) { + return; + } + var dX = this.pan.start.x - this.positionOverPlot.x, + dY = this.pan.start.y - this.positionOverPlot.y, + xRange = this.config.xAxis.get('displayRange'), + yRange = this.config.yAxis.get('displayRange'); + + this.config.xAxis.set('displayRange', { + min: xRange.min + dX, + max: xRange.max + dX + }); + this.config.yAxis.set('displayRange', { + min: yRange.min + dY, + max: yRange.max + dY + }); + }; + + MCTPlotController.prototype.trackHistory = function () { + this.plotHistory.push({ + x: this.config.xAxis.get('displayRange'), + y: this.config.yAxis.get('displayRange') + }); + }; + + MCTPlotController.prototype.endPan = function () { + this.pan = undefined; + this.$scope.$emit('user:viewport:change:end'); + }; + + MCTPlotController.prototype.watchForMarquee = function () { + this.$canvas.removeClass('plot-drag'); + this.$canvas.addClass('plot-marquee'); + this.allowPan = false; + this.allowMarquee = true; + }; + + MCTPlotController.prototype.watchForPan = function () { + this.$canvas.addClass('plot-drag'); + this.$canvas.removeClass('plot-marquee'); + this.allowPan = true; + this.allowMarquee = false; + }; + + MCTPlotController.prototype.toggleInteractionMode = function (event) { + if (event.keyCode === 18) { // control key. + this.watchForPan(); + } + }; + + MCTPlotController.prototype.resetInteractionMode = function (event) { + if (event.keyCode === 18) { + this.watchForMarquee(); + } + }; + + MCTPlotController.prototype.freeze = function () { + this.config.yAxis.set('frozen', true); + this.config.xAxis.set('frozen', true); + }; + + MCTPlotController.prototype.clear = function () { + this.config.yAxis.set('frozen', false); + this.config.xAxis.set('frozen', false); + this.$scope.plotHistory = this.plotHistory = []; + this.$scope.$emit('user:viewport:change:end'); + }; + + MCTPlotController.prototype.back = function () { + var previousAxisRanges = this.plotHistory.pop(); + if (this.plotHistory.length === 0) { + this.clear(); + return; + } + this.config.xAxis.set('displayRange', previousAxisRanges.x); + this.config.yAxis.set('displayRange', previousAxisRanges.y); + this.$scope.$emit('user:viewport:change:end'); + }; + + MCTPlotController.prototype.destroy = function () { + this.stopListening(); + }; + + return MCTPlotController; +}); diff --git a/src/plugins/plot/src/plot/MCTPlotDirective.js b/src/plugins/plot/src/plot/MCTPlotDirective.js new file mode 100644 index 0000000000..2d1e155e1b --- /dev/null +++ b/src/plugins/plot/src/plot/MCTPlotDirective.js @@ -0,0 +1,46 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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([ + './MCTPlotController', + 'text!../../res/templates/mct-plot.html' +], function ( + MCTPlotController, + PlotTemplate +) { + + function MCTPlot() { + + return { + restrict: "E", + template: PlotTemplate, + controller: MCTPlotController, + controllerAs: 'mctPlotController', + bindToController: { + config: "=" + }, + scope: true + }; + } + + return MCTPlot; +}); diff --git a/src/plugins/plot/src/plot/MCTTicksController.js b/src/plugins/plot/src/plot/MCTTicksController.js new file mode 100644 index 0000000000..5ae128cd12 --- /dev/null +++ b/src/plugins/plot/src/plot/MCTTicksController.js @@ -0,0 +1,248 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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([ + 'lodash', + '../lib/eventHelpers' +], function ( + _, + eventHelpers +) { + + var e10 = Math.sqrt(50), + e5 = Math.sqrt(10), + e2 = Math.sqrt(2); + + /** + * Nicely formatted tick steps from d3-array. + */ + function tickStep(start, stop, count) { + var step0 = Math.abs(stop - start) / Math.max(0, count), + step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10)), + error = step0 / step1; + if (error >= e10) { + step1 *= 10; + } else if (error >= e5) { + step1 *= 5; + } else if (error >= e2) { + step1 *= 2; + } + return stop < start ? -step1 : step1; + } + + /** + * Find the precision (number of decimals) of a step. Used to round + * ticks to precise values. + */ + function getPrecision(step) { + var exponential = step.toExponential(), + i = exponential.indexOf('e'); + if (i === -1) { + return 0; + } + + var precision = Math.max(0, -(+exponential.slice(i + 1))); + + if (precision > 20) { + precision = 20; + } + + return precision; + } + + + /** + * Linear tick generation from d3-array. + */ + function ticks(start, stop, count) { + var step = tickStep(start, stop, count), + precision = getPrecision(step); + return _.range( + Math.ceil(start / step) * step, + Math.floor(stop / step) * step + step / 2, // inclusive + step + ).map(function round(tick) { + return +tick.toFixed(precision); + }); + } + + function commonPrefix(a, b) { + var maxLen = Math.min(a.length, b.length); + var breakpoint = 0; + for (var i = 0; i < maxLen; i++) { + if (a[i] !== b[i]) { + break; + } + if (a[i] === ' ') { + breakpoint = i + 1; + } + } + return a.slice(0, breakpoint); + } + + function commonSuffix(a, b) { + var maxLen = Math.min(a.length, b.length); + var breakpoint = 0; + for (var i = 0; i <= maxLen; i++) { + if (a[a.length - i] !== b[b.length - i]) { + break; + } + if ('. '.indexOf(a[a.length - i]) !== -1) { + breakpoint = i; + } + } + return a.slice(a.length - breakpoint); + } + + function MCTTicksController($scope, $element) { + this.$scope = $scope; + this.$element = $element; + + this.tickCount = 4; + this.tickUpdate = false; + this.listenTo(this.axis, 'change:displayRange', this.updateTicks, this); + this.listenTo(this.axis, 'change:format', this.updateTicks, this); + this.listenTo(this.$scope, '$destroy', this.stopListening, this); + this.updateTicks(); + } + + MCTTicksController.$inject = ['$scope', '$element']; + + eventHelpers.extend(MCTTicksController.prototype); + + /** + * Determine whether ticks should be regenerated for a given range. + * Ticks are updated a) if they don't exist, b) if the existing ticks are + * outside of given range, or c) if the range exceeds the size of the tick + * range by more than one tick step. + * @private + */ + MCTTicksController.prototype.shouldRegenerateTicks = function (range) { + if (!this.tickRange || !this.$scope.ticks || !this.$scope.ticks.length) { + return true; + } + if (this.tickRange.max > range.max || this.tickRange.min < range.min) { + return true; + } + if (Math.abs(range.max - this.tickRange.max) > this.tickRange.step) { + return true; + } + if (Math.abs(this.tickRange.min - range.min) > this.tickRange.step) { + return true; + } + return false; + }; + + MCTTicksController.prototype.getTicks = function () { + var number = this.tickCount; + var clampRange = this.axis.get('values'); + var range = this.axis.get('displayRange'); + if (clampRange) { + return clampRange.filter(function (value) { + return value <= range.max && value >= range.min; + }, this); + } + return ticks(range.min, range.max, number); + }; + + MCTTicksController.prototype.updateTicks = function () { + var range = this.axis.get('displayRange'); + if (!range) { + delete this.$scope.min; + delete this.$scope.max; + delete this.$scope.interval; + delete this.tickRange; + delete this.$scope.ticks; + delete this.shouldCheckWidth; + return; + } + var format = this.axis.get('format'); + if (!format) { + return; + } + this.$scope.min = range.min; + this.$scope.max = range.max; + this.$scope.interval = Math.abs(range.min - range.max); + if (this.shouldRegenerateTicks(range)) { + var newTicks = this.getTicks(); + this.tickRange = { + min: Math.min.apply(Math, newTicks), + max: Math.max.apply(Math, newTicks), + step: newTicks[1] - newTicks[0] + }; + + newTicks = newTicks + .map(function (tickValue) { + return { + value: tickValue, + text: format(tickValue) + }; + }, this); + + if (newTicks.length && typeof newTicks[0].text === 'string') { + var tickText = newTicks.map(function (t) { + return t.text; + }); + var prefix = tickText.reduce(commonPrefix); + var suffix = tickText.reduce(commonSuffix); + newTicks.forEach(function (t, i) { + t.fullText = t.text; + if (suffix.length) { + t.text = t.text.slice(prefix.length, -suffix.length); + } else { + t.text = t.text.slice(prefix.length); + } + }); + } + this.$scope.ticks = newTicks; + this.shouldCheckWidth = true; + } + this.scheduleTickUpdate(); + }; + + MCTTicksController.prototype.scheduleTickUpdate = function () { + if (this.tickUpdate) { + return; + } + this.tickUpdate = true; + setTimeout(this.doTickUpdate.bind(this), 0); + }; + + MCTTicksController.prototype.doTickUpdate = function () { + if (this.shouldCheckWidth) { + this.$scope.$digest(); + var element = this.$element[0], + tickElements = element.querySelectorAll('.gl-plot-tick > span'), + tickWidth = Number([].reduce.call(tickElements, function (memo, first) { + return Math.max(memo, first.offsetWidth); + }, 0)); + + this.$scope.tickWidth = tickWidth; + this.$scope.$emit('plot:tickWidth', tickWidth); + this.shouldCheckWidth = false; + } + this.$scope.$digest(); + this.tickUpdate = false; + }; + + return MCTTicksController; +}); diff --git a/src/plugins/plot/src/plot/MCTTicksDirective.js b/src/plugins/plot/src/plot/MCTTicksDirective.js new file mode 100644 index 0000000000..3e849cf0eb --- /dev/null +++ b/src/plugins/plot/src/plot/MCTTicksDirective.js @@ -0,0 +1,43 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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([ + './MCTTicksController' +], function ( + MCTTicksController +) { + + function MCTTicksDirective() { + return { + priority: 1000, + restrict: "E", + scope: true, + controllerAs: 'ticksController', + controller: MCTTicksController, + bindToController: { + axis: '=' + } + }; + } + + return MCTTicksDirective; +}); diff --git a/platform/features/plot/src/services/ExportImageService.js b/src/plugins/plot/src/services/ExportImageService.js similarity index 75% rename from platform/features/plot/src/services/ExportImageService.js rename to src/plugins/plot/src/services/ExportImageService.js index 7738d47224..5c3a6bd2c6 100644 --- a/platform/features/plot/src/services/ExportImageService.js +++ b/src/plugins/plot/src/services/ExportImageService.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2017, United States Government + * Open MCT, Copyright (c) 2014-2018, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -32,7 +32,6 @@ define( html2canvas, saveAs ) { - var self = this; /** * The export image service will export any HTML node to @@ -43,15 +42,15 @@ define( * @param {constant} EXPORT_IMAGE_TIMEOUT time in milliseconds before a timeout error is returned * @constructor */ - function ExportImageService($q, $timeout, $log, EXPORT_IMAGE_TIMEOUT, injHtml2Canvas, injSaveAs, injFileReader, injChangeBackgroundColor) { - self.$q = $q; - self.$timeout = $timeout; - self.$log = $log; - self.EXPORT_IMAGE_TIMEOUT = EXPORT_IMAGE_TIMEOUT; - self.html2canvas = injHtml2Canvas || html2canvas; - self.saveAs = injSaveAs || saveAs; - self.reader = injFileReader || new FileReader(); - self.changeBackgroundColor = injChangeBackgroundColor || self.changeBackgroundColor; + function ExportImageService($q, $timeout, $log) { + this.$q = $q; + this.$timeout = $timeout; + this.$log = $log; + this.EXPORT_IMAGE_TIMEOUT = 1000; + } + + function changeBackgroundColor(element, color) { + element.style.backgroundColor = color; } /** @@ -61,42 +60,42 @@ define( * @param {string} type of image to convert the element to * @returns {promise} */ - function renderElement(element, type, color) { - var defer = self.$q.defer(), + ExportImageService.prototype.renderElement = function (element, type, color) { + var defer = this.$q.defer(), validTypes = ["png", "jpg", "jpeg"], renderTimeout, originalColor; if (validTypes.indexOf(type) === -1) { - self.$log.error("Invalid type requested. Try: (" + validTypes.join(",") + ")"); + this.$log.error("Invalid type requested. Try: (" + validTypes.join(",") + ")"); return; } if (color) { // Save color to be restored later originalColor = element.style.backgroundColor || ''; - // Defaulting to white so we can see the chart when printed - self.changeBackgroundColor(element, color); + changeBackgroundColor(element, color); } - renderTimeout = self.$timeout(function () { + renderTimeout = this.$timeout(function () { defer.reject("html2canvas timed out"); - self.$log.warn("html2canvas timed out"); - }, self.EXPORT_IMAGE_TIMEOUT); + this.$log.warn("html2canvas timed out"); + }.bind(this), this.EXPORT_IMAGE_TIMEOUT); try { - self.html2canvas(element, { + html2canvas(element, { onrendered: function (canvas) { if (color) { - self.changeBackgroundColor(element, originalColor); + changeBackgroundColor(element, originalColor); } - switch (type.toLowerCase()) { case "png": canvas.toBlob(defer.resolve, "image/png"); break; + default: + case "jpg": case "jpeg": canvas.toBlob(defer.resolve, "image/jpeg"); break; @@ -105,19 +104,42 @@ define( }); } catch (e) { defer.reject(e); - self.$log.warn("html2canvas failed with error: " + e); + this.$log.warn("html2canvas failed with error: " + e); } defer.promise.finally(function () { renderTimeout.cancel(); - if (color) { - self.changeBackgroundColor(element, originalColor); + changeBackgroundColor(element, originalColor); } }); return defer.promise; - } + }; + + /** + * Takes a screenshot of a DOM node and exports to JPG. + * @param {node} element to be exported + * @param {string} filename the exported image + * @returns {promise} + */ + ExportImageService.prototype.exportJPG = function (element, filename, color) { + return this.renderElement(element, "jpeg", color).then(function (img) { + saveAs(img, filename); + }); + }; + + /** + * Takes a screenshot of a DOM node and exports to PNG. + * @param {node} element to be exported + * @param {string} filename the exported image + * @returns {promise} + */ + ExportImageService.prototype.exportPNG = function (element, filename, color) { + return this.renderElement(element, "png", color).then(function (img) { + saveAs(img, filename); + }); + }; /** * canvas.toBlob() not supported in IE < 10, Opera, and Safari. This polyfill @@ -143,37 +165,6 @@ define( } } - /** - * @private - */ - self.changeBackgroundColor = function (element, color) { - element.style.backgroundColor = color; - }; - - /** - * Takes a screenshot of a DOM node and exports to JPG. - * @param {node} element to be exported - * @param {string} filename the exported image - * @returns {promise} - */ - ExportImageService.prototype.exportJPG = function (element, filename, color) { - return renderElement(element, "jpeg", color).then(function (img) { - self.saveAs(img, filename); - }); - }; - - /** - * Takes a screenshot of a DOM node and exports to PNG. - * @param {node} element to be exported - * @param {string} filename the exported image - * @returns {promise} - */ - ExportImageService.prototype.exportPNG = function (element, filename, color) { - return renderElement(element, "png", color).then(function (img) { - self.saveAs(img, filename); - }); - }; - polyfillToBlob(); return ExportImageService; diff --git a/src/plugins/plot/src/telemetry/MCTOverlayPlot.js b/src/plugins/plot/src/telemetry/MCTOverlayPlot.js new file mode 100644 index 0000000000..dcebe96d09 --- /dev/null +++ b/src/plugins/plot/src/telemetry/MCTOverlayPlot.js @@ -0,0 +1,37 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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([ + 'text!../../res/templates/plot.html' +], function ( + PlotTemplate +) { + return function MCTOverlayPlot() { + return { + restrict: "E", + template: PlotTemplate, + scope: { + domainObject: "=" + } + }; + }; +}); diff --git a/src/plugins/plot/src/telemetry/PlotController.js b/src/plugins/plot/src/telemetry/PlotController.js new file mode 100644 index 0000000000..e3ca0d34bc --- /dev/null +++ b/src/plugins/plot/src/telemetry/PlotController.js @@ -0,0 +1,239 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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. + *****************************************************************************/ + +/*jscs:disable disallowDanglingUnderscores */ + +define([ + 'lodash', + '../configuration/PlotConfigurationModel', + '../configuration/configStore', + '../lib/eventHelpers' +], function ( + _, + PlotConfigurationModel, + configStore, + eventHelpers +) { + + /** + TODO: Need to separate off plot configuration and specifying of defaults, + is part of onDomainObjectChange as it can be triggered by mutation. + */ + + /** + * Controller for a plot. + * + * @constructor. + */ + function PlotController( + $scope, + $element, + formatService, + openmct, + objectService, + exportImageService + ) { + + this.$scope = $scope; + this.$element = $element; + this.formatService = formatService; + this.openmct = openmct; + this.objectService = objectService; + this.exportImageService = exportImageService; + + $scope.pending = 0; + + this.listenTo($scope, 'user:viewport:change:end', this.onUserViewportChangeEnd, this); + this.listenTo($scope, '$destroy', this.destroy, this); + + this.config = this.getConfig(this.$scope.domainObject); + this.listenTo(this.config.series, 'add', this.addSeries, this); + this.listenTo(this.config.series, 'remove', this.removeSeries, this); + this.config.series.forEach(this.addSeries, this); + + this.followTimeConductor(); + } + + eventHelpers.extend(PlotController.prototype); + + PlotController.prototype.followTimeConductor = function () { + this.listenTo(this.openmct.time, 'bounds', this.updateDisplayBounds, this); + this.listenTo(this.openmct.time, 'timeSystem', this.onTimeSystemChange, this); + this.synchronized(true); + }; + + PlotController.prototype.loadSeriesData = function (series) { + this.startLoading(); + var options = { + size: this.$element[0].offsetWidth, + domain: this.config.xAxis.get('key') + }; + + series.load(options) + .then(this.stopLoading.bind(this)); + }; + + PlotController.prototype.addSeries = function (series) { + this.listenTo(series, 'change:yKey', function () { + this.loadSeriesData(series); + }, this); + this.loadSeriesData(series); + }; + + PlotController.prototype.removeSeries = function (plotSeries) { + this.stopListening(plotSeries); + }; + + PlotController.prototype.getConfig = function (domainObject) { + var configId = domainObject.getId(); + var config = configStore.get(configId); + if (!config) { + var newDomainObject = domainObject.useCapability('adapter'); + config = new PlotConfigurationModel({ + id: configId, + domainObject: newDomainObject, + openmct: this.openmct + }); + configStore.add(configId, config); + } + configStore.track(configId); + return config; + }; + + PlotController.prototype.onTimeSystemChange = function (timeSystem) { + this.config.xAxis.set('key', timeSystem.key); + }; + + PlotController.prototype.destroy = function () { + configStore.untrack(this.config.id); + this.stopListening(); + }; + + PlotController.prototype.loadMoreData = function (range, purge) { + this.config.series.map(function (plotSeries) { + this.startLoading(); + plotSeries.load({ + size: this.$element[0].offsetWidth, + start: range.min, + end: range.max + }) + .then(this.stopLoading.bind(this)); + if (purge) { + plotSeries.purgeRecordsOutsideRange(range); + } + }, this); + }; + + /** + * Track latest display bounds. Forces update when not receiving ticks. + */ + PlotController.prototype.updateDisplayBounds = function (bounds, isTick) { + var newRange = { + min: bounds.start, + max: bounds.end + }; + this.config.xAxis.set('range', newRange); + if (!isTick) { + this.$scope.$broadcast('plot:clearHistory'); + this.loadMoreData(newRange, true); + } else { + // Drop any data that is more than 1x (max-min) before min. + // Limit these purges to once a second. + if (!this.nextPurge || this.nextPurge < Date.now()) { + var keepRange = { + min: newRange.min - (newRange.max - newRange.min), + max: newRange.max + }; + this.config.series.forEach(function (series) { + series.purgeRecordsOutsideRange(keepRange); + }); + this.nextPurge = Date.now() + 1000; + } + } + }; + + PlotController.prototype.startLoading = function () { + this.$scope.pending += 1; + }; + + PlotController.prototype.stopLoading = function () { + this.$scope.pending -= 1; + }; + + /** + * Getter/setter for "synchronized" value. If not synchronized and + * time conductor is in clock mode, will mark objects as unsynced so that + * displays can update accordingly. + * @private + */ + PlotController.prototype.synchronized = function (value) { + if (typeof value !== 'undefined') { + this._synchronized = value; + var isUnsynced = !value && this.openmct.time.clock(); + if (this.$scope.domainObject.getCapability('status')) { + this.$scope.domainObject.getCapability('status') + .set('timeconductor-unsynced', isUnsynced); + } + } + return this._synchronized; + }; + + /** + * Handle end of user viewport change: load more data for current display + * bounds, and mark view as synchronized if bounds match configured bounds. + * @private + */ + PlotController.prototype.onUserViewportChangeEnd = function () { + var xDisplayRange = this.config.xAxis.get('displayRange'); + var xRange = this.config.xAxis.get('range'); + + this.loadMoreData(xDisplayRange); + + this.synchronized(xRange.min === xDisplayRange.min && + xRange.max === xDisplayRange.max); + }; + + /** + * Export view as JPG. + */ + PlotController.prototype.exportJPG = function () { + this.hideExportButtons = true; + this.exportImageService.exportJPG(this.$element[0], 'plot.jpg', 'white') + .finally(function () { + this.hideExportButtons = false; + }.bind(this)); + }; + + /** + * Export view as PNG. + */ + PlotController.prototype.exportPNG = function () { + this.hideExportButtons = true; + this.exportImageService.exportPNG(this.$element[0], 'plot.png', 'white') + .finally(function () { + this.hideExportButtons = false; + }.bind(this)); + }; + + return PlotController; + +}); diff --git a/src/plugins/plot/src/telemetry/StackedPlotController.js b/src/plugins/plot/src/telemetry/StackedPlotController.js new file mode 100644 index 0000000000..95b3513b1d --- /dev/null +++ b/src/plugins/plot/src/telemetry/StackedPlotController.js @@ -0,0 +1,146 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2018, 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([ + 'lodash' +], function ( + _ +) { + + function StackedPlotController($scope, openmct, objectService, $element, exportImageService) { + var tickWidth = 0, + newFormatObject, + composition, + currentRequest, + unlisten, + tickWidthMap = {}; + + this.$element = $element; + this.exportImageService = exportImageService; + + $scope.telemetryObjects = []; + + function oldId(newIdentifier) { + var idParts = []; + if (newIdentifier.namespace) { + idParts.push(newIdentifier.namespace.replace(/\:/g, '\\:')); + } + idParts.push(newIdentifier.key); + return idParts.join(':'); + } + + function onDomainObjectChange(domainObject) { + var thisRequest = { + pending: 0 + }; + currentRequest = thisRequest; + $scope.currentRequest = thisRequest; + var telemetryObjects = $scope.telemetryObjects = []; + var thisTickWidthMap = {}; + tickWidthMap = thisTickWidthMap; + + if (unlisten) { + unlisten(); + unlisten = undefined; + } + + function addChild(child) { + var id = oldId(child.identifier); + thisTickWidthMap[id] = 0; + thisRequest.pending += 1; + objectService.getObjects([id]) + .then(function (objects) { + thisRequest.pending -= 1; + var childObj = objects[id]; + telemetryObjects.push(childObj); + }); + } + + function removeChild(childIdentifier) { + var id = oldId(childIdentifier); + delete thisTickWidthMap[id]; + var childObj = telemetryObjects.filter(function (c) { + return c.getId() === id; + })[0]; + if (childObj) { + var index = telemetryObjects.indexOf(childObj); + telemetryObjects.splice(index, 1); + $scope.$broadcast('plot:tickWidth', _.max(tickWidthMap)); + } + } + thisRequest.pending += 1; + openmct.objects.get(domainObject.getId()) + .then(function (obj) { + thisRequest.pending -= 1; + if (thisRequest !== currentRequest) { + return; + } + newFormatObject = obj; + composition = openmct.composition.get(obj); + composition.on('add', addChild); + composition.on('remove', removeChild); + composition.load(); + unlisten = function () { + composition.off('add', addChild); + composition.off('remove', removeChild); + }; + }); + } + + $scope.$watch('domainObject', onDomainObjectChange); + + $scope.$on('plot:tickWidth', function ($e, width) { + var plotId = $e.targetScope.domainObject.getId(); + if (!tickWidthMap.hasOwnProperty(plotId)) { + return; + } + tickWidthMap[plotId] = Math.max(width, tickWidthMap[plotId]); + var newTickWidth = _.max(tickWidthMap); + if (newTickWidth !== tickWidth || width !== tickWidth) { + tickWidth = newTickWidth; + $scope.$broadcast('plot:tickWidth', tickWidth); + } + }); + + $scope.$on('plot:highlight:update', function ($e, point) { + $scope.$broadcast('plot:highlight:set', point); + }); + } + + StackedPlotController.prototype.exportJPG = function () { + this.hideExportButtons = true; + this.exportImageService.exportJPG(this.$element[0], 'stacked-plot.jpg') + .finally(function () { + this.hideExportButtons = false; + }.bind(this)); + }; + + StackedPlotController.prototype.exportPNG = function () { + this.hideExportButtons = true; + this.exportImageService.exportPNG(this.$element[0], 'stacked-plot.png') + .finally(function () { + this.hideExportButtons = false; + }.bind(this)); + }; + + return StackedPlotController; +}); diff --git a/src/plugins/plugins.js b/src/plugins/plugins.js index efa857f263..0deb2f5497 100644 --- a/src/plugins/plugins.js +++ b/src/plugins/plugins.js @@ -30,7 +30,8 @@ define([ '../../platform/import-export/bundle', './summaryWidget/plugin', './URLIndicatorPlugin/URLIndicatorPlugin', - './telemetryMean/plugin' + './telemetryMean/plugin', + './plot/plugin' ], function ( _, UTCTimeSystem, @@ -41,7 +42,8 @@ define([ ImportExport, SummaryWidget, URLIndicatorPlugin, - TelemetryMean + TelemetryMean, + PlotPlugin ) { var bundleMap = { CouchDB: 'platform/persistence/couch', @@ -126,6 +128,8 @@ define([ }; plugins.ExampleImagery = ExampleImagery; + plugins.Plot = PlotPlugin; + plugins.SummaryWidget = SummaryWidget; plugins.TelemetryMean = TelemetryMean; plugins.URLIndicatorPlugin = URLIndicatorPlugin;