[Time Conductor] Added pan to Time Conductor

This commit is contained in:
Henry 2016-09-12 16:53:48 -07:00
parent d77922d66c
commit 7fcafb6b58
6 changed files with 285 additions and 172 deletions

View File

@ -24,16 +24,18 @@ define([
"./src/ui/TimeConductorViewService", "./src/ui/TimeConductorViewService",
"./src/ui/TimeConductorController", "./src/ui/TimeConductorController",
"./src/TimeConductor", "./src/TimeConductor",
"./src/ui/ConductorAxisController",
"./src/ui/MctConductorAxis", "./src/ui/MctConductorAxis",
"./src/ui/NumberFormat", "./src/ui/NumberFormat",
"text!./res/templates/time-conductor.html", "text!./res/templates/time-conductor.html",
"text!./res/templates/mode-selector/mode-selector.html", "text!./res/templates/mode-selector/mode-selector.html",
"text!./res/templates/mode-selector/mode-menu.html", "text!./res/templates/mode-selector/mode-menu.html",
'legacyRegistry' "legacyRegistry"
], function ( ], function (
TimeConductorViewService, TimeConductorViewService,
TimeConductorController, TimeConductorController,
TimeConductor, TimeConductor,
ConductorAxisController,
MCTConductorAxis, MCTConductorAxis,
NumberFormat, NumberFormat,
timeConductorTemplate, timeConductorTemplate,
@ -69,6 +71,14 @@ define([
"timeConductorViewService", "timeConductorViewService",
"timeSystems[]" "timeSystems[]"
] ]
},
{
"key": "ConductorAxisController",
"implementation": ConductorAxisController,
"depends": [
"timeConductor",
"formatService"
]
} }
], ],
"directives": [ "directives": [

View File

@ -136,6 +136,7 @@
bottom: 0; bottom: 0;
left: 0; left: 0;
z-index: 1; z-index: 1;
pointer-events: none;
.l-time-range-w { .l-time-range-w {
// Wraps a datetime text input field // Wraps a datetime text input field
height: 100%; height: 100%;
@ -159,6 +160,9 @@
content: 'End'; content: 'End';
} }
} }
.l-time-conductor-inputs {
pointer-events: auto;
}
input[type="text"] { input[type="text"] {
@include trans-prop-nice(padding, 250ms); @include trans-prop-nice(padding, 250ms);
} }

View File

