Compare commits
	
		
			20 Commits
		
	
	
		
			vista-4.2-
			...
			plot-vee-t
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					681ffef1d3 | ||
| 
						 | 
					e15f58c1c6 | ||
| 
						 | 
					785226cc5a | ||
| 
						 | 
					e597c47171 | ||
| 
						 | 
					fb7b46fcaa | ||
| 
						 | 
					0435e1b667 | ||
| 
						 | 
					0de4192345 | ||
| 
						 | 
					3bdbf2aa56 | ||
| 
						 | 
					baee0870d3 | ||
| 
						 | 
					b4d0786369 | ||
| 
						 | 
					eb69e02ce3 | ||
| 
						 | 
					056b3f61ce | ||
| 
						 | 
					a0dc3da8fb | ||
| 
						 | 
					48f345a46b | ||
| 
						 | 
					889a5c6ea9 | ||
| 
						 | 
					5502009127 | ||
| 
						 | 
					cb41be7922 | ||
| 
						 | 
					52b8720d37 | ||
| 
						 | 
					bb8c8a75ab | ||
| 
						 | 
					b40494ac95 | 
@@ -11,7 +11,7 @@
 | 
			
		||||
    "platform/telemetry",
 | 
			
		||||
    "platform/features/layout",
 | 
			
		||||
    "platform/features/pages",
 | 
			
		||||
    "platform/features/plot",
 | 
			
		||||
    "platform/features/plot-reborn",
 | 
			
		||||
    "platform/features/scrolling",
 | 
			
		||||
    "platform/forms",
 | 
			
		||||
    "platform/persistence/queue",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										57
									
								
								platform/features/plot-reborn/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								platform/features/plot-reborn/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
# plot-reborn
 | 
			
		||||
 | 
			
		||||
The `plot-reborn` bundle provides directives for composing plot based views.
 | 
			
		||||
It also exposes domain objects for plotting telemetry points.
 | 
			
		||||
 | 
			
		||||
## Views
 | 
			
		||||
 | 
			
		||||
* OverlayPlot: can be used on any domain object that has or delegates a 
 | 
			
		||||
    telemetry capability.
 | 
			
		||||
 | 
			
		||||
* StackedPlot: can be used on any domain object that delegates telemetry or 
 | 
			
		||||
    delegates composition of elements that have telemetry.
 | 
			
		||||
 | 
			
		||||
## Directives
 | 
			
		||||
 | 
			
		||||
* `mct-chart`: an element that takes `series`, `viewport`, and
 | 
			
		||||
    `rectangles` and plots the data.  Adding points to a series after it has 
 | 
			
		||||
    been initially plotted can be done either by recreating the series object
 | 
			
		||||
    or by broadcasting "series:data:add" with arguments `event`, `seriesIndex`, 
 | 
			
		||||
    `points`.  This will append `points` to the `series` at index `seriesIndex`.
 | 
			
		||||
 | 
			
		||||
* `mct-plot`: A directive that wraps a mct-chart and handles user interactions
 | 
			
		||||
    with that plot.  It emits events that a parent view can use for coordinating
 | 
			
		||||
    functionality:
 | 
			
		||||
    * emits a `user:viewport:change:start` event when the viewport begins being
 | 
			
		||||
        changed by a user, to allow any parent controller to prevent viewport 
 | 
			
		||||
        modifications while the user is interacting with the plot.
 | 
			
		||||
    * emits a `user:viewport:change:end` event when the user has finished
 | 
			
		||||
        changing the viewport.  This allows a controller on a parent scope to
 | 
			
		||||
        track viewport history and provide any necessary functionality
 | 
			
		||||
        around viewport changes, e.g. viewport history.
 | 
			
		||||
 | 
			
		||||
* `mct-overlay-plot`: A directive that takes `domainObject` and plots either a
 | 
			
		||||
    single series of data (in the case of a single telemetry object) or multiple
 | 
			
		||||
    series of data (in the case of a object which delegates telemetry).
 | 
			
		||||
 | 
			
		||||
## Controllers
 | 
			
		||||
 | 
			
		||||
NOTE: this section not accurate.  Essentially, these controllers format data for
 | 
			
		||||
the mct-chart directive.  They also handle live viewport updating, as well as
 | 
			
		||||
managing all transformations from domain objects to views.
 | 
			
		||||
 | 
			
		||||
* StackPlotController: Uses the composition capability of a StackPlot domain 
 | 
			
		||||
    object to retrieve SubPlots and render them with individual PlotControllers.
 | 
			
		||||
* PlotController: Uses either a domain object that delegates telemetry or a 
 | 
			
		||||
    domain object with telemetry to and feeds that data to the mct-chart 
 | 
			
		||||
    directive.
 | 
			
		||||
 | 
			
		||||
## TODOS:
 | 
			
		||||
 | 
			
		||||
* [ ] Re-implement history stack.
 | 
			
		||||
* [ ] Re-implement plot pallette.
 | 
			
		||||
* [ ] Re-implement stacked plot viewport synchronization (share viewport object)
 | 
			
		||||
* [ ] Other things?
 | 
			
		||||
* [ ] Handle edge cases with marquee zoom/panning.
 | 
			
		||||
