diff --git a/bower.json b/bower.json
index 7c913754cf3..22294e93564 100644
--- a/bower.json
+++ b/bower.json
@@ -18,6 +18,8 @@
"node-uuid": "^1.4.7",
"comma-separated-values": "^3.6.4",
"FileSaver.js": "^0.0.2",
- "zepto": "^1.1.6"
+ "zepto": "^1.1.6",
+ "eventemitter3": "^1.2.0",
+ "d3": "~4.1.0"
}
}
diff --git a/main.js b/main.js
index 4fba458a4c7..5873209adea 100644
--- a/main.js
+++ b/main.js
@@ -28,13 +28,15 @@ requirejs.config({
"angular-route": "bower_components/angular-route/angular-route.min",
"csv": "bower_components/comma-separated-values/csv.min",
"es6-promise": "bower_components/es6-promise/promise.min",
+ "EventEmitter": "bower_components/eventemitter3/index",
"moment": "bower_components/moment/moment",
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
"screenfull": "bower_components/screenfull/dist/screenfull.min",
"text": "bower_components/text/text",
"uuid": "bower_components/node-uuid/uuid",
- "zepto": "bower_components/zepto/zepto.min"
+ "zepto": "bower_components/zepto/zepto.min",
+ "d3": "bower_components/d3/d3.min"
},
"shim": {
"angular": {
@@ -43,6 +45,9 @@ requirejs.config({
"angular-route": {
"deps": ["angular"]
},
+ "EventEmitter": {
+ "exports": "EventEmitter"
+ },
"moment-duration-format": {
"deps": ["moment"]
},
@@ -51,6 +56,9 @@ requirejs.config({
},
"zepto": {
"exports": "Zepto"
+ },
+ "d3": {
+ "exports": "d3"
}
}
});
@@ -82,6 +90,7 @@ define([
'./platform/features/pages/bundle',
'./platform/features/plot/bundle',
'./platform/features/timeline/bundle',
+ './platform/features/conductor-v2/bundle',
'./platform/features/table/bundle',
'./platform/forms/bundle',
'./platform/identity/bundle',
diff --git a/platform/commonUI/browse/res/templates/browse-object.html b/platform/commonUI/browse/res/templates/browse-object.html
index 123d96bbd64..fffb0172455 100644
--- a/platform/commonUI/browse/res/templates/browse-object.html
+++ b/platform/commonUI/browse/res/templates/browse-object.html
@@ -43,7 +43,7 @@
-
+
diff --git a/platform/features/conductor-v2/bundle.js b/platform/features/conductor-v2/bundle.js
new file mode 100644
index 00000000000..279e4904589
--- /dev/null
+++ b/platform/features/conductor-v2/bundle.js
@@ -0,0 +1,82 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+define([
+ "./src/TimeConductor",
+ "./src/TimeConductorController",
+ "./src/MCTConductorAxis",
+ "text!./res/templates/time-conductor.html",
+ "text!./res/templates/mode-selector/mode-selector.html",
+ "text!./res/templates/mode-selector/mode-menu.html",
+ 'legacyRegistry'
+], function (
+ TimeConductor,
+ TimeConductorController,
+ MCTConductorAxis,
+ timeConductorTemplate,
+ modeSelectorTemplate,
+ modeMenuTemplate,
+ legacyRegistry
+) {
+
+ legacyRegistry.register("platform/features/conductor-v2", {
+ "extensions": {
+ "services": [
+ {
+ "key": "timeConductor",
+ "implementation": TimeConductor
+ }
+ ],
+ "controllers": [
+ {
+ "key": "TimeConductorController",
+ "implementation": TimeConductorController,
+ "depends": [
+ "$scope",
+ "timeConductor"
+ ]
+ }
+ ],
+ "directives": [
+ {
+ "key": "mctConductorAxis",
+ "implementation": MCTConductorAxis,
+ "depends": ["$timeout"]
+ }
+ ],
+ "representations": [
+ {
+ "key": "time-conductor",
+ "template": timeConductorTemplate
+ },
+ {
+ "key": "mode-selector",
+ "template": modeSelectorTemplate
+ },
+ {
+ "key": "mode-menu",
+ "template": modeMenuTemplate
+ }
+ ]
+ }
+ });
+});
diff --git a/platform/features/conductor-v2/res/templates/mode-selector/mode-menu.html b/platform/features/conductor-v2/res/templates/mode-selector/mode-menu.html
new file mode 100644
index 00000000000..d926037cc89
--- /dev/null
+++ b/platform/features/conductor-v2/res/templates/mode-selector/mode-menu.html
@@ -0,0 +1,80 @@
+
+
+
+
+
diff --git a/platform/features/conductor-v2/res/templates/mode-selector/mode-selector.html b/platform/features/conductor-v2/res/templates/mode-selector/mode-selector.html
new file mode 100644
index 00000000000..06cadb11821
--- /dev/null
+++ b/platform/features/conductor-v2/res/templates/mode-selector/mode-selector.html
@@ -0,0 +1,50 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/platform/features/conductor-v2/res/templates/time-conductor.html b/platform/features/conductor-v2/res/templates/time-conductor.html
new file mode 100644
index 00000000000..dd24b09a1bf
--- /dev/null
+++ b/platform/features/conductor-v2/res/templates/time-conductor.html
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/platform/features/conductor-v2/src/MctConductorAxis.js b/platform/features/conductor-v2/src/MctConductorAxis.js
new file mode 100644
index 00000000000..54bf4520ae2
--- /dev/null
+++ b/platform/features/conductor-v2/src/MctConductorAxis.js
@@ -0,0 +1,100 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+define(
+ [
+ "zepto",
+ "d3"
+ ],
+ function ($, d3) {
+
+ /**
+ * The mct-control will dynamically include the control
+ * for a form element based on a symbolic key. Individual
+ * controls are defined under the extension category
+ * `controls`; this allows plug-ins to introduce new form
+ * control types while still making use of the form
+ * generator to ensure an overall consistent form style.
+ * @constructor
+ * @memberof platform/forms
+ */
+ function MCTConductorAxis($timeout) {
+
+ function link(scope, element, attrs, ngModelController) {
+ $timeout(function () {
+ var target = element[0].firstChild,
+ width = target.offsetWidth,
+ height = target.offsetHeight,
+ padding = 1;
+
+ var vis = d3.select(target)
+ .append('svg:svg')
+ //.attr('viewBox', '0 0 ' + width + ' ' +
+ // height)
+ .attr('width', '100%')
+ .attr('height', height);
+ //.attr('preserveAspectRatio', 'xMidYMid meet');
+
+ // define the x scale (horizontal)
+ var mindate = new Date(2012,0,1),
+ maxdate = new Date(2016,0,1);
+
+ var xScale = d3.scaleTime()
+ .domain([mindate, maxdate])
+ .range([padding, width - padding * 2]);
+
+ var xAxis = d3.axisTop()
+ .scale(xScale);
+
+ // draw x axis with labels and move to the bottom of the chart area
+ var axisElement = vis.append("g")
+ .attr("class", "xaxis") // give it a class so it can be used to select only xaxis labels below
+ .attr("transform", "translate(0," + (height - padding) + ")");
+
+ axisElement.call(xAxis);
+ }, 1000);
+ }
+
+ return {
+ // Only show at the element level
+ restrict: "E",
+
+ template: "
",
+
+ // ngOptions is terminal, so we need to be higher priority
+ priority: 1000,
+
+ // Link function
+ link: link,
+
+ // Pass through Angular's normal input field attributes
+ scope: {
+ // Used to choose which form control to use
+ start: "=",
+ end: "="
+ }
+ };
+ }
+
+ return MCTConductorAxis;
+ }
+);
diff --git a/platform/features/conductor-v2/src/TimeConductor.js b/platform/features/conductor-v2/src/TimeConductor.js
new file mode 100644
index 00000000000..dd4a29d8ab5
--- /dev/null
+++ b/platform/features/conductor-v2/src/TimeConductor.js
@@ -0,0 +1,183 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+define(['EventEmitter'], function (EventEmitter) {
+
+ /**
+ * The public API for setting and querying time conductor state. The
+ * time conductor is the means by which the temporal bounds of a view
+ * are controlled. Time-sensitive views will typically respond to
+ * changes to bounds or other properties of the time conductor and
+ * update the data displayed based on the time conductor state.
+ *
+ * The TimeConductor extends the EventEmitter class. A number of events are
+ * fired when properties of the time conductor change, which are
+ * documented below.
+ * @constructor
+ */
+ function TimeConductor() {
+ EventEmitter.call(this);
+
+ //The Time System
+ this.system = undefined;
+ //The Time Of Interest
+ this.toi = undefined;
+
+ this.boundsVal = {
+ start: undefined,
+ end: undefined
+ };
+
+ //Default to fixed mode
+ this.followMode = false;
+ }
+
+ TimeConductor.prototype = Object.create(EventEmitter.prototype);
+
+ /**
+ * Validate the given bounds. This can be used for pre-validation of
+ * bounds, for example by views validating user inputs.
+ * @param bounds The start and end time of the conductor.
+ * @returns {string | true} A validation error, or true if valid
+ */
+ TimeConductor.prototype.validateBounds = function (bounds) {
+ if ((bounds.start === undefined) ||
+ (bounds.end === undefined) ||
+ isNaN(bounds.start) ||
+ isNaN(bounds.end)
+ ) {
+ return "Start and end must be specified as integer values";
+ } else if (bounds.start > bounds.end) {
+ return "Specified start date exceeds end bound";
+ }
+ return true;
+ };
+
+ function throwOnError(validationResult) {
+ if (validationResult !== true) {
+ throw new Error(validationResult);
+ }
+ }
+
+ /**
+ * Get or set the follow mode of the time conductor. In follow mode the
+ * time conductor ticks, regularly updating the bounds from a timing
+ * source appropriate to the selected time system and mode of the time
+ * conductor.
+ * @fires TimeConductor#follow
+ * @param {boolean} followMode
+ * @returns {boolean}
+ */
+ TimeConductor.prototype.follow = function (followMode) {
+ if (arguments.length > 0) {
+ this.followMode = followMode;
+ /**
+ * @event TimeConductor#follow The TimeConductor has toggled
+ * into or out of follow mode.
+ * @property {boolean} followMode true if follow mode is
+ * enabled, otherwise false.
+ */
+ this.emit('follow', this.followMode);
+ }
+ return this.followMode;
+ };
+
+ /**
+ * @typedef {Object} TimeConductorBounds
+ * @property {number} start The start time displayed by the time conductor in ms since epoch. Epoch determined by current time system
+ * @property {number} end The end time displayed by the time conductor in ms since epoch.
+ */
+ /**
+ * Get or set the start and end time of the time conductor. Basic validation
+ * of bounds is performed.
+ *
+ * @param {TimeConductorBounds} newBounds
+ * @throws {Error} Validation error
+ * @fires TimeConductor#bounds
+ * @returns {TimeConductorBounds}
+ */
+ TimeConductor.prototype.bounds = function (newBounds) {
+ if (arguments.length > 0) {
+ throwOnError(this.validateBounds(newBounds));
+ this.boundsVal = newBounds;
+ /**
+ * @event TimeConductor#bounds The start time, end time, or
+ * both have been updated
+ * @property {TimeConductorBounds} bounds
+ */
+ this.emit('bounds', this.boundsVal);
+ }
+ return this.boundsVal;
+ };
+
+ /**
+ * Get or set the time system of the TimeConductor. Time systems determine
+ * units, epoch, and other aspects of time representation. When changing
+ * the time system in use, new valid bounds must also be provided.
+ * @param {TimeSystem} newTimeSystem
+ * @param {TimeConductorBounds} bounds
+ * @fires TimeConductor#timeSystem
+ * @returns {TimeSystem} The currently applied time system
+ */
+ TimeConductor.prototype.timeSystem = function (newTimeSystem, bounds) {
+ if (arguments.length >= 2) {
+ this.system = newTimeSystem;
+ /**
+ * @event TimeConductor#timeSystem The time system used by the time
+ * conductor has changed. A change in Time System will always be
+ * followed by a bounds event specifying new query bounds
+ * @property {TimeSystem} The value of the currently applied
+ * Time System
+ * */
+ this.emit('timeSystem', this.system);
+ // Do something with bounds here. Try and convert between
+ // time systems? Or just set defaults when time system changes?
+ // eg.
+ this.bounds(bounds);
+ } else if (arguments.length === 1) {
+ throw new Error('Must set bounds when changing time system');
+ }
+ return this.system;
+ };
+
+ /**
+ * Get or set the Time of Interest. The Time of Interest is the temporal
+ * focus of the current view. It can be manipulated by the user from the
+ * time conductor or from other views.
+ * @fires TimeConductor#timeOfInterest
+ * @param newTOI
+ * @returns {number} the current time of interest
+ */
+ TimeConductor.prototype.timeOfInterest = function (newTOI) {
+ if (arguments.length > 0) {
+ this.toi = newTOI;
+ /**
+ * @event TimeConductor#timeOfInterest The Time of Interest has moved.
+ * @property {number} Current time of interest
+ */
+ this.emit('timeOfInterest', this.toi);
+ }
+ return this.toi;
+ };
+
+ return TimeConductor;
+});
diff --git a/platform/features/conductor-v2/src/TimeConductorController.js b/platform/features/conductor-v2/src/TimeConductorController.js
new file mode 100644
index 00000000000..6b714d2ba2a
--- /dev/null
+++ b/platform/features/conductor-v2/src/TimeConductorController.js
@@ -0,0 +1,56 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+define(
+ [],
+ function () {
+
+ function TimeConductorController($scope, conductor) {
+ this.$scope = $scope;
+ this.conductor = conductor;
+
+ /*
+ Stuff to put on scope
+ - parameters
+ - formModel
+ */
+ this.$scope.formModel = {
+ start: '2012-01-01 00:00:00.000Z',
+ end: '2016-01-01 00:00:00.000Z'
+ };
+ }
+
+ TimeConductorController.prototype.validateStart = function () {
+ return true;
+ };
+
+ TimeConductorController.prototype.validateEnd = function () {
+ return true;
+ };
+
+ TimeConductorController.prototype.updateBoundsFromForm = function () {
+
+ };
+
+ return TimeConductorController;
+ }
+);
diff --git a/platform/features/conductor-v2/src/TimeConductorSpec.js b/platform/features/conductor-v2/src/TimeConductorSpec.js
new file mode 100644
index 00000000000..745b61cd226
--- /dev/null
+++ b/platform/features/conductor-v2/src/TimeConductorSpec.js
@@ -0,0 +1,110 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+define(['./TimeConductor'], function (TimeConductor) {
+ describe("The Time Conductor", function () {
+ var tc,
+ timeSystem,
+ bounds,
+ eventListener,
+ toi,
+ follow;
+
+ beforeEach(function () {
+ tc = new TimeConductor();
+ timeSystem = {};
+ bounds = {start: 0, end: 0};
+ eventListener = jasmine.createSpy("eventListener");
+ toi = 111;
+ follow = true;
+ });
+
+ it("Supports setting and querying of time of interest and and follow mode", function () {
+ expect(tc.timeOfInterest()).not.toBe(toi);
+ tc.timeOfInterest(toi);
+ expect(tc.timeOfInterest()).toBe(toi);
+
+ expect(tc.follow()).not.toBe(follow);
+ tc.follow(follow);
+ expect(tc.follow()).toBe(follow);
+ });
+
+ it("Allows setting of valid bounds", function () {
+ bounds = {start: 0, end: 1};
+ expect(tc.bounds()).not.toBe(bounds);
+ expect(tc.bounds.bind(tc, bounds)).not.toThrow();
+ expect(tc.bounds()).toBe(bounds);
+ });
+
+ it("Disallows setting of invalid bounds", function () {
+ bounds = {start: 1, end: 0};
+ expect(tc.bounds()).not.toBe(bounds);
+ expect(tc.bounds.bind(tc, bounds)).toThrow();
+ expect(tc.bounds()).not.toBe(bounds);
+
+ bounds = {start: 1};
+ expect(tc.bounds()).not.toBe(bounds);
+ expect(tc.bounds.bind(tc, bounds)).toThrow();
+ expect(tc.bounds()).not.toBe(bounds);
+ });
+
+ it("Allows setting of time system with bounds", function () {
+ expect(tc.timeSystem()).not.toBe(timeSystem);
+ expect(tc.timeSystem.bind(tc, timeSystem, bounds)).not.toThrow();
+ expect(tc.timeSystem()).toBe(timeSystem);
+ });
+
+ it("Disallows setting of time system without bounds", function () {
+ expect(tc.timeSystem()).not.toBe(timeSystem);
+ expect(tc.timeSystem.bind(tc, timeSystem)).toThrow();
+ expect(tc.timeSystem()).not.toBe(timeSystem);
+ });
+
+ it("Emits an event when time system changes", function () {
+ expect(eventListener).not.toHaveBeenCalled();
+ tc.on("timeSystem", eventListener);
+ tc.timeSystem(timeSystem, bounds);
+ expect(eventListener).toHaveBeenCalledWith(timeSystem);
+ });
+
+ it("Emits an event when time of interest changes", function () {
+ expect(eventListener).not.toHaveBeenCalled();
+ tc.on("timeOfInterest", eventListener);
+ tc.timeOfInterest(toi);
+ expect(eventListener).toHaveBeenCalledWith(toi);
+ });
+
+ it("Emits an event when bounds change", function () {
+ expect(eventListener).not.toHaveBeenCalled();
+ tc.on("bounds", eventListener);
+ tc.bounds(bounds);
+ expect(eventListener).toHaveBeenCalledWith(bounds);
+ });
+
+ it("Emits an event when follow mode changes", function () {
+ expect(eventListener).not.toHaveBeenCalled();
+ tc.on("follow", eventListener);
+ tc.follow(follow);
+ expect(eventListener).toHaveBeenCalledWith(follow);
+ });
+ });
+});
diff --git a/test-main.js b/test-main.js
index 90da6dabb95..103b7bbac20 100644
--- a/test-main.js
+++ b/test-main.js
@@ -48,13 +48,14 @@ requirejs.config({
"angular-route": "bower_components/angular-route/angular-route.min",
"csv": "bower_components/comma-separated-values/csv.min",
"es6-promise": "bower_components/es6-promise/promise.min",
+ "EventEmitter": "bower_components/eventemitter3/index",
"moment": "bower_components/moment/moment",
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
"screenfull": "bower_components/screenfull/dist/screenfull.min",
"text": "bower_components/text/text",
"uuid": "bower_components/node-uuid/uuid",
- "zepto": "bower_components/zepto/zepto.min"
+ "zepto": "bower_components/zepto/zepto.min",
},
"shim": {
@@ -64,6 +65,9 @@ requirejs.config({
"angular-route": {
"deps": [ "angular" ]
},
+ "EventEmitter": {
+ "exports": "EventEmitter"
+ },
"moment-duration-format": {
"deps": [ "moment" ]
},