From 622f1f8be7527069dda0d2b8ec73b522f0b20701 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 2 Dec 2014 14:38:03 -0800 Subject: [PATCH] [Plot] Add JSDoc Add inline comments to Plot scripts. WTD-533. --- platform/features/plot/src/GLChart.js | 55 +++++++- platform/features/plot/src/MCTChart.js | 54 +++++++- platform/features/plot/src/PlotController.js | 118 +++++++++++++++--- .../features/plot/src/elements/PlotAxis.js | 31 +++++ .../plot/src/elements/PlotFormatter.js | 20 +++ .../features/plot/src/elements/PlotPalette.js | 39 ++++++ .../plot/src/elements/PlotPanZoomStack.js | 78 ++++++++++++ .../plot/src/elements/PlotPosition.js | 44 ++++++- .../plot/src/elements/PlotPreparer.js | 57 ++++++++- .../plot/src/elements/PlotTickGenerator.js | 25 ++++ 10 files changed, 494 insertions(+), 27 deletions(-) diff --git a/platform/features/plot/src/GLChart.js b/platform/features/plot/src/GLChart.js index c7c576f7e3..284333beaf 100644 --- a/platform/features/plot/src/GLChart.js +++ b/platform/features/plot/src/GLChart.js @@ -1,4 +1,4 @@ -/*global define,Promise,Float32Array*/ +/*global define,Float32Array*/ /** * Module defining GLPlot. Created by vwoeltje on 11/12/14. @@ -8,6 +8,7 @@ define( function () { "use strict"; + // WebGL shader sources (for drawing plain colors) var FRAGMENT_SHADER = [ "precision mediump float;", "uniform vec4 uColor;", @@ -24,6 +25,13 @@ define( "}" ].join('\n'); + /** + * Create a new chart which uses WebGL for rendering. + * + * @constructor + * @param {CanvasElement} canvas the canvas object to render upon + * @throws {Error} an error is thrown if WebGL is unavailable. + */ function GLChart(canvas) { var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"), vertexShader, @@ -35,6 +43,7 @@ define( uOrigin, buffer; + // Ensure a context was actually available before proceeding if (!gl) { throw new Error("WebGL unavailable."); } @@ -47,25 +56,33 @@ define( gl.shaderSource(fragmentShader, FRAGMENT_SHADER); gl.compileShader(fragmentShader); + // Assemble vertex/fragment shaders into programs program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); gl.useProgram(program); + // Get locations for attribs/uniforms from the + // shader programs (to pass values into shaders at draw-time) aVertexPosition = gl.getAttribLocation(program, "aVertexPosition"); - gl.enableVertexAttribArray(aVertexPosition); - uColor = gl.getUniformLocation(program, "uColor"); uDimensions = gl.getUniformLocation(program, "uDimensions"); uOrigin = gl.getUniformLocation(program, "uOrigin"); + gl.enableVertexAttribArray(aVertexPosition); + // Create a buffer to holds points which will be drawn buffer = gl.createBuffer(); + // Use a line width of 2.0 for legibility gl.lineWidth(2.0); + + // Enable blending, for smoothness gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + // Utility function to handle drawing of a buffer; + // drawType will determine whether this is a box, line, etc. function doDraw(drawType, buf, color, points) { gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, buf, gl.DYNAMIC_DRAW); @@ -75,23 +92,51 @@ define( } return { + /** + * Clear the chart. + */ clear: function () { gl.viewport(0, 0, canvas.width, canvas.height); gl.clear(gl.COLOR_BUFFER_BIT + gl.DEPTH_BUFFER_BIT); }, + /** + * Set the logical boundaries of the chart. + * @param {number[]} dimensions the horizontal and + * vertical dimensions of the chart + * @param {number[]} origin the horizontal/vertical + * origin of the chart + */ setDimensions: function (dimensions, origin) { gl.uniform2fv(uDimensions, dimensions); gl.uniform2fv(uOrigin, origin); }, + /** + * Draw the supplied buffer as a line strip (a sequence + * of line segments), in the chosen color. + * @param {Float32Array} buf the line strip to draw, + * in alternating x/y positions + * @param {number[]} color the color to use when drawing + * the line, as an RGBA color where each element + * is in the range of 0.0-1.0 + * @param {number} points the number of points to draw + */ drawLine: function (buf, color, points) { doDraw(gl.LINE_STRIP, buf, color, points); }, + /** + * Draw a rectangle extending from one corner to another, + * in the chosen color. + * @param {number[]} min the first corner of the rectangle + * @param {number[]} max the opposite corner + * @param {number[]} color the color to use when drawing + * the rectangle, as an RGBA color where each element + * is in the range of 0.0-1.0 + */ drawSquare: function (min, max, color) { doDraw(gl.TRIANGLE_FAN, new Float32Array( min.concat([min[0], max[1]]).concat(max).concat([max[0], min[1]]) ), color, 4); - }, - gl: gl + } }; } return GLChart; diff --git a/platform/features/plot/src/MCTChart.js b/platform/features/plot/src/MCTChart.js index 89d0def6c3..5c01bc6efc 100644 --- a/platform/features/plot/src/MCTChart.js +++ b/platform/features/plot/src/MCTChart.js @@ -1,4 +1,4 @@ -/*global define,Promise*/ +/*global define*/ /** * Module defining MCTChart. Created by vwoeltje on 11/12/14. @@ -11,6 +11,34 @@ define( var TEMPLATE = ""; /** + * The mct-chart directive provides a canvas element which can be + * drawn upon, to support Plot view and similar visualizations. + * + * This directive takes one attribute, "draw", which is an Angular + * expression which will be two-way bound to a drawing object. This + * drawing object should contain: + * + * * `dimensions`: An object describing the logical bounds of the + * drawable area, containing two fields: + * * `origin`: The position, in logical coordinates, of the + * lower-left corner of the chart area. A two-element array. + * * `dimensions`: A two-element array containing the width + * and height of the chart area, in logical coordinates. + * * `lines`: An array of lines to be drawn, where each line is + * expressed as an object containing: + * * `buffer`: A Float32Array containing points in the line, + * in logical coordinate, in sequential x/y pairs. + * * `color`: The color of the line, as a four-element RGBA + * array, where each element is in the range of 0.0-1.0 + * * `points`: The number of points in the line. + * * `boxes`: An array of rectangles to draw in the chart area + * (used for marquee zoom). Each is an object containing: + * * `start`: The first corner of the rectangle (as a two-element + * array, logical coordinates) + * * `end`: The opposite corner of the rectangle (again, as a + * two-element array) + * * `color`: The color of the box, as a four-element RGBA + * array, where each element is in the range of 0.0-1.0 * * @constructor */ @@ -30,20 +58,29 @@ define( return; } + // Handle drawing, based on contents of the "draw" object + // in scope function doDraw(draw) { + // Ensure canvas context has same resolution + // as canvas element canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight; + + // Clear previous contents chart.clear(); + // Nothing to draw if no draw object defined if (!draw) { return; } + // Set logical boundaries for the chart chart.setDimensions( draw.dimensions || [1, 1], draw.origin || [0, 0] ); + // Draw line segments (draw.lines || []).forEach(function (line) { chart.drawLine( line.buffer, @@ -52,6 +89,7 @@ define( ); }); + // Draw boxes (e.g. marquee zoom rect) (draw.boxes || []).forEach(function (box) { chart.drawSquare( box.start, @@ -62,6 +100,9 @@ define( } + // Issue a drawing call, if-and-only-if canvas size + // has changed. This will be called on a timer, since + // there is no event to depend on. function drawIfResized() { if (canvas.width !== canvas.offsetWidth || canvas.height !== canvas.offsetHeight) { @@ -69,14 +110,25 @@ define( } } + // Check for resize, on a timer $interval(drawIfResized, 1000); + + // Watch "draw" for external changes to the set of + // things to be drawn. scope.$watchCollection("draw", doDraw); } return { + // Apply directive only to elements restrict: "E", + + // Template to use (a canvas element) template: TEMPLATE, + + // Link function; set up scope link: linkChart, + + // Initial, isolate scope for the directive scope: { draw: "=" } }; } diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index c18f60b4a1..f1fee47f31 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -1,4 +1,4 @@ -/*global define,moment,Promise*/ +/*global define*/ /** * Module defining PlotController. Created by vwoeltje on 11/12/14. @@ -32,6 +32,13 @@ define( RANGE_TICKS = 7; /** + * The PlotController is responsible for any computation/logic + * associated with displaying the plot view. Specifically, these + * responsibilities include: + * + * * Describing axes and labeling. + * * Handling user interactions. + * * Deciding what needs to be drawn in the chart area. * * @constructor */ @@ -50,6 +57,8 @@ define( formatter.formatDomainValue)(v); } + // Converts from pixel coordinates to domain-range, + // to interpret mouse gestures. function mousePositionToDomainRange(mousePosition) { return new PlotPosition( mousePosition.x, @@ -60,11 +69,36 @@ define( ).getPosition(); } + // Utility function to get the mouse position (in x,y + // pixel coordinates in the canvas area) from a mouse + // event object. + function toMousePosition($event) { + var target = $event.target, + bounds = target.getBoundingClientRect(); + + return { + x: $event.clientX - bounds.left, + y: $event.clientY - bounds.top, + width: bounds.width, + height: bounds.height + }; + } + + // Convert a domain-range position to a displayable + // position. This will subtract the domain offset, which + // is used to bias domain values to minimize loss-of-precision + // associated with conversion to a 32-bit floating point + // format (which is needed in the chart area itself, by WebGL.) function toDisplayable(position) { return [ position[0] - domainOffset, position[1] ]; } + // Update the 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. $scope.draw.boxes = marqueeStart ? [{ start: toDisplayable(mousePositionToDomainRange(marqueeStart)), @@ -73,9 +107,12 @@ define( }] : 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. $scope.draw.dimensions = panZoom.dimensions; $scope.draw.origin = [ panZoom.origin[0] - domainOffset, @@ -83,6 +120,7 @@ define( ]; } + // Update tick marks in scope. function updateTicks() { var tickGenerator = new PlotTickGenerator(panZoomStack, formatter); @@ -92,6 +130,8 @@ define( tickGenerator.generateRangeTicks(RANGE_TICKS); } + // Populate the scope with axis information (specifically, options + // available for each axis.) function setupAxes(metadatas) { $scope.axes = [ new PlotAxis("domain", metadatas, AXIS_DEFAULTS[0]), @@ -99,34 +139,48 @@ define( ]; } + // Respond to newly-available telemetry data; update the + // drawing area accordingly. function plotTelemetry() { - var prepared, data, telemetry; + var prepared, datas, telemetry; + // Get a reference to the TelemetryController telemetry = $scope.telemetry; + // Nothing to plot without TelemetryController if (!telemetry) { return; } + // Ensure axes have been initialized (we will want to + // get the active axis below) if (!$scope.axes) { setupAxes(telemetry.getMetadata()); } - data = telemetry.getResponse(); + // Get data sets + datas = telemetry.getResponse(); + // Prepare data sets for rendering prepared = new PlotPreparer( - data, + datas, ($scope.axes[0].active || {}).key, ($scope.axes[1].active || {}).key ); + // Fit to the boundaries of the data, but don't + // override any user-initiated pan-zoom changes. panZoomStack.setBasePanZoom( prepared.getOrigin(), prepared.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. domainOffset = prepared.getDomainOffset(); + // Draw the buffers. Select color by index. $scope.draw.lines = prepared.getBuffers().map(function (buf, i) { return { buffer: buf, @@ -140,19 +194,11 @@ define( updateTicks(); } - function toMousePosition($event) { - var target = $event.target, - bounds = target.getBoundingClientRect(); - - return { - x: $event.clientX - bounds.left, - y: $event.clientY - bounds.top, - width: bounds.width, - height: bounds.height - }; - } - + // Perform a marquee zoom. function marqueeZoom(start, end) { + // Determine what boundary is described by the marquee, + // in domain-range values. Use the minima for origin, so that + // it doesn't matter what direction the user marqueed in. var a = mousePositionToDomainRange(start), b = mousePositionToDomainRange(end), origin = [ @@ -164,7 +210,10 @@ define( Math.max(a[1], b[1]) - origin[1] ]; + // Push the new state onto the pan-zoom stack panZoomStack.pushPanZoom(origin, dimensions); + + // Make sure tick marks reflect new bounds updateTicks(); } @@ -173,25 +222,49 @@ define( $scope.draw = {}; 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 + */ getColor: function (index) { return PlotPalette.getStringColor(index); }, + /** + * Get the coordinates (as displayable text) for the + * current mouse position. + * @returns {string[]} the displayable domain and range + * coordinates over which the mouse is hovered + */ getHoverCoordinates: function () { return mousePosition ? mousePositionToDomainRange( mousePosition ).map(formatValue) : []; }, + /** + * Handle mouse movement over the chart area. + * @param $event the mouse event + */ hover: function ($event) { mousePosition = toMousePosition($event); if (marqueeStart) { updateMarqueeBox(); } }, + /** + * Initiate a marquee zoom action. + * @param $event the mouse event + */ startMarquee: function ($event) { mousePosition = marqueeStart = toMousePosition($event); updateMarqueeBox(); }, + /** + * Complete a marquee zoom action. + * @param $event the mouse event + */ endMarquee: function ($event) { mousePosition = toMousePosition($event); if (marqueeStart) { @@ -201,13 +274,26 @@ define( updateDrawingBounds(); } }, + /** + * 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 + */ isZoomed: function () { return panZoomStack.getDepth() > 1; }, + /** + * Undo the most recent pan/zoom change and restore + * the prior state. + */ stepBackPanZoom: function () { panZoomStack.popPanZoom(); updateDrawingBounds(); }, + /** + * Undo all pan/zoom changes and restore the initial state. + */ unzoom: function () { panZoomStack.clearPanZoom(); updateDrawingBounds(); diff --git a/platform/features/plot/src/elements/PlotAxis.js b/platform/features/plot/src/elements/PlotAxis.js index 3ab4edd3b7..a27ac072e3 100644 --- a/platform/features/plot/src/elements/PlotAxis.js +++ b/platform/features/plot/src/elements/PlotAxis.js @@ -5,10 +5,30 @@ define( function () { "use strict"; + /** + * A PlotAxis provides a template-ready set of options + * for the domain or range axis, sufficient to populate + * selectors. + * + * @constructor + * @param {string} axisType the field in metadatas to + * look at for axis options; usually one of + * "domains" or "ranges" + * @param {object[]} metadatas metadata objects, as + * returned by the `getMetadata()` method of + * a `telemetry` capability. + * @param {object} defaultValue the value to use for the + * active state in the event that no options are + * found; should contain "name" and "key" at + * minimum. + * + */ function PlotAxis(axisType, metadatas, defaultValue) { var keys = {}, options = []; + // Look through all metadata objects and assemble a list + // of all possible domain or range options function buildOptionsForMetadata(m) { (m[axisType] || []).forEach(function (option) { if (!keys[option.key]) { @@ -24,7 +44,18 @@ define( // template, so expose properties directly to facilitate // two-way data binding (for drop-down menus) return { + /** + * The set of options applicable for this axis; + * an array of objects, where each object contains a + * "key" field and a "name" field (for machine- and + * human-readable names respectively) + */ options: options, + /** + * The currently chosen option for this axis. An + * initial value is provided; this will be updated + * directly form the plot template. + */ active: options[0] || defaultValue }; } diff --git a/platform/features/plot/src/elements/PlotFormatter.js b/platform/features/plot/src/elements/PlotFormatter.js index 49af46698d..de6d02bb60 100644 --- a/platform/features/plot/src/elements/PlotFormatter.js +++ b/platform/features/plot/src/elements/PlotFormatter.js @@ -5,8 +5,15 @@ define( function () { "use strict"; + // Date format to use for domain values; in particular, + // use day-of-year instead of month/day var DATE_FORMAT = "YYYY-DDD HH:mm:ss"; + /** + * The PlotFormatter is responsible for formatting (as text + * for display) values along either the domain or range of a + * plot. + */ function PlotFormatter() { function formatDomainValue(v) { return moment.utc(v).format(DATE_FORMAT); @@ -17,7 +24,20 @@ define( } return { + /** + * Format a domain value. + * @param {number} v the domain value; a timestamp + * in milliseconds since start of 1970 + * @returns {string} a textual representation of the + * data and time, suitable for display. + */ formatDomainValue: formatDomainValue, + /** + * Format a range value. + * @param {number} v the range value; a numeric value + * @returns {string} a textual representation of the + * value, suitable for display. + */ formatRangeValue: formatRangeValue }; } diff --git a/platform/features/plot/src/elements/PlotPalette.js b/platform/features/plot/src/elements/PlotPalette.js index b365a78845..26b19d2a98 100644 --- a/platform/features/plot/src/elements/PlotPalette.js +++ b/platform/features/plot/src/elements/PlotPalette.js @@ -52,18 +52,57 @@ define( }).concat([1]); // RGBA }); + /** + * PlotPalette allows a consistent set of colors to be retrieved + * by index, in various color formats. All PlotPalette methods are + * static, so there is no need for a constructor call; using + * this will simply return PlotPalette itself. + * @constructor + */ function PlotPalette() { return PlotPalette; } + /** + * Look up a color in the plot's palette, by index. + * This will be returned as a three element array of RGB + * values, as integers in the range of 0-255. + * @param {number} i the index of the color to look up + * @return {number[]} the color, as integer RGB values + */ PlotPalette.getIntegerColor = function (i) { return integerPalette[Math.floor(i) % integerPalette.length]; }; + + /** + * Look up a color in the plot's palette, by index. + * This will be returned as a three element array of RGB + * values, in the range of 0.0-1.0. + * + * This format is present specifically to support use with + * WebGL, which expects colors of that form. + * + * @param {number} i the index of the color to look up + * @return {number[]} the color, as floating-point RGB values + */ PlotPalette.getFloatColor = function (i) { return floatPalette[Math.floor(i) % floatPalette.length]; }; + + /** + * Look up a color in the plot's palette, by index. + * This will be returned as a string using #-prefixed + * six-digit RGB hex notation (e.g. #FF0000) + * See http://www.w3.org/TR/css3-color/#rgb-color. + * + * This format is useful for representing colors in in-line + * styles. + * + * @param {number} i the index of the color to look up + * @return {string} the color, as a style-friendly string + */ PlotPalette.getStringColor = function (i) { return stringPalette[Math.floor(i) % stringPalette.length]; }; diff --git a/platform/features/plot/src/elements/PlotPanZoomStack.js b/platform/features/plot/src/elements/PlotPanZoomStack.js index 70b604bb95..33b1286233 100644 --- a/platform/features/plot/src/elements/PlotPanZoomStack.js +++ b/platform/features/plot/src/elements/PlotPanZoomStack.js @@ -5,9 +5,30 @@ define( function () { "use strict"; + /** + * The PlotPanZoomStack is responsible for maintaining the + * pan-zoom state of a plot (expressed as a boundary starting + * at an origin and extending to certain dimensions) in a + * stack, to support the back and unzoom buttons in plot controls. + * + * Dimensions and origins are here described each by two-element + * arrays, where the first element describes a value or quantity + * along the domain axis, and the second element describes the same + * along the range axis. + * + * @constructor + * @param {number[]} origin the plot's origin, initially + * @param {number[]} dimensions the plot's dimensions, initially + */ function PlotPanZoomStack(origin, dimensions) { + // Use constructor parameters as the stack's initial state var stack = [{ origin: origin, dimensions: dimensions }]; + // Various functions which follow are simply wrappers for + // normal stack-like array methods, with the exception that + // they prevent undesired modification and enforce that this + // stack must remain non-empty. + // See JSDoc for specific methods below for more detail. function getDepth() { return stack.length; } @@ -43,13 +64,70 @@ define( } return { + /** + * Get the current stack depth; that is, the number + * of items on the stack. A depth of one means that no + * panning or zooming relative to the base value has + * been applied. + * @returns {number} the depth of the stack + */ getDepth: getDepth, + + /** + * Push a new pan-zoom state onto the stack; this will + * become the active pan-zoom state. + * @param {number[]} origin the new origin + * @param {number[]} dimensions the new dimensions + */ pushPanZoom: pushPanZoom, + + /** + * Pop a pan-zoom state from the stack. Whatever pan-zoom + * state was previously present will become current. + * If called when there is only one pan-zoom state on the + * stack, this acts as a no-op (that is, the lowest + * pan-zoom state on the stack cannot be popped, to ensure + * that some pan-zoom state is always available.) + */ popPanZoom: popPanZoom, + + /** + * Set the base pan-zoom state; that is, the state at the + * bottom of the stack. This allows the "unzoomed" state of + * a plot to be updated (e.g. as new data comes in) without + * interfering with the user's chosen zoom level. + * @param {number[]} origin the base origin + * @param {number[]} dimensions the base dimensions + */ setBasePanZoom: setBasePanZoom, + + /** + * Clear the pan-zoom stack down to its bottom element; + * in effect, pop all elements but the last, e.g. to remove + * any temporary user modifications to pan-zoom state. + */ clearPanZoom: clearPanZoom, + + /** + * Get the current pan-zoom state (the state at the top + * of the stack), expressed as an object with "origin" and + * "dimensions" fields. + * @returns {object} the current pan-zoom state + */ getPanZoom: getPanZoom, + + /** + * Get the current origin, as represented on the top of the + * stack. + * @returns {number[]} the current plot origin + */ getOrigin: getOrigin, + + /** + * Get the current dimensions, as represented on the top of + * the stack. + * @returns {number[]} the current plot dimensions + */ getDimensions: getDimensions }; } diff --git a/platform/features/plot/src/elements/PlotPosition.js b/platform/features/plot/src/elements/PlotPosition.js index 9590f7116a..af0525e833 100644 --- a/platform/features/plot/src/elements/PlotPosition.js +++ b/platform/features/plot/src/elements/PlotPosition.js @@ -5,27 +5,65 @@ define( function () { "use strict"; + /** + * A PlotPosition converts from pixel coordinates to domain-range + * coordinates, based on the current plot boundary as described on + * the pan-zoom stack. + * + * These coordinates are not updated after construction; that is, + * they represent the result of the conversion at the time the + * PlotPosition was instantiated. Care should be taken when retaining + * PlotPosition objects across changes to the pan-zoom stack. + * + * @constructor + * @param {number} x the horizontal pixel position in the plot area + * @param {number} y the vertical pixel position in the plot area + * @param {number} width the width of the plot area + * @param {number} height the height of the plot area + * @param {PanZoomStack} panZoomStack the applicable pan-zoom stack, + * used to determine the plot's domain-range boundaries. + */ function PlotPosition(x, y, width, height, panZoomStack) { var panZoom = panZoomStack.getPanZoom(), origin = panZoom.origin, dimensions = panZoom.dimensions, position; + function convert(v, i) { + return v * dimensions[i] + origin[i]; + } + if (!dimensions || !origin) { + // We need both dimensions and origin to compute a position position = []; } else { - position = [ x / width, (height - y) / height ].map(function (v, i) { - return v * dimensions[i] + origin[i]; - }); + // Convert from pixel to domain-range space. + // Note that range is reversed from the y-axis in pixel space + //(positive range points up, positive pixel-y points down) + position = [ x / width, (height - y) / height ].map(convert); } return { + /** + * Get the domain value corresponding to this pixel position. + * @returns {number} the domain value + */ getDomain: function () { return position[0]; }, + /** + * Get the range value corresponding to this pixel position. + * @returns {number} the range value + */ getRange: function () { return position[1]; }, + /** + * Get the domain and values corresponding to this + * pixel position. + * @returns {number[]} an array containing the domain and + * the range value, in that order + */ getPosition: function () { return position; } diff --git a/platform/features/plot/src/elements/PlotPreparer.js b/platform/features/plot/src/elements/PlotPreparer.js index 3ea882b43e..f87c26128f 100644 --- a/platform/features/plot/src/elements/PlotPreparer.js +++ b/platform/features/plot/src/elements/PlotPreparer.js @@ -10,6 +10,16 @@ define( function identity(x) { return x; } + /** + * The PlotPreparer is responsible for handling data sets and + * preparing them to be rendered. It creates a WebGL-plottable + * Float32Array for each trace, and tracks the boundaries of the + * data sets (since this is convenient to do during the same pass). + * @constructor + * @param {Telemetry[]} datas telemetry data objects + * @param {string} domain the key to use when looking up domain values + * @param {string} range the key to use when looking up range values + */ function PlotPreparer(datas, domain, range) { var index, vertices = [], @@ -23,14 +33,18 @@ define( // Remove any undefined data sets datas = (datas || []).filter(identity); - // Filter out un + // Do a first pass to determine the domain offset. + // This will be use to reduce the magnitude of domain values + // in the buffer, to minimize loss-of-precision when + // converting to a 32-bit float. datas.forEach(function (data) { domainOffset = Math.min(data.getDomainValue(0, domain), domainOffset); }); + // Assemble buffers, and track bounds of the data present datas.forEach(function (data, i) { vertices.push([]); - for (index = 0; index < data.getPointCount(); index = index + 1) { + for (index = 0; index < data.getPointCount(); index += 1) { x = data.getDomainValue(index, domain); y = data.getRangeValue(index, range); vertices[i].push(x - domainOffset); @@ -42,23 +56,62 @@ define( } }); + // If range is empty, add some padding if (max[1] === min[1]) { max[1] = max[1] + 1.0; min[1] = min[1] - 1.0; } + // Convert to Float32Array buffers = vertices.map(function (v) { return new Float32Array(v); }); return { + /** + * Get the dimensions which bound all data in the provided + * data sets. This is given as a two-element array where the + * first element is domain, and second is range. + * @returns {number[]} the dimensions which bound this data set + */ getDimensions: function () { return [max[0] - min[0], max[1] - min[1]]; }, + /** + * Get the origin of this data set's boundary. + * This is given as a two-element array where the + * first element is domain, and second is range. + * The domain value here is not adjusted by the domain offset. + * @returns {number[]} the origin of this data set's boundary + */ getOrigin: function () { return min; }, + /** + * Get the domain offset; this offset will have been subtracted + * from all domain values in all buffers returned by this + * preparer, in order to minimize loss-of-precision due to + * conversion to the 32-bit float format needed by WebGL. + * @returns {number} the domain offset + */ getDomainOffset: function () { return domainOffset; }, + /** + * Get all renderable buffers for this data set. This will + * be returned as an array which can be correlated back to + * the provided telemetry data objects (from the constructor + * call) by index. + * + * Internally, these are flattened; each buffer contains a + * sequence of alternating domain and range values. + * + * All domain values in all buffers will have been adjusted + * from their original values by subtraction of the domain + * offset; this minimizes loss-of-precision resulting from + * the conversion to 32-bit floats, which may otherwise + * cause aliasing artifacts (particularly for timestamps) + * + * @returns {Float32Array[]} the buffers for these traces + */ getBuffers: function () { return buffers; } diff --git a/platform/features/plot/src/elements/PlotTickGenerator.js b/platform/features/plot/src/elements/PlotTickGenerator.js index c6486e5c99..80b92a27cf 100644 --- a/platform/features/plot/src/elements/PlotTickGenerator.js +++ b/platform/features/plot/src/elements/PlotTickGenerator.js @@ -5,8 +5,22 @@ define( function () { "use strict"; + /** + * The PlotTickGenerator provides labels for ticks along the + * domain and range axes of the plot, to support the plot + * template. + * + * @constructor + * @param {PlotPanZoomStack} panZoomStack the pan-zoom stack for + * this plot, used to determine plot boundaries + * @param {PlotFormatter} formatter used to format (for display) + * domain and range values. + */ function PlotTickGenerator(panZoomStack, formatter) { + // Generate ticks; interpolate from start up to + // start + span in count steps, using the provided + // formatter to represent each value. function generateTicks(start, span, count, format) { var step = span / (count - 1), result = [], @@ -23,6 +37,11 @@ define( return { + /** + * Generate tick marks for the domain axis. + * @param {number} count the number of ticks + * @returns {string[]} labels for those ticks + */ generateDomainTicks: function (count) { var panZoom = panZoomStack.getPanZoom(); return generateTicks( @@ -32,6 +51,12 @@ define( formatter.formatDomainValue ); }, + + /** + * Generate tick marks for the range axis. + * @param {number} count the number of ticks + * @returns {string[]} labels for those ticks + */ generateRangeTicks: function (count) { var panZoom = panZoomStack.getPanZoom(); return generateTicks(