diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index 53793f7530..cca3a15f30 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -7,31 +7,16 @@ define( [ "./elements/PlotPreparer", "./elements/PlotPalette", - "./elements/PlotPanZoomStack", - "./elements/PlotPosition", - "./elements/PlotTickGenerator", - "./elements/PlotFormatter", "./elements/PlotAxis", "./modes/PlotModeOptions" ], - function ( - PlotPreparer, - PlotPalette, - PlotPanZoomStack, - PlotPosition, - PlotTickGenerator, - PlotFormatter, - PlotAxis, - PlotModeOptions - ) { + function (PlotPreparer, PlotPalette, PlotAxis, PlotModeOptions) { "use strict"; var AXIS_DEFAULTS = [ { "name": "Time" }, { "name": "Value" } - ], - DOMAIN_TICKS = 5, - RANGE_TICKS = 7; + ]; /** * The PlotController is responsible for any computation/logic @@ -90,10 +75,14 @@ define( modeOptions.getModeHandler().plotTelemetry(prepared); } + // Trigger an update of a specific subplot; + // used in a loop to update all subplots. function updateSubplot(subplot) { subplot.update(); } + // Set up available modes (stacked/overlaid), based on the + // set of telemetry objects in this plot view. function setupModes(telemetryObjects) { modeOptions = new PlotModeOptions(telemetryObjects || []); } diff --git a/platform/features/plot/src/SubPlot.js b/platform/features/plot/src/SubPlot.js index a87f2b5de7..aa87164d20 100644 --- a/platform/features/plot/src/SubPlot.js +++ b/platform/features/plot/src/SubPlot.js @@ -1,18 +1,31 @@ /*global define*/ define( - ['./elements/PlotPosition', './elements/PlotFormatter', './elements/PlotTickGenerator'], + [ + './elements/PlotPosition', + './elements/PlotFormatter', + './elements/PlotTickGenerator' + ], function (PlotPosition, PlotFormatter, PlotTickGenerator) { "use strict"; - var AXIS_DEFAULTS = [ - { "name": "Time" }, - { "name": "Value" } - ], - DOMAIN_TICKS = 5, + var DOMAIN_TICKS = 5, RANGE_TICKS = 7; + /** + * A SubPlot is an individual plot within a Plot View (which + * may contain multiple plots, specifically when in Stacked + * plot mode.) + * @constructor + * @param {DomainObject[]} telemetryObjects the domain objects + * which will be plotted in this sub-plot + * @param {PlotPanZoomStack} panZoomStack the stack of pan-zoom + * states which is applicable to this sub-plot + */ function SubPlot(telemetryObjects, panZoomStack) { + // 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 = [], @@ -133,19 +146,44 @@ define( updateTicks(); return { + /** + * Get the set of domain objects which are being + * represented in this sub-plot. + * @returns {DomainObject[]} the domain objects which + * will have data plotted in this sub-plot + */ getTelemetryObjects: function () { return telemetryObjects; }, + /** + * Get ticks mark information appropriate for using in the + * template for this sub-plot's domain axis, as prepared + * by the PlotTickGenerator. + * @returns {Array} tick marks for the domain axis + */ getDomainTicks: function () { return domainTicks; }, + /** + * Get ticks mark information appropriate for using in the + * template for this sub-plot's range axis, as prepared + * by the PlotTickGenerator. + * @returns {Array} tick marks for the range axis + */ getRangeTicks: function () { return rangeTicks; }, + /** + * Get the drawing object associated with this sub-plot; + * this object will be passed to the mct-chart in which + * this sub-plot's lines will be plotted, as its "draw" + * attribute, and should have the same internal format + * expected by that directive. + * @return {object} the drawing object + */ getDrawingObject: function () { return draw; }, - /** * Get the coordinates (as displayable text) for the * current mouse position. @@ -199,9 +237,28 @@ define( updateMarqueeBox(); updateTicks(); }, + /** + * Set the domain offset associated with this sub-plot. + * A domain offset is subtracted from all domain + * before lines are drawn to avoid artifacts associated + * with the use of 32-bit floats when domain values + * are often timestamps (due to insufficient precision.) + * A SubPlot will be drawing boxes (for marquee zoom) in + * the same offset coordinate space, so it needs to know + * the value of this to position that marquee box + * correctly. + * @param {number} value the domain offset + */ setDomainOffset: function (value) { domainOffset = value; }, + /** + * When used with no argument, check whether or not the user + * is currently hovering over this subplot. When used with + * an argument, set that state. + * @param {boolean} [state] the new hovering state + * @returns {boolean} the hovering state + */ isHovering: function (state) { if (state !== undefined) { isHovering = state; diff --git a/platform/features/plot/src/elements/PlotPanZoomStackGroup.js b/platform/features/plot/src/elements/PlotPanZoomStackGroup.js index 385f54ee9a..f0a9903363 100644 --- a/platform/features/plot/src/elements/PlotPanZoomStackGroup.js +++ b/platform/features/plot/src/elements/PlotPanZoomStackGroup.js @@ -5,16 +5,32 @@ define( function (PlotPanZoomStack) { "use strict"; + /** + * A plot pan zoom stack group provides a collection of individual + * pan-zoom stacks that synchronize upon the domain axis, but + * remain independent upon the range axis. This supports panning + * and zooming in stacked-plot mode (and, importantly, + * stepping back through those states.) + * @constructor + * @param {number} count the number of stacks to include in this + * group + */ function PlotPanZoomStackGroup(count) { var stacks = [], decoratedStacks = [], i; + // Push a pan-zoom state; the index argument identifies + // which stack originated the request (all other stacks + // will ignore the range part of the change.) function pushPanZoom(origin, dimensions, index) { stacks.forEach(function (stack, i) { if (i === index) { + // Do a normal push for the specified stack stack.pushPanZoom(origin, dimensions); } else { + // For other stacks, do a push, but repeat + // their current range axis bounds. stack.pushPanZoom( [ origin[0], stack.getOrigin()[1] ], [ dimensions[0], stack.getDimensions()[1] ] @@ -23,28 +39,34 @@ define( }); } - + // Pop one pan-zoom state from all stacks function popPanZoom() { stacks.forEach(function (stack) { stack.popPanZoom(); }); } + // Set the base pan-zoom state for all stacks function setBasePanZoom(origin, dimensions) { stacks.forEach(function (stack) { stack.setBasePanZoom(origin, dimensions); }); } + // Clear the pan-zoom state of all stacks function clearPanZoom() { stacks.forEach(function (stack) { stack.clearPanZoom(); }); } + // Decorate a pan-zoom stack; returns an object with + // the same interface, but whose stack-mutation methods + // effect all items in the group. function decorateStack(stack, index) { var result = Object.create(stack); + // Use the methods defined above result.pushPanZoom = function (origin, dimensions) { pushPanZoom(origin, dimensions, index); }; @@ -55,19 +77,69 @@ define( return result; } - for (i = 0; i < count; i += 1) { + // Create the stacks in this group ... + while (stacks.length < count) { stacks.push(new PlotPanZoomStack([], [])); } + // ... and their decorated-to-synchronize versions. decoratedStacks = stacks.map(decorateStack); return { + /** + * Pop a pan-zoom state from all stacks in the group. + * If called when there is only one pan-zoom state on each + * 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, - clearPanZoom: clearPanZoom, + + /** + * Set the base pan-zoom state for all stacks in this group. + * This changes the state at the bottom of each stack. + * This allows the "unzoomed" state of plots to be updated + * (e.g. as new data comes in) without + * interfering with the user's chosen pan/zoom states. + * @param {number[]} origin the base origin + * @param {number[]} dimensions the base dimensions + */ setBasePanZoom: setBasePanZoom, + + /** + * Clear all pan-zoom stacks in this group down to + * their 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 stack depth; that is, the number + * of items on each stack in the group. + * A depth of one means that no + * panning or zooming relative to the base value has + * been applied. + * @returns {number} the depth of the stacks in this group + */ getDepth: function () { + // All stacks are kept in sync, so look up depth + // from the first one. return stacks.length > 0 ? stacks[0].getDepth() : 0; }, + /** + * Get a specific pan-zoom stack in this group. + * Stacks are specified by index; this index must be less + * than the count provided at construction time, and must + * not be less than zero. + * The stack returned by this function will be synchronized + * to other stacks in this group; that is, mutating that + * stack directly will result in other stacks in this group + * undergoing similar updates to ensure that domain bounds + * remain the same. + * @param {number} index the index of the stack to get + * @returns {PlotPanZoomStack} the pan-zoom stack in the + * group identified by that index + */ getPanZoomStack: function (index) { return decoratedStacks[index]; } diff --git a/platform/features/plot/src/modes/PlotModeOptions.js b/platform/features/plot/src/modes/PlotModeOptions.js index 6937b001c6..d91f43c43e 100644 --- a/platform/features/plot/src/modes/PlotModeOptions.js +++ b/platform/features/plot/src/modes/PlotModeOptions.js @@ -18,29 +18,66 @@ define( factory: PlotOverlayMode }; + /** + * Determines which plotting modes (stacked/overlaid) + * are applicable in a given plot view, maintains current + * selection state thereof, and provides handlers for the + * different behaviors associated with these modes. + * @constructor + * @param {DomainObject[]} the telemetry objects being + * represented in this plot view + */ function PlotModeOptions(telemetryObjects) { var options = telemetryObjects.length > 1 ? [ OVERLAID, STACKED ] : [ OVERLAID ], - mode = options[0], + 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 + */ getModeHandler: function () { + // Lazily initialize if (!modeHandler) { modeHandler = mode.factory(telemetryObjects); } 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 + */ 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 + */ 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` + */ 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; } } diff --git a/platform/features/plot/src/modes/PlotOverlayMode.js b/platform/features/plot/src/modes/PlotOverlayMode.js index 217fe049e7..e754faf249 100644 --- a/platform/features/plot/src/modes/PlotOverlayMode.js +++ b/platform/features/plot/src/modes/PlotOverlayMode.js @@ -5,6 +5,12 @@ define( function (SubPlot, PlotPalette, PlotPanZoomStack) { "use strict"; + /** + * Handles plotting in Overlaid mode. In overlaid mode, there + * is one sub-plot which contains all plotted objects. + * @constructor + * @param {DomainObject[]} the domain objects to be plotted + */ function PlotOverlayMode(telemetryObjects) { var domainOffset, panZoomStack = new PlotPanZoomStack([], []), @@ -37,13 +43,32 @@ define( } return { + /** + * Plot telemetry to the sub-plot(s) managed by this mode. + * @param {PlotPreparer} prepared the prepared data to plot + */ 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 + */ 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 + */ isZoomed: function () { return panZoomStack.getDepth() > 1; }, + /** + * Undo the most recent pan/zoom change and restore + * the prior state. + */ stepBackPanZoom: function () { panZoomStack.popPanZoom(); subplot.update(); diff --git a/platform/features/plot/src/modes/PlotStackMode.js b/platform/features/plot/src/modes/PlotStackMode.js index 77ebb94c41..f8e5167205 100644 --- a/platform/features/plot/src/modes/PlotStackMode.js +++ b/platform/features/plot/src/modes/PlotStackMode.js @@ -5,6 +5,12 @@ define( function (SubPlot, PlotPalette, PlotPanZoomStackGroup) { "use strict"; + /** + * Handles plotting in Stacked mode. In stacked mode, there + * is one sub-plot for each plotted object. + * @constructor + * @param {DomainObject[]} the domain objects to be plotted + */ function PlotStackMode(telemetryObjects) { var domainOffset, panZoomStackGroup = @@ -24,7 +30,8 @@ define( // floating point values for display. subplot.setDomainOffset(prepared.getDomainOffset()); - // Draw the buffers. Always use the 0th color + // Draw the buffers. Always use the 0th color, because there + // is one line per plot. subplot.getDrawingObject().lines = [{ buffer: buffer, color: PlotPalette.getFloatColor(0), @@ -48,19 +55,41 @@ define( } return { + /** + * Plot telemetry to the sub-plot(s) managed by this mode. + * @param {PlotPreparer} prepared the prepared data to plot + */ 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 + */ 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 + */ isZoomed: function () { return panZoomStackGroup.getDepth() > 1; }, + /** + * Undo the most recent pan/zoom change and restore + * the prior state. + */ stepBackPanZoom: function () { panZoomStackGroup.popPanZoom(); subplots.forEach(function (subplot) { subplot.update(); }); }, + /** + * Undo all pan/zoom changes and restore the initial state. + */ unzoom: function () { panZoomStackGroup.clearPanZoom(); subplots.forEach(function (subplot) {