Compare commits

...

2 Commits

Author SHA1 Message Date
86b3da1e86 Addressed comments from PR 2016-06-24 19:04:42 -07:00
a910b86109 [Time Conductor] #933 Time Conductor V2 API proposal 2016-06-08 11:03:32 +01:00
9 changed files with 718 additions and 0 deletions

View File

@ -0,0 +1,150 @@
## Notes
API is notional for now, based on use-cases identified below. Possible the
use cases are not sufficient, so please include in comments
any other use cases you'd like to see.
Plan now is to start building out test suite for the use cases identified below
in order to get the API functional. Need to discuss how UI aspects of timeline will be implemented.
Propose in place refactoring of existing timeline rather than starting again.
Some caveats / open questions
* I don't understand the use case shown on page 52 of UI sketches. It shows RT/FT, with deltas,
with inner interval unlocked. Not sure what result would be, has inner end switched to fixed?
Also example on page 55 in real-time where inner end < now. Is there a use case for this? Semantically, it's saying
show me real time, but stale data. Why would a user want this? My feeling is that if the inner
OR outer ends are moved behind NOW in real-time mode then you drop into historical mode.
* For the API itself, have ignored question of how it's namespaced / exposed.
Examples assume global namespace and availability from window object.
For now API implemented as standard standard Require JS AMDs. Could attach
to window from bundle.js. Perhaps attaching to window not best approach though...
* Have not included validation (eg. start time < end time) or any other
business logic such as what happens when outer interval gets dragged
within range of inner interval. Focus is on teasing out the public API
right now.
* Time systems are vague right now also, I don't know how they're going
to work or whether any API has yet been specified.
* Not clear on the differences between real-time and follow-time as it
concerns the time conductor? For now the API has an end bounds mode
of FOLLOW which automatically tracks current time, and a start time mode
of RELATIVE. I can envision a real-time plot that is not in follow time mode,
but not sure what implication is for time conductor itself and how it
differs from an historical plot?
* Should the time conductor be responsible for choosing time system / domain? Currently
it is.
## Use Cases
1. Historical session is loaded and system sets time bounds on conductor
2. Real-time session is loaded, setting custom start and end deltas
3. User changes time of interest
4. Plot controller listens for change to TOI
5. Plot Controller updated on tick
6. Plot Controller updated when user changes bounds (eg to reset plot zoom)
7. Conductor controller needs to update bounds and mode on TC when user changes bounds
### Additional possible use-cases
1. Telemetry adapter wants to indicate presence of data at a particular time
2. Time conductor controller wants to paint map of data availability.
These use-cases could be features of the TimeConductor, but perhaps makes
sense to make knowledge of data availability the sole preserve of telemetry
adapters, not TimeConductor itself. Adapters will be ultimately responsible
for providing these data so doesn't make much sense to duplicate elsewhere.
The TimeConductorController - which knows tick interval on scale (which
TimeConductor API does not) - could simply request data availability from
telemetry API and paint it into the Time Conductor UI
## Example implementations of use cases
### 1. Real time session is loaded (outside of TC) and system sets time bounds on conductor
``` javascript
function loadSession(telemetryMetadata) {
var tc = MCT.conductor;
tc.timeSystem(session.timeSystem());
//Set start and end modes to fixed date
tc.mode(new FixedMode());
//Set both inner and outer bounds
tc.bounds({start: session.start(), end: session.end()});
}
```
### 2. Real-time session is loaded (outside of TC), setting custom start and end deltas
``` javascript
function loadSession(session) {
var tc = MCT.conductor;
var FIFTEEN_MINUTES = 15 * 60 * 1000;
// Could have a central ticking source somewhere, or connect to a
// remote ticking source. Should not need to be done manually with
// each session load. Actually not quite sure what to do with tick
// sources yet.
var tickSource = new LocalClock();
tickSource.attach(); // Start ticking
var mode = new RealtimeMode({
startDelta: FIFTEEN_MINUTES,
endDelta: 0 // End delta offset is from "Now" in the time system
});
tc.timeSystem(session.timeSystem());
// Set mode to realtime, specifying a tick source
tc.mode(mode);
//No need to set bounds manually, will be established by mode and the deltas specified
}
```
### 3. User changes time of interest
```javascript
//Somewhere in the TimeConductorController...
function changeTOI(newTime) {
MCT.conductor.timeOfInterest(newTime);
}
```
### 4. Plot controller listens for change to TOI
```javascript
// toi is attribute of Time Conductor object. Add a listener to the time
// conductor to be alerted to changes in value
// Time conductor is an event emitter, listen to timeOfInterest event
MCT.conductor.on("timeOfInterest", function (timeOfInterest) {
plot.setTimeOfInterest(timeOfInterest);
}
```
### 5. Plot Controller updated on tick
``` javascript
MCT.conductor.on("bounds", function (bounds) {
plotUpdater.setDomainBounds(bounds.start, bounds.end);
});
```
### 6. Plot Controller updated when user changes bounds (eg to reset plot zoom)
``` javascript
MCT.conductor.on("refresh", function (conductor) {
plot.setBounds(conductor.bounds());
//Also need to reset tick labels. if time system has changed.
}
```
### 7. Conductor controller needs to update bounds and mode on TC when user changes bounds
```javascript
var tc = MCT.conductor;
function dragStartHandle(finalPos){
var bounds = tc.bounds();
bounds.start = positionToTime(finalPos)
tc.bounds(bounds);
}
function dragEndHandle(finalPos){
var bounds = tc.bounds();
bounds.end = positionToTime(finalPos);
tc.bounds(bounds);
}
```

