[Code Style] Use prototypes in Plot bundle

WTD-1482.
This commit is contained in:
Victor Woeltjen
2015-08-13 12:12:15 -07:00
parent aefad6fdd3
commit 820c15d74c
11 changed files with 844 additions and 852 deletions

View File

@ -31,115 +31,88 @@ define(
* *
* @memberof platform/features/plot * @memberof platform/features/plot
* @constructor * @constructor
* @implements {platform/features/plot.Chart}
* @param {CanvasElement} canvas the canvas object to render upon * @param {CanvasElement} canvas the canvas object to render upon
* @throws {Error} an error is thrown if Canvas's 2D API is unavailable. * @throws {Error} an error is thrown if Canvas's 2D API is unavailable.
*/ */
function Canvas2DChart(canvas) { function Canvas2DChart(canvas) {
var c2d = canvas.getContext('2d'), this.canvas = canvas;
width = canvas.width, this.c2d = canvas.getContext('2d');
height = canvas.height, this.width = canvas.width;
dimensions = [ width, height ], this.height = canvas.height;
origin = [ 0, 0 ]; this.dimensions = [ this.width, this.height ];
this.origin = [ 0, 0 ];
if (!this.c2d) {
throw new Error("Canvas 2d API unavailable.");
}
}
// Convert from logical to physical x coordinates // Convert from logical to physical x coordinates
function x(v) { Canvas2DChart.prototype.x = function (v) {
return ((v - origin[0]) / dimensions[0]) * width; return ((v - this.origin[0]) / this.dimensions[0]) * this.width;
} };
// Convert from logical to physical y coordinates // Convert from logical to physical y coordinates
function y(v) { Canvas2DChart.prototype.y = function (v) {
return height - ((v - origin[1]) / dimensions[1]) * height; return this.height -
} ((v - this.origin[1]) / this.dimensions[1]) * this.height;
};
// Set the color to be used for drawing operations // Set the color to be used for drawing operations
function setColor(color) { Canvas2DChart.prototype.setColor = function (color) {
var mappedColor = color.map(function (c, i) { var mappedColor = color.map(function (c, i) {
return i < 3 ? Math.floor(c * 255) : (c); return i < 3 ? Math.floor(c * 255) : (c);
}).join(','); }).join(',');
c2d.strokeStyle = "rgba(" + mappedColor + ")"; this.c2d.strokeStyle = "rgba(" + mappedColor + ")";
c2d.fillStyle = "rgba(" + mappedColor + ")"; this.c2d.fillStyle = "rgba(" + mappedColor + ")";
} };
if (!c2d) {
throw new Error("Canvas 2d API unavailable.");
}
return { Canvas2DChart.prototype.clear = function () {
/** var canvas = this.canvas;
* Clear the chart. this.width = canvas.width;
* @memberof platform/features/plot.Canvas2DChart# this.height = canvas.height;
*/ this.c2d.clearRect(0, 0, this.width, this.height);
clear: function () { };
width = canvas.width;
height = canvas.height; Canvas2DChart.prototype.setDimensions = function (newDimensions, newOrigin) {
c2d.clearRect(0, 0, width, height); this.dimensions = newDimensions;
}, this.origin = newOrigin;
/** };
* Set the logical boundaries of the chart.
* @param {number[]} dimensions the horizontal and Canvas2DChart.prototype.drawLine = function (buf, color, points) {
* vertical dimensions of the chart
* @param {number[]} origin the horizontal/vertical
* origin of the chart
* @memberof platform/features/plot.Canvas2DChart#
*/
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
* @memberof platform/features/plot.Canvas2DChart#
*/
drawLine: function (buf, color, points) {
var i; var i;
setColor(color); this.setColor(color);
// Configure context to draw two-pixel-thick lines // Configure context to draw two-pixel-thick lines
c2d.lineWidth = 2; this.c2d.lineWidth = 2;
// Start a new path... // Start a new path...
if (buf.length > 1) { if (buf.length > 1) {
c2d.beginPath(); this.c2d.beginPath();
c2d.moveTo(x(buf[0]), y(buf[1])); this.c2d.moveTo(this.x(buf[0]), this.y(buf[1]));
} }
// ...and add points to it... // ...and add points to it...
for (i = 2; i < points * 2; i = i + 2) { for (i = 2; i < points * 2; i = i + 2) {
c2d.lineTo(x(buf[i]), y(buf[i + 1])); this.c2d.lineTo(this.x(buf[i]), this.y(buf[i + 1]));
} }
// ...before finally drawing it. // ...before finally drawing it.
c2d.stroke(); this.c2d.stroke();
}, };
/**
* Draw a rectangle extending from one corner to another, Canvas2DChart.prototype.drawSquare = function (min, max, color) {
* in the chosen color. var x1 = this.x(min[0]),
* @param {number[]} min the first corner of the rectangle y1 = this.y(min[1]),
* @param {number[]} max the opposite corner w = this.x(max[0]) - x1,
* @param {number[]} color the color to use when drawing h = this.y(max[1]) - y1;
* the rectangle, as an RGBA color where each element
* is in the range of 0.0-1.0 this.setColor(color);
* @memberof platform/features/plot.Canvas2DChart# this.c2d.fillRect(x1, y1, w, h);
*/
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 Canvas2DChart; return Canvas2DChart;
} }

View File

@ -51,6 +51,7 @@ define(
* *
* @memberof platform/features/plot * @memberof platform/features/plot
* @constructor * @constructor
* @implements {platform/features/plot.Chart}
* @param {CanvasElement} canvas the canvas object to render upon * @param {CanvasElement} canvas the canvas object to render upon
* @throws {Error} an error is thrown if WebGL is unavailable. * @throws {Error} an error is thrown if WebGL is unavailable.
*/ */
@ -62,8 +63,7 @@ define(
aVertexPosition, aVertexPosition,
uColor, uColor,
uDimensions, uDimensions,
uOrigin, uOrigin;
buffer;
// Ensure a context was actually available before proceeding // Ensure a context was actually available before proceeding
if (!gl) { if (!gl) {
@ -94,7 +94,7 @@ define(
gl.enableVertexAttribArray(aVertexPosition); gl.enableVertexAttribArray(aVertexPosition);
// Create a buffer to holds points which will be drawn // Create a buffer to holds points which will be drawn
buffer = gl.createBuffer(); this.buffer = gl.createBuffer();
// Use a line width of 2.0 for legibility // Use a line width of 2.0 for legibility
gl.lineWidth(2.0); gl.lineWidth(2.0);
@ -103,22 +103,27 @@ define(
gl.enable(gl.BLEND); gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
// Utility function to handle drawing of a buffer; this.gl = gl;
// drawType will determine whether this is a box, line, etc. this.aVertexPosition = aVertexPosition;
function doDraw(drawType, buf, color, points) { this.uColor = uColor;
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); this.uDimensions = uDimensions;
gl.bufferData(gl.ARRAY_BUFFER, buf, gl.DYNAMIC_DRAW); this.uOrigin = uOrigin;
gl.vertexAttribPointer(aVertexPosition, 2, gl.FLOAT, false, 0, 0);
gl.uniform4fv(uColor, color);
gl.drawArrays(drawType, 0, points);
} }
return { // Utility function to handle drawing of a buffer;
/** // drawType will determine whether this is a box, line, etc.
* Clear the chart. GLChart.prototype.doDraw = function (drawType, buf, color, points) {
* @memberof platform/features/plot.GLChart# var gl = this.gl;
*/ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
clear: function () { gl.bufferData(gl.ARRAY_BUFFER, buf, gl.DYNAMIC_DRAW);
gl.vertexAttribPointer(this.aVertexPosition, 2, gl.FLOAT, false, 0, 0);
gl.uniform4fv(this.uColor, color);
gl.drawArrays(drawType, 0, points);
};
GLChart.prototype.clear = function () {
var gl = this.gl;
// Set the viewport size; note that we use the width/height // Set the viewport size; note that we use the width/height
// that our WebGL context reports, which may be lower // that our WebGL context reports, which may be lower
// resolution than the canvas we requested. // resolution than the canvas we requested.
@ -129,53 +134,28 @@ define(
gl.drawingBufferHeight gl.drawingBufferHeight
); );
gl.clear(gl.COLOR_BUFFER_BIT + gl.DEPTH_BUFFER_BIT); gl.clear(gl.COLOR_BUFFER_BIT + gl.DEPTH_BUFFER_BIT);
}, };
/**
* Set the logical boundaries of the chart.
* @param {number[]} dimensions the horizontal and GLChart.prototype.setDimensions = function (dimensions, origin) {
* vertical dimensions of the chart var gl = this.gl;
* @param {number[]} origin the horizontal/vertical
* origin of the chart
* @memberof platform/features/plot.GLChart#
*/
setDimensions: function (dimensions, origin) {
if (dimensions && dimensions.length > 0 && if (dimensions && dimensions.length > 0 &&
origin && origin.length > 0) { origin && origin.length > 0) {
gl.uniform2fv(uDimensions, dimensions); gl.uniform2fv(this.uDimensions, dimensions);
gl.uniform2fv(uOrigin, origin); gl.uniform2fv(this.uOrigin, origin);
}
},
/**
* Draw the supplied buffer as a line strip (a sequence
* of line segments), in the chosen color.
* @param {Float32Array} buf the line strip to draw,
* in alternating x/y positions
* @param {number[]} color the color to use when drawing
* the line, as an RGBA color where each element
* is in the range of 0.0-1.0
* @param {number} points the number of points to draw
* @memberof platform/features/plot.GLChart#
*/
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
* @memberof platform/features/plot.GLChart#
*/
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);
} }
}; };
}
GLChart.prototype.drawLine = function (buf, color, points) {
this.doDraw(this.gl.LINE_STRIP, buf, color, points);
};
GLChart.prototype.drawSquare = function (min, max, color) {
this.doDraw(this.gl.TRIANGLE_FAN, new Float32Array(
min.concat([min[0], max[1]]).concat(max).concat([max[0], min[1]])
), color, 4);
};
return GLChart; return GLChart;
} }
); );

