new-plot import (#1557)

Merge of new plot
* Introduces new Plot object and view
* Removes Old Plot
* Add LAD support and state type to generators
* Removes Telemetry Panel Type
* Telemetry API Updates
* UTCFormat.parse: passthrough numbers
* TelemetryAPI: default request arguments
* TelemetryAPI: fix enum formatting
* Markup and styling to support new plots
This commit is contained in:
Pete Richards
2018-03-02 14:29:34 -08:00
committed by Andrew Henry
parent 6145843e86
commit 5726fe6313
131 changed files with 6652 additions and 8247 deletions

250
src/plugins/plot/plugin.js Normal file
View File

@ -0,0 +1,250 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define([
"./src/chart/MCTChartDirective",
"./src/plot/MCTPlotDirective",
'./src/plot/MCTTicksDirective',
"./src/telemetry/MCTOverlayPlot",
"./src/telemetry/PlotController",
"./src/telemetry/StackedPlotController",
"./src/inspector/PlotInspector",
"./src/inspector/PlotOptionsController",
"./src/inspector/HideElementPoolDirective",
"./src/services/ExportImageService",
'./src/PlotViewPolicy',
"text!./res/templates/plot-options.html",
"text!./res/templates/plot-options-browse.html",
"text!./res/templates/plot-options-edit.html",
"text!./res/templates/stacked-plot.html",
"text!./res/templates/plot.html"
], function (
MCTChartDirective,
MCTPlotDirective,
MCTTicksDirective,
MCTOverlayPlot,
PlotController,
StackedPlotController,
PlotInspector,
PlotOptionsController,
HideElementPool,
ExportImageService,
PlotViewPolicy,
plotOptionsTemplate,
plotOptionsBrowseTemplate,
plotOptionsEditTemplate,
StackedPlotTemplate,
PlotTemplate
) {
var installed = false;
function PlotPlugin() {
return function install(openmct) {
if (installed) {
return;
}
installed = true;
openmct.legacyRegistry.register("openmct/plot", {
"name": "Plot view for telemetry, reborn",
"extensions": {
"policies": [
{
"category": "view",
"implementation": PlotViewPolicy,
"depends": [
"openmct"
]
}
],
"views": [
{
"name": "Plot",
"key": "plot-single",
"cssClass": "icon-telemetry",
"template": PlotTemplate,
"needs": [
"telemetry"
],
"delegation": false,
"priority": "mandatory"
},
{
"name": "Overlay Plot",
"key": "overlayPlot",
"cssClass": "icon-plot-overlay",
"type": "telemetry.plot.overlay",
"template": PlotTemplate,
"editable": true
},
{
"name": "Stacked Plot",
"key": "stackedPlot",
"cssClass": "icon-plot-stacked",
"type": "telemetry.plot.stacked",
"template": StackedPlotTemplate,
"editable": true
}
],
"directives": [
{
"key": "mctTicks",
"implementation": MCTTicksDirective,
"depends": []
},
{
"key": "mctChart",
"implementation": MCTChartDirective,
"depends": [
"$interval",
"$log"
]
},
{
"key": "mctPlot",
"implementation": MCTPlotDirective,
"depends": [],
"templateUrl": "templates/mct-plot.html"
},
{
"key": "mctOverlayPlot",
"implementation": MCTOverlayPlot,
"depends": []
},
{
"key": "hideElementPool",
"implementation": HideElementPool,
"depends": []
}
],
"controllers": [
{
"key": "PlotController",
"implementation": PlotController,
"depends": [
"$scope",
"$element",
"formatService",
"openmct",
"objectService",
"exportImageService"
]
},
{
"key": "StackedPlotController",
"implementation": StackedPlotController,
"depends": [
"$scope",
"openmct",
"objectService",
"$element",
"exportImageService"
]
},
{
"key": "PlotOptionsController",
"implementation": PlotOptionsController,
"depends": [
"$scope",
"openmct",
"$timeout"
]
}
],
"services": [
{
"key": "exportImageService",
"implementation": ExportImageService,
"depends": [
"$q",
"$timeout",
"$log"
]
}
],
"types": [
{
"key": "telemetry.plot.overlay",
"name": "Overlay Plot",
"cssClass": "icon-plot-overlay",
"description": "Combine multiple telemetry elements and view them together as a plot with common X and Y axes. Can be added to Display Layouts.",
"features": "creation",
"contains": [
{
"has": "telemetry"
}
],
"model": {
composition: [],
configuration: {
series: [],
yAxis: {},
xAxis: {}
}
},
"properties": [],
"inspector": "plot-options",
"priority": 891
},
{
"key": "telemetry.plot.stacked",
"name": "Stacked Plot",
"cssClass": "icon-plot-stacked",
"description": "Combine multiple telemetry elements and view them together as a plot with a common X axis and individual Y axes. Can be added to Display Layouts.",
"features": "creation",
"contains": [
"telemetry.plot.overlay",
{"has": "telemetry"}
],
"model": {
"composition": []
},
"properties": [],
"priority": 890
}
],
"representations": [
{
"key": "plot-options",
"template": plotOptionsTemplate
},
{
"key": "plot-options-browse",
"template": plotOptionsBrowseTemplate
},
{
"key": "plot-options-edit",
"template": plotOptionsEditTemplate
}
]
}
});
openmct.legacyRegistry.enable("openmct/plot");
};
}
return PlotPlugin;
});

View File

@ -0,0 +1,210 @@
<!--
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-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>

View File

@ -0,0 +1,130 @@
<!--
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>

View File

@ -0,0 +1,229 @@
<!--
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>

View File

@ -0,0 +1,31 @@
<!--
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>

View File

@ -0,0 +1,50 @@
<!--
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>

View File

@ -0,0 +1,53 @@
<!--
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>

View File

@ -0,0 +1,67 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
function () {
/**
* Policy preventing the Plot view from being made available for
* domain objects which have non-numeric telemetry.
* @implements {Policy.<View, DomainObject>}
* @constructor
* @memberof platform/features/plot
*/
function PlotViewPolicy(openmct) {
this.openmct = openmct;
}
PlotViewPolicy.prototype.hasNumericTelemetry = function (domainObject) {
var adaptedObject = domainObject.useCapability('adapter');
if (!adaptedObject.telemetry) {
return domainObject.hasCapability('delegation') &&
domainObject.getCapability('delegation')
.doesDelegateCapability('telemetry');
}
var metadata = this.openmct.telemetry.getMetadata(adaptedObject);
var rangeValues = metadata.valuesForHints(['range']);
if (rangeValues.length === 0) {
return false;
}
return !rangeValues.every(function (value) {
return value.format === 'string';
});
};
PlotViewPolicy.prototype.allow = function (view, domainObject) {
if (view.key === 'plot-single') {
return this.hasNumericTelemetry(domainObject);
}
return true;
};
return PlotViewPolicy;
}
);

View File

@ -0,0 +1,77 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define([
'../lib/extend',
'../lib/eventHelpers'
], function (
extend,
eventHelpers
) {
function MCTChartAlarmPointSet(series, chart, offset) {
this.series = series;
this.chart = chart;
this.offset = offset;
this.points = [];
this.listenTo(series, 'add', this.append, this);
this.listenTo(series, 'remove', this.remove, this);
this.listenTo(series, 'reset', this.reset, this);
this.listenTo(series, 'destroy', this.destroy, this);
series.data.forEach(function (point, index) {
this.append(point, index, series);
}, this);
}
MCTChartAlarmPointSet.prototype.append = function (datum) {
if (datum.mctLimitState) {
this.points.push({
x: this.offset.xVal(datum, this.series),
y: this.offset.yVal(datum, this.series),
datum: datum
});
}
};
MCTChartAlarmPointSet.prototype.remove = function (datum) {
this.points = this.points.filter(function (p) {
return p.datum !== datum;
});
};
MCTChartAlarmPointSet.prototype.reset = function () {
this.points = [];
};
MCTChartAlarmPointSet.prototype.destroy = function () {
this.stopListening();
};
eventHelpers.extend(MCTChartAlarmPointSet.prototype);
return MCTChartAlarmPointSet;
});

View File

@ -0,0 +1,400 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,requestAnimationFrame,Float32Array*/
/**
* Module defining MCTChart. Created by vwoeltje on 11/12/14.
*/
define([
'./MCTChartLineLinear',
'./MCTChartLineStepAfter',
'./MCTChartPointSet',
'./MCTChartAlarmPointSet',
'../draw/DrawLoader',
'../lib/eventHelpers',
'lodash'
],
function (
MCTChartLineLinear,
MCTChartLineStepAfter,
MCTChartPointSet,
MCTChartAlarmPointSet,
DrawLoader,
eventHelpers,
_
) {
var MARKER_SIZE = 6.0,
HIGHLIGHT_SIZE = MARKER_SIZE * 2.0;
/**
* Offsetter adjusts x and y values by a fixed amount,
* generally increasing the precision of the 32 bit float representation
* required for plotting.
*
* @constructor
*/
function MCTChartController($scope) {
this.$scope = $scope;
this.isDestroyed = false;
this.lines = [];
this.pointSets = [];
this.alarmSets = [];
this.offset = {};
this.config = $scope.config;
this.listenTo(this.$scope, '$destoy', this.destroy, this);
this.draw = this.draw.bind(this);
this.scheduleDraw = this.scheduleDraw.bind(this);
this.seriesElements = new WeakMap();
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this);
this.listenTo(this.config.yAxis, 'change:key', this.clearOffset, this);
this.listenTo(this.config.xAxis, 'change:key', this.clearOffset, this);
this.listenTo(this.config.yAxis, 'change', this.scheduleDraw);
this.listenTo(this.config.xAxis, 'change', this.scheduleDraw);
this.$scope.$watch('highlights', this.scheduleDraw);
this.$scope.$watch('rectangles', this.scheduleDraw);
this.config.series.forEach(this.onSeriesAdd, this);
}
eventHelpers.extend(MCTChartController.prototype);
MCTChartController.$inject = ['$scope'];
MCTChartController.prototype.onSeriesAdd = function (series) {
this.listenTo(series, 'change:interpolate', this.changeInterpolate, this);
this.listenTo(series, 'change:markers', this.changeMarkers, this);
this.listenTo(series, 'change:alarmMarkers', this.changeAlarmMarkers, this);
this.listenTo(series, 'change', this.scheduleDraw);
this.listenTo(series, 'add', this.scheduleDraw);
this.makeChartElement(series);
};
MCTChartController.prototype.changeInterpolate = function (mode, o, series) {
if (mode === o) {
return;
}
var elements = this.seriesElements.get(series);
elements.lines.forEach(function (line) {
this.lines.splice(this.lines.indexOf(line), 1);
line.destroy();
}, this);
elements.lines = [];
var newLine = this.lineForSeries(series);
if (newLine) {
elements.lines.push(newLine);
this.lines.push(newLine);
}
};
MCTChartController.prototype.changeAlarmMarkers = function (mode, o, series) {
if (mode === o) {
return;
}
var elements = this.seriesElements.get(series);
if (elements.alarmSet) {
elements.alarmSet.destroy();
this.alarmSets.splice(this.alarmSets.indexOf(elements.alarmSet), 1);
}
elements.alarmSet = this.alarmPointSetForSeries(series);
if (elements.alarmSet) {
this.alarmSets.push(elements.alarmSet);
}
};
MCTChartController.prototype.changeMarkers = function (mode, o, series) {
if (mode === o) {
return;
}
var elements = this.seriesElements.get(series);
elements.pointSets.forEach(function (pointSet) {
this.pointSets.splice(this.pointSets.indexOf(pointSet), 1);
pointSet.destroy();
}, this);
elements.pointSets = [];
var pointSet = this.pointSetForSeries(series);
if (pointSet) {
elements.pointSets.push(pointSet);
this.pointSets.push(pointSet);
}
};
MCTChartController.prototype.onSeriesRemove = function (series) {
this.stopListening(series);
this.removeChartElement(series);
this.scheduleDraw();
};
MCTChartController.prototype.destroy = function () {
this.isDestroyed = true;
this.stopListening();
_.invoke(this.lines, 'destroy');
DrawLoader.releaseDrawAPI(this.drawAPI);
};
MCTChartController.prototype.clearOffset = function () {
delete this.offset.x;
delete this.offset.y;
delete this.offset.xVal;
delete this.offset.yVal;
delete this.offset.xKey;
delete this.offset.yKey;
this.lines.forEach(function (line) {
line.reset();
});
this.pointSets.forEach(function (pointSet) {
pointSet.reset();
});
};
MCTChartController.prototype.setOffset = function (offsetPoint, index, series) {
if (this.offset.x && this.offset.y) {
return;
}
var offsets = {
x: series.getXVal(offsetPoint),
y: series.getYVal(offsetPoint)
};
this.offset.x = function (x) {
return x - offsets.x;
}.bind(this);
this.offset.y = function (y) {
return y - offsets.y;
}.bind(this);
this.offset.xVal = function (point, pSeries) {
return this.offset.x(pSeries.getXVal(point));
}.bind(this);
this.offset.yVal = function (point, pSeries) {
return this.offset.y(pSeries.getYVal(point));
}.bind(this);
};
MCTChartController.prototype.initializeCanvas = function (canvas, overlay) {
this.canvas = canvas;
this.overlay = overlay;
this.drawAPI = DrawLoader.getDrawAPI(canvas, overlay);
return !!this.drawAPI;
};
MCTChartController.prototype.removeChartElement = function (series) {
var elements = this.seriesElements.get(series);
elements.lines.forEach(function (line) {
this.lines.splice(this.lines.indexOf(line), 1);
line.destroy();
}, this);
elements.pointSets.forEach(function (pointSet) {
this.pointSets.splice(this.pointSets.indexOf(pointSet), 1);
pointSet.destroy();
}, this);
this.seriesElements.delete(series);
};
MCTChartController.prototype.lineForSeries = function (series) {
if (series.get('interpolate') === 'linear') {
return new MCTChartLineLinear(
series,
this,
this.offset
);
}
if (series.get('interpolate') === 'stepAfter') {
return new MCTChartLineStepAfter(
series,
this,
this.offset
);
}
};
MCTChartController.prototype.pointSetForSeries = function (series) {
if (series.get('markers')) {
return new MCTChartPointSet(
series,
this,
this.offset
);
}
};
MCTChartController.prototype.alarmPointSetForSeries = function (series) {
if (series.get('alarmMarkers')) {
return new MCTChartAlarmPointSet(
series,
this,
this.offset
);
}
};
MCTChartController.prototype.makeChartElement = function (series) {
var elements = {
lines: [],
pointSets: []
};
var line = this.lineForSeries(series);
if (line) {
elements.lines.push(line);
this.lines.push(line);
}
var pointSet = this.pointSetForSeries(series);
if (pointSet) {
elements.pointSets.push(pointSet);
this.pointSets.push(pointSet);
}
elements.alarmSet = this.alarmPointSetForSeries(series);
if (elements.alarmSet) {
this.alarmSets.push(elements.alarmSet);
}
this.seriesElements.set(series, elements);
};
MCTChartController.prototype.canDraw = function () {
if (!this.offset.x || !this.offset.y) {
return false;
}
return true;
};
MCTChartController.prototype.scheduleDraw = function () {
if (!this.drawScheduled) {
requestAnimationFrame(this.draw);
this.drawScheduled = true;
}
};
MCTChartController.prototype.draw = function () {
this.drawScheduled = false;
if (this.isDestroyed) {
return;
}
this.drawAPI.clear();
if (this.canDraw()) {
this.updateViewport();
this.drawSeries();
this.drawRectangles();
this.drawHighlights();
}
};
MCTChartController.prototype.updateViewport = function () {
var xRange = this.config.xAxis.get('displayRange'),
yRange = this.config.yAxis.get('displayRange');
if (!xRange || !yRange) {
return;
}
var dimensions = [
xRange.max - xRange.min,
yRange.max - yRange.min
],
origin = [
this.offset.x(xRange.min),
this.offset.y(yRange.min)
];
this.drawAPI.setDimensions(
dimensions,
origin
);
};
MCTChartController.prototype.drawSeries = function () {
this.lines.forEach(this.drawLine, this);
this.pointSets.forEach(this.drawPoints, this);
this.alarmSets.forEach(this.drawAlarmPoints, this);
};
MCTChartController.prototype.drawAlarmPoints = function (alarmSet) {
this.drawAPI.drawLimitPoints(
alarmSet.points,
alarmSet.series.get('color').asRGBAArray(),
alarmSet.series.get('markerSize')
);
};
MCTChartController.prototype.drawPoints = function (chartElement) {
this.drawAPI.drawPoints(
chartElement.getBuffer(),
chartElement.color().asRGBAArray(),
chartElement.count,
chartElement.series.get('markerSize')
);
};
MCTChartController.prototype.drawLine = function (chartElement) {
this.drawAPI.drawLine(
chartElement.getBuffer(),
chartElement.color().asRGBAArray(),
chartElement.count
);
};
MCTChartController.prototype.drawHighlights = function () {
if (this.$scope.highlights && this.$scope.highlights.length) {
this.$scope.highlights.forEach(this.drawHighlight, this);
}
};
MCTChartController.prototype.drawHighlight = function (highlight) {
var points = new Float32Array([
this.offset.xVal(highlight.point, highlight.series),
this.offset.yVal(highlight.point, highlight.series)
]),
color = highlight.series.get('color').asRGBAArray(),
pointCount = 1;
this.drawAPI.drawPoints(points, color, pointCount, HIGHLIGHT_SIZE);
};
MCTChartController.prototype.drawRectangles = function () {
if (this.$scope.rectangles) {
this.$scope.rectangles.forEach(this.drawRectangle, this);
}
};
MCTChartController.prototype.drawRectangle = function (rect) {
this.drawAPI.drawSquare(
[
this.offset.x(rect.start.x),
this.offset.y(rect.start.y)
],
[
this.offset.x(rect.end.x),
this.offset.y(rect.end.y)
],
rect.color
);
};
return MCTChartController;
});

