mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 10:44:21 +00:00
Compare commits
95 Commits
notebook-e
...
plotly-tes
Author | SHA1 | Date | |
---|---|---|---|
352fe8ea7c | |||
29650f54de | |||
02d00aeb07 | |||
fd21594e4a | |||
5d0beb4351 | |||
f88d3bcaf3 | |||
c1102ed4b1 | |||
fcd8a9a9c9 | |||
5f729640b2 | |||
5fc12c771a | |||
7931177497 | |||
195aa0a95b | |||
c252d435bf | |||
87aa5a4342 | |||
bd98b81339 | |||
56794b0ed5 | |||
0398679abc | |||
1bc60f8108 | |||
122f3efa1f | |||
32791b442d | |||
7aaccdb286 | |||
f49556adad | |||
f726bfa31a | |||
e9c6c5760e | |||
07c3dd6cfa | |||
651a369391 | |||
ae67a2f438 | |||
1acda469a9 | |||
d5b0ef735c | |||
a6cac13dfc | |||
3d058151f2 | |||
76817193eb | |||
35ef4407be | |||
f3526f9185 | |||
70649b0657 | |||
3b89bf0b8c | |||
a314aa3c95 | |||
dc5723c227 | |||
61fd7d4e4e | |||
32fc871e67 | |||
6b74a133d8 | |||
ceaf4c2ef0 | |||
982b69e7e1 | |||
8e5182732d | |||
2d0b92cc38 | |||
75e846ea3f | |||
1b5dee981d | |||
4fee7f73e7 | |||
4308a4c9cf | |||
abbffcfbf1 | |||
01710876fb | |||
9e3d97c85d | |||
519075001e | |||
96a48dd9ee | |||
550daae76f | |||
b8fcb8ff14 | |||
373ddd0bf5 | |||
d62cc6b3ee | |||
5116d38437 | |||
29771f2722 | |||
7bfe4bb25c | |||
510c637081 | |||
d7c65fec4c | |||
626e2d8e80 | |||
1fe673c1f5 | |||
d9b00574e7 | |||
7c48b3ba9a | |||
b6e589eed4 | |||
fb1813c14b | |||
cce834f873 | |||
ca60d02614 | |||
9520c09b49 | |||
0d70717b35 | |||
f18da542d6 | |||
e7c38d473b | |||
2f8db31a33 | |||
1c58d8c85f | |||
baf426055c | |||
aa3af520ea | |||
4a43893ccc | |||
77f50a41c9 | |||
e4cd5f441f | |||
a9cfb002fb | |||
b603a5b722 | |||
be165761c7 | |||
da88cf58cc | |||
bc2bd53f9b | |||
6b7fd5f22a | |||
4ee214a142 | |||
4af24db38a | |||
8db27809ef | |||
3116b1addd | |||
5f67c45b50 | |||
8158afc29a | |||
13b3acb7e0 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -38,3 +38,4 @@ protractor/logs
|
||||
npm-debug.log
|
||||
|
||||
package-lock.json
|
||||
report.*.json
|
||||
|
@ -2,7 +2,6 @@
|
||||
"name": "openmct",
|
||||
"version": "1.0.0-snapshot",
|
||||
"description": "The Open MCT core platform",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"angular": ">=1.8.0",
|
||||
"angular-route": "1.4.14",
|
||||
@ -39,13 +38,13 @@
|
||||
"istanbul-instrumenter-loader": "^3.0.1",
|
||||
"jasmine-core": "^3.1.0",
|
||||
"jsdoc": "^3.3.2",
|
||||
"karma": "^2.0.3",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma": "5.0.9",
|
||||
"karma-chrome-launcher": "3.1.0",
|
||||
"karma-cli": "^1.0.1",
|
||||
"karma-coverage": "^1.1.2",
|
||||
"karma-coverage-istanbul-reporter": "^2.1.1",
|
||||
"karma-html-reporter": "^0.2.7",
|
||||
"karma-jasmine": "^1.1.2",
|
||||
"karma-jasmine": "^2.0.0",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-webpack": "^3.0.0",
|
||||
"location-bar": "^3.0.1",
|
||||
@ -60,6 +59,8 @@
|
||||
"node-bourbon": "^4.2.3",
|
||||
"node-sass": "^4.9.2",
|
||||
"painterro": "^0.2.65",
|
||||
"plotly.js-basic-dist-min": "^1.54.6",
|
||||
"plotly.js-gl2d-dist-min": "^1.54.5",
|
||||
"printj": "^1.2.1",
|
||||
"raw-loader": "^0.5.1",
|
||||
"request": "^2.69.0",
|
||||
|
@ -252,6 +252,7 @@ define([
|
||||
// Plugins that are installed by default
|
||||
|
||||
this.install(this.plugins.Plot());
|
||||
this.install(this.plugins.PlotlyPlot());
|
||||
this.install(this.plugins.TelemetryTable());
|
||||
this.install(PreviewPlugin.default());
|
||||
this.install(LegacyIndicatorsPlugin());
|
||||
|
72
src/plugins/plotlyPlot/PlotlyViewProvider.js
Normal file
72
src/plugins/plotlyPlot/PlotlyViewProvider.js
Normal file
@ -0,0 +1,72 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2019, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT 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 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import PlotlyViewLayout from './components/PlotlyViewLayout.vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default function PlotlyViewProvider(openmct) {
|
||||
return {
|
||||
key: 'plotlyPlot',
|
||||
name: 'Plotly Plot',
|
||||
cssClass: 'icon-plot-overlay',
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'plotlyPlot';
|
||||
},
|
||||
canEdit: function (domainObject) {
|
||||
return domainObject.type === 'plotlyPlot';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show: function (element, isEditing) {
|
||||
component = new Vue({
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject
|
||||
},
|
||||
el: element,
|
||||
components: {
|
||||
PlotlyViewLayout
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isEditing
|
||||
}
|
||||
},
|
||||
template: '<plotly-view-layout :isEditing="isEditing"></plotly-view-layout>'
|
||||
});
|
||||
},
|
||||
onEditModeChange: function (isEditing) {
|
||||
component.isEditing = isEditing;
|
||||
},
|
||||
destroy: function (element) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
317
src/plugins/plotlyPlot/components/PlotlyViewLayout.vue
Normal file
317
src/plugins/plotlyPlot/components/PlotlyViewLayout.vue
Normal file
@ -0,0 +1,317 @@
|
||||
<template>
|
||||
<div :id="plotId"
|
||||
class="l-view-section">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import Plotly from 'plotly.js-gl2d-dist-min';
|
||||
import Plotly from 'plotly.js-basic-dist-min';
|
||||
import BoundedTableRowCollection from '../../telemetryTable/collections/BoundedTableRowCollection';
|
||||
import TelemetryTableRow from '../../telemetryTable/TelemetryTableRow';
|
||||
import TelemetryTableColumn from '../../telemetryTable/TelemetryTableColumn';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data: function () {
|
||||
return {
|
||||
bounds: this.openmct.time.bounds(),
|
||||
plotData: {},
|
||||
outstandingRequests: 0,
|
||||
subscriptions: {},
|
||||
plotComposition: undefined,
|
||||
timestampKey: this.openmct.time.timeSystem().key,
|
||||
yAxisLabel: '',
|
||||
plotId: this.openmct.objects.makeKeyString(this.domainObject.identifier)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getContainerHeight: function () {
|
||||
return this.plotElement.parentNode.offsetHeight - 5;
|
||||
},
|
||||
getContainerWidth: function () {
|
||||
return this.plotElement.parentNode.offsetWidth - 5;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.plotElement = document.getElementById(this.plotId);
|
||||
this.openmct.time.on('bounds', this.updateDomain);
|
||||
this.openmct.time.on('bounds', this.updateData);
|
||||
|
||||
this.loadComposition();
|
||||
this.createPlot();
|
||||
|
||||
this.boundedRows = {};
|
||||
this.limitEvaluators = {};
|
||||
this.columnMaps = {};
|
||||
this.drawBuffers = {};
|
||||
this.telemetryObjects = [];
|
||||
this.subscriptions = {};
|
||||
this.boundedRowsUnlisteners = {};
|
||||
this.traceIndices = {};
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
Object.values(this.subscriptions)
|
||||
.forEach(subscription => subscription());
|
||||
|
||||
this.openmct.time.off('bounds', this.updateDomain);
|
||||
this.openmct.time.off('bounds', this.updateData);
|
||||
|
||||
Object.values(this.boundedRowsUnlisteners).forEach((unlisteners) => {
|
||||
unlisteners.forEach(unlistener => unlistener());
|
||||
});
|
||||
|
||||
this.plotComposition.off('add', this.addTelemetryObject);
|
||||
this.plotComposition.off('remove', this.removeTelemetryObject);
|
||||
},
|
||||
methods: {
|
||||
loadComposition() {
|
||||
this.plotComposition = this.openmct.composition.get(this.domainObject);
|
||||
this.plotComposition.on('add', this.addTelemetryObject);
|
||||
this.plotComposition.on('remove', this.removeTelemetryObject);
|
||||
this.plotComposition.load()
|
||||
},
|
||||
addTelemetryObject(telemetryObject) {
|
||||
this.telemetryObjects.push(telemetryObject);
|
||||
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||
this.addTraceForObject(telemetryObject);
|
||||
|
||||
this.requestData(telemetryObject);
|
||||
let subscription = this.subscribe(telemetryObject);
|
||||
this.subscriptions[keyString] = subscription;
|
||||
},
|
||||
removeTelemetryObject(identifier) {
|
||||
const keyString = this.openmct.objects.makeKeyString(identifier);
|
||||
const index = this.telemetryObjects.findIndex(object => identifier.key === object.identifier.key);
|
||||
this.unsubscribe(keyString);
|
||||
this.removeTraceForObject(this.telemetryObjects[index]);
|
||||
this.telemetryObjects = this.telemetryObjects.filter(object => !(identifier.key === object.identifier.key));
|
||||
if (!this.telemetryObjects.length) {
|
||||
Plotly.purge(this.plotElement);
|
||||
this.createPlot();
|
||||
}
|
||||
},
|
||||
updateDomain(bounds, isTick) {
|
||||
let newDomain = {
|
||||
'xaxis.range': [
|
||||
bounds.start,
|
||||
bounds.end
|
||||
]
|
||||
};
|
||||
Plotly.relayout(this.plotElement, newDomain);
|
||||
},
|
||||
updateData(bounds, isTick) {
|
||||
if (!isTick) {
|
||||
this.clearData();
|
||||
this.telemetryObjects.forEach(telemetryObject => this.requestData(telemetryObject));
|
||||
}
|
||||
},
|
||||
clearData() {
|
||||
this.telemetryObjects.forEach(telemetryObject => this.resetTraceForObject(telemetryObject));
|
||||
},
|
||||
requestData(telemetryObject) {
|
||||
return this.openmct.telemetry.request(telemetryObject)
|
||||
.then(telemetryData => {
|
||||
const keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||
let columnMap = this.columnMaps[keyString];
|
||||
let limitEvaluator = this.limitEvaluators[keyString];
|
||||
const telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
|
||||
this.boundedRows[keyString].add(telemetryRows);
|
||||
});
|
||||
},
|
||||
subscribe(telemetryObject) {
|
||||
const keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||
let columnMap = this.columnMaps[keyString];
|
||||
let limitEvaluator = this.limitEvaluators[keyString];
|
||||
|
||||
return this.openmct.telemetry.subscribe(telemetryObject, (datum) => {
|
||||
let newRow = new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator);
|
||||
this.boundedRows[keyString].add(newRow);
|
||||
});
|
||||
},
|
||||
unsubscribe(keyString) {
|
||||
this.subscriptions[keyString]();
|
||||
delete this.subscriptions[keyString];
|
||||
},
|
||||
createPlot() {
|
||||
let timeSystem = this.openmct.time.timeSystem();
|
||||
let bounds = this.openmct.time.bounds();
|
||||
let formatMetadata = {
|
||||
key: timeSystem.key,
|
||||
name: timeSystem.name,
|
||||
format: timeSystem.timeFormat
|
||||
}
|
||||
this.timeFormatter = this.openmct.telemetry.getValueFormatter(formatMetadata);
|
||||
let xRange = [
|
||||
bounds.start,
|
||||
bounds.end
|
||||
];
|
||||
|
||||
let layout = {
|
||||
hovermode: 'compare',
|
||||
hoverdistance: -1,
|
||||
autosize: true,
|
||||
showlegend: true,
|
||||
legend: {
|
||||
y: 1.07,
|
||||
"orientation": "h"
|
||||
},
|
||||
height: this.getContainerHeight,
|
||||
font: {
|
||||
family: "'Helvetica Neue', Helvetica, Arial, sans-serif",
|
||||
size: "12px",
|
||||
color: "#aaa"
|
||||
},
|
||||
xaxis: {
|
||||
title: timeSystem.name,
|
||||
type: 'date',
|
||||
zeroline: false,
|
||||
showgrid: false,
|
||||
range: xRange
|
||||
},
|
||||
yaxis: {
|
||||
zeroline: false,
|
||||
showgrid: false,
|
||||
tickwidth: 3,
|
||||
tickcolor: 'transparent',
|
||||
autorange: true,
|
||||
visible: false
|
||||
},
|
||||
margin: {
|
||||
l: 40,
|
||||
r: 5,
|
||||
b: 40,
|
||||
t: 0
|
||||
},
|
||||
paper_bgcolor: 'transparent',
|
||||
plot_bgcolor: 'transparent'
|
||||
};
|
||||
Plotly.newPlot(
|
||||
this.plotElement,
|
||||
[],
|
||||
layout,
|
||||
{
|
||||
displayModeBar: true, // turns off hover-activated toolbar
|
||||
staticPlot: false // turns off hover effects on datapoints
|
||||
}
|
||||
);
|
||||
|
||||
},
|
||||
resetTraceForObject(telemetryObject) {
|
||||
this.removeTraceForObject(telemetryObject);
|
||||
this.addTraceForObject(telemetryObject);
|
||||
},
|
||||
removeTraceForObject(telemetryObject) {
|
||||
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||
let index = this.traceIndices[keyString];
|
||||
Plotly.deleteTraces(this.plotElement, index);
|
||||
|
||||
delete this.traceIndices[keyString];
|
||||
this.recalculateTraceIndices();
|
||||
|
||||
this.boundedRowsUnlisteners[keyString].forEach((unlistener) => unlistener());
|
||||
},
|
||||
addTraceForObject(telemetryObject) {
|
||||
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||
let boundedRows = new BoundedTableRowCollection(this.openmct);
|
||||
this.boundedRows[keyString] = boundedRows;
|
||||
|
||||
this.traceIndices[keyString] = Object.keys(this.traceIndices).length;
|
||||
this.recalculateTraceIndices();
|
||||
|
||||
Plotly.addTraces(this.plotElement, {
|
||||
x: [],
|
||||
y: [],
|
||||
name: telemetryObject.name,
|
||||
type: "scattergl",
|
||||
mode: 'lines+markers',
|
||||
marker: {
|
||||
size: 5
|
||||
},
|
||||
line: {
|
||||
shape: 'linear',
|
||||
width: 1.5
|
||||
}
|
||||
});
|
||||
|
||||
const metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
|
||||
|
||||
let columnMap = metadataValues.reduce((map, metadatum) => {
|
||||
let column = new TelemetryTableColumn(this.openmct, metadatum);
|
||||
map[metadatum.key] = column;
|
||||
return map;
|
||||
}, {});
|
||||
const limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
|
||||
const valueFormatter = this.openmct.telemetry.getValueFormatter(this.openmct.telemetry.getMetadata(telemetryObject).valuesForHints(['range'])[0]);
|
||||
let layout_update = {
|
||||
yaxis: {title: valueFormatter.valueMetadata.name, visible: true}
|
||||
};
|
||||
Plotly.update(this.plotElement, {}, layout_update)
|
||||
this.columnMaps[keyString] = columnMap;
|
||||
this.limitEvaluators[keyString] = limitEvaluator;
|
||||
|
||||
let timeSystemKey = this.openmct.time.timeSystem().key;
|
||||
let drawBuffer = {
|
||||
keyString,
|
||||
x: [],
|
||||
y: []
|
||||
};
|
||||
|
||||
this.drawBuffers[keyString] = drawBuffer;
|
||||
|
||||
const addRow = (rows) => {
|
||||
if (rows instanceof Array) {
|
||||
rows.forEach(row => {
|
||||
drawBuffer.x.push(row.datum[timeSystemKey]);
|
||||
drawBuffer.y.push(valueFormatter.format(row.datum));
|
||||
})
|
||||
} else {
|
||||
drawBuffer.x.push(rows.datum[timeSystemKey]);
|
||||
drawBuffer.y.push(valueFormatter.format(rows.datum));
|
||||
}
|
||||
this.scheduleDraw();
|
||||
}
|
||||
|
||||
boundedRows.on('add', addRow);
|
||||
this.boundedRowsUnlisteners[keyString] = [];
|
||||
|
||||
this.boundedRowsUnlisteners[keyString].push(() => {
|
||||
boundedRows.off('add', addRow);
|
||||
})
|
||||
},
|
||||
recalculateTraceIndices() {
|
||||
Object.keys(this.traceIndices).forEach((key, indexOfKey) => {
|
||||
this.traceIndices[key] = indexOfKey;
|
||||
});
|
||||
},
|
||||
scheduleDraw() {
|
||||
if (!this.drawing) {
|
||||
this.drawing = true;
|
||||
requestAnimationFrame(() => {
|
||||
let dataForXAxes = [];
|
||||
let dataForYAxes = [];
|
||||
let traceIndices = [];
|
||||
Object.values(this.drawBuffers).forEach((drawBuffer) => {
|
||||
dataForXAxes.push(drawBuffer.x);
|
||||
dataForYAxes.push(drawBuffer.y);
|
||||
traceIndices.push(this.traceIndices[drawBuffer.keyString]);
|
||||
drawBuffer.x = [];
|
||||
drawBuffer.y = [];
|
||||
});
|
||||
Plotly.extendTraces(
|
||||
this.plotElement,
|
||||
{
|
||||
x: dataForXAxes,
|
||||
y: dataForYAxes
|
||||
},
|
||||
traceIndices
|
||||
);
|
||||
|
||||
this.drawing = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
3
src/plugins/plotlyPlot/components/plotly.scss
Normal file
3
src/plugins/plotlyPlot/components/plotly.scss
Normal file
@ -0,0 +1,3 @@
|
||||
.plot svg {
|
||||
}
|
||||
|
17
src/plugins/plotlyPlot/plugin.js
Normal file
17
src/plugins/plotlyPlot/plugin.js
Normal file
@ -0,0 +1,17 @@
|
||||
import PlotlyViewProvider from './PlotlyViewProvider';
|
||||
|
||||
export default function plugin() {
|
||||
return function install(openmct) {
|
||||
openmct.objectViews.addProvider(new PlotlyViewProvider(openmct));
|
||||
|
||||
openmct.types.addType('plotlyPlot', {
|
||||
name: "Plotly Plot",
|
||||
description: "Simple plot rendered by plotly.js",
|
||||
creatable: true,
|
||||
cssClass: 'icon-plot-overlay',
|
||||
initialize: function (domainObject) {
|
||||
domainObject.composition = [];
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
@ -34,6 +34,7 @@ define([
|
||||
'./URLIndicatorPlugin/URLIndicatorPlugin',
|
||||
'./telemetryMean/plugin',
|
||||
'./plot/plugin',
|
||||
'./plotlyPlot/plugin',
|
||||
'./telemetryTable/plugin',
|
||||
'./staticRootPlugin/plugin',
|
||||
'./notebook/plugin',
|
||||
@ -69,6 +70,7 @@ define([
|
||||
URLIndicatorPlugin,
|
||||
TelemetryMean,
|
||||
PlotPlugin,
|
||||
PlotlyPlotPlugin,
|
||||
TelemetryTablePlugin,
|
||||
StaticRootPlugin,
|
||||
Notebook,
|
||||
@ -177,8 +179,8 @@ define([
|
||||
plugins.ExampleImagery = ExampleImagery;
|
||||
plugins.ImageryPlugin = ImageryPlugin;
|
||||
plugins.Plot = PlotPlugin;
|
||||
plugins.PlotlyPlot = PlotlyPlotPlugin.default;
|
||||
plugins.TelemetryTable = TelemetryTablePlugin;
|
||||
|
||||
plugins.SummaryWidget = SummaryWidget;
|
||||
plugins.TelemetryMean = TelemetryMean;
|
||||
plugins.URLIndicator = URLIndicatorPlugin;
|
||||
|
@ -73,6 +73,7 @@ define(
|
||||
* @private
|
||||
*/
|
||||
addOne(row) {
|
||||
// console.log('SortedTableRowCollection addOne', row);
|
||||
if (this.sortOptions === undefined) {
|
||||
throw 'Please specify sort options';
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
@import "../plugins/folderView/components/list-item.scss";
|
||||
@import "../plugins/folderView/components/list-view.scss";
|
||||
@import "../plugins/imagery/components/imagery-view-layout.scss";
|
||||
@import "../plugins/plotlyPlot/components/plotly.scss";
|
||||
@import "../plugins/telemetryTable/components/table-row.scss";
|
||||
@import "../plugins/telemetryTable/components/telemetry-filter-indicator.scss";
|
||||
@import "../plugins/tabs/components/tabs.scss";
|
||||
|
Reference in New Issue
Block a user