View File

@ -206,6 +206,45 @@ define(
}; };
} }
/**
* @interface platform/features/plot.Chart
* @private
*/
/**
* Clear the chart.
* @method platform/features/plot.Chart#clear
*/
/**
* Set the logical boundaries of the chart.
* @param {number[]} dimensions the horizontal and
* vertical dimensions of the chart
* @param {number[]} origin the horizontal/vertical
* origin of the chart
* @memberof platform/features/plot.Chart#setDimensions
*/
/**
* Draw the supplied buffer as a line strip (a sequence
* of line segments), in the chosen color.
* @param {Float32Array} buf the line strip to draw,
* in alternating x/y positions
* @param {number[]} color the color to use when drawing
* the line, as an RGBA color where each element
* is in the range of 0.0-1.0
* @param {number} points the number of points to draw
* @memberof platform/features/plot.Chart#drawLine
*/
/**
* Draw a rectangle extending from one corner to another,
* in the chosen color.
* @param {number[]} min the first corner of the rectangle
* @param {number[]} max the opposite corner
* @param {number[]} color the color to use when drawing
* the rectangle, as an RGBA color where each element
* is in the range of 0.0-1.0
* @memberof platform/features/plot.Chart#drawSquare
*/
return MCTChart; return MCTChart;
} }
); );

View File