View File

@ -0,0 +1,67 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
/**
* Module defining MCTChart. Created by vwoeltje on 11/12/14.
*/
define([
'./MCTChartController'
], function (
MCTChartController
) {
var TEMPLATE = "<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;
});

View File

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

View File

@ -0,0 +1,78 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define([
'./MCTChartSeriesElement'
], function (
MCTChartSeriesElement
) {
var MCTChartLineStepAfter = MCTChartSeriesElement.extend({
removePoint: function (point, index, count) {
if (index > 0 && index / 2 < this.count) {
this.buffer[index + 1] = this.buffer[index - 1];
}
},
vertexCountForPointAtIndex: function (index) {
if (index === 0 && this.count === 0) {
return 2;
}
return 4;
},
startIndexForPointAtIndex: function (index) {
if (index === 0) {
return 0;
}
return 2 + ((index - 1) * 4);
},
addPoint: function (point, start, count) {
if (start === 0 && this.count === 0) {
// First point is easy.
this.buffer[start] = point.x;
this.buffer[start + 1] = point.y; // one point
} else if (start === 0 && this.count > 0) {
// Unshifting requires adding an extra point.
this.buffer[start] = point.x;
this.buffer[start + 1] = point.y;
this.buffer[start + 2] = this.buffer[start + 4];
this.buffer[start + 3] = point.y;
} else {
// Appending anywhere in line, insert standard two points.
this.buffer[start] = point.x;
this.buffer[start + 1] = this.buffer[start - 1];
this.buffer[start + 2] = point.x;
this.buffer[start + 3] = point.y;
if (start < this.count * 2) {
// Insert into the middle, need to update the following
// point.
this.buffer[start + 5] = point.y;
}
}
}
});
return MCTChartLineStepAfter;
});

View File

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

View File

@ -0,0 +1,162 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,Float32Array*/
define([
'../lib/extend',
'../lib/eventHelpers'
], function (
extend,
eventHelpers
) {
function MCTChartSeriesElement(series, chart, offset) {
this.series = series;
this.chart = chart;
this.offset = offset;
this.buffer = new Float32Array(20000);
this.count = 0;
this.listenTo(series, 'add', this.append, this);
this.listenTo(series, 'remove', this.remove, this);
this.listenTo(series, 'reset', this.reset, this);
this.listenTo(series, 'destroy', this.destroy, this);
series.data.forEach(function (point, index) {
this.append(point, index, series);
}, this);
}
MCTChartSeriesElement.extend = extend;
eventHelpers.extend(MCTChartSeriesElement.prototype);
MCTChartSeriesElement.prototype.getBuffer = function () {
if (this.isTempBuffer) {
this.buffer = new Float32Array(this.buffer);
this.isTempBuffer = false;
}
return this.buffer;
};
MCTChartSeriesElement.prototype.color = function () {
return this.series.get('color');
};
MCTChartSeriesElement.prototype.vertexCountForPointAtIndex = function (index) {
return 2;
};
MCTChartSeriesElement.prototype.startIndexForPointAtIndex = function (index) {
return 2 * index;
};
MCTChartSeriesElement.prototype.removeSegments = function (index, count) {
var target = index,
start = index + count,
end = this.count * 2;
this.buffer.copyWithin(target, start, end);
for (var zero = end - count; zero < end; zero++) {
this.buffer[zero] = 0;
}
};
MCTChartSeriesElement.prototype.removePoint = function (point, index, count) {
// by default, do nothing.
};
MCTChartSeriesElement.prototype.remove = function (point, index, series) {
var vertexCount = this.vertexCountForPointAtIndex(index);
var removalPoint = this.startIndexForPointAtIndex(index);
this.removeSegments(removalPoint, vertexCount);
this.removePoint(
this.makePoint(point, series),
removalPoint,
vertexCount
);
this.count -= (vertexCount / 2);
};
MCTChartSeriesElement.prototype.makePoint = function (point, series) {
if (!this.offset.xVal) {
this.chart.setOffset(point, undefined, series);
}
return {
x: this.offset.xVal(point, series),
y: this.offset.yVal(point, series)
};
};
MCTChartSeriesElement.prototype.append = function (point, index, series) {
var pointsRequired = this.vertexCountForPointAtIndex(index);
var insertionPoint = this.startIndexForPointAtIndex(index);
this.growIfNeeded(pointsRequired);
this.makeInsertionPoint(insertionPoint, pointsRequired);
this.addPoint(
this.makePoint(point, series),
insertionPoint,
pointsRequired
);
this.count += (pointsRequired / 2);
};
MCTChartSeriesElement.prototype.makeInsertionPoint = function (insertionPoint, pointsRequired) {
if (this.count * 2 > insertionPoint) {
if (!this.isTempBuffer) {
this.buffer = Array.prototype.slice.apply(this.buffer);
this.isTempBuffer = true;
}
var target = insertionPoint + pointsRequired,
start = insertionPoint;
for (; start < target; start++) {
this.buffer.splice(start, 0, 0);
}
}
};
MCTChartSeriesElement.prototype.reset = function () {
this.buffer = new Float32Array(20000);
this.count = 0;
if (this.offset.x) {
this.series.data.forEach(function (point, index) {
this.append(point, index, this.series);
}, this);
}
};
MCTChartSeriesElement.prototype.growIfNeeded = function (pointsRequired) {
var remainingPoints = this.buffer.length - this.count * 2;
var temp;
if (remainingPoints <= pointsRequired) {
temp = new Float32Array(this.buffer.length + 20000);
temp.set(this.buffer);
this.buffer = temp;
}
};
MCTChartSeriesElement.prototype.destroy = function () {
this.stopListening();
};
return MCTChartSeriesElement;
});

View File

@ -0,0 +1,133 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define([
'lodash',
'EventEmitter',
'./Model',
'../lib/extend',
'../lib/eventHelpers'
], function (
_,
EventEmitter,
Model,
extend,
eventHelpers
) {
function Collection(options) {
if (options.models) {
this.models = options.models.map(this.modelFn, this);
} else {
this.models = [];
}
this.initialize(options);
}
_.extend(Collection.prototype, EventEmitter.prototype);
eventHelpers.extend(Collection.prototype);
Collection.extend = extend;
Collection.prototype.initialize = function (options) {
};
Collection.prototype.modelClass = Model;
Collection.prototype.modelFn = function (model) {
if (model instanceof this.modelClass) {
model.collection = this;
return model;
}
return new this.modelClass({
collection: this,
model: model
});
};
Collection.prototype.first = function () {
return this.at(0);
};
Collection.prototype.forEach = function (iteree, context) {
this.models.forEach(iteree, context);
};
Collection.prototype.map = function (iteree, context) {
return this.models.map(iteree, context);
};
Collection.prototype.filter = function (iteree, context) {
return this.models.filter(iteree, context);
};
Collection.prototype.size = function () {
return this.models.length;
};
Collection.prototype.at = function (index) {
return this.models[index];
};
Collection.prototype.add = function (model) {
model = this.modelFn(model);
var index = this.models.length;
this.models.push(model);
this.emit('add', model, index);
};
Collection.prototype.insert = function (model, index) {
model = this.modelFn(model);
this.models.splice(index, 0, model);
this.emit('add', model, index + 1);
};
Collection.prototype.indexOf = function (model) {
return _.findIndex(
this.models,
function (m) {
return m === model;
}
);
};
Collection.prototype.remove = function (model) {
var index = this.indexOf(model);
if (index === -1) {
throw new Error('model not found in collection.');
}
this.models.splice(index, 1);
this.emit('remove', model, index);
};
Collection.prototype.destroy = function (model) {
this.forEach(function (m) {
m.destroy();
});
this.stopListening();
};
return Collection;
});

View File

@ -0,0 +1,61 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./Model'
], function (
Model
) {
/**
* TODO: doc strings.
*/
var LegendModel = Model.extend({
listenToSeriesCollection: function (seriesCollection) {
this.seriesCollection = seriesCollection;
this.listenTo(this.seriesCollection, 'add', this.setHeight, this);
this.listenTo(this.seriesCollection, 'remove', this.setHeight, this);
this.listenTo(this, 'change:expanded', this.setHeight, this);
this.set('expanded', this.get('expandByDefault'));
},
setHeight: function () {
var expanded = this.get('expanded');
if (this.get('position') !== 'top') {
this.set('height', '0px');
} else {
this.set('height', expanded ? (20 * (this.seriesCollection.size() + 1) + 40) + 'px' : '21px');
}
},
defaults: function (options) {
return {
position: 'top',
expandByDefault: false,
valueToShowWhenCollapsed: 'nearestValue',
showTimestampWhenExpanded: true,
showValueWhenExpanded: true,
showMaximumWhenExpanded: true,
showMinimumWhenExpanded: true
};
}
});
return LegendModel;
});

View File

@ -0,0 +1,102 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define([
'lodash',
'EventEmitter',
'../lib/extend',
'../lib/eventHelpers'
], function (
_,
EventEmitter,
extend,
eventHelpers
) {
function Model(options) {
if (!options) {
options = {};
}
this.id = options.id;
this.model = options.model;
this.collection = options.collection;
var defaults = this.defaults(options);
if (!this.model) {
this.model = options.model = defaults;
} else {
_.defaultsDeep(this.model, defaults);
}
this.initialize(options);
}
_.extend(Model.prototype, EventEmitter.prototype);
eventHelpers.extend(Model.prototype);
Model.extend = extend;
Model.prototype.idAttr = 'id';
Model.prototype.defaults = function (options) {
return {};
};
Model.prototype.initialize = function (model) {
};
/**
* Destroy the model, removing all listeners and subscriptions.
*/
Model.prototype.destroy = function () {
this.emit('destroy');
this.removeAllListeners();
};
Model.prototype.id = function () {
return this.get(this.idAttr);
};
Model.prototype.get = function (attribute) {
return this.model[attribute];
};
Model.prototype.has = function (attribute) {
return _.has(this.model, attribute);
};
Model.prototype.set = function (attribute, value) {
var oldValue = this.model[attribute];
this.model[attribute] = value;
this.emit('change', attribute, value, oldValue, this);
this.emit('change:' + attribute, value, oldValue, this);
};
Model.prototype.unset = function (attribute) {
var oldValue = this.model[attribute];
delete this.model[attribute];
this.emit('change', attribute, undefined, oldValue, this);
this.emit('change:' + attribute, undefined, oldValue, this);
};
return Model;
});

View File

