diff --git a/platform/commonUI/formats/src/UTCTimeFormat.js b/platform/commonUI/formats/src/UTCTimeFormat.js index 26919e3160..a31194f0b3 100644 --- a/platform/commonUI/formats/src/UTCTimeFormat.js +++ b/platform/commonUI/formats/src/UTCTimeFormat.js @@ -95,8 +95,15 @@ define([ })[0][0]; } + /** + * Returns a description of the current range of the time conductor's + * bounds. + * @param timeRange + * @returns {*} + */ UTCTimeFormat.prototype.timeUnits = function (timeRange) { var momentified = moment.duration(timeRange); + return [ ["Decades", function (r) { return r.years() > 15; diff --git a/platform/features/conductor-v2/conductor/bundle.js b/platform/features/conductor-v2/conductor/bundle.js index a3f2150e46..506c0f52ba 100644 --- a/platform/features/conductor-v2/conductor/bundle.js +++ b/platform/features/conductor-v2/conductor/bundle.js @@ -73,15 +73,6 @@ define([ "formatService" ] }, - { - "key": "ConductorAxisController", - "implementation": ConductorAxisController, - "depends": [ - "openmct", - "formatService", - "timeConductorViewService" - ] - }, { "key": "ConductorTOIController", "implementation": ConductorTOIController, diff --git a/platform/features/conductor-v2/conductor/res/templates/conductor-data.html b/platform/features/conductor-v2/conductor/res/templates/conductor-data.html deleted file mode 100644 index 96cbb10090..0000000000 --- a/platform/features/conductor-v2/conductor/res/templates/conductor-data.html +++ /dev/null @@ -1,15 +0,0 @@ - -
- -
-
-
2016-09-15 21:31:30.000Z
-
-
-
- \ No newline at end of file diff --git a/platform/features/conductor-v2/conductor/res/templates/time-conductor.html b/platform/features/conductor-v2/conductor/res/templates/time-conductor.html index 82c15860c0..955a5d2941 100644 --- a/platform/features/conductor-v2/conductor/res/templates/time-conductor.html +++ b/platform/features/conductor-v2/conductor/res/templates/time-conductor.html @@ -120,8 +120,8 @@ class="time-conductor-zoom-current-range flex-elem flex-fixed holder">{{timeUnits}} diff --git a/platform/features/conductor-v2/conductor/src/ui/ConductorAxisController.js b/platform/features/conductor-v2/conductor/src/ui/ConductorAxisController.js index 586097325a..f4dfeb0d33 100644 --- a/platform/features/conductor-v2/conductor/src/ui/ConductorAxisController.js +++ b/platform/features/conductor-v2/conductor/src/ui/ConductorAxisController.js @@ -32,20 +32,15 @@ define( * labelled 'ticks'. It requires 'start' and 'end' integer values to * be specified as attributes. */ - function ConductorAxisController(openmct, formatService, conductorViewService) { + function ConductorAxisController(openmct, formatService, conductorViewService, scope, element) { // Dependencies this.d3 = d3; this.formatService = formatService; this.conductor = openmct.conductor; this.conductorViewService = conductorViewService; - // Runtime properties (set by 'link' function) - this.target = undefined; - this.xScale = undefined; - this.xAxis = undefined; - this.axisElement = undefined; + this.scope = scope; this.initialized = false; - this.msPerPixel = undefined; this.bounds = this.conductor.bounds(); this.timeSystem = this.conductor.timeSystem(); @@ -56,6 +51,8 @@ define( }).forEach(function (key) { this[key] = ConductorAxisController.prototype[key].bind(this); }.bind(this)); + + this.initialize(element); } ConductorAxisController.prototype.destroy = function () { @@ -65,70 +62,18 @@ define( this.conductorViewService.off("zoom-stop", this.onZoomStop) }; - ConductorAxisController.prototype.changeBounds = function (bounds) { - this.bounds = bounds; - if (this.initialized && !this.zooming) { - this.setScale(); - } - }; - - ConductorAxisController.prototype.setScale = function () { - var width = this.target.offsetWidth; - var timeSystem = this.conductor.timeSystem(); - var bounds = this.bounds; - - if (timeSystem.isUTCBased()) { - this.xScale = this.xScale || this.d3.scaleUtc(); - this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]); - } else { - this.xScale = this.xScale || this.d3.scaleLinear(); - this.xScale.domain([bounds.start, bounds.end]); - } - - this.xScale.range([PADDING, width - PADDING * 2]); - this.axisElement.call(this.xAxis); - - this.msPerPixel = (bounds.end - bounds.start) / width; - }; - - ConductorAxisController.prototype.changeTimeSystem = function (timeSystem) { - this.timeSystem = timeSystem; - - var key = timeSystem.formats()[0]; - if (this.initialized && key !== undefined) { - var format = this.formatService.getFormat(key); - var bounds = this.conductor.bounds(); - - if (timeSystem.isUTCBased()) { - this.xScale = this.d3.scaleUtc(); - } else { - this.xScale = this.d3.scaleLinear(); - } - - this.xAxis.scale(this.xScale); - //Define a custom format function - this.xAxis.tickFormat(function (tickValue) { - // Normalize date representations to numbers - if (tickValue instanceof Date) { - tickValue = tickValue.getTime(); - } - return format.format(tickValue, { - min: bounds.start, - max: bounds.end - }); - }); - this.axisElement.call(this.xAxis); - } - }; - - ConductorAxisController.prototype.link = function (scope, element) { + /** + * Set defaults, and apply d3 axis to the + * @param scope + * @param element + */ + ConductorAxisController.prototype.initialize = function (element) { this.target = element[0].firstChild; - this.scope = scope; var height = this.target.offsetHeight; var vis = this.d3.select(this.target) - .append("svg:svg") - .attr("width", "100%") - .attr("height", height); + .append("svg:svg") + .attr("width", "100%") + .attr("height", height); this.xAxis = this.d3.axisTop(); @@ -153,6 +98,73 @@ define( this.conductorViewService.on("zoom-stop", this.onZoomStop); }; + ConductorAxisController.prototype.changeBounds = function (bounds) { + this.bounds = bounds; + if (this.initialized && !this.zooming) { + this.setScale(); + } + }; + + /** + * Set the scale of the axis, based on current conductor bounds. + */ + ConductorAxisController.prototype.setScale = function () { + var width = this.target.offsetWidth; + var timeSystem = this.conductor.timeSystem(); + var bounds = this.bounds; + + if (timeSystem.isUTCBased()) { + this.xScale = this.xScale || this.d3.scaleUtc(); + this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]); + } else { + this.xScale = this.xScale || this.d3.scaleLinear(); + this.xScale.domain([bounds.start, bounds.end]); + } + + this.xScale.range([PADDING, width - PADDING * 2]); + this.axisElement.call(this.xAxis); + + this.msPerPixel = (bounds.end - bounds.start) / width; + }; + + /** + * When the time system changes, update the scale and formatter used + * for showing times. + * @param timeSystem + */ + ConductorAxisController.prototype.changeTimeSystem = function (timeSystem) { + this.timeSystem = timeSystem; + + var key = timeSystem.formats()[0]; + if (this.initialized && key !== undefined) { + var format = this.formatService.getFormat(key); + var bounds = this.conductor.bounds(); + + //The D3 scale used depends on the type of time system as d3 + // supports UTC out of the box. + if (timeSystem.isUTCBased()) { + this.xScale = this.d3.scaleUtc(); + } else { + this.xScale = this.d3.scaleLinear(); + } + + this.xAxis.scale(this.xScale); + + //Define a custom format function + this.xAxis.tickFormat(function (tickValue) { + // Normalize date representations to numbers + if (tickValue instanceof Date) { + tickValue = tickValue.getTime(); + } + return format.format(tickValue, { + min: bounds.start, + max: bounds.end + }); + }); + this.axisElement.call(this.xAxis); + } + }; + ConductorAxisController.prototype.panStop = function () { //resync view bounds with time conductor bounds this.conductorViewService.emit("pan-stop"); @@ -170,6 +182,12 @@ define( this.zooming = false; }; + /** + * Initiate panning via a click + drag gesture on the time conductor + * scale. Panning triggers a "pan" event + * @param {number} delta the offset from the original click event + * @see TimeConductorViewService# + */ ConductorAxisController.prototype.pan = function (delta) { if (!this.conductor.follow()) { var deltaInMs = delta[0] * this.msPerPixel; diff --git a/platform/features/conductor-v2/conductor/src/ui/ConductorTOIController.js b/platform/features/conductor-v2/conductor/src/ui/ConductorTOIController.js index 3f3c7a6c15..b9e0e5d64d 100644 --- a/platform/features/conductor-v2/conductor/src/ui/ConductorTOIController.js +++ b/platform/features/conductor-v2/conductor/src/ui/ConductorTOIController.js @@ -59,6 +59,14 @@ define( this.conductorViewService.off('pan', this.setOffsetFromBounds); }; + /** + * Given some bounds, set horizontal position of TOI indicator based + * on current conductor TOI value. Bounds are provided so that + * ephemeral bounds from zoom and pan events can be used as well + * as current conductor bounds, allowing TOI to be updated in + * realtime during scroll and zoom. + * @param {TimeConductorBounds} bounds + */ ConductorTOIController.prototype.setOffsetFromBounds = function (bounds) { var toi = this.conductor.timeOfInterest(); if (toi !== undefined) { @@ -79,7 +87,12 @@ define( } }; + /** + * Set time of interest + * @param e The angular $event object + */ ConductorTOIController.prototype.click = function (e) { + //TOI is set using the alt key modified + primary click if (e.altKey) { var element = $(e.currentTarget); var width = element.width(); diff --git a/platform/features/conductor-v2/conductor/src/ui/MctConductorAxis.js b/platform/features/conductor-v2/conductor/src/ui/MctConductorAxis.js index d664057af6..27d4826c93 100644 --- a/platform/features/conductor-v2/conductor/src/ui/MctConductorAxis.js +++ b/platform/features/conductor-v2/conductor/src/ui/MctConductorAxis.js @@ -20,7 +20,7 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -define([], function () { +define(['./ConductorAxisController'], function (ConductorAxisController) { function MctConductorAxis() { /** @@ -30,11 +30,15 @@ define([], function () { */ return { - controller: 'ConductorAxisController', + controller: [ + 'openmct', + 'formatService', + 'timeConductorViewService', + '$scope', + '$element', + ConductorAxisController + ], controllerAs: 'axis', - link: function(scope, element, attrs, controller){ - controller.link(scope, element); - }, restrict: 'E', priority: 1000, diff --git a/platform/features/conductor-v2/conductor/src/ui/TimeConductorController.js b/platform/features/conductor-v2/conductor/src/ui/TimeConductorController.js index b81c884265..eb0e1007ca 100644 --- a/platform/features/conductor-v2/conductor/src/ui/TimeConductorController.js +++ b/platform/features/conductor-v2/conductor/src/ui/TimeConductorController.js @@ -114,34 +114,39 @@ define( this.conductorViewService.off('pan-stop', this.onPanStop); }; - TimeConductorController.prototype.onPan = function (bounds) { - this.panning = true; - this.$scope.boundsModel.start = bounds.start; - this.$scope.boundsModel.end = bounds.end; - }; - - TimeConductorController.prototype.onPanStop = function () { - this.panning = false; - }; - TimeConductorController.prototype.changeBounds = function (bounds) { if (!this.zooming && !this.panning) { this.setFormFromBounds(bounds); } }; + /** + * Does the currently selected time system support zooming? To + * support zooming a time system must, at a minimum, define some + * values for maximum and minimum zoom levels. Additionally + * TimeFormats, a related concept, may also support providing time + * unit feedback for the zoom level label, eg "seconds, minutes, + * hours, etc..." + * @returns {boolean} + */ + TimeConductorController.prototype.supportsZoom = function () { + var timeSystem = this.conductor.timeSystem(); + return timeSystem && + timeSystem.defaults() && + timeSystem.defaults().zoom; + }; + /** * Called when the bounds change in the time conductor. Synchronizes * the bounds values in the time conductor with those in the form - * - * @private + * @param {TimeConductorBounds} */ TimeConductorController.prototype.setFormFromBounds = function (bounds) { if (!this.zooming && ! this.panning) { this.$scope.boundsModel.start = bounds.start; this.$scope.boundsModel.end = bounds.end; - if (this.supportsZoom) { + if (this.supportsZoom()) { this.currentZoom = this.toSliderValue(bounds.end - bounds.start); this.toTimeUnits(bounds.end - bounds.start); } @@ -157,7 +162,9 @@ define( }; /** - * @private + * On mode change, populate form based on time systems available + * from the selected mode. + * @param mode */ TimeConductorController.prototype.setFormFromMode = function (mode) { this.$scope.modeModel.selectedKey = mode; @@ -169,25 +176,22 @@ define( }); }; - /** - * @private - */ TimeConductorController.prototype.setFormFromDeltas = function (deltas) { this.$scope.boundsModel.startDelta = deltas.start; this.$scope.boundsModel.endDelta = deltas.end; }; /** - * @private + * Initialize the form when time system changes. + * @param timeSystem */ TimeConductorController.prototype.setFormFromTimeSystem = function (timeSystem) { var timeSystemModel = this.$scope.timeSystemModel; timeSystemModel.selected = timeSystem; timeSystemModel.format = timeSystem.formats()[0]; timeSystemModel.deltaFormat = timeSystem.deltaFormat(); - this.supportsZoom = timeSystem.defaults().zoom !== undefined; - if (this.supportsZoom) { + if (this.supportsZoom()) { timeSystemModel.minZoom = timeSystem.defaults().zoom.min; timeSystemModel.maxZoom = timeSystem.defaults().zoom.max; } @@ -261,7 +265,6 @@ define( * Sets the selected time system. Will populate form with the default * bounds and deltas defined in the selected time system. * - * @private * @param newTimeSystem */ TimeConductorController.prototype.changeTimeSystem = function (newTimeSystem) { @@ -277,6 +280,12 @@ define( } }; + /** + * Takes a time span and calculates a slider increment value, used + * to set the horizontal offset of the slider. + * @param {number} timeSpan a duration of time, in ms + * @returns {number} a value between 0.01 and 0.99, in increments of .01 + */ TimeConductorController.prototype.toSliderValue = function (timeSpan) { var timeSystem = this.conductor.timeSystem(); if (timeSystem) { @@ -286,6 +295,11 @@ define( } }; + /** + * Given a time span, set a label for the units of time that it, + * roughly, represents. Leverages + * @param {TimeSpan} timeSpan + */ TimeConductorController.prototype.toTimeUnits = function (timeSpan) { if (this.conductor.timeSystem()) { var timeFormat = this.formatService.getFormat(this.conductor.timeSystem().formats()[0]); @@ -293,7 +307,14 @@ define( } }; - TimeConductorController.prototype.zoomDrag = function(sliderValue) { + /** + * Zooming occurs when the user manipulates the zoom slider. + * Zooming updates the scale and bounds fields immediately, but does + * not trigger a bounds change to other views until the mouse button + * is released. + * @param bounds + */ + TimeConductorController.prototype.onZoom = function(sliderValue) { var zoomDefaults = this.conductor.timeSystem().defaults().zoom; var timeSpan = Math.pow((1 - sliderValue), 4) * (zoomDefaults.min - zoomDefaults.max); @@ -309,7 +330,7 @@ define( } }; - TimeConductorController.prototype.zoomStop = function () { + TimeConductorController.prototype.onZoomStop = function () { this.updateBoundsFromForm(this.$scope.boundsModel); this.updateDeltasFromForm(this.$scope.boundsModel); this.zooming = false; @@ -317,6 +338,24 @@ define( this.conductorViewService.emit('zoom-stop'); }; + /** + * Panning occurs when the user grabs the conductor scale and drags + * it left or right to slide the window of time represented by the + * conductor. Panning updates the scale and bounds fields + * immediately, but does not trigger a bounds change to other views + * until the mouse button is released. + * @param bounds + */ + TimeConductorController.prototype.onPan = function (bounds) { + this.panning = true; + this.$scope.boundsModel.start = bounds.start; + this.$scope.boundsModel.end = bounds.end; + }; + + TimeConductorController.prototype.onPanStop = function () { + this.panning = false; + }; + return TimeConductorController; } ); diff --git a/platform/features/conductor-v2/conductor/src/ui/TimeConductorMode.js b/platform/features/conductor-v2/conductor/src/ui/TimeConductorMode.js index 58790fbf2d..d48dd8dbd3 100644 --- a/platform/features/conductor-v2/conductor/src/ui/TimeConductorMode.js +++ b/platform/features/conductor-v2/conductor/src/ui/TimeConductorMode.js @@ -186,6 +186,11 @@ define( return this.dlts; }; + /** + * + * @param deltas + * @returns {TimeConductorBounds} + */ TimeConductorMode.prototype.calculateBoundsFromDeltas = function (deltas) { var oldEnd = this.conductor.bounds().end; @@ -202,9 +207,9 @@ define( }; /** - * Performs zoom calculation. Will calculate new bounds and deltas - * based on desired timeSpan - * @param timeSpan + * Calculates bounds and deltas based on a timeSpan. Collectively + * the bounds and deltas will constitute the new zoom level. + * @param {number} timeSpan time duration in ms. */ TimeConductorMode.prototype.calculateZoom = function (timeSpan) { var zoom = {}; diff --git a/platform/features/conductor-v2/conductor/src/ui/TimeConductorViewService.js b/platform/features/conductor-v2/conductor/src/ui/TimeConductorViewService.js index 891c09c1fe..792141f427 100644 --- a/platform/features/conductor-v2/conductor/src/ui/TimeConductorViewService.js +++ b/platform/features/conductor-v2/conductor/src/ui/TimeConductorViewService.js @@ -32,6 +32,7 @@ define( * exposes details of the UI that are not represented on the * TimeConductor API itself such as modes and deltas. * + * @memberof platform.features.conductor * @param conductor * @param timeSystems * @constructor @@ -203,6 +204,15 @@ define( return this.currentMode.availableTimeSystems(); }; + /** + * Zoom to given time span. Will fire a zoom event with new zoom + * bounds. Zoom bounds emitted in this way are considered ephemeral + * and should be overridden by any time conductor bounds events. Does + * not set bounds globally. + * @param {number} zoom A time duration in ms + * @fires platform.features.conductor.TimeConductorViewService~zoom + * @see module:openmct.TimeConductor#bounds + */ TimeConductorViewService.prototype.zoom = function (timeSpan) { var zoom = this.currentMode.calculateZoom(timeSpan); this.emit("zoom", zoom); diff --git a/platform/features/conductor-v2/conductor/src/ui/TimeOfInterestController.js b/platform/features/conductor-v2/conductor/src/ui/TimeOfInterestController.js index 0eb01fb566..deafd80270 100644 --- a/platform/features/conductor-v2/conductor/src/ui/TimeOfInterestController.js +++ b/platform/features/conductor-v2/conductor/src/ui/TimeOfInterestController.js @@ -65,6 +65,10 @@ define( } }; + /** + * When time system is changed, update the formatter used to + * display the current TOI label + */ TimeOfInterestController.prototype.changeTimeSystem = function (timeSystem) { this.format = this.formatService.getFormat(timeSystem.formats()[0]); }; @@ -78,6 +82,10 @@ define( this.conductor.timeOfInterest(undefined); }; + /** + * Sends out a time of interest event with the effect of resetting + * the TOI displayed in views. + */ TimeOfInterestController.prototype.resync = function () { this.conductor.timeOfInterest(this.conductor.timeOfInterest()); }; diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js index c1ca6c3a50..c11e2e44f8 100644 --- a/platform/features/plot/src/PlotController.js +++ b/platform/features/plot/src/PlotController.js @@ -195,6 +195,9 @@ define( true // Lossless ); replot(); + + changeTimeOfInterest(conductor.timeOfInterest()); + conductor.on("timeOfInterest", changeTimeOfInterest); } // Release the current subscription (called when scope is destroyed) @@ -280,10 +283,6 @@ define( new PlotAxis("ranges", [], AXIS_DEFAULTS[1]) ]; - changeTimeOfInterest(conductor.timeOfInterest()); - - conductor.on("timeOfInterest", changeTimeOfInterest); - // Watch for changes to the selected axis $scope.$watch("axes[0].active.key", domainRequery); $scope.$watch("axes[1].active.key", rangeRequery); diff --git a/platform/features/table/src/controllers/MCTTableController.js b/platform/features/table/src/controllers/MCTTableController.js index d46b35f765..f7533a62c1 100644 --- a/platform/features/table/src/controllers/MCTTableController.js +++ b/platform/features/table/src/controllers/MCTTableController.js @@ -98,6 +98,10 @@ define( $scope.$on('add:row', this.addRow); $scope.$on('remove:row', this.removeRow); + /** + * Populated from the default-sort attribute on MctTable + * directive tag. + */ $scope.$watch('defaultSort', function (defaultSort) { $scope.sortColumn = defaultSort; $scope.sortDirection = 'asc'; @@ -108,7 +112,11 @@ define( */ $scope.resize = this.setElementSizes; - // Time conductor integration + /** + * Scope variable that is populated from the 'time-columns' + * attribute on the MctTable tag. Indicates which columns, while + * sorted, can be used for indicated time of interest. + */ $scope.$watch("timeColumns", function (timeColumns){ if (timeColumns) { this.destroyConductorListeners(); @@ -208,6 +216,7 @@ define( }; /** + * Return first visible row, based on current scroll state. * @private */ MCTTableController.prototype.firstVisible = function () { @@ -228,6 +237,7 @@ define( }; /** + * Return last visible row, based on current scroll state. * @private */ MCTTableController.prototype.lastVisible = function () { @@ -550,8 +560,11 @@ define( this.$scope.displayRows = this.filterAndSort(newRows || []); this.resize(newRows).then(this.setVisibleRows) + //Timeout following setVisibleRows to allow digest to + // perform DOM changes, otherwise scrollTo won't work. .then(this.$timeout) .then(function() { + //If TOI specified, scroll to it var timeOfInterest = this.conductor.timeOfInterest(); if (timeOfInterest) { this.setTimeOfInterest(timeOfInterest); diff --git a/platform/features/table/src/controllers/TelemetryTableController.js b/platform/features/table/src/controllers/TelemetryTableController.js index c5f3521078..e03959a47a 100644 --- a/platform/features/table/src/controllers/TelemetryTableController.js +++ b/platform/features/table/src/controllers/TelemetryTableController.js @@ -77,6 +77,11 @@ define( this.conductor.off('timeSystem', this.sortByTimeSystem); } + /** + * Based on the selected time system, find a matching domain column + * to sort by. By default will just match on key. + * @param timeSystem + */ TelemetryTableController.prototype.sortByTimeSystem = function (timeSystem) { var scope = this.$scope; scope.defaultSort = undefined; @@ -89,9 +94,6 @@ define( } }; - /** - * @private - */ TelemetryTableController.prototype.unregisterChangeListeners = function () { this.changeListeners.forEach(function (listener) { return listener && listener(); diff --git a/platform/features/table/src/directives/MCTTable.js b/platform/features/table/src/directives/MCTTable.js index 6670e0a457..4c26d46ea8 100644 --- a/platform/features/table/src/directives/MCTTable.js +++ b/platform/features/table/src/directives/MCTTable.js @@ -97,7 +97,12 @@ define( enableFilter: "=?", enableSort: "=?", autoScroll: "=?", + // Used to indicate which columns contain time data. This + // will be used for determining when the table is sorted + // by the column that can be used for time conductor + // time of interest. timeColumns: "=?", + // Indicate the column that should be sorted on by default defaultSort: "=?" } }; diff --git a/src/api/TimeConductor.js b/src/api/TimeConductor.js index fa0607e487..ed5c228551 100644 --- a/src/api/TimeConductor.js +++ b/src/api/TimeConductor.js @@ -54,10 +54,6 @@ define(['EventEmitter'], function (EventEmitter) { TimeConductor.prototype = Object.create(EventEmitter.prototype); - TimeConductor.prototype.on = function (event) { - EventEmitter.prototype.on.apply(this, arguments); - }; - /** * Validate the given bounds. This can be used for pre-validation of * bounds, for example by views validating user inputs.