@ -61,15 +61,11 @@ define(
throttle, throttle,
PLOT_FIXED_DURATION PLOT_FIXED_DURATION
) { ) {
var subPlotFactory = new SubPlotFactory(telemetryFormatter), var self = this,
modeOptions = new PlotModeOptions([], subPlotFactory), subPlotFactory = new SubPlotFactory(telemetryFormatter),
subplots = [],
cachedObjects = [], cachedObjects = [],
limitTracker,
updater, updater,
handle, handle;
scheduleUpdate,
domainOffset;
// Populate the scope with axis information (specifically, options // Populate the scope with axis information (specifically, options
// available for each axis.) // available for each axis.)
@ -91,18 +87,13 @@ define(
function setupModes(telemetryObjects) { function setupModes(telemetryObjects) {
if (cachedObjects !== telemetryObjects) { if (cachedObjects !== telemetryObjects) {
cachedObjects = telemetryObjects; cachedObjects = telemetryObjects;
modeOptions = new PlotModeOptions( self.modeOptions = new PlotModeOptions(
telemetryObjects || [], telemetryObjects || [],
subPlotFactory subPlotFactory
); );
} }
} }
// Update all sub-plots
function update() {
scheduleUpdate();
}
// Reinstantiate the plot updater (e.g. because we have a // Reinstantiate the plot updater (e.g. because we have a
// new subscription.) This will clear the plot. // new subscription.) This will clear the plot.
function recreateUpdater() { function recreateUpdater() {
@ -112,7 +103,7 @@ define(
($scope.axes[1].active || {}).key, ($scope.axes[1].active || {}).key,
PLOT_FIXED_DURATION PLOT_FIXED_DURATION
); );
limitTracker = new PlotLimitTracker( self.limitTracker = new PlotLimitTracker(
handle, handle,
($scope.axes[1].active || {}).key ($scope.axes[1].active || {}).key
); );
@ -125,19 +116,19 @@ define(
} }
if (updater) { if (updater) {
updater.update(); updater.update();
modeOptions.getModeHandler().plotTelemetry(updater); self.modeOptions.getModeHandler().plotTelemetry(updater);
} }
if (limitTracker) { if (self.limitTracker) {
limitTracker.update(); self.limitTracker.update();
} }
update(); self.update();
} }
// Display new historical data as it becomes available // Display new historical data as it becomes available
function addHistoricalData(domainObject, series) { function addHistoricalData(domainObject, series) {
updater.addHistorical(domainObject, series); updater.addHistorical(domainObject, series);
modeOptions.getModeHandler().plotTelemetry(updater); self.modeOptions.getModeHandler().plotTelemetry(updater);
update(); self.update();
} }
// Issue a new request for historical telemetry // Issue a new request for historical telemetry
@ -174,115 +165,118 @@ define(
} }
} }
this.modeOptions = new PlotModeOptions([], subPlotFactory);
this.updateValues = updateValues;
// Create a throttled update function
this.scheduleUpdate = throttle(function () {
self.modeOptions.getModeHandler().getSubPlots()
.forEach(updateSubplot);
});
// Subscribe to telemetry when a domain object becomes available // Subscribe to telemetry when a domain object becomes available
$scope.$watch('domainObject', subscribe); $scope.$watch('domainObject', subscribe);
// Unsubscribe when the plot is destroyed // Unsubscribe when the plot is destroyed
$scope.$on("$destroy", releaseSubscription); $scope.$on("$destroy", releaseSubscription);
// Create a throttled update function }
scheduleUpdate = throttle(function () {
modeOptions.getModeHandler().getSubPlots()
.forEach(updateSubplot);
});
return {
/** /**
* Get the color (as a style-friendly string) to use * Get the color (as a style-friendly string) to use
* for plotting the trace at the specified index. * for plotting the trace at the specified index.
* @param {number} index the index of the trace * @param {number} index the index of the trace
* @returns {string} the color, in #RRGGBB form * @returns {string} the color, in #RRGGBB form
* @memberof platform/features/plot.PlotController#
*/ */
getColor: function (index) { PlotController.prototype.getColor = function (index) {
return PlotPalette.getStringColor(index); return PlotPalette.getStringColor(index);
}, };
/** /**
* Check if the plot is zoomed or panned out * Check if the plot is zoomed or panned out
* of its default state (to determine whether back/unzoom * of its default state (to determine whether back/unzoom
* controls should be shown) * controls should be shown)
* @returns {boolean} true if not in default state * @returns {boolean} true if not in default state
* @memberof platform/features/plot.PlotController#
*/ */
isZoomed: function () { PlotController.prototype.isZoomed = function () {
return modeOptions.getModeHandler().isZoomed(); return this.modeOptions.getModeHandler().isZoomed();
}, };
/** /**
* Undo the most recent pan/zoom change and restore * Undo the most recent pan/zoom change and restore
* the prior state. * the prior state.
* @memberof platform/features/plot.PlotController#
*/ */
stepBackPanZoom: function () { PlotController.prototype.stepBackPanZoom = function () {
return modeOptions.getModeHandler().stepBackPanZoom(); return this.modeOptions.getModeHandler().stepBackPanZoom();
}, };
/** /**
* Undo all pan/zoom changes and restore the initial state. * Undo all pan/zoom changes and restore the initial state.
* @memberof platform/features/plot.PlotController#
*/ */
unzoom: function () { PlotController.prototype.unzoom = function () {
return modeOptions.getModeHandler().unzoom(); return this.modeOptions.getModeHandler().unzoom();
}, };
/** /**
* Get the mode options (Stacked/Overlaid) that are applicable * Get the mode options (Stacked/Overlaid) that are applicable
* for this plot. * for this plot.
* @memberof platform/features/plot.PlotController#
*/ */
getModeOptions: function () { PlotController.prototype.getModeOptions = function () {
return modeOptions.getModeOptions(); return this.modeOptions.getModeOptions();
}, };
/** /**
* Get the current mode that is applicable to this plot. This * Get the current mode that is applicable to this plot. This
* will include key, name, and glyph fields. * will include key, name, and glyph fields.
* @memberof platform/features/plot.PlotController#
*/ */
getMode: function () { PlotController.prototype.getMode = function () {
return modeOptions.getMode(); return this.modeOptions.getMode();
}, };
/** /**
* Set the mode which should be active in this plot. * Set the mode which should be active in this plot.
* @param mode one of the mode options returned from * @param mode one of the mode options returned from
* getModeOptions() * getModeOptions()
* @memberof platform/features/plot.PlotController#
*/ */
setMode: function (mode) { PlotController.prototype.setMode = function (mode) {
modeOptions.setMode(mode); this.modeOptions.setMode(mode);
updateValues(); this.updateValues();
}, };
/** /**
* Get all individual plots contained within this Plot view. * Get all individual plots contained within this Plot view.
* (Multiple may be contained when in Stacked mode). * (Multiple may be contained when in Stacked mode).
* @returns {SubPlot[]} all subplots in this Plot view * @returns {SubPlot[]} all subplots in this Plot view
* @memberof platform/features/plot.PlotController#
*/ */
getSubPlots: function () { PlotController.prototype.getSubPlots = function () {
return modeOptions.getModeHandler().getSubPlots(); return this.modeOptions.getModeHandler().getSubPlots();
}, };
/** /**
* Get the CSS class to apply to the legend for this domain * Get the CSS class to apply to the legend for this domain
* object; this will reflect limit state. * object; this will reflect limit state.
* @returns {string} the CSS class * @returns {string} the CSS class
* @memberof platform/features/plot.PlotController#
*/ */
getLegendClass: function (telemetryObject) { PlotController.prototype.getLegendClass = function (telemetryObject) {
return limitTracker && return this.limitTracker &&
limitTracker.getLegendClass(telemetryObject); this.limitTracker.getLegendClass(telemetryObject);
}, };
/** /**
* Explicitly update all plots. * Explicitly update all plots.
* @memberof platform/features/plot.PlotController#
*/ */
update: update, PlotController.prototype.update = function () {
this.scheduleUpdate();
};
/** /**
* Check if a request is pending (to show the wait spinner) * Check if a request is pending (to show the wait spinner)
* @memberof platform/features/plot.PlotController#
*/ */
isRequestPending: function () { PlotController.prototype.isRequestPending = function () {
// Placeholder; this should reflect request state // Placeholder; this should reflect request state
// when requesting historical telemetry // when requesting historical telemetry
return false; return false;
}
}; };
}
return PlotController; return PlotController;
} }

View File