@ -0,0 +1,134 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define([
'./Collection',
'./Model',
'./SeriesCollection',
'./XAxisModel',
'./YAxisModel',
'./LegendModel',
'lodash'
], function (
Collection,
Model,
SeriesCollection,
XAxisModel,
YAxisModel,
LegendModel,
_
) {
/**
* PlotConfiguration model stores the configuration of a plot and some
* limited state. The indiidual parts of the plot configuration model
* handle setting defaults and updating in response to various changes.
*
*/
var PlotConfigurationModel = Model.extend({
/**
* Initializes all sub models and then passes references to submodels
* to those that need it.
*/
initialize: function (options) {
this.openmct = options.openmct;
this.xAxis = new XAxisModel({
model: options.model.xAxis,
plot: this,
openmct: options.openmct
});
this.yAxis = new YAxisModel({
model: options.model.yAxis,
plot: this,
openmct: options.openmct
});
this.legend = new LegendModel({
model: options.model.legend,
plot: this,
openmct: options.openmct
});
this.series = new SeriesCollection({
models: options.model.series,
plot: this,
openmct: options.openmct
});
this.removeMutationListener = this.openmct.objects.observe(
this.get('domainObject'),
'*',
this.updateDomainObject.bind(this)
);
this.yAxis.listenToSeriesCollection(this.series);
this.legend.listenToSeriesCollection(this.series);
this.listenTo(this, 'destroy', this.onDestroy, this);
},
/**
* Retrieve the persisted series config for a given identifier.
*/
getPersistedSeriesConfig: function (identifier) {
var domainObject = this.get('domainObject');
if (!domainObject.configuration || !domainObject.configuration.series) {
return;
}
return domainObject.configuration.series.filter(function (seriesConfig) {
return seriesConfig.identifier.key === identifier.key &&
seriesConfig.identifier.namespace === identifier.namespace;
})[0];
},
/**
* Update the domain object with the given value.
*/
updateDomainObject: function (domainObject) {
this.set('domainObject', domainObject);
},
/**
* Clean up all objects and remove all listeners.
*/
onDestroy: function () {
this.xAxis.destroy();
this.yAxis.destroy();
this.series.destroy();
this.legend.destroy();
this.removeMutationListener();
},
/**
* Return defaults, which are extracted from the passed in domain
* object.
*/
defaults: function (options) {
return {
series: [],
domainObject: options.domainObject,
xAxis: {
},
yAxis: _.cloneDeep(_.get(options.domainObject, 'configuration.yAxis', {})),
legend: _.cloneDeep(_.get(options.domainObject, 'configuration.legend', {}))
};
}
});
return PlotConfigurationModel;
});

View File

@ -0,0 +1,357 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define([
'lodash',
'../configuration/Model',
'../lib/extend',
'EventEmitter'
], function (
_,
Model,
extend,
EventEmitter
) {
/**
* Plot series handle interpreting telemetry metadata for a single telemetry
* object, querying for that data, and formatting it for display purposes.
*
* Plot series emit both collection events and model events:
* `change` when any property changes
* `change:<prop_name>` when a specific property changes.
* `destroy`: when series is destroyed
* `add`: whenever a data point is added to a series
* `remove`: whenever a data point is removed from a series.
* `reset`: whenever the collection is emptied.
*
* Plot series have the following Model properties:
*
* `name`: name of series.
* `identifier`: the Open MCT identifier for the telemetry source for this
* series.
* `xKey`: the telemetry value key for x values fetched from this series.
* `yKey`: the telemetry value key for y values fetched from this series.
* `interpolate`: interpolate method, either `undefined` (no interpolation),
* `linear` (points are connected via straight lines), or
* `stepAfter` (points are connected by steps).
* `markers`: boolean, whether or not this series should render with markers.
* `markerSize`: number, size in pixels of markers for this series.
* `alarmMarkers`: whether or not to display alarm markers for this series.
* `stats`: An object that tracks the min and max y values observed in this
* series. This property is checked and updated whenever data is
* added.
*
* Plot series have the following instance properties:
*
* `metadata`: the Open MCT Telemetry Metadata Manager for the associated
* telemetry point.
* `formats`: the Open MCT format map for this telemetry point.
*/
var PlotSeries = Model.extend({
constructor: function (options) {
this.metadata = options
.openmct
.telemetry
.getMetadata(options.domainObject);
this.formats = options
.openmct
.telemetry
.getFormatMap(this.metadata);
this.data = [];
this.listenTo(this, 'change:xKey', this.onXKeyChange, this);
this.listenTo(this, 'change:yKey', this.onYKeyChange, this);
Model.apply(this, arguments);
this.onXKeyChange(this.get('xKey'));
this.onYKeyChange(this.get('yKey'));
},
/**
* Set defaults for telemetry series.
*/
defaults: function (options) {
var range = this.metadata.valuesForHints(['range'])[0];
return {
name: options.domainObject.name,
xKey: options.collection.plot.xAxis.get('key'),
yKey: range.key,
markers: true,
markerSize: 2.0,
alarmMarkers: true
};
},
/**
* Remove real-time subscription when destroyed.
*/
onDestroy: function (model) {
if (this.unsubscribe) {
this.unsubscribe();
}
},
initialize: function (options) {
this.openmct = options.openmct;
this.domainObject = options.domainObject;
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject);
this.on('destroy', this.onDestroy, this);
},
/**
* Fetch historical data and establish a realtime subscription. Returns
* a promise that is resolved when all connections have been successfully
* established.
*
* @returns {Promise}
*/
fetch: function (options) {
options = _.extend({}, {size: 1000, strategy: 'minmax'}, options || {});
if (!this.unsubscribe) {
this.unsubscribe = this.openmct
.telemetry
.subscribe(
this.domainObject,
this.add.bind(this)
);
}
return this.openmct
.telemetry
.request(this.domainObject, options)
.then(function (points) {
var newPoints = _(this.data)
.concat(points)
.sortBy(this.getXVal)
.uniq(true, this.getXVal)
.value();
this.reset(newPoints);
}.bind(this));
},
/**
* Update x formatter on x change.
*/
onXKeyChange: function (xKey) {
var format = this.formats[xKey];
this.getXVal = format.parse.bind(format);
},
/**
* Update y formatter on change, default to stepAfter interpolation if
* y range is an enumeration.
*/
onYKeyChange: function (newKey, oldKey) {
if (newKey === oldKey) {
return;
}
var valueMetadata = this.metadata.value(newKey);
var persistedConfig = this.get('persistedConfiguration');
if (!persistedConfig || !persistedConfig.interpolate) {
if (valueMetadata.format === 'enum') {
this.set('interpolate', 'stepAfter');
} else {
this.set('interpolate', 'linear');
}
}
this.evaluate = function (datum) {
return this.limitEvaluator.evaluate(datum, valueMetadata);
}.bind(this);
var format = this.formats[newKey];
this.getYVal = format.parse.bind(format);
},
formatX: function (point) {
return this.formats[this.get('xKey')].format(point);
},
formatY: function (point) {
return this.formats[this.get('yKey')].format(point);
},
/**
* Clear stats and recalculate from existing data.
*/
resetStats: function () {
this.unset('stats');
this.data.forEach(this.updateStats, this);
},
/**
* Reset plot series. If new data is provided, will add that
* data to series after reset.
*/
reset: function (newData) {
this.data = [];
this.resetStats();
this.emit('reset');
if (newData) {
newData.forEach(function (point) {
this.add(point, true);
}, this);
}
},
/**
* Return the point closest to a given x value.
*/
nearestPoint: function (xValue) {
var insertIndex = this.sortedIndex(xValue),
lowPoint = this.data[insertIndex - 1],
highPoint = this.data[insertIndex],
indexVal = this.getXVal(xValue),
lowDistance = lowPoint ?
indexVal - this.getXVal(lowPoint) :
Number.POSITIVE_INFINITY,
highDistance = highPoint ?
this.getXVal(highPoint) - indexVal :
Number.POSITIVE_INFINITY,
nearestPoint = highDistance < lowDistance ? highPoint : lowPoint;
return nearestPoint;
},
/**
* Override this to implement plot series loading functionality. Must return
* a promise that is resolved when loading is completed.
*
* @private
* @returns {Promise}
*/
load: function (options) {
return this.fetch(options)
.then(function (res) {
this.emit('load');
return res;
}.bind(this));
},
/**
* Find the insert index for a given point to maintain sort order.
* @private
*/
sortedIndex: function (point) {
return _.sortedIndex(this.data, point, this.getXVal);
},
/**
* Update min/max stats for the series.
* @private
*/
updateStats: function (point) {
var value = this.getYVal(point);
var stats = this.get('stats');
var changed = false;
if (!stats) {
stats = {
minValue: value,
minPoint: point,
maxValue: value,
maxPoint: point
};
changed = true;
} else {
if (stats.maxValue < value) {
stats.maxValue = value;
stats.maxPoint = point;
changed = true;
}
if (stats.minValue > value) {
stats.minValue = value;
stats.minPoint = point;
changed = true;
}
}
if (changed) {
this.set('stats', {
minValue: stats.minValue,
minPoint: stats.minPoint,
maxValue: stats.maxValue,
maxPoint: stats.maxPoint
});
}
},
/**
* Add a point to the data array while maintaining the sort order of
* the array and preventing insertion of points with a duplicate x
* value. Can provide an optional argument to append a point without
* maintaining sort order and dupe checks, which improves performance
* when adding an array of points that are already properly sorted.
*
* @private
* @param {Object} point a telemetry datum.
* @param {Boolean} [appendOnly] default false, if true will append
* a point to the end without dupe checking.
*/
add: function (point, appendOnly) {
var insertIndex = this.data.length;
if (!appendOnly) {
insertIndex = this.sortedIndex(point);
if (this.getXVal(this.data[insertIndex]) === this.getXVal(point)) {
return;
}
if (this.getXVal(this.data[insertIndex - 1]) === this.getXVal(point)) {
return;
}
}
this.updateStats(point);
point.mctLimitState = this.evaluate(point);
this.data.splice(insertIndex, 0, point);
this.emit('add', point, insertIndex, this);
},
/**
* Remove a point from the data array and notify listeners.
* @private
*/
remove: function (point) {
var index = this.data.indexOf(point);
this.data.splice(index, 1);
this.emit('remove', point, index, this);
},
/**
* Purges records outside a given x range. Changes removal method based
* on number of records to remove: for large purge, reset data and
* rebuild array. for small purge, removes points and emits updates.
*
* @public
* @param {Object} range
* @param {number} range.min minimum x value to keep
* @param {number} range.max maximum x value to keep.
*/
purgeRecordsOutsideRange: function (range) {
var startIndex = this.sortedIndex(range.min);
var endIndex = this.sortedIndex(range.max) + 1;
var pointsToRemove = startIndex + (this.data.length - endIndex + 1);
if (pointsToRemove > 0) {
if (pointsToRemove < 1000) {
this.data.slice(0, startIndex).forEach(this.remove, this);
this.data.slice(endIndex, this.data.length).forEach(this.remove, this);
this.resetStats();
} else {
var newData = this.data.slice(startIndex, endIndex);
this.reset(newData);
}
}
}
});
return PlotSeries;
});

View File

@ -0,0 +1,158 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./PlotSeries',
'./Collection',
'./Model',
'../lib/color',
'lodash'
], function (
PlotSeries,
Collection,
Model,
color,
_
) {
var SeriesCollection = Collection.extend({
modelClass: PlotSeries,
initialize: function (options) {
this.plot = options.plot;
this.openmct = options.openmct;
this.palette = new color.ColorPalette();
this.listenTo(this, 'add', this.onSeriesAdd, this);
this.listenTo(this, 'remove', this.onSeriesRemove, this);
this.listenTo(this.plot, 'change:domainObject', this.trackPersistedConfig, this);
var domainObject = this.plot.get('domainObject');
if (domainObject.telemetry) {
this.addTelemetryObject(domainObject);
} else {
this.watchTelemetryContainer(domainObject);
}
},
trackPersistedConfig: function (domainObject) {
domainObject.configuration.series.forEach(function (seriesConfig) {
var series = this.byIdentifier(seriesConfig.identifier);
if (series) {
series.set('persistedConfiguration', seriesConfig);
}
}, this);
},
watchTelemetryContainer: function (domainObject) {
var composition = this.openmct.composition.get(domainObject);
this.listenTo(composition, 'add', this.addTelemetryObject, this);
this.listenTo(composition, 'remove', this.removeTelemetryObject, this);
composition.load();
},
addTelemetryObject: function (domainObject) {
var seriesConfig = {
identifier: domainObject.identifier
};
var plotObject = this.plot.get('domainObject');
if (plotObject.type === 'telemetry.plot.overlay') {
var index = this.size();
if (!plotObject.configuration.series[index]) {
this.openmct.objects.mutate(
plotObject,
'configuration.series[' + index + ']',
seriesConfig
);
}
seriesConfig = this.plot.get('domainObject').configuration.series[index];
// Clone to prevent accidental mutation by ref.
seriesConfig = JSON.parse(JSON.stringify(seriesConfig));
}
seriesConfig.persistedConfiguration =
this.plot.getPersistedSeriesConfig(domainObject.identifier);
this.add(new PlotSeries({
model: seriesConfig,
domainObject: domainObject,
collection: this,
openmct: this.openmct
}));
},
removeTelemetryObject: function (identifier) {
// TODO: properly locate in self (and parent configuration)
// Instead of binding via index, which is not guaranteed because
// edits could occur when plotcontroller is not instantiated.
// This bug also extends to the plotOptions form which currently
// relies on indexes that match.
var plotObject = this.plot.get('domainObject');
if (plotObject.type === 'telemetry.plot.overlay') {
var index = _.findIndex(plotObject.configuration.series, function (s) {
return _.isEqual(identifier, s.identifier);
});
this.remove(this.at(index));
// Because this is triggered by a composition change, we have
// to defer mutation of our plot object, otherwise we might
// mutate an outdated version of the plotObject.
setTimeout(function () {
var newPlotObject = this.plot.get('domainObject');
var cSeries = newPlotObject.configuration.series.slice();
cSeries.splice(index, 1);
this.openmct.objects.mutate(newPlotObject, 'configuration.series', cSeries);
}.bind(this));
}
},
onSeriesAdd: function (series) {
var seriesColor = series.get('color');
if (seriesColor) {
if (!(seriesColor instanceof color.Color)) {
seriesColor = color.Color.fromHexString(seriesColor);
series.set('color', seriesColor);
}
this.palette.remove(seriesColor);
} else {
series.set('color', this.palette.getNextColor());
}
this.listenTo(series, 'change:color', this.updateColorPalette, this);
},
onSeriesRemove: function (series) {
this.palette.return(series.get('color'));
this.stopListening(series);
series.destroy();
},
updateColorPalette: function (newColor, oldColor) {
this.palette.remove(newColor);
var seriesWithColor = this.filter(function (series) {
return series.get('color') === newColor;
})[0];
if (!seriesWithColor) {
this.palette.return(oldColor);
}
},
byIdentifier: function (identifier) {
return this.filter(function (series) {
var seriesIdentifier = series.get('identifier');
return seriesIdentifier.namespace === identifier.namespace &&
seriesIdentifier.key === identifier.key;
})[0];
}
});
return SeriesCollection;
});