View File

@ -0,0 +1,148 @@
/*****************************************************************************
* 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",
"./UTCTimeSystem",
"./modes/RelativeMode",
"./modes/FixedMode"
], function (EventEmitter, UTCTimeSystem, RelativeMode, FixedMode) {
/**
* A class for setting and querying time conductor state.
*
* @event TimeConductor:refresh The time conductor has changed, and its values should be re-queried
* @event TimeConductor:bounds The start time, end time, or both have been updated
* @event TimeConductor:timeOfInterest The Time of Interest has moved.
* @constructor
*/
function TimeConductor() {
EventEmitter.call(this);
//The Time System
this.system = new UTCTimeSystem();
//The Time Of Interest
this.toi = undefined;
this.bounds = {
start: undefined,
end: undefined
};
//Default to fixed mode
this.modeVal = new FixedMode();
}
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 ||
!bounds.end ||
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 validationResult;
}
}
/**
* Set the mode of the time conductor.
* @param {FixedMode | RealtimeMode} newMode
* @fires TimeConductor#refresh
* @returns {FixedMode | RealtimeMode}
*/
TimeConductor.prototype.mode = function (newMode) {
if (arguments.length > 0) {
this.modeVal = newMode;
this.emit('refresh', this);
newMode.initialize();
}
return this.modeVal;
};
/**
* @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.
*/
/**
* Set the start and end time of the time conductor. Basic validation of bounds is performed.
*
* @param {TimeConductorBounds} newBounds
* @throws {string} Validation error
* @fires TimeConductor#bounds
* @returns {TimeConductorBounds}
*/
TimeConductor.prototype.bounds = function (newBounds) {
if (arguments.length > 0) {
throwOnError(this.validateBounds(newBounds));
this.bounds = newBounds;
this.emit('bounds', this.bounds);
}
return this.bounds;
};
/**
* Set the time system of the TimeConductor. Time systems determine units, epoch, and other aspects of time representation.
* @param newTimeSystem
* @fires TimeConductor#refresh
* @returns {TimeSystem} The currently applied time system
*/
TimeConductor.prototype.timeSystem = function (newTimeSystem) {
if (arguments.length > 0) {
this.system = newTimeSystem;
this.emit('refresh', this);
}
return this.system;
};
/**
* 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.
* @param newTOI
* @returns {*}
*/
TimeConductor.prototype.timeOfInterest = function (newTOI) {
if (arguments.length > 0) {
this.toi = newTOI;
this.emit('toi');
}
return this.toi;
};
return TimeConductor;
});

View File

@ -0,0 +1,69 @@
/*****************************************************************************
* 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 TimeConductorBounds(conductor) {
this.listeners = [];
this.start = new TimeConductorLimit(this);
this.end = new TimeConductorLimit(this);
this.conductor = conductor;
}
/**
* @private
*/
TimeConductorBounds.prototype.notify = function (eventType) {
eventType = eventType || this.conductor.EventTypes.EITHER;
this.listeners.forEach(function (element){
if (element.eventType & eventType){
element.listener(this);
}
});
};
/**
* Listen for changes to the bounds
* @param listener a callback function to be called when the bounds change. The bounds object will be passed into
* the function (ie. 'this')
* @param eventType{TimeConductorBounds.EventType} The event type to listen to, ie. system, user, or Both. If not provied, will default to both.
* @returns {Function} an 'unlisten' function
*/
TimeConductorBounds.prototype.listen = function (listener, eventType) {
var self = this,
wrappedListener = {
listener: listener,
eventType: eventType
};
this.listeners.push(wrappedListener);
return function () {
self.listeners = self.listeners.filter(function (element){
return element !== wrappedListener;
});
};
};
return TimeConductorBounds;
});

View File