@ -1,6 +1,18 @@
<!-- Parent holder for time conductor. follow-mode | fixed-mode --> <!-- Parent holder for time conductor. follow-mode | fixed-mode -->
<style>
.fixed-mode .l-axis-holder {
cursor: grab;
cursor: -webkit-grab;
}
.fixed-mode .l-axis-holder:active {
cursor: grabbing;
cursor: -webkit-grabbing;
}
</style>
<div ng-controller="TimeConductorController as tcController" <div ng-controller="TimeConductorController as tcController"
class="holder grows flex-elem l-flex-row l-time-conductor {{modeModel.selectedKey}}-mode {{timeSystemModel.selected.metadata.key}}-time-system"> class="holder grows flex-elem l-flex-row l-time-conductor {{modeModel.selectedKey}}-mode {{timeSystemModel.selected.metadata.key}}-time-system"
ng-class="{'status-panning': panning}">
<div class="flex-elem holder time-conductor-icon"> <div class="flex-elem holder time-conductor-icon">
<div class="hand-little"></div> <div class="hand-little"></div>
@ -13,63 +25,67 @@
<form class="l-time-conductor-inputs-holder" <form class="l-time-conductor-inputs-holder"
ng-submit="tcController.updateBoundsFromForm(boundsModel)"> ng-submit="tcController.updateBoundsFromForm(boundsModel)">
<span class="l-time-range-w start-w"> <span class="l-time-range-w start-w">
<span class="l-time-range-input-w start-date"> <span class="l-time-conductor-inputs">
<span class="title"></span> <span class="l-time-range-input-w start-date">
<mct-control key="'datetime-field'" <span class="title"></span>
structure="{ <mct-control key="'datetime-field'"
format: timeSystemModel.format, structure="{
validate: tcController.validation.validateStart format: timeSystemModel.format,
}" validate: tcController.validation.validateStart
ng-model="boundsModel" }"
ng-blur="tcController.updateBoundsFromForm(boundsModel)" ng-model="boundsModel"
field="'start'" ng-blur="tcController.updateBoundsFromForm(boundsModel)"
class="time-range-input"> field="'start'"
</mct-control> class="time-range-input">
</span> </mct-control>
<span class="l-time-range-input-w time-delta start-delta" </span>
ng-class="{'hide':(modeModel.selectedKey === 'fixed')}"> <span class="l-time-range-input-w time-delta start-delta"
- ng-class="{'hide':(modeModel.selectedKey === 'fixed')}">
<mct-control key="'datetime-field'" -
structure="{ <mct-control key="'datetime-field'"
format: timeSystemModel.deltaFormat, structure="{
validate: tcController.validation.validateStartDelta format: timeSystemModel.deltaFormat,
}" validate: tcController.validation.validateStartDelta
ng-model="boundsModel" }"
ng-blur="tcController.updateDeltasFromForm(boundsModel)" ng-model="boundsModel"
field="'startDelta'" ng-blur="tcController.updateDeltasFromForm(boundsModel)"
class="hrs-min-input"> field="'startDelta'"
</mct-control> class="hrs-min-input">
</mct-control>
</span>
</span> </span>
</span> </span>
<span class="l-time-range-w end-w"> <span class="l-time-range-w end-w">
<span class="l-time-range-input-w end-date" <span class="l-time-conductor-inputs">
ng-controller="ToggleController as t2"> <span class="l-time-range-input-w end-date"
<span class="title"></span> ng-controller="ToggleController as t2">
<mct-control key="'datetime-field'" <span class="title"></span>
structure="{ <mct-control key="'datetime-field'"
format: timeSystemModel.format, structure="{
validate: tcController.validation.validateEnd format: timeSystemModel.format,
}" validate: tcController.validation.validateEnd
ng-model="boundsModel" }"
ng-blur="tcController.updateBoundsFromForm(boundsModel)" ng-model="boundsModel"
ng-disabled="modeModel.selectedKey !== 'fixed'" ng-blur="tcController.updateBoundsFromForm(boundsModel)"
field="'end'" ng-disabled="modeModel.selectedKey !== 'fixed'"
class="time-range-input"> field="'end'"
</mct-control> class="time-range-input">
</span> </mct-control>
<span class="l-time-range-input-w time-delta end-delta" </span>
ng-class="{'hide':(modeModel.selectedKey === 'fixed')}"> <span class="l-time-range-input-w time-delta end-delta"
+ ng-class="{'hide':(modeModel.selectedKey === 'fixed')}">
<mct-control key="'datetime-field'" +
structure="{ <mct-control key="'datetime-field'"
format: timeSystemModel.deltaFormat, structure="{
validate: tcController.validation.validateEndDelta format: timeSystemModel.deltaFormat,
}" validate: tcController.validation.validateEndDelta
ng-model="boundsModel" }"
ng-blur="tcController.updateDeltasFromForm(boundsModel)" ng-model="boundsModel"
field="'endDelta'" ng-blur="tcController.updateDeltasFromForm(boundsModel)"
class="hrs-min-input"> field="'endDelta'"
</mct-control> class="hrs-min-input">
</mct-control>
</span>
</span> </span>
</span> </span>

View File

@ -0,0 +1,169 @@
/*****************************************************************************
* 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(
[
"d3"
],
function (d3) {
var PADDING = 1;
/**
* The mct-conductor-axis renders a horizontal axis with regular
* labelled 'ticks'. It requires 'start' and 'end' integer values to
* be specified as attributes.
*/
function ConductorAxisController(conductor, formatService) {
// Dependencies
this.d3 = d3;
this.formatService = formatService;
this.conductor = conductor;
// Runtime properties (set by 'link' function)
this.target = undefined;
this.xScale = undefined;
this.xAxis = undefined;
this.axisElement = undefined;
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();
}
ConductorAxisController.prototype.changeBounds = function (bounds) {
this.bounds = bounds;
if (this.initialized) {
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) {
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);
this.xAxis = this.d3.axisTop();
// draw x axis with labels and move to the bottom of the chart area
this.axisElement = vis.append("g")
.attr("transform", "translate(0," + (height - PADDING) + ")");
this.initialized = true;
if (this.timeSystem !== undefined) {
this.changeTimeSystem(this.timeSystem);
this.setScale(this.bounds);
}
//Respond to changes in conductor
this.conductor.on("timeSystem", this.changeTimeSystem);
this.conductor.on("bounds", this.changeBounds);
};
ConductorAxisController.prototype.panEnd = function () {
//resync view bounds with time conductor bounds
this.conductor.bounds(this.bounds);
this.scope.$emit("pan-stop");
};
ConductorAxisController.prototype.pan = function (delta) {
if (!this.conductor.follow()) {
var deltaInMs = delta[0] * this.msPerPixel;
var bounds = this.conductor.bounds();
var start = Math.floor((bounds.start - deltaInMs) / 1000) * 1000;
var end = Math.floor((bounds.end - deltaInMs) / 1000) * 1000;
this.bounds = {
start: start,
end: end
};
this.setScale();
this.scope.$emit("pan", this.bounds);
}
};
ConductorAxisController.prototype.resize = function () {
if (this.initialized) {
this.setScale();
}
};
return ConductorAxisController;
}
);