View File

@ -0,0 +1,89 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./Model'
], function (
Model
) {
/**
* TODO: doc strings.
*/
var XAxisModel = Model.extend({
initialize: function (options) {
this.plot = options.plot;
this.on('change:range', function (newValue, oldValue, model) {
if (!model.get('frozen')) {
model.set('displayRange', newValue);
}
});
this.on('change:frozen', function (frozen, oldValue, model) {
if (!frozen) {
model.set('range', this.get('range'));
}
});
if (this.get('range')) {
this.set('range', this.get('range'));
}
this.listenTo(this, 'change:key', this.changeKey, this);
},
changeKey: function (newKey) {
var series = this.plot.series.first();
if (series) {
var xMetadata = series.metadata.value(newKey);
var xFormat = series.formats[newKey];
this.set('label', xMetadata.name);
this.set('format', xFormat.format.bind(xFormat));
} else {
this.set('format', function (x) {
return x;
});
this.set('label', newKey);
}
this.plot.series.forEach(function (plotSeries) {
plotSeries.set('xKey', newKey);
plotSeries.reset();
});
},
defaults: function (options) {
var bounds = options.openmct.time.bounds();
var timeSystem = options.openmct.time.timeSystem();
var format = options.openmct.$injector.get('formatService')
.getFormat(timeSystem.timeFormat);
return {
name: timeSystem.name,
key: timeSystem.key,
format: format.format.bind(format),
range: {
min: bounds.start,
max: bounds.end
},
frozen: false
};
}
});
return XAxisModel;
});

View File

@ -0,0 +1,219 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./Model',
'lodash'
], function (
Model,
_
) {
/**
* YAxis model
*
* TODO: docstrings.
*
* has the following Model properties:
*
* `autoscale`: boolean, whether or not to autoscale.
* `autoscalePadding`: float, percent of padding to display in plots.
* `displayRange`: the current display range for the x Axis.
* `format`: the formatter for the axis.
* `frozen`: boolean, if true, displayRange will not be updated automatically.
* Used to temporarily disable automatic updates during user interaction.
* `label`: label to display on axis.
* `stats`: Min and Max Values of data, automatically updated by observing
* plot series.
* `values`: for enumerated types, an array of possible display values.
* `range`: the user-configured range to use for display, when autoscale is
* disabled.
*
*/
var YAxisModel = Model.extend({
initialize: function (options) {
this.plot = options.plot;
this.listenTo(this, 'change:stats', this.calculateAutoscaleExtents, this);
this.listenTo(this, 'change:autoscale', this.toggleAutoscale, this);
this.listenTo(this, 'change:autoscalePadding', this.updatePadding, this);
this.listenTo(this, 'change:frozen', this.toggleFreeze, this);
this.listenTo(this, 'change:range', this.updateDisplayRange, this);
this.updateDisplayRange(this.get('range'));
},
listenToSeriesCollection: function (seriesCollection) {
this.seriesCollection = seriesCollection;
this.listenTo(this.seriesCollection, 'add', function (series) {
this.trackSeries(series);
this.updateFromSeries(this.seriesCollection);
}, this);
this.listenTo(this.seriesCollection, 'remove', function (series) {
this.untrackSeries(series);
this.updateFromSeries(this.seriesCollection);
}, this);
this.seriesCollection.forEach(this.trackSeries, this);
this.updateFromSeries(this.seriesCollection);
},
updateDisplayRange: function (range) {
if (!this.get('autoscale')) {
this.set('displayRange', range);
}
},
toggleFreeze: function (frozen) {
if (!frozen) {
this.toggleAutoscale(this.get('autoscale'));
}
},
applyPadding: function (range) {
var padding = Math.abs(range.max - range.min) * this.get('autoscalePadding');
if (padding === 0) {
padding = 1;
}
return {
min: range.min - padding,
max: range.max + padding
};
},
updatePadding: function (newPadding) {
if (this.get('autoscale') && !this.get('frozen') && this.has('stats')) {
this.set('displayRange', this.applyPadding(this.get('stats')));
}
},
calculateAutoscaleExtents: function (newStats) {
if (this.get('autoscale') && !this.get('frozen')) {
if (!newStats) {
this.unset('displayRange');
} else {
this.set('displayRange', this.applyPadding(newStats));
}
}
},
updateStats: function (seriesStats) {
if (!this.has('stats')) {
this.set('stats', {
min: seriesStats.minValue,
max: seriesStats.maxValue
});
return;
}
var stats = this.get('stats');
var changed = false;
if (stats.min > seriesStats.minValue) {
changed = true;
stats.min = seriesStats.minValue;
}
if (stats.max < seriesStats.maxValue) {
changed = true;
stats.max = seriesStats.maxValue;
}
if (changed) {
this.set('stats', {
min: stats.min,
max: stats.max
});
}
},
resetStats: function () {
this.unset('stats');
this.seriesCollection.forEach(function (series) {
if (series.has('stats')) {
this.updateStats(series.get('stats'));
}
}, this);
},
trackSeries: function (series) {
this.listenTo(series, 'change:stats', function (seriesStats) {
if (!seriesStats) {
this.resetStats();
} else {
this.updateStats(seriesStats);
}
}, this);
},
untrackSeries: function (series) {
this.stopListening(series);
this.resetStats();
},
toggleAutoscale: function (autoscale) {
if (autoscale) {
this.set('displayRange', this.applyPadding(this.get('stats')));
} else {
this.set('displayRange', this.get('range'));
}
},
/**
* Update yAxis format, values, and label from known series.
*/
updateFromSeries: function (series) {
var sampleSeries = series.first();
if (!sampleSeries) {
return;
}
var yKey = sampleSeries.get('yKey');
var yMetadata = sampleSeries.metadata.value(yKey);
var yFormat = sampleSeries.formats[yKey];
this.set('format', yFormat.format.bind(yFormat));
this.set('values', yMetadata.values);
var plotModel = this.plot.get('domainObject');
var label = _.get(plotModel, 'configuration.xAxis.label');
if (!label) {
var labelUnits = series.map(function (s) {
return s.metadata.value(s.get('yKey')).units;
}).reduce(function (a, b) {
if (a === undefined) {
return b;
}
if (a === b) {
return a;
}
return '';
}, undefined);
if (labelUnits) {
this.set('label', labelUnits);
return;
}
var labelName = series.map(function (s) {
return s.metadata.value(s.get('yKey')).name;
}).reduce(function (a, b) {
if (a === undefined) {
return b;
}
if (a === b) {
return a;
}
return '';
}, undefined);
this.set('label', labelName);
}
},
defaults: function (options) {
return {
frozen: false,
autoscale: true,
autoscalePadding: 0.1
};
}
});
return YAxisModel;
});

View File

@ -0,0 +1,58 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
], function (
) {
function ConfigStore() {
this.store = {};
this.tracking = {};
}
ConfigStore.prototype.track = function (id) {
if (!this.tracking[id]) {
this.tracking[id] = 0;
}
this.tracking[id] += 1;
};
ConfigStore.prototype.untrack = function (id) {
this.tracking[id] -= 1;
if (this.tracking[id] <= 0) {
delete this.tracking[id];
this.store[id].destroy();
delete this.store[id];
}
};
ConfigStore.prototype.add = function (id, config) {
this.store[id] = config;
};
ConfigStore.prototype.get = function (id) {
return this.store[id];
};
var STORE = new ConfigStore();
return STORE;
});

View File

@ -0,0 +1,160 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
], function (
) {
/**
* Create a new draw API utilizing the Canvas's 2D API for rendering.
*
* @constructor
* @param {CanvasElement} canvas the canvas object to render upon
* @throws {Error} an error is thrown if Canvas's 2D API is unavailab
*/
function Draw2D(canvas) {
this.canvas = canvas;
this.c2d = canvas.getContext('2d');
this.width = canvas.width;
this.height = canvas.height;
this.dimensions = [this.width, this.height];
this.origin = [0, 0];
if (!this.c2d) {
throw new Error("Canvas 2d API unavailable.");
}
}
// Convert from logical to physical x coordinates
Draw2D.prototype.x = function (v) {
return ((v - this.origin[0]) / this.dimensions[0]) * this.width;
};
// Convert from logical to physical y coordinates
Draw2D.prototype.y = function (v) {
return this.height -
((v - this.origin[1]) / this.dimensions[1]) * this.height;
};
// Set the color to be used for drawing operations
Draw2D.prototype.setColor = function (color) {
var mappedColor = color.map(function (c, i) {
return i < 3 ? Math.floor(c * 255) : (c);
}).join(',');
this.c2d.strokeStyle = "rgba(" + mappedColor + ")";
this.c2d.fillStyle = "rgba(" + mappedColor + ")";
};
Draw2D.prototype.clear = function () {
this.width = this.canvas.width = this.canvas.offsetWidth;
this.height = this.canvas.height = this.canvas.offsetHeight;
this.c2d.clearRect(0, 0, this.width, this.height);
};
Draw2D.prototype.setDimensions = function (newDimensions, newOrigin) {
this.dimensions = newDimensions;
this.origin = newOrigin;
};
Draw2D.prototype.drawLine = function (buf, color, points) {
var i;
this.setColor(color);
// Configure context to draw two-pixel-thick lines
this.c2d.lineWidth = 1;
// Start a new path...
if (buf.length > 1) {
this.c2d.beginPath();
this.c2d.moveTo(this.x(buf[0]), this.y(buf[1]));
}
// ...and add points to it...
for (i = 2; i < points * 2; i = i + 2) {
this.c2d.lineTo(this.x(buf[i]), this.y(buf[i + 1]));
}
// ...before finally drawing it.
this.c2d.stroke();
};
Draw2D.prototype.drawSquare = function (min, max, color) {
var x1 = this.x(min[0]),
y1 = this.y(min[1]),
w = this.x(max[0]) - x1,
h = this.y(max[1]) - y1;
this.setColor(color);
this.c2d.fillRect(x1, y1, w, h);
};
Draw2D.prototype.drawPoints = function (
buf,
color,
points,
pointSize
) {
var i = 0,
offset = pointSize / 2;
this.setColor(color);
for (; i < points; i++) {
this.c2d.fillRect(
this.x(buf[i * 2]) - offset,
this.y(buf[i * 2 + 1]) - offset,
pointSize,
pointSize
);
}
};
Draw2D.prototype.drawLimitPoint = function (x, y, size) {
this.c2d.fillRect(x + size, y, size, size);
this.c2d.fillRect(x, y + size, size, size);
this.c2d.fillRect(x - size, y, size, size);
this.c2d.fillRect(x, y - size, size, size);
};
Draw2D.prototype.drawLimitPoints = function (points, color, pointSize) {
var limitSize = pointSize * 2;
var offset = limitSize / 2;
this.setColor(color);
for (var i = 0; i < points.length; i++) {
this.drawLimitPoint(
this.x(points[i].x) - offset,
this.y(points[i].y) - offset,
limitSize
);
}
};
return Draw2D;
});

View File

@ -0,0 +1,95 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/* global console */
define(
[
'./DrawWebGL',
'./Draw2D'
],
function (DrawWebGL, Draw2D) {
var CHARTS = [
{
MAX_INSTANCES: 16,
API: DrawWebGL,
ALLOCATIONS: []
},
{
MAX_INSTANCES: Number.MAX_INFINITY,
API: Draw2D,
ALLOCATIONS: []
}
];
/**
* Draw loader attaches a draw API to a canvas element and returns the
* draw API.
*/
return {
/**
* Return the first draw API available. Returns
* `undefined` if a draw API could not be constructed.
*.
* @param {CanvasElement} canvas - The canvas eelement to attach
the draw API to.
*/
getDrawAPI: function (canvas, overlay) {
var api;
CHARTS.forEach(function (CHART_TYPE) {
if (api) {
return;
}
if (CHART_TYPE.ALLOCATIONS.length >=
CHART_TYPE.MAX_INSTANCES) {
return;
}
try {
api = new CHART_TYPE.API(canvas, overlay);
CHART_TYPE.ALLOCATIONS.push(api);
} catch (e) {
console.warn([
"Could not instantiate chart",
CHART_TYPE.API.name,
";",
e.message
].join(" "));
}
});
if (!api) {
console.warn("Cannot initialize mct-chart.");
}
return api;
},
releaseDrawAPI: function (api) {
CHARTS.forEach(function (CHART_TYPE) {
if (api instanceof CHART_TYPE.API) {
CHART_TYPE.ALLOCATIONS.splice(CHART_TYPE.ALLOCATIONS.indexOf(api), 1);
}
});
}
};
}
);

View File