@ -0,0 +1,71 @@
/*****************************************************************************
* 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 () {
/**
* Defines a limit object for a time conductor bounds, ie. start or end values. Holds time and delta values.
*
* TODO: Calculation of time from delta. Should probably be done from the 'tick' function at a higher level,
* which has start and end values in scope to do calculations.
* @param listener
* @constructor
*/
function TimeConductorLimit(listener) {
this.deltaVal = undefined;
this.timeVal = undefined;
this.listener = listener;
}
/**
* Get or set the start value for the bounds. If newVal is provided, will set the start value. May only set delta in
* RELATIVE mode.
* @param {Number} [newVal] a time in ms. A negative value describes a time in the past, positive in the future. A
* start time cannot have a positive delta offset, but an end time can.
* @param {TimeConductor.EventTypes} [eventType=TimeConductor.EventTypes.EITHER] The type of event (User, System, or
* Either)
* @returns {Number} the start date (in milliseconds since some epoch, depending on time system)
*/
TimeConductorLimit.prototype.delta = function (newVal, eventType) {
if (arguments.length > 0) {
this.deltaVal = newVal;
this.listener.notify(eventType);
}
return this.deltaVal;
};
/**
* Get or set the end value for the bounds. If newVal is provided, will set the end value. May only set time in FIXED
* mode
* @param {Number} [newVal] A time in ms relative to time system epoch.
* @param {TimeConductor.EventTypes} [eventType=TimeConductor.EventTypes.EITHER] The type of event (User, System, or Either)
* @returns {Number} the end date (in milliseconds since some epoch, depending on time system)
*/
TimeConductorLimit.prototype.time = function (newVal, eventType) {
if (arguments.length > 0) {
this.timeVal = newVal;
this.listener.notify(eventType);
}
return this.timeVal;
};
return TimeConductorLimit;
});

View File

@ -0,0 +1,29 @@
/*****************************************************************************
* 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 FixedMode(options) {
}
return FixedMode;
});

View File

@ -0,0 +1,71 @@
/*****************************************************************************
* 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 () {
/**
* Class representing the real-time mode of the Time Conductor. In this mode,
* the bounds are updated automatically based on a timing source.
*
* @param options
* @constructor
*/
function RealtimeMode(options) {
this.startDelta = options.startDelta;
this.endDelta = options.endDelta;
this.tickSource = options.tickSource;
this.system = undefined;
}
/**
* @private
*/
this.prototype.initialize = function (conductor) {
var self = this;
/**
* Deltas can be specified for start and end. An end delta will mean
* that the end bound is always in the future by 'endDelta' units
*/
this.startDelta = this.startDelta || conductor.timeSystem().DEFAULT_DELTA;
this.endDelta = this.endDelta || 0;
function setBounds() {
var now = conductor.timeSystem().now();
conductor.bounds({
start: now - self.startDelta,
end: now + self.endDelta
});
}
/**
* If a tick source is specified, listen for ticks
*/
if (this.tickSource) {
this.tickSource.on("tick", setBounds);
}
//Set initial bounds
setBounds();
};
return RealtimeMode;
});

View File

@ -0,0 +1,50 @@
/*****************************************************************************
* 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([
"./TimingSource"
], function (TimingSource) {
/**
* A timing source that 'ticks' when new data is available
* @implements TimingSource
* @constructor
*/
function DataAvailabilityTicker(){
TimingSource.call(this);
}
DataAvailabilityTicker.prototype = Object.create(TimingSource.prototype);
/**
* Registers an event listener to listen for data availability at telemetry source
*/
DataAvailabilityTicker.prototype.attach = function () {};
/**
* Unregisters event listeners, seasing tick events.
*/
DataAvailabilityTicker.prototype.detach = function () {};
DataAvailabilityTicker.prototype.attached = function () {}
});

View File

@ -0,0 +1,74 @@
/*****************************************************************************
* 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([
"./TimingSource"
], function (TimingSource) {
var ONE_SECOND = 1 * 1000;
/**
* A clock that ticks at the given interval (given in ms).
*
* @implements TimingSource
* @constructor
*/
function LocalClock(interval){
TimingSource.call(this);
this.interval = interval;
this.intervalHandle = undefined;
}
LocalClock.prototype = Object.create(TimingSource.prototype);
/**
* Start the clock ticking. Ticks can be listened to by registering
* listeners of the "tick" event
*/
LocalClock.prototype.attach = function () {
function tick() {
this.emit("tick");
}
this.stop();
this.intervalHandle = setInterval(this.bind(this), this.interval || ONE_SECOND);
};
/**
* Stop the currently running clock. "tick" events will no longer be emitted
*/
LocalClock.prototype.detach = function () {
if (this.intervalHandle) {
clearInterval(this.intervalHandle);
}
};
/**
* @returns {boolean} true if the clock is currently running
*/
LocalClock.prototype.attached = function () {
return !!this.intervalHandle;
}
});

View File

@ -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([
"EventEmitter"
], function (EventEmitter) {
/**
* An interface defining a timing source. A timing source is a local or remote source of 'tick' events.
* Could be used to tick when new data is received from a data source.
* @interface
* @constructor
*/
function TimingSource(){
EventEmitter.call(this);
}
TimingSource.prototype = Object.create(EventEmitter.prototype);
/**
* Attach to the timing source. If it's a local clock, will start a local timing loop. If remote, will connect to
* remote source. If event driven (eg. based on data availability) will attach an event listener to telemetry source.
*/
TimingSource.prototype.attach = function () {};
/**
* Detach from the timing source
*/
TimingSource.prototype.detach = function () {};
/**
* @returns {boolean} true if current attached to timing source
*/
TimingSource.prototype.attached = function () {}
});