mirror of
https://github.com/nasa/openmct.git
synced 2025-06-28 19:53:02 +00:00
Compare commits
1 Commits
plot-sync
...
compositio
Author | SHA1 | Date | |
---|---|---|---|
195f48a267 |
@ -30,8 +30,7 @@ define([
|
|||||||
amplitude: 1,
|
amplitude: 1,
|
||||||
period: 10,
|
period: 10,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
dataRateInHz: 1,
|
dataRateInHz: 1
|
||||||
phase: 0
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function GeneratorProvider() {
|
function GeneratorProvider() {
|
||||||
@ -51,12 +50,9 @@ define([
|
|||||||
'amplitude',
|
'amplitude',
|
||||||
'period',
|
'period',
|
||||||
'offset',
|
'offset',
|
||||||
'dataRateInHz',
|
'dataRateInHz'
|
||||||
'phase',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
request = request || {};
|
|
||||||
|
|
||||||
var workerRequest = {};
|
var workerRequest = {};
|
||||||
|
|
||||||
props.forEach(function (prop) {
|
props.forEach(function (prop) {
|
||||||
@ -71,7 +67,7 @@ define([
|
|||||||
}
|
}
|
||||||
workerRequest[prop] = Number(workerRequest[prop]);
|
workerRequest[prop] = Number(workerRequest[prop]);
|
||||||
});
|
});
|
||||||
workerRequest.name = domainObject.name;
|
|
||||||
return workerRequest;
|
return workerRequest;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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 (
|
|
||||||
|
|
||||||
) {
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
});
|
|
@ -62,11 +62,10 @@
|
|||||||
self.postMessage({
|
self.postMessage({
|
||||||
id: message.id,
|
id: message.id,
|
||||||
data: {
|
data: {
|
||||||
name: data.name,
|
|
||||||
utc: nextStep,
|
utc: nextStep,
|
||||||
yesterday: nextStep - 60*60*24*1000,
|
yesterday: nextStep - 60*60*24*1000,
|
||||||
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase),
|
sin: sin(nextStep, data.period, data.amplitude, data.offset),
|
||||||
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase)
|
cos: cos(nextStep, data.period, data.amplitude, data.offset)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
nextStep += step;
|
nextStep += step;
|
||||||
@ -83,22 +82,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onRequest(message) {
|
function onRequest(message) {
|
||||||
var request = message.data;
|
var data = message.data;
|
||||||
if (request.end == undefined) {
|
if (data.end == undefined) {
|
||||||
request.end = Date.now();
|
data.end = Date.now();
|
||||||
}
|
}
|
||||||
if (request.start == undefined){
|
if (data.start == undefined){
|
||||||
request.start = request.end - FIFTEEN_MINUTES;
|
data.start = data.end - FIFTEEN_MINUTES;
|
||||||
}
|
}
|
||||||
|
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
var start = request.start;
|
var start = data.start;
|
||||||
var end = request.end > now ? now : request.end;
|
var end = data.end > now ? now : data.end;
|
||||||
var amplitude = request.amplitude;
|
var amplitude = data.amplitude;
|
||||||
var period = request.period;
|
var period = data.period;
|
||||||
var offset = request.offset;
|
var offset = data.offset;
|
||||||
var dataRateInHz = request.dataRateInHz;
|
var dataRateInHz = data.dataRateInHz;
|
||||||
var phase = request.phase;
|
|
||||||
|
|
||||||
var step = 1000 / dataRateInHz;
|
var step = 1000 / dataRateInHz;
|
||||||
var nextStep = start - (start % step) + step;
|
var nextStep = start - (start % step) + step;
|
||||||
@ -107,11 +105,10 @@
|
|||||||
|
|
||||||
for (; nextStep < end && data.length < 5000; nextStep += step) {
|
for (; nextStep < end && data.length < 5000; nextStep += step) {
|
||||||
data.push({
|
data.push({
|
||||||
name: request.name,
|
|
||||||
utc: nextStep,
|
utc: nextStep,
|
||||||
yesterday: nextStep - 60*60*24*1000,
|
yesterday: nextStep - 60*60*24*1000,
|
||||||
sin: sin(nextStep, period, amplitude, offset, phase),
|
sin: sin(nextStep, period, amplitude, offset),
|
||||||
cos: cos(nextStep, period, amplitude, offset, phase)
|
cos: cos(nextStep, period, amplitude, offset)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
@ -120,14 +117,14 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function cos(timestamp, period, amplitude, offset, phase) {
|
function cos(timestamp, period, amplitude, offset) {
|
||||||
return amplitude *
|
return amplitude *
|
||||||
Math.cos(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset;
|
Math.cos(timestamp / period / 1000 * Math.PI * 2) + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sin(timestamp, period, amplitude, offset, phase) {
|
function sin(timestamp, period, amplitude, offset) {
|
||||||
return amplitude *
|
return amplitude *
|
||||||
Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset;
|
Math.sin(timestamp / period / 1000 * Math.PI * 2) + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendError(error, message) {
|
function sendError(error, message) {
|
||||||
|
@ -23,12 +23,10 @@
|
|||||||
|
|
||||||
define([
|
define([
|
||||||
"./GeneratorProvider",
|
"./GeneratorProvider",
|
||||||
"./SinewaveLimitCapability",
|
"./SinewaveLimitCapability"
|
||||||
"./StateGeneratorProvider"
|
|
||||||
], function (
|
], function (
|
||||||
GeneratorProvider,
|
GeneratorProvider,
|
||||||
SinewaveLimitCapability,
|
SinewaveLimitCapability
|
||||||
StateGeneratorProvider
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
var legacyExtensions = {
|
var legacyExtensions = {
|
||||||
@ -48,75 +46,6 @@ define([
|
|||||||
openmct.legacyExtension(type, extension)
|
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", {
|
openmct.types.addType("generator", {
|
||||||
name: "Sine Wave Generator",
|
name: "Sine Wave Generator",
|
||||||
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
||||||
@ -170,18 +99,6 @@ define([
|
|||||||
"dataRateInHz"
|
"dataRateInHz"
|
||||||
],
|
],
|
||||||
pattern: "^\\d*(\\.\\d*)?$"
|
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) {
|
initialize: function (object) {
|
||||||
@ -190,12 +107,7 @@ define([
|
|||||||
amplitude: 1,
|
amplitude: 1,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
dataRateInHz: 1,
|
dataRateInHz: 1,
|
||||||
phase: 0,
|
|
||||||
values: [
|
values: [
|
||||||
{
|
|
||||||
key: "name",
|
|
||||||
name: "Name"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: "utc",
|
key: "utc",
|
||||||
name: "Time",
|
name: "Time",
|
||||||
@ -230,7 +142,6 @@ define([
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
openmct.telemetry.addProvider(new GeneratorProvider());
|
openmct.telemetry.addProvider(new GeneratorProvider());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -48,9 +48,8 @@ define([
|
|||||||
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg"
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg"
|
||||||
];
|
];
|
||||||
|
|
||||||
function pointForTimestamp(timestamp, name) {
|
function pointForTimestamp(timestamp) {
|
||||||
return {
|
return {
|
||||||
name: name,
|
|
||||||
utc: Math.floor(timestamp / 5000) * 5000,
|
utc: Math.floor(timestamp / 5000) * 5000,
|
||||||
url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length]
|
url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length]
|
||||||
};
|
};
|
||||||
@ -62,7 +61,7 @@ define([
|
|||||||
},
|
},
|
||||||
subscribe: function (domainObject, callback) {
|
subscribe: function (domainObject, callback) {
|
||||||
var interval = setInterval(function () {
|
var interval = setInterval(function () {
|
||||||
callback(pointForTimestamp(Date.now(), domainObject.name));
|
callback(pointForTimestamp(Date.now()));
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
return function (interval) {
|
return function (interval) {
|
||||||
@ -80,8 +79,8 @@ define([
|
|||||||
var start = options.start;
|
var start = options.start;
|
||||||
var end = options.end;
|
var end = options.end;
|
||||||
var data = [];
|
var data = [];
|
||||||
while (start <= end && data.length < 5000) {
|
while (start < end && data.length < 5000) {
|
||||||
data.push(pointForTimestamp(start, domainObject.name));
|
data.push(pointForTimestamp(start));
|
||||||
start += 5000;
|
start += 5000;
|
||||||
}
|
}
|
||||||
return Promise.resolve(data);
|
return Promise.resolve(data);
|
||||||
@ -94,7 +93,7 @@ define([
|
|||||||
options.strategy === 'latest';
|
options.strategy === 'latest';
|
||||||
},
|
},
|
||||||
request: function (domainObject, options) {
|
request: function (domainObject, options) {
|
||||||
return Promise.resolve([pointForTimestamp(Date.now(), domainObject.name)]);
|
return Promise.resolve([pointForTimestamp(Date.now())]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -110,10 +109,6 @@ define([
|
|||||||
initialize: function (object) {
|
initialize: function (object) {
|
||||||
object.telemetry = {
|
object.telemetry = {
|
||||||
values: [
|
values: [
|
||||||
{
|
|
||||||
name: 'Name',
|
|
||||||
key: 'name'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'Time',
|
name: 'Time',
|
||||||
key: 'utc',
|
key: 'utc',
|
||||||
|
@ -58,7 +58,11 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.w-mct-example > div { margin-bottom: $interiorMarginLg; }
|
.w-mct-example {
|
||||||
|
div {
|
||||||
|
margin-bottom: $interiorMarginLg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
code,
|
code,
|
||||||
pre {
|
pre {
|
||||||
|
@ -89,8 +89,7 @@ module.exports = function(config) {
|
|||||||
"dist/reports/coverage",
|
"dist/reports/coverage",
|
||||||
check: {
|
check: {
|
||||||
global: {
|
global: {
|
||||||
lines: 80,
|
lines: 80
|
||||||
excludes: ['src/plugins/plot/**/*.js']
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -101,7 +101,6 @@ define([
|
|||||||
var openmct = new MCT();
|
var openmct = new MCT();
|
||||||
|
|
||||||
openmct.legacyRegistry = defaultRegistry;
|
openmct.legacyRegistry = defaultRegistry;
|
||||||
openmct.install(openmct.plugins.Plot());
|
|
||||||
|
|
||||||
if (typeof BUILD_CONSTANTS !== 'undefined') {
|
if (typeof BUILD_CONSTANTS !== 'undefined') {
|
||||||
openmct.install(buildInfo(BUILD_CONSTANTS));
|
openmct.install(buildInfo(BUILD_CONSTANTS));
|
||||||
|
@ -64,10 +64,8 @@ define(
|
|||||||
var domainObject = selection[0].context.oldItem;
|
var domainObject = selection[0].context.oldItem;
|
||||||
self.refreshComposition(domainObject);
|
self.refreshComposition(domainObject);
|
||||||
|
|
||||||
if (domainObject) {
|
self.mutationListener = domainObject.getCapability('mutation')
|
||||||
self.mutationListener = domainObject.getCapability('mutation')
|
.listen(self.refreshComposition.bind(self, domainObject));
|
||||||
.listen(self.refreshComposition.bind(self, domainObject));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.filterBy = filterBy;
|
$scope.filterBy = filterBy;
|
||||||
@ -88,7 +86,7 @@ define(
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
ElementsController.prototype.refreshComposition = function (domainObject) {
|
ElementsController.prototype.refreshComposition = function (domainObject) {
|
||||||
var selectedObjectComposition = domainObject && domainObject.useCapability('composition');
|
var selectedObjectComposition = domainObject.useCapability('composition');
|
||||||
|
|
||||||
if (selectedObjectComposition) {
|
if (selectedObjectComposition) {
|
||||||
selectedObjectComposition.then(function (composition) {
|
selectedObjectComposition.then(function (composition) {
|
||||||
|
@ -127,16 +127,6 @@ define(
|
|||||||
expect(mockUnlisten).toHaveBeenCalled();
|
expect(mockUnlisten).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not listen on mutation for element proxy selectable", function () {
|
|
||||||
selectable[0] = {
|
|
||||||
context: {
|
|
||||||
elementProxy: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
|
|
||||||
expect(mockDomainObject.getCapability).not.toHaveBeenCalledWith('mutation');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -99,7 +99,7 @@ $plotXBarH: 32px;
|
|||||||
$plotLegendH: 20px;
|
$plotLegendH: 20px;
|
||||||
$plotSwatchD: 8px;
|
$plotSwatchD: 8px;
|
||||||
// 1: Top, 2: right, 3: bottom, 4: left
|
// 1: Top, 2: right, 3: bottom, 4: left
|
||||||
$plotDisplayArea: (0, 0, $plotXBarH, $plotYBarW);
|
$plotDisplayArea: ($plotLegendH + $interiorMargin, 0, $plotXBarH, $plotYBarW);
|
||||||
/* min plot height is based on user testing to find minimum useful height */
|
/* min plot height is based on user testing to find minimum useful height */
|
||||||
$plotMinH: 95px;
|
$plotMinH: 95px;
|
||||||
/*************** Bubbles */
|
/*************** Bubbles */
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
* Use https://icomoon.io/app with icomoon-project-openmct-symbols-12px.json
|
* Use https://icomoon.io/app with icomoon-project-openmct-symbols-12px.json
|
||||||
* to generate font files
|
* 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');
|
||||||
src: url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.eot?#iefix') format('embedded-opentype'),
|
src: url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.eot?#iefix') format('embedded-opentype'),
|
||||||
url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.woff') format('woff'),
|
url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.woff') format('woff'),
|
||||||
@ -248,12 +248,6 @@ a.disabled {
|
|||||||
color: rgba(#fff, 0.2);
|
color: rgba(#fff, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.comma-list span {
|
|
||||||
&:not(:first-child) {
|
|
||||||
&:before { content: ', '; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.test-stripes {
|
.test-stripes {
|
||||||
@include bgDiagonalStripes();
|
@include bgDiagonalStripes();
|
||||||
}
|
}
|
||||||
|
@ -44,12 +44,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.t-alert-unsynced {
|
|
||||||
@extend .icon-alert-triangle;
|
|
||||||
color: $colorPausedBg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bar .ui-symbol {
|
.bar .ui-symbol {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
@ -87,5 +81,18 @@
|
|||||||
@include transform(scale(0.3));
|
@include transform(scale(0.3));
|
||||||
z-index: 2;
|
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;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,6 @@
|
|||||||
.l-inspector-part {
|
.l-inspector-part {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding-right: $interiorMargin;
|
padding-right: $interiorMargin;
|
||||||
|
|
||||||
.tree .form {
|
.tree .form {
|
||||||
margin-left: $treeVCW + $interiorMarginLg;
|
margin-left: $treeVCW + $interiorMarginLg;
|
||||||
}
|
}
|
||||||
@ -79,7 +78,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.form-row {
|
.form-row {
|
||||||
// To be replaced with .inspector-config, see below.
|
|
||||||
@include align-items(center);
|
@include align-items(center);
|
||||||
border: none !important;
|
border: none !important;
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
@ -101,12 +99,15 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul li {
|
||||||
|
margin-bottom: $interiorMarginLg;
|
||||||
|
}
|
||||||
|
|
||||||
em.t-inspector-part-header {
|
em.t-inspector-part-header {
|
||||||
border-radius: $basicCr;
|
border-radius: $basicCr;
|
||||||
background-color: $colorInspectorSectionHeaderBg;
|
background-color: $colorInspectorSectionHeaderBg;
|
||||||
color: $colorInspectorSectionHeaderFg;
|
color: $colorInspectorSectionHeaderFg;
|
||||||
margin-top: $interiorMarginLg;
|
margin-bottom: $interiorMargin;
|
||||||
//margin-bottom: $interiorMargin;
|
|
||||||
padding: floor($formTBPad * .75) $formLRPad;
|
padding: floor($formTBPad * .75) $formLRPad;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
@ -200,102 +201,3 @@ mct-representation:not(.s-status-editing) .l-inspect {
|
|||||||
pointer-events: inherit;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -70,7 +70,6 @@
|
|||||||
@import "fixed-position";
|
@import "fixed-position";
|
||||||
@import "lists/tabular";
|
@import "lists/tabular";
|
||||||
@import "plots/plots-main";
|
@import "plots/plots-main";
|
||||||
@import "plots/legend";
|
|
||||||
@import "iframe";
|
@import "iframe";
|
||||||
@import "views";
|
@import "views";
|
||||||
@import "items/item";
|
@import "items/item";
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.l-view-section {
|
.l-view-section {
|
||||||
//@include test(orange, 0.1);
|
|
||||||
@include absPosDefault(0);
|
@include absPosDefault(0);
|
||||||
h2 {
|
h2 {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
@ -150,26 +150,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************** 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 */
|
/******************************************************** CUSTOM CHECKBOXES */
|
||||||
label.checkbox.custom,
|
label.checkbox.custom,
|
||||||
label.radio.custom {
|
label.radio.custom {
|
||||||
|
@ -398,6 +398,10 @@ body.desktop .t-message-list {
|
|||||||
.object-header {
|
.object-header {
|
||||||
.t-object-alert {
|
.t-object-alert {
|
||||||
display: inline;
|
display: inline;
|
||||||
|
&.t-alert-unsynced {
|
||||||
|
@extend .icon-alert-triangle;
|
||||||
|
color: $colorPausedBg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,70 +20,53 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
.l-palette {
|
.l-palette {
|
||||||
|
$d: 16px;
|
||||||
|
$colorsPerRow: 10;
|
||||||
|
$m: 1;
|
||||||
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: $interiorMargin !important;
|
padding: $interiorMargin !important;
|
||||||
}
|
|
||||||
|
|
||||||
.l-palette-row {
|
.l-palette-row {
|
||||||
$d: 16px;
|
@include clearfix;
|
||||||
$m: 1;
|
line-height: $d;
|
||||||
$colorsPerRow: 10;
|
width: ($d * $colorsPerRow) + ($m * $colorsPerRow);
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
line-height: $d;
|
|
||||||
width: ($d * $colorsPerRow) + ($m * $colorsPerRow);
|
|
||||||
|
|
||||||
&.l-option-row {
|
&.l-option-row {
|
||||||
margin-bottom: $interiorMargin;
|
margin-bottom: $interiorMargin;
|
||||||
.s-palette-item {
|
.s-palette-item {
|
||||||
border-color: $colorPaletteFg;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -34,7 +34,18 @@ body.touch {
|
|||||||
line-height: $mobileTreeItemH !important;
|
line-height: $mobileTreeItemH !important;
|
||||||
.view-control {
|
.view-control {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
width: ceil($mobileTreeItemH * 0.5);
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.t-object-label {
|
.t-object-label {
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
|
@ -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.
|
|
||||||
*****************************************************************************/
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,64 +20,18 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
.abs.holder-plot {
|
.abs.holder-plot {
|
||||||
right: $interiorMargin; // Fend off the scrollbar when less than min-height;
|
// Fend off the scrollbar when less than min-height;
|
||||||
.t-object-alert.t-alert-unsynced {
|
right: $interiorMargin;
|
||||||
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 {
|
.gl-plot {
|
||||||
color: $colorPlotFg;
|
color: $colorPlotFg;
|
||||||
display: flex;
|
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: $plotMinH;
|
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 {
|
.gl-plot-wrapper-display-area-and-x-axis {
|
||||||
// Holds the plot area and the X-axis only
|
// Holds the plot area and the X-axis only
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -95,6 +49,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.gl-plot-axis-area.gl-plot-x {
|
.gl-plot-axis-area.gl-plot-x {
|
||||||
|
//@include test(green);
|
||||||
top: auto;
|
top: auto;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@ -108,7 +63,7 @@
|
|||||||
.gl-plot-axis-area {
|
.gl-plot-axis-area {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
&.gl-plot-y {
|
&.gl-plot-y {
|
||||||
top: nth($plotDisplayArea, 1);
|
top: $plotLegendH + $interiorMargin;
|
||||||
right: auto;
|
right: auto;
|
||||||
bottom: nth($plotDisplayArea, 3);
|
bottom: nth($plotDisplayArea, 3);
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -203,6 +158,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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 */
|
/****************************** Limits and Out-of-Bounds data */
|
||||||
|
|
||||||
.l-limit-bar,
|
.l-limit-bar,
|
||||||
@ -269,6 +235,39 @@
|
|||||||
border: 1px solid $colorPlotAreaBorder;
|
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 {
|
.tick {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border: 0 $colorPlotHash solid;
|
border: 0 $colorPlotHash solid;
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
ul.tree {
|
ul.tree {
|
||||||
@include menuUlReset();
|
@include menuUlReset();
|
||||||
@include user-select(none);
|
@include user-select(none);
|
||||||
> li {
|
li {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@ -53,10 +53,12 @@ ul.tree {
|
|||||||
.view-control {
|
.view-control {
|
||||||
color: $colorItemTreeVC;
|
color: $colorItemTreeVC;
|
||||||
margin-right: $interiorMargin;
|
margin-right: $interiorMargin;
|
||||||
|
height: 100%;
|
||||||
|
line-height: inherit;
|
||||||
width: $treeVCW;
|
width: $treeVCW;
|
||||||
&:before { display: block; }
|
&:before { display: none; }
|
||||||
&.no-children {
|
&.has-children {
|
||||||
&:before { display: none; }
|
&:before { display: block; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,9 +83,9 @@ define([
|
|||||||
this.activeObject = domainObject;
|
this.activeObject = domainObject;
|
||||||
|
|
||||||
if (domainObject && domainObject.hasCapability('composition')) {
|
if (domainObject && domainObject.hasCapability('composition')) {
|
||||||
$(this.toggleView.elements()).removeClass('no-children');
|
$(this.toggleView.elements()).addClass('has-children');
|
||||||
} else {
|
} else {
|
||||||
$(this.toggleView.elements()).addClass('no-children');
|
$(this.toggleView.elements()).removeClass('has-children');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (domainObject && domainObject.hasCapability('status')) {
|
if (domainObject && domainObject.hasCapability('status')) {
|
||||||
|
@ -181,8 +181,6 @@ $colorPlotHash: $colorTick;
|
|||||||
$stylePlotHash: dashed;
|
$stylePlotHash: dashed;
|
||||||
$colorPlotAreaBorder: $colorInteriorBorder;
|
$colorPlotAreaBorder: $colorInteriorBorder;
|
||||||
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
|
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
|
||||||
$legendCollapsedNameMaxW: 50%;
|
|
||||||
$legendHoverValueBg: rgba($colorBodyFg, 0.1);
|
|
||||||
|
|
||||||
// Tree
|
// Tree
|
||||||
$colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent);
|
$colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent);
|
||||||
|
@ -181,8 +181,6 @@ $colorPlotHash: $colorTick;
|
|||||||
$stylePlotHash: dashed;
|
$stylePlotHash: dashed;
|
||||||
$colorPlotAreaBorder: $colorInteriorBorder;
|
$colorPlotAreaBorder: $colorInteriorBorder;
|
||||||
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
|
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
|
||||||
$legendCollapsedNameMaxW: 50%;
|
|
||||||
$legendHoverValueBg: rgba($colorBodyFg, 0.2);
|
|
||||||
|
|
||||||
// Tree
|
// Tree
|
||||||
$colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent);
|
$colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent);
|
||||||
|
@ -337,6 +337,46 @@ define([
|
|||||||
"conversion": "number[]"
|
"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[]"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -272,7 +272,7 @@ define(
|
|||||||
self.resizeHandles = self.generateDragHandles(self.selectedElementProxy);
|
self.resizeHandles = self.generateDragHandles(self.selectedElementProxy);
|
||||||
} else {
|
} else {
|
||||||
// Make fixed view selectable if it's not already.
|
// Make fixed view selectable if it's not already.
|
||||||
if (!self.fixedViewSelectable && selectable.length === 1) {
|
if (!self.fixedViewSelectable) {
|
||||||
self.fixedViewSelectable = true;
|
self.fixedViewSelectable = true;
|
||||||
selection.context.viewProxy = new FixedProxy(addElement, $q, dialogService);
|
selection.context.viewProxy = new FixedProxy(addElement, $q, dialogService);
|
||||||
self.openmct.selection.select(selection);
|
self.openmct.selection.select(selection);
|
||||||
|
37
platform/features/plot/README.md
Normal file
37
platform/features/plot/README.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# 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.
|
||||||
|
|
157
platform/features/plot/bundle.js
Normal file
157
platform/features/plot/bundle.js
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,70 @@
|
|||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<div ng-controller="PlotOptionsController" class="flex-elem grows l-inspector-part">
|
||||||
|
<em class="t-inspector-part-header" title="Display properties for this object">Plot Options</em>
|
||||||
|
<mct-form
|
||||||
|
ng-model="configuration.plot.xAxis"
|
||||||
|
structure="xAxisForm"
|
||||||
|
name="xAxisFormState"
|
||||||
|
class="flex-elem l-flex-row no-margin">
|
||||||
|
</mct-form>
|
||||||
|
<mct-form
|
||||||
|
ng-model="configuration.plot.yAxis"
|
||||||
|
structure="yAxisForm"
|
||||||
|
name="yAxisFormState"
|
||||||
|
class="flex-elem l-flex-row no-margin">
|
||||||
|
</mct-form>
|
||||||
|
<div class="form">
|
||||||
|
<div class="section-header ng-binding ng-scope">
|
||||||
|
Plot Series
|
||||||
|
</div>
|
||||||
|
<ul class="first flex-elem grows vscroll">
|
||||||
|
<ul class="tree">
|
||||||
|
<li ng-repeat="child in children">
|
||||||
|
<span ng-controller="ToggleController as toggle">
|
||||||
|
<span ng-controller="TreeNodeController as treeNode">
|
||||||
|
<span class="tree-item menus-to-left">
|
||||||
|
<span
|
||||||
|
class='ui-symbol view-control flex-elem has-children'
|
||||||
|
ng-class="{ expanded: toggle.isActive() }"
|
||||||
|
ng-click="toggle.toggle(); treeNode.trackExpansion()">
|
||||||
|
</span>
|
||||||
|
<mct-representation
|
||||||
|
class="rep-object-label"
|
||||||
|
key="'label'"
|
||||||
|
mct-object="child">
|
||||||
|
</mct-representation>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<mct-form
|
||||||
|
ng-class="{hidden: !toggle.isActive()}"
|
||||||
|
ng-model="configuration.plot.series[$index]"
|
||||||
|
structure="plotSeriesForm"
|
||||||
|
name="plotOptionsState"
|
||||||
|
class="flex-elem l-flex-row">
|
||||||
|
</mct-form>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
165
platform/features/plot/res/templates/plot.html
Normal file
165
platform/features/plot/res/templates/plot.html
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<span ng-controller="PlotController as plot"
|
||||||
|
class="abs holder holder-plot has-control-bar">
|
||||||
|
<div class="l-control-bar" ng-show="!plot.hideExportButtons">
|
||||||
|
<span class="l-btn-set">
|
||||||
|
<a class="s-button t-export labeled icon-download"
|
||||||
|
ng-click="plot.exportPNG()"
|
||||||
|
title="Export This View's Data as PNG">
|
||||||
|
PNG
|
||||||
|
</a>
|
||||||
|
<a class="s-button t-export labeled"
|
||||||
|
ng-click="plot.exportJPG()"
|
||||||
|
title="Export This View's Data as JPG">
|
||||||
|
JPG
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="l-view-section">
|
||||||
|
<div class="gl-plot"
|
||||||
|
ng-style="{ height: 100 / plot.getSubPlots().length + '%'}"
|
||||||
|
ng-repeat="subplot in plot.getSubPlots()">
|
||||||
|
<div class="gl-plot-legend">
|
||||||
|
<span class='plot-legend-item'
|
||||||
|
ng-repeat="telemetryObject in subplot.getTelemetryObjects()"
|
||||||
|
ng-class="plot.getLegendClass(telemetryObject)">
|
||||||
|
<span class='plot-color-swatch'
|
||||||
|
ng-style="{ 'background-color': plot.getColor($index) }">
|
||||||
|
</span>
|
||||||
|
<span class='title-label'>{{telemetryObject.getModel().name}}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gl-plot-axis-area gl-plot-y">
|
||||||
|
<div class="gl-plot-label gl-plot-y-label">
|
||||||
|
{{axes[1].active.name}}
|
||||||
|
</div>
|
||||||
|
<div ng-repeat="tick in subplot.getRangeTicks()"
|
||||||
|
class="gl-plot-tick gl-plot-y-tick-label"
|
||||||
|
ng-style="{ bottom: (100 * $index / (subplot.getRangeTicks().length - 1)) + '%' }">
|
||||||
|
{{tick.label | reverse}}
|
||||||
|
</div>
|
||||||
|
<div class="gl-plot-y-options gl-plot-local-controls"
|
||||||
|
ng-if="axes[1].options.length > 1">
|
||||||
|
<div class='form-control shell select'>
|
||||||
|
<select class="form-control input shell"
|
||||||
|
ng-model="axes[1].active"
|
||||||
|
ng-options="option.name for option in axes[1].options">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gl-plot-wrapper-display-area-and-x-axis">
|
||||||
|
<mct-include key="'time-of-interest'"
|
||||||
|
class="l-toi-holder show-val"
|
||||||
|
ng-if="toiPerc"
|
||||||
|
ng-class="{ 'pinned': toiPinned, 'val-to-left': toiPerc > 80 }"
|
||||||
|
ng-style="{'left': toiPerc + '%'}"></mct-include>
|
||||||
|
|
||||||
|
<div class="gl-plot-coords"
|
||||||
|
ng-if="subplot.isHovering() && subplot.getHoverCoordinates()">
|
||||||
|
{{subplot.getHoverCoordinates()}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gl-plot-display-area"
|
||||||
|
ng-mouseenter="subplot.isHovering(true);"
|
||||||
|
ng-mouseleave="subplot.isHovering(false)"
|
||||||
|
ng-class="{ loading: plot.isRequestPending() }">
|
||||||
|
|
||||||
|
<!-- Out-of-bounds data indicators -->
|
||||||
|
<!-- ng-show is temporarily hard-coded in next element -->
|
||||||
|
<div ng-show="false" class="l-oob-data l-oob-data-up"></div>
|
||||||
|
<div ng-show="false" class="l-oob-data l-oob-data-dwn"></div>
|
||||||
|
<div class="gl-plot-hash hash-v"
|
||||||
|
ng-repeat="tick in subplot.getDomainTicks()"
|
||||||
|
ng-style="{ left: (100 * $index / (subplot.getDomainTicks().length - 1)) + '%', height: '100%' }"
|
||||||
|
ng-show="$index > 0 && $index < (subplot.getDomainTicks().length - 1)">
|
||||||
|
</div>
|
||||||
|
<div class="gl-plot-hash hash-h"
|
||||||
|
ng-repeat="tick in subplot.getRangeTicks()"
|
||||||
|
ng-style="{ bottom: (100 * $index / (subplot.getRangeTicks().length - 1)) + '%', width: '100%' }"
|
||||||
|
ng-show="$index > 0 && $index < (subplot.getRangeTicks().length - 1)">
|
||||||
|
</div>
|
||||||
|
<mct-chart draw="subplot.getDrawingObject()"
|
||||||
|
ng-if="subplot.getTelemetryObjects().length > 0"
|
||||||
|
ng-mousemove="subplot.hover($event)"
|
||||||
|
mct-drag="subplot.continueDrag($event)"
|
||||||
|
mct-drag-down="subplot.startDrag($event)"
|
||||||
|
mct-drag-up="subplot.endDrag($event); plot.update()">
|
||||||
|
</mct-chart>
|
||||||
|
<!-- TODO: Move into correct position; make part of group; infer from set of actions -->
|
||||||
|
<div class="l-local-controls gl-plot-local-controls t-plot-display-controls"
|
||||||
|
ng-if="$first">
|
||||||
|
<a class="s-button icon-arrow-left"
|
||||||
|
ng-click="plot.stepBackPanZoom()"
|
||||||
|
ng-show="plot.isZoomed()"
|
||||||
|
title="Restore previous pan/zoom">
|
||||||
|
</a>
|
||||||
|
<a class="s-button icon-arrows-out"
|
||||||
|
ng-click="plot.unzoom()"
|
||||||
|
ng-show="plot.isZoomed()"
|
||||||
|
title="Reset pan/zoom">
|
||||||
|
</a>
|
||||||
|
<div class="menu-element s-menu-button menus-to-left {{plot.getMode().cssClass}}"
|
||||||
|
ng-if="plot.getModeOptions().length > 1"
|
||||||
|
ng-controller="ClickAwayController as toggle">
|
||||||
|
<span class="l-click-area" ng-click="toggle.toggle()"></span>
|
||||||
|
<span>{{plot.getMode().name}}</span>
|
||||||
|
<div class="menu" ng-show="toggle.isActive()">
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="option in plot.getModeOptions()"
|
||||||
|
ng-click="plot.setMode(option); toggle.setState(false)"
|
||||||
|
class="{{option.cssClass}}">
|
||||||
|
{{option.name}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ng-if="$last" class="gl-plot-axis-area gl-plot-x">
|
||||||
|
<div ng-repeat="tick in subplot.getDomainTicks()"
|
||||||
|
class="gl-plot-tick gl-plot-x-tick-label"
|
||||||
|
ng-show="$index > 0 && $index < (subplot.getDomainTicks().length - 1)"
|
||||||
|
ng-style="{ left: (100 * $index / (subplot.getDomainTicks().length - 1)) + '%' }">
|
||||||
|
{{tick.label | reverse}}
|
||||||
|
</div>
|
||||||
|
<div class="gl-plot-label gl-plot-x-label">
|
||||||
|
{{axes[0].active.name}}
|
||||||
|
</div>
|
||||||
|
<div class="gl-plot-x-options gl-plot-local-controls"
|
||||||
|
ng-if="axes[0].options.length > 1">
|
||||||
|
<div class='form-control shell select'>
|
||||||
|
<select class="form-control input shell"
|
||||||
|
ng-model="axes[0].active"
|
||||||
|
ng-options="option.name for option in axes[0].options">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
117
platform/features/plot/src/Canvas2DChart.js
Normal file
117
platform/features/plot/src/Canvas2DChart.js
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
160
platform/features/plot/src/GLChart.js
Normal file
160
platform/features/plot/src/GLChart.js
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
250
platform/features/plot/src/MCTChart.js
Normal file
250
platform/features/plot/src/MCTChart.js
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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 = "<canvas style='position: absolute; background: none; width: 100%; height: 100%;'></canvas>";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
437
platform/features/plot/src/PlotController.js
Normal file
437
platform/features/plot/src/PlotController.js
Normal file
@ -0,0 +1,437 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
195
platform/features/plot/src/PlotOptionsController.js
Normal file
195
platform/features/plot/src/PlotOptionsController.js
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
150
platform/features/plot/src/PlotOptionsForm.js
Normal file
150
platform/features/plot/src/PlotOptionsForm.js
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
415
platform/features/plot/src/SubPlot.js
Normal file
415
platform/features/plot/src/SubPlot.js
Normal file
@ -0,0 +1,415 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
59
platform/features/plot/src/SubPlotFactory.js
Normal file
59
platform/features/plot/src/SubPlotFactory.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
134
platform/features/plot/src/elements/PlotAxis.js
Normal file
134
platform/features/plot/src/elements/PlotAxis.js
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
78
platform/features/plot/src/elements/PlotLimitTracker.js
Normal file
78
platform/features/plot/src/elements/PlotLimitTracker.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
118
platform/features/plot/src/elements/PlotLine.js
Normal file
118
platform/features/plot/src/elements/PlotLine.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
268
platform/features/plot/src/elements/PlotLineBuffer.js
Normal file
268
platform/features/plot/src/elements/PlotLineBuffer.js
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
133
platform/features/plot/src/elements/PlotPalette.js
Normal file
133
platform/features/plot/src/elements/PlotPalette.js
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
141
platform/features/plot/src/elements/PlotPanZoomStack.js
Normal file
141
platform/features/plot/src/elements/PlotPanZoomStack.js
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
167
platform/features/plot/src/elements/PlotPanZoomStackGroup.js
Normal file
167
platform/features/plot/src/elements/PlotPanZoomStackGroup.js
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
95
platform/features/plot/src/elements/PlotPosition.js
Normal file
95
platform/features/plot/src/elements/PlotPosition.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
153
platform/features/plot/src/elements/PlotPreparer.js
Normal file
153
platform/features/plot/src/elements/PlotPreparer.js
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
80
platform/features/plot/src/elements/PlotSeriesWindow.js
Normal file
80
platform/features/plot/src/elements/PlotSeriesWindow.js
Normal file
@ -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 () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,76 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
102
platform/features/plot/src/elements/PlotTickGenerator.js
Normal file
102
platform/features/plot/src/elements/PlotTickGenerator.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
353
platform/features/plot/src/elements/PlotUpdater.js
Normal file
353
platform/features/plot/src/elements/PlotUpdater.js
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
155
platform/features/plot/src/modes/PlotModeOptions.js
Normal file
155
platform/features/plot/src/modes/PlotModeOptions.js
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
88
platform/features/plot/src/modes/PlotOverlayMode.js
Normal file
88
platform/features/plot/src/modes/PlotOverlayMode.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
104
platform/features/plot/src/modes/PlotStackMode.js
Normal file
104
platform/features/plot/src/modes/PlotStackMode.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
@ -1,5 +1,5 @@
|
|||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* Administration. All rights reserved.
|
||||||
*
|
*
|
||||||
@ -54,7 +54,7 @@ define(
|
|||||||
};
|
};
|
||||||
|
|
||||||
PlotViewPolicy.prototype.allow = function (view, domainObject) {
|
PlotViewPolicy.prototype.allow = function (view, domainObject) {
|
||||||
if (view.key === 'plot-single') {
|
if (view.key === 'plot') {
|
||||||
return this.hasNumericTelemetry(domainObject);
|
return this.hasNumericTelemetry(domainObject);
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* Administration. All rights reserved.
|
||||||
*
|
*
|
||||||
@ -32,6 +32,7 @@ define(
|
|||||||
html2canvas,
|
html2canvas,
|
||||||
saveAs
|
saveAs
|
||||||
) {
|
) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The export image service will export any HTML node to
|
* The export image service will export any HTML node to
|
||||||
@ -42,15 +43,15 @@ define(
|
|||||||
* @param {constant} EXPORT_IMAGE_TIMEOUT time in milliseconds before a timeout error is returned
|
* @param {constant} EXPORT_IMAGE_TIMEOUT time in milliseconds before a timeout error is returned
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function ExportImageService($q, $timeout, $log) {
|
function ExportImageService($q, $timeout, $log, EXPORT_IMAGE_TIMEOUT, injHtml2Canvas, injSaveAs, injFileReader, injChangeBackgroundColor) {
|
||||||
this.$q = $q;
|
self.$q = $q;
|
||||||
this.$timeout = $timeout;
|
self.$timeout = $timeout;
|
||||||
this.$log = $log;
|
self.$log = $log;
|
||||||
this.EXPORT_IMAGE_TIMEOUT = 1000;
|
self.EXPORT_IMAGE_TIMEOUT = EXPORT_IMAGE_TIMEOUT;
|
||||||
}
|
self.html2canvas = injHtml2Canvas || html2canvas;
|
||||||
|
self.saveAs = injSaveAs || saveAs;
|
||||||
function changeBackgroundColor(element, color) {
|
self.reader = injFileReader || new FileReader();
|
||||||
element.style.backgroundColor = color;
|
self.changeBackgroundColor = injChangeBackgroundColor || self.changeBackgroundColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,42 +61,42 @@ define(
|
|||||||
* @param {string} type of image to convert the element to
|
* @param {string} type of image to convert the element to
|
||||||
* @returns {promise}
|
* @returns {promise}
|
||||||
*/
|
*/
|
||||||
ExportImageService.prototype.renderElement = function (element, type, color) {
|
function renderElement(element, type, color) {
|
||||||
var defer = this.$q.defer(),
|
var defer = self.$q.defer(),
|
||||||
validTypes = ["png", "jpg", "jpeg"],
|
validTypes = ["png", "jpg", "jpeg"],
|
||||||
renderTimeout,
|
renderTimeout,
|
||||||
originalColor;
|
originalColor;
|
||||||
|
|
||||||
if (validTypes.indexOf(type) === -1) {
|
if (validTypes.indexOf(type) === -1) {
|
||||||
this.$log.error("Invalid type requested. Try: (" + validTypes.join(",") + ")");
|
self.$log.error("Invalid type requested. Try: (" + validTypes.join(",") + ")");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (color) {
|
if (color) {
|
||||||
// Save color to be restored later
|
// Save color to be restored later
|
||||||
originalColor = element.style.backgroundColor || '';
|
originalColor = element.style.backgroundColor || '';
|
||||||
|
|
||||||
// Defaulting to white so we can see the chart when printed
|
// Defaulting to white so we can see the chart when printed
|
||||||
changeBackgroundColor(element, color);
|
self.changeBackgroundColor(element, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTimeout = this.$timeout(function () {
|
renderTimeout = self.$timeout(function () {
|
||||||
defer.reject("html2canvas timed out");
|
defer.reject("html2canvas timed out");
|
||||||
this.$log.warn("html2canvas timed out");
|
self.$log.warn("html2canvas timed out");
|
||||||
}.bind(this), this.EXPORT_IMAGE_TIMEOUT);
|
}, self.EXPORT_IMAGE_TIMEOUT);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
html2canvas(element, {
|
self.html2canvas(element, {
|
||||||
onrendered: function (canvas) {
|
onrendered: function (canvas) {
|
||||||
if (color) {
|
if (color) {
|
||||||
changeBackgroundColor(element, originalColor);
|
self.changeBackgroundColor(element, originalColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type.toLowerCase()) {
|
switch (type.toLowerCase()) {
|
||||||
case "png":
|
case "png":
|
||||||
canvas.toBlob(defer.resolve, "image/png");
|
canvas.toBlob(defer.resolve, "image/png");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
|
||||||
case "jpg":
|
|
||||||
case "jpeg":
|
case "jpeg":
|
||||||
canvas.toBlob(defer.resolve, "image/jpeg");
|
canvas.toBlob(defer.resolve, "image/jpeg");
|
||||||
break;
|
break;
|
||||||
@ -104,42 +105,19 @@ define(
|
|||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
defer.reject(e);
|
defer.reject(e);
|
||||||
this.$log.warn("html2canvas failed with error: " + e);
|
self.$log.warn("html2canvas failed with error: " + e);
|
||||||
}
|
}
|
||||||
|
|
||||||
defer.promise.finally(function () {
|
defer.promise.finally(function () {
|
||||||
renderTimeout.cancel();
|
renderTimeout.cancel();
|
||||||
|
|
||||||
if (color) {
|
if (color) {
|
||||||
changeBackgroundColor(element, originalColor);
|
self.changeBackgroundColor(element, originalColor);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return defer.promise;
|
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
|
* canvas.toBlob() not supported in IE < 10, Opera, and Safari. This polyfill
|
||||||
@ -165,6 +143,37 @@ 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();
|
polyfillToBlob();
|
||||||
|
|
||||||
return ExportImageService;
|
return ExportImageService;
|
95
platform/features/plot/test/Canvas2DChartSpec.js
Normal file
95
platform/features/plot/test/Canvas2DChartSpec.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
143
platform/features/plot/test/GLChartSpec.js
Normal file
143
platform/features/plot/test/GLChartSpec.js
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
216
platform/features/plot/test/MCTChartSpec.js
Normal file
216
platform/features/plot/test/MCTChartSpec.js
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
403
platform/features/plot/test/PlotControllerSpec.js
Normal file
403
platform/features/plot/test/PlotControllerSpec.js
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
/*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('<div />');
|
||||||
|
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());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
147
platform/features/plot/test/PlotOptionsControllerSpec.js
Normal file
147
platform/features/plot/test/PlotOptionsControllerSpec.js
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -1,5 +1,5 @@
|
|||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* Administration. All rights reserved.
|
||||||
*
|
*
|
||||||
@ -19,40 +19,29 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
define([
|
|
||||||
], function (
|
|
||||||
) {
|
|
||||||
|
|
||||||
function ConfigStore() {
|
define(
|
||||||
this.store = {};
|
['../src/PlotOptionsForm'],
|
||||||
this.tracking = {};
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
);
|
||||||
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;
|
|
||||||
});
|
|
66
platform/features/plot/test/SubPlotFactorySpec.js
Normal file
66
platform/features/plot/test/SubPlotFactorySpec.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
208
platform/features/plot/test/SubPlotSpec.js
Normal file
208
platform/features/plot/test/SubPlotSpec.js
Normal file
@ -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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
107
platform/features/plot/test/elements/PlotAxisSpec.js
Normal file
107
platform/features/plot/test/elements/PlotAxisSpec.js
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
100
platform/features/plot/test/elements/PlotLimitTrackerSpec.js
Normal file
100
platform/features/plot/test/elements/PlotLimitTrackerSpec.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
167
platform/features/plot/test/elements/PlotLineBufferSpec.js
Normal file
167
platform/features/plot/test/elements/PlotLineBufferSpec.js
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
133
platform/features/plot/test/elements/PlotLineSpec.js
Normal file
133
platform/features/plot/test/elements/PlotLineSpec.js
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
123
platform/features/plot/test/elements/PlotPaletteSpec.js
Normal file
123
platform/features/plot/test/elements/PlotPaletteSpec.js
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,126 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
99
platform/features/plot/test/elements/PlotPanZoomStackSpec.js
Normal file
99
platform/features/plot/test/elements/PlotPanZoomStackSpec.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
67
platform/features/plot/test/elements/PlotPositionSpec.js
Normal file
67
platform/features/plot/test/elements/PlotPositionSpec.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
93
platform/features/plot/test/elements/PlotPreparerSpec.js
Normal file
93
platform/features/plot/test/elements/PlotPreparerSpec.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
93
platform/features/plot/test/elements/PlotSeriesWindowSpec.js
Normal file
93
platform/features/plot/test/elements/PlotSeriesWindowSpec.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,71 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,73 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
237
platform/features/plot/test/elements/PlotUpdaterSpec.js
Normal file
237
platform/features/plot/test/elements/PlotUpdaterSpec.js
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
87
platform/features/plot/test/modes/PlotModeOptionsSpec.js
Normal file
87
platform/features/plot/test/modes/PlotModeOptionsSpec.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
184
platform/features/plot/test/modes/PlotOverlayModeSpec.js
Normal file
184
platform/features/plot/test/modes/PlotOverlayModeSpec.js
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
179
platform/features/plot/test/modes/PlotStackModeSpec.js
Normal file
179
platform/features/plot/test/modes/PlotStackModeSpec.js
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
123
platform/features/plot/test/policies/PlotViewPolicySpec.js
Normal file
123
platform/features/plot/test/policies/PlotViewPolicySpec.js
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
146
platform/features/plot/test/services/ExportImageServiceSpec.js
Normal file
146
platform/features/plot/test/services/ExportImageServiceSpec.js
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -205,9 +205,6 @@ define(
|
|||||||
},
|
},
|
||||||
getPointCount: function () {
|
getPointCount: function () {
|
||||||
return telemetry.length;
|
return telemetry.length;
|
||||||
},
|
|
||||||
getData: function () {
|
|
||||||
return telemetry;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -148,9 +148,7 @@ define([
|
|||||||
var limitEvaluator = oldObject.getCapability("limit");
|
var limitEvaluator = oldObject.getCapability("limit");
|
||||||
|
|
||||||
if (!limitEvaluator) {
|
if (!limitEvaluator) {
|
||||||
return {
|
return;
|
||||||
evaluate: function () {}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -100,18 +100,6 @@ define([
|
|||||||
delete valueMetadata.hints.y;
|
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')) {
|
if (!valueMetadata.hints.hasOwnProperty('priority')) {
|
||||||
valueMetadata.hints.priority = index;
|
valueMetadata.hints.priority = index;
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ define([
|
|||||||
this.formatter = numberFormatter;
|
this.formatter = numberFormatter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valueMetadata.format === 'enum') {
|
if (valueMetadata.type === 'enum') {
|
||||||
this.formatter = {};
|
this.formatter = {};
|
||||||
this.enumerations = valueMetadata.enumerations.reduce(function (vm, e) {
|
this.enumerations = valueMetadata.enumerations.reduce(function (vm, e) {
|
||||||
vm.byValue[e.value] = e.string;
|
vm.byValue[e.value] = e.string;
|
||||||
@ -57,16 +57,11 @@ define([
|
|||||||
return vm;
|
return vm;
|
||||||
}, {byValue: {}, byString: {}});
|
}, {byValue: {}, byString: {}});
|
||||||
this.formatter.format = function (value) {
|
this.formatter.format = function (value) {
|
||||||
if (typeof value === "number") {
|
return this.enumerations.byValue[value];
|
||||||
return this.enumerations.byValue[value] || value;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
this.formatter.parse = function (string) {
|
this.formatter.parse = function (string) {
|
||||||
if (typeof string === "string") {
|
if (typeof string === "string" && this.enumerations.hasOwnProperty(string)) {
|
||||||
if (this.enumerations.byString.hasOwnProperty(string)) {
|
return this.enumerations.byString[string];
|
||||||
return this.enumerations.byString[string];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return Number(string);
|
return Number(string);
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
@ -73,6 +73,7 @@ define([
|
|||||||
'../platform/features/my-items/bundle',
|
'../platform/features/my-items/bundle',
|
||||||
'../platform/features/pages/bundle',
|
'../platform/features/pages/bundle',
|
||||||
'../platform/features/hyperlink/bundle',
|
'../platform/features/hyperlink/bundle',
|
||||||
|
'../platform/features/plot/bundle',
|
||||||
'../platform/features/static-markup/bundle',
|
'../platform/features/static-markup/bundle',
|
||||||
'../platform/features/table/bundle',
|
'../platform/features/table/bundle',
|
||||||
'../platform/features/timeline/bundle',
|
'../platform/features/timeline/bundle',
|
||||||
@ -119,6 +120,7 @@ define([
|
|||||||
'platform/features/listview',
|
'platform/features/listview',
|
||||||
'platform/features/pages',
|
'platform/features/pages',
|
||||||
'platform/features/hyperlink',
|
'platform/features/hyperlink',
|
||||||
|
'platform/features/plot',
|
||||||
'platform/features/timeline',
|
'platform/features/timeline',
|
||||||
'platform/features/table',
|
'platform/features/table',
|
||||||
'platform/forms',
|
'platform/forms',
|
||||||
|
@ -1,250 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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;
|
|
||||||
});
|
|
@ -1,215 +0,0 @@
|
|||||||
<!--
|
|
||||||
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.
|
|
||||||
-->
|
|
||||||
<div class="gl-plot plot-legend-{{legend.get('position')}} {{legend.get('expanded')? 'plot-legend-expanded' : 'plot-legend-collapsed'}}">
|
|
||||||
<div class="gl-plot-legend flex-elem l-flex-row"
|
|
||||||
ng-class="{ 'hover-on-plot': !!highlights.length }"
|
|
||||||
ng-show="legend.get('position') !== 'hidden'">
|
|
||||||
<span class="view-control flex-elem"
|
|
||||||
ng-class="{ expanded: legend.get('expanded') }"
|
|
||||||
ng-click="legend.set('expanded', !legend.get('expanded'));">
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<!-- COLLAPSED PLOT LEGEND -->
|
|
||||||
<div class="plot-wrapper-collapsed-legend">
|
|
||||||
<div class="plot-legend-item"
|
|
||||||
ng-repeat="series in series track by $index">
|
|
||||||
<div class="plot-series-swatch-and-name">
|
|
||||||
<span class="plot-series-color-swatch"
|
|
||||||
ng-style="{ 'background-color': series.get('color').asHexString() }">
|
|
||||||
</span>
|
|
||||||
<span class="plot-series-name">{{ series.get('name') }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="plot-series-value hover-value-enabled value-to-display-{{ legend.get('valueToShowWhenCollapsed') }} {{ series.closest._limit.cssClass }}"
|
|
||||||
ng-class="{ 'cursor-hover': (legend.get('valueToShowWhenCollapsed').indexOf('nearest') != -1) }"
|
|
||||||
ng-show="!!highlights.length && legend.get('valueToShowWhenCollapsed') !== 'none'">
|
|
||||||
{{ 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']);
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- EXPANDED PLOT LEGEND -->
|
|
||||||
<div class="plot-wrapper-expanded-legend flex-elem grows">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th ng-if="legend.get('showTimestampWhenExpanded')">
|
|
||||||
Timestamp
|
|
||||||
</th>
|
|
||||||
<th ng-if="legend.get('showValueWhenExpanded')">
|
|
||||||
Value
|
|
||||||
</th>
|
|
||||||
<th ng-if="legend.get('showMinimumWhenExpanded')"
|
|
||||||
class="mobile-hide">
|
|
||||||
Min
|
|
||||||
</th>
|
|
||||||
<th ng-if="legend.get('showMaximumWhenExpanded')"
|
|
||||||
class="mobile-hide">
|
|
||||||
Max
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tr ng-repeat="series in series" class="plot-legend-item">
|
|
||||||
<td class="plot-series-swatch-and-name">
|
|
||||||
<span class="plot-series-color-swatch"
|
|
||||||
ng-style="{ 'background-color': series.get('color').asHexString() }">
|
|
||||||
</span>
|
|
||||||
<span class="plot-series-name">{{ series.get('name') }}</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td ng-if="legend.get('showTimestampWhenExpanded')">
|
|
||||||
<span class="plot-series-value cursor-hover hover-value-enabled">
|
|
||||||
{{ series.closest && series.formatX(series.closest) }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td ng-if="legend.get('showValueWhenExpanded')">
|
|
||||||
<span class="plot-series-value cursor-hover hover-value-enabled"
|
|
||||||
ng-class="series.closest._limit.cssClass">
|
|
||||||
{{ series.formatY(series.closest) }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td ng-if="legend.get('showMinimumWhenExpanded')"
|
|
||||||
class="mobile-hide">
|
|
||||||
<span class="plot-series-value">
|
|
||||||
{{ series.formatY(series.get('stats').minPoint) }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td ng-if="legend.get('showMaximumWhenExpanded')"
|
|
||||||
class="mobile-hide">
|
|
||||||
<span class="plot-series-value">
|
|
||||||
{{ series.formatY(series.get('stats').maxPoint) }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
|
|
||||||
<div class="gl-plot-axis-area gl-plot-y"
|
|
||||||
ng-style="{
|
|
||||||
width: (tickWidth + 30) + 'px'
|
|
||||||
}">
|
|
||||||
|
|
||||||
<div class="gl-plot-label gl-plot-y-label">
|
|
||||||
{{ yAxis.get('label') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<mct-ticks axis="yAxis">
|
|
||||||
<div ng-repeat="tick in ticks track by tick.text"
|
|
||||||
class="gl-plot-tick gl-plot-y-tick-label"
|
|
||||||
ng-style="{ top: (100 * (max - tick.value) / interval) + '%' }"
|
|
||||||
title="{{:: tick.fullText || tick.text }}"
|
|
||||||
style="margin-top: -0.50em; direction: ltr;">
|
|
||||||
<span>{{:: tick.text}}</span>
|
|
||||||
</div>
|
|
||||||
</mct-ticks>
|
|
||||||
</div>
|
|
||||||
<div class="gl-plot-wrapper-display-area-and-x-axis"
|
|
||||||
ng-style="{
|
|
||||||
left: (tickWidth + 30) + 'px'
|
|
||||||
}">
|
|
||||||
<span class="t-object-alert t-alert-unsynced" title="This plot is not currently displaying the latest data. Reset Pan/zoom to return to view latest data."></span>
|
|
||||||
<div class="gl-plot-display-area">
|
|
||||||
<mct-ticks axis="xAxis">
|
|
||||||
<div class="gl-plot-hash hash-v"
|
|
||||||
ng-repeat="tick in ticks track by tick.value"
|
|
||||||
ng-style="{
|
|
||||||
right: (100 * (max - tick.value) / interval) + '%',
|
|
||||||
height: '100%'
|
|
||||||
}">
|
|
||||||
</div>
|
|
||||||
</mct-ticks>
|
|
||||||
|
|
||||||
|
|
||||||
<mct-ticks axis="yAxis">
|
|
||||||
<div class="gl-plot-hash hash-h"
|
|
||||||
ng-repeat="tick in ticks track by tick.value"
|
|
||||||
ng-style="{ bottom: (100 * (tick.value - min) / interval) + '%', width: '100%' }">
|
|
||||||
</div>
|
|
||||||
</mct-ticks>
|
|
||||||
|
|
||||||
|
|
||||||
<mct-chart config="config"
|
|
||||||
series="series"
|
|
||||||
rectangles="rectangles"
|
|
||||||
highlights="highlights"
|
|
||||||
the-x-axis="xAxis"
|
|
||||||
the-y-axis="yAxis">
|
|
||||||
</mct-chart>
|
|
||||||
|
|
||||||
<div class="l-local-controls gl-plot-local-controls"
|
|
||||||
ng-show="plotHistory.length"
|
|
||||||
style="position: absolute; top: 8px; right: 8px;">
|
|
||||||
|
|
||||||
<a class="s-button icon-brackets"
|
|
||||||
ng-click="plot.syncConductor()"
|
|
||||||
title="Synchronize Time Conductor to plot bounds">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a class="s-button icon-arrow-left"
|
|
||||||
ng-click="plot.back()"
|
|
||||||
title="Restore previous pan/zoom">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a class="s-button icon-arrows-out"
|
|
||||||
ng-click="plot.clear()"
|
|
||||||
title="Reset pan/zoom">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span class="t-wait-spinner loading" ng-show="plot.isRequestPending()">
|
|
||||||
</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="gl-plot-axis-area gl-plot-x"
|
|
||||||
ng-style="{
|
|
||||||
left: (tickWidth - 30) + 'px'
|
|
||||||
}">
|
|
||||||
|
|
||||||
<mct-ticks axis="xAxis">
|
|
||||||
<div ng-repeat="tick in ticks track by tick.text"
|
|
||||||
class="gl-plot-tick gl-plot-x-tick-label"
|
|
||||||
ng-style="{
|
|
||||||
left: (100 * (tick.value - min) / interval) + '%'
|
|
||||||
}"
|
|
||||||
ng-title=":: tick.fullText || tick.text">
|
|
||||||
{{:: tick.text | reverse}}
|
|
||||||
</div>
|
|
||||||
</mct-ticks>
|
|
||||||
|
|
||||||
<div class="gl-plot-label gl-plot-x-label">
|
|
||||||
{{ xAxis.get('label') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
@ -1,130 +0,0 @@
|
|||||||
<!--
|
|
||||||
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.
|
|
||||||
-->
|
|
||||||
<div ng-controller="PlotOptionsController"
|
|
||||||
class="flex-elem grows l-inspector-part">
|
|
||||||
<ul class="flex-elem grows l-inspector-part"><li>
|
|
||||||
<em class="t-inspector-part-header">Plot Series</em>
|
|
||||||
<ul class="first flex-elem grows vscroll">
|
|
||||||
<ul class="tree">
|
|
||||||
<li ng-repeat="series in config.series.models">
|
|
||||||
<span class="tree-item menus-to-left">
|
|
||||||
<span
|
|
||||||
class='ui-symbol view-control flex-elem'
|
|
||||||
ng-class="{ expanded: series.expanded }"
|
|
||||||
ng-click="series.expanded = !series.expanded">
|
|
||||||
</span>
|
|
||||||
<mct-representation
|
|
||||||
class="rep-object-label"
|
|
||||||
key="'label'"
|
|
||||||
mct-object="series.oldObject">
|
|
||||||
</mct-representation>
|
|
||||||
</span>
|
|
||||||
<div class="l-flex-col inspector-config" ng-show="series.expanded">
|
|
||||||
<div class="inspector-properties">
|
|
||||||
<div class="label">Line Style</div>
|
|
||||||
<div class="value">{{ {
|
|
||||||
'none': 'None',
|
|
||||||
'linear': 'Linear interpolation',
|
|
||||||
'stepAfter': 'Step After'
|
|
||||||
}[series.get('interpolate')] }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="inspector-properties">
|
|
||||||
<div class="label">Markers</div>
|
|
||||||
<div class="value">
|
|
||||||
{{series.get('markers') ? "On, " + series.get('markerSize') + "px" : "Off"}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="inspector-properties">
|
|
||||||
<div class="label">Color</div>
|
|
||||||
<div class="value">
|
|
||||||
<span class="color-swatch"
|
|
||||||
ng-style="{
|
|
||||||
'background': series.get('color').asHexString(),
|
|
||||||
'display': 'inline-block',
|
|
||||||
'border': '1px solid rgba(255, 255, 255, 0.2)',
|
|
||||||
'height': '10px',
|
|
||||||
'width': '10px',
|
|
||||||
'vertical-align': 'middle',
|
|
||||||
'margin-left': '3px',
|
|
||||||
'margin-top': -'2px'
|
|
||||||
}">
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<em class="t-inspector-part-header">Y Axis</em>
|
|
||||||
<div class="inspector-properties first">
|
|
||||||
<div class="label">Label</div>
|
|
||||||
<div class="value">{{ config.yAxis.get('label') }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="inspector-properties">
|
|
||||||
<div class="label">Autoscale</div>
|
|
||||||
<div class="value">
|
|
||||||
{{ config.yAxis.get('autoscale') ? "On" : "Off" }}
|
|
||||||
{{ config.yAxis.get('autoscale') ? (config.yAxis.get('autoscalePadding') * 100) + "%" : ""}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="inspector-properties"
|
|
||||||
ng-if="!form.yAxis.autoscale">
|
|
||||||
<div class="label">Min</div>
|
|
||||||
<div class="value">{{ config.yAxis.get('range').min }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="inspector-properties"
|
|
||||||
ng-if="!form.yAxis.autoscale">
|
|
||||||
<div class="label">Max</div>
|
|
||||||
<div class="value">{{ config.yAxis.get('range').max }}</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<em class="t-inspector-part-header">Legend</em>
|
|
||||||
<div class="inspector-properties first">
|
|
||||||
<div class="label">Position</div>
|
|
||||||
<div class="value">{{ config.legend.get('position') }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="inspector-properties">
|
|
||||||
<div class="label">Expand by Default</div>
|
|
||||||
<div class="value">{{ config.legend.get('expandByDefault') ? "Yes" : "No" }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="inspector-properties">
|
|
||||||
<div class="label">Show when collapsed:</div>
|
|
||||||
<div class="value">{{
|
|
||||||
config.legend.get('valueToShowWhenCollapsed').replace('nearest', '')
|
|
||||||
}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="inspector-properties">
|
|
||||||
<div class="label">Show when expanded:</div>
|
|
||||||
<div class="value comma-list">
|
|
||||||
<span ng-if="config.legend.get('showTimestampWhenExpanded')">Timestamp</span>
|
|
||||||
<span ng-if="config.legend.get('showValueWhenExpanded')">Value</span>
|
|
||||||
<span ng-if="config.legend.get('showMinimumWhenExpanded')">Min</span>
|
|
||||||
<span ng-if="config.legend.get('showMaximumWhenExpanded')">Max</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
@ -1,229 +0,0 @@
|
|||||||
<!--
|
|
||||||
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.
|
|
||||||
-->
|
|
||||||
<div ng-controller="PlotOptionsController"
|
|
||||||
class="flex-elem grows l-inspector-part">
|
|
||||||
<em class="t-inspector-part-header"
|
|
||||||
title="Display properties for this object">
|
|
||||||
Series Options
|
|
||||||
</em>
|
|
||||||
<ul class="first flex-elem grows vscroll">
|
|
||||||
<ul class="tree">
|
|
||||||
<li ng-repeat="series in config.series.models">
|
|
||||||
<span class="tree-item menus-to-left">
|
|
||||||
<span
|
|
||||||
class='ui-symbol view-control flex-elem'
|
|
||||||
ng-class="{ expanded: series.expanded }"
|
|
||||||
ng-click="series.expanded = !series.expanded">
|
|
||||||
</span>
|
|
||||||
<mct-representation
|
|
||||||
class="rep-object-label"
|
|
||||||
key="'label'"
|
|
||||||
mct-object="series.oldObject">
|
|
||||||
</mct-representation>
|
|
||||||
</span>
|
|
||||||
<div class="inspector-config" ng-show="series.expanded">
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<label>Value:</label>
|
|
||||||
<span class="control">
|
|
||||||
<div class="select">
|
|
||||||
<select ng-model="form.series[$index].yKey">
|
|
||||||
<option ng-repeat="option in form.series[$index].yAxisOptions"
|
|
||||||
value="{{option.value}}"
|
|
||||||
ng-selected="option.value == form.series[$index].yKey">
|
|
||||||
{{option.name}}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label>Line Style:</label>
|
|
||||||
<span class="control">
|
|
||||||
<div class="select">
|
|
||||||
<select ng-model="form.series[$index].interpolate">
|
|
||||||
<option value="none">None</option>
|
|
||||||
<option value="linear">Linear interpolate</option>
|
|
||||||
<option value="stepAfter">Step After</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li class="controls-first">
|
|
||||||
<label>Show Markers</label>
|
|
||||||
<span class="control"><input type="checkbox" ng-model="form.series[$index].markers"/></span>
|
|
||||||
</li>
|
|
||||||
<li class="controls-first">
|
|
||||||
<label>Show Alarm Markers</label>
|
|
||||||
<span class="control"><input type="checkbox" ng-model="form.series[$index].alarmMarkers"/></span>
|
|
||||||
</li>
|
|
||||||
<li ng-show="form.series[$index].markers || form.series[$index].alarmMarkers">
|
|
||||||
<label>Marker Size:</label>
|
|
||||||
<span class="control"><input class="sm" type="text" ng-model="form.series[$index].markerSize"/></span>
|
|
||||||
</li>
|
|
||||||
<ul ng-controller="ClickAwayController as toggle" ng-show="form.series[$index].interpolate !== 'none' || form.series[$index].markers">
|
|
||||||
<li>
|
|
||||||
<label>Color:</label>
|
|
||||||
<span class="control">
|
|
||||||
<div class="s-menu-button" ng-click="toggle.toggle()">
|
|
||||||
<span class="color-swatch" ng-style="{ background: series.get('color').asHexString() }">
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li class="connects-to-previous l-inline-palette" ng-show="toggle.isActive()">
|
|
||||||
<div class="l-palette-row" ng-repeat="group in config.series.palette.groups()">
|
|
||||||
<div class="l-palette-item s-palette-item"
|
|
||||||
ng-repeat="color in group"
|
|
||||||
xng-class="{ 'icon-check': series.get('color') === color }"
|
|
||||||
ng-style="{ background: color.asHexString() }"
|
|
||||||
ng-click="setColor(series, color, config.series.models.indexOf(series))">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</ul>
|
|
||||||
<form class="inspector-config"
|
|
||||||
ng-show="!!config.series.models.length">
|
|
||||||
<em class="t-inspector-part-header"
|
|
||||||
title="Options for plot axes display">
|
|
||||||
Y Axis
|
|
||||||
</em>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<label>Label:</label>
|
|
||||||
<input class="control" type="text" ng-model="form.yAxis.label"/>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul ng-show="!(form.yAxis.key == 'enum')">
|
|
||||||
<li class="section-header">Scaling</li>
|
|
||||||
<li class="controls-first">
|
|
||||||
<label>Autoscale</label>
|
|
||||||
<span class="control">
|
|
||||||
<input type="checkbox" ng-model="form.yAxis.autoscale"/>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li class="form-error"
|
|
||||||
ng-show="!(form.yAxis.key == 'enum') && !form.yAxis.autoscale && validation['form.yAxis.range']">
|
|
||||||
{{ validation['form.yAxis.range'] }}
|
|
||||||
</li>
|
|
||||||
<li ng-show="!form.yAxis.autoscale">
|
|
||||||
<label>Minimum:</label>
|
|
||||||
<span class="control">
|
|
||||||
<input class="sm" type="text" ng-model="form.yAxis.range.min"/>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li ng-show="!form.yAxis.autoscale">
|
|
||||||
<label>Maximum:</label>
|
|
||||||
<span class="control">
|
|
||||||
<input class="sm" type="text" ng-model="form.yAxis.range.max"/>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li ng-show="form.yAxis.autoscale">
|
|
||||||
<label>Padding:</label>
|
|
||||||
<span class="control">
|
|
||||||
<input class="sm" type="text" ng-model="form.yAxis.autoscalePadding"/>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<em class="t-inspector-part-header"
|
|
||||||
title="Options for legend display">
|
|
||||||
Legend
|
|
||||||
</em>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<label>Position:</label>
|
|
||||||
<span class="control">
|
|
||||||
<div class="select">
|
|
||||||
<select ng-model="form.legend.position">
|
|
||||||
<option value="hidden">Hidden</option>
|
|
||||||
<option value="top">Top</option>
|
|
||||||
<option value="right">Right</option>
|
|
||||||
<option value="bottom">Bottom</option>
|
|
||||||
<option value="left">Left</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li class="controls-first">
|
|
||||||
<label>Expand by default</label>
|
|
||||||
<span class="control">
|
|
||||||
<input type="checkbox" ng-model="form.legend.expandByDefault"/>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li class="controls-under">
|
|
||||||
<label>Show when collapsed:</label>
|
|
||||||
<span class="control">
|
|
||||||
<div class="select">
|
|
||||||
<select ng-model="form.legend.valueToShowWhenCollapsed">
|
|
||||||
<option value="none">None</option>
|
|
||||||
<option value="nearestTimestamp">Nearest Timestamp</option>
|
|
||||||
<option value="nearestValue">Nearest Value</option>
|
|
||||||
<option value="min">Minimum</option>
|
|
||||||
<option value="max">Maximum</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li class="controls-under">
|
|
||||||
<ul>
|
|
||||||
<li><label>Show when expanded:</label></li>
|
|
||||||
<li class="controls-first">
|
|
||||||
<label>Nearest Timestamp</label>
|
|
||||||
<span class="control">
|
|
||||||
<input type="checkbox"
|
|
||||||
ng-model="form.legend.showTimestampWhenExpanded"/>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li class="controls-first">
|
|
||||||
<label>Nearest Value</label>
|
|
||||||
<span class="control">
|
|
||||||
<input type="checkbox"
|
|
||||||
ng-model="form.legend.showValueWhenExpanded"/>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li class="controls-first">
|
|
||||||
<label>Minimum</label>
|
|
||||||
<span class="control">
|
|
||||||
<input type="checkbox"
|
|
||||||
ng-model="form.legend.showMinimumWhenExpanded"/>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li class="controls-first">
|
|
||||||
<label>Maximum</label>
|
|
||||||
<span class="control">
|
|
||||||
<input type="checkbox"
|
|
||||||
ng-model="form.legend.showMaximumWhenExpanded"/>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
@ -1,31 +0,0 @@
|
|||||||
<!--
|
|
||||||
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.
|
|
||||||
-->
|
|
||||||
<div ng-if="domainObject.getCapability('editor').inEditContext()">
|
|
||||||
<mct-representation key="'plot-options-edit'"
|
|
||||||
mct-object="domainObject">
|
|
||||||
</mct-representation>
|
|
||||||
</div>
|
|
||||||
<div ng-if="!domainObject.getCapability('editor').inEditContext()">
|
|
||||||
<mct-representation key="'plot-options-browse'"
|
|
||||||
mct-object="domainObject">
|
|
||||||
</mct-representation>
|
|
||||||
</div>
|
|
@ -1,50 +0,0 @@
|
|||||||
<!--
|
|
||||||
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.
|
|
||||||
-->
|
|
||||||
<span ng-controller="PlotController as controller"
|
|
||||||
class="abs holder holder-plot has-control-bar"
|
|
||||||
ng-class="{
|
|
||||||
'loading': !!pending
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="l-control-bar" ng-show="!controller.hideExportButtons">
|
|
||||||
<span class="l-btn-set">
|
|
||||||
<a class="s-button t-export labeled icon-download"
|
|
||||||
ng-click="controller.exportPNG()"
|
|
||||||
title="Export This View's Data as PNG">
|
|
||||||
PNG
|
|
||||||
</a>
|
|
||||||
<a class="s-button t-export labeled"
|
|
||||||
ng-click="controller.exportJPG()"
|
|
||||||
title="Export This View's Data as JPG">
|
|
||||||
JPG
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="l-view-section">
|
|
||||||
<mct-plot config="controller.config"
|
|
||||||
series="series"
|
|
||||||
the-y-axis="yAxis"
|
|
||||||
the-x-axis="xAxis">
|
|
||||||
</mct-plot>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
@ -1,53 +0,0 @@
|
|||||||
<!--
|
|
||||||
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.
|
|
||||||
-->
|
|
||||||
<span ng-controller="StackedPlotController as stackedPlot"
|
|
||||||
class="abs holder holder-plot has-control-bar t-plot-stacked"
|
|
||||||
ng-class="{
|
|
||||||
'loading': !!currentRequest.pending
|
|
||||||
}">
|
|
||||||
|
|
||||||
<div class="l-control-bar" ng-show="!stackedPlot.hideExportButtons">
|
|
||||||
<span class="l-btn-set">
|
|
||||||
<a class="s-button t-export labeled icon-download"
|
|
||||||
ng-click="stackedPlot.exportPNG()"
|
|
||||||
title="Export This View's Data as PNG">
|
|
||||||
PNG
|
|
||||||
</a>
|
|
||||||
<a class="s-button t-export labeled"
|
|
||||||
ng-click="stackedPlot.exportJPG()"
|
|
||||||
title="Export This View's Data as JPG">
|
|
||||||
JPG
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="l-view-section">
|
|
||||||
<div class="gl-plot child-frame"
|
|
||||||
ng-repeat="telemetryObject in telemetryObjects"
|
|
||||||
ng-class="{
|
|
||||||
's-status-timeconductor-unsynced': telemetryObject
|
|
||||||
.getCapability('status')
|
|
||||||
.get('timeconductor-unsynced')
|
|
||||||
}">
|
|
||||||
<mct-overlay-plot domain-object="telemetryObject"></mct-overlay-plot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
@ -1,77 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
});
|
|
@ -1,401 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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);
|
|
||||||
window.chart = 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;
|
|
||||||
});
|
|
@ -1,67 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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 = "<canvas style='position: absolute; background: none; width: 100%; height: 100%;'></canvas>";
|
|
||||||
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;
|
|
||||||
});
|
|
@ -1,40 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user