diff --git a/platform/features/plot/res/templates/plot.html b/platform/features/plot/res/templates/plot.html index b82dfe083a..de66ee0f15 100644 --- a/platform/features/plot/res/templates/plot.html +++ b/platform/features/plot/res/templates/plot.html @@ -6,7 +6,7 @@
+ ng-repeat="telemetryObject in subplot.getTelemetryObjects()"> @@ -16,7 +16,7 @@
- {{plot.getHoverCoordinates().join(', ')}} + {{subplot.getHoverCoordinates().join(', ')}}
@@ -48,17 +48,17 @@ ng-mouseenter="representation.showControls = true">
+ ng-repeat="tick in subplot.getDomainTicks()" + ng-style="{ left: (100 * $index / (subplot.getDomainTicks().length - 1)) + '%', height: '100%' }" + ng-show="$index > 0 && $index < (subplot.getDomainTicks().length - 1)">
+ ng-repeat="tick in subplot.getRangeTicks()" + ng-style="{ bottom: (100 * $index / (subplot.getRangeTicks().length - 1)) + '%', width: '100%' }" + ng-show="$index > 0 && $index < (subplot.getRangeTicks().length - 1)">
- @@ -117,10 +117,10 @@
-
+ ng-show="$index > 0 && $index < (subplot.getDomainTicks().length - 1)" + ng-style="{ left: (100 * $index / (subplot.getDomainTicks().length - 1)) + '%' }"> {{tick.label}}
diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index b8a3b98c62..841dc95726 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -45,94 +45,10 @@ define( * @constructor */ function PlotController($scope) { - var mousePosition, - marqueeStart, - panZoomStack = new PlotPanZoomStack([], []), - formatter = new PlotFormatter(), - modeOptions, + var modeOptions = new PlotModeOptions([]), + subplots = [], domainOffset; - // Utility, for map/forEach loops. Index 0 is domain, - // index 1 is range. - function formatValue(v, i) { - return (i ? - formatter.formatRangeValue : - formatter.formatDomainValue)(v); - } - - // Converts from pixel coordinates to domain-range, - // to interpret mouse gestures. - function mousePositionToDomainRange(mousePosition) { - return new PlotPosition( - mousePosition.x, - mousePosition.y, - mousePosition.width, - mousePosition.height, - panZoomStack - ).getPosition(); - } - - // Utility function to get the mouse position (in x,y - // pixel coordinates in the canvas area) from a mouse - // event object. - function toMousePosition($event) { - var target = $event.target, - bounds = target.getBoundingClientRect(); - - return { - x: $event.clientX - bounds.left, - y: $event.clientY - bounds.top, - width: bounds.width, - height: bounds.height - }; - } - - // Convert a domain-range position to a displayable - // position. This will subtract the domain offset, which - // is used to bias domain values to minimize loss-of-precision - // associated with conversion to a 32-bit floating point - // format (which is needed in the chart area itself, by WebGL.) - function toDisplayable(position) { - return [ position[0] - domainOffset, position[1] ]; - } - - // Update the 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)), - 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. - $scope.draw.dimensions = panZoom.dimensions; - $scope.draw.origin = [ - panZoom.origin[0] - domainOffset, - panZoom.origin[1] - ]; - } - - // Update tick marks in scope. - function updateTicks() { - var tickGenerator = new PlotTickGenerator(panZoomStack, formatter); - - $scope.domainTicks = - tickGenerator.generateDomainTicks(DOMAIN_TICKS); - $scope.rangeTicks = - tickGenerator.generateRangeTicks(RANGE_TICKS); - } - // Populate the scope with axis information (specifically, options // available for each axis.) function setupAxes(metadatas) { @@ -171,63 +87,16 @@ define( ($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, - color: PlotPalette.getFloatColor(i), - points: buf.length / 2 - }; - }); - - updateDrawingBounds(); - updateMarqueeBox(); - updateTicks(); - } - - // Perform a marquee zoom. - function marqueeZoom(start, end) { - // Determine what boundary is described by the marquee, - // in domain-range values. Use the minima for origin, so that - // it doesn't matter what direction the user marqueed in. - var a = mousePositionToDomainRange(start), - b = mousePositionToDomainRange(end), - origin = [ - Math.min(a[0], b[0]), - Math.min(a[1], b[1]) - ], - dimensions = [ - Math.max(a[0], b[0]) - origin[0], - Math.max(a[1], b[1]) - origin[1] - ]; - - // Push the new state onto the pan-zoom stack - panZoomStack.pushPanZoom(origin, dimensions); - - // Make sure tick marks reflect new bounds - updateTicks(); + modeOptions.getModeHandler().plotTelemetry(prepared); } function setupModes(telemetryObjects) { - modeOptions = new PlotModeOptions(telemetryObjects); + modeOptions = new PlotModeOptions(telemetryObjects || []); } $scope.$watch("telemetry.getTelemetryObjects()", setupModes); $scope.$watch("telemetry.getMetadata()", setupAxes); $scope.$on("telemetryUpdate", plotTelemetry); - $scope.draw = {}; return { /** @@ -239,49 +108,6 @@ define( 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) { - marqueeZoom(marqueeStart, mousePosition); - marqueeStart = undefined; - updateMarqueeBox(); - updateDrawingBounds(); - } - }, /** * Check if the plot is zoomed or panned out * of its default state (to determine whether back/unzoom @@ -289,36 +115,34 @@ define( * @returns {boolean} true if not in default state */ isZoomed: function () { - return panZoomStack.getDepth() > 1; + return modeOptions.getModeHandler().isZoomed(); }, /** * Undo the most recent pan/zoom change and restore * the prior state. */ stepBackPanZoom: function () { - panZoomStack.popPanZoom(); - updateDrawingBounds(); + return modeOptions.getModeHandler().stepBackPanZoom(); }, /** * Undo all pan/zoom changes and restore the initial state. */ unzoom: function () { - panZoomStack.clearPanZoom(); - updateDrawingBounds(); + return modeOptions.getModeHandler().unzoom(); }, /** * Get the mode options (Stacked/Overlaid) that are applicable * for this plot. */ getModeOptions: function () { - return modeOptions && modeOptions.getModeOptions(); + return modeOptions.getModeOptions(); }, /** * Get the current mode that is applicable to this plot. This * will include key, name, and glyph fields. */ getMode: function () { - return modeOptions && modeOptions.getMode(); + return modeOptions.getMode(); }, /** * Set the mode which should be active in this plot. @@ -326,7 +150,15 @@ define( * getModeOptions() */ setMode: function (mode) { - return modeOptions && modeOptions.setMode(mode); + return modeOptions.setMode(mode); + }, + /** + * 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 + */ + getSubPlots: function () { + return modeOptions.getModeHandler().getSubPlots(); } }; diff --git a/platform/features/plot/src/SubPlot.js b/platform/features/plot/src/SubPlot.js new file mode 100644 index 0000000000..298aa3cfbc --- /dev/null +++ b/platform/features/plot/src/SubPlot.js @@ -0,0 +1,201 @@ +/*global define*/ + +define( + ['elements/PlotPosition', 'elements/PlotFormatter', 'elements/PlotTickGenerator'], + function (PlotPosition, PlotFormatter, PlotTickGenerator) { + "use strict"; + + var AXIS_DEFAULTS = [ + { "name": "Time" }, + { "name": "Value" } + ], + DOMAIN_TICKS = 5, + RANGE_TICKS = 7; + + function SubPlot(telemetryObjects, panZoomStack) { + var draw = {}, + rangeTicks = [], + domainTicks = [], + formatter = new PlotFormatter(), + domainOffset, + mousePosition, + marqueeStart; + + // Utility, for map/forEach loops. Index 0 is domain, + // index 1 is range. + function formatValue(v, i) { + return (i ? + formatter.formatRangeValue : + formatter.formatDomainValue)(v); + } + + // Converts from pixel coordinates to domain-range, + // to interpret mouse gestures. + function mousePositionToDomainRange(mousePosition) { + return new PlotPosition( + mousePosition.x, + mousePosition.y, + mousePosition.width, + mousePosition.height, + panZoomStack + ).getPosition(); + } + + // Utility function to get the mouse position (in x,y + // pixel coordinates in the canvas area) from a mouse + // event object. + function toMousePosition($event) { + var target = $event.target, + bounds = target.getBoundingClientRect(); + + return { + x: $event.clientX - bounds.left, + y: $event.clientY - bounds.top, + width: bounds.width, + height: bounds.height + }; + } + + // Convert a domain-range position to a displayable + // position. This will subtract the domain offset, which + // is used to bias domain values to minimize loss-of-precision + // associated with conversion to a 32-bit floating point + // format (which is needed in the chart area itself, by WebGL.) + function toDisplayable(position) { + return [ position[0] - domainOffset, position[1] ]; + } + + // Update the drawable marquee area to reflect current + // mouse position (or don't show it at all, if no marquee + // zoom is in progress) + function updateMarqueeBox() { + // Express this as a box in the draw object, which + // is passed to an mct-chart in the template for rendering. + draw.boxes = marqueeStart ? + [{ + start: toDisplayable(mousePositionToDomainRange(marqueeStart)), + end: toDisplayable(mousePositionToDomainRange(mousePosition)), + color: [1, 1, 1, 0.5 ] + }] : undefined; + } + + // Update the bounds (origin and dimensions) of the drawing area. + function updateDrawingBounds() { + var panZoom = panZoomStack.getPanZoom(); + + // Communicate pan-zoom state from stack to the draw object + // which is passed to mct-chart in the template. + draw.dimensions = panZoom.dimensions; + draw.origin = [ + panZoom.origin[0] - domainOffset, + panZoom.origin[1] + ]; + } + + // Update tick marks in scope. + function updateTicks() { + var tickGenerator = new PlotTickGenerator(panZoomStack, formatter); + + domainTicks = + tickGenerator.generateDomainTicks(DOMAIN_TICKS); + rangeTicks = + tickGenerator.generateRangeTicks(RANGE_TICKS); + } + + + // Perform a marquee zoom. + function marqueeZoom(start, end) { + // Determine what boundary is described by the marquee, + // in domain-range values. Use the minima for origin, so that + // it doesn't matter what direction the user marqueed in. + var a = mousePositionToDomainRange(start), + b = mousePositionToDomainRange(end), + origin = [ + Math.min(a[0], b[0]), + Math.min(a[1], b[1]) + ], + dimensions = [ + Math.max(a[0], b[0]) - origin[0], + Math.max(a[1], b[1]) - origin[1] + ]; + + // Push the new state onto the pan-zoom stack + panZoomStack.pushPanZoom(origin, dimensions); + + // Make sure tick marks reflect new bounds + updateTicks(); + } + + return { + getTelemetryObjects: function () { + return telemetryObjects; + }, + getDomainTicks: function () { + return domainTicks; + }, + getRangeTicks: function () { + return rangeTicks; + }, + getDrawingObject: function () { + return draw; + }, + + /** + * Get the coordinates (as displayable text) for the + * current mouse position. + * @returns {string[]} the displayable domain and range + * coordinates over which the mouse is hovered + */ + getHoverCoordinates: function () { + return 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) { + marqueeZoom(marqueeStart, mousePosition); + marqueeStart = undefined; + updateMarqueeBox(); + updateDrawingBounds(); + } + }, + /** + * Update the drawing bounds, marquee box, and + * tick marks for this subplot. + */ + update: function () { + updateDrawingBounds(); + updateMarqueeBox(); + updateTicks(); + } + }; + } + + return SubPlot; + + } +); \ No newline at end of file diff --git a/platform/features/plot/src/modes/PlotModeOptions.js b/platform/features/plot/src/modes/PlotModeOptions.js index 8afb3c7234..6fbcc31d91 100644 --- a/platform/features/plot/src/modes/PlotModeOptions.js +++ b/platform/features/plot/src/modes/PlotModeOptions.js @@ -8,21 +8,30 @@ define( var STACKED = { key: "stacked", name: "Stacked", - glyph: "8" + glyph: "8", + factory: PlotOverlayMode }, OVERLAID = { key: "overlaid", name: "Overlaid", - glyph: "6" + glyph: "6", + factory: PlotStackedMode }; function PlotModeOptions(telemetryObjects) { var options = telemetryObjects.length > 1 ? [ OVERLAID, STACKED ] : [ OVERLAID ], - mode = options[0]; + mode = options[0], + modeHandler; return { + getModeHandler: function () { + if (!modeHandler) { + modeHandler = mode.factory(telemetryObjects); + } + return modeHandler; + }, getModeOptions: function () { return options; }, @@ -30,7 +39,10 @@ define( return mode; }, setMode: function (option) { - mode = option; + if (mode !== option) { + mode = option; + modeHandler = undefined; + } } }; } diff --git a/platform/features/plot/src/modes/PlotOverlayMode.js b/platform/features/plot/src/modes/PlotOverlayMode.js new file mode 100644 index 0000000000..3df9c9cb9f --- /dev/null +++ b/platform/features/plot/src/modes/PlotOverlayMode.js @@ -0,0 +1,60 @@ +/*global define*/ + +define( + ["../SubPlot", "../elements/PlotPalette", "../elements/PlotPanZoomStack"], + function (SubPlot, PlotPalette, PlotPanZoomStack) { + "use strict"; + + function PlotOverlayMode(telemetryObjects) { + var domainOffset, + panZoomStack = new PlotPanZoomStack([], []), + subplot = new SubPlot(telemetryObjects, panZoomStack), + subplots = [ 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() + ); + + // 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. + subplot.getDrawingObject().lines = prepared.getBuffers().map(function (buf, i) { + return { + buffer: buf, + color: PlotPalette.getFloatColor(i), + points: buf.length / 2 + }; + }); + + subplot.update(); + } + + return { + plotTelemetry: plotTelemetry, + getSubPlots: function () { + return subplots; + }, + isZoomed: function () { + return panZoomStack.getDepth() > 1; + }, + stepBackPanZoom: function () { + panZoomStack.pop(); + subplot.update(); + }, + unzoom: function () { + panZoomStack.clearPanZoom(); + subplot.update(); + } + }; + } + + return PlotOverlayMode; + } +); \ No newline at end of file