@ -0,0 +1,226 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
], function (
) {
// WebGL shader sources (for drawing plain colors)
var FRAGMENT_SHADER = [
"precision mediump float;",
"uniform vec4 uColor;",
"void main(void) {",
"gl_FragColor = uColor;",
"}"
].join('\n'),
VERTEX_SHADER = [
"attribute vec2 aVertexPosition;",
"uniform vec2 uDimensions;",
"uniform vec2 uOrigin;",
"uniform float uPointSize;",
"void main(void) {",
"gl_Position = vec4(2.0 * ((aVertexPosition - uOrigin) / uDimensions) - vec2(1,1), 0, 1);",
"gl_PointSize = uPointSize;",
"}"
].join('\n');
/**
* Create a draw api utilizing WebGL.
*
* @constructor
* @param {CanvasElement} canvas the canvas object to render upon
* @throws {Error} an error is thrown if WebGL is unavailable.
*/
function DrawWebGL(canvas, overlay) {
this.canvas = canvas;
this.gl = this.canvas.getContext("webgl", { preserveDrawingBuffer: true }) ||
this.canvas.getContext("experimental-webgl", { preserveDrawingBuffer: true });
this.overlay = overlay;
this.c2d = overlay.getContext('2d');
if (!this.c2d) {
throw new Error("No canvas 2d!");
}
// Ensure a context was actually available before proceeding
if (!this.gl) {
throw new Error("WebGL unavailable.");
}
// Initialize shaders
this.vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
this.gl.shaderSource(this.vertexShader, VERTEX_SHADER);
this.gl.compileShader(this.vertexShader);
this.fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
this.gl.shaderSource(this.fragmentShader, FRAGMENT_SHADER);
this.gl.compileShader(this.fragmentShader);
// Assemble vertex/fragment shaders into programs
this.program = this.gl.createProgram();
this.gl.attachShader(this.program, this.vertexShader);
this.gl.attachShader(this.program, this.fragmentShader);
this.gl.linkProgram(this.program);
this.gl.useProgram(this.program);
// Get locations for attribs/uniforms from the
// shader programs (to pass values into shaders at draw-time)
this.aVertexPosition = this.gl.getAttribLocation(this.program, "aVertexPosition");
this.uColor = this.gl.getUniformLocation(this.program, "uColor");
this.uDimensions = this.gl.getUniformLocation(this.program, "uDimensions");
this.uOrigin = this.gl.getUniformLocation(this.program, "uOrigin");
this.uPointSize = this.gl.getUniformLocation(this.program, "uPointSize");
this.gl.enableVertexAttribArray(this.aVertexPosition);
// Create a buffer to holds points which will be drawn
this.buffer = this.gl.createBuffer();
// Use a line width of 2.0 for legibility
this.gl.lineWidth(2.0);
// Enable blending, for smoothness
this.gl.enable(this.gl.BLEND);
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
}
// Convert from logical to physical x coordinates
DrawWebGL.prototype.x = function (v) {
return ((v - this.origin[0]) / this.dimensions[0]) * this.width;
};
// Convert from logical to physical y coordinates
DrawWebGL.prototype.y = function (v) {
return this.height -
((v - this.origin[1]) / this.dimensions[1]) * this.height;
};
DrawWebGL.prototype.doDraw = function (drawType, buf, color, points) {
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, buf, this.gl.DYNAMIC_DRAW);
this.gl.vertexAttribPointer(this.aVertexPosition, 2, this.gl.FLOAT, false, 0, 0);
this.gl.uniform4fv(this.uColor, color);
this.gl.drawArrays(drawType, 0, points);
};
DrawWebGL.prototype.clear = function () {
this.height = this.canvas.height = this.canvas.offsetHeight;
this.width = this.canvas.width = this.canvas.offsetWidth;
this.overlay.height = this.overlay.offsetHeight;
this.overlay.width = this.overlay.offsetWidth;
// Set the viewport size; note that we use the width/height
// that our WebGL context reports, which may be lower
// resolution than the canvas we requested.
this.gl.viewport(
0,
0,
this.gl.drawingBufferWidth,
this.gl.drawingBufferHeight
);
this.gl.clear(this.gl.COLOR_BUFFER_BIT + this.gl.DEPTH_BUFFER_BIT);
};
/**
* Set the logical boundaries of the chart.
* @param {number[]} dimensions the horizontal and
* vertical dimensions of the chart
* @param {number[]} origin the horizontal/vertical
* origin of the chart
*/
DrawWebGL.prototype.setDimensions = function (dimensions, origin) {
this.dimensions = dimensions;
this.origin = origin;
if (dimensions && dimensions.length > 0 &&
origin && origin.length > 0) {
this.gl.uniform2fv(this.uDimensions, dimensions);
this.gl.uniform2fv(this.uOrigin, origin);
}
};
/**
* Draw the supplied buffer as a line strip (a sequence
* of line segments), in the chosen color.
* @param {Float32Array} buf the line strip to draw,
* in alternating x/y positions
* @param {number[]} color the color to use when drawing
* the line, as an RGBA color where each element
* is in the range of 0.0-1.0
* @param {number} points the number of points to draw
*/
DrawWebGL.prototype.drawLine = function (buf, color, points) {
this.doDraw(this.gl.LINE_STRIP, buf, color, points);
};
/**
* Draw the buffer as points.
*
*/
DrawWebGL.prototype.drawPoints = function (buf, color, points, pointSize) {
this.gl.uniform1f(this.uPointSize, pointSize);
this.doDraw(this.gl.POINTS, buf, color, points);
};
/**
* Draw a rectangle extending from one corner to another,
* in the chosen color.
* @param {number[]} min the first corner of the rectangle
* @param {number[]} max the opposite corner
* @param {number[]} color the color to use when drawing
* the rectangle, as an RGBA color where each element
* is in the range of 0.0-1.0
*/
DrawWebGL.prototype.drawSquare = function (min, max, color) {
this.doDraw(this.gl.TRIANGLE_FAN, new Float32Array(
min.concat([min[0], max[1]]).concat(max).concat([max[0], min[1]])
), color, 4);
};
DrawWebGL.prototype.drawLimitPoint = function (x, y, size) {
this.c2d.fillRect(x + size, y, size, size);
this.c2d.fillRect(x, y + size, size, size);
this.c2d.fillRect(x - size, y, size, size);
this.c2d.fillRect(x, y - size, size, size);
};
DrawWebGL.prototype.drawLimitPoints = function (points, color, pointSize) {
var limitSize = pointSize * 2;
var offset = limitSize / 2;
var mappedColor = color.map(function (c, i) {
return i < 3 ? Math.floor(c * 255) : (c);
}).join(',');
this.c2d.strokeStyle = "rgba(" + mappedColor + ")";
this.c2d.fillStyle = "rgba(" + mappedColor + ")";
for (var i = 0; i < points.length; i++) {
this.drawLimitPoint(
this.x(points[i].x) - offset,
this.y(points[i].y) - offset,
limitSize
);
}
};
return DrawWebGL;
});

View File

@ -0,0 +1,55 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(function () {
/**
* Simple directive that removes the elements pool when used in the
* inspector region. Workaround until we have better control of screen
* regions.
*/
return function HideElementPoolDirective() {
return {
restrict: "A",
link: function ($scope, $element) {
var splitter = $element.parent();
while (splitter[0].tagName !== 'MCT-SPLIT-PANE') {
splitter = splitter.parent();
}
[
'.split-pane-component.pane.bottom',
'mct-splitter'
].forEach(function (selector) {
var element = splitter[0].querySelectorAll(selector)[0];
element.style.maxHeight = '0px';
element.style.minHeight = '0px';
});
splitter[0]
.querySelectorAll('.split-pane-component.pane.top')[0]
.style
.bottom = '0px';
}
};
};
});

View File

@ -0,0 +1,68 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[
'./Region'
],
function (Region) {
/**
* Defines the a default Inspector region. Captured in a class to
* allow for modular extension and customization of regions based on
* the typical case.
* @memberOf platform/commonUI/regions
* @constructor
*/
function InspectorRegion() {
Region.call(this, {'name': 'Inspector'});
this.buildRegion();
}
InspectorRegion.prototype = Object.create(Region.prototype);
InspectorRegion.prototype.constructor = Region;
/**
* @private
*/
InspectorRegion.prototype.buildRegion = function () {
var metadataRegion = {
name: 'metadata',
title: 'Metadata Region',
// Which modes should the region part be visible in? If
// nothing provided here, then assumed that part is visible
// in both. The visibility or otherwise of a region part
// should be decided by a policy. In this case, 'modes' is a
// shortcut that is used by the EditableRegionPolicy.
modes: ['browse', 'edit'],
content: {
key: 'object-properties'
}
};
this.addRegion(new Region(metadataRegion), 0);
};
return InspectorRegion;
}
);

View File

@ -0,0 +1,40 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./Region'
], function (
Region
) {
var PlotBrowseRegion = new Region({
name: "plot-options",
title: "Plot Options",
modes: ['browse'],
content: {
key: "plot-options-browse"
}
});
return PlotBrowseRegion;
});

View File

@ -0,0 +1,18 @@
/*global define*/
define([
'./Region'
], function (
Region
) {
var PlotEditRegion = new Region({
name: "plot-options",
title: "Plot Options",
modes: ['edit'],
content: {
key: "plot-options-edit"
}
});
return PlotEditRegion;
});

View File

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

View File

@ -0,0 +1,258 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'../configuration/configStore',
'../lib/eventHelpers',
'../../../../api/objects/object-utils',
'lodash'
], function (
configStore,
eventHelpers,
objectUtils,
_
) {
/**
* The LayoutController is responsible for supporting the
* Layout view. It arranges frames according to saved configuration
* and provides methods for updating these based on mouse
* movement.
* @memberof platform/features/plot
* @constructor
* @param {Scope} $scope the controller's Angular scope
*/
function PlotOptionsController($scope, openmct, $timeout) {
this.$scope = $scope;
this.openmct = openmct;
this.$timeout = $timeout;
this.configId = $scope.domainObject.getId();
this.setUpScope();
}
eventHelpers.extend(PlotOptionsController.prototype);
PlotOptionsController.prototype.setColor = function (series, color) {
var seriesWithColor = this.config.series.filter(function (s) {
return s.get('color') === color;
})[0];
var oldColor = series.get('color');
series.set('color', color);
var seriesIndex = this.config.series.indexOf(series);
this.openmct.objects.mutate(
this.domainObject,
'configuration.series[' + seriesIndex + '].color',
color.asHexString()
);
if (seriesWithColor) {
seriesWithColor.set('color', oldColor);
var oldSeriesIndex = this.config.series.indexOf(seriesWithColor);
this.openmct.objects.mutate(
this.domainObject,
'configuration.series[' + oldSeriesIndex + '].color',
oldColor.asHexString()
);
}
};
PlotOptionsController.prototype.updateDomainObject = function (domainObject) {
this.domainObject = domainObject;
};
PlotOptionsController.prototype.destroy = function () {
configStore.untrack(this.configId);
this.stopListening();
this.unlisten();
};
PlotOptionsController.prototype.setUpScope = function () {
var config = configStore.get(this.configId);
if (!config) {
this.$timeout(this.setUpScope.bind(this));
return;
}
configStore.track(this.configId);
this.config = this.$scope.config = config;
this.$scope.setColor = this.setColor.bind(this);
this.$scope.form = {series: []};
this.$scope.validation = {};
this.domainObject = this.config.get('domainObject');
this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject.bind(this));
this.listenTo(this.$scope, '$destroy', this.destroy, this);
this.listenTo(config.series, 'add', this.addSeries, this);
this.listenTo(config.series, 'remove', this.resetAllSeries, this);
config.series.forEach(this.addSeries, this);
this.linkFields(config.yAxis, 'label', 'form.yAxis.label', undefined, undefined, 'configuration.yAxis.label');
this.linkFields(config.yAxis, 'autoscale', 'form.yAxis.autoscale', Boolean, undefined, 'configuration.yAxis.autoscale');
this.linkFields(config.yAxis, 'autoscalePadding', 'form.yAxis.autoscalePadding', Number, undefined, 'configuration.yAxis.autoscalePadding');
this.linkFields(config.yAxis, 'range', 'form.yAxis.range', function coerceRange(range) {
if (!range) {
return {
min: 0,
max: 0
};
}
var newRange = {};
if (typeof range.min !== undefined) {
newRange.min = Number(range.min);
}
if (typeof range.max !== undefined) {
newRange.max = Number(range.max);
}
return newRange;
}, function validateRange(range) {
if (!range) {
return 'Need range';
}
if (!range.min) {
return 'Must specify Minimum';
}
if (!range.max) {
return 'Must specify Maximum';
}
if (_.isNaN(Number(range.min))) {
return 'Minimum must be a number.';
}
if (_.isNaN(Number(range.max))) {
return 'Maximum must be a number.';
}
if (Number(range.min) > Number(range.max)) {
return 'Minimum must be less than Maximum.';
}
if (config.yAxis.get('autoscale')) {
return false;
}
return true;
}, 'configuration.yAxis.range');
this.linkFields(config.legend, 'position', 'form.legend.position', undefined, undefined, 'configuration.legend.position');
this.linkFields(config.legend, 'expandByDefault', 'form.legend.expandByDefault', Boolean, undefined, 'configuration.legend.expandByDefault');
this.linkFields(config.legend, 'valueToShowWhenCollapsed', 'form.legend.valueToShowWhenCollapsed', undefined, undefined, 'configuration.legend.valueToShowWhenCollapsed');
this.linkFields(config.legend, 'showValueWhenExpanded', 'form.legend.showValueWhenExpanded', Boolean, undefined, 'configuration.legend.showValueWhenExpanded');
this.linkFields(config.legend, 'showTimestampWhenExpanded', 'form.legend.showTimestampWhenExpanded', Boolean, undefined, 'configuration.legend.showTimestampWhenExpanded');
this.linkFields(config.legend, 'showMaximumWhenExpanded', 'form.legend.showMaximumWhenExpanded', Boolean, undefined, 'configuration.legend.showMaximumWhenExpanded');
this.linkFields(config.legend, 'showMinimumWhenExpanded', 'form.legend.showMinimumWhenExpanded', Boolean, undefined, 'configuration.legend.showMinimumWhenExpanded');
};
PlotOptionsController.prototype.addSeries = function (series, index) {
if (this.$scope.form.series[index]) {
// the way listeners work, this will blow up. At this point, it
// can't technically occur, but if we added some sort of reordering
// at a later date, it would not be clear why this doesn't work.
// So here's to hoping this helps debugging.
throw new Error('Plot options does not support insert at index.');
}
var metadata = series.metadata;
this.$scope.form.series[index] = {
yAxisOptions: metadata.valuesForHints(['range']).map(function (o) {
return {
name: o.key,
value: o.key
};
})
};
var seriesObject = series.domainObject;
var seriesId = objectUtils.makeKeyString(seriesObject.identifier);
var configPath = 'configuration.series[' + index + '].';
var path = 'form.series[' + index + '].';
this.$scope.domainObject.useCapability('composition')
.then(function (children) {
children.forEach(function (child) {
if (child.getId() === seriesId) {
series.oldObject = child;
}
});
}.bind(this));
this.linkFields(series, 'yKey', path + 'yKey', undefined, undefined, configPath + 'yKey');
this.linkFields(series, 'interpolate', path + 'interpolate', undefined, undefined, configPath + 'interpolate');
this.linkFields(series, 'markers', path + 'markers', undefined, undefined, configPath + 'markers');
this.linkFields(series, 'markerSize', path + 'markerSize', Number, undefined, configPath + 'markerSize');
this.linkFields(series, 'alarmMarkers', path + 'alarmMarkers', Boolean, undefined, configPath + 'alarmMarkers');
};
PlotOptionsController.prototype.resetAllSeries = function (series, index) {
this.removeSeries(series);
this.config.series.forEach(this.removeSeries, this);
this.$scope.form.series = [];
this.config.series.forEach(this.addSeries, this);
};
PlotOptionsController.prototype.removeSeries = function (series) {
this.stopListening(series);
series.stopListening(this.$scope);
};
PlotOptionsController.prototype.linkFields = function (
model,
prop,
scopePath,
coerce,
validate,
objectPath
) {
if (!coerce) {
coerce = function (v) {
return v;
};
}
if (!validate) {
validate = function () {
return true;
};
}
this.listenTo(model, 'change:' + prop, function (newVal, oldVal) {
if (!_.isEqual(coerce(_.get(this.$scope, scopePath)), coerce(newVal))) {
_.set(this.$scope, scopePath, coerce(newVal));
}
}, this);
model.listenTo(this.$scope, 'change:' + scopePath, function (newVal, oldVal) {
if (_.isEqual(coerce(newVal), coerce(model.get(prop)))) {
return; // Don't trigger excessive mutations.
}
var validationResult = validate(newVal);
if (validationResult === true) {
delete this.$scope.validation[scopePath];
} else {
this.$scope.validation[scopePath] = validationResult;
return;
}
if (!_.isEqual(coerce(newVal), coerce(oldVal))) {
model.set(prop, coerce(newVal));
if (objectPath && this.$scope.domainObject.getCapability('editor').isEditContextRoot()) {
this.openmct.objects.mutate(this.domainObject, objectPath, coerce(newVal));
}
}
}, this);
_.set(this.$scope, scopePath, coerce(model.get(prop)));
};
return PlotOptionsController;
});

