diff --git a/platform/commonUI/formats/src/UTCTimeFormat.js b/platform/commonUI/formats/src/UTCTimeFormat.js
index 3faab7a620..26919e3160 100644
--- a/platform/commonUI/formats/src/UTCTimeFormat.js
+++ b/platform/commonUI/formats/src/UTCTimeFormat.js
@@ -95,6 +95,38 @@ define([
})[0][0];
}
+ UTCTimeFormat.prototype.timeUnits = function (timeRange) {
+ var momentified = moment.duration(timeRange);
+ return [
+ ["Decades", function (r) {
+ return r.years() > 15;
+ }],
+ ["Years", function (r) {
+ return r.years() > 1;
+ }],
+ ["Months", function (r) {
+ return r.years() === 1 || r.months() > 1;
+ }],
+ ["Days", function (r) {
+ return r.months() === 1 || r.days() > 1;
+ }],
+ ["Hours", function (r) {
+ return r.days() === 1 || r.hours() > 1;
+ }],
+ ["Minutes", function (r) {
+ return r.hours() === 1 || r.minutes() > 1;
+ }],
+ ["Seconds", function (r) {
+ return r.minutes() === 1 || r.seconds() > 1;
+ }],
+ ["Milliseconds", function (r) {
+ return true;
+ }]
+ ].filter(function (row){
+ return row[1](momentified);
+ })[0][0];
+ };
+
/**
*
* @param value
diff --git a/platform/features/conductor-v2/compatibility/src/ConductorRepresenter.js b/platform/features/conductor-v2/compatibility/src/ConductorRepresenter.js
index fe19bbc66a..7013906ef9 100644
--- a/platform/features/conductor-v2/compatibility/src/ConductorRepresenter.js
+++ b/platform/features/conductor-v2/compatibility/src/ConductorRepresenter.js
@@ -85,6 +85,7 @@ define(
ConductorRepresenter.prototype.destroy = function destroy() {
this.conductor.off("bounds", this.boundsListener);
this.conductor.off("timeSystem", this.timeSystemListener);
+ this.conductor.off("follow", this.followListener);
};
return ConductorRepresenter;
diff --git a/platform/features/conductor-v2/conductor/bundle.js b/platform/features/conductor-v2/conductor/bundle.js
index 1fbeebdea7..c9520de1bb 100644
--- a/platform/features/conductor-v2/conductor/bundle.js
+++ b/platform/features/conductor-v2/conductor/bundle.js
@@ -69,7 +69,17 @@ define([
"$window",
"timeConductor",
"timeConductorViewService",
- "timeSystems[]"
+ "timeSystems[]",
+ "formatService"
+ ]
+ },
+ {
+ "key": "ConductorAxisController",
+ "implementation": ConductorAxisController,
+ "depends": [
+ "timeConductor",
+ "formatService",
+ "timeConductorViewService"
]
},
{
diff --git a/platform/features/conductor-v2/conductor/res/sass/_time-conductor-base.scss b/platform/features/conductor-v2/conductor/res/sass/_time-conductor-base.scss
index b48c3177e6..722adbdb8c 100644
--- a/platform/features/conductor-v2/conductor/res/sass/_time-conductor-base.scss
+++ b/platform/features/conductor-v2/conductor/res/sass/_time-conductor-base.scss
@@ -312,13 +312,13 @@
.l-time-conductor-zoom-w {
@include justify-content(flex-end);
.time-conductor-zoom {
- display: none; // TEMP per request from Andrew 8/1/16
+ //display: none; // TEMP per request from Andrew 8/1/16
height: $r3H;
min-width: 100px;
width: 20%;
}
.time-conductor-zoom-current-range {
- display: none; // TEMP per request from Andrew 8/1/16
+ //display: none; // TEMP per request from Andrew 8/1/16
color: $colorTick;
}
}
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 5dc88c8b1d..2a385bf3ed 100644
--- a/platform/features/conductor-v2/conductor/res/templates/time-conductor.html
+++ b/platform/features/conductor-v2/conductor/res/templates/time-conductor.html
@@ -131,8 +131,15 @@
-
-
+ {{timeUnits}}
+
diff --git a/platform/features/conductor-v2/conductor/src/timeSystems/TimeSystem.js b/platform/features/conductor-v2/conductor/src/timeSystems/TimeSystem.js
index 652ea9ed0f..51987b39c8 100644
--- a/platform/features/conductor-v2/conductor/src/timeSystems/TimeSystem.js
+++ b/platform/features/conductor-v2/conductor/src/timeSystems/TimeSystem.js
@@ -73,8 +73,20 @@ define([], function () {
throw new Error('Not implemented');
};
- /**
+ /***
*
+ * @typedef {object} TimeConductorZoom
+ * @property {number} min The largest time span that the time
+ * conductor can display in this time system
+ * @property {number} max The smallest time span that the time
+ * conductor can display in this time system
+ *
+ * @typedef {object} TimeSystemDefault
+ * @property {TimeConductorDeltas} deltas The deltas to apply by default
+ * when this time system is active. Applies to real-time modes only
+ * @property {TimeConductorBounds} bounds The bounds to apply by default
+ * when this time system is active
+ * @property {TimeConductorZoom} zoom Default min and max zoom levels
* @returns {TimeSystemDefault[]} At least one set of default values for
* this time system.
*/
diff --git a/platform/features/conductor-v2/conductor/src/ui/ConductorAxisController.js b/platform/features/conductor-v2/conductor/src/ui/ConductorAxisController.js
index b88177181d..2fbe958e45 100644
--- a/platform/features/conductor-v2/conductor/src/ui/ConductorAxisController.js
+++ b/platform/features/conductor-v2/conductor/src/ui/ConductorAxisController.js
@@ -32,11 +32,12 @@ define(
* labelled 'ticks'. It requires 'start' and 'end' integer values to
* be specified as attributes.
*/
- function ConductorAxisController(conductor, formatService) {
+ function ConductorAxisController(conductor, formatService, conductorViewService) {
// Dependencies
this.d3 = d3;
this.formatService = formatService;
this.conductor = conductor;
+ this.conductorViewService = conductorViewService;
// Runtime properties (set by 'link' function)
this.target = undefined;
@@ -46,14 +47,22 @@ define(
this.initialized = false;
this.msPerPixel = undefined;
- this.setScale = this.setScale.bind(this);
- this.changeBounds = this.changeBounds.bind(this);
- this.changeTimeSystem = this.changeTimeSystem.bind(this);
-
this.bounds = conductor.bounds();
this.timeSystem = conductor.timeSystem();
+
+ //Bind all class functions to 'this'
+ Object.keys(ConductorAxisController.prototype).filter(function (key) {
+ return typeof ConductorAxisController.prototype[key] === 'function';
+ }).forEach(function (key) {
+ this[key] = ConductorAxisController.prototype[key].bind(this);
+ }.bind(this));
}
+ ConductorAxisController.prototype.destroy = function () {
+ this.conductor.off('timeSystem', this.changeTimeSystem);
+ this.conductor.off('bounds', this.setScale);
+ };
+
ConductorAxisController.prototype.changeBounds = function (bounds) {
this.bounds = bounds;
if (this.initialized) {
@@ -129,18 +138,26 @@ define(
if (this.timeSystem !== undefined) {
this.changeTimeSystem(this.timeSystem);
- this.setScale(this.bounds);
+ this.setScale();
}
//Respond to changes in conductor
this.conductor.on("timeSystem", this.changeTimeSystem);
this.conductor.on("bounds", this.changeBounds);
+
+ this.scope.$on("$destroy", this.destroy);
+
+ this.conductorViewService.on("zoom", this.zoom);
};
- ConductorAxisController.prototype.panEnd = function () {
+ ConductorAxisController.prototype.panStop = function () {
//resync view bounds with time conductor bounds
this.conductor.bounds(this.bounds);
- this.scope.$emit("pan-stop");
+ this.conductorViewService.emit("pan-stop");
+ };
+
+ ConductorAxisController.prototype.zoom = function (bounds) {
+ this.changeBounds(bounds);
};
ConductorAxisController.prototype.pan = function (delta) {
@@ -154,7 +171,7 @@ define(
end: end
};
this.setScale();
- this.scope.$emit("pan", this.bounds);
+ this.conductorViewService.emit("pan", this.bounds);
}
};
diff --git a/platform/features/conductor-v2/conductor/src/ui/MctConductorAxis.js b/platform/features/conductor-v2/conductor/src/ui/MctConductorAxis.js
index 8b874badf2..d664057af6 100644
--- a/platform/features/conductor-v2/conductor/src/ui/MctConductorAxis.js
+++ b/platform/features/conductor-v2/conductor/src/ui/MctConductorAxis.js
@@ -41,7 +41,7 @@ define([], function () {
template: ''
}
diff --git a/platform/features/conductor-v2/conductor/src/ui/MctConductorAxisSpec.js b/platform/features/conductor-v2/conductor/src/ui/MctConductorAxisSpec.js
index 5c45521ea4..0fe3c4cf3f 100644
--- a/platform/features/conductor-v2/conductor/src/ui/MctConductorAxisSpec.js
+++ b/platform/features/conductor-v2/conductor/src/ui/MctConductorAxisSpec.js
@@ -32,7 +32,9 @@ define(['./MctConductorAxis'], function (MctConductorAxis) {
d3;
beforeEach(function () {
- mockScope = {};
+ mockScope = jasmine.createSpyObj("scope", [
+ "$on"
+ ]);
//Add some HTML elements
mockTarget = {
@@ -49,7 +51,8 @@ define(['./MctConductorAxis'], function (MctConductorAxis) {
mockConductor = jasmine.createSpyObj("conductor", [
"timeSystem",
"bounds",
- "on"
+ "on",
+ "off"
]);
mockConductor.bounds.andReturn(mockBounds);
@@ -85,6 +88,13 @@ define(['./MctConductorAxis'], function (MctConductorAxis) {
expect(mockConductor.on).toHaveBeenCalledWith("bounds", directive.setScale);
});
+ it("on scope destruction, deregisters listeners", function () {
+ expect(mockScope.$on).toHaveBeenCalledWith("$destroy", directive.destroy);
+ directive.destroy();
+ expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", directive.changeTimeSystem);
+ expect(mockConductor.off).toHaveBeenCalledWith("bounds", directive.setScale);
+ });
+
describe("when the time system changes", function () {
var mockTimeSystem;
var mockFormat;
diff --git a/platform/features/conductor-v2/conductor/src/ui/TimeConductorController.js b/platform/features/conductor-v2/conductor/src/ui/TimeConductorController.js
index 3026232f18..176d1bbb2f 100644
--- a/platform/features/conductor-v2/conductor/src/ui/TimeConductorController.js
+++ b/platform/features/conductor-v2/conductor/src/ui/TimeConductorController.js
@@ -26,7 +26,7 @@ define(
],
function (TimeConductorValidation) {
- function TimeConductorController($scope, $window, timeConductor, conductorViewService, timeSystems) {
+ function TimeConductorController($scope, $window, timeConductor, conductorViewService, timeSystems, formatService) {
var self = this;
@@ -43,6 +43,7 @@ define(
this.conductor = timeConductor;
this.modes = conductorViewService.availableModes();
this.validation = new TimeConductorValidation(this.conductor);
+ this.formatService = formatService;
// Construct the provided time system definitions
this.timeSystems = timeSystems.map(function (timeSystemConstructor) {
@@ -53,9 +54,6 @@ define(
this.initializeScope();
this.conductor.on('bounds', this.setFormFromBounds);
- this.conductor.on('follow', function (follow) {
- $scope.followMode = follow;
- });
this.conductor.on('timeSystem', this.changeTimeSystem);
// If no mode selected, select fixed as the default
@@ -100,14 +98,25 @@ define(
// Watch scope for selection of mode or time system by user
this.$scope.$watch('modeModel.selectedKey', this.setMode);
- this.$scope.$on('pan', function (e, bounds) {
- this.$scope.panning = true;
- this.setFormFromBounds(bounds);
- }.bind(this));
+ this.conductorViewService.on('pan', this.pan);
- this.$scope.$on('pan-stop', function () {
- this.$scope.panning = false;
- }.bind(this));
+ this.conductorViewService.on('pan-stop', this.panStop);
+
+ this.$scope.$on('$destroy', this.destroy);
+ };
+
+ TimeConductorController.prototype.destroy = function () {
+ this.conductor.off('bounds', this.setFormFromBounds);
+ this.conductor.off('timeSystem', this.changeTimeSystem);
+ };
+
+ TimeConductorController.prototype.pan = function (bounds) {
+ this.$scope.panning = true;
+ this.setFormFromBounds(bounds);
+ };
+
+ TimeConductorController.prototype.panStop = function () {
+ this.$scope.panning = false;
};
/**
@@ -119,6 +128,10 @@ define(
TimeConductorController.prototype.setFormFromBounds = function (bounds) {
this.$scope.boundsModel.start = bounds.start;
this.$scope.boundsModel.end = bounds.end;
+
+ this.$scope.currentZoom = this.toSliderValue(bounds.end - bounds.start);
+ this.toTimeUnits(bounds.end - bounds.start);
+
if (!this.pendingUpdate) {
this.pendingUpdate = true;
this.$window.requestAnimationFrame(function () {
@@ -153,9 +166,12 @@ define(
* @private
*/
TimeConductorController.prototype.setFormFromTimeSystem = function (timeSystem) {
- this.$scope.timeSystemModel.selected = timeSystem;
- this.$scope.timeSystemModel.format = timeSystem.formats()[0];
- this.$scope.timeSystemModel.deltaFormat = timeSystem.deltaFormat();
+ var timeSystemModel = this.$scope.timeSystemModel;
+ timeSystemModel.selected = timeSystem;
+ timeSystemModel.format = timeSystem.formats()[0];
+ timeSystemModel.deltaFormat = timeSystem.deltaFormat();
+ timeSystemModel.minZoom = timeSystem.defaults().zoom.min;
+ timeSystemModel.maxZoom = timeSystem.defaults().zoom.max;
};
@@ -242,6 +258,41 @@ define(
}
};
+ TimeConductorController.prototype.toSliderValue = function (timeSpan) {
+ var timeSystem = this.conductor.timeSystem();
+ if (timeSystem) {
+ var zoomDefaults = this.conductor.timeSystem().defaults().zoom;
+ var perc = timeSpan / (zoomDefaults.min - zoomDefaults.max);
+ return 1 - Math.pow(perc, 1 / 4);
+ }
+ };
+
+ TimeConductorController.prototype.toTimeSpan = function (sliderValue) {
+ var center = this.$scope.boundsModel.start +
+ ((this.$scope.boundsModel.end - this.$scope.boundsModel.start) / 2);
+ var zoomDefaults = this.conductor.timeSystem().defaults().zoom;
+ var timeSpan = Math.pow((1 - sliderValue), 4) * (zoomDefaults.min - zoomDefaults.max);
+ return {start: center - timeSpan / 2, end: center + timeSpan / 2};
+ };
+
+ TimeConductorController.prototype.toTimeUnits = function (timeSpan) {
+ if (this.conductor.timeSystem()) {
+ var timeFormat = this.formatService.getFormat(this.conductor.timeSystem().formats()[0]);
+ this.$scope.timeUnits = timeFormat.timeUnits && timeFormat.timeUnits(timeSpan);
+ }
+ }
+
+ TimeConductorController.prototype.zoom = function(sliderValue) {
+ var bounds = this.toTimeSpan(sliderValue);
+ this.setFormFromBounds(bounds);
+ this.conductorViewService.emit("zoom", bounds);
+ };
+
+ TimeConductorController.prototype.zoomStop = function (sliderValue) {
+ var bounds = this.toTimeSpan(sliderValue);
+ this.conductor.bounds(bounds);
+ };
+
return TimeConductorController;
}
);
diff --git a/platform/features/conductor-v2/conductor/src/ui/TimeConductorControllerSpec.js b/platform/features/conductor-v2/conductor/src/ui/TimeConductorControllerSpec.js
index 7d1457eafe..39759c60f5 100644
--- a/platform/features/conductor-v2/conductor/src/ui/TimeConductorControllerSpec.js
+++ b/platform/features/conductor-v2/conductor/src/ui/TimeConductorControllerSpec.js
@@ -30,14 +30,18 @@ define(['./TimeConductorController'], function (TimeConductorController) {
var controller;
beforeEach(function () {
- mockScope = jasmine.createSpyObj("$scope", ["$watch"]);
+ mockScope = jasmine.createSpyObj("$scope", [
+ "$watch",
+ "$on"
+ ]);
mockWindow = jasmine.createSpyObj("$window", ["requestAnimationFrame"]);
mockTimeConductor = jasmine.createSpyObj(
"TimeConductor",
[
"bounds",
"timeSystem",
- "on"
+ "on",
+ "off"
]
);
mockTimeConductor.bounds.andReturn({start: undefined, end: undefined});
@@ -124,9 +128,16 @@ define(['./TimeConductorController'], function (TimeConductorController) {
});
it("listens for changes to conductor state", function () {
- expect(mockTimeConductor.on).toHaveBeenCalledWith("timeSystem", jasmine.any(Function));
- expect(mockTimeConductor.on).toHaveBeenCalledWith("bounds", jasmine.any(Function));
- expect(mockTimeConductor.on).toHaveBeenCalledWith("follow", jasmine.any(Function));
+ expect(mockTimeConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
+ expect(mockTimeConductor.on).toHaveBeenCalledWith("bounds", controller.setFormFromBounds);
+ });
+
+ it("deregisters conductor listens when scope is destroyed", function () {
+ expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy);
+
+ controller.destroy();
+ expect(mockTimeConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
+ expect(mockTimeConductor.off).toHaveBeenCalledWith("bounds", controller.setFormFromBounds);
});
it("when time system changes, sets time system on scope", function () {
@@ -164,17 +175,6 @@ define(['./TimeConductorController'], function (TimeConductorController) {
expect(mockScope.boundsModel.start).toEqual(bounds.start);
expect(mockScope.boundsModel.end).toEqual(bounds.end);
});
-
- it("responds to a change in 'follow' state of the time conductor", function () {
- var followListener = getListener("follow");
- expect(followListener).toBeDefined();
-
- followListener(true);
- expect(mockScope.followMode).toEqual(true);
-
- followListener(false);
- expect(mockScope.followMode).toEqual(false);
- });
});
describe("when user makes changes from UI", function () {
diff --git a/platform/features/conductor-v2/conductor/src/ui/TimeConductorViewService.js b/platform/features/conductor-v2/conductor/src/ui/TimeConductorViewService.js
index 8cbc349520..9796e2bc3b 100644
--- a/platform/features/conductor-v2/conductor/src/ui/TimeConductorViewService.js
+++ b/platform/features/conductor-v2/conductor/src/ui/TimeConductorViewService.js
@@ -22,9 +22,10 @@
define(
[
+ 'EventEmitter',
'./TimeConductorMode'
],
- function (TimeConductorMode) {
+ function (EventEmitter, TimeConductorMode) {
/**
* A class representing the state of the time conductor view. This
@@ -36,6 +37,9 @@ define(
* @constructor
*/
function TimeConductorViewService(conductor, timeSystems) {
+
+ EventEmitter.call(this);
+
this.systems = timeSystems.map(function (timeSystemConstructor) {
return timeSystemConstructor();
});
@@ -97,6 +101,8 @@ define(
}
}
+ TimeConductorViewService.prototype = Object.create(EventEmitter.prototype);
+
/**
* Getter/Setter for the Time Conductor Mode. Modes determine the
* behavior of the time conductor, especially with regards to the
diff --git a/platform/features/conductor-v2/utcTimeSystem/src/UTCTimeSystem.js b/platform/features/conductor-v2/utcTimeSystem/src/UTCTimeSystem.js
index b6e969c3eb..780bfa8db1 100644
--- a/platform/features/conductor-v2/utcTimeSystem/src/UTCTimeSystem.js
+++ b/platform/features/conductor-v2/utcTimeSystem/src/UTCTimeSystem.js
@@ -64,13 +64,17 @@ define([
return this.sources;
};
- UTCTimeSystem.prototype.defaults = function (key) {
+ UTCTimeSystem.prototype.defaults = function () {
var now = Math.ceil(Date.now() / 1000) * 1000;
+ var ONE_MINUTE = 60 * 1 * 1000;
+ var FIFTY_YEARS = 50 * 365 * 24 * 60 * 60 * 1000;
+
return {
key: 'utc-default',
name: 'UTC time system defaults',
deltas: {start: FIFTEEN_MINUTES, end: 0},
- bounds: {start: now - FIFTEEN_MINUTES, end: now}
+ bounds: {start: now - FIFTEEN_MINUTES, end: now},
+ zoom: {min: FIFTY_YEARS, max: ONE_MINUTE}
};
};