openmct/platform/features/plot/src/SubPlot.js
Victor Woeltjen 10863514cb [Licenses] Add license headers
WTD-1051.
2015-05-13 16:43:30 -07:00

314 lines
13 KiB
JavaScript

/*****************************************************************************
* 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(
[
'./elements/PlotPosition',
'./elements/PlotTickGenerator'
],
function (PlotPosition, PlotTickGenerator) {
"use strict";
var DOMAIN_TICKS = 5,
RANGE_TICKS = 7;
/**
* A SubPlot is an individual plot within a Plot View (which
* may contain multiple plots, specifically when in Stacked
* plot mode.)
* @constructor
* @param {DomainObject[]} telemetryObjects the domain objects
* which will be plotted in this sub-plot
* @param {PlotPanZoomStack} panZoomStack the stack of pan-zoom
* states which is applicable to this sub-plot
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service; used to convert domain/range values
* from telemetry data sets to a human-readable form.
*/
function SubPlot(telemetryObjects, panZoomStack, telemetryFormatter) {
// We are used from a template often, so maintain
// state in local variables to allow for fast look-up,
// as is normal for controllers.
var draw = {},
rangeTicks = [],
domainTicks = [],
formatter = telemetryFormatter,
domainOffset,
mousePosition,
marqueeStart,
hoverCoordinates,
isHovering = false;
// Utility, for map/forEach loops. Index 0 is domain,
// index 1 is range.
function formatValue(v, i) {
return (i ?
formatter.formatRangeValue :
formatter.formatDomainValue)(v);
}
// Utility function for filtering out empty strings.
function isNonEmpty(v) {
return typeof v === 'string' && v !== "";
}
// Converts from pixel coordinates to domain-range,
// to interpret mouse gestures.
function mousePositionToDomainRange(mousePosition) {
return new PlotPosition(
mousePosition.x,
mousePosition.y,
mousePosition.width,
mousePosition.height,
panZoomStack
).getPosition();
}
// Utility function to get the mouse position (in x,y
// pixel coordinates in the canvas area) from a mouse
// event object.
function toMousePosition($event) {
var target = $event.target,
bounds = target.getBoundingClientRect();
return {
x: $event.clientX - bounds.left,
y: $event.clientY - bounds.top,
width: bounds.width,
height: bounds.height
};
}
// Convert a domain-range position to a displayable
// position. This will subtract the domain offset, which
// is used to bias domain values to minimize loss-of-precision
// associated with conversion to a 32-bit floating point
// format (which is needed in the chart area itself, by WebGL.)
function toDisplayable(position) {
return [ position[0] - domainOffset, position[1] ];
}
// Update the currnet hover coordinates
function updateHoverCoordinates() {
hoverCoordinates = mousePosition &&
mousePositionToDomainRange(mousePosition)
.map(formatValue)
.filter(isNonEmpty)
.join(", ");
}
// Update the drawable marquee area to reflect current
// mouse position (or don't show it at all, if no marquee
// zoom is in progress)
function updateMarqueeBox() {
// Express this as a box in the draw object, which
// is passed to an mct-chart in the template for rendering.
draw.boxes = marqueeStart ?
[{
start: toDisplayable(mousePositionToDomainRange(marqueeStart)),
end: toDisplayable(mousePositionToDomainRange(mousePosition)),
color: [1, 1, 1, 0.5 ]
}] : undefined;
}
// Update the bounds (origin and dimensions) of the drawing area.
function updateDrawingBounds() {
var panZoom = panZoomStack.getPanZoom();
// Communicate pan-zoom state from stack to the draw object
// which is passed to mct-chart in the template.
draw.dimensions = panZoom.dimensions;
draw.origin = [
panZoom.origin[0] - domainOffset,
panZoom.origin[1]
];
}
// Update tick marks in scope.
function updateTicks() {
var tickGenerator = new PlotTickGenerator(panZoomStack, formatter);
domainTicks =
tickGenerator.generateDomainTicks(DOMAIN_TICKS);
rangeTicks =
tickGenerator.generateRangeTicks(RANGE_TICKS);
}
// Perform a marquee zoom.
function marqueeZoom(start, end) {
// Determine what boundary is described by the marquee,
// in domain-range values. Use the minima for origin, so that
// it doesn't matter what direction the user marqueed in.
var a = mousePositionToDomainRange(start),
b = mousePositionToDomainRange(end),
origin = [
Math.min(a[0], b[0]),
Math.min(a[1], b[1])
],
dimensions = [
Math.max(a[0], b[0]) - origin[0],
Math.max(a[1], b[1]) - origin[1]
];
// Proceed with zoom if zoom dimensions are non zeros
if (!(dimensions[0] === 0 && dimensions[1] === 0)) {
// Push the new state onto the pan-zoom stack
panZoomStack.pushPanZoom(origin, dimensions);
// Make sure tick marks reflect new bounds
updateTicks();
}
}
// Start with the right initial drawing bounds,
// tick marks
updateDrawingBounds();
updateTicks();
return {
/**
* Get the set of domain objects which are being
* represented in this sub-plot.
* @returns {DomainObject[]} the domain objects which
* will have data plotted in this sub-plot
*/
getTelemetryObjects: function () {
return telemetryObjects;
},
/**
* Get ticks mark information appropriate for using in the
* template for this sub-plot's domain axis, as prepared
* by the PlotTickGenerator.
* @returns {Array} tick marks for the domain axis
*/
getDomainTicks: function () {
return domainTicks;
},
/**
* Get ticks mark information appropriate for using in the
* template for this sub-plot's range axis, as prepared
* by the PlotTickGenerator.
* @returns {Array} tick marks for the range axis
*/
getRangeTicks: function () {
return rangeTicks;
},
/**
* Get the drawing object associated with this sub-plot;
* this object will be passed to the mct-chart in which
* this sub-plot's lines will be plotted, as its "draw"
* attribute, and should have the same internal format
* expected by that directive.
* @return {object} the drawing object
*/
getDrawingObject: function () {
return draw;
},
/**
* Get the coordinates (as displayable text) for the
* current mouse position.
* @returns {string[]} the displayable domain and range
* coordinates over which the mouse is hovered
*/
getHoverCoordinates: function () {
return hoverCoordinates;
},
/**
* Handle mouse movement over the chart area.
* @param $event the mouse event
*/
hover: function ($event) {
isHovering = true;
mousePosition = toMousePosition($event);
updateHoverCoordinates();
if (marqueeStart) {
updateMarqueeBox();
}
},
/**
* Initiate a marquee zoom action.
* @param $event the mouse event
*/
startMarquee: function ($event) {
mousePosition = marqueeStart = toMousePosition($event);
updateMarqueeBox();
},
/**
* Complete a marquee zoom action.
* @param $event the mouse event
*/
endMarquee: function ($event) {
mousePosition = toMousePosition($event);
if (marqueeStart) {
marqueeZoom(marqueeStart, mousePosition);
marqueeStart = undefined;
updateMarqueeBox();
updateDrawingBounds();
}
},
/**
* Update the drawing bounds, marquee box, and
* tick marks for this subplot.
*/
update: function () {
updateDrawingBounds();
updateMarqueeBox();
updateTicks();
},
/**
* Set the domain offset associated with this sub-plot.
* A domain offset is subtracted from all domain
* before lines are drawn to avoid artifacts associated
* with the use of 32-bit floats when domain values
* are often timestamps (due to insufficient precision.)
* A SubPlot will be drawing boxes (for marquee zoom) in
* the same offset coordinate space, so it needs to know
* the value of this to position that marquee box
* correctly.
* @param {number} value the domain offset
*/
setDomainOffset: function (value) {
domainOffset = value;
},
/**
* When used with no argument, check whether or not the user
* is currently hovering over this subplot. When used with
* an argument, set that state.
* @param {boolean} [state] the new hovering state
* @returns {boolean} the hovering state
*/
isHovering: function (state) {
if (state !== undefined) {
isHovering = state;
}
return isHovering;
}
};
}
return SubPlot;
}
);