View File

@ -0,0 +1,177 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
], function () {
/**
* A class for encapsulating structure and behaviour of the plot
* options form
* @memberOf platform/features/plot
* @param topic
* @constructor
*/
function PlotOptionsForm() {
/*
Defined below are the form structures for the plot options.
*/
this.xAxisForm = {
name: 'x-axis',
sections: [{
name: 'x-axis',
rows: [
{
name: 'Domain',
control: 'select',
key: 'key',
options: [
{
name: 'SCET',
value: 'scet'
},
{
name: 'ERT',
value: 'ert'
},
{
name: 'SCLK',
value: 'sclk'
},
{
name: 'LST',
value: 'lst'
}
]
}
]
}]};
this.yAxisForm = {
name: 'y-axis',
sections: [{
// Will need to be repeated for each y-axis, with a
// distinct name for each. Ideally the name of the axis
// itself.
name: 'y-axis',
rows: [
{
name: 'Range',
control: 'select',
key: 'key',
options: [
{
name: 'Autoselect',
value: 'auto'
},
{
name: 'EU',
value: 'eu'
},
{
name: 'DN',
value: 'dn'
},
{
name: 'Status',
value: 'enum'
}
]
},
{
name: 'Autoscale',
control: 'checkbox',
key: 'autoscale'
},
{
name: 'Min',
control: 'textfield',
key: 'min',
pattern: '[0-9]',
inputsize: 'sm'
},
{
name: 'Max',
control: 'textfield',
key: 'max',
pattern: '[0-9]',
inputsize: 'sm'
}
]
}]
};
this.plotSeriesForm = {
name: 'Series Options',
sections: [
{
rows: [
{
name: 'Color',
control: 'color',
key: 'color'
}]
},
{
rows: [
{
name: 'Markers',
control: 'checkbox',
key: 'markers',
layout: 'control-first'
}
]
},
{
rows: [
{
name: 'No Line',
control: 'radio',
key: 'interpolate',
value: 'none',
layout: 'control-first'
},
{
name: 'Step Line',
control: 'radio',
key: 'interpolate',
value: 'stepAfter',
layout: 'control-first'
},
{
name: 'Linear Line',
control: 'radio',
key: 'interpolate',
value: 'linear',
layout: 'control-first'
}
]
}
]
};
}
return PlotOptionsForm;
});

View File

@ -0,0 +1,100 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
/**
* @typeDef {object} PartContents
* @property {string} key If the part is defined as a
* representation, the key corresponding to the representation.
* @memberOf platform/commonUI/regions
*/
/**
* @typeDef {object} RegionConfiguration
* @property {string} name A unique name for this region part
* @property {PartContents} [content] the details of the region being
* defined
* @property {Array<string>} [modes] the modes that this region
* should be included in. Options are 'edit' and 'browse'. By
* default, will be included in both. Inclusion of regions is
* determined by policies of category 'region'. By default, the
* {EditableRegionPolicy} will be applied.
* @memberOf platform/commonUI/regions
*/
/**
* Defines the interface for a screen region. A screen region is a
* section of the browse an edit screens for an object. Regions are
* declared in object type definitions.
* @memberOf platform/commonUI/regions
* @abstract
* @constructor
*/
function Region(configuration) {
configuration = configuration || {};
this.name = configuration.name;
this.content = configuration.content;
this.modes = configuration.modes;
this.regions = [];
}
/**
* Adds a sub-region to this region.
* @param {Region} region the part to add
* @param {number} [index] the position to insert the region. By default
* will add to the end
*/
Region.prototype.addRegion = function (region, index) {
if (index) {
this.regions.splice(index, 0, region);
} else {
this.regions.push(region);
}
};
/**
* Removes a sub-region from this region.
* @param {Region | number | strnig} region The region to
* remove. If a number, will remove the region at that index. If a
* string, will remove the region with the matching name. If an
* object, will attempt to remove that object from the Region
*/
Region.prototype.removeRegion = function (region) {
if (typeof region === 'number') {
this.regions.splice(region, 1);
} else if (typeof region === 'string') {
this.regions = this.regions.filter(function (thisRegion) {
return thisRegion.name !== region;
});
} else {
this.regions.splice(this.regions.indexOf(region), 1);
}
};
return Region;
}
);

View File

@ -0,0 +1,205 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/* global console */
define(function () {
var COLOR_PALETTE = [
[0x20, 0xB2, 0xAA],
[0x9A, 0xCD, 0x32],
[0xFF, 0x8C, 0x00],
[0xD2, 0xB4, 0x8C],
[0x40, 0xE0, 0xD0],
[0x41, 0x69, 0xFF],
[0xFF, 0xD7, 0x00],
[0x6A, 0x5A, 0xCD],
[0xEE, 0x82, 0xEE],
[0xCC, 0x99, 0x66],
[0x99, 0xCC, 0xCC],
[0x66, 0xCC, 0x33],
[0xFF, 0xCC, 0x00],
[0xFF, 0x66, 0x33],
[0xCC, 0x66, 0xFF],
[0xFF, 0x00, 0x66],
[0xFF, 0xFF, 0x00],
[0x80, 0x00, 0x80],
[0x00, 0x86, 0x8B],
[0x00, 0x8A, 0x00],
[0xFF, 0x00, 0x00],
[0x00, 0x00, 0xFF],
[0xF5, 0xDE, 0xB3],
[0xBC, 0x8F, 0x8F],
[0x46, 0x82, 0xB4],
[0xFF, 0xAF, 0xAF],
[0x43, 0xCD, 0x80],
[0xCD, 0xC1, 0xC5],
[0xA0, 0x52, 0x2D],
[0x64, 0x95, 0xED]
];
function isDefaultColor(color) {
var a = color.asIntegerArray();
return COLOR_PALETTE.some(function (b) {
return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
});
}
/**
* A representation of a color that allows conversions between different
* formats.
*
* @constructor
*/
function Color(integerArray) {
this.integerArray = integerArray;
}
Color.fromHexString = function (hexString) {
if (!/#([0-9a-fA-F]{2}){2}/.test(hexString)) {
throw new Error(
'Invalid input "' +
hexString +
'". Hex string must be in CSS format e.g. #00FF00'
);
}
return new Color([
parseInt(hexString.slice(1, 3), 16),
parseInt(hexString.slice(3, 5), 16),
parseInt(hexString.slice(5, 7), 16)
]);
};
/**
* Return color as a three element array of RGB values, where each value
* is a integer in the range of 0-255.
*
* @return {number[]} the color, as integer RGB values
*/
Color.prototype.asIntegerArray = function () {
return this.integerArray.map(function (c) {
return c;
});
};
/**
* Return color as a string using #-prefixed six-digit RGB hex notation
* (e.g. #FF0000). See http://www.w3.org/TR/css3-color/#rgb-color.
*
* @return {string} the color, as a style-friendly string
*/
Color.prototype.asHexString = function () {
return '#' + this.integerArray.map(function (c) {
return (c < 16 ? '0' : '') + c.toString(16);
}).join('');
};
/**
* Return color as a RGBA float array.
*
* This format is present specifically to support use with
* WebGL, which expects colors of that form.
*
* @return {number[]} the color, as floating-point RGBA values
*/
Color.prototype.asRGBAArray = function () {
return this.integerArray.map(function (c) {
return c / 255.0;
}).concat([1]);
};
Color.prototype.equalTo = function (otherColor) {
return this.asHexString() === otherColor.asHexString();
};
/**
* A color palette stores a set of colors and allows for different
* methods of color allocation.
*
* @constructor
*/
function ColorPalette() {
var allColors = this.allColors = COLOR_PALETTE.map(function (color) {
return new Color(color);
});
this.colorGroups = [[], [], []];
for (var i = 0; i < allColors.length; i++) {
this.colorGroups[i % 3].push(allColors[i]);
}
this.reset();
}
/**
*
*/
ColorPalette.prototype.groups = function () {
return this.colorGroups;
};
ColorPalette.prototype.reset = function () {
this.availableColors = this.allColors.slice();
};
ColorPalette.prototype.remove = function (color) {
this.availableColors = this.availableColors.filter(function (c) {
return !c.equalTo(color);
});
};
ColorPalette.prototype.return = function (color) {
if (isDefaultColor(color)) {
this.availableColors.unshift(color);
}
};
ColorPalette.prototype.getByHexString = function (hexString) {
var color = Color.fromHexString(hexString);
return color;
};
/**
* @returns {Color} the next unused color in the palette. If all colors
* have been allocated, it will wrap around.
*/
ColorPalette.prototype.getNextColor = function () {
if (!this.availableColors.length) {
console.warn('Color Palette empty, reusing colors!');
this.reset();
}
return this.availableColors.shift();
};
/**
* @param {number} index the index of the color to return. An index
* value larger than the size of the index will wrap around.
* @returns {Color}
*/
ColorPalette.prototype.getColor = function (index) {
return this.colors[index % this.colors.length];
};
return {
Color: Color,
ColorPalette: ColorPalette
};
});

View File

@ -0,0 +1,97 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*jscs:disable disallowDanglingUnderscores */
define([
], function (
) {
var helperFunctions = {
listenTo: function (object, event, callback, context) {
if (!this._listeningTo) {
this._listeningTo = [];
}
var listener = {
object: object,
event: event,
callback: callback,
context: context,
_cb: !!context ? callback.bind(context) : callback
};
if (object.$watch && event.indexOf('change:') === 0) {
var scopePath = event.replace('change:', '');
listener.unlisten = object.$watch(scopePath, listener._cb, true);
} else if (object.$on) {
listener.unlisten = object.$on(event, listener._cb);
} else if (object.addEventListener) {
object.addEventListener(event, listener._cb);
} else {
object.on(event, listener._cb);
}
this._listeningTo.push(listener);
},
stopListening: function (object, event, callback, context) {
if (!this._listeningTo) {
this._listeningTo = [];
}
this._listeningTo.filter(function (listener) {
if (object && object !== listener.object) {
return false;
}
if (event && event !== listener.event) {
return false;
}
if (callback && callback !== listener.callback) {
return false;
}
if (context && context !== listener.context) {
return false;
}
return true;
})
.map(function (listener) {
if (listener.unlisten) {
listener.unlisten();
} else if (listener.object.removeEventListener) {
listener.object.removeEventListener(listener.event, listener._cb);
} else {
listener.object.off(listener.event, listener._cb);
}
return listener;
})
.forEach(function (listener) {
this._listeningTo.splice(this._listeningTo.indexOf(listener), 1);
}, this);
},
extend: function (object) {
object.listenTo = helperFunctions.listenTo;
object.stopListening = helperFunctions.stopListening;
}
};
return helperFunctions;
});

View File

@ -0,0 +1,67 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*jscs:disable disallowDanglingUnderscores */
define([
], function (
) {
function extend(props) {
/*jshint validthis: true*/
var parent = this,
child,
Surrogate;
if (props && props.hasOwnProperty('constructor')) {
child = props.constructor;
} else {
child = function () {
return parent.apply(this, arguments);
};
}
Object.keys(parent).forEach(function copyStaticProperties(propKey) {
child[propKey] = parent[propKey];
});
// Surrogate allows inheriting from parent without invoking constructor.
Surrogate = function () {
this.constructor = child;
};
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate();
if (props) {
Object.keys(props).forEach(function copyInstanceProperties(key) {
child.prototype[key] = props[key];
});
}
child.__super__ = parent.prototype;
return child;
}
return extend;
});

View File

@ -0,0 +1,78 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*jscs:disable disallowDanglingUnderscores */
define([
], function (
) {
/**
* A scale has an input domain and an output range. It provides functions
* `scale` return the range value associated with a domain value.
* `invert` return the domain value associated with range value.
*/
function LinearScale(domain) {
this.domain(domain);
}
LinearScale.prototype.domain = function (newDomain) {
if (newDomain) {
this._domain = newDomain;
this._domainDenominator = newDomain.max - newDomain.min;
}
return this._domain;
};
LinearScale.prototype.range = function (newRange) {
if (newRange) {
this._range = newRange;
this._rangeDenominator = newRange.max - newRange.min;
}
return this._range;
};
LinearScale.prototype.scale = function (domainValue) {
if (!this._domain || !this._range) {
return;
}
var domainOffset = domainValue - this._domain.min,
rangeFraction = domainOffset - this._domainDenominator,
rangeOffset = rangeFraction * this._rangeDenominator,
rangeValue = rangeOffset + this._range.min;
return rangeValue;
};
LinearScale.prototype.invert = function (rangeValue) {
if (!this._domain || !this._range) {
return;
}
var rangeOffset = rangeValue - this._range.min,
domainFraction = rangeOffset / this._rangeDenominator,
domainOffset = domainFraction * this._domainDenominator,
domainValue = domainOffset + this._domain.min;
return domainValue;
};
return LinearScale;
});
/**
*
*/