View File

@ -20,127 +20,33 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define( define([], function () {
[
"d3"
],
function (d3) {
var PADDING = 1;
/** function MctConductorAxis() {
* The mct-conductor-axis renders a horizontal axis with regular /**
* labelled 'ticks'. It requires 'start' and 'end' integer values to * The mct-conductor-axis renders a horizontal axis with regular
* be specified as attributes. * labelled 'ticks'. It requires 'start' and 'end' integer values to
*/ * be specified as attributes.
function MCTConductorAxis(conductor, formatService) { */
// Dependencies
this.d3 = d3;
this.conductor = conductor;
this.formatService = formatService;
// Runtime properties (set by 'link' function) return {
this.target = undefined; controller: 'ConductorAxisController',
this.xScale = undefined; controllerAs: 'axis',
this.xAxis = undefined; link: function(scope, element, attrs, controller){
this.axisElement = undefined; controller.link(scope, element);
},
// Angular Directive interface restrict: 'E',
this.link = this.link.bind(this); priority: 1000,
this.restrict = "E";
this.template =
"<div class=\"l-axis-holder\" mct-resize=\"resize()\"></div>";
this.priority = 1000;
//Bind all class functions to 'this' template: '<div class="l-axis-holder" ' +
Object.keys(MCTConductorAxis.prototype).filter(function (key) { ' mct-drag-down="axis.panStart()"' +
return typeof MCTConductorAxis.prototype[key] === 'function'; ' mct-drag-up="axis.panEnd(delta)"' +
}).forEach(function (key) { ' mct-drag="axis.pan(delta)"' +
this[key] = this[key].bind(this); ' mct-resize="axis.resize()"></div>'
}.bind(this)); }
} }
MCTConductorAxis.prototype.setScale = function () { return MctConductorAxis;
var width = this.target.offsetWidth;
var timeSystem = this.conductor.timeSystem();
var bounds = this.conductor.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);
};
MCTConductorAxis.prototype.changeTimeSystem = function (timeSystem) {
var key = timeSystem.formats()[0];
if (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);
}
};
MCTConductorAxis.prototype.destroy = function () {
this.conductor.off('timeSystem', this.changeTimeSystem);
this.conductor.off('bounds', this.setScale);
};
MCTConductorAxis.prototype.link = function (scope, element) {
var conductor = this.conductor;
this.target = element[0].firstChild;
var height = this.target.offsetHeight;
var vis = this.d3.select(this.target)
.append('svg:svg')
.attr('width', '100%')
.attr('height', height);
this.xAxis = this.d3.axisTop();
// draw x axis with labels and move to the bottom of the chart area
this.axisElement = vis.append("g")
.attr("transform", "translate(0," + (height - PADDING) + ")");
scope.resize = this.setScale;
conductor.on('timeSystem', this.changeTimeSystem);
//On conductor bounds changes, redraw ticks
conductor.on('bounds', this.setScale);
scope.$on("$destroy", this.destroy);
if (conductor.timeSystem() !== undefined) {
this.changeTimeSystem(conductor.timeSystem());
this.setScale();
}
};
return function (conductor, formatService) {
return new MCTConductorAxis(conductor, formatService);
};
} }
); );

View File

@ -97,6 +97,14 @@ define(
// Watch scope for selection of mode or time system by user // Watch scope for selection of mode or time system by user
this.$scope.$watch('modeModel.selectedKey', this.setMode); this.$scope.$watch('modeModel.selectedKey', this.setMode);
this.$scope.$on('pan', function (e, bounds) {
this.$scope.panning = true;
this.setFormFromBounds(bounds);
}.bind(this));
this.$scope.$on('pan-stop', function () {
this.$scope.panning = false;
}.bind(this));
this.$scope.$on('$destroy', this.destroy); this.$scope.$on('$destroy', this.destroy);
}; };