@ -50,25 +50,18 @@ define(
// We are used from a template often, so maintain // We are used from a template often, so maintain
// state in local variables to allow for fast look-up, // state in local variables to allow for fast look-up,
// as is normal for controllers. // as is normal for controllers.
var draw = {}, this.telemetryObjects = telemetryObjects;
rangeTicks = [], this.domainTicks = [];
domainTicks = [], this.rangeTicks = [];
formatter = telemetryFormatter, this.formatter = telemetryFormatter;
domainOffset, this.draw = {};
mousePosition, this.hovering = false;
marqueeStart, this.panZoomStack = panZoomStack;
panStart,
panStartBounds,
subPlotBounds,
hoverCoordinates,
isHovering = false;
// Utility, for map/forEach loops. Index 0 is domain, // Start with the right initial drawing bounds,
// index 1 is range. // tick marks
function formatValue(v, i) { this.updateDrawingBounds();
return (i ? this.updateTicks();
formatter.formatRangeValue :
formatter.formatDomainValue)(v);
} }
// Utility function for filtering out empty strings. // Utility function for filtering out empty strings.
@ -78,21 +71,21 @@ define(
// Converts from pixel coordinates to domain-range, // Converts from pixel coordinates to domain-range,
// to interpret mouse gestures. // to interpret mouse gestures.
function mousePositionToDomainRange(mousePosition) { SubPlot.prototype.mousePositionToDomainRange = function (mousePosition) {
return new PlotPosition( return new PlotPosition(
mousePosition.x, mousePosition.x,
mousePosition.y, mousePosition.y,
mousePosition.width, mousePosition.width,
mousePosition.height, mousePosition.height,
panZoomStack this.panZoomStack
).getPosition(); ).getPosition();
} };
// Utility function to get the mouse position (in x,y // Utility function to get the mouse position (in x,y
// pixel coordinates in the canvas area) from a mouse // pixel coordinates in the canvas area) from a mouse
// event object. // event object.
function toMousePosition($event) { SubPlot.prototype.toMousePosition = function ($event) {
var bounds = subPlotBounds; var bounds = this.subPlotBounds;
return { return {
x: $event.clientX - bounds.left, x: $event.clientX - bounds.left,
@ -100,91 +93,235 @@ define(
width: bounds.width, width: bounds.width,
height: bounds.height height: bounds.height
}; };
} };
// Convert a domain-range position to a displayable // Convert a domain-range position to a displayable
// position. This will subtract the domain offset, which // position. This will subtract the domain offset, which
// is used to bias domain values to minimize loss-of-precision // is used to bias domain values to minimize loss-of-precision
// associated with conversion to a 32-bit floating point // associated with conversion to a 32-bit floating point
// format (which is needed in the chart area itself, by WebGL.) // format (which is needed in the chart area itself, by WebGL.)
function toDisplayable(position) { SubPlot.prototype.toDisplayable = function (position) {
return [ position[0] - domainOffset, position[1] ]; return [ position[0] - this.domainOffset, position[1] ];
};
// Update the current hover coordinates
SubPlot.prototype.updateHoverCoordinates = function () {
var formatter = this.formatter;
// Utility, for map/forEach loops. Index 0 is domain,
// index 1 is range.
function formatValue(v, i) {
return (i ?
formatter.formatRangeValue :
formatter.formatDomainValue)(v);
} }
this.hoverCoordinates = this.mousePosition &&
// Update the currnet hover coordinates this.mousePositionToDomainRange(this.mousePosition)
function updateHoverCoordinates() {
hoverCoordinates = mousePosition &&
mousePositionToDomainRange(mousePosition)
.map(formatValue) .map(formatValue)
.filter(isNonEmpty) .filter(isNonEmpty)
.join(", "); .join(", ");
} };
// Update the drawable marquee area to reflect current // Update the drawable marquee area to reflect current
// mouse position (or don't show it at all, if no marquee // mouse position (or don't show it at all, if no marquee
// zoom is in progress) // zoom is in progress)
function updateMarqueeBox() { SubPlot.prototype.updateMarqueeBox = function () {
// Express this as a box in the draw object, which // Express this as a box in the draw object, which
// is passed to an mct-chart in the template for rendering. // is passed to an mct-chart in the template for rendering.
draw.boxes = marqueeStart ? this.draw.boxes = this.marqueeStart ?
[{ [{
start: toDisplayable(mousePositionToDomainRange(marqueeStart)), start: this.toDisplayable(
end: toDisplayable(mousePositionToDomainRange(mousePosition)), this.mousePositionToDomainRange(this.marqueeStart)
),
end: this.toDisplayable(
this.mousePositionToDomainRange(this.mousePosition)
),
color: [1, 1, 1, 0.5 ] color: [1, 1, 1, 0.5 ]
}] : undefined; }] : undefined;
} };
// Update the bounds (origin and dimensions) of the drawing area. // Update the bounds (origin and dimensions) of the drawing area.
function updateDrawingBounds() { SubPlot.prototype.updateDrawingBounds = function () {
var panZoom = panZoomStack.getPanZoom(); var panZoom = this.panZoomStack.getPanZoom();
// Communicate pan-zoom state from stack to the draw object // Communicate pan-zoom state from stack to the draw object
// which is passed to mct-chart in the template. // which is passed to mct-chart in the template.
draw.dimensions = panZoom.dimensions; this.draw.dimensions = panZoom.dimensions;
draw.origin = [ this.draw.origin = [
panZoom.origin[0] - domainOffset, panZoom.origin[0] - this.domainOffset,
panZoom.origin[1] panZoom.origin[1]
]; ];
} };
// Update tick marks in scope. // Update tick marks in scope.
function updateTicks() { SubPlot.prototype.updateTicks = function () {
var tickGenerator = new PlotTickGenerator(panZoomStack, formatter); var tickGenerator =
new PlotTickGenerator(this.panZoomStack, this.formatter);
domainTicks = this.domainTicks =
tickGenerator.generateDomainTicks(DOMAIN_TICKS); tickGenerator.generateDomainTicks(DOMAIN_TICKS);
rangeTicks = this.rangeTicks =
tickGenerator.generateRangeTicks(RANGE_TICKS); tickGenerator.generateRangeTicks(RANGE_TICKS);
} };
function updatePan() { SubPlot.prototype.updatePan = function () {
var start, current, delta, nextOrigin; var start, current, delta, nextOrigin;
// Clear the previous panning pan-zoom state // Clear the previous panning pan-zoom state
panZoomStack.popPanZoom(); this.panZoomStack.popPanZoom();
// Calculate what the new resulting pan-zoom should be // Calculate what the new resulting pan-zoom should be
start = mousePositionToDomainRange(panStart); start = this.mousePositionToDomainRange(
current = mousePositionToDomainRange(mousePosition); this.panStart,
this.panZoomStack
);
current = this.mousePositionToDomainRange(
this.mousePosition,
this.panZoomStack
);
delta = [ current[0] - start[0], current[1] - start[1] ]; delta = [ current[0] - start[0], current[1] - start[1] ];
nextOrigin = [ nextOrigin = [
panStartBounds.origin[0] - delta[0], this.panStartBounds.origin[0] - delta[0],
panStartBounds.origin[1] - delta[1] this.panStartBounds.origin[1] - delta[1]
]; ];
// ...and push a new one at the current mouse position // ...and push a new one at the current mouse position
panZoomStack.pushPanZoom(nextOrigin, panStartBounds.dimensions); this.panZoomStack
} .pushPanZoom(nextOrigin, this.panStartBounds.dimensions);
};
/**
* Get the set of domain objects which are being
* represented in this sub-plot.
* @returns {DomainObject[]} the domain objects which
* will have data plotted in this sub-plot
*/
SubPlot.prototype.getTelemetryObjects = function () {
return this.telemetryObjects;
};
/**
* Get ticks mark information appropriate for using in the
* template for this sub-plot's domain axis, as prepared
* by the PlotTickGenerator.
* @returns {Array} tick marks for the domain axis
*/
SubPlot.prototype.getDomainTicks = function () {
return this.domainTicks;
};
/**
* Get ticks mark information appropriate for using in the
* template for this sub-plot's range axis, as prepared
* by the PlotTickGenerator.
* @returns {Array} tick marks for the range axis
*/
SubPlot.prototype.getRangeTicks = function () {
return this.rangeTicks;
};
/**
* Get the drawing object associated with this sub-plot;
* this object will be passed to the mct-chart in which
* this sub-plot's lines will be plotted, as its "draw"
* attribute, and should have the same internal format
* expected by that directive.
* @return {object} the drawing object
*/
SubPlot.prototype.getDrawingObject = function () {
return this.draw;
};
/**
* Get the coordinates (as displayable text) for the
* current mouse position.
* @returns {string[]} the displayable domain and range
* coordinates over which the mouse is hovered
*/
SubPlot.prototype.getHoverCoordinates = function () {
return this.hoverCoordinates;
};
/**
* Handle mouse movement over the chart area.
* @param $event the mouse event
* @memberof platform/features/plot.SubPlot#
*/
SubPlot.prototype.hover = function ($event) {
this.hovering = true;
this.subPlotBounds = $event.target.getBoundingClientRect();
this.mousePosition = this.toMousePosition($event);
this.updateHoverCoordinates();
if (this.marqueeStart) {
this.updateMarqueeBox();
}
if (this.panStart) {
this.updatePan();
this.updateDrawingBounds();
this.updateTicks();
}
};
/**
* Continue a previously-start pan or zoom gesture.
* @param $event the mouse event
* @memberof platform/features/plot.SubPlot#
*/
SubPlot.prototype.continueDrag = function ($event) {
this.mousePosition = this.toMousePosition($event);
if (this.marqueeStart) {
this.updateMarqueeBox();
}
if (this.panStart) {
this.updatePan();
this.updateDrawingBounds();
this.updateTicks();
}
};
/**
* Initiate a marquee zoom action.
* @param $event the mouse event
*/
SubPlot.prototype.startDrag = function ($event) {
this.subPlotBounds = $event.target.getBoundingClientRect();
this.mousePosition = this.toMousePosition($event);
// Treat any modifier key as a pan
if ($event.altKey || $event.shiftKey || $event.ctrlKey) {
// Start panning
this.panStart = this.mousePosition;
this.panStartBounds = this.panZoomStack.getPanZoom();
// We're starting a pan, so add this back as a
// state on the stack; it will get replaced
// during the pan.
this.panZoomStack.pushPanZoom(
this.panStartBounds.origin,
this.panStartBounds.dimensions
);
$event.preventDefault();
} else {
// Start marquee zooming
this.marqueeStart = this.mousePosition;
this.updateMarqueeBox();
}
};
/**
* Complete a marquee zoom action.
* @param $event the mouse event
*/
SubPlot.prototype.endDrag = function ($event) {
var self = this;
// Perform a marquee zoom. // Perform a marquee zoom.
function marqueeZoom(start, end) { function marqueeZoom(start, end) {
// Determine what boundary is described by the marquee, // Determine what boundary is described by the marquee,
// in domain-range values. Use the minima for origin, so that // in domain-range values. Use the minima for origin, so that
// it doesn't matter what direction the user marqueed in. // it doesn't matter what direction the user marqueed in.
var a = mousePositionToDomainRange(start), var a = self.mousePositionToDomainRange(start),
b = mousePositionToDomainRange(end), b = self.mousePositionToDomainRange(end),
origin = [ origin = [
Math.min(a[0], b[0]), Math.min(a[0], b[0]),
Math.min(a[1], b[1]) Math.min(a[1], b[1])
@ -197,164 +334,39 @@ define(
// Proceed with zoom if zoom dimensions are non zeros // Proceed with zoom if zoom dimensions are non zeros
if (!(dimensions[0] === 0 && dimensions[1] === 0)) { if (!(dimensions[0] === 0 && dimensions[1] === 0)) {
// Push the new state onto the pan-zoom stack // Push the new state onto the pan-zoom stack
panZoomStack.pushPanZoom(origin, dimensions); self.panZoomStack.pushPanZoom(origin, dimensions);
// Make sure tick marks reflect new bounds // Make sure tick marks reflect new bounds
updateTicks(); self.updateTicks();
} }
} }
// Start with the right initial drawing bounds, this.mousePosition = this.toMousePosition($event);
// tick marks this.subPlotBounds = undefined;
updateDrawingBounds(); if (this.marqueeStart) {
updateTicks(); marqueeZoom(this.marqueeStart, this.mousePosition);
this.marqueeStart = undefined;
return { this.updateMarqueeBox();
/** this.updateDrawingBounds();
* Get the set of domain objects which are being this.updateTicks();
* represented in this sub-plot.
* @returns {DomainObject[]} the domain objects which
* will have data plotted in this sub-plot
* @memberof platform/features/plot.SubPlot#
*/
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
* @memberof platform/features/plot.SubPlot#
*/
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
* @memberof platform/features/plot.SubPlot#
*/
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
* @memberof platform/features/plot.SubPlot#
*/
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
* @memberof platform/features/plot.SubPlot#
*/
getHoverCoordinates: function () {
return hoverCoordinates;
},
/**
* Handle mouse movement over the chart area.
* @param $event the mouse event
* @memberof platform/features/plot.SubPlot#
*/
hover: function ($event) {
isHovering = true;
subPlotBounds = $event.target.getBoundingClientRect();
mousePosition = toMousePosition($event);
updateHoverCoordinates();
if (marqueeStart) {
updateMarqueeBox();
} }
if (panStart) { if (this.panStart) {
updatePan();
updateDrawingBounds();
updateTicks();
}
},
/**
* Continue a previously-start pan or zoom gesture.
* @param $event the mouse event
* @memberof platform/features/plot.SubPlot#
*/
continueDrag: function ($event) {
mousePosition = toMousePosition($event);
if (marqueeStart) {
updateMarqueeBox();
}
if (panStart) {
updatePan();
updateDrawingBounds();
updateTicks();
}
},
/**
* Initiate a marquee zoom action.
* @param $event the mouse event
* @memberof platform/features/plot.SubPlot#
*/
startDrag: function ($event) {
subPlotBounds = $event.target.getBoundingClientRect();
mousePosition = toMousePosition($event);
// Treat any modifier key as a pan
if ($event.altKey || $event.shiftKey || $event.ctrlKey) {
// Start panning
panStart = mousePosition;
panStartBounds = panZoomStack.getPanZoom();
// We're starting a pan, so add this back as a
// state on the stack; it will get replaced
// during the pan.
panZoomStack.pushPanZoom(
panStartBounds.origin,
panStartBounds.dimensions
);
$event.preventDefault();
} else {
// Start marquee zooming
marqueeStart = mousePosition;
updateMarqueeBox();
}
},
/**
* Complete a marquee zoom action.
* @param $event the mouse event
* @memberof platform/features/plot.SubPlot#
*/
endDrag: function ($event) {
mousePosition = toMousePosition($event);
subPlotBounds = undefined;
if (marqueeStart) {
marqueeZoom(marqueeStart, mousePosition);
marqueeStart = undefined;
updateMarqueeBox();
updateDrawingBounds();
updateTicks();
}
if (panStart) {
// End panning // End panning
panStart = undefined; this.panStart = undefined;
panStartBounds = undefined; this.panStartBounds = undefined;
} }
}, };
/** /**
* Update the drawing bounds, marquee box, and * Update the drawing bounds, marquee box, and
* tick marks for this subplot. * tick marks for this subplot.
* @memberof platform/features/plot.SubPlot#
*/ */
update: function () { SubPlot.prototype.update = function () {
updateDrawingBounds(); this.updateDrawingBounds();
updateMarqueeBox(); this.updateMarqueeBox();
updateTicks(); this.updateTicks();
}, };
/** /**
* Set the domain offset associated with this sub-plot. * Set the domain offset associated with this sub-plot.
* A domain offset is subtracted from all domain * A domain offset is subtracted from all domain
@ -366,27 +378,24 @@ define(
* the value of this to position that marquee box * the value of this to position that marquee box
* correctly. * correctly.
* @param {number} value the domain offset * @param {number} value the domain offset
* @memberof platform/features/plot.SubPlot#
*/ */
setDomainOffset: function (value) { SubPlot.prototype.setDomainOffset = function (value) {
domainOffset = value; this.domainOffset = value;
}, };
/** /**
* When used with no argument, check whether or not the user * When used with no argument, check whether or not the user
* is currently hovering over this subplot. When used with * is currently hovering over this subplot. When used with
* an argument, set that state. * an argument, set that state.
* @param {boolean} [state] the new hovering state * @param {boolean} [state] the new hovering state
* @returns {boolean} the hovering state * @returns {boolean} the hovering state
* @memberof platform/features/plot.SubPlot#
*/ */
isHovering: function (state) { SubPlot.prototype.isHovering = function (state) {
if (state !== undefined) { if (state !== undefined) {
isHovering = state; this.hovering = state;
}
return isHovering;
} }
return this.hovering;
}; };
}
return SubPlot; return SubPlot;

View File

@ -35,7 +35,9 @@ define(
* @constructor * @constructor
*/ */
function SubPlotFactory(telemetryFormatter) { function SubPlotFactory(telemetryFormatter) {
return { this.telemetryFormatter = telemetryFormatter;
}
/** /**
* Instantiate a new sub-plot. * Instantiate a new sub-plot.
* @param {DomainObject[]} telemetryObjects the domain objects * @param {DomainObject[]} telemetryObjects the domain objects
@ -44,18 +46,14 @@ define(
* states which is applicable to this sub-plot * states which is applicable to this sub-plot
* @returns {SubPlot} the instantiated sub-plot * @returns {SubPlot} the instantiated sub-plot
* @method * @method
* @memberof SubPlotFactory
* @memberof platform/features/plot.SubPlotFactory#
*/ */
createSubPlot: function (telemetryObjects, panZoomStack) { SubPlotFactory.prototype.createSubPlot = function (telemetryObjects, panZoomStack) {
return new SubPlot( return new SubPlot(
telemetryObjects, telemetryObjects,
panZoomStack, panZoomStack,
telemetryFormatter this.telemetryFormatter
); );
}
}; };
}
return SubPlotFactory; return SubPlotFactory;

View File

@ -30,15 +30,53 @@ define(
key: "stacked", key: "stacked",
name: "Stacked", name: "Stacked",
glyph: "m", glyph: "m",
factory: PlotStackMode Constructor: PlotStackMode
}, },
OVERLAID = { OVERLAID = {
key: "overlaid", key: "overlaid",
name: "Overlaid", name: "Overlaid",
glyph: "6", glyph: "6",
factory: PlotOverlayMode Constructor: PlotOverlayMode
}; };
/**
* Handles distinct behavior associated with different
* plot modes.
*
* @interface platform/features/plot.PlotModeHandler
* @private
*/
/**
* Plot telemetry to the sub-plot(s) managed by this mode.
* @param {platform/features/plot.PlotUpdater} updater a source
* of data that is ready to plot
* @method platform/features/plot.PlotModeHandler#plotTelemetry
*/
/**
* Get all sub-plots to be displayed in this mode; used
* to populate the plot template.
* @return {platform/features/plot.SubPlot[]} all sub-plots to
* display in this mode
* @method platform/features/plot.PlotModeHandler#getSubPlots
*/
/**
* Check if we are not in our base pan-zoom state (that is,
* there are some temporary user modifications to the
* current pan-zoom state.)
* @returns {boolean} true if not in the base pan-zoom state
* @method platform/features/plot.PlotModeHandler#isZoomed
*/
/**
* Undo the most recent pan/zoom change and restore
* the prior state.
* @method platform/features/plot.PlotModeHandler#stepBackPanZoom
*/
/**
* Undo all pan/zoom change and restore the base state.
* @method platform/features/plot.PlotModeHandler#unzoom
*/
/** /**
* Determines which plotting modes (stacked/overlaid) * Determines which plotting modes (stacked/overlaid)
* are applicable in a given plot view, maintains current * are applicable in a given plot view, maintains current
@ -46,72 +84,73 @@ define(
* different behaviors associated with these modes. * different behaviors associated with these modes.
* @memberof platform/features/plot * @memberof platform/features/plot
* @constructor * @constructor
* @param {DomainObject[]} the telemetry objects being * @param {DomainObject[]} telemetryObjects the telemetry objects being
* represented in this plot view * represented in this plot view
* @param {platform/features/plot.SubPlotFactory} subPlotFactory a
* factory for creating sub-plots
*/ */
function PlotModeOptions(telemetryObjects, subPlotFactory) { function PlotModeOptions(telemetryObjects, subPlotFactory) {
var options = telemetryObjects.length > 1 ? this.options = telemetryObjects.length > 1 ?
[ OVERLAID, STACKED ] : [ OVERLAID ], [ OVERLAID, STACKED ] : [ OVERLAID ];
mode = options[0], // Initial selection (overlaid) this.mode = this.options[0]; // Initial selection (overlaid)
modeHandler; this.telemetryObjects = telemetryObjects;
this.subPlotFactory = subPlotFactory;
}
return {
/** /**
* Get a handler for the current mode. This will handle * Get a handler for the current mode. This will handle
* plotting telemetry, providing subplots for the template, * plotting telemetry, providing subplots for the template,
* and view-level interactions with pan-zoom state. * and view-level interactions with pan-zoom state.
* @returns {PlotOverlayMode|PlotStackMode} a handler * @returns {PlotOverlayMode|PlotStackMode} a handler
* for the current mode * for the current mode
* @memberof platform/features/plot.PlotModeOptions#
*/ */
getModeHandler: function () { PlotModeOptions.prototype.getModeHandler = function () {
// Lazily initialize // Lazily initialize
if (!modeHandler) { if (!this.modeHandler) {
modeHandler = mode.factory( this.modeHandler = new this.mode.Constructor(
telemetryObjects, this.telemetryObjects,
subPlotFactory this.subPlotFactory
); );
} }
return modeHandler; return this.modeHandler;
}, };
/** /**
* Get all mode options available for each plot. Each * Get all mode options available for each plot. Each
* mode contains a `name` and `glyph` field suitable * mode contains a `name` and `glyph` field suitable
* for display in a template. * for display in a template.
* @return {Array} the available modes * @return {Array} the available modes
* @memberof platform/features/plot.PlotModeOptions#
*/ */
getModeOptions: function () { PlotModeOptions.prototype.getModeOptions = function () {
return options; return this.options;
}, };
/** /**
* Get the plotting mode option currently in use. * Get the plotting mode option currently in use.
* This will be one of the elements returned from * This will be one of the elements returned from
* `getModeOptions`. * `getModeOptions`.
* @return {object} the current mode * @return {*} the current mode
* @memberof platform/features/plot.PlotModeOptions#
*/ */
getMode: function () { PlotModeOptions.prototype.getMode = function () {
return mode; return this.mode;
}, };
/** /**
* Set the plotting mode option to use. * Set the plotting mode option to use.
* The passed argument must be one of the options * The passed argument must be one of the options
* returned by `getModeOptions`. * returned by `getModeOptions`.
* @param {object} option one of the plot mode options * @param {object} option one of the plot mode options
* from `getModeOptions` * from `getModeOptions`
* @memberof platform/features/plot.PlotModeOptions#
*/ */
setMode: function (option) { PlotModeOptions.prototype.setMode = function (option) {
if (mode !== option) { if (this.mode !== option) {
mode = option; this.mode = option;
// Clear the existing mode handler, so it // Clear the existing mode handler, so it
// can be instantiated next time it's needed. // can be instantiated next time it's needed.
modeHandler = undefined; this.modeHandler = undefined;
}
} }
}; };
}
return PlotModeOptions; return PlotModeOptions;
} }

View File

@ -31,81 +31,59 @@ define(
* is one sub-plot which contains all plotted objects. * is one sub-plot which contains all plotted objects.
* @memberof platform/features/plot * @memberof platform/features/plot
* @constructor * @constructor
* @implements {platform/features/plot.PlotModeHandler}
* @param {DomainObject[]} the domain objects to be plotted * @param {DomainObject[]} the domain objects to be plotted
*/ */
function PlotOverlayMode(telemetryObjects, subPlotFactory) { function PlotOverlayMode(telemetryObjects, subPlotFactory) {
var domainOffset, this.panZoomStack = new PlotPanZoomStack([], []);
panZoomStack = new PlotPanZoomStack([], []), this.subplot = subPlotFactory.createSubPlot(
subplot = subPlotFactory.createSubPlot(
telemetryObjects, telemetryObjects,
panZoomStack this.panZoomStack
), );
subplots = [ subplot ]; this.subplots = [ this.subplot ];
}
function plotTelemetry(prepared) { PlotOverlayMode.prototype.plotTelemetry = function (updater) {
// Fit to the boundaries of the data, but don't // Fit to the boundaries of the data, but don't
// override any user-initiated pan-zoom changes. // override any user-initiated pan-zoom changes.
panZoomStack.setBasePanZoom( this.panZoomStack.setBasePanZoom(
prepared.getOrigin(), updater.getOrigin(),
prepared.getDimensions() updater.getDimensions()
); );
// Track the domain offset, used to bias domain values // Track the domain offset, used to bias domain values
// to minimize loss of precision when converted to 32-bit // to minimize loss of precision when converted to 32-bit
// floating point values for display. // floating point values for display.
subplot.setDomainOffset(prepared.getDomainOffset()); this.subplot.setDomainOffset(updater.getDomainOffset());
// Draw the buffers. Select color by index. // Draw the buffers. Select color by index.
subplot.getDrawingObject().lines = prepared.getLineBuffers().map(function (buf, i) { this.subplot.getDrawingObject().lines =
updater.getLineBuffers().map(function (buf, i) {
return { return {
buffer: buf.getBuffer(), buffer: buf.getBuffer(),
color: PlotPalette.getFloatColor(i), color: PlotPalette.getFloatColor(i),
points: buf.getLength() points: buf.getLength()
}; };
}); });
}
return {
/**
* Plot telemetry to the sub-plot(s) managed by this mode.
* @param {PlotPreparer} prepared the prepared data to plot
* @memberof platform/features/plot.PlotOverlayMode#
*/
plotTelemetry: plotTelemetry,
/**
* Get all sub-plots to be displayed in this mode; used
* to populate the plot template.
* @return {SubPlot[]} all sub-plots to display in this mode
* @memberof platform/features/plot.PlotOverlayMode#
*/
getSubPlots: function () {
return subplots;
},
/**
* Check if we are not in our base pan-zoom state (that is,
* there are some temporary user modifications to the
* current pan-zoom state.)
* @returns {boolean} true if not in the base pan-zoom state
* @memberof platform/features/plot.PlotOverlayMode#
*/
isZoomed: function () {
return panZoomStack.getDepth() > 1;
},
/**
* Undo the most recent pan/zoom change and restore
* the prior state.
* @memberof platform/features/plot.PlotOverlayMode#
*/
stepBackPanZoom: function () {
panZoomStack.popPanZoom();
subplot.update();
},
unzoom: function () {
panZoomStack.clearPanZoom();
subplot.update();
}
}; };
}
PlotOverlayMode.prototype.getSubPlots = function () {
return this.subplots;
};
PlotOverlayMode.prototype.isZoomed = function () {
return this.panZoomStack.getDepth() > 1;
};
PlotOverlayMode.prototype.stepBackPanZoom = function () {
this.panZoomStack.popPanZoom();
this.subplot.update();
};
PlotOverlayMode.prototype.unzoom = function () {
this.panZoomStack.clearPanZoom();
this.subplot.update();
};
return PlotOverlayMode; return PlotOverlayMode;
} }

View File

@ -31,20 +31,24 @@ define(
* is one sub-plot for each plotted object. * is one sub-plot for each plotted object.
* @memberof platform/features/plot * @memberof platform/features/plot
* @constructor * @constructor
* @implements {platform/features/plot.PlotModeHandler}
* @param {DomainObject[]} the domain objects to be plotted * @param {DomainObject[]} the domain objects to be plotted
*/ */
function PlotStackMode(telemetryObjects, subPlotFactory) { function PlotStackMode(telemetryObjects, subPlotFactory) {
var domainOffset, var self = this;
panZoomStackGroup =
new PlotPanZoomStackGroup(telemetryObjects.length), this.panZoomStackGroup =
subplots = telemetryObjects.map(function (telemetryObject, i) { new PlotPanZoomStackGroup(telemetryObjects.length);
this.subplots = telemetryObjects.map(function (telemetryObject, i) {
return subPlotFactory.createSubPlot( return subPlotFactory.createSubPlot(
[telemetryObject], [telemetryObject],
panZoomStackGroup.getPanZoomStack(i) self.panZoomStackGroup.getPanZoomStack(i)
); );
}); });
}
function plotTelemetryTo(subplot, prepared, index) { PlotStackMode.prototype.plotTelemetryTo = function (subplot, prepared, index) {
var buffer = prepared.getLineBuffers()[index]; var buffer = prepared.getLineBuffers()[index];
// Track the domain offset, used to bias domain values // Track the domain offset, used to bias domain values
@ -59,70 +63,43 @@ define(
color: PlotPalette.getFloatColor(0), color: PlotPalette.getFloatColor(0),
points: buffer.getLength() points: buffer.getLength()
}]; }];
} };
function plotTelemetry(prepared) { PlotStackMode.prototype.plotTelemetry = function (prepared) {
var self = this;
// Fit to the boundaries of the data, but don't // Fit to the boundaries of the data, but don't
// override any user-initiated pan-zoom changes. // override any user-initiated pan-zoom changes.
panZoomStackGroup.setBasePanZoom( this.panZoomStackGroup.setBasePanZoom(
prepared.getOrigin(), prepared.getOrigin(),
prepared.getDimensions() prepared.getDimensions()
); );
subplots.forEach(function (subplot, index) { this.subplots.forEach(function (subplot, index) {
plotTelemetryTo(subplot, prepared, index); self.plotTelemetryTo(subplot, prepared, index);
});
};
PlotStackMode.prototype.getSubPlots = function () {
return this.subplots;
};
PlotStackMode.prototype.isZoomed = function () {
return this.panZoomStackGroup.getDepth() > 1;
};
PlotStackMode.prototype.stepBackPanZoom = function () {
this.panZoomStackGroup.popPanZoom();
this.subplots.forEach(function (subplot) {
subplot.update();
});
};
PlotStackMode.prototype.unzoom = function () {
this.panZoomStackGroup.clearPanZoom();
this.subplots.forEach(function (subplot) {
subplot.update();
}); });
}
return {
/**
* Plot telemetry to the sub-plot(s) managed by this mode.
* @param {PlotPreparer} prepared the prepared data to plot
* @memberof platform/features/plot.PlotStackMode#
*/
plotTelemetry: plotTelemetry,
/**
* Get all sub-plots to be displayed in this mode; used
* to populate the plot template.
* @return {SubPlot[]} all sub-plots to display in this mode
* @memberof platform/features/plot.PlotStackMode#
*/
getSubPlots: function () {
return subplots;
},
/**
* Check if we are not in our base pan-zoom state (that is,
* there are some temporary user modifications to the
* current pan-zoom state.)
* @returns {boolean} true if not in the base pan-zoom state
* @memberof platform/features/plot.PlotStackMode#
*/
isZoomed: function () {
return panZoomStackGroup.getDepth() > 1;
},
/**
* Undo the most recent pan/zoom change and restore
* the prior state.
* @memberof platform/features/plot.PlotStackMode#
*/
stepBackPanZoom: function () {
panZoomStackGroup.popPanZoom();
subplots.forEach(function (subplot) {
subplot.update();
});
},
/**
* Undo all pan/zoom changes and restore the initial state.
* @memberof platform/features/plot.PlotStackMode#
*/
unzoom: function () {
panZoomStackGroup.clearPanZoom();
subplots.forEach(function (subplot) {
subplot.update();
});
}
}; };
}
return PlotStackMode; return PlotStackMode;
} }