View File

@ -0,0 +1,339 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./LinearScale',
'../lib/eventHelpers'
], function (
LinearScale,
eventHelpers
) {
/**
* MCTPlotController handles user interactions with the plot canvas.
* It supports pan and zoom, implements zoom history, and supports locating
* values near the cursor.
*/
function MCTPlotController($scope, $element, $window) {
this.$scope = $scope;
this.$scope.config = this.config;
this.$scope.plot = this;
this.$element = $element;
this.$window = $window;
this.xScale = new LinearScale(this.config.xAxis.get('displayRange'));
this.yScale = new LinearScale(this.config.yAxis.get('displayRange'));
this.pan = undefined;
this.marquee = undefined;
this.chartElementBounds = undefined;
this.tickUpdate = false;
this.$scope.plotHistory = this.plotHistory = [];
this.listenTo(this.$scope, 'plot:clearHistory', this.clear, this);
this.initialize();
}
MCTPlotController.$inject = ['$scope', '$element', '$window'];
eventHelpers.extend(MCTPlotController.prototype);
MCTPlotController.prototype.initialize = function () {
this.$canvas = this.$element.find('canvas');
this.listenTo(this.$canvas, 'mousemove', this.trackMousePosition, this);
this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this);
this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this);
this.watchForMarquee();
this.listenTo(this.$window, 'keydown', this.toggleInteractionMode, this);
this.listenTo(this.$window, 'keyup', this.resetInteractionMode, this);
this.$scope.rectangles = [];
this.$scope.tickWidth = 0;
this.$scope.xAxis = this.config.xAxis;
this.$scope.yAxis = this.config.yAxis;
this.$scope.series = this.config.series.models;
this.$scope.legend = this.config.legend;
this.listenTo(this.$scope, '$destroy', this.destroy, this);
this.listenTo(this.$scope, 'plot:tickWidth', this.onTickWidthChange, this);
this.listenTo(this.$scope, 'plot:highlight:set', this.onPlotHighlightSet, this);
this.listenTo(this.config.xAxis, 'change:displayRange', this.onXAxisChange, this);
this.listenTo(this.config.yAxis, 'change:displayRange', this.onYAxisChange, this);
};
MCTPlotController.prototype.onXAxisChange = function (displayBounds) {
if (displayBounds) {
this.xScale.domain(displayBounds);
}
};
MCTPlotController.prototype.onYAxisChange = function (displayBounds) {
if (displayBounds) {
this.yScale.domain(displayBounds);
}
};
MCTPlotController.prototype.onTickWidthChange = function ($event, width) {
if ($event.targetScope.domainObject !== this.$scope.domainObject) {
// Always accept tick width if it comes from a different object.
this.$scope.tickWidth = width;
} else {
// Otherwise, only accept tick with if it's larger.
var newWidth = Math.max(width, this.$scope.tickWidth);
if (newWidth !== this.$scope.tickWidth) {
this.$scope.tickWidth = newWidth;
this.$scope.$digest();
}
}
};
MCTPlotController.prototype.trackMousePosition = function ($event) {
this.trackChartElementBounds($event);
this.xScale.range({min: 0, max: this.chartElementBounds.width});
this.yScale.range({min: 0, max: this.chartElementBounds.height});
this.positionOverElement = {
x: $event.clientX - this.chartElementBounds.left,
y: this.chartElementBounds.height -
($event.clientY - this.chartElementBounds.top)
};
this.positionOverPlot = {
x: this.xScale.invert(this.positionOverElement.x),
y: this.yScale.invert(this.positionOverElement.y)
};
this.highlightValues(this.positionOverPlot.x);
this.updateMarquee();
this.updatePan();
this.$scope.$digest();
$event.preventDefault();
};
MCTPlotController.prototype.trackChartElementBounds = function ($event) {
if ($event.target === this.$canvas[1]) {
this.chartElementBounds = $event.target.getBoundingClientRect();
}
};
MCTPlotController.prototype.onPlotHighlightSet = function ($e, point) {
if (point === this.highlightPoint) {
return;
}
this.highlightValues(point);
};
MCTPlotController.prototype.highlightValues = function (point) {
this.highlightPoint = point;
this.$scope.$emit('plot:highlight:update', point);
if (!point) {
this.$scope.highlights = [];
this.$scope.series.map(function (series) {
delete series.closest;
});
} else {
this.$scope.highlights = this.$scope.series
.filter(function (series) {
return series.data.length > 0;
}).map(function (series) {
series.closest = series.nearestPoint(point);
return {
series: series,
point: series.closest
};
}, this);
}
this.$scope.$digest();
};
MCTPlotController.prototype.untrackMousePosition = function () {
this.positionOverElement = undefined;
this.positionOverPlot = undefined;
this.highlightValues();
};
MCTPlotController.prototype.onMouseDown = function ($event) {
this.listenTo(this.$window, 'mouseup', this.onMouseUp, this);
this.listenTo(this.$window, 'mousemove', this.trackMousePosition, this);
if (this.allowPan) {
return this.startPan($event);
}
if (this.allowMarquee) {
return this.startMarquee($event);
}
};
MCTPlotController.prototype.onMouseUp = function ($event) {
this.stopListening(this.$window, 'mouseup', this.onMouseUp, this);
this.stopListening(this.$window, 'mousemove', this.trackMousePosition, this);
if (this.pan) {
this.endPan($event);
}
if (this.marquee) {
this.endMarquee($event);
}
this.$scope.$apply();
};
MCTPlotController.prototype.updateMarquee = function () {
if (!this.marquee) {
return;
}
this.marquee.end = this.positionOverPlot;
};
MCTPlotController.prototype.startMarquee = function ($event) {
this.trackMousePosition($event);
if (this.positionOverPlot) {
this.freeze();
this.marquee = {
start: this.positionOverPlot,
end: this.positionOverPlot,
color: [1, 1, 1, 0.5]
};
this.$scope.rectangles.push(this.marquee);
this.trackHistory();
}
};
MCTPlotController.prototype.endMarquee = function () {
if (this.marquee.start.x !== this.marquee.end.x &&
this.marquee.start.y !== this.marquee.end.y) {
this.$scope.xAxis.set('displayRange', {
min: Math.min(this.marquee.start.x, this.marquee.end.x),
max: Math.max(this.marquee.start.x, this.marquee.end.x)
});
this.$scope.yAxis.set('displayRange', {
min: Math.min(this.marquee.start.y, this.marquee.end.y),
max: Math.max(this.marquee.start.y, this.marquee.end.y)
});
this.$scope.$emit('user:viewport:change:end');
}
this.$scope.rectangles = [];
this.marquee = undefined;
};
MCTPlotController.prototype.startPan = function ($event) {
this.trackMousePosition($event);
this.freeze();
this.pan = {
start: this.positionOverPlot
};
$event.preventDefault();
this.trackHistory();
return false;
};
MCTPlotController.prototype.updatePan = function () {
// calculate offset between points. Apply that offset to viewport.
if (!this.pan) {
return;
}
var dX = this.pan.start.x - this.positionOverPlot.x,
dY = this.pan.start.y - this.positionOverPlot.y,
xRange = this.config.xAxis.get('displayRange'),
yRange = this.config.yAxis.get('displayRange');
this.config.xAxis.set('displayRange', {
min: xRange.min + dX,
max: xRange.max + dX
});
this.config.yAxis.set('displayRange', {
min: yRange.min + dY,
max: yRange.max + dY
});
};
MCTPlotController.prototype.trackHistory = function () {
this.plotHistory.push({
x: this.config.xAxis.get('displayRange'),
y: this.config.yAxis.get('displayRange')
});
};
MCTPlotController.prototype.endPan = function () {
this.pan = undefined;
this.$scope.$emit('user:viewport:change:end');
};
MCTPlotController.prototype.watchForMarquee = function () {
this.$canvas.removeClass('plot-drag');
this.$canvas.addClass('plot-marquee');
this.allowPan = false;
this.allowMarquee = true;
};
MCTPlotController.prototype.watchForPan = function () {
this.$canvas.addClass('plot-drag');
this.$canvas.removeClass('plot-marquee');
this.allowPan = true;
this.allowMarquee = false;
};
MCTPlotController.prototype.toggleInteractionMode = function (event) {
if (event.keyCode === 18) { // control key.
this.watchForPan();
}
};
MCTPlotController.prototype.resetInteractionMode = function (event) {
if (event.keyCode === 18) {
this.watchForMarquee();
}
};
MCTPlotController.prototype.freeze = function () {
this.config.yAxis.set('frozen', true);
this.config.xAxis.set('frozen', true);
};
MCTPlotController.prototype.clear = function () {
this.config.yAxis.set('frozen', false);
this.config.xAxis.set('frozen', false);
this.$scope.plotHistory = this.plotHistory = [];
this.$scope.$emit('user:viewport:change:end');
};
MCTPlotController.prototype.back = function () {
var previousAxisRanges = this.plotHistory.pop();
if (this.plotHistory.length === 0) {
this.clear();
return;
}
this.config.xAxis.set('displayRange', previousAxisRanges.x);
this.config.yAxis.set('displayRange', previousAxisRanges.y);
this.$scope.$emit('user:viewport:change:end');
};
MCTPlotController.prototype.destroy = function () {
this.stopListening();
};
return MCTPlotController;
});

View File

@ -0,0 +1,46 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./MCTPlotController',
'text!../../res/templates/mct-plot.html'
], function (
MCTPlotController,
PlotTemplate
) {
function MCTPlot() {
return {
restrict: "E",
template: PlotTemplate,
controller: MCTPlotController,
controllerAs: 'mctPlotController',
bindToController: {
config: "="
},
scope: true
};
}
return MCTPlot;
});

View File

@ -0,0 +1,248 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'lodash',
'../lib/eventHelpers'
], function (
_,
eventHelpers
) {
var e10 = Math.sqrt(50),
e5 = Math.sqrt(10),
e2 = Math.sqrt(2);
/**
* Nicely formatted tick steps from d3-array.
*/
function tickStep(start, stop, count) {
var step0 = Math.abs(stop - start) / Math.max(0, count),
step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10)),
error = step0 / step1;
if (error >= e10) {
step1 *= 10;
} else if (error >= e5) {
step1 *= 5;
} else if (error >= e2) {
step1 *= 2;
}
return stop < start ? -step1 : step1;
}
/**
* Find the precision (number of decimals) of a step. Used to round
* ticks to precise values.
*/
function getPrecision(step) {
var exponential = step.toExponential(),
i = exponential.indexOf('e');
if (i === -1) {
return 0;
}
var precision = Math.max(0, -(+exponential.slice(i + 1)));
if (precision > 20) {
precision = 20;
}
return precision;
}
/**
* Linear tick generation from d3-array.
*/
function ticks(start, stop, count) {
var step = tickStep(start, stop, count),
precision = getPrecision(step);
return _.range(
Math.ceil(start / step) * step,
Math.floor(stop / step) * step + step / 2, // inclusive
step
).map(function round(tick) {
return +tick.toFixed(precision);
});
}
function commonPrefix(a, b) {
var maxLen = Math.min(a.length, b.length);
var breakpoint = 0;
for (var i = 0; i < maxLen; i++) {
if (a[i] !== b[i]) {
break;
}
if (a[i] === ' ') {
breakpoint = i + 1;
}
}
return a.slice(0, breakpoint);
}
function commonSuffix(a, b) {
var maxLen = Math.min(a.length, b.length);
var breakpoint = 0;
for (var i = 0; i <= maxLen; i++) {
if (a[a.length - i] !== b[b.length - i]) {
break;
}
if ('. '.indexOf(a[a.length - i]) !== -1) {
breakpoint = i;
}
}
return a.slice(a.length - breakpoint);
}
function MCTTicksController($scope, $element) {
this.$scope = $scope;
this.$element = $element;
this.tickCount = 4;
this.tickUpdate = false;
this.listenTo(this.axis, 'change:displayRange', this.updateTicks, this);
this.listenTo(this.axis, 'change:format', this.updateTicks, this);
this.listenTo(this.$scope, '$destroy', this.stopListening, this);
this.updateTicks();
}
MCTTicksController.$inject = ['$scope', '$element'];
eventHelpers.extend(MCTTicksController.prototype);
/**
* Determine whether ticks should be regenerated for a given range.
* Ticks are updated a) if they don't exist, b) if the existing ticks are
* outside of given range, or c) if the range exceeds the size of the tick
* range by more than one tick step.
* @private
*/
MCTTicksController.prototype.shouldRegenerateTicks = function (range) {
if (!this.tickRange || !this.$scope.ticks || !this.$scope.ticks.length) {
return true;
}
if (this.tickRange.max > range.max || this.tickRange.min < range.min) {
return true;
}
if (Math.abs(range.max - this.tickRange.max) > this.tickRange.step) {
return true;
}
if (Math.abs(this.tickRange.min - range.min) > this.tickRange.step) {
return true;
}
return false;
};
MCTTicksController.prototype.getTicks = function () {
var number = this.tickCount;
var clampRange = this.axis.get('values');
var range = this.axis.get('displayRange');
if (clampRange) {
return clampRange.filter(function (value) {
return value <= range.max && value >= range.min;
}, this);
}
return ticks(range.min, range.max, number);
};
MCTTicksController.prototype.updateTicks = function () {
var range = this.axis.get('displayRange');
if (!range) {
delete this.$scope.min;
delete this.$scope.max;
delete this.$scope.interval;
delete this.tickRange;
delete this.$scope.ticks;
delete this.shouldCheckWidth;
return;
}
var format = this.axis.get('format');
if (!format) {
return;
}
this.$scope.min = range.min;
this.$scope.max = range.max;
this.$scope.interval = Math.abs(range.min - range.max);
if (this.shouldRegenerateTicks(range)) {
var newTicks = this.getTicks();
this.tickRange = {
min: Math.min.apply(Math, newTicks),
max: Math.max.apply(Math, newTicks),
step: newTicks[1] - newTicks[0]
};
newTicks = newTicks
.map(function (tickValue) {
return {
value: tickValue,
text: format(tickValue)
};
}, this);
if (newTicks.length && typeof newTicks[0].text === 'string') {
var tickText = newTicks.map(function (t) {
return t.text;
});
var prefix = tickText.reduce(commonPrefix);
var suffix = tickText.reduce(commonSuffix);
newTicks.forEach(function (t, i) {
t.fullText = t.text;
if (suffix.length) {
t.text = t.text.slice(prefix.length, -suffix.length);
} else {
t.text = t.text.slice(prefix.length);
}
});
}
this.$scope.ticks = newTicks;
this.shouldCheckWidth = true;
}
this.scheduleTickUpdate();
};
MCTTicksController.prototype.scheduleTickUpdate = function () {
if (this.tickUpdate) {
return;
}
this.tickUpdate = true;
setTimeout(this.doTickUpdate.bind(this), 0);
};
MCTTicksController.prototype.doTickUpdate = function () {
if (this.shouldCheckWidth) {
this.$scope.$digest();
var element = this.$element[0],
tickElements = element.querySelectorAll('.gl-plot-tick > span'),
tickWidth = Number([].reduce.call(tickElements, function (memo, first) {
return Math.max(memo, first.offsetWidth);
}, 0));
this.$scope.tickWidth = tickWidth;
this.$scope.$emit('plot:tickWidth', tickWidth);
this.shouldCheckWidth = false;
}
this.$scope.$digest();
this.tickUpdate = false;
};
return MCTTicksController;
});

