From 820c15d74cd796dbc89cc9351b7d4e41ffd5ad74 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Thu, 13 Aug 2015 12:12:15 -0700 Subject: [PATCH] [Code Style] Use prototypes in Plot bundle WTD-1482. --- platform/features/plot/src/Canvas2DChart.js | 175 +++-- platform/features/plot/src/GLChart.js | 130 ++-- platform/features/plot/src/MCTChart.js | 39 ++ platform/features/plot/src/PlotController.js | 238 ++++--- platform/features/plot/src/SubPlot.js | 605 +++++++++--------- platform/features/plot/src/SubPlotFactory.js | 38 +- .../plot/src/modes/PlotModeOptions.js | 167 +++-- .../plot/src/modes/PlotOverlayMode.js | 102 ++- .../features/plot/src/modes/PlotStackMode.js | 145 ++--- .../plot/src/policies/PlotViewPolicy.js | 43 +- .../features/plot/test/PlotControllerSpec.js | 14 +- 11 files changed, 844 insertions(+), 852 deletions(-) diff --git a/platform/features/plot/src/Canvas2DChart.js b/platform/features/plot/src/Canvas2DChart.js index 9fcee46123..5917207920 100644 --- a/platform/features/plot/src/Canvas2DChart.js +++ b/platform/features/plot/src/Canvas2DChart.js @@ -31,116 +31,89 @@ define( * * @memberof platform/features/plot * @constructor + * @implements {platform/features/plot.Chart} * @param {CanvasElement} canvas the canvas object to render upon * @throws {Error} an error is thrown if Canvas's 2D API is unavailable. */ function Canvas2DChart(canvas) { - var c2d = canvas.getContext('2d'), - width = canvas.width, - height = canvas.height, - dimensions = [ width, height ], - origin = [ 0, 0 ]; + this.canvas = canvas; + this.c2d = canvas.getContext('2d'); + this.width = canvas.width; + this.height = canvas.height; + this.dimensions = [ this.width, this.height ]; + this.origin = [ 0, 0 ]; - // 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) { + if (!this.c2d) { throw new Error("Canvas 2d API unavailable."); } - - return { - /** - * Clear the chart. - * @memberof platform/features/plot.Canvas2DChart# - */ - 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 - * @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; - - 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 - * @memberof platform/features/plot.Canvas2DChart# - */ - 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); - } - }; } + // Convert from logical to physical x coordinates + Canvas2DChart.prototype.x = function (v) { + return ((v - this.origin[0]) / this.dimensions[0]) * this.width; + }; + + // Convert from logical to physical y coordinates + Canvas2DChart.prototype.y = function (v) { + return this.height - + ((v - this.origin[1]) / this.dimensions[1]) * this.height; + }; + + // Set the color to be used for drawing operations + Canvas2DChart.prototype.setColor = function (color) { + var mappedColor = color.map(function (c, i) { + return i < 3 ? Math.floor(c * 255) : (c); + }).join(','); + this.c2d.strokeStyle = "rgba(" + mappedColor + ")"; + this.c2d.fillStyle = "rgba(" + mappedColor + ")"; + }; + + + Canvas2DChart.prototype.clear = function () { + var canvas = this.canvas; + this.width = canvas.width; + this.height = canvas.height; + this.c2d.clearRect(0, 0, this.width, this.height); + }; + + Canvas2DChart.prototype.setDimensions = function (newDimensions, newOrigin) { + this.dimensions = newDimensions; + this.origin = newOrigin; + }; + + Canvas2DChart.prototype.drawLine = function (buf, color, points) { + var i; + + this.setColor(color); + + // Configure context to draw two-pixel-thick lines + this.c2d.lineWidth = 2; + + // Start a new path... + if (buf.length > 1) { + this.c2d.beginPath(); + this.c2d.moveTo(this.x(buf[0]), this.y(buf[1])); + } + + // ...and add points to it... + for (i = 2; i < points * 2; i = i + 2) { + this.c2d.lineTo(this.x(buf[i]), this.y(buf[i + 1])); + } + + // ...before finally drawing it. + this.c2d.stroke(); + }; + + Canvas2DChart.prototype.drawSquare = function (min, max, color) { + var x1 = this.x(min[0]), + y1 = this.y(min[1]), + w = this.x(max[0]) - x1, + h = this.y(max[1]) - y1; + + this.setColor(color); + this.c2d.fillRect(x1, y1, w, h); + }; + return Canvas2DChart; } ); diff --git a/platform/features/plot/src/GLChart.js b/platform/features/plot/src/GLChart.js index a390cf1153..6dc7934fa5 100644 --- a/platform/features/plot/src/GLChart.js +++ b/platform/features/plot/src/GLChart.js @@ -51,6 +51,7 @@ define( * * @memberof platform/features/plot * @constructor + * @implements {platform/features/plot.Chart} * @param {CanvasElement} canvas the canvas object to render upon * @throws {Error} an error is thrown if WebGL is unavailable. */ @@ -62,8 +63,7 @@ define( aVertexPosition, uColor, uDimensions, - uOrigin, - buffer; + uOrigin; // Ensure a context was actually available before proceeding if (!gl) { @@ -94,7 +94,7 @@ define( gl.enableVertexAttribArray(aVertexPosition); // 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 gl.lineWidth(2.0); @@ -103,79 +103,59 @@ define( 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. - * @memberof platform/features/plot.GLChart# - */ - 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 - * @memberof platform/features/plot.GLChart# - */ - 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 - * @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); - } - }; + this.gl = gl; + this.aVertexPosition = aVertexPosition; + this.uColor = uColor; + this.uDimensions = uDimensions; + this.uOrigin = uOrigin; } + + // Utility function to handle drawing of a buffer; + // drawType will determine whether this is a box, line, etc. + GLChart.prototype.doDraw = function (drawType, buf, color, points) { + var gl = this.gl; + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + 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 + // 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); + }; + + + GLChart.prototype.setDimensions = function (dimensions, origin) { + var gl = this.gl; + if (dimensions && dimensions.length > 0 && + origin && origin.length > 0) { + gl.uniform2fv(this.uDimensions, dimensions); + gl.uniform2fv(this.uOrigin, origin); + } + }; + + 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; } ); diff --git a/platform/features/plot/src/MCTChart.js b/platform/features/plot/src/MCTChart.js index 951ca3532b..e8c9db74e4 100644 --- a/platform/features/plot/src/MCTChart.js +++ b/platform/features/plot/src/MCTChart.js @@ -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; } ); diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index da8dcfb168..a54fff83dd 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -61,15 +61,11 @@ define( throttle, PLOT_FIXED_DURATION ) { - var subPlotFactory = new SubPlotFactory(telemetryFormatter), - modeOptions = new PlotModeOptions([], subPlotFactory), - subplots = [], + var self = this, + subPlotFactory = new SubPlotFactory(telemetryFormatter), cachedObjects = [], - limitTracker, updater, - handle, - scheduleUpdate, - domainOffset; + handle; // Populate the scope with axis information (specifically, options // available for each axis.) @@ -91,18 +87,13 @@ define( function setupModes(telemetryObjects) { if (cachedObjects !== telemetryObjects) { cachedObjects = telemetryObjects; - modeOptions = new PlotModeOptions( + self.modeOptions = new PlotModeOptions( telemetryObjects || [], subPlotFactory ); } } - // Update all sub-plots - function update() { - scheduleUpdate(); - } - // Reinstantiate the plot updater (e.g. because we have a // new subscription.) This will clear the plot. function recreateUpdater() { @@ -112,7 +103,7 @@ define( ($scope.axes[1].active || {}).key, PLOT_FIXED_DURATION ); - limitTracker = new PlotLimitTracker( + self.limitTracker = new PlotLimitTracker( handle, ($scope.axes[1].active || {}).key ); @@ -125,19 +116,19 @@ define( } if (updater) { updater.update(); - modeOptions.getModeHandler().plotTelemetry(updater); + self.modeOptions.getModeHandler().plotTelemetry(updater); } - if (limitTracker) { - limitTracker.update(); + if (self.limitTracker) { + self.limitTracker.update(); } - update(); + self.update(); } // Display new historical data as it becomes available function addHistoricalData(domainObject, series) { updater.addHistorical(domainObject, series); - modeOptions.getModeHandler().plotTelemetry(updater); - update(); + self.modeOptions.getModeHandler().plotTelemetry(updater); + self.update(); } // Issue a new request for historical telemetry @@ -174,116 +165,119 @@ 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 $scope.$watch('domainObject', subscribe); // Unsubscribe when the plot is destroyed $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 - * for plotting the trace at the specified index. - * @param {number} index the index of the trace - * @returns {string} the color, in #RRGGBB form - * @memberof platform/features/plot.PlotController# - */ - getColor: function (index) { - return PlotPalette.getStringColor(index); - }, - /** - * Check if the plot is zoomed or panned out - * of its default state (to determine whether back/unzoom - * controls should be shown) - * @returns {boolean} true if not in default state - * @memberof platform/features/plot.PlotController# - */ - isZoomed: function () { - return modeOptions.getModeHandler().isZoomed(); - }, - /** - * Undo the most recent pan/zoom change and restore - * the prior state. - * @memberof platform/features/plot.PlotController# - */ - stepBackPanZoom: function () { - return modeOptions.getModeHandler().stepBackPanZoom(); - }, - /** - * Undo all pan/zoom changes and restore the initial state. - * @memberof platform/features/plot.PlotController# - */ - unzoom: function () { - return modeOptions.getModeHandler().unzoom(); - }, - /** - * Get the mode options (Stacked/Overlaid) that are applicable - * for this plot. - * @memberof platform/features/plot.PlotController# - */ - getModeOptions: function () { - return modeOptions.getModeOptions(); - }, - /** - * Get the current mode that is applicable to this plot. This - * will include key, name, and glyph fields. - * @memberof platform/features/plot.PlotController# - */ - getMode: function () { - return modeOptions.getMode(); - }, - /** - * Set the mode which should be active in this plot. - * @param mode one of the mode options returned from - * getModeOptions() - * @memberof platform/features/plot.PlotController# - */ - setMode: function (mode) { - modeOptions.setMode(mode); - updateValues(); - }, - /** - * Get all individual plots contained within this Plot view. - * (Multiple may be contained when in Stacked mode). - * @returns {SubPlot[]} all subplots in this Plot view - * @memberof platform/features/plot.PlotController# - */ - getSubPlots: function () { - return modeOptions.getModeHandler().getSubPlots(); - }, - /** - * Get the CSS class to apply to the legend for this domain - * object; this will reflect limit state. - * @returns {string} the CSS class - * @memberof platform/features/plot.PlotController# - */ - getLegendClass: function (telemetryObject) { - return limitTracker && - limitTracker.getLegendClass(telemetryObject); - }, - /** - * Explicitly update all plots. - * @memberof platform/features/plot.PlotController# - */ - update: update, - /** - * Check if a request is pending (to show the wait spinner) - * @memberof platform/features/plot.PlotController# - */ - isRequestPending: function () { - // Placeholder; this should reflect request state - // when requesting historical telemetry - return false; - } - }; } + /** + * Get the color (as a style-friendly string) to use + * for plotting the trace at the specified index. + * @param {number} index the index of the trace + * @returns {string} the color, in #RRGGBB form + */ + PlotController.prototype.getColor = function (index) { + return PlotPalette.getStringColor(index); + }; + + /** + * Check if the plot is zoomed or panned out + * of its default state (to determine whether back/unzoom + * controls should be shown) + * @returns {boolean} true if not in default state + */ + PlotController.prototype.isZoomed = function () { + return this.modeOptions.getModeHandler().isZoomed(); + }; + + /** + * Undo the most recent pan/zoom change and restore + * the prior state. + */ + PlotController.prototype.stepBackPanZoom = function () { + return this.modeOptions.getModeHandler().stepBackPanZoom(); + }; + + /** + * Undo all pan/zoom changes and restore the initial state. + */ + PlotController.prototype.unzoom = function () { + return this.modeOptions.getModeHandler().unzoom(); + }; + + /** + * Get the mode options (Stacked/Overlaid) that are applicable + * for this plot. + */ + PlotController.prototype.getModeOptions = function () { + return this.modeOptions.getModeOptions(); + }; + + /** + * Get the current mode that is applicable to this plot. This + * will include key, name, and glyph fields. + */ + PlotController.prototype.getMode = function () { + return this.modeOptions.getMode(); + }; + + /** + * Set the mode which should be active in this plot. + * @param mode one of the mode options returned from + * getModeOptions() + */ + PlotController.prototype.setMode = function (mode) { + this.modeOptions.setMode(mode); + this.updateValues(); + }; + + /** + * Get all individual plots contained within this Plot view. + * (Multiple may be contained when in Stacked mode). + * @returns {SubPlot[]} all subplots in this Plot view + */ + PlotController.prototype.getSubPlots = function () { + return this.modeOptions.getModeHandler().getSubPlots(); + }; + + /** + * Get the CSS class to apply to the legend for this domain + * object; this will reflect limit state. + * @returns {string} the CSS class + */ + PlotController.prototype.getLegendClass = function (telemetryObject) { + return this.limitTracker && + this.limitTracker.getLegendClass(telemetryObject); + }; + + /** + * Explicitly update all plots. + */ + PlotController.prototype.update = function () { + this.scheduleUpdate(); + }; + + /** + * Check if a request is pending (to show the wait spinner) + */ + PlotController.prototype.isRequestPending = function () { + // Placeholder; this should reflect request state + // when requesting historical telemetry + return false; + }; + return PlotController; } ); diff --git a/platform/features/plot/src/SubPlot.js b/platform/features/plot/src/SubPlot.js index 9cd8b829cf..06b7f7bb0f 100644 --- a/platform/features/plot/src/SubPlot.js +++ b/platform/features/plot/src/SubPlot.js @@ -50,141 +50,278 @@ define( // 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, - panStart, - panStartBounds, - subPlotBounds, - hoverCoordinates, - isHovering = false; + this.telemetryObjects = telemetryObjects; + this.domainTicks = []; + this.rangeTicks = []; + this.formatter = telemetryFormatter; + this.draw = {}; + this.hovering = false; + this.panZoomStack = panZoomStack; + + // Start with the right initial drawing bounds, + // tick marks + this.updateDrawingBounds(); + this.updateTicks(); + } + + // 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. + SubPlot.prototype.mousePositionToDomainRange = function (mousePosition) { + return new PlotPosition( + mousePosition.x, + mousePosition.y, + mousePosition.width, + mousePosition.height, + this.panZoomStack + ).getPosition(); + }; + + // Utility function to get the mouse position (in x,y + // pixel coordinates in the canvas area) from a mouse + // event object. + SubPlot.prototype.toMousePosition = function ($event) { + var bounds = this.subPlotBounds; + + 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.) + SubPlot.prototype.toDisplayable = function (position) { + 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); + formatter.formatRangeValue : + formatter.formatDomainValue)(v); } - // Utility function for filtering out empty strings. - function isNonEmpty(v) { - return typeof v === 'string' && v !== ""; + this.hoverCoordinates = this.mousePosition && + this.mousePositionToDomainRange(this.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) + SubPlot.prototype.updateMarqueeBox = function () { + // Express this as a box in the draw object, which + // is passed to an mct-chart in the template for rendering. + this.draw.boxes = this.marqueeStart ? + [{ + start: this.toDisplayable( + this.mousePositionToDomainRange(this.marqueeStart) + ), + end: this.toDisplayable( + this.mousePositionToDomainRange(this.mousePosition) + ), + color: [1, 1, 1, 0.5 ] + }] : undefined; + }; + + // Update the bounds (origin and dimensions) of the drawing area. + SubPlot.prototype.updateDrawingBounds = function () { + var panZoom = this.panZoomStack.getPanZoom(); + + // Communicate pan-zoom state from stack to the draw object + // which is passed to mct-chart in the template. + this.draw.dimensions = panZoom.dimensions; + this.draw.origin = [ + panZoom.origin[0] - this.domainOffset, + panZoom.origin[1] + ]; + }; + + // Update tick marks in scope. + SubPlot.prototype.updateTicks = function () { + var tickGenerator = + new PlotTickGenerator(this.panZoomStack, this.formatter); + + this.domainTicks = + tickGenerator.generateDomainTicks(DOMAIN_TICKS); + this.rangeTicks = + tickGenerator.generateRangeTicks(RANGE_TICKS); + }; + + SubPlot.prototype.updatePan = function () { + var start, current, delta, nextOrigin; + + // Clear the previous panning pan-zoom state + this.panZoomStack.popPanZoom(); + + // Calculate what the new resulting pan-zoom should be + start = this.mousePositionToDomainRange( + this.panStart, + this.panZoomStack + ); + current = this.mousePositionToDomainRange( + this.mousePosition, + this.panZoomStack + ); + + delta = [ current[0] - start[0], current[1] - start[1] ]; + nextOrigin = [ + this.panStartBounds.origin[0] - delta[0], + this.panStartBounds.origin[1] - delta[1] + ]; + + // ...and push a new one at the current mouse position + 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(); } - - // 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(); + if (this.panStart) { + this.updatePan(); + this.updateDrawingBounds(); + this.updateTicks(); } + }; - // 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 bounds = subPlotBounds; - - return { - x: $event.clientX - bounds.left, - y: $event.clientY - bounds.top, - width: bounds.width, - height: bounds.height - }; + /** + * 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(); } - - // 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] ]; + if (this.panStart) { + this.updatePan(); + this.updateDrawingBounds(); + this.updateTicks(); } + }; - - // 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); - } - - function updatePan() { - var start, current, delta, nextOrigin; - - // Clear the previous panning pan-zoom state - panZoomStack.popPanZoom(); - - // Calculate what the new resulting pan-zoom should be - start = mousePositionToDomainRange(panStart); - current = mousePositionToDomainRange(mousePosition); - delta = [ current[0] - start[0], current[1] - start[1] ]; - nextOrigin = [ - panStartBounds.origin[0] - delta[0], - panStartBounds.origin[1] - delta[1] - ]; - - // ...and push a new one at the current mouse position - panZoomStack.pushPanZoom(nextOrigin, panStartBounds.dimensions); + /** + * 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. 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), + var a = self.mousePositionToDomainRange(start), + b = self.mousePositionToDomainRange(end), origin = [ Math.min(a[0], b[0]), Math.min(a[1], b[1]) @@ -197,196 +334,68 @@ define( // 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); + self.panZoomStack.pushPanZoom(origin, dimensions); // Make sure tick marks reflect new bounds - updateTicks(); + self.updateTicks(); } } - // Start with the right initial drawing bounds, - // tick marks - updateDrawingBounds(); - updateTicks(); + this.mousePosition = this.toMousePosition($event); + this.subPlotBounds = undefined; + if (this.marqueeStart) { + marqueeZoom(this.marqueeStart, this.mousePosition); + this.marqueeStart = undefined; + this.updateMarqueeBox(); + this.updateDrawingBounds(); + this.updateTicks(); + } + if (this.panStart) { + // End panning + this.panStart = undefined; + this.panStartBounds = undefined; + } + }; - 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 - * @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) { - 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 - panStart = undefined; - panStartBounds = undefined; - } - }, - /** - * Update the drawing bounds, marquee box, and - * tick marks for this subplot. - * @memberof platform/features/plot.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 - * @memberof platform/features/plot.SubPlot# - */ - 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 - * @memberof platform/features/plot.SubPlot# - */ - isHovering: function (state) { - if (state !== undefined) { - isHovering = state; - } - return isHovering; - } - }; - } + /** + * Update the drawing bounds, marquee box, and + * tick marks for this subplot. + */ + SubPlot.prototype.update = function () { + this.updateDrawingBounds(); + this.updateMarqueeBox(); + this.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 + */ + SubPlot.prototype.setDomainOffset = function (value) { + this.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 + */ + SubPlot.prototype.isHovering = function (state) { + if (state !== undefined) { + this.hovering = state; + } + return this.hovering; + }; return SubPlot; diff --git a/platform/features/plot/src/SubPlotFactory.js b/platform/features/plot/src/SubPlotFactory.js index 33eb8fff65..6de318f106 100644 --- a/platform/features/plot/src/SubPlotFactory.js +++ b/platform/features/plot/src/SubPlotFactory.js @@ -35,28 +35,26 @@ define( * @constructor */ function SubPlotFactory(telemetryFormatter) { - return { - /** - * Instantiate a new sub-plot. - * @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 - * @returns {SubPlot} the instantiated sub-plot - * @method - * @memberof SubPlotFactory - * @memberof platform/features/plot.SubPlotFactory# - */ - createSubPlot: function (telemetryObjects, panZoomStack) { - return new SubPlot( - telemetryObjects, - panZoomStack, - telemetryFormatter - ); - } - }; + this.telemetryFormatter = telemetryFormatter; } + /** + * Instantiate a new sub-plot. + * @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 + * @returns {SubPlot} the instantiated sub-plot + * @method + */ + SubPlotFactory.prototype.createSubPlot = function (telemetryObjects, panZoomStack) { + return new SubPlot( + telemetryObjects, + panZoomStack, + this.telemetryFormatter + ); + }; + return SubPlotFactory; } diff --git a/platform/features/plot/src/modes/PlotModeOptions.js b/platform/features/plot/src/modes/PlotModeOptions.js index efeea30fb5..bd03129698 100644 --- a/platform/features/plot/src/modes/PlotModeOptions.js +++ b/platform/features/plot/src/modes/PlotModeOptions.js @@ -30,15 +30,53 @@ define( key: "stacked", name: "Stacked", glyph: "m", - factory: PlotStackMode + Constructor: PlotStackMode }, OVERLAID = { key: "overlaid", name: "Overlaid", 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) * are applicable in a given plot view, maintains current @@ -46,73 +84,74 @@ define( * different behaviors associated with these modes. * @memberof platform/features/plot * @constructor - * @param {DomainObject[]} the telemetry objects being + * @param {DomainObject[]} telemetryObjects the telemetry objects being * represented in this plot view + * @param {platform/features/plot.SubPlotFactory} subPlotFactory a + * factory for creating sub-plots */ function PlotModeOptions(telemetryObjects, subPlotFactory) { - var options = telemetryObjects.length > 1 ? - [ OVERLAID, STACKED ] : [ OVERLAID ], - mode = options[0], // Initial selection (overlaid) - modeHandler; - - return { - /** - * Get a handler for the current mode. This will handle - * plotting telemetry, providing subplots for the template, - * and view-level interactions with pan-zoom state. - * @returns {PlotOverlayMode|PlotStackMode} a handler - * for the current mode - * @memberof platform/features/plot.PlotModeOptions# - */ - getModeHandler: function () { - // Lazily initialize - if (!modeHandler) { - modeHandler = mode.factory( - telemetryObjects, - subPlotFactory - ); - } - return modeHandler; - }, - /** - * Get all mode options available for each plot. Each - * mode contains a `name` and `glyph` field suitable - * for display in a template. - * @return {Array} the available modes - * @memberof platform/features/plot.PlotModeOptions# - */ - getModeOptions: function () { - return options; - }, - /** - * Get the plotting mode option currently in use. - * This will be one of the elements returned from - * `getModeOptions`. - * @return {object} the current mode - * @memberof platform/features/plot.PlotModeOptions# - */ - getMode: function () { - return mode; - }, - /** - * Set the plotting mode option to use. - * The passed argument must be one of the options - * returned by `getModeOptions`. - * @param {object} option one of the plot mode options - * from `getModeOptions` - * @memberof platform/features/plot.PlotModeOptions# - */ - setMode: function (option) { - if (mode !== option) { - mode = option; - // Clear the existing mode handler, so it - // can be instantiated next time it's needed. - modeHandler = undefined; - } - } - }; + this.options = telemetryObjects.length > 1 ? + [ OVERLAID, STACKED ] : [ OVERLAID ]; + this.mode = this.options[0]; // Initial selection (overlaid) + this.telemetryObjects = telemetryObjects; + this.subPlotFactory = subPlotFactory; } + /** + * Get a handler for the current mode. This will handle + * plotting telemetry, providing subplots for the template, + * and view-level interactions with pan-zoom state. + * @returns {PlotOverlayMode|PlotStackMode} a handler + * for the current mode + */ + PlotModeOptions.prototype.getModeHandler = function () { + // Lazily initialize + if (!this.modeHandler) { + this.modeHandler = new this.mode.Constructor( + this.telemetryObjects, + this.subPlotFactory + ); + } + return this.modeHandler; + }; + + /** + * Get all mode options available for each plot. Each + * mode contains a `name` and `glyph` field suitable + * for display in a template. + * @return {Array} the available modes + */ + PlotModeOptions.prototype.getModeOptions = function () { + return this.options; + }; + + /** + * Get the plotting mode option currently in use. + * This will be one of the elements returned from + * `getModeOptions`. + * @return {*} the current mode + */ + PlotModeOptions.prototype.getMode = function () { + return this.mode; + }; + + /** + * Set the plotting mode option to use. + * The passed argument must be one of the options + * returned by `getModeOptions`. + * @param {object} option one of the plot mode options + * from `getModeOptions` + */ + PlotModeOptions.prototype.setMode = function (option) { + if (this.mode !== option) { + this.mode = option; + // Clear the existing mode handler, so it + // can be instantiated next time it's needed. + this.modeHandler = undefined; + } + }; + + return PlotModeOptions; } ); diff --git a/platform/features/plot/src/modes/PlotOverlayMode.js b/platform/features/plot/src/modes/PlotOverlayMode.js index 6baad8a546..809d800ef2 100644 --- a/platform/features/plot/src/modes/PlotOverlayMode.js +++ b/platform/features/plot/src/modes/PlotOverlayMode.js @@ -31,81 +31,59 @@ define( * is one sub-plot which contains all plotted objects. * @memberof platform/features/plot * @constructor + * @implements {platform/features/plot.PlotModeHandler} * @param {DomainObject[]} the domain objects to be plotted */ function PlotOverlayMode(telemetryObjects, subPlotFactory) { - var domainOffset, - panZoomStack = new PlotPanZoomStack([], []), - subplot = subPlotFactory.createSubPlot( - telemetryObjects, - panZoomStack - ), - subplots = [ subplot ]; + this.panZoomStack = new PlotPanZoomStack([], []); + this.subplot = subPlotFactory.createSubPlot( + telemetryObjects, + this.panZoomStack + ); + this.subplots = [ this.subplot ]; + } - function plotTelemetry(prepared) { - // Fit to the boundaries of the data, but don't - // override any user-initiated pan-zoom changes. - panZoomStack.setBasePanZoom( - prepared.getOrigin(), - prepared.getDimensions() - ); + PlotOverlayMode.prototype.plotTelemetry = function (updater) { + // Fit to the boundaries of the data, but don't + // override any user-initiated pan-zoom changes. + this.panZoomStack.setBasePanZoom( + updater.getOrigin(), + updater.getDimensions() + ); - // Track the domain offset, used to bias domain values - // to minimize loss of precision when converted to 32-bit - // floating point values for display. - subplot.setDomainOffset(prepared.getDomainOffset()); + // Track the domain offset, used to bias domain values + // to minimize loss of precision when converted to 32-bit + // floating point values for display. + this.subplot.setDomainOffset(updater.getDomainOffset()); - // Draw the buffers. Select color by index. - subplot.getDrawingObject().lines = prepared.getLineBuffers().map(function (buf, i) { + // Draw the buffers. Select color by index. + this.subplot.getDrawingObject().lines = + updater.getLineBuffers().map(function (buf, i) { return { buffer: buf.getBuffer(), color: PlotPalette.getFloatColor(i), 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; } diff --git a/platform/features/plot/src/modes/PlotStackMode.js b/platform/features/plot/src/modes/PlotStackMode.js index c64f3fe286..b20e9b9e32 100644 --- a/platform/features/plot/src/modes/PlotStackMode.js +++ b/platform/features/plot/src/modes/PlotStackMode.js @@ -31,99 +31,76 @@ define( * is one sub-plot for each plotted object. * @memberof platform/features/plot * @constructor + * @implements {platform/features/plot.PlotModeHandler} * @param {DomainObject[]} the domain objects to be plotted */ function PlotStackMode(telemetryObjects, subPlotFactory) { - var domainOffset, - panZoomStackGroup = - new PlotPanZoomStackGroup(telemetryObjects.length), - subplots = telemetryObjects.map(function (telemetryObject, i) { + var self = this; + + this.panZoomStackGroup = + new PlotPanZoomStackGroup(telemetryObjects.length); + + this.subplots = telemetryObjects.map(function (telemetryObject, i) { return subPlotFactory.createSubPlot( [telemetryObject], - panZoomStackGroup.getPanZoomStack(i) + self.panZoomStackGroup.getPanZoomStack(i) ); }); - - function plotTelemetryTo(subplot, prepared, index) { - var buffer = prepared.getLineBuffers()[index]; - - // Track the domain offset, used to bias domain values - // to minimize loss of precision when converted to 32-bit - // floating point values for display. - subplot.setDomainOffset(prepared.getDomainOffset()); - - // Draw the buffers. Always use the 0th color, because there - // is one line per plot. - subplot.getDrawingObject().lines = [{ - buffer: buffer.getBuffer(), - color: PlotPalette.getFloatColor(0), - points: buffer.getLength() - }]; - } - - function plotTelemetry(prepared) { - // Fit to the boundaries of the data, but don't - // override any user-initiated pan-zoom changes. - panZoomStackGroup.setBasePanZoom( - prepared.getOrigin(), - prepared.getDimensions() - ); - - subplots.forEach(function (subplot, index) { - plotTelemetryTo(subplot, prepared, index); - }); - } - - 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(); - }); - } - }; } + PlotStackMode.prototype.plotTelemetryTo = function (subplot, prepared, index) { + var buffer = prepared.getLineBuffers()[index]; + + // Track the domain offset, used to bias domain values + // to minimize loss of precision when converted to 32-bit + // floating point values for display. + subplot.setDomainOffset(prepared.getDomainOffset()); + + // Draw the buffers. Always use the 0th color, because there + // is one line per plot. + subplot.getDrawingObject().lines = [{ + buffer: buffer.getBuffer(), + color: PlotPalette.getFloatColor(0), + points: buffer.getLength() + }]; + }; + + PlotStackMode.prototype.plotTelemetry = function (prepared) { + var self = this; + // Fit to the boundaries of the data, but don't + // override any user-initiated pan-zoom changes. + this.panZoomStackGroup.setBasePanZoom( + prepared.getOrigin(), + prepared.getDimensions() + ); + + this.subplots.forEach(function (subplot, 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 PlotStackMode; } ); diff --git a/platform/features/plot/src/policies/PlotViewPolicy.js b/platform/features/plot/src/policies/PlotViewPolicy.js index 26a64c0101..1a2793aaa7 100644 --- a/platform/features/plot/src/policies/PlotViewPolicy.js +++ b/platform/features/plot/src/policies/PlotViewPolicy.js @@ -28,39 +28,38 @@ define( /** * Policy preventing the Plot view from being made available for * domain objects which have non-numeric telemetry. - * @implements {Policy} + * @implements {Policy.} * @constructor * @memberof platform/features/plot */ function PlotViewPolicy() { - function hasImageTelemetry(domainObject) { - var telemetry = domainObject && - domainObject.getCapability('telemetry'), - metadata = telemetry ? telemetry.getMetadata() : {}, - ranges = metadata.ranges || []; + } - // Generally, we want to allow Plot for telemetry-providing - // objects (most telemetry is plottable.) We only want to - // suppress this for telemetry which only has explicitly - // non-numeric values. - return ranges.length === 0 || ranges.some(function (range) { + function hasNumericTelemetry(domainObject) { + var telemetry = domainObject && + domainObject.getCapability('telemetry'), + metadata = telemetry ? telemetry.getMetadata() : {}, + ranges = metadata.ranges || []; + + // Generally, we want to allow Plot for telemetry-providing + // objects (most telemetry is plottable.) We only want to + // suppress this for telemetry which only has explicitly + // non-numeric values. + return ranges.length === 0 || ranges.some(function (range) { // Assume format is numeric if it is undefined // (numeric telemetry is the common case) return range.format === undefined || - range.format === 'number'; + range.format === 'number'; }); + } + + PlotViewPolicy.prototype.allow = function (view, domainObject) { + if (view.key === 'plot') { + return hasNumericTelemetry(domainObject); } - return { - allow: function (view, domainObject) { - if (view.key === 'plot') { - return hasImageTelemetry(domainObject); - } - - return true; - } - }; - } + return true; + }; return PlotViewPolicy; } diff --git a/platform/features/plot/test/PlotControllerSpec.js b/platform/features/plot/test/PlotControllerSpec.js index 32529b0f3d..e6c79b4e54 100644 --- a/platform/features/plot/test/PlotControllerSpec.js +++ b/platform/features/plot/test/PlotControllerSpec.js @@ -39,6 +39,12 @@ define( mockSeries, controller; + function bind(method, thisObj) { + return function () { + return method.apply(thisObj, arguments); + }; + } + beforeEach(function () { mockScope = jasmine.createSpyObj( @@ -196,13 +202,13 @@ define( }); 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 () { - expect(controller.isZoomed).not.toThrow(); - expect(controller.stepBackPanZoom).not.toThrow(); - expect(controller.unzoom).not.toThrow(); + expect(bind(controller.isZoomed, controller)).not.toThrow(); + expect(bind(controller.stepBackPanZoom, controller)).not.toThrow(); + expect(bind(controller.unzoom, controller)).not.toThrow(); }); it("indicates if a request is pending", function () {