* [ ] Tidy code.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										98
									
								
								platform/features/plot-reborn/bundle.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								platform/features/plot-reborn/bundle.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "Plot view for telemetry, reborn",
 | 
			
		||||
    "extensions": {
 | 
			
		||||
        "views": [
 | 
			
		||||
            {
 | 
			
		||||
                "name": "Plot",
 | 
			
		||||
                "key": "plot-single",
 | 
			
		||||
                "glyph": "6",
 | 
			
		||||
                "templateUrl": "templates/plot.html",
 | 
			
		||||
                "needs": ["telemetry"],
 | 
			
		||||
                "uses": ["composition"],
 | 
			
		||||
                "delegation": false
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "name": "Overlay Plot",
 | 
			
		||||
                "key": "plot",
 | 
			
		||||
                "glyph": "6",
 | 
			
		||||
                "type": "telemetry.plot.overlay",
 | 
			
		||||
                "templateUrl": "templates/plot.html",
 | 
			
		||||
                "needs": ["telemetry", "composition"],
 | 
			
		||||
                "uses": ["composition"],
 | 
			
		||||
                "delegation": true
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "name": "Stacked Plot",
 | 
			
		||||
                "key": "stackedPlot",
 | 
			
		||||
                "glyph": "6",
 | 
			
		||||
                "type": "telemetry.plot.stacked",
 | 
			
		||||
                "templateUrl": "templates/stacked-plot.html",
 | 
			
		||||
                "needs": ["composition", "delegation"],
 | 
			
		||||
                "uses": ["composition"],
 | 
			
		||||
                "gestures": [ "drop" ],
 | 
			
		||||
                "delegation": true
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "directives": [
 | 
			
		||||
            {
 | 
			
		||||
                "key": "mctChart",
 | 
			
		||||
                "implementation": "directives/MCTChart.js",
 | 
			
		||||
                "depends": [ "$interval", "$log" ]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "key": "mctPlot",
 | 
			
		||||
                "implementation": "directives/MCTPlot.js",
 | 
			
		||||
                "depends": [],
 | 
			
		||||
                "templateUrl": "templates/mct-plot.html"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "key": "mctOverlayPlot",
 | 
			
		||||
                "implementation": "directives/MCTOverlayPlot.js",
 | 
			
		||||
                "depends": []
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "controllers": [
 | 
			
		||||
            {
 | 
			
		||||
                "key": "PlotController",
 | 
			
		||||
                "implementation": "controllers/PlotController.js",
 | 
			
		||||
                "depends": [ "$scope", "$q", "colorService"]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "key": "StackedPlotController",
 | 
			
		||||
                "implementation": "controllers/StackedPlotController.js",
 | 
			
		||||
                "depends": [ "$scope" ]
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "types": [
 | 
			
		||||
            {
 | 
			
		||||
                "key": "telemetry.plot.overlay",
 | 
			
		||||
                "name": "Overlay Plot",
 | 
			
		||||
                "glyph": "t",
 | 
			
		||||
                "description": "A plot containing one or more telemetry elements.",
 | 
			
		||||
                "delegates": ["telemetry"],
 | 
			
		||||
                "features": "creation",
 | 
			
		||||
                "contains": [{"has": "telemetry"}],
 | 
			
		||||
                "model": {"composition": []},
 | 
			
		||||
                "properties": []
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "key": "telemetry.plot.stacked",
 | 
			
		||||
                "name": "Stacked Plot",
 | 
			
		||||
                "glyph": "t",
 | 
			
		||||
                "description": "A stacked plot of overlay plots.",
 | 
			
		||||
                "delegates": ["delegation"],
 | 
			
		||||
                "features": "creation",
 | 
			
		||||
                "contains": ["telemetry.plot.overlay", {"has": "telemetry"}],
 | 
			
		||||
                "model": {"composition": []},
 | 
			
		||||
                "properties": []
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "services": [
 | 
			
		||||
            {
 | 
			
		||||
                "key": "colorService",
 | 
			
		||||
                "implementation": "services/ColorService.js",
 | 
			
		||||
                "description": "Provides objects for working with colors."
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										76
									
								
								platform/features/plot-reborn/res/templates/mct-plot.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								platform/features/plot-reborn/res/templates/mct-plot.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
<!--TODO: Don't require plotcontroller here. -->
 | 
			
		||||
<div class="gl-plot">
 | 
			
		||||
    <div class="gl-plot-legend">
 | 
			
		||||
        <span class="plot-legend-item"
 | 
			
		||||
              ng-repeat="series in series track by $index">
 | 
			
		||||
            <span class="plot-color-swatch"
 | 
			
		||||
                  ng-style="{ 'background-color': series.color.asHexString() }">
 | 
			
		||||
            </span>
 | 
			
		||||
            <span class="title-label">{{ series.name }}</span>
 | 
			
		||||
        </span>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="gl-plot-coords"
 | 
			
		||||
         ng-if="mouseCoordinates">
 | 
			
		||||
        {{ displayableDomain(mouseCoordinates.positionAsPlotPoint.domain)  }},
 | 
			
		||||
        {{ displayableRange(mouseCoordinates.positionAsPlotPoint.range) }}
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="gl-plot-axis-area gl-plot-y">
 | 
			
		||||
 | 
			
		||||
        <div class="gl-plot-label gl-plot-y-label">
 | 
			
		||||
            {{ axes.range.label}}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div ng-repeat="tick in axes.range.ticks track by $index"
 | 
			
		||||
             class="gl-plot-tick gl-plot-y-tick-label"
 | 
			
		||||
             ng-style="{ top: (100 * $index / (axes.range.ticks.length - 1)) + '%' }"
 | 
			
		||||
             style="margin-top: -0.50em;">
 | 
			
		||||
            {{ displayableRange(tick) }}
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="gl-plot-display-area">
 | 
			
		||||
 | 
			
		||||
        <div class="gl-plot-hash hash-v"
 | 
			
		||||
             ng-repeat="tick in axes.domain.ticks track by $index"
 | 
			
		||||
             ng-style="{ left: (100 * $index / (axes.domain.ticks.length - 1)) + '%', height: '100%' }"
 | 
			
		||||
             ng-show="$index > 0 && $index < (axes.domain.ticks.length - 1)">
 | 
			
		||||
            <!--TODO: Show/hide using CSS? -->
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="gl-plot-hash hash-h"
 | 
			
		||||
             ng-repeat="tick in axes.range.ticks track by $index"
 | 
			
		||||
             ng-style="{ bottom: (100 * $index / (axes.range.ticks.length - 1)) + '%', width: '100%' }"
 | 
			
		||||
             ng-show="$index > 0 && $index < (axes.range.ticks.length - 1)">
 | 
			
		||||
             <!--TODO: Show/hide using CSS? -->
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <mct-chart series="series"
 | 
			
		||||
                   viewport="viewport"
 | 
			
		||||
                   rectangles="rectangles"
 | 
			
		||||
                   ng-mousemove="plot.trackMousePosition($event)"
 | 
			
		||||
                   ng-mouseleave="plot.untrackMousePosition()"
 | 
			
		||||
                   ng-mousedown="plot.startMarquee()"
 | 
			
		||||
                   ng-mouseup="plot.endMarquee()">
 | 
			
		||||
        </mct-chart>
 | 
			
		||||
 | 
			
		||||
        <span class="t-wait-spinner loading" ng-show="plot.isRequestPending()">
 | 
			
		||||
        </span>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="gl-plot-axis-area gl-plot-x">
 | 
			
		||||
        <div ng-repeat="tick in axes.domain.ticks track by $index"
 | 
			
		||||
             class="gl-plot-tick gl-plot-x-tick-label"
 | 
			
		||||
             ng-show="$index > 0 && $index < (axes.domain.ticks.length - 1)"
 | 
			
		||||
             ng-style="{ left: (100 * $index / (axes.domain.ticks.length - 1)) + '%' }">
 | 
			
		||||
            {{ displayableDomain(tick) }}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="gl-plot-label gl-plot-x-label">
 | 
			
		||||
            {{ axes.domain.label }}
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										9
									
								
								platform/features/plot-reborn/res/templates/plot.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								platform/features/plot-reborn/res/templates/plot.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
<span ng-controller="PlotController as controller">
 | 
			
		||||
    <mct-plot series="series"
 | 
			
		||||
              viewport="viewport"
 | 
			
		||||
              rectangles="rectangles"
 | 
			
		||||
              axes="axes"
 | 
			
		||||
              displayable-range="displayableRange"
 | 
			
		||||
              displayable-domain="displayableDomain">
 | 
			
		||||
              </mct-plot>
 | 
			
		||||
</span>
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
<span ng-controller="StackedPlotController as stackedPlot">
 | 
			
		||||
    <div class="gl-plot"
 | 
			
		||||
        ng-style="{ height: 100 / telemetryObjects.length + '%'}"
 | 
			
		||||
        ng-repeat="telemetryObject in telemetryObjects">
 | 
			
		||||
 | 
			
		||||
        <mct-overlay-plot domain-object="telemetryObject"></mct-overlay-plot>
 | 
			
		||||
    </div>
 | 
			
		||||
</span>
 | 
			
		||||
							
								
								
									
										248
									
								
								platform/features/plot-reborn/src/controllers/PlotController.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								platform/features/plot-reborn/src/controllers/PlotController.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,248 @@
 | 
			
		||||
/*global define,requestAnimationFrame*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    [],
 | 
			
		||||
    function () {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        // TODO: Store this in more accessible locations / retrieve from
 | 
			
		||||
        // domainObject metadata.
 | 
			
		||||
        var DOMAIN_INTERVAL = 2 * 60 * 1000; // Two minutes.
 | 
			
		||||
 | 
			
		||||
        function PlotController($scope, $q, colorService) {
 | 
			
		||||
            var plotHistory = [],
 | 
			
		||||
                isLive = true,
 | 
			
		||||
                extrema = {},
 | 
			
		||||
                unsubscribes = [],
 | 
			
		||||
                palette = new colorService.ColorPalette(),
 | 
			
		||||
                pendingUpdate,
 | 
			
		||||
                selectedDomain;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            function setToDefaultViewport() {
 | 
			
		||||
                // TODO: We shouldn't set the viewport until we have received data or something has given us a reasonable viewport.
 | 
			
		||||
                if (!extrema.domain || !extrema.range) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                $scope.viewport = {
 | 
			
		||||
                    topLeft: {
 | 
			
		||||
                        domain: extrema.domain.min,
 | 
			
		||||
                        range: extrema.range.max
 | 
			
		||||
                    },
 | 
			
		||||
                    bottomRight: {
 | 
			
		||||
                        domain: extrema.domain.max,
 | 
			
		||||
                        range: extrema.range.min
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            setToDefaultViewport();
 | 
			
		||||
 | 
			
		||||
            $scope.displayableRange = function (rangeValue) {
 | 
			
		||||
                // TODO: Call format function provided by domain object.
 | 
			
		||||
                return rangeValue;
 | 
			
		||||
            };
 | 
			
		||||
            $scope.displayableDomain = function (domainValue) {
 | 
			
		||||
                // TODO: Call format function provided by domain object.
 | 
			
		||||
                return new Date(domainValue).toUTCString();
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            $scope.series = [];
 | 
			
		||||
 | 
			
		||||
            $scope.rectangles = [];
 | 
			
		||||
 | 
			
		||||
            function addPointToSeries(series, seriesIndex, point) {
 | 
			
		||||
                if (!extrema.domain) {
 | 
			
		||||
                    extrema.domain = {};
 | 
			
		||||
                    extrema.domain.max = extrema.domain.min = point.domain;
 | 
			
		||||
                } else {
 | 
			
		||||
                    extrema.domain.max = Math.max(
 | 
			
		||||
                        extrema.domain.max, point.domain
 | 
			
		||||
                    );
 | 
			
		||||
                    extrema.domain.min = Math.min(
 | 
			
		||||
                        extrema.domain.min, point.domain
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                if (!extrema.range) {
 | 
			
		||||
                    extrema.range = {};
 | 
			
		||||
                    extrema.range.max = extrema.range.min = point.range;
 | 
			
		||||
                } else {
 | 
			
		||||
                    extrema.range.max = Math.max(
 | 
			
		||||
                        extrema.range.max, point.range
 | 
			
		||||
                    );
 | 
			
		||||
                    extrema.range.min = Math.min(
 | 
			
		||||
                        extrema.range.min, point.range
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                series.data.push(point);
 | 
			
		||||
                $scope.$broadcast('series:data:add', seriesIndex, [point]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function addTelemetrySeriesToPlotSeries(series, seriesIndex) {
 | 
			
		||||
                return function (telemSeries) {
 | 
			
		||||
                    var i = 0,
 | 
			
		||||
                        len = telemSeries.getPointCount();
 | 
			
		||||
 | 
			
		||||
                    for (; i < len; i++) {
 | 
			
		||||
                        addPointToSeries(series, seriesIndex, {
 | 
			
		||||
                            domain: telemSeries.getDomainValue(i, selectedDomain),
 | 
			
		||||
                            range: telemSeries.getRangeValue(i)
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                    return;
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function startRealTimeFeed(series, seriesIndex, telemetryCapability) {
 | 
			
		||||
                return function () {
 | 
			
		||||
                    var updater = addTelemetrySeriesToPlotSeries(
 | 
			
		||||
                        series,
 | 
			
		||||
                        seriesIndex
 | 
			
		||||
                    );
 | 
			
		||||
                    unsubscribes.push(telemetryCapability.subscribe(updater));
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function subscribeToDomainObject(domainObject) {
 | 
			
		||||
                var telemetryCapability = domainObject.getCapability('telemetry'),
 | 
			
		||||
                    model = domainObject.getModel(),
 | 
			
		||||
                    series,
 | 
			
		||||
                    seriesIndex;
 | 
			
		||||
 | 
			
		||||
                series = {
 | 
			
		||||
                    name: model.name,
 | 
			
		||||
                    // TODO: Bring back PlotPalette.
 | 
			
		||||
                    color: palette.getColor($scope.series.length),
 | 
			
		||||
                    data: []
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                $scope.series.push(series);
 | 
			
		||||
                seriesIndex = $scope.series.indexOf(series);
 | 
			
		||||
 | 
			
		||||
                return telemetryCapability
 | 
			
		||||
                    .requestData({})
 | 
			
		||||
                    .then(addTelemetrySeriesToPlotSeries(
 | 
			
		||||
                        series,
 | 
			
		||||
                        seriesIndex
 | 
			
		||||
                    ))
 | 
			
		||||
                    .then(startRealTimeFeed(
 | 
			
		||||
                        series,
 | 
			
		||||
                        seriesIndex,
 | 
			
		||||
                        telemetryCapability
 | 
			
		||||
                    ));
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function unlinkDomainObject() {
 | 
			
		||||
                $scope.series = [];
 | 
			
		||||
                extrema = {};
 | 
			
		||||
                unsubscribes.forEach(function(unsubscribe) {
 | 
			
		||||
                    unsubscribe();
 | 
			
		||||
                });
 | 
			
		||||
                unsubscribes = [];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            function linkDomainObject() {
 | 
			
		||||
                var domainObject = $scope.domainObject;
 | 
			
		||||
                unlinkDomainObject();
 | 
			
		||||
                if (!domainObject || !domainObject.hasCapability) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                if (domainObject.hasCapability('telemetry')) {
 | 
			
		||||
                    subscribeToDomainObject(domainObject);
 | 
			
		||||
                } else if (domainObject.hasCapability('delegation')) {
 | 
			
		||||
                    // Makes no sense that we have to use a subscription to get domain objects associated with delegates (and their names).  We can map the same series generation code to telemetry delegates; Let's do that ourselves.
 | 
			
		||||
                    var subscribeToDelegates = function(delegates) {
 | 
			
		||||
                        return $q.all(delegates.map(subscribeToDomainObject));
 | 
			
		||||
                        // TODO: Should return a promise.
 | 
			
		||||
                    };
 | 
			
		||||
                    domainObject
 | 
			
		||||
                        .getCapability('delegation')
 | 
			
		||||
                        .getDelegates('telemetry')
 | 
			
		||||
                        .then(subscribeToDelegates);
 | 
			
		||||
                        // TODO: should have a catch.
 | 
			
		||||
                } else {
 | 
			
		||||
                    throw new Error('Domain object type not supported.');
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            function onUserViewportChangeStart() {
 | 
			
		||||
                // TODO: this is a great time to track a history entry.
 | 
			
		||||
                // Disable live mode so they have full control of viewport.
 | 
			
		||||
                plotHistory.push($scope.viewport);
 | 
			
		||||
                isLive = false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function onUserViewportChangeEnd(event, viewport) {
 | 
			
		||||
                // If the new viewport is "close enough" to the maxDomain then
 | 
			
		||||
                // enable live mode.  Set empirically to 10% of the domain
 | 
			
		||||
                // interval.
 | 
			
		||||
                // TODO: Better UX pattern for this.
 | 
			
		||||
 | 
			
		||||
                // if (Math.abs(maxDomain - viewport.bottomRight.domain) < (DOMAIN_INTERVAL/10)) {
 | 
			
		||||
                //     isLive = true;
 | 
			
		||||
                //     $scope.viewport.bottomRight.domain = maxDomain;
 | 
			
		||||
                // } else {
 | 
			
		||||
                //     isLive = false;
 | 
			
		||||
                // }
 | 
			
		||||
                plotHistory.push(viewport);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
             function viewportForExtrema() {
 | 
			
		||||
                 return {
 | 
			
		||||
                    topLeft: {
 | 
			
		||||
                        domain: extrema.domain.min,
 | 
			
		||||
                        range: extrema.range.max
 | 
			
		||||
                    },
 | 
			
		||||
                    bottomRight: {
 | 
			
		||||
                        domain: extrema.domain.max,
 | 
			
		||||
                        range: extrema.range.min
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function followDataIfLive() {
 | 
			
		||||
                if (pendingUpdate) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                requestAnimationFrame(function () {
 | 
			
		||||
                    if (isLive) {
 | 
			
		||||
                        $scope.viewport = viewportForExtrema();
 | 
			
		||||
                    }
 | 
			
		||||
                    pendingUpdate = false;
 | 
			
		||||
                });
 | 
			
		||||
                pendingUpdate = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $scope.$on('series:data:add', followDataIfLive);
 | 
			
		||||
            $scope.$on('user:viewport:change:end', onUserViewportChangeEnd);
 | 
			
		||||
            $scope.$on('user:viewport:change:start', onUserViewportChangeStart);
 | 
			
		||||
 | 
			
		||||
            $scope.$on(
 | 
			
		||||
                'telemetry:display:bounds',
 | 
			
		||||
                function (event, bounds) {
 | 
			
		||||
                    selectedDomain = bounds.domain;
 | 
			
		||||
                    linkDomainObject();
 | 
			
		||||
                }
 | 
			
		||||
            );
 | 
			
		||||
            $scope.$watch('domainObject', linkDomainObject);
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                historyBack: function() {
 | 
			
		||||
                    // TODO: Step History Back.
 | 
			
		||||
                },
 | 
			
		||||
                historyForward: function() {
 | 
			
		||||
                    // TODO: Step History Forward.
 | 
			
		||||
                },
 | 
			
		||||
                resetZoom: function() {
 | 
			
		||||
                    // TODO: Reset view to defaults.  Keep history stack alive?
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return PlotController;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
/*global define */
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    function () {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        function StackedPlotController($scope) {
 | 
			
		||||
 | 
			
		||||
            $scope.telemetryObjects = [];
 | 
			
		||||
 | 
			
		||||
            var linkDomainObject = function(domainObject) {
 | 
			
		||||
                $scope.telemetryObjects = [];
 | 
			
		||||
                if (domainObject.hasCapability('telemetry')) {
 | 
			
		||||
                    $scope.telemetryObjects = [domainObject];
 | 
			
		||||
                } else if (domainObject.hasCapability('delegation')) {
 | 
			
		||||
 | 
			
		||||
                    var addObjectsIfCompatible = function(objects) {
 | 
			
		||||
                        objects.forEach(function(object) {
 | 
			
		||||
                            if (object.hasCapability('telemetry')) {
 | 
			
		||||
                                $scope.telemetryObjects.push(object);
 | 
			
		||||
                            } else if (object.hasCapability('delegation')) {
 | 
			
		||||
                                $scope.telemetryObjects.push(object);
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                    };
 | 
			
		||||
                    domainObject
 | 
			
		||||
                        .useCapability('composition')
 | 
			
		||||
                        .then(addObjectsIfCompatible);
 | 
			
		||||
                        // TODO: should have a catch.
 | 
			
		||||
                } else {
 | 
			
		||||
                    throw new Error('Domain object type not supported.');
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            $scope.$watch('domainObject', linkDomainObject);
 | 
			
		||||
        }
 | 
			
		||||
        return StackedPlotController;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										250
									
								
								platform/features/plot-reborn/src/directives/MCTChart.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								platform/features/plot-reborn/src/directives/MCTChart.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,250 @@
 | 
			
		||||
/*global define,requestAnimationFrame,Float32Array*/
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Module defining MCTChart. Created by vwoeltje on 11/12/14.
 | 
			
		||||
 */
 | 
			
		||||
define(
 | 
			
		||||
    ["../draw/DrawLoader"],
 | 
			
		||||
    function (DrawLoader) {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        var TEMPLATE = "<canvas style='position: absolute; background: none; width: 100%; height: 100%;'></canvas>";
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Offsetter adjusts domain and range values by a fixed amount,
 | 
			
		||||
         * generally increasing the precision of the 32 bit float representation
 | 
			
		||||
         * required for plotting.
 | 
			
		||||
         *
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function Offsetter(domainOffset, rangeOffset) {
 | 
			
		||||
            this.domainOffset = domainOffset;
 | 
			
		||||
            this.rangeOffset = rangeOffset;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Offsetter.prototype.domain = function(dataDomain) {
 | 
			
		||||
            return dataDomain - this.domainOffset;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Offsetter.prototype.range = function(dataRange) {
 | 
			
		||||
            return dataRange - this.rangeOffset;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * MCTChart draws charts utilizing a drawAPI.
 | 
			
		||||
         *
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function MCTChart($interval) {
 | 
			
		||||
 | 
			
		||||
            function linkChart($scope, $element) {
 | 
			
		||||
                var canvas = $element.find("canvas")[0],
 | 
			
		||||
                    isDestroyed = false,
 | 
			
		||||
                    activeInterval,
 | 
			
		||||
                    drawAPI,
 | 
			
		||||
                    lines = [],
 | 
			
		||||
                    offset;
 | 
			
		||||
 | 
			
		||||
                drawAPI = DrawLoader.getDrawAPI(canvas);
 | 
			
		||||
 | 
			
		||||
                if (!drawAPI) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function ensureOffset() {
 | 
			
		||||
                    if (offset) {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    if ($scope.series &&
 | 
			
		||||
                        $scope.series.length &&
 | 
			
		||||
                        $scope.series[0].data &&
 | 
			
		||||
                        $scope.series[0].data.length) {
 | 
			
		||||
 | 
			
		||||
                        // Take offset from series.
 | 
			
		||||
                        var point = $scope.series[0].data[0];
 | 
			
		||||
                        offset = new Offsetter(
 | 
			
		||||
                            point.domain,
 | 
			
		||||
                            point.range
 | 
			
		||||
                        );
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    // TODO: Fallback and get offset from viewport.
 | 
			
		||||
                    // TODO: Fallback and get offset from rectangles.
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function lineFromSeries(series) {
 | 
			
		||||
                    // TODO: handle when lines get longer than 10,000 points.
 | 
			
		||||
                    // Each line allocates 10,000 points.  This should be more
 | 
			
		||||
                    // that we ever need, but we have to decide how to handle
 | 
			
		||||
                    // this at the higher level.  I imagine the plot controller
 | 
			
		||||
                    // should watch it's series and when they get huge, slice
 | 
			
		||||
                    // them in half and delete the oldest half.
 | 
			
		||||
                    //
 | 
			
		||||
                    // As long as the controller replaces $scope.series with a
 | 
			
		||||
                    // new series object, then this directive will
 | 
			
		||||
                    // automatically generate new arrays for those lines.
 | 
			
		||||
                    // In practice, the overhead of regenerating these lines
 | 
			
		||||
                    // appears minimal.
 | 
			
		||||
                    var lineBuffer = new Float32Array(20000),
 | 
			
		||||
                        i = 0;
 | 
			
		||||
                    ensureOffset();
 | 
			
		||||
                    for (i = 0; i < series.data.length; i++) {
 | 
			
		||||
                        lineBuffer[2*i] = offset.domain(series.data[i].domain);
 | 
			
		||||
                        lineBuffer[2*i+1] = offset.range(series.data[i].range);
 | 
			
		||||
                    }
 | 
			
		||||
                    return {
 | 
			
		||||
                        color: series.color,
 | 
			
		||||
                        buffer: lineBuffer,
 | 
			
		||||
                        pointCount: series.data.length
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function drawSeries() {
 | 
			
		||||
                    if (!lines && !lines.length) {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    lines.forEach(function(line) {
 | 
			
		||||
                        drawAPI.drawLine(
 | 
			
		||||
                            line.buffer,
 | 
			
		||||
                            line.color.asRGBAArray(),
 | 
			
		||||
                            line.pointCount
 | 
			
		||||
                        );
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function drawRectangles() {
 | 
			
		||||
                    if ($scope.rectangles) {
 | 
			
		||||
                        $scope.rectangles.forEach(function(rect) {
 | 
			
		||||
                            drawAPI.drawSquare(
 | 
			
		||||
                                [
 | 
			
		||||
                                    offset.domain(rect.start.domain),
 | 
			
		||||
                                    offset.range(rect.start.range)
 | 
			
		||||
                                ],
 | 
			
		||||
                                [
 | 
			
		||||
                                    offset.domain(rect.end.domain),
 | 
			
		||||
                                    offset.range(rect.end.range)
 | 
			
		||||
                                ],
 | 
			
		||||
                                rect.color
 | 
			
		||||
                            );
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function updateViewport() {
 | 
			
		||||
                    var dimensions,
 | 
			
		||||
                        origin;
 | 
			
		||||
 | 
			
		||||
                    dimensions = [
 | 
			
		||||
                        Math.abs(
 | 
			
		||||
                            offset.domain($scope.viewport.topLeft.domain) -
 | 
			
		||||
                            offset.domain($scope.viewport.bottomRight.domain)
 | 
			
		||||
                        ),
 | 
			
		||||
                        Math.abs(
 | 
			
		||||
                            offset.range($scope.viewport.topLeft.range) -
 | 
			
		||||
                            offset.range($scope.viewport.bottomRight.range)
 | 
			
		||||
                        )
 | 
			
		||||
                    ];
 | 
			
		||||
 | 
			
		||||
                    origin = [
 | 
			
		||||
                        offset.domain(
 | 
			
		||||
                            $scope.viewport.topLeft.domain
 | 
			
		||||
                        ),
 | 
			
		||||
                        offset.range(
 | 
			
		||||
                            $scope.viewport.bottomRight.range
 | 
			
		||||
                        )
 | 
			
		||||
                    ];
 | 
			
		||||
 | 
			
		||||
                    drawAPI.setDimensions(
 | 
			
		||||
                        dimensions,
 | 
			
		||||
                        origin
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function onSeriesDataAdd(event, seriesIndex, points) {
 | 
			
		||||
                    var line = lines[seriesIndex];
 | 
			
		||||
                    if (!line) { return; }
 | 
			
		||||
                    ensureOffset();
 | 
			
		||||
                    points.forEach(function (point) {
 | 
			
		||||
                        line.buffer[2*line.pointCount] = offset.domain(point.domain);
 | 
			
		||||
                        line.buffer[2*line.pointCount+1] = offset.range(point.range);
 | 
			
		||||
                        line.pointCount += 1;
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                function redraw() {
 | 
			
		||||
                    if (isDestroyed) {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    requestAnimationFrame(redraw);
 | 
			
		||||
                    canvas.width = canvas.offsetWidth;
 | 
			
		||||
                    canvas.height = canvas.offsetHeight;
 | 
			
		||||
                    drawAPI.clear();
 | 
			
		||||
                    ensureOffset();
 | 
			
		||||
                    if (!offset) {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    updateViewport();
 | 
			
		||||
                    drawSeries();
 | 
			
		||||
                    drawRectangles();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                function drawIfResized() {
 | 
			
		||||
                    if (canvas.width !== canvas.offsetWidth ||
 | 
			
		||||
                            canvas.height !== canvas.offsetHeight) {
 | 
			
		||||
                        redraw();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function destroyChart() {
 | 
			
		||||
                    isDestroyed = true;
 | 
			
		||||
                    if (activeInterval) {
 | 
			
		||||
                        $interval.cancel(activeInterval);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function recreateLines() {
 | 
			
		||||
                    offset = undefined;
 | 
			
		||||
                    if ($scope.series && $scope.series.length) {
 | 
			
		||||
                        lines = $scope.series.map(lineFromSeries);
 | 
			
		||||
                    }
 | 
			
		||||
                    ensureOffset();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Check for resize, on a timer
 | 
			
		||||
                activeInterval = $interval(drawIfResized, 1000);
 | 
			
		||||
 | 
			
		||||
                $scope.$on('series:data:add', onSeriesDataAdd);
 | 
			
		||||
                $scope.$watchCollection('series', recreateLines);
 | 
			
		||||
                redraw();
 | 
			
		||||
 | 
			
		||||
                // Stop checking for resize when $scope is destroyed
 | 
			
		||||
                $scope.$on("$destroy", destroyChart);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                // Apply directive only to $elements
 | 
			
		||||
                restrict: "E",
 | 
			
		||||
 | 
			
		||||
                // Template to use (a canvas $element)
 | 
			
		||||
                template: TEMPLATE,
 | 
			
		||||
 | 
			
		||||
                // Link function; set up $scope
 | 
			
		||||
                link: linkChart,
 | 
			
		||||
 | 
			
		||||
                // Initial, isolate $scope for the directive
 | 
			
		||||
                scope: {
 | 
			
		||||
                    draw: "=" ,
 | 
			
		||||
                    rectangles: "=",
 | 
			
		||||
                    series: "=",
 | 
			
		||||
                    viewport: "="
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return MCTChart;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -0,0 +1,14 @@
 | 
			
		||||
/*global define*/
 | 
			
		||||
 | 
			
		||||
define(function () {
 | 
			
		||||
        return function MCTOverlayPlot() {
 | 
			
		||||
            return {
 | 
			
		||||
                restrict: "E",
 | 
			
		||||
                templateUrl: 'platform/features/plot-reborn/res/templates/plot.html',
 | 
			
		||||
                scope: {
 | 
			
		||||
                    domainObject: "="
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										272
									
								
								platform/features/plot-reborn/src/directives/MCTPlot.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								platform/features/plot-reborn/src/directives/MCTPlot.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,272 @@
 | 
			
		||||
/*global define,window*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    [
 | 
			
		||||
        '../lib/utils'
 | 
			
		||||
    ],
 | 
			
		||||
    function (utils) {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        var RANGE_TICK_COUNT = 7,
 | 
			
		||||
            DOMAIN_TICK_COUNT = 5;
 | 
			
		||||
 | 
			
		||||
        function MCTPlot() {
 | 
			
		||||
 | 
			
		||||
            function link($scope, $element) {
 | 
			
		||||
                // Now that we're here, let's handle some scope management that the controller would otherwise handle.
 | 
			
		||||
 | 
			
		||||
                if (typeof $scope.rectangles === "undefined") {
 | 
			
		||||
                    $scope.rectangles = [];
 | 
			
		||||
                }
 | 
			
		||||
                if (typeof $scope.displayableRange === "undefined") {
 | 
			
		||||
                    $scope.displayableRange = function (x) { return x; };
 | 
			
		||||
                }
 | 
			
		||||
                if (typeof $scope.displayableDomain === "undefined") {
 | 
			
		||||
                    $scope.displayableDomain = function (x) { return x; };
 | 
			
		||||
                }
 | 
			
		||||
                if (typeof $scope.axes === "undefined") {
 | 
			
		||||
                    $scope.axes = {
 | 
			
		||||
                        domain: {
 | 
			
		||||
                            label: "Time",
 | 
			
		||||
                            tickCount: DOMAIN_TICK_COUNT,
 | 
			
		||||
                            ticks: []
 | 
			
		||||
                        },
 | 
			
		||||
                        range: {
 | 
			
		||||
                            label: "Value",
 | 
			
		||||
                            tickCount: RANGE_TICK_COUNT,
 | 
			
		||||
                            ticks: []
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                var dragStart,
 | 
			
		||||
                    marqueeBox = {},
 | 
			
		||||
                    marqueeRect, // Set when exists.
 | 
			
		||||
                    chartElementBounds,
 | 
			
		||||
                    $canvas = $element.find('canvas');
 | 
			
		||||
 | 
			
		||||
                function updateAxesForCurrentViewport() {
 | 
			
		||||
                    // Update axes definitions for current viewport.
 | 
			
		||||
                    if (!$scope.axes || !$scope.viewport) {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    ['domain', 'range'].forEach(function(axisName) {
 | 
			
		||||
                        var axis = $scope.axes[axisName],
 | 
			
		||||
                            firstTick = $scope.viewport.topLeft[axisName],
 | 
			
		||||
                            lastTick = $scope.viewport.bottomRight[axisName],
 | 
			
		||||
                            axisSize = firstTick - lastTick,
 | 
			
		||||
                            denominator = axis.tickCount - 1,
 | 
			
		||||
                            tickNumber,
 | 
			
		||||
                            tickIncrement,
 | 
			
		||||
                            tickValue;
 | 
			
		||||
                        // Yes, ticksize is negative for domain and positive for range.
 | 
			
		||||
                        // It's because ticks are generated/displayed top to bottom and left to right.
 | 
			
		||||
                        axis.ticks = [];
 | 
			
		||||
                        for (tickNumber = 0; tickNumber < axis.tickCount; tickNumber++) {
 | 
			
		||||
                            tickIncrement = (axisSize * (tickNumber / denominator));
 | 
			
		||||
                            tickValue = firstTick - tickIncrement;
 | 
			
		||||
                            axis.ticks.push(
 | 
			
		||||
                                tickValue
 | 
			
		||||
                            );
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function drawMarquee() {
 | 
			
		||||
                    // Create rectangle for Marquee if it should be set.
 | 
			
		||||
                    if (marqueeBox && marqueeBox.start && marqueeBox.end) {
 | 
			
		||||
                        if (!marqueeRect) {
 | 
			
		||||
                            marqueeRect = {};
 | 
			
		||||
                            $scope.rectangles.push(marqueeRect);
 | 
			
		||||
                        }
 | 
			
		||||
                        marqueeRect.start = marqueeBox.start;
 | 
			
		||||
                        marqueeRect.end = marqueeBox.end;
 | 
			
		||||
                        marqueeRect.color = [1, 1, 1, 0.5];
 | 
			
		||||
                        marqueeRect.layer = 'top'; // TODO: implement this.
 | 
			
		||||
                        $scope.$broadcast('rectangle-change');
 | 
			
		||||
                    } else if (marqueeRect && $scope.rectangles.indexOf(marqueeRect) !== -1) {
 | 
			
		||||
                        $scope.rectangles.splice($scope.rectangles.indexOf(marqueeRect));
 | 
			
		||||
                        marqueeRect = undefined;
 | 
			
		||||
                        $scope.$broadcast('rectangle-change');
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function untrackMousePosition() {
 | 
			
		||||
                    $scope.mouseCoordinates = undefined;
 | 
			
		||||
                }
 | 
			
		||||
                function updateMarquee() {
 | 
			
		||||
                    // Update the marquee box in progress.
 | 
			
		||||
                    marqueeBox.end = $scope.mouseCoordinates.positionAsPlotPoint;
 | 
			
		||||
                    drawMarquee();
 | 
			
		||||
                }
 | 
			
		||||
                function startMarquee() {
 | 
			
		||||
                    marqueeBox.start = $scope.mouseCoordinates.positionAsPlotPoint;
 | 
			
		||||
                }
 | 
			
		||||
                function endMarquee() {
 | 
			
		||||
                    // marqueeBox start/end are opposite corners but we need
 | 
			
		||||
                    // topLeft and bottomRight.
 | 
			
		||||
                    var boxPoints = utils.boxPointsFromOppositeCorners(marqueeBox.start, marqueeBox.end),
 | 
			
		||||
                        newViewport = utils.oppositeCornersFromBoxPoints(boxPoints);
 | 
			
		||||
 | 
			
		||||
                    marqueeBox = {};
 | 
			
		||||
                    drawMarquee();
 | 
			
		||||
                    $scope.$emit('user:viewport:change:end', newViewport);
 | 
			
		||||
                    $scope.viewport = newViewport;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function startDrag($event) {
 | 
			
		||||
                    $scope.$emit('user:viewport:change:start');
 | 
			
		||||
                    if (!$scope.mouseCoordinates) {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    $event.preventDefault();
 | 
			
		||||
                    // Track drag location relative to position over element
 | 
			
		||||
                    // not domain, as chart viewport will change as we drag.
 | 
			
		||||
                    dragStart = $scope.mouseCoordinates.positionAsPlotPoint;
 | 
			
		||||
                    // Tell controller that we're starting to navigate.
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function updateDrag() {
 | 
			
		||||
                    // calculate offset between points.  Apply that offset to viewport.
 | 
			
		||||
                    var newPosition = $scope.mouseCoordinates.positionAsPlotPoint,
 | 
			
		||||
                        dDomain = dragStart.domain - newPosition.domain,
 | 
			
		||||
                        dRange = dragStart.range - newPosition.range;
 | 
			
		||||
 | 
			
		||||
                    $scope.viewport = {
 | 
			
		||||
                        topLeft: {
 | 
			
		||||
                            domain: $scope.viewport.topLeft.domain + dDomain,
 | 
			
		||||
                            range: $scope.viewport.topLeft.range + dRange
 | 
			
		||||
                        },
 | 
			
		||||
                        bottomRight: {
 | 
			
		||||
                            domain: $scope.viewport.bottomRight.domain + dDomain,
 | 
			
		||||
                            range: $scope.viewport.bottomRight.range + dRange
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function endDrag() {
 | 
			
		||||
                    dragStart = undefined;
 | 
			
		||||
                    $scope.$emit('user:viewport:change:end', $scope.viewport);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function trackMousePosition($event) {
 | 
			
		||||
                    if (!$scope.viewport) { return; }
 | 
			
		||||
                    // Calculate coordinates of mouse related to canvas and as
 | 
			
		||||
                    // domain, range value and make available in scope for display.
 | 
			
		||||
 | 
			
		||||
                    var bounds = $event.target.getBoundingClientRect(),
 | 
			
		||||
                        positionOverElement,
 | 
			
		||||
                        positionAsPlotPoint;
 | 
			
		||||
 | 
			
		||||
                    chartElementBounds = bounds;
 | 
			
		||||
 | 
			
		||||
                    positionOverElement = {
 | 
			
		||||
                        x: $event.clientX - bounds.left,
 | 
			
		||||
                        y: $event.clientY - bounds.top
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    positionAsPlotPoint = utils.elementPositionAsPlotPosition(
 | 
			
		||||
                        positionOverElement,
 | 
			
		||||
                        bounds,
 | 
			
		||||
                        $scope.viewport
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    $scope.mouseCoordinates = {
 | 
			
		||||
                        positionOverElement: positionOverElement,
 | 
			
		||||
                        positionAsPlotPoint: positionAsPlotPoint
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    if (marqueeBox && marqueeBox.start) {
 | 
			
		||||
                        updateMarquee();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (dragStart) {
 | 
			
		||||
                        updateDrag();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function watchForMarquee() {
 | 
			
		||||
                    $canvas.removeClass('plot-drag');
 | 
			
		||||
                    $canvas.addClass('plot-marquee');
 | 
			
		||||
                    $canvas.on('mousedown', startMarquee);
 | 
			
		||||
                    $canvas.on('mouseup', endMarquee);
 | 
			
		||||
                    $canvas.off('mousedown', startDrag);
 | 
			
		||||
                    $canvas.off('mouseup', endDrag);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function watchForDrag() {
 | 
			
		||||
                    $canvas.addClass('plot-drag');
 | 
			
		||||
                    $canvas.removeClass('plot-marquee');
 | 
			
		||||
                    $canvas.on('mousedown', startDrag);
 | 
			
		||||
                    $canvas.on('mouseup', endDrag);
 | 
			
		||||
                    $canvas.off('mousedown', startMarquee);
 | 
			
		||||
                    $canvas.off('mouseup', endMarquee);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function toggleInteractionMode(event) {
 | 
			
		||||
                    if (event.keyCode === 18) { // control key.
 | 
			
		||||
                        watchForDrag();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function resetInteractionMode(event) {
 | 
			
		||||
                    if (event.keyCode === 18) {
 | 
			
		||||
                        watchForMarquee();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function stopWatching() {
 | 
			
		||||
                    $canvas.off('mousedown', startDrag);
 | 
			
		||||
                    $canvas.off('mouseup', endDrag);
 | 
			
		||||
                    $canvas.off('mousedown', startMarquee);
 | 
			
		||||
                    $canvas.off('mouseup', endMarquee);
 | 
			
		||||
                    window.removeEventListener('keydown', toggleInteractionMode);
 | 
			
		||||
                    window.removeEventListener('keyup', resetInteractionMode);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $canvas.on('mousemove', trackMousePosition);
 | 
			
		||||
                $canvas.on('mouseleave', untrackMousePosition);
 | 
			
		||||
                watchForMarquee();
 | 
			
		||||
 | 
			
		||||
                window.addEventListener('keydown', toggleInteractionMode);
 | 
			
		||||
                window.addEventListener('keyup', resetInteractionMode);
 | 
			
		||||
 | 
			
		||||
                function onViewportChange() {
 | 
			
		||||
                    if ($scope.mouseCoordinates && chartElementBounds) {
 | 
			
		||||
                        $scope.mouseCoordinates.positionAsPlotPoint =
 | 
			
		||||
                            utils.elementPositionAsPlotPosition(
 | 
			
		||||
                                $scope.mouseCoordinates.positionOverElement,
 | 
			
		||||
                                chartElementBounds,
 | 
			
		||||
                                $scope.viewport
 | 
			
		||||
                            );
 | 
			
		||||
                    }
 | 
			
		||||
                    // TODO: Discuss whether marqueeBox start should be fixed to data or fixed to canvas element, especially when "isLive is true".
 | 
			
		||||
                    updateAxesForCurrentViewport();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $scope.$watchCollection('viewport', onViewportChange);
 | 
			
		||||
 | 
			
		||||
                $scope.$on('$destroy', stopWatching);
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                restrict: "E",
 | 
			
		||||
                templateUrl: 'platform/features/plot-reborn/res/templates/mct-plot.html',
 | 
			
		||||
                link: link,
 | 
			
		||||
                scope: {
 | 
			
		||||
                    viewport: "=",
 | 
			
		||||
                    series: "=",
 | 
			
		||||
                    rectangles: "=?",
 | 
			
		||||
                    axes: "=?",
 | 
			
		||||
                    displayableRange: "=?",
 | 
			
		||||
                    displayableDomain: "=?"
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return MCTPlot;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										120
									
								
								platform/features/plot-reborn/src/draw/Draw2D.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								platform/features/plot-reborn/src/draw/Draw2D.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,120 @@
 | 
			
		||||
/*global define*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    [],
 | 
			
		||||
    function () {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * 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 unavailable.
 | 
			
		||||
         */
 | 
			
		||||
        function Draw2D(canvas) {
 | 
			
		||||
            var c2d = canvas.getContext('2d'),
 | 
			
		||||
                width = canvas.width,
 | 
			
		||||
                height = canvas.height,
 | 
			
		||||
                dimensions = [ width, height ],
 | 
			
		||||
                origin = [ 0, 0 ];
 | 
			
		||||
 | 
			
		||||
            // Convert from logical to physical x coordinates
 | 
			
		||||
            function x(v) {
 | 
			
		||||
                return ((v - origin[0]) / dimensions[0]) * width;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Convert from logical to physical y coordinates
 | 
			
		||||
            function y(v) {
 | 
			
		||||
                return height - ((v - origin[1]) / dimensions[1]) * height;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Set the color to be used for drawing operations
 | 
			
		||||
            function setColor(color) {
 | 
			
		||||
                var mappedColor = color.map(function (c, i) {
 | 
			
		||||
                        return i < 3 ? Math.floor(c * 255) : (c);
 | 
			
		||||
                    }).join(',');
 | 
			
		||||
                c2d.strokeStyle = "rgba(" + mappedColor + ")";
 | 
			
		||||
                c2d.fillStyle = "rgba(" + mappedColor + ")";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!c2d) {
 | 
			
		||||
                throw new Error("Canvas 2d API unavailable.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                /**
 | 
			
		||||
                 * Clear the chart.
 | 
			
		||||
                 */
 | 
			
		||||
                clear: function () {
 | 
			
		||||
                    width = canvas.width;
 | 
			
		||||
                    height = canvas.height;
 | 
			
		||||
                    c2d.clearRect(0, 0, width, height);
 | 
			
		||||
                },
 | 
			
		||||
                /**
 | 
			
		||||
                 * 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
 | 
			
		||||
                 */
 | 
			
		||||
                setDimensions: function (newDimensions, newOrigin) {
 | 
			
		||||
                    dimensions = newDimensions;
 | 
			
		||||
                    origin = newOrigin;
 | 
			
		||||
                },
 | 
			
		||||
                /**
 | 
			
		||||
                 * 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
 | 
			
		||||
                 */
 | 
			
		||||
                drawLine: function (buf, color, points) {
 | 
			
		||||
                    var i;
 | 
			
		||||
 | 
			
		||||
                    setColor(color);
 | 
			
		||||
 | 
			
		||||
                    // Configure context to draw two-pixel-thick lines
 | 
			
		||||
                    c2d.lineWidth = 2;
 | 
			
		||||
 | 
			
		||||
                    // Start a new path...
 | 
			
		||||
                    if (buf.length > 1) {
 | 
			
		||||
                        c2d.beginPath();
 | 
			
		||||
                        c2d.moveTo(x(buf[0]), y(buf[1]));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // ...and add points to it...
 | 
			
		||||
                    for (i = 2; i < points * 2; i = i + 2) {
 | 
			
		||||
                        c2d.lineTo(x(buf[i]), y(buf[i + 1]));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // ...before finally drawing it.
 | 
			
		||||
                    c2d.stroke();
 | 
			
		||||
                },
 | 
			
		||||
                /**
 | 
			
		||||
                 * 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
 | 
			
		||||
                 */
 | 
			
		||||
                drawSquare: function (min, max, color) {
 | 
			
		||||
                    var x1 = x(min[0]),
 | 
			
		||||
                        y1 = y(min[1]),
 | 
			
		||||
                        w = x(max[0]) - x1,
 | 
			
		||||
                        h = y(max[1]) - y1;
 | 
			
		||||
 | 
			
		||||
                    setColor(color);
 | 
			
		||||
                    c2d.fillRect(x1, y1, w, h);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Draw2D;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										46
									
								
								platform/features/plot-reborn/src/draw/DrawLoader.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								platform/features/plot-reborn/src/draw/DrawLoader.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
/*global define,$log */
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    [
 | 
			
		||||
        './DrawWebGL',
 | 
			
		||||
        './Draw2D'
 | 
			
		||||
    ],
 | 
			
		||||
    function (DrawWebGL, Draw2D) {
 | 
			
		||||
 | 
			
		||||
        var CHARTS = [
 | 
			
		||||
            DrawWebGL,
 | 
			
		||||
            Draw2D
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * 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) {
 | 
			
		||||
                var i;
 | 
			
		||||
                for (i = 0; i < CHARTS.length; i++) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        return new CHARTS[i](canvas);
 | 
			
		||||
                    } catch (e) {
 | 
			
		||||
                        $log.warn([
 | 
			
		||||
                            "Could not instantiate chart",
 | 
			
		||||
                            CHARTS[i].name,
 | 
			
		||||
                            ";",
 | 
			
		||||
                            e.message
 | 
			
		||||
                        ].join(" "));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                $log.warn("Cannot initialize mct-chart.");
 | 
			
		||||
                return undefined;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										152
									
								
								platform/features/plot-reborn/src/draw/DrawWebGL.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								platform/features/plot-reborn/src/draw/DrawWebGL.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,152 @@
 | 
			
		||||
/*global define,Float32Array*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    [],
 | 
			
		||||
    function () {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        // WebGL shader sources (for drawing plain colors)
 | 
			
		||||
        var FRAGMENT_SHADER = [
 | 
			
		||||
                "precision mediump float;",
 | 
			
		||||
                "uniform vec4 uColor;",
 | 
			
		||||
                "void main(void) {",
 | 
			
		||||
                "gl_FragColor = uColor;",
 | 
			
		||||
                "}"
 | 
			
		||||
            ].join('\n'),
 | 
			
		||||
            VERTEX_SHADER = [
 | 
			
		||||
                "attribute vec2 aVertexPosition;",
 | 
			
		||||
                "uniform vec2 uDimensions;",
 | 
			
		||||
                "uniform vec2 uOrigin;",
 | 
			
		||||
                "void main(void) {",
 | 
			
		||||
                "gl_Position = vec4(2.0 * ((aVertexPosition - uOrigin) / uDimensions) - vec2(1,1), 0, 1);",
 | 
			
		||||
                "}"
 | 
			
		||||
            ].join('\n');
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Create a 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) {
 | 
			
		||||
            var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"),
 | 
			
		||||
                vertexShader,
 | 
			
		||||
                fragmentShader,
 | 
			
		||||
                program,
 | 
			
		||||
                aVertexPosition,
 | 
			
		||||
                uColor,
 | 
			
		||||
                uDimensions,
 | 
			
		||||
                uOrigin,
 | 
			
		||||
                buffer;
 | 
			
		||||
 | 
			
		||||
            // Ensure a context was actually available before proceeding
 | 
			
		||||
            if (!gl) {
 | 
			
		||||
                throw new Error("WebGL unavailable.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Initialize shaders
 | 
			
		||||
            vertexShader = gl.createShader(gl.VERTEX_SHADER);
 | 
			
		||||
            gl.shaderSource(vertexShader, VERTEX_SHADER);
 | 
			
		||||
            gl.compileShader(vertexShader);
 | 
			
		||||
            fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
 | 
			
		||||
            gl.shaderSource(fragmentShader, FRAGMENT_SHADER);
 | 
			
		||||
            gl.compileShader(fragmentShader);
 | 
			
		||||
 | 
			
		||||
            // Assemble vertex/fragment shaders into programs
 | 
			
		||||
            program = gl.createProgram();
 | 
			
		||||
            gl.attachShader(program, vertexShader);
 | 
			
		||||
            gl.attachShader(program, fragmentShader);
 | 
			
		||||
            gl.linkProgram(program);
 | 
			
		||||
            gl.useProgram(program);
 | 
			
		||||
 | 
			
		||||
            // Get locations for attribs/uniforms from the
 | 
			
		||||
            // shader programs (to pass values into shaders at draw-time)
 | 
			
		||||
            aVertexPosition = gl.getAttribLocation(program, "aVertexPosition");
 | 
			
		||||
            uColor = gl.getUniformLocation(program, "uColor");
 | 
			
		||||
            uDimensions = gl.getUniformLocation(program, "uDimensions");
 | 
			
		||||
            uOrigin = gl.getUniformLocation(program, "uOrigin");
 | 
			
		||||
            gl.enableVertexAttribArray(aVertexPosition);
 | 
			
		||||
 | 
			
		||||
            // Create a buffer to holds points which will be drawn
 | 
			
		||||
            buffer = gl.createBuffer();
 | 
			
		||||
 | 
			
		||||
            // Use a line width of 2.0 for legibility
 | 
			
		||||
            gl.lineWidth(2.0);
 | 
			
		||||
 | 
			
		||||
            // Enable blending, for smoothness
 | 
			
		||||
            gl.enable(gl.BLEND);
 | 
			
		||||
            gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
 | 
			
		||||
 | 
			
		||||
            // Utility function to handle drawing of a buffer;
 | 
			
		||||
            // drawType will determine whether this is a box, line, etc.
 | 
			
		||||
            function doDraw(drawType, buf, color, points) {
 | 
			
		||||
                gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
 | 
			
		||||
                gl.bufferData(gl.ARRAY_BUFFER, buf, gl.DYNAMIC_DRAW);
 | 
			
		||||
                gl.vertexAttribPointer(aVertexPosition, 2, gl.FLOAT, false, 0, 0);
 | 
			
		||||
                gl.uniform4fv(uColor, color);
 | 
			
		||||
                gl.drawArrays(drawType, 0, points);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                /**
 | 
			
		||||
                 * Clear the chart.
 | 
			
		||||
                 */
 | 
			
		||||
                clear: function () {
 | 
			
		||||
                    // Set the viewport size; note that we use the width/height
 | 
			
		||||
                    // that our WebGL context reports, which may be lower
 | 
			
		||||
                    // resolution than the canvas we requested.
 | 
			
		||||
                    gl.viewport(
 | 
			
		||||
                        0,
 | 
			
		||||
                        0,
 | 
			
		||||
                        gl.drawingBufferWidth,
 | 
			
		||||
                        gl.drawingBufferHeight
 | 
			
		||||
                    );
 | 
			
		||||
                    gl.clear(gl.COLOR_BUFFER_BIT + gl.DEPTH_BUFFER_BIT);
 | 
			
		||||
                },
 | 
			
		||||
                /**
 | 
			
		||||
                 * 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
 | 
			
		||||
                 */
 | 
			
		||||
                setDimensions: function (dimensions, origin) {
 | 
			
		||||
                    if (dimensions && dimensions.length > 0 &&
 | 
			
		||||
                            origin && origin.length > 0) {
 | 
			
		||||
                        gl.uniform2fv(uDimensions, dimensions);
 | 
			
		||||
                        gl.uniform2fv(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
 | 
			
		||||
                 */
 | 
			
		||||
                drawLine: function (buf, color, points) {
 | 
			
		||||
                    doDraw(gl.LINE_STRIP, 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
 | 
			
		||||
                 */
 | 
			
		||||
                drawSquare: function (min, max, color) {
 | 
			
		||||
                    doDraw(gl.TRIANGLE_FAN, new Float32Array(
 | 
			
		||||
                        min.concat([min[0], max[1]]).concat(max).concat([max[0], min[1]])
 | 
			
		||||
                    ), color, 4);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        return DrawWebGL;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										75
									
								
								platform/features/plot-reborn/src/lib/utils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								platform/features/plot-reborn/src/lib/utils.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
/*global define*/
 | 
			
		||||
 | 
			
		||||
define(function() {
 | 
			
		||||
    "use strict";
 | 
			
		||||
 | 
			
		||||
    var utils = {};
 | 
			
		||||
 | 
			
		||||
    utils.boxPointsFromOppositeCorners = function(start, end) {
 | 
			
		||||
        // Given two points defining opposite corners of a square,
 | 
			
		||||
        // return an array of points containing all of the boxes' rectangles.
 | 
			
		||||
        return [
 | 
			
		||||
            start,
 | 
			
		||||
            {domain: start.domain, range: end.range},
 | 
			
		||||
            end,
 | 
			
		||||
            {domain: end.domain, range: start.range}
 | 
			
		||||
        ];
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    utils.oppositeCornersFromBoxPoints = function(boxPoints) {
 | 
			
		||||
        // Given an array of box points, return the topLeft and bottomRight points of the box.
 | 
			
		||||
        var topLeft = boxPoints.reduce(function(topLeft, currentPoint) {
 | 
			
		||||
            if (!topLeft) {
 | 
			
		||||
                return currentPoint;
 | 
			
		||||
            }
 | 
			
		||||
            if (currentPoint.domain <= topLeft.domain &&
 | 
			
		||||
                    currentPoint.range >= topLeft.range) {
 | 
			
		||||
                return currentPoint;
 | 
			
		||||
            }
 | 
			
		||||
            return topLeft;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        var bottomRight = boxPoints.reduce(function(bottomRight, currentPoint) {
 | 
			
		||||
            if (!bottomRight) {
 | 
			
		||||
                return currentPoint;
 | 
			
		||||
            }
 | 
			
		||||
            if (currentPoint.domain >= bottomRight.domain &&
 | 
			
		||||
                    currentPoint.range <= bottomRight.range) {
 | 
			
		||||
                return currentPoint;
 | 
			
		||||
            }
 | 
			
		||||
            return bottomRight;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            topLeft: topLeft,
 | 
			
		||||
            bottomRight: bottomRight
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    utils.elementPositionAsPlotPosition = function(elementPosition, elementBounds, viewport) {
 | 
			
		||||
        // Convert an (x, y) pair in element space to a
 | 
			
		||||
        // (domain, range) pair viewport.
 | 
			
		||||
 | 
			
		||||
        // Element space has (0,0) as the topLeft corner, With x
 | 
			
		||||
        // increasing to the right and y increasing to the bottom.
 | 
			
		||||
 | 
			
		||||
        var maxDomain = viewport.bottomRight.domain;
 | 
			
		||||
        var minDomain = viewport.topLeft.domain;
 | 
			
		||||
        var domainDenominator = maxDomain - minDomain;
 | 
			
		||||
 | 
			
		||||
        var maxRange = viewport.topLeft.range;
 | 
			
		||||
        var minRange = viewport.bottomRight.range;
 | 
			
		||||
        var rangeDenominator = maxRange - minRange;
 | 
			
		||||
 | 
			
		||||
        var xFraction = elementPosition.x / elementBounds.width;
 | 
			
		||||
        var yFraction = elementPosition.y / elementBounds.height;
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            domain: minDomain + domainDenominator * xFraction,
 | 
			
		||||
            range: maxRange - rangeDenominator * yFraction
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    return utils;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										154
									
								
								platform/features/plot-reborn/src/services/ColorService.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								platform/features/plot-reborn/src/services/ColorService.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,154 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT Web, Copyright (c) 2014-2015, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT Web 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 Web 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(
 | 
			
		||||
    function () {
 | 
			
		||||
        'use strict';
 | 
			
		||||
 | 
			
		||||
        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 ]
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * A representation of a color that allows conversions between different
 | 
			
		||||
         * formats.
 | 
			
		||||
         *
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function Color(integerArray) {
 | 
			
		||||
            this.integerArray = integerArray;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * 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]);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * A color palette stores a set of colors and allows for different
 | 
			
		||||
         * methods of color allocation.
 | 
			
		||||
         *
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function ColorPalette() {
 | 
			
		||||
            this.nextColor = 0;
 | 
			
		||||
            this.colors = COLOR_PALETTE.map(function (color) {
 | 
			
		||||
                return new Color(color);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * @returns {Color} the next unused color in the palette.  If all colors
 | 
			
		||||
         * have been allocated, it will wrap around.
 | 
			
		||||
         */
 | 
			
		||||
        ColorPalette.prototype.getNextColor = function () {
 | 
			
		||||
            var color = this.colors[this.nextColor % this.colors.length];
 | 
			
		||||
            this.nextColor++;
 | 
			
		||||
            return color;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * @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];
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        function ColorService() {}
 | 
			
		||||
 | 
			
		||||
        ColorService.prototype.ColorPalette = ColorPalette;
 | 
			
		||||
        ColorService.prototype.Color = Color;
 | 
			
		||||
 | 
			
		||||
        function getColorService() {
 | 
			
		||||
            return new ColorService();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return getColorService;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
		Reference in New Issue
	
	Block a user