View File

@ -0,0 +1,43 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./MCTTicksController'
], function (
MCTTicksController
) {
function MCTTicksDirective() {
return {
priority: 1000,
restrict: "E",
scope: true,
controllerAs: 'ticksController',
controller: MCTTicksController,
bindToController: {
axis: '='
}
};
}
return MCTTicksDirective;
});

View File

@ -0,0 +1,172 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/**
* Module defining ExportImageService. Created by hudsonfoo on 09/02/16
*/
define(
[
"html2canvas",
"saveAs"
],
function (
html2canvas,
saveAs
) {
/**
* The export image service will export any HTML node to
* JPG, or PNG.
* @param {object} $q
* @param {object} $timeout
* @param {object} $log
* @param {constant} EXPORT_IMAGE_TIMEOUT time in milliseconds before a timeout error is returned
* @constructor
*/
function ExportImageService($q, $timeout, $log) {
this.$q = $q;
this.$timeout = $timeout;
this.$log = $log;
this.EXPORT_IMAGE_TIMEOUT = 1000;
}
function changeBackgroundColor(element, color) {
element.style.backgroundColor = color;
}
/**
* Renders an HTML element into a base64 encoded image
* as a BLOB, PNG, or JPG.
* @param {node} element that will be converted to an image
* @param {string} type of image to convert the element to
* @returns {promise}
*/
ExportImageService.prototype.renderElement = function (element, type, color) {
var defer = this.$q.defer(),
validTypes = ["png", "jpg", "jpeg"],
renderTimeout,
originalColor;
if (validTypes.indexOf(type) === -1) {
this.$log.error("Invalid type requested. Try: (" + validTypes.join(",") + ")");
return;
}
if (color) {
// Save color to be restored later
originalColor = element.style.backgroundColor || '';
// Defaulting to white so we can see the chart when printed
changeBackgroundColor(element, color);
}
renderTimeout = this.$timeout(function () {
defer.reject("html2canvas timed out");
this.$log.warn("html2canvas timed out");
}.bind(this), this.EXPORT_IMAGE_TIMEOUT);
try {
html2canvas(element, {
onrendered: function (canvas) {
if (color) {
changeBackgroundColor(element, originalColor);
}
switch (type.toLowerCase()) {
case "png":
canvas.toBlob(defer.resolve, "image/png");
break;
default:
case "jpg":
case "jpeg":
canvas.toBlob(defer.resolve, "image/jpeg");
break;
}
}
});
} catch (e) {
defer.reject(e);
this.$log.warn("html2canvas failed with error: " + e);
}
defer.promise.finally(function () {
renderTimeout.cancel();
if (color) {
changeBackgroundColor(element, originalColor);
}
});
return defer.promise;
};
/**
* Takes a screenshot of a DOM node and exports to JPG.
* @param {node} element to be exported
* @param {string} filename the exported image
* @returns {promise}
*/
ExportImageService.prototype.exportJPG = function (element, filename, color) {
return this.renderElement(element, "jpeg", color).then(function (img) {
saveAs(img, filename);
});
};
/**
* Takes a screenshot of a DOM node and exports to PNG.
* @param {node} element to be exported
* @param {string} filename the exported image
* @returns {promise}
*/
ExportImageService.prototype.exportPNG = function (element, filename, color) {
return this.renderElement(element, "png", color).then(function (img) {
saveAs(img, filename);
});
};
/**
* canvas.toBlob() not supported in IE < 10, Opera, and Safari. This polyfill
* implements the method in browsers that would not otherwise support it.
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
*/
function polyfillToBlob() {
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, "toBlob", {
value: function (callback, type, quality) {
var binStr = atob(this.toDataURL(type, quality).split(',')[1]),
len = binStr.length,
arr = new Uint8Array(len);
for (var i = 0; i < len; i++) {
arr[i] = binStr.charCodeAt(i);
}
callback(new Blob([arr], {type: type || "image/png"}));
}
});
}
}
polyfillToBlob();
return ExportImageService;
}
);

View File

@ -0,0 +1,37 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'text!../../res/templates/plot.html'
], function (
PlotTemplate
) {
return function MCTOverlayPlot() {
return {
restrict: "E",
template: PlotTemplate,
scope: {
domainObject: "="
}
};
};
});

View File

@ -0,0 +1,239 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*jscs:disable disallowDanglingUnderscores */
define([
'lodash',
'../configuration/PlotConfigurationModel',
'../configuration/configStore',
'../lib/eventHelpers'
], function (
_,
PlotConfigurationModel,
configStore,
eventHelpers
) {
/**
TODO: Need to separate off plot configuration and specifying of defaults,
is part of onDomainObjectChange as it can be triggered by mutation.
*/
/**
* Controller for a plot.
*
* @constructor.
*/
function PlotController(
$scope,
$element,
formatService,
openmct,
objectService,
exportImageService
) {
this.$scope = $scope;
this.$element = $element;
this.formatService = formatService;
this.openmct = openmct;
this.objectService = objectService;
this.exportImageService = exportImageService;
$scope.pending = 0;
this.listenTo($scope, 'user:viewport:change:end', this.onUserViewportChangeEnd, this);
this.listenTo($scope, '$destroy', this.destroy, this);
this.config = this.getConfig(this.$scope.domainObject);
this.listenTo(this.config.series, 'add', this.addSeries, this);
this.listenTo(this.config.series, 'remove', this.removeSeries, this);
this.config.series.forEach(this.addSeries, this);
this.followTimeConductor();
}
eventHelpers.extend(PlotController.prototype);
PlotController.prototype.followTimeConductor = function () {
this.listenTo(this.openmct.time, 'bounds', this.updateDisplayBounds, this);
this.listenTo(this.openmct.time, 'timeSystem', this.onTimeSystemChange, this);
this.synchronized(true);
};
PlotController.prototype.loadSeriesData = function (series) {
this.startLoading();
var options = {
size: this.$element[0].offsetWidth,
domain: this.config.xAxis.get('key')
};
series.load(options)
.then(this.stopLoading.bind(this));
};
PlotController.prototype.addSeries = function (series) {
this.listenTo(series, 'change:yKey', function () {
this.loadSeriesData(series);
}, this);
this.loadSeriesData(series);
};
PlotController.prototype.removeSeries = function (plotSeries) {
this.stopListening(plotSeries);
};
PlotController.prototype.getConfig = function (domainObject) {
var configId = domainObject.getId();
var config = configStore.get(configId);
if (!config) {
var newDomainObject = domainObject.useCapability('adapter');
config = new PlotConfigurationModel({
id: configId,
domainObject: newDomainObject,
openmct: this.openmct
});
configStore.add(configId, config);
}
configStore.track(configId);
return config;
};
PlotController.prototype.onTimeSystemChange = function (timeSystem) {
this.config.xAxis.set('key', timeSystem.key);
};
PlotController.prototype.destroy = function () {
configStore.untrack(this.config.id);
this.stopListening();
};
PlotController.prototype.loadMoreData = function (range, purge) {
this.config.series.map(function (plotSeries) {
this.startLoading();
plotSeries.load({
size: this.$element[0].offsetWidth,
start: range.min,
end: range.max
})
.then(this.stopLoading.bind(this));
if (purge) {
plotSeries.purgeRecordsOutsideRange(range);
}
}, this);
};
/**
* Track latest display bounds. Forces update when not receiving ticks.
*/
PlotController.prototype.updateDisplayBounds = function (bounds, isTick) {
var newRange = {
min: bounds.start,
max: bounds.end
};
this.config.xAxis.set('range', newRange);
if (!isTick) {
this.$scope.$broadcast('plot:clearHistory');
this.loadMoreData(newRange, true);
} else {
// Drop any data that is more than 1x (max-min) before min.
// Limit these purges to once a second.
if (!this.nextPurge || this.nextPurge < Date.now()) {
var keepRange = {
min: newRange.min - (newRange.max - newRange.min),
max: newRange.max
};
this.config.series.forEach(function (series) {
series.purgeRecordsOutsideRange(keepRange);
});
this.nextPurge = Date.now() + 1000;
}
}
};
PlotController.prototype.startLoading = function () {
this.$scope.pending += 1;
};
PlotController.prototype.stopLoading = function () {
this.$scope.pending -= 1;
};
/**
* Getter/setter for "synchronized" value. If not synchronized and
* time conductor is in clock mode, will mark objects as unsynced so that
* displays can update accordingly.
* @private
*/
PlotController.prototype.synchronized = function (value) {
if (typeof value !== 'undefined') {
this._synchronized = value;
var isUnsynced = !value && this.openmct.time.clock();
if (this.$scope.domainObject.getCapability('status')) {
this.$scope.domainObject.getCapability('status')
.set('timeconductor-unsynced', isUnsynced);
}
}
return this._synchronized;
};
/**
* Handle end of user viewport change: load more data for current display
* bounds, and mark view as synchronized if bounds match configured bounds.
* @private
*/
PlotController.prototype.onUserViewportChangeEnd = function () {
var xDisplayRange = this.config.xAxis.get('displayRange');
var xRange = this.config.xAxis.get('range');
this.loadMoreData(xDisplayRange);
this.synchronized(xRange.min === xDisplayRange.min &&
xRange.max === xDisplayRange.max);
};
/**
* Export view as JPG.
*/
PlotController.prototype.exportJPG = function () {
this.hideExportButtons = true;
this.exportImageService.exportJPG(this.$element[0], 'plot.jpg', 'white')
.finally(function () {
this.hideExportButtons = false;
}.bind(this));
};
/**
* Export view as PNG.
*/
PlotController.prototype.exportPNG = function () {
this.hideExportButtons = true;
this.exportImageService.exportPNG(this.$element[0], 'plot.png', 'white')
.finally(function () {
this.hideExportButtons = false;
}.bind(this));
};
return PlotController;
});

View File

@ -0,0 +1,146 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'lodash'
], function (
_
) {
function StackedPlotController($scope, openmct, objectService, $element, exportImageService) {
var tickWidth = 0,
newFormatObject,
composition,
currentRequest,
unlisten,
tickWidthMap = {};
this.$element = $element;
this.exportImageService = exportImageService;
$scope.telemetryObjects = [];
function oldId(newIdentifier) {
var idParts = [];
if (newIdentifier.namespace) {
idParts.push(newIdentifier.namespace.replace(/\:/g, '\\:'));
}
idParts.push(newIdentifier.key);
return idParts.join(':');
}
function onDomainObjectChange(domainObject) {
var thisRequest = {
pending: 0
};
currentRequest = thisRequest;
$scope.currentRequest = thisRequest;
var telemetryObjects = $scope.telemetryObjects = [];
var thisTickWidthMap = {};
tickWidthMap = thisTickWidthMap;
if (unlisten) {
unlisten();
unlisten = undefined;
}
function addChild(child) {
var id = oldId(child.identifier);
thisTickWidthMap[id] = 0;
thisRequest.pending += 1;
objectService.getObjects([id])
.then(function (objects) {
thisRequest.pending -= 1;
var childObj = objects[id];
telemetryObjects.push(childObj);
});
}
function removeChild(childIdentifier) {
var id = oldId(childIdentifier);
delete thisTickWidthMap[id];
var childObj = telemetryObjects.filter(function (c) {
return c.getId() === id;
})[0];
if (childObj) {
var index = telemetryObjects.indexOf(childObj);
telemetryObjects.splice(index, 1);
$scope.$broadcast('plot:tickWidth', _.max(tickWidthMap));
}
}
thisRequest.pending += 1;
openmct.objects.get(domainObject.getId())
.then(function (obj) {
thisRequest.pending -= 1;
if (thisRequest !== currentRequest) {
return;
}
newFormatObject = obj;
composition = openmct.composition.get(obj);
composition.on('add', addChild);
composition.on('remove', removeChild);
composition.load();
unlisten = function () {
composition.off('add', addChild);
composition.off('remove', removeChild);
};
});
}
$scope.$watch('domainObject', onDomainObjectChange);
$scope.$on('plot:tickWidth', function ($e, width) {
var plotId = $e.targetScope.domainObject.getId();
if (!tickWidthMap.hasOwnProperty(plotId)) {
return;
}
tickWidthMap[plotId] = Math.max(width, tickWidthMap[plotId]);
var newTickWidth = _.max(tickWidthMap);
if (newTickWidth !== tickWidth || width !== tickWidth) {
tickWidth = newTickWidth;
$scope.$broadcast('plot:tickWidth', tickWidth);
}
});
$scope.$on('plot:highlight:update', function ($e, point) {
$scope.$broadcast('plot:highlight:set', point);
});
}
StackedPlotController.prototype.exportJPG = function () {
this.hideExportButtons = true;
this.exportImageService.exportJPG(this.$element[0], 'stacked-plot.jpg')
.finally(function () {
this.hideExportButtons = false;
}.bind(this));
};
StackedPlotController.prototype.exportPNG = function () {
this.hideExportButtons = true;
this.exportImageService.exportPNG(this.$element[0], 'stacked-plot.png')
.finally(function () {
this.hideExportButtons = false;
}.bind(this));
};
return StackedPlotController;
});