View File

@ -28,12 +28,14 @@ define(
/** /**
* Policy preventing the Plot view from being made available for * Policy preventing the Plot view from being made available for
* domain objects which have non-numeric telemetry. * domain objects which have non-numeric telemetry.
* @implements {Policy} * @implements {Policy.<View, DomainObject>}
* @constructor * @constructor
* @memberof platform/features/plot * @memberof platform/features/plot
*/ */
function PlotViewPolicy() { function PlotViewPolicy() {
function hasImageTelemetry(domainObject) { }
function hasNumericTelemetry(domainObject) {
var telemetry = domainObject && var telemetry = domainObject &&
domainObject.getCapability('telemetry'), domainObject.getCapability('telemetry'),
metadata = telemetry ? telemetry.getMetadata() : {}, metadata = telemetry ? telemetry.getMetadata() : {},
@ -51,16 +53,13 @@ define(
}); });
} }
return { PlotViewPolicy.prototype.allow = function (view, domainObject) {
allow: function (view, domainObject) {
if (view.key === 'plot') { if (view.key === 'plot') {
return hasImageTelemetry(domainObject); return hasNumericTelemetry(domainObject);
} }
return true; return true;
}
}; };
}
return PlotViewPolicy; return PlotViewPolicy;
} }

View File

@ -39,6 +39,12 @@ define(
mockSeries, mockSeries,
controller; controller;
function bind(method, thisObj) {
return function () {
return method.apply(thisObj, arguments);
};
}
beforeEach(function () { beforeEach(function () {
mockScope = jasmine.createSpyObj( mockScope = jasmine.createSpyObj(
@ -196,13 +202,13 @@ define(
}); });
it("allows plots to be updated", function () { it("allows plots to be updated", function () {
expect(controller.update).not.toThrow(); expect(bind(controller.update, controller)).not.toThrow();
}); });
it("allows changing pan-zoom state", function () { it("allows changing pan-zoom state", function () {
expect(controller.isZoomed).not.toThrow(); expect(bind(controller.isZoomed, controller)).not.toThrow();
expect(controller.stepBackPanZoom).not.toThrow(); expect(bind(controller.stepBackPanZoom, controller)).not.toThrow();
expect(controller.unzoom).not.toThrow(); expect(bind(controller.unzoom, controller)).not.toThrow();
}); });
it("indicates if a request is pending", function () { it("indicates if a request is pending", function () {