alt-click to select TOI from table

This commit is contained in:
Henry 2016-10-06 10:23:42 -07:00
commit dfbbc3b0c5
18 changed files with 213 additions and 175 deletions

View File

@ -75,7 +75,7 @@ $treeContextTriggerW: 20px;
/*************** Tabular */
$tabularHeaderH: 22px;
$tabularTdPadLR: $itemPadLR;
$tabularTdPadTB: 3px;
$tabularTdPadTB: 2px;
/*************** Imagery */
$imageMainControlBarH: 25px;
$imageThumbsD: 120px;

View File

@ -20,21 +20,6 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*
Plots
-toi-holder:before vertical line
-toi: glyphs
Tables
tr: border color
td:before: glyphs
TC
-toi-holder:before, after: vertical lines
-toi: glyphs
*/
.l-toi-holder,
.l-toi-holder:after,
.l-toi-holder:before,
@ -44,7 +29,6 @@ TC
position: absolute;
}
.l-toi-holder {
$p: 3px;
@include transform(translateX(-50%));
@ -52,14 +36,16 @@ TC
position: absolute;
top: 0;
bottom: 0;
width: $toiW; // Needs to be an even number to avoid sub-pixel antialiasing of the vertical line
width: $toiW;
&:not(.pinned) {
opacity: 0;
pointer-events: none;
}
&.pinned {
opacity: 1;
}
&:before,
&:after {
// Vertical lines. TC uses both; plot only uses :before
@ -73,8 +59,9 @@ TC
bottom: 0;
width: 2px;
}
.l-toi {
// Holds clock icon an unpin button
// Holds clock icon and unpin button
font-size: $toiW;
height: $toiW;
width: $toiW;
@ -83,6 +70,7 @@ TC
@extend .icon-clock;
&:hover {
&:before {
color: $toiColorBgAlert;
content: $glyph-icon-x-in-circle;
}
}
@ -90,26 +78,33 @@ TC
}
.l-toi-val {
$tbP: 1px;
background-color: $toiColorBg; //rgba($toiColorBg, 0.8);
border-radius: $controlCr;
box-sizing: content-box;
display: inline-block;
color: $toiColorFg;
font-size: 0.7rem;
height: $toiW;
right: $toiW + $interiorMarginSm;
top: 50%;
@include transform(translateY(-50%));
line-height: $toiW;
padding: $tbP $p;
white-space: nowrap;
display: none; // Hide by default; see .show-val below
}
&.val-to-right {
&.show-val {
.l-toi-val {
right: auto;
left: $toiW + $interiorMarginSm;
$tbP: 1px;
background-color: $toiColorBg;
border-radius: $controlCr;
box-sizing: content-box;
color: $toiColorFg;
display: inline-block;
font-size: 0.7rem;
font-weight: 400;
height: $toiW;
right: $toiW + $interiorMarginSm;
top: 50%;
@include transform(translateY(-50%));
line-height: $toiW;
padding: $tbP $p;
white-space: nowrap;
}
&.val-to-right {
.l-toi-val {
right: auto;
left: $toiW + $interiorMarginSm;
}
}
}
}
@ -120,38 +115,42 @@ table {
tbody, .tbody {
tr, .tr {
&.l-toi.pinned {
td {
border-top: 1px dashed $toiColorBg;
&:first-child:before {
@extend .ui-symbol;
@include transform(translate(-50%, -50%));
content: $glyph-icon-clock;
display: block;
position: absolute;
text-shadow: 0 1px 15px black;
left: 50%;
top: 0;
z-index: 2;
color: $toiColorBg;
}
}
border-top: 1px dashed $toiColorBg;
td, .td {
&:first-child {
&:before,
&:after {
@include transform(translate(-50%, -50%));
display: block;
position: absolute;
left: 50%;
bottom: auto;
top: 0;
&.highlight-bottom-edge {
td {
border-bottom: 1px dashed $toiColorBg;
//border-top: 1px solid transparent;
&:first-child:before {
@include transform(translate(-50%, 50%));
top: auto;
bottom: 0;
}
&:before {
@extend .icon-clock;
color: $toiColorBg;
cursor: pointer;
z-index: 3;
}
&:after {
border-radius: 100%;
content: '';
background: $toiColorBlocker;
height: $toiW + $interiorMargin;
width: $toiW + $interiorMargin;
z-index: 2;
}
}
}
&:hover {
td:first-child:before {
content: $glyph-icon-x-in-circle;
cursor: pointer;
td, .td {
&:first-child:before {
color: $toiColorBgAlert;
content: $glyph-icon-x-in-circle !important;
}
}
}
}
@ -164,6 +163,7 @@ table {
.gl-plot-wrapper-display-area-and-x-axis {
.l-toi-holder {
bottom: nth($plotDisplayArea, 3) - $interiorMargin;
z-index: 3;
&:after {
display: none;
}
@ -172,8 +172,6 @@ table {
@include transform(translateY(100%));
bottom: 0;
}
z-index: 3;
}
}
}

View File

@ -51,13 +51,9 @@ table {
tbody, .tbody {
display: table-row-group;
/* tr, .tr {
&:hover {
background: rgba($colorTabBodyFg, 0.1);
}
}*/
}
tr, .tr {
border-top: 1px solid $colorTabBorder;
display: table-row;
&:first-child .td {
border-top: none;
@ -71,11 +67,12 @@ table {
}
th, .th, td, .td {
display: table-cell;
font-size: 0.7rem;
}
th, .th {
border-left: 1px solid $colorTabHeaderBorder;
color: $colorTabHeaderFg;
padding: $tabularTdPadLR $tabularTdPadLR;
padding: $tabularTdPadTB $tabularTdPadLR;
white-space: nowrap;
vertical-align: middle; // This is crucial to hiding f**king 4px height injected by browser by default
&:first-child {
@ -99,7 +96,6 @@ table {
}
}
td, .td {
border-top: 1px solid $colorTabBorder;
min-width: 20px;
color: $colorTelemFresh;
padding: $tabularTdPadTB $tabularTdPadLR;

View File

@ -104,9 +104,11 @@ $colorInspectorSectionHeaderBg: $colorFormSectionHeader;
$colorInspectorSectionHeaderFg: pullForward($colorInspectorBg, 40%);
// Time of Interest
$toiColorBg: #6b93c6; //$colorBtnMajorBg;
$toiColorBg: #6b93c6;
$toiColorBlocker: $colorBodyBg; // Color of blocker element beneath the TOI icon
$toiColorFg: #000;
$toiW: 12px;
$toiColorBgAlert: #cf644a; // $colorFormInvalid;
$toiW: 12px; // Needs to be an even number to avoid sub-pixel antialiasing of the vertical line
// Status colors, mainly used for messaging and item ancillary symbols
$colorStatusFg: #ccc;

View File

@ -105,8 +105,10 @@ $colorInspectorSectionHeaderFg: pullForward($colorInspectorBg, 40%);
// Time of Interest
$toiColorBg: #6b93c6;
$toiColorBlocker: $colorBodyBg; // Color of blocker element beneath the TOI icon
$toiColorFg: #fff;
$toiW: 12px;
$toiColorBgAlert: #a7292a; // $colorFormInvalid;
$toiW: 12px; // Needs to be an even number to avoid sub-pixel antialiasing of the vertical line
// Status colors, mainly used for messaging and item ancillary symbols
$colorStatusFg: #fff;

View File

@ -91,7 +91,7 @@ define([
"$scope",
"timeConductor",
"timeConductorViewService",
"$timeout"
"formatService"
]
}
],

View File

@ -244,11 +244,6 @@
left: nth($timeCondAxisLROffset, 1);
right: nth($timeCondAxisLROffset, 2);
&:hover {
// Hide the cursor, because the TOI element essentially "becomes" the cursor
// when the user is hovering over the visualization area.
// AH - not any more it doesn't?
//cursor: none;
.l-toi-holder.hover {
opacity: 1;
}

View File

@ -100,15 +100,13 @@
ng-click="toi.click($event)">
<a class="l-page-button s-icon-button icon-pointer-left"></a>
<div class="l-data-visualization">
<!-- Note:
- val-to-right should be applied when l-toi-holder left < 160px
-->
<div class="l-toi-holder"
ng-class="{ 'pinned': toi.pinned, 'val-to-right': false }"
<!-- Note: - val-to-right should be applied when l-toi-holder left < 150px -->
<div class="l-toi-holder show-val"
ng-class="{ 'pinned': toi.pinned, 'val-to-right': toi.left < 20 }"
ng-style="{'left': toi.left + '%'}">
<div class="l-toi">
<a class="t-button-unpin icon-button" ng-click="toi.pinned = false"></a>
<div class="l-toi-val">2016-09-15 21:31:30.000Z</div>
<span class="l-toi-val">{{toi.toiText}}</span>
</div>
</div>
</div>

View File

@ -123,6 +123,12 @@ define(['EventEmitter'], function (EventEmitter) {
* @property {TimeConductorBounds} bounds
*/
this.emit('bounds', this.boundsVal);
// If a bounds change results in a TOI outside of the current
// bounds, unset it
if (this.toi < newBounds.start || this.toi > newBounds.end) {
this.timeOfInterest(undefined);
}
}
//Return a copy to prevent direct mutation of time conductor bounds.
return JSON.parse(JSON.stringify(this.boundsVal));
@ -158,17 +164,21 @@ define(['EventEmitter'], function (EventEmitter) {
/**
* 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.
* time conductor or from other views. The time of interest can
* effectively be unset by assigning a value of 'undefined'.
* @fires TimeConductor#timeOfInterest
* @param newTOI
* @returns {number} the current time of interest
* @param {number | undefined} newTOI A new time of interest, represented
* as a
* number that is valid in the current time system.
* @returns {number | undefined} 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
* @event TimeConductor#timeOfInterest The Time of Interest has
* changed.
* @property {number | undefined} Current time of interest
*/
this.emit('timeOfInterest', this.toi);
}

View File

@ -29,9 +29,11 @@ define(
* labelled 'ticks'. It requires 'start' and 'end' integer values to
* be specified as attributes.
*/
function ConductorTOIController($scope, conductor, conductorViewService, $timeout) {
function ConductorTOIController($scope, conductor, conductorViewService, formatService) {
this.conductor = conductor;
this.conductorViewService = conductorViewService;
this.formatService = formatService;
this.toiText = undefined;
//Bind all class functions to 'this'
Object.keys(ConductorTOIController.prototype).filter(function (key) {
@ -43,19 +45,10 @@ define(
this.conductor.on('timeOfInterest', this.changeTimeOfInterest);
this.conductorViewService.on('zoom', this.setOffsetFromBounds);
this.conductorViewService.on('pan', this.setOffsetFromBounds);
this.$timeout = $timeout;
var generateRandomTOI = function () {
var bounds = conductor.bounds();
var range = bounds.end - bounds.start;
var toi = Math.random() * range + bounds.start;
console.log('calculated random TOI of ' + toi);
conductor.timeOfInterest(toi);
//this.timeoutHandle = $timeout(generateRandomTOI, 1000);
}.bind(this);
//this.timeoutHandle = $timeout(generateRandomTOI, 2000);
this.conductor.on('timeSystem', this.changeTimeSystem);
if (conductor.timeSystem()) {
this.changeTimeSystem(conductor.timeSystem());
}
$scope.$on('$destroy', this.destroy);
@ -63,23 +56,28 @@ define(
ConductorTOIController.prototype.destroy = function () {
this.conductor.off('timeOfInterest', this.setOffsetFromBounds);
this.conductor.off('timeSystem', this.changeTimeSystem);
this.conductorViewService.off('zoom', this.setOffsetFromBounds);
this.conductorViewService.off('pan', this.setOffsetFromBounds);
this.$timeout.cancel(this.timeoutHandle);
};
ConductorTOIController.prototype.setOffsetFromBounds = function (bounds) {
var toi = this.conductor.timeOfInterest();
var offset = toi - bounds.start;
this.left = offset / (bounds.end - bounds.start) * 100;
var duration = bounds.end - bounds.start;
this.left = offset / duration * 100;
this.toiText = this.format.format(toi);
};
ConductorTOIController.prototype.changeTimeSystem = function (timeSystem) {
this.format = this.formatService.getFormat(timeSystem.formats()[0]);
};
ConductorTOIController.prototype.changeTimeOfInterest = function () {
var bounds = this.conductor.bounds();
if (bounds) {
this.setOffsetFromBounds(bounds);
this.pinned = true;
this.pinned = this.conductor.timeOfInterest() !== undefined;
}
};
@ -96,17 +94,8 @@ define(
}
};
/*
ConductorTOIController.prototype.zoom = function (bounds) {
this.changeTOI(bounds)
};
ConductorTOIController.prototype.pan = function (bounds) {
this.changeTOI(bounds)
};*/
ConductorTOIController.prototype.resize = function () {
//Do something
//Do something?
};
return ConductorTOIController;

View File

@ -75,15 +75,15 @@
</div>
</div>
<!-- new wrapper inserted here -->
<div class="gl-plot-wrapper-display-area-and-x-axis">
<!-- TOI element -->
<!-- TOI element. val-to-right should be true when 'left' is < 150px -->
<div class="l-toi-holder show-val"
ng-class="{ pinned: true, 'val-to-right': true }"
ng-class="{ pinned: false, 'val-to-right': true }"
style="left: 0%">
<!-- Need text val at bottom, plus vertical line -->
<span class="l-toi">
<a class="t-button-unpin icon-button" ng-click="unpin()"></a>
<a class="t-button-unpin icon-button"
title="Unset Time of Interest"
ng-click="dummyUnpin()"></a>
<span class="l-toi-val">21:31:30</span>
</span>
</div>

View File

@ -109,7 +109,7 @@ define([
{
"key": "HistoricalTableController",
"implementation": HistoricalTableController,
"depends": ["$scope", "telemetryHandler", "telemetryFormatter", "$timeout"]
"depends": ["$scope", "telemetryHandler", "telemetryFormatter", "$timeout", "timeConductor"]
},
{
"key": "RealtimeTableController",

View File

@ -1,9 +1,11 @@
<div ng-controller="HistoricalTableController" ng-class="{'loading': loading}">
<div ng-controller="HistoricalTableController as tableController"
ng-class="{'loading': loading}">
<mct-table
headers="headers"
rows="rows"
enableFilter="true"
enableSort="true"
on-row-click="tableController.onRowClick(event, rowIndex, sortColumn, sortDirection)"
class="tabular-holder has-control-bar">
</mct-table>
</div>

View File

@ -49,20 +49,23 @@
</tr>
</thead>
<tbody>
<!--ng-class="{ 'l-toi pinned': false }"-->
<!--ng-click="dummyUnpin()" -->
<tr ng-repeat="visibleRow in visibleRows track by visibleRow.rowIndex"
ng-class="{ 'l-toi active pinned': false }"
ng-style="{
top: visibleRow.offsetY + 'px',
}">
<td ng-repeat="header in displayHeaders"
ng-style=" {
width: columnWidths[$index] + 'px',
'max-width': columnWidths[$index] + 'px',
}"
class="{{visibleRow.contents[header].cssClass}}">
{{ visibleRow.contents[header].text }}
</td>
</tr>
</tbody>
</table>
class="{{visibleRow.contents.cssClass}}"
ng-click="table.onRowClick($event, visibleRow.contents)"
ng-style="{
top: visibleRow.offsetY + 'px',
}">
<td ng-repeat="header in displayHeaders"
ng-style=" {
width: columnWidths[$index] + 'px',
'max-width': columnWidths[$index] + 'px',
}"
class="{{visibleRow.contents[header].cssClass}}">
{{ visibleRow.contents[header].text }}
</td>
</tr>
</tbody>
</table>
</div>

View File

@ -36,7 +36,7 @@ define(
* @param telemetryFormatter
* @constructor
*/
function HistoricalTableController($scope, telemetryHandler, telemetryFormatter, $timeout) {
function HistoricalTableController($scope, telemetryHandler, telemetryFormatter, $timeout, conductor) {
var self = this;
this.$timeout = $timeout;
@ -49,7 +49,7 @@ define(
}
});
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
TableController.call(this, $scope, telemetryHandler, telemetryFormatter, conductor);
}
HistoricalTableController.prototype = Object.create(TableController.prototype);
@ -59,6 +59,7 @@ define(
* @private
*/
HistoricalTableController.prototype.doneProcessing = function (rowData) {
//Set table rows to formatted data;
this.$scope.rows = rowData;
this.$scope.loading = false;
};
@ -109,8 +110,9 @@ define(
//Process rows in a batch with size not exceeding a maximum length
for (; i < end; i++) {
rowData.push(this.table.getRowValues(telemetryObject,
this.handle.makeDatum(telemetryObject, series, i)));
var datum = this.handle.makeDatum(telemetryObject, series, i);
this.data.push(datum);
rowData.push(this.table.getRowValues(telemetryObject, datum));
}
//Done processing all rows for this object.
@ -123,7 +125,7 @@ define(
// before continuing processing
this.timeoutHandle = this.$timeout(this.processTelemetryObjects.bind(this, objects, offset, end, rowData));
};
/**
* Populates historical data on scope when it becomes available from
* the telemetry API
@ -132,7 +134,7 @@ define(
if (this.timeoutHandle) {
this.$timeout.cancel(this.timeoutHandle);
}
this.data = [];
this.timeoutHandle = this.$timeout(this.processTelemetryObjects.bind(this, this.handle.getTelemetryObjects(), 0, 0, []));
};

View File

@ -25,6 +25,13 @@ define(
this.tbody = element.find('tbody');
this.$scope.sizingRow = {};
//Bind all class functions to 'this'
Object.keys(MCTTableController.prototype).filter(function (key) {
return typeof MCTTableController.prototype[key] === 'function';
}).forEach(function (key) {
this[key] = MCTTableController.prototype[key].bind(this);
}.bind(this));
this.scrollable.on('scroll', this.onScroll.bind(this));
$scope.visibleRows = [];
@ -294,37 +301,38 @@ define(
/**
* @private
*/
MCTTableController.prototype.insertSorted = function (array, element) {
var index = -1,
self = this,
sortKey = this.$scope.sortColumn;
MCTTableController.prototype.binarySearch = function (searchArray, searchElement, min, max) {
var sampleAt = Math.floor((max - min) / 2) + min;
function binarySearch(searchArray, searchElement, min, max) {
var sampleAt = Math.floor((max - min) / 2) + min;
if (max < min) {
return min; // Element is not in array, min gives direction
}
switch (self.sortComparator(searchElement[sortKey].text,
searchArray[sampleAt][sortKey].text)) {
case -1:
return binarySearch(searchArray, searchElement, min,
sampleAt - 1);
case 0 :
return sampleAt;
case 1 :
return binarySearch(searchArray, searchElement,
sampleAt + 1, max);
}
if (max < min) {
return min; // Element is not in array, min gives direction
}
switch (this.sortComparator(searchElement[this.$scope.sortColumn].text,
searchArray[sampleAt][this.$scope.sortColumn].text)) {
case -1:
return this.binarySearch(searchArray, searchElement, min,
sampleAt - 1);
case 0 :
return sampleAt;
case 1 :
return this.binarySearch(searchArray, searchElement,
sampleAt + 1, max);
}
};
/**
* @private
*/
MCTTableController.prototype.insertSorted = function (array, element) {
var index = -1;
if (!this.$scope.sortColumn || !this.$scope.sortDirection) {
//No sorting applied, push it on the end.
index = array.length;
} else {
//Sort is enabled, perform binary search to find insertion point
index = binarySearch(array, element, 0, array.length - 1);
index = this.binarySearch(array, element, 0, array.length - 1);
}
if (index === -1) {
array.unshift(element);
@ -488,6 +496,11 @@ define(
this.resize(newRows).then(this.setVisibleRows.bind(this));
};
MCTTableController.prototype.onRowClick = function (event, row) {
var index = this.$scope.rows.indexOf(row);
this.$scope.onRowClick({event: event, rowIndex:index, sortColumn: this.$scope.sortColumn, sortDirection: this.$scope.sortDirection});
};
/**
* Applies user defined filters to rows. These filters are based on
* the text entered in the search areas in each column.

View File

@ -26,9 +26,10 @@
*/
define(
[
'../TableConfiguration'
'../TableConfiguration',
'../DomainColumn'
],
function (TableConfiguration) {
function (TableConfiguration, DomainColumn) {
/**
* The TableController is responsible for getting data onto the page
@ -43,7 +44,8 @@ define(
function TelemetryTableController(
$scope,
telemetryHandler,
telemetryFormatter
telemetryFormatter,
conductor
) {
var self = this;
@ -54,6 +56,8 @@ define(
this.table = new TableConfiguration($scope.domainObject,
telemetryFormatter);
this.changeListeners = [];
this.conductor = conductor;
this.data = [];
$scope.rows = [];
@ -64,9 +68,27 @@ define(
});
// Unsubscribe when the plot is destroyed
this.$scope.$on("$destroy", this.destroy.bind(this));
this.$scope.$on("$destroy", this.destroy);
}
TelemetryTableController.prototype.onRowClick = function (event, rowIndex, sortBy, sortOrder) {
var datum = this.data[rowIndex];
if (event.altKey) {
console.log("selected: " + this.$scope.rows[rowIndex]);
//Is column one that we can use to set time of interest?
var domainColumn = this.table.columns.filter(function (column) {
return column instanceof DomainColumn &&
column.getTitle() === sortBy;
})[0];
if (domainColumn) {
var timeOfInterest = datum[domainColumn.domainMetadata.key];
this.conductor.timeOfInterest(timeOfInterest);
}
}
};
/**
* @private
*/
@ -188,6 +210,10 @@ define(
});
};
TelemetryTableController.prototype.changeTimeOfInterest = function (toi) {
}
return TelemetryTableController;
}
);

View File

@ -88,12 +88,14 @@ define(
'exportService',
MCTTableController
],
controllerAs: "table",
scope: {
headers: "=",
rows: "=",
enableFilter: "=?",
enableSort: "=?",
autoScroll: "=?"
autoScroll: "=?",
onRowClick: "&"
}
};
}