diff --git a/platform/features/plot-reborn/src/controllers/PlotController.js b/platform/features/plot-reborn/src/controllers/PlotController.js index 4bf74fac6c..fe0b17f05d 100644 --- a/platform/features/plot-reborn/src/controllers/PlotController.js +++ b/platform/features/plot-reborn/src/controllers/PlotController.js @@ -7,15 +7,17 @@ define( // TODO: Store this in more accessible locations / retrieve from // domainObject metadata. - var DOMAIN_INTERVAL = 1 * 60 * 1000; // One minute. + var DOMAIN_INTERVAL = 2 * 60 * 1000; // Two minutes. function PlotController($scope, colorService) { - var plotHistory = []; - var isLive = true; - var maxDomain = +new Date(); - var subscriptions = []; - var palette = new colorService.ColorPalette(); - var setToDefaultViewport = function() { + var plotHistory = [], + isLive = true, + maxDomain = +new Date(), + subscriptions = [], + palette = new colorService.ColorPalette(); + + + function setToDefaultViewport() { // TODO: We shouldn't set the viewport until we have received data or something has given us a reasonable viewport. $scope.viewport = { topLeft: { @@ -27,15 +29,15 @@ define( range: -1 } }; - }; + } setToDefaultViewport(); - $scope.displayableRange = function(rangeValue) { + $scope.displayableRange = function (rangeValue) { // TODO: Call format function provided by domain object. return rangeValue; }; - $scope.displayableDomain = function(domainValue) { + $scope.displayableDomain = function (domainValue) { // TODO: Call format function provided by domain object. return new Date(domainValue).toUTCString(); }; @@ -44,25 +46,33 @@ define( $scope.rectangles = []; - var updateSeriesFromTelemetry = function(series, seriesIndex, telemetry) { - var domainValue = telemetry.getDomainValue(telemetry.getPointCount() - 1); - var rangeValue = telemetry.getRangeValue(telemetry.getPointCount() - 1); + function updateSeriesFromTelemetry(series, seriesIndex, telemetry) { + var domainValue = telemetry.getDomainValue( + telemetry.getPointCount() - 1 + ), + rangeValue = telemetry.getRangeValue( + telemetry.getPointCount() - 1 + ), + newTelemetry; // Track the biggest domain we've seen for sticky-ness. maxDomain = Math.max(maxDomain, domainValue); - var newTelemetry = { + newTelemetry = { domain: domainValue, range: rangeValue }; series.data.push(newTelemetry); $scope.$broadcast('series:data:add', seriesIndex, [newTelemetry]); - }; + } - var subscribeToDomainObject = function(domainObject) { - var telemetryCapability = domainObject.getCapability('telemetry'); - var model = domainObject.getModel(); + function subscribeToDomainObject(domainObject) { + var telemetryCapability = domainObject.getCapability('telemetry'), + model = domainObject.getModel(), + series, + seriesIndex, + updater; - var series = { + series = { name: model.name, // TODO: Bring back PlotPalette. color: palette.getColor($scope.series.length), @@ -70,17 +80,25 @@ define( }; $scope.series.push(series); - var seriesIndex = $scope.series.indexOf(series); + seriesIndex = $scope.series.indexOf(series); - var updater = updateSeriesFromTelemetry.bind( + updater = updateSeriesFromTelemetry.bind( null, series, seriesIndex ); subscriptions.push(telemetryCapability.subscribe(updater)); - }; + } - var linkDomainObject = function(domainObject) { + function unlinkDomainObject() { + subscriptions.forEach(function(subscription) { + subscription.unsubscribe(); + }); + subscriptions = []; + } + + + function linkDomainObject(domainObject) { unlinkDomainObject(); if (domainObject.hasCapability('telemetry')) { subscribeToDomainObject(domainObject); @@ -98,23 +116,17 @@ define( } else { throw new Error('Domain object type not supported.'); } - }; + } - var unlinkDomainObject = function() { - subscriptions.forEach(function(subscription) { - subscription.unsubscribe(); - }); - subscriptions = []; - }; - var onUserViewportChangeStart = function() { + function onUserViewportChangeStart() { // TODO: this is a great time to track a history entry. // Disable live mode so they have full control of viewport. plotHistory.push($scope.viewport); isLive = false; - }; + } - var onUserViewportChangeEnd = function(event, viewport) { + function onUserViewportChangeEnd(event, viewport) { // If the new viewport is "close enough" to the maxDomain then // enable live mode. Set empirically to 10% of the domain // interval. @@ -127,10 +139,10 @@ define( isLive = false; } plotHistory.push(viewport); - }; + } - var viewportForMaxDomain = function() { - var viewport = { + function viewportForMaxDomain() { + return { topLeft: { range: $scope.viewport.topLeft.range, domain: maxDomain - DOMAIN_INTERVAL @@ -140,14 +152,13 @@ define( domain: maxDomain } }; - return viewport; - }; + } - var followDataIfLive = function() { + function followDataIfLive() { if (isLive) { $scope.viewport = viewportForMaxDomain(); } - }; + } $scope.$on('series:data:add', followDataIfLive); $scope.$on('user:viewport:change:end', onUserViewportChangeEnd); @@ -155,7 +166,7 @@ define( $scope.$watch('domainObject', linkDomainObject); - var controller = { + return { historyBack: function() { // TODO: Step History Back. }, @@ -166,8 +177,6 @@ define( // TODO: Reset view to defaults. Keep history stack alive? } }; - - return controller; } return PlotController; diff --git a/platform/features/plot-reborn/src/directives/MCTChart.js b/platform/features/plot-reborn/src/directives/MCTChart.js index ff3206f271..37b39ac11f 100644 --- a/platform/features/plot-reborn/src/directives/MCTChart.js +++ b/platform/features/plot-reborn/src/directives/MCTChart.js @@ -51,24 +51,6 @@ define( return; } - function redraw() { - if (isDestroyed) { - return; - } - - requestAnimationFrame(redraw); - canvas.width = canvas.offsetWidth; - canvas.height = canvas.offsetHeight; - drawAPI.clear(); - createOffset(); - if (!offset) { - return; - } - updateViewport(); - drawSeries(); - drawRectangles(); - } - function createOffset() { if (offset) { return; @@ -84,18 +66,30 @@ define( ); } - function drawIfResized() { - if (canvas.width !== canvas.offsetWidth || - canvas.height !== canvas.offsetHeight) { - redraw(); - } - } - - function destroyChart() { - isDestroyed = true; - if (activeInterval) { - $interval.cancel(activeInterval); + function lineFromSeries(series) { + // TODO: handle when lines get longer than 10,000 points. + // Each line allocates 10,000 points. This should be more + // that we ever need, but we have to decide how to handle + // this at the higher level. I imagine the plot controller + // should watch it's series and when they get huge, slice + // them in half and delete the oldest half. + // + // As long as the controller replaces $scope.series with a + // new series object, then this directive will + // automatically generate new arrays for those lines. + // In practice, the overhead of regenerating these lines + // appears minimal. + var lineBuffer = new Float32Array(20000), + i = 0; + for (i = 0; i < series.data.length; i++) { + lineBuffer[2*i] = offset.domain(series.data[i].domain); + lineBuffer[2*i+1] = offset.range(series.data[i].range); } + return { + color: series.color, + buffer: lineBuffer, + pointCount: series.data.length + }; } function drawSeries() { @@ -132,7 +126,10 @@ define( } function updateViewport() { - var dimensions = [ + var dimensions, + origin; + + dimensions = [ Math.abs( offset.domain($scope.viewport.topLeft.domain) - offset.domain($scope.viewport.bottomRight.domain) @@ -143,7 +140,7 @@ define( ) ]; - var origin = [ + origin = [ offset.domain( $scope.viewport.topLeft.domain ), @@ -158,31 +155,6 @@ define( ); } - function lineFromSeries(series) { - // TODO: handle when lines get longer than 10,000 points. - // Each line allocates 10,000 points. This should be more - // that we ever need, but we have to decide how to handle - // this at the higher level. I imagine the plot controller - // should watch it's series and when they get huge, slice - // them in half and delete the oldest half. - // - // As long as the controller replaces $scope.series with a - // new series object, then this directive will - // automatically generate new arrays for those lines. - // In practice, the overhead of regenerating these lines - // appears minimal. - var lineBuffer = new Float32Array(20000); - for (var i = 0; i < series.data.length; i++) { - lineBuffer[2*i] = offset.domain(series.data[i].domain); - lineBuffer[2*i+1] = offset.range(series.data[i].range); - } - return { - color: series.color, - buffer: lineBuffer, - pointCount: series.data.length - }; - } - function onSeriesDataAdd(event, seriesIndex, points) { var line = lines[seriesIndex]; points.forEach(function (point) { @@ -192,6 +164,41 @@ define( }); } + + + function redraw() { + if (isDestroyed) { + return; + } + + requestAnimationFrame(redraw); + canvas.width = canvas.offsetWidth; + canvas.height = canvas.offsetHeight; + drawAPI.clear(); + createOffset(); + if (!offset) { + return; + } + updateViewport(); + drawSeries(); + drawRectangles(); + } + + + function drawIfResized() { + if (canvas.width !== canvas.offsetWidth || + canvas.height !== canvas.offsetHeight) { + redraw(); + } + } + + function destroyChart() { + isDestroyed = true; + if (activeInterval) { + $interval.cancel(activeInterval); + } + } + // Check for resize, on a timer activeInterval = $interval(drawIfResized, 1000); diff --git a/platform/features/plot-reborn/src/directives/MCTPlot.js b/platform/features/plot-reborn/src/directives/MCTPlot.js index b26d3728a3..cef2126935 100644 --- a/platform/features/plot-reborn/src/directives/MCTPlot.js +++ b/platform/features/plot-reborn/src/directives/MCTPlot.js @@ -7,8 +7,8 @@ define( function (utils) { "use strict"; - var RANGE_TICK_COUNT = 7; - var DOMAIN_TICK_COUNT = 5; + var RANGE_TICK_COUNT = 7, + DOMAIN_TICK_COUNT = 5; function MCTPlot() { @@ -40,34 +40,37 @@ define( } - var dragStart; - var marqueeBox = {}; - var marqueeRect; // Set when exists. - var chartElementBounds; - var $canvas = $element.find('canvas'); + var dragStart, + marqueeBox = {}, + marqueeRect, // Set when exists. + chartElementBounds, + $canvas = $element.find('canvas'); - var updateAxesForCurrentViewport = function() { + function updateAxesForCurrentViewport() { // Update axes definitions for current viewport. ['domain', 'range'].forEach(function(axisName) { - var axis = $scope.axes[axisName]; - var firstTick = $scope.viewport.topLeft[axisName]; - var lastTick = $scope.viewport.bottomRight[axisName]; - var axisSize = firstTick - lastTick; - var denominator = axis.tickCount - 1; + var axis = $scope.axes[axisName], + firstTick = $scope.viewport.topLeft[axisName], + lastTick = $scope.viewport.bottomRight[axisName], + axisSize = firstTick - lastTick, + denominator = axis.tickCount - 1, + tickNumber, + tickIncrement, + tickValue; // Yes, ticksize is negative for domain and positive for range. // It's because ticks are generated/displayed top to bottom and left to right. axis.ticks = []; - for (var tickNumber = 0; tickNumber < axis.tickCount; tickNumber++) { - var tickIncrement = (axisSize * (tickNumber / denominator)); - var tickValue = firstTick - tickIncrement; + for (tickNumber = 0; tickNumber < axis.tickCount; tickNumber++) { + tickIncrement = (axisSize * (tickNumber / denominator)); + tickValue = firstTick - tickIncrement; axis.ticks.push( tickValue ); } }); - }; + } - var drawMarquee = function() { + function drawMarquee() { // Create rectangle for Marquee if it should be set. if (marqueeBox && marqueeBox.start && marqueeBox.end) { if (!marqueeRect) { @@ -79,30 +82,88 @@ define( marqueeRect.color = [1, 1, 1, 0.5]; marqueeRect.layer = 'top'; // TODO: implement this. $scope.$broadcast('rectangle-change'); - } else if (marqueeRect && $scope.rectangles.indexOf(marqueeRect) != -1) { + } else if (marqueeRect && $scope.rectangles.indexOf(marqueeRect) !== -1) { $scope.rectangles.splice($scope.rectangles.indexOf(marqueeRect)); marqueeRect = undefined; $scope.$broadcast('rectangle-change'); } - }; + } - var untrackMousePosition = function() { + function untrackMousePosition() { $scope.mouseCoordinates = undefined; - }; + } + function updateMarquee() { + // Update the marquee box in progress. + marqueeBox.end = $scope.mouseCoordinates.positionAsPlotPoint; + drawMarquee(); + } + function startMarquee() { + marqueeBox.start = $scope.mouseCoordinates.positionAsPlotPoint; + } + function endMarquee() { + // marqueeBox start/end are opposite corners but we need + // topLeft and bottomRight. + var boxPoints = utils.boxPointsFromOppositeCorners(marqueeBox.start, marqueeBox.end), + newViewport = utils.oppositeCornersFromBoxPoints(boxPoints); - var trackMousePosition = function($event) { + marqueeBox = {}; + drawMarquee(); + $scope.$emit('user:viewport:change:end', newViewport); + $scope.viewport = newViewport; + } + + function startDrag($event) { + $scope.$emit('user:viewport:change:start'); + if (!$scope.mouseCoordinates) { + return; + } + $event.preventDefault(); + // Track drag location relative to position over element + // not domain, as chart viewport will change as we drag. + dragStart = $scope.mouseCoordinates.positionAsPlotPoint; + // Tell controller that we're starting to navigate. + return false; + } + + function updateDrag() { + // calculate offset between points. Apply that offset to viewport. + var newPosition = $scope.mouseCoordinates.positionAsPlotPoint, + dDomain = dragStart.domain - newPosition.domain, + dRange = dragStart.range - newPosition.range; + + $scope.viewport = { + topLeft: { + domain: $scope.viewport.topLeft.domain + dDomain, + range: $scope.viewport.topLeft.range + dRange + }, + bottomRight: { + domain: $scope.viewport.bottomRight.domain + dDomain, + range: $scope.viewport.bottomRight.range + dRange + } + }; + } + + function endDrag() { + dragStart = undefined; + $scope.$emit('user:viewport:change:end', $scope.viewport); + } + + function trackMousePosition($event) { // Calculate coordinates of mouse related to canvas and as // domain, range value and make available in scope for display. - var bounds = $event.target.getBoundingClientRect(); + var bounds = $event.target.getBoundingClientRect(), + positionOverElement, + positionAsPlotPoint; + chartElementBounds = bounds; - var positionOverElement = { + positionOverElement = { x: $event.clientX - bounds.left, y: $event.clientY - bounds.top }; - var positionAsPlotPoint = utils.elementPositionAsPlotPosition( + positionAsPlotPoint = utils.elementPositionAsPlotPosition( positionOverElement, bounds, $scope.viewport @@ -120,104 +181,46 @@ define( if (dragStart) { updateDrag(); } - }; + } - var startMarquee = function() { - marqueeBox.start = $scope.mouseCoordinates.positionAsPlotPoint; - }; - - var updateMarquee = function() { - // Update the marquee box in progress. - marqueeBox.end = $scope.mouseCoordinates.positionAsPlotPoint; - drawMarquee(); - }; - - var endMarquee = function() { - // marqueeBox start/end are opposite corners but we need - // topLeft and bottomRight. - var boxPoints = utils.boxPointsFromOppositeCorners(marqueeBox.start, marqueeBox.end); - var newViewport = utils.oppositeCornersFromBoxPoints(boxPoints); - - marqueeBox = {}; - drawMarquee(); - $scope.$emit('user:viewport:change:end', newViewport); - $scope.viewport = newViewport; - }; - - var startDrag = function($event) { - $scope.$emit('user:viewport:change:start'); - if (!$scope.mouseCoordinates) { - return; - } - $event.preventDefault(); - // Track drag location relative to position over element - // not domain, as chart viewport will change as we drag. - dragStart = $scope.mouseCoordinates.positionAsPlotPoint; - // Tell controller that we're starting to navigate. - return false; - }; - - var updateDrag = function() { - // calculate offset between points. Apply that offset to viewport. - var newPosition = $scope.mouseCoordinates.positionAsPlotPoint; - var dDomain = dragStart.domain - newPosition.domain; - var dRange = dragStart.range - newPosition.range; - - $scope.viewport = { - topLeft: { - domain: $scope.viewport.topLeft.domain + dDomain, - range: $scope.viewport.topLeft.range + dRange - }, - bottomRight: { - domain: $scope.viewport.bottomRight.domain + dDomain, - range: $scope.viewport.bottomRight.range + dRange - } - }; - }; - - var endDrag = function() { - dragStart = undefined; - $scope.$emit('user:viewport:change:end', $scope.viewport); - }; - - var watchForMarquee = function() { + function watchForMarquee() { $canvas.removeClass('plot-drag'); $canvas.addClass('plot-marquee'); $canvas.on('mousedown', startMarquee); $canvas.on('mouseup', endMarquee); $canvas.off('mousedown', startDrag); $canvas.off('mouseup', endDrag); - }; + } - var watchForDrag = function() { + function watchForDrag() { $canvas.addClass('plot-drag'); $canvas.removeClass('plot-marquee'); $canvas.on('mousedown', startDrag); $canvas.on('mouseup', endDrag); $canvas.off('mousedown', startMarquee); $canvas.off('mouseup', endMarquee); - }; + } - var stopWatching = function() { + function toggleInteractionMode(event) { + if (event.keyCode === '18') { // control key. + watchForDrag(); + } + } + + function resetInteractionMode(event) { + if (event.keyCode === '18') { + watchForMarquee(); + } + } + + function stopWatching() { $canvas.off('mousedown', startDrag); $canvas.off('mouseup', endDrag); $canvas.off('mousedown', startMarquee); $canvas.off('mouseup', endMarquee); window.removeEventListener('keydown', toggleInteractionMode); window.removeEventListener('keyup', resetInteractionMode); - }; - - var toggleInteractionMode = function(event) { - if (event.keyCode == '18') { // control key. - watchForDrag(); - } - }; - - var resetInteractionMode = function(event) { - if (event.keyCode == '18') { - watchForMarquee(); - } - }; + } $canvas.on('mousemove', trackMousePosition); $canvas.on('mouseleave', untrackMousePosition); @@ -226,7 +229,7 @@ define( window.addEventListener('keydown', toggleInteractionMode); window.addEventListener('keyup', resetInteractionMode); - var onViewportChange = function() { + function onViewportChange() { if ($scope.mouseCoordinates && chartElementBounds) { $scope.mouseCoordinates.positionAsPlotPoint = utils.elementPositionAsPlotPosition( @@ -235,11 +238,9 @@ define( $scope.viewport ); } - if (marqueeBox && marqueeBox.start) { - // TODO: Discuss whether marqueeBox start should be fixed to data or fixed to canvas element, especially when "isLive is true". - } + // TODO: Discuss whether marqueeBox start should be fixed to data or fixed to canvas element, especially when "isLive is true". updateAxesForCurrentViewport(); - }; + } $scope.$watchCollection('viewport', onViewportChange); diff --git a/platform/features/plot-reborn/src/draw/DrawLoader.js b/platform/features/plot-reborn/src/draw/DrawLoader.js index 3f517fe1c8..8389f67ca5 100644 --- a/platform/features/plot-reborn/src/draw/DrawLoader.js +++ b/platform/features/plot-reborn/src/draw/DrawLoader.js @@ -1,4 +1,4 @@ -/*global define */ +/*global define,$log */ define( [ @@ -25,7 +25,8 @@ define( the draw API to. */ getDrawAPI: function (canvas) { - for (var i = 0; i < CHARTS.length; i++) { + var i; + for (i = 0; i < CHARTS.length; i++) { try { return new CHARTS[i](canvas); } catch (e) { @@ -42,4 +43,4 @@ define( } }; } -); \ No newline at end of file +);