Plot composition re-ordering support (#2120)

* [Plot] Allow 0 in plot y axis bounds

Allow 0 values in plot y axis bounds.  Also properly re-do validation
when changing from a bad value to the previously used good value.

Fixes https://github.com/nasa/openmct/issues/2082

* [Plot] separate config index from composition index

Locate the configuration index by checking identifiers instead of
relying on order of composition to match order of configuration.

Ensures that editing a plot after reordering composition does not
edit wrong object.

Fixes https://github.com/nasa/openmct/issues/2101

* [Plot] refactor form controllers, separate concerns

Refactor form controllers to simplify the plot options inspector.

Individual plot models each have a form controller which handles
three way binding between the scope, model, and domain object configuration.

Added support to linkFields for dynamic object paths so that mutations
always find the right object in the configuration instead of breaking
when indices change.

* support deferred init for sub-object selection

* Style fixes

* Remove assignment to window
This commit is contained in:
Pete Richards 2018-07-20 14:14:01 -07:00 committed by Pegah Sarram
parent cc22fd4e9d
commit 8d21d420ae
9 changed files with 573 additions and 229 deletions

View File

@ -31,6 +31,9 @@ define([
"./src/telemetry/StackedPlotController",
"./src/inspector/PlotInspector",
"./src/inspector/PlotOptionsController",
"./src/inspector/PlotLegendFormController",
"./src/inspector/PlotYAxisFormController",
"./src/inspector/PlotSeriesFormController",
"./src/inspector/HideElementPoolDirective",
"./src/services/ExportImageService",
'./src/PlotViewPolicy',
@ -48,6 +51,9 @@ define([
StackedPlotController,
PlotInspector,
PlotOptionsController,
PlotLegendFormController,
PlotYAxisFormController,
PlotSeriesFormController,
HideElementPool,
ExportImageService,
PlotViewPolicy,
@ -171,6 +177,33 @@ define([
"openmct",
"$timeout"
]
},
{
key: "PlotLegendFormController",
implementation: PlotLegendFormController,
depends: [
"$scope",
"openmct",
"$attrs"
]
},
{
key: "PlotYAxisFormController",
implementation: PlotYAxisFormController,
depends: [
"$scope",
"openmct",
"$attrs"
]
},
{
key: "PlotSeriesFormController",
implementation: PlotSeriesFormController,
depends: [
"$scope",
"openmct",
"$attrs"
]
}
],
"services": [

View File

@ -22,28 +22,30 @@
<div ng-controller="PlotOptionsController">
<ul class="tree l-inspector-part">
<h2 title="Display properties for this object">Plot Series Options</h2>
<li ng-repeat="series in config.series.models" ng-init="seriesForm = form.series[$index]">
<li ng-repeat="series in plotSeries"
ng-controller="PlotSeriesFormController"
form-model="series">
<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">
ng-class="{ expanded: expanded }"
ng-click="expanded = !expanded">
</span>
<mct-representation class="rep-object-label"
key="'label'"
mct-object="series.oldObject">
</mct-representation>
</span>
<ul class="grid-properties" ng-show="series.expanded">
<ul class="grid-properties" ng-show="expanded">
<li class="grid-row">
<!-- Value to be displayed -->
<div class="grid-cell label"
title="The field to be plotted as a value for this series.">Value</div>
<div class="grid-cell value">
<div class="select">
<select ng-model="seriesForm.yKey">
<option ng-repeat="option in seriesForm.yAxisOptions"
<select ng-model="form.yKey">
<option ng-repeat="option in yKeyOptions"
value="{{option.value}}"
ng-selected="option.value == seriesForm.yKey">
ng-selected="option.value == form.yKey">
{{option.name}}
</option>
</select>
@ -55,7 +57,7 @@
title="The line rendering style for this series.">Line Style</div>
<div class="grid-cell value">
<div class="select">
<select ng-model="seriesForm.interpolate">
<select ng-model="form.interpolate">
<option value="none">None</option>
<option value="linear">Linear interpolate</option>
<option value="stepAfter">Step after</option>
@ -66,21 +68,21 @@
<li class="grid-row">
<div class="grid-cell label"
title="Whether markers are displayed.">Markers</div>
<div class="grid-cell value"><input type="checkbox" ng-model="seriesForm.markers"/></div>
<div class="grid-cell value"><input type="checkbox" ng-model="form.markers"/></div>
</li>
<li class="grid-row">
<div class="grid-cell label"
title="Display markers visually denoting points in alarm.">Alarm Markers</div>
<div class="grid-cell value"><input type="checkbox" ng-model="seriesForm.alarmMarkers"/></div>
<div class="grid-cell value"><input type="checkbox" ng-model="form.alarmMarkers"/></div>
</li>
<li class="grid-row" ng-show="seriesForm.markers || seriesForm.alarmMarkers">
<li class="grid-row" ng-show="form.markers || form.alarmMarkers">
<div class="grid-cell label"
title="The size of regular and alarm markers for this series.">Marker Size:</div>
<div class="grid-cell value"><input class="sm" type="text" ng-model="seriesForm.markerSize"/></div>
<div class="grid-cell value"><input class="sm" type="text" ng-model="form.markerSize"/></div>
</li>
<li class="grid-row"
ng-controller="ClickAwayController as toggle"
ng-show="seriesForm.interpolate !== 'none' || seriesForm.markers">
ng-show="form.interpolate !== 'none' || form.markers">
<div class="grid-cell label"
title="Manually set the plot line and marker color for this series.">Color</div>
<div class="grid-cell value">
@ -93,9 +95,9 @@
<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-class="{ 'selected': series.get('color').equalTo(color) }"
ng-style="{ background: color.asHexString() }"
ng-click="setColor(series, color, config.series.models.indexOf(series))">
ng-click="setColor(color)">
</div>
</div>
</div>
@ -104,57 +106,62 @@
</ul>
</li>
</ul>
<div class="grid-properties" ng-show="!!config.series.models.length">
<div class="grid-properties"
ng-show="!!config.series.models.length"
ng-controller="PlotYAxisFormController"
form-model="config.yAxis">
<ul class="l-inspector-part">
<h2>Y Axis</h2>
<li class="grid-row">
<div class="grid-cell label"
title="Manually override how the Y axis is labeled.">Label</div>
<div class="grid-cell value"><input class="control" type="text" ng-model="form.yAxis.label"/></div>
<div class="grid-cell value"><input class="control" type="text" ng-model="form.label"/></div>
</li>
</ul>
<ul class="l-inspector-part" ng-show="!(form.yAxis.key == 'enum')">
<ul class="l-inspector-part">
<h2>Y Axis Scaling</h2>
<li class="grid-row">
<div class="grid-cell label"
title="Automatically scale the Y axis to keep all values in view.">Autoscale</div>
<div class="grid-cell value"><input type="checkbox" ng-model="form.yAxis.autoscale"/></div>
<div class="grid-cell value"><input type="checkbox" ng-model="form.autoscale"/></div>
</li>
<li class="grid-row" ng-show="form.yAxis.autoscale">
<li class="grid-row" ng-show="form.autoscale">
<div class="grid-cell label"
title="Percentage of padding above and below plotted min and max values. 0.1, 1.0, etc.">
Padding</div>
<div class="grid-cell value">
<input class="sm" type="text" ng-model="form.yAxis.autoscalePadding"/>
<input class="sm" type="text" ng-model="form.autoscalePadding"/>
</div>
</li>
</ul>
<ul class="l-inspector-part" ng-show="!form.yAxis.autoscale">
<ul class="l-inspector-part" ng-show="!form.autoscale">
<div class="grid-span-all form-error"
ng-show="!(form.yAxis.key == 'enum') && !form.yAxis.autoscale && validation['form.yAxis.range']">
{{ validation['form.yAxis.range'] }}
ng-show="!form.autoscale && validation.range">
{{ validation.range }}
</div>
<li class="grid-row force-border">
<div class="grid-cell label"
title="Minimum Y axis value.">Minimum Value</div>
<div class="grid-cell value">
<input class="sm" type="text" ng-model="form.yAxis.range.min"/>
<input class="sm" type="number" ng-model="form.range.min"/>
</div>
</li>
<li class="grid-row">
<div class="grid-cell label"
title="Maximum Y axis value.">Maximum Value</div>
<div class="grid-cell value"><input class="sm" type="text" ng-model="form.yAxis.range.max"/></div>
<div class="grid-cell value"><input class="sm" type="number" ng-model="form.range.max"/></div>
</li>
</ul>
<ul class="l-inspector-part">
</div>
<div class="grid-properties" ng-show="!!config.series.models.length">
<ul class="l-inspector-part" ng-controller="PlotLegendFormController" form-model="config.legend">
<h2 title="Legend options">Legend</h2>
<li class="grid-row">
<div class="grid-cell label"
title="The position of the legend relative to the plot display area.">Position</div>
<div class="grid-cell value">
<div class="select">
<select ng-model="form.legend.position">
<select ng-model="form.position">
<option value="hidden">Hidden</option>
<option value="top">Top</option>
<option value="right">Right</option>
@ -167,14 +174,14 @@
<li class="grid-row">
<div class="grid-cell label"
title="Show the legend expanded by default">Expand by default</div>
<div class="grid-cell value"><input type="checkbox" ng-model="form.legend.expandByDefault"/></div>
<div class="grid-cell value"><input type="checkbox" ng-model="form.expandByDefault"/></div>
</li>
<li class="grid-row">
<div class="grid-cell label"
title="What to display in the legend when it's collapsed.">When collapsed show</div>
<div class="grid-cell value">
<div class="select">
<select ng-model="form.legend.valueToShowWhenCollapsed">
<select ng-model="form.valueToShowWhenCollapsed">
<option value="none">nothing</option>
<option value="nearestTimestamp">nearest timestamp</option>
<option value="nearestValue">nearest Value</option>
@ -190,13 +197,13 @@
<div class="grid-cell value">
<ul>
<li><input type="checkbox"
ng-model="form.legend.showTimestampWhenExpanded"/> Nearest timestamp</li>
ng-model="form.showTimestampWhenExpanded"/> Nearest timestamp</li>
<li><input type="checkbox"
ng-model="form.legend.showValueWhenExpanded"/> Nearest value</li>
ng-model="form.showValueWhenExpanded"/> Nearest value</li>
<li><input type="checkbox"
ng-model="form.legend.showMinimumWhenExpanded"/> Minimum value</li>
ng-model="form.showMinimumWhenExpanded"/> Minimum value</li>
<li><input type="checkbox"
ng-model="form.legend.showMaximumWhenExpanded"/> Maximum value</li>
ng-model="form.showMaximumWhenExpanded"/> Maximum value</li>
</ul>
</div>

View File

@ -116,9 +116,20 @@ define([
initialize: function (options) {
this.openmct = options.openmct;
this.domainObject = options.domainObject;
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject);
this.on('destroy', this.onDestroy, this);
},
locateOldObject: function (oldStyleParent) {
return oldStyleParent.useCapability('composition')
.then(function (children) {
this.oldObject = children
.filter(function (child) {
return child.getId() === this.keyString;
}, this)[0];
}.bind(this));
},
/**
* Fetch historical data and establish a realtime subscription. Returns
* a promise that is resolved when all connections have been successfully

View File

@ -64,28 +64,27 @@ define([
this.listenTo(composition, 'remove', this.removeTelemetryObject, this);
composition.load();
},
addTelemetryObject: function (domainObject) {
var seriesConfig = {
identifier: domainObject.identifier
};
addTelemetryObject: function (domainObject, index) {
var seriesConfig = this.plot.getPersistedSeriesConfig(domainObject.identifier);
var plotObject = this.plot.get('domainObject');
if (plotObject.type === 'telemetry.plot.overlay') {
var index = this.size();
if (!plotObject.configuration.series[index]) {
if (!seriesConfig) {
seriesConfig = {
identifier: domainObject.identifier
};
if (plotObject.type === 'telemetry.plot.overlay') {
this.openmct.objects.mutate(
plotObject,
'configuration.series[' + index + ']',
'configuration.series[' + this.size() + ']',
seriesConfig
);
seriesConfig = this.plot
.getPersistedSeriesConfig(domainObject.identifier);
}
seriesConfig = this.plot.get('domainObject').configuration.series[index];
// Clone to prevent accidental mutation by ref.
seriesConfig = JSON.parse(JSON.stringify(seriesConfig));
}
seriesConfig.persistedConfiguration =
this.plot.getPersistedSeriesConfig(domainObject.identifier);
// Clone to prevent accidental mutation by ref.
seriesConfig = JSON.parse(JSON.stringify(seriesConfig));
this.add(new PlotSeries({
model: seriesConfig,
@ -95,11 +94,6 @@ define([
}));
},
removeTelemetryObject: function (identifier) {
// TODO: properly locate in self (and parent configuration)
// Instead of binding via index, which is not guaranteed because
// edits could occur when plotcontroller is not instantiated.
// This bug also extends to the plotOptions form which currently
// relies on indexes that match.
var plotObject = this.plot.get('domainObject');
if (plotObject.type === 'telemetry.plot.overlay') {
var index = _.findIndex(plotObject.configuration.series, function (s) {

View File

@ -0,0 +1,69 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./PlotModelFormController'
], function (
PlotModelFormController
) {
var PlotLegendFormController = PlotModelFormController.extend({
fields: [
{
modelProp: 'position',
objectPath: 'configuration.legend.position'
},
{
modelProp: 'expandByDefault',
coerce: Boolean,
objectPath: 'configuration.legend.expandByDefault'
},
{
modelProp: 'valueToShowWhenCollapsed',
objectPath: 'configuration.legend.valueToShowWhenCollapsed'
},
{
modelProp: 'showValueWhenExpanded',
coerce: Boolean,
objectPath: 'configuration.legend.showValueWhenExpanded'
},
{
modelProp: 'showTimestampWhenExpanded',
coerce: Boolean,
objectPath: 'configuration.legend.showTimestampWhenExpanded'
},
{
modelProp: 'showMaximumWhenExpanded',
coerce: Boolean,
objectPath: 'configuration.legend.showMaximumWhenExpanded'
},
{
modelProp: 'showMinimumWhenExpanded',
coerce: Boolean,
objectPath: 'configuration.legend.showMinimumWhenExpanded'
}
]
});
return PlotLegendFormController;
});

View File

@ -0,0 +1,173 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'../lib/eventHelpers',
'../lib/extend',
'lodash'
], function (
eventHelpers,
extend,
_
) {
/**
* Generic type for model controllers. Should be extended to build a form
* for a specific model.
*/
function PlotModelFormController($scope, openmct, attrs) {
this.$scope = $scope;
this.openmct = openmct;
this.attrs = attrs;
if (this.isReady()) {
this.initializeScope();
} else {
this.$scope.$watch(this.isReady.bind(this), function (isReady) {
if (isReady) {
this.initializeScope();
}
}.bind(this));
}
}
PlotModelFormController.extend = extend;
eventHelpers.extend(PlotModelFormController.prototype);
PlotModelFormController.prototype.isReady = function () {
return !!this.$scope.formDomainObject &&
!!this.$scope.$eval(this.attrs.formModel);
};
/**
* Initialize scope is called when the formDomainObject has been set.
* This may be deferred until after the controller construction in cases
* where the object has not yet loaded.
*/
PlotModelFormController.prototype.initializeScope = function () {
this.domainObject = this.$scope.formDomainObject;
this.model = this.$scope.$eval(this.attrs.formModel);
this.unlisten = this.openmct.objects.observe(
this.domainObject,
'*',
this.updateDomainObject.bind(this)
);
this.$scope.form = {};
this.$scope.validation = {};
this.listenTo(this.$scope, '$destroy', this.destroy, this);
this.initialize();
this.initForm();
};
PlotModelFormController.prototype.updateDomainObject = function (domainObject) {
this.domainObject = domainObject;
};
PlotModelFormController.prototype.destroy = function () {
this.stopListening();
this.model.stopListening(this.$scope);
this.unlisten();
};
PlotModelFormController.prototype.fields = [];
/** override for custom initializer **/
PlotModelFormController.prototype.initialize = function () {
};
PlotModelFormController.prototype.initForm = function () {
this.fields.forEach(function (field) {
this.linkFields(
field.modelProp,
field.formProp,
field.coerce,
field.validate,
field.objectPath
);
}, this);
};
PlotModelFormController.prototype.linkFields = function (
prop,
formProp,
coerce,
validate,
objectPath
) {
if (!formProp) {
formProp = prop;
}
var formPath = 'form.' + formProp;
if (!coerce) {
coerce = function (v) {
return v;
};
}
if (!validate) {
validate = function () {
return true;
};
}
if (objectPath && !_.isFunction(objectPath)) {
var staticObjectPath = objectPath;
objectPath = function () {
return staticObjectPath;
};
}
this.listenTo(this.model, 'change:' + prop, function (newVal, oldVal) {
if (!_.isEqual(coerce(_.get(this.$scope, formPath)), coerce(newVal))) {
_.set(this.$scope, formPath, coerce(newVal));
}
}, this);
this.model.listenTo(this.$scope, 'change:' + formPath, function (newVal, oldVal) {
var validationResult = validate(newVal, this.model);
if (validationResult === true) {
delete this.$scope.validation[formProp];
} else {
this.$scope.validation[formProp] = validationResult;
return;
}
if (_.isEqual(coerce(newVal), coerce(this.model.get(prop)))) {
return; // Don't trigger excessive mutations.
}
if (!_.isEqual(coerce(newVal), coerce(oldVal))) {
this.model.set(prop, coerce(newVal));
if (objectPath) {
this.openmct.objects.mutate(
this.domainObject,
objectPath(this.domainObject, this.model),
coerce(newVal)
);
}
}
}, this);
_.set(this.$scope, formPath, coerce(this.model.get(prop)));
};
return PlotModelFormController;
});

View File

@ -32,15 +32,6 @@ define([
_
) {
/**
* The LayoutController is responsible for supporting the
* Layout view. It arranges frames according to saved configuration
* and provides methods for updating these based on mouse
* movement.
* @memberof platform/features/plot
* @constructor
* @param {Scope} $scope the controller's Angular scope
*/
function PlotOptionsController($scope, openmct, $timeout) {
this.$scope = $scope;
this.openmct = openmct;
@ -52,31 +43,6 @@ define([
eventHelpers.extend(PlotOptionsController.prototype);
PlotOptionsController.prototype.setColor = function (series, color) {
var seriesWithColor = this.config.series.filter(function (s) {
return s.get('color') === color;
})[0];
var oldColor = series.get('color');
series.set('color', color);
var seriesIndex = this.config.series.indexOf(series);
this.openmct.objects.mutate(
this.domainObject,
'configuration.series[' + seriesIndex + '].color',
color.asHexString()
);
if (seriesWithColor) {
seriesWithColor.set('color', oldColor);
var oldSeriesIndex = this.config.series.indexOf(seriesWithColor);
this.openmct.objects.mutate(
this.domainObject,
'configuration.series[' + oldSeriesIndex + '].color',
oldColor.asHexString()
);
}
};
PlotOptionsController.prototype.updateDomainObject = function (domainObject) {
this.domainObject = domainObject;
};
@ -94,163 +60,32 @@ define([
return;
}
configStore.track(this.configId);
this.config = this.$scope.config = config;
this.$scope.setColor = this.setColor.bind(this);
this.$scope.form = {series: []};
this.$scope.validation = {};
this.$scope.plotSeries = [];
this.domainObject = this.config.get('domainObject');
this.$scope.formDomainObject = this.domainObject;
this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject.bind(this));
this.listenTo(this.$scope, '$destroy', this.destroy, this);
this.listenTo(config.series, 'add', this.addSeries, this);
this.listenTo(config.series, 'remove', this.resetAllSeries, this);
config.series.forEach(this.addSeries, this);
this.linkFields(config.yAxis, 'label', 'form.yAxis.label', undefined, undefined, 'configuration.yAxis.label');
this.linkFields(config.yAxis, 'autoscale', 'form.yAxis.autoscale', Boolean, undefined, 'configuration.yAxis.autoscale');
this.linkFields(config.yAxis, 'autoscalePadding', 'form.yAxis.autoscalePadding', Number, undefined, 'configuration.yAxis.autoscalePadding');
this.linkFields(config.yAxis, 'range', 'form.yAxis.range', function coerceRange(range) {
if (!range) {
return {
min: 0,
max: 0
};
}
var newRange = {};
if (typeof range.min !== undefined) {
newRange.min = Number(range.min);
}
if (typeof range.max !== undefined) {
newRange.max = Number(range.max);
}
return newRange;
}, function validateRange(range) {
if (!range) {
return 'Need range';
}
if (!range.min) {
return 'Must specify Minimum';
}
if (!range.max) {
return 'Must specify Maximum';
}
if (_.isNaN(Number(range.min))) {
return 'Minimum must be a number.';
}
if (_.isNaN(Number(range.max))) {
return 'Maximum must be a number.';
}
if (Number(range.min) > Number(range.max)) {
return 'Minimum must be less than Maximum.';
}
if (config.yAxis.get('autoscale')) {
return false;
}
return true;
}, 'configuration.yAxis.range');
this.linkFields(config.legend, 'position', 'form.legend.position', undefined, undefined, 'configuration.legend.position');
this.linkFields(config.legend, 'expandByDefault', 'form.legend.expandByDefault', Boolean, undefined, 'configuration.legend.expandByDefault');
this.linkFields(config.legend, 'valueToShowWhenCollapsed', 'form.legend.valueToShowWhenCollapsed', undefined, undefined, 'configuration.legend.valueToShowWhenCollapsed');
this.linkFields(config.legend, 'showValueWhenExpanded', 'form.legend.showValueWhenExpanded', Boolean, undefined, 'configuration.legend.showValueWhenExpanded');
this.linkFields(config.legend, 'showTimestampWhenExpanded', 'form.legend.showTimestampWhenExpanded', Boolean, undefined, 'configuration.legend.showTimestampWhenExpanded');
this.linkFields(config.legend, 'showMaximumWhenExpanded', 'form.legend.showMaximumWhenExpanded', Boolean, undefined, 'configuration.legend.showMaximumWhenExpanded');
this.linkFields(config.legend, 'showMinimumWhenExpanded', 'form.legend.showMinimumWhenExpanded', Boolean, undefined, 'configuration.legend.showMinimumWhenExpanded');
};
PlotOptionsController.prototype.addSeries = function (series, index) {
if (this.$scope.form.series[index]) {
// the way listeners work, this will blow up. At this point, it
// can't technically occur, but if we added some sort of reordering
// at a later date, it would not be clear why this doesn't work.
// So here's to hoping this helps debugging.
throw new Error('Plot options does not support insert at index.');
}
var metadata = series.metadata;
this.$scope.form.series[index] = {
yAxisOptions: metadata.valuesForHints(['range']).map(function (o) {
return {
name: o.key,
value: o.key
};
})
};
var seriesObject = series.domainObject;
var seriesId = objectUtils.makeKeyString(seriesObject.identifier);
var configPath = 'configuration.series[' + index + '].';
var path = 'form.series[' + index + '].';
this.$scope.domainObject.useCapability('composition')
.then(function (children) {
children.forEach(function (child) {
if (child.getId() === seriesId) {
series.oldObject = child;
}
});
}.bind(this));
this.$scope.plotSeries[index] = series;
series.locateOldObject(this.$scope.domainObject);
this.linkFields(series, 'yKey', path + 'yKey', undefined, undefined, configPath + 'yKey');
this.linkFields(series, 'interpolate', path + 'interpolate', undefined, undefined, configPath + 'interpolate');
this.linkFields(series, 'markers', path + 'markers', undefined, undefined, configPath + 'markers');
this.linkFields(series, 'markerSize', path + 'markerSize', Number, undefined, configPath + 'markerSize');
this.linkFields(series, 'alarmMarkers', path + 'alarmMarkers', Boolean, undefined, configPath + 'alarmMarkers');
};
PlotOptionsController.prototype.resetAllSeries = function (series, index) {
this.removeSeries(series);
this.config.series.forEach(this.removeSeries, this);
this.$scope.form.series = [];
this.config.series.forEach(this.addSeries, this);
};
PlotOptionsController.prototype.removeSeries = function (series) {
this.stopListening(series);
series.stopListening(this.$scope);
};
PlotOptionsController.prototype.linkFields = function (
model,
prop,
scopePath,
coerce,
validate,
objectPath
) {
if (!coerce) {
coerce = function (v) {
return v;
};
}
if (!validate) {
validate = function () {
return true;
};
}
this.listenTo(model, 'change:' + prop, function (newVal, oldVal) {
if (!_.isEqual(coerce(_.get(this.$scope, scopePath)), coerce(newVal))) {
_.set(this.$scope, scopePath, coerce(newVal));
}
}, this);
model.listenTo(this.$scope, 'change:' + scopePath, function (newVal, oldVal) {
if (_.isEqual(coerce(newVal), coerce(model.get(prop)))) {
return; // Don't trigger excessive mutations.
}
var validationResult = validate(newVal);
if (validationResult === true) {
delete this.$scope.validation[scopePath];
} else {
this.$scope.validation[scopePath] = validationResult;
return;
}
if (!_.isEqual(coerce(newVal), coerce(oldVal))) {
model.set(prop, coerce(newVal));
if (objectPath && this.$scope.domainObject.getCapability('editor').isEditContextRoot()) {
this.openmct.objects.mutate(this.domainObject, objectPath, coerce(newVal));
}
}
}, this);
_.set(this.$scope, scopePath, coerce(model.get(prop)));
this.$timeout(function () {
this.$scope.plotSeries = [];
this.$timeout(function () {
this.config.series.forEach(this.addSeries, this);
}.bind(this));
}.bind(this));
};
return PlotOptionsController;

View File

@ -0,0 +1,126 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./PlotModelFormController',
'lodash'
], function (
PlotModelFormController,
_
) {
function dynamicPathForKey(key) {
return function (object, model) {
var modelIdentifier = model.get('identifier');
var index = _.findIndex(object.configuration.series, function (s) {
return _.isEqual(s.identifier, modelIdentifier);
});
return 'configuration.series[' + index + '].' + key;
};
}
var PlotSeriesFormController = PlotModelFormController.extend({
/**
* Set the color for the current plot series. If the new color was
* already assigned to a different plot series, then swap the colors.
*/
setColor: function (color) {
var oldColor = this.model.get('color');
var otherSeriesWithColor = this.model.collection.filter(function (s) {
return s.get('color') === color;
})[0];
this.model.set('color', color);
var getPath = dynamicPathForKey('color');
var seriesColorPath = getPath(this.domainObject, this.model);
this.openmct.objects.mutate(
this.domainObject,
seriesColorPath,
color.asHexString()
);
if (otherSeriesWithColor) {
otherSeriesWithColor.set('color', oldColor);
var otherSeriesColorPath = getPath(
this.domainObject,
otherSeriesWithColor
);
this.openmct.objects.mutate(
this.domainObject,
otherSeriesColorPath,
oldColor.asHexString()
);
}
},
/**
* Populate scope with options and add setColor callback.
*/
initialize: function () {
this.$scope.setColor = this.setColor.bind(this);
var metadata = this.model.metadata;
this.$scope.yKeyOptions = metadata
.valuesForHints(['range'])
.map(function (o) {
return {
name: o.key,
value: o.key
};
});
},
fields: [
{
modelProp: 'yKey',
objectPath: dynamicPathForKey('yKey')
},
{
modelProp: 'interpolate',
objectPath: dynamicPathForKey('interpolate')
},
{
modelProp: 'markers',
objectPath: dynamicPathForKey('markers')
},
{
modelProp: 'markerSize',
coerce: Number,
objectPath: dynamicPathForKey('markerSize')
},
{
modelProp: 'alarmMarkers',
coerce: Boolean,
objectPath: dynamicPathForKey('alarmMarkers')
}
]
});
return PlotSeriesFormController;
});

View File

@ -0,0 +1,96 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./PlotModelFormController',
'lodash'
], function (
PlotModelFormController,
_
) {
var PlotYAxisFormController = PlotModelFormController.extend({
fields: [
{
modelProp: 'label',
objectPath: 'configuration.yAxis.label'
},
{
modelProp: 'autoscale',
coerce: Boolean,
objectPath: 'configuration.yAxis.autoscale'
},
{
modelProp: 'autoscalePadding',
coerce: Number,
objectPath: 'configuration.yAxis.autoscalePadding'
},
{
modelProp: 'range',
objectPath: 'form.yAxis.range',
coerce: function coerceRange(range) {
if (!range) {
return {
min: 0,
max: 0
};
}
var newRange = {};
if (typeof range.min !== 'undefined' && range.min !== null) {
newRange.min = Number(range.min);
}
if (typeof range.max !== 'undefined' && range.max !== null) {
newRange.max = Number(range.max);
}
return newRange;
},
validate: function validateRange(range, model) {
if (!range) {
return 'Need range';
}
if (range.min === '' || range.min === null || typeof range.min === 'undefined') {
return 'Must specify Minimum';
}
if (range.max === '' || range.max === null || typeof range.max === 'undefined') {
return 'Must specify Maximum';
}
if (_.isNaN(Number(range.min))) {
return 'Minimum must be a number.';
}
if (_.isNaN(Number(range.max))) {
return 'Maximum must be a number.';
}
if (Number(range.min) > Number(range.max)) {
return 'Minimum must be less than Maximum.';
}
if (model.get('autoscale')) {
return false;
}
return true;
}
}
]
});
return PlotYAxisFormController;
});