mirror of
https://github.com/nasa/openmct.git
synced 2024-12-21 14:07:50 +00:00
[Scrolling] Bring in scrolling list view
Bring in scrolling list view from the sandbox branch in preparation for clean up, testing, and integration to complete transition of scrolling list views, WTD-534.
This commit is contained in:
parent
622f1f8be7
commit
d304cc4343
24
platform/features/scrolling/bundle.json
Normal file
24
platform/features/scrolling/bundle.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "Scrolling Lists",
|
||||
"description": "Time-ordered list of latest data.",
|
||||
"extensions": {
|
||||
"views": [
|
||||
{
|
||||
"key": "scrolling",
|
||||
"name": "Scrolling",
|
||||
"description": "Scrolling list of data values.",
|
||||
"templateUrl": "templates/scrolling.html",
|
||||
"needs": [ "telemetry" ],
|
||||
"delegation": true
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "ScrollingListController",
|
||||
"implementation": "ScrollingListController.js",
|
||||
"depends": [ "$scope" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
}
|
25
platform/features/scrolling/res/templates/scrolling.html
Normal file
25
platform/features/scrolling/res/templates/scrolling.html
Normal file
@ -0,0 +1,25 @@
|
||||
<div class="w1" ng-controller="TelemetryController as telemetry">
|
||||
<!-- Dynamically set height based on number of rows desired. In current CSS, each row occupies 18px -->
|
||||
<div class="w2"
|
||||
ng-style="{ height: rows.length * 18 + 'px' }"
|
||||
ng-controller="ScrollingListController">
|
||||
<div class="tabular">
|
||||
|
||||
<div class="tr header">
|
||||
<!-- Enable sorting by binding click handlers to the <em> element in each <th>. Note that CSS classes must be added dynamically to the current <th> that is sorting. For example, if sorting by ID: <div class="th sort asc"> or <div class="th sort desc">. -->
|
||||
<div class="th" ng-repeat="header in headers">
|
||||
<em>{{header}}</em>{{header}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tr" ng-repeat="row in rows">
|
||||
<div class="td" ng-repeat="cell in row">
|
||||
{{cell}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
31
platform/features/scrolling/src/DomainColumn.js
Normal file
31
platform/features/scrolling/src/DomainColumn.js
Normal file
@ -0,0 +1,31 @@
|
||||
/*global define,Promise*/
|
||||
|
||||
/**
|
||||
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function DomainColumn(domainMetadata) {
|
||||
return {
|
||||
getTitle: function () {
|
||||
return domainMetadata.name;
|
||||
},
|
||||
getValue: function (domainObject, data, index) {
|
||||
return data.getDomainValue(
|
||||
index,
|
||||
domainMetadata.key
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return DomainColumn;
|
||||
}
|
||||
);
|
28
platform/features/scrolling/src/NameColumn.js
Normal file
28
platform/features/scrolling/src/NameColumn.js
Normal file
@ -0,0 +1,28 @@
|
||||
/*global define,Promise*/
|
||||
|
||||
/**
|
||||
* Module defining NameColumn. Created by vwoeltje on 11/18/14.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function NameColumn() {
|
||||
return {
|
||||
getTitle: function () {
|
||||
return "Name";
|
||||
},
|
||||
getValue: function (domainObject) {
|
||||
return domainObject.getModel().name;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return NameColumn;
|
||||
}
|
||||
);
|
31
platform/features/scrolling/src/RangeColumn.js
Normal file
31
platform/features/scrolling/src/RangeColumn.js
Normal file
@ -0,0 +1,31 @@
|
||||
/*global define,Promise*/
|
||||
|
||||
/**
|
||||
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function RangeColumn(rangeMetadata) {
|
||||
return {
|
||||
getTitle: function () {
|
||||
return rangeMetadata.name;
|
||||
},
|
||||
getValue: function (domainObject, data, index) {
|
||||
return data.getRangeValue(
|
||||
index,
|
||||
rangeMetadata.key
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return RangeColumn;
|
||||
}
|
||||
);
|
174
platform/features/scrolling/src/ScrollingListController.js
Normal file
174
platform/features/scrolling/src/ScrollingListController.js
Normal file
@ -0,0 +1,174 @@
|
||||
/*global define,Promise*/
|
||||
|
||||
/**
|
||||
* Module defining ListController. Created by vwoeltje on 11/18/14.
|
||||
*/
|
||||
define(
|
||||
["./NameColumn", "./DomainColumn", "./RangeColumn"],
|
||||
function (NameColumn, DomainColumn, RangeColumn) {
|
||||
"use strict";
|
||||
|
||||
var ROW_COUNT = 18;
|
||||
|
||||
/**
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function ScrollingListController($scope) {
|
||||
var columns = []; // Domain used
|
||||
|
||||
/**
|
||||
* Look up the most recent values from a set of data objects.
|
||||
* Returns an array of objects in the order in which data
|
||||
* should be displayed; each element is an object with
|
||||
* two properties:
|
||||
*
|
||||
* * objectIndex: The index of the domain object associated
|
||||
* with the data point to be displayed in that
|
||||
* row.
|
||||
* * pointIndex: The index of the data point itself, within
|
||||
* its data set.
|
||||
*
|
||||
* @param {Array<Telemetry>} datas an array of the most recent
|
||||
* data objects; expected to be in the same order
|
||||
* as the domain objects provided at constructor
|
||||
* @param {Array<ScrollingColumn}
|
||||
*/
|
||||
function getLatestDataValues(datas) {
|
||||
var latest = [],
|
||||
candidate,
|
||||
candidateTime,
|
||||
used = datas.map(function () { return 0; });
|
||||
|
||||
// This algorithm is O(nk) for n rows and k telemetry elements;
|
||||
// one O(k) linear search for a max is made for each of n rows.
|
||||
// This could be done in O(n lg k + k lg k), using a priority
|
||||
// queue (where priority is max-finding) containing k initial
|
||||
// values. For n rows, pop the max from the queue and replenish
|
||||
// the queue with a value from the data at the same
|
||||
// objectIndex, if available.
|
||||
// But k is small, so this might not give an observable
|
||||
// improvement in performance.
|
||||
|
||||
// Find the most recent unused data point (this will be used
|
||||
// in a loop to find and the N most recent data points)
|
||||
function findCandidate(data, i) {
|
||||
var nextTime,
|
||||
pointCount = data.getPointCount(),
|
||||
pointIndex = pointCount - used[i] - 1;
|
||||
if (data && pointIndex >= 0) {
|
||||
nextTime = data.getDomainValue(pointIndex);
|
||||
if (nextTime > candidateTime) {
|
||||
candidateTime = nextTime;
|
||||
candidate = {
|
||||
objectIndex: i,
|
||||
pointIndex: pointIndex
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assemble a list of the most recent data points
|
||||
while (latest.length < ROW_COUNT) {
|
||||
// Reset variables pre-search
|
||||
candidateTime = Number.NEGATIVE_INFINITY;
|
||||
candidate = undefined;
|
||||
|
||||
// Linear search for most recent
|
||||
datas.forEach(findCandidate);
|
||||
|
||||
if (candidate) {
|
||||
// Record this data point - it is the most recent
|
||||
latest.push(candidate);
|
||||
|
||||
// Track the data points used so we can look farther back
|
||||
// in the data set on the next iteration
|
||||
used[candidate.objectIndex] = used[candidate.objectIndex] + 1;
|
||||
} else {
|
||||
break; // Ran out of candidates; not enough data points available
|
||||
}
|
||||
}
|
||||
|
||||
return latest;
|
||||
}
|
||||
|
||||
function getRows(telemetry) {
|
||||
var datas = telemetry.getResponse(),
|
||||
objects = telemetry.getTelemetryObjects(),
|
||||
values = getLatestDataValues(datas);
|
||||
|
||||
return values.map(function (value) {
|
||||
return columns.map(function (column) {
|
||||
return column.getValue(
|
||||
objects[value.objectIndex],
|
||||
datas[value.objectIndex],
|
||||
value.pointIndex
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateRows() {
|
||||
var telemetry = $scope.telemetry;
|
||||
$scope.rows = telemetry ? getRows(telemetry) : [];
|
||||
}
|
||||
|
||||
function setupColumns() {
|
||||
var telemetry = $scope.telemetry,
|
||||
domainKeys = {},
|
||||
rangeKeys = {},
|
||||
metadata;
|
||||
|
||||
function addDomain(domain) {
|
||||
var key = domain.key;
|
||||
if (key && !domainKeys[key]) {
|
||||
columns.push(new DomainColumn(domain));
|
||||
}
|
||||
}
|
||||
|
||||
function addRange(range) {
|
||||
var key = range.key;
|
||||
if (key && !rangeKeys[key]) {
|
||||
columns.push(new RangeColumn(range));
|
||||
}
|
||||
}
|
||||
|
||||
if (!telemetry) {
|
||||
columns = [];
|
||||
$scope.rows = [];
|
||||
$scope.headers = [];
|
||||
return;
|
||||
}
|
||||
|
||||
columns = [ new NameColumn() ];
|
||||
|
||||
// Add domain, range columns
|
||||
metadata = telemetry.getMetadata();
|
||||
(metadata || []).forEach(function (metadata) {
|
||||
(metadata.domains || []).forEach(addDomain);
|
||||
(metadata.ranges || []).forEach(addRange);
|
||||
});
|
||||
|
||||
// Add default domain, range columns if none
|
||||
// were described in metadata.
|
||||
if (Object.keys(domainKeys).length < 1) {
|
||||
columns.push(new DomainColumn({ name: "Time" }));
|
||||
}
|
||||
if (Object.keys(rangeKeys).length < 1) {
|
||||
columns.push(new RangeColumn({ name: "Value" }));
|
||||
}
|
||||
|
||||
$scope.headers = columns.map(function (column) {
|
||||
return column.getTitle();
|
||||
});
|
||||
|
||||
updateRows();
|
||||
}
|
||||
|
||||
$scope.$on("telemetryUpdate", updateRows);
|
||||
$scope.$watch("telemetry", setupColumns);
|
||||
}
|
||||
|
||||
return ScrollingListController;
|
||||
}
|
||||
);
|
Loading…
Reference in New Issue
Block a user