diff --git a/platform/commonUI/general/src/directives/MCTDrag.js b/platform/commonUI/general/src/directives/MCTDrag.js index b832123f0c..f12aae5c5f 100644 --- a/platform/commonUI/general/src/directives/MCTDrag.js +++ b/platform/commonUI/general/src/directives/MCTDrag.js @@ -57,13 +57,17 @@ define( // mouse may leave this element during the drag. var body = $document.find('body'), initialPosition, + $event, delta; // Utility function to cause evaluation of mctDrag, // mctDragUp, etc function fireListener(name) { // Evaluate the expression, with current delta - scope.$eval(attrs[name], { delta: delta }); + scope.$eval(attrs[name], { + delta: delta, + $event: $event + }); // Trigger prompt digestion scope.$apply(); @@ -82,6 +86,9 @@ define( delta = currentPosition.map(function (v, i) { return v - initialPosition[i]; }); + + // Also track the plain event for firing listeners + $event = event; } // Called during a drag, on mousemove @@ -106,7 +113,7 @@ define( fireListener("mctDragUp"); - // Clear out start-of-drag position + // Clear out start-of-drag position, target initialPosition = undefined; // Don't show selection highlights, etc @@ -131,6 +138,7 @@ define( // Don't show selection highlights, etc event.preventDefault(); + return false; } @@ -148,4 +156,4 @@ define( return MCTDrag; } -); \ No newline at end of file +); diff --git a/platform/commonUI/general/test/directives/MCTDragSpec.js b/platform/commonUI/general/test/directives/MCTDragSpec.js index 3f4f1dcadf..1d4568f72b 100644 --- a/platform/commonUI/general/test/directives/MCTDragSpec.js +++ b/platform/commonUI/general/test/directives/MCTDragSpec.js @@ -81,10 +81,11 @@ define( }); it("invokes mctDragDown when dragging begins", function () { - mockElement.on.mostRecentCall.args[1](testEvent(42, 60)); + var event = testEvent(42, 60); + mockElement.on.mostRecentCall.args[1](event); expect(mockScope.$eval).toHaveBeenCalledWith( testAttrs.mctDragDown, - { delta: [0, 0] } + { delta: [0, 0], $event: event } ); }); @@ -101,23 +102,27 @@ define( }); it("invokes mctDrag expression during drag", function () { + var event; + mockElement.on.mostRecentCall.args[1](testEvent(42, 60)); // Find and invoke the mousemove listener mockBody.on.calls.forEach(function (call) { if (call.args[0] === 'mousemove') { - call.args[1](testEvent(52, 200)); + call.args[1](event = testEvent(52, 200)); } }); // Should have passed that delta to mct-drag expression expect(mockScope.$eval).toHaveBeenCalledWith( testAttrs.mctDrag, - { delta: [10, 140] } + { delta: [10, 140], $event: event } ); }); it("invokes mctDragUp expression after drag", function () { + var event; + mockElement.on.mostRecentCall.args[1](testEvent(42, 60)); // Find and invoke the mousemove listener @@ -129,7 +134,7 @@ define( // Find and invoke the mousemove listener mockBody.on.calls.forEach(function (call) { if (call.args[0] === 'mouseup') { - call.args[1](testEvent(40, 71)); + call.args[1](event = testEvent(40, 71)); } }); @@ -138,7 +143,7 @@ define( // initial position expect(mockScope.$eval).toHaveBeenCalledWith( testAttrs.mctDragUp, - { delta: [-2, 11] } + { delta: [-2, 11], $event: event } ); // Should also have unregistered listeners @@ -147,4 +152,4 @@ define( }); } -); \ No newline at end of file +); diff --git a/platform/features/plot/bundle.json b/platform/features/plot/bundle.json index bbbea34bdc..d367c028cb 100644 --- a/platform/features/plot/bundle.json +++ b/platform/features/plot/bundle.json @@ -23,7 +23,21 @@ { "key": "PlotController", "implementation": "PlotController.js", - "depends": [ "$scope", "telemetryFormatter", "telemetryHandler", "throttle" ] + "depends": [ + "$scope", + "telemetryFormatter", + "telemetryHandler", + "throttle", + "PLOT_FIXED_DURATION" + ] + } + ], + "constants": [ + { + "key": "PLOT_FIXED_DURATION", + "value": 900000, + "priority": "fallback", + "comment": "Fifteen minutes." } ], "policies": [ diff --git a/platform/features/plot/res/templates/plot.html b/platform/features/plot/res/templates/plot.html index dd1d5356f5..fa74ef14cb 100644 --- a/platform/features/plot/res/templates/plot.html +++ b/platform/features/plot/res/templates/plot.html @@ -82,8 +82,9 @@ + mct-drag="subplot.continueDrag($event)" + mct-drag-down="subplot.startDrag($event)" + mct-drag-up="subplot.endDrag($event); plot.update()"> diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index fcce051968..7eeca3b786 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -51,7 +51,13 @@ define( * * @constructor */ - function PlotController($scope, telemetryFormatter, telemetryHandler, throttle) { + function PlotController( + $scope, + telemetryFormatter, + telemetryHandler, + throttle, + PLOT_FIXED_DURATION + ) { var subPlotFactory = new SubPlotFactory(telemetryFormatter), modeOptions = new PlotModeOptions([], subPlotFactory), subplots = [], @@ -99,7 +105,8 @@ define( updater = new PlotUpdater( handle, ($scope.axes[0].active || {}).key, - ($scope.axes[1].active || {}).key + ($scope.axes[1].active || {}).key, + PLOT_FIXED_DURATION ); } @@ -161,7 +168,7 @@ define( // Unsubscribe when the plot is destroyed $scope.$on("$destroy", releaseSubscription); - + // Create a throttled update function scheduleUpdate = throttle(function () { modeOptions.getModeHandler().getSubPlots() @@ -248,4 +255,4 @@ define( return PlotController; } -); \ No newline at end of file +); diff --git a/platform/features/plot/src/SubPlot.js b/platform/features/plot/src/SubPlot.js index cb0fcec077..7c74751b27 100644 --- a/platform/features/plot/src/SubPlot.js +++ b/platform/features/plot/src/SubPlot.js @@ -56,6 +56,9 @@ define( domainOffset, mousePosition, marqueeStart, + panStart, + panStartBounds, + subPlotBounds, hoverCoordinates, isHovering = false; @@ -88,8 +91,7 @@ define( // pixel coordinates in the canvas area) from a mouse // event object. function toMousePosition($event) { - var target = $event.target, - bounds = target.getBoundingClientRect(); + var bounds = subPlotBounds; return { x: $event.clientX - bounds.left, @@ -155,6 +157,25 @@ define( 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); + } + // Perform a marquee zoom. function marqueeZoom(start, end) { @@ -241,31 +262,77 @@ define( */ 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 + */ + continueDrag: function ($event) { + mousePosition = toMousePosition($event); + if (marqueeStart) { + updateMarqueeBox(); + } + if (panStart) { + updatePan(); + updateDrawingBounds(); + updateTicks(); + } }, /** * Initiate a marquee zoom action. * @param $event the mouse event */ - startMarquee: function ($event) { - mousePosition = marqueeStart = toMousePosition($event); - updateMarqueeBox(); + 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 */ - endMarquee: function ($event) { + 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; } }, /** @@ -311,4 +378,4 @@ define( return SubPlot; } -); \ No newline at end of file +); diff --git a/platform/features/plot/src/elements/PlotLineBuffer.js b/platform/features/plot/src/elements/PlotLineBuffer.js index 2862a7e6be..e51e6e8a61 100644 --- a/platform/features/plot/src/elements/PlotLineBuffer.js +++ b/platform/features/plot/src/elements/PlotLineBuffer.js @@ -43,9 +43,9 @@ define( var mid = Math.floor((min + max) / 2), found = buffer[mid * 2]; - // Collisions are not wanted + // On collisions, insert at same index if (found === value) { - return -1; + return mid; } // Otherwise, if we're down to a single index, @@ -258,4 +258,4 @@ define( return PlotLineBuffer; } -); \ No newline at end of file +); diff --git a/platform/features/plot/src/elements/PlotUpdater.js b/platform/features/plot/src/elements/PlotUpdater.js index df7eef9abc..f05632d8f3 100644 --- a/platform/features/plot/src/elements/PlotUpdater.js +++ b/platform/features/plot/src/elements/PlotUpdater.js @@ -42,8 +42,10 @@ define( * @param {TelemetryHandle} handle the handle to telemetry access * @param {string} domain the key to use when looking up domain values * @param {string} range the key to use when looking up range values + * @param {number} maxDuration maximum plot duration to display + * @param {number} maxPoints maximum number of points to display */ - function PlotUpdater(handle, domain, range, maxPoints) { + function PlotUpdater(handle, domain, range, fixedDuration, maxPoints) { var ids = [], lines = {}, dimensions = [0, 0], @@ -107,6 +109,7 @@ define( lines = next; } + // Initialize the domain offset, based on these observed values function initializeDomainOffset(values) { domainOffset = @@ -133,7 +136,7 @@ define( } // Update dimensions and origin based on extrema of plots - function updateExtrema() { + function updateBounds() { if (bufferArray.length > 0) { domainExtrema = bufferArray.map(function (lineBuffer) { return lineBuffer.getDomainExtrema(); @@ -143,10 +146,40 @@ define( return lineBuffer.getRangeExtrema(); }).reduce(reduceExtrema); + // Calculate best-fit dimensions dimensions = (rangeExtrema[0] === rangeExtrema[1]) ? [dimensionsOf(domainExtrema), 2.0 ] : [dimensionsOf(domainExtrema), dimensionsOf(rangeExtrema)]; origin = [originOf(domainExtrema), originOf(rangeExtrema)]; + + // ...then enforce a fixed duration if needed + if (fixedDuration !== undefined) { + origin[0] = origin[0] + dimensions[0] - fixedDuration; + dimensions[0] = fixedDuration; + } + } + } + + // Enforce maximum duration on all plot lines; not that + // domain extrema must be up-to-date for this to behave correctly. + function enforceDuration() { + var cutoff; + + function enforceDurationForBuffer(plotLineBuffer) { + var index = plotLineBuffer.findInsertionIndex(cutoff); + if (index > 0) { + // Leave one point untrimmed, such that line will + // continue off left edge of visible plot area. + plotLineBuffer.trim(index - 1); + } + } + + if (fixedDuration !== undefined && + domainExtrema !== undefined && + (domainExtrema[1] - domainExtrema[0] > fixedDuration)) { + cutoff = domainExtrema[1] - fixedDuration; + bufferArray.forEach(enforceDurationForBuffer); + updateBounds(); // Extrema may have changed now } } @@ -180,8 +213,8 @@ define( // Add new data objects.forEach(addPointFor); - // Finally, update extrema - updateExtrema(); + // Then, update extrema + updateBounds(); } // Add historical data for this domain object @@ -213,12 +246,12 @@ define( line.addSeries(series, domain, range); } - // Finally, update extrema - updateExtrema(); + // Update extrema + updateBounds(); } // Use a default MAX_POINTS if none is provided - maxPoints = maxPoints || MAX_POINTS; + maxPoints = maxPoints !== undefined ? maxPoints : MAX_POINTS; // Initially prepare state for these objects. // Note that this may be an empty array at this time, @@ -290,4 +323,4 @@ define( return PlotUpdater; } -); \ No newline at end of file +); diff --git a/platform/features/plot/test/SubPlotSpec.js b/platform/features/plot/test/SubPlotSpec.js index a093b9e289..58cd19faab 100644 --- a/platform/features/plot/test/SubPlotSpec.js +++ b/platform/features/plot/test/SubPlotSpec.js @@ -127,7 +127,7 @@ define( // Simulate a marquee zoom. Note that the mockElement // is 100 by 100 and starts at 10,20 - subplot.startMarquee({ + subplot.startDrag({ target: mockElement, clientX: 60, clientY: 45 @@ -137,7 +137,7 @@ define( clientX: 75, clientY: 85 }); - subplot.endMarquee({ + subplot.endDrag({ target: mockElement, clientX: 80, clientY: 95 @@ -162,7 +162,7 @@ define( // Simulate a marquee zoom. Note that the mockElement // is 100 by 100 and starts at 10,20 - subplot.startMarquee({ + subplot.startDrag({ target: mockElement, clientX: 60, clientY: 45 @@ -172,7 +172,7 @@ define( clientX: 75, clientY: 85 }); - subplot.endMarquee({ + subplot.endDrag({ target: mockElement, clientX: 60, clientY: 45 @@ -198,4 +198,4 @@ define( }); } -); \ No newline at end of file +); diff --git a/platform/features/plot/test/elements/PlotLineBufferSpec.js b/platform/features/plot/test/elements/PlotLineBufferSpec.js index 1066d12d2d..54c0c04c77 100644 --- a/platform/features/plot/test/elements/PlotLineBufferSpec.js +++ b/platform/features/plot/test/elements/PlotLineBufferSpec.js @@ -83,9 +83,6 @@ define( expect(buffer.findInsertionIndex(10)).toEqual(4); expect(buffer.findInsertionIndex(14.5)).toEqual(5); expect(buffer.findInsertionIndex(20)).toEqual(6); - - // 9 is already in there, disallow insertion - expect(buffer.findInsertionIndex(9)).toEqual(-1); }); it("allows insertion in the middle", function () { @@ -169,4 +166,4 @@ define( }); } -); \ No newline at end of file +);