mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 10:44:21 +00:00
Compare commits
13 Commits
v1.7.1
...
expose-vue
Author | SHA1 | Date | |
---|---|---|---|
bdc3edda88 | |||
aebb5df611 | |||
605eeff9d7 | |||
a83ee1f90f | |||
fe899cbcc8 | |||
633bac2ed5 | |||
dacec48aec | |||
3ca133c782 | |||
12416b8079 | |||
9920e67c83 | |||
0e80a5b8a0 | |||
05f9202fe4 | |||
0da35a44b0 |
1
API.md
1
API.md
@ -430,6 +430,7 @@ Known hints:
|
||||
* `domain`: Values with a `domain` hint will be used for the x-axis of a plot, and tables will render columns for these values first.
|
||||
* `range`: Values with a `range` hint will be used as the y-axis on a plot, and tables will render columns for these values after the `domain` values.
|
||||
* `image`: Indicates that the value may be interpreted as the URL to an image file, in which case appropriate views will be made available.
|
||||
* `imageDownloadName`: Indicates that the value may be interpreted as the name of the image file.
|
||||
|
||||
##### The Time Conductor and Telemetry
|
||||
|
||||
|
@ -50,11 +50,16 @@ define([
|
||||
const IMAGE_DELAY = 20000;
|
||||
|
||||
function pointForTimestamp(timestamp, name) {
|
||||
const url = IMAGE_SAMPLES[Math.floor(timestamp / IMAGE_DELAY) % IMAGE_SAMPLES.length];
|
||||
const urlItems = url.split('/');
|
||||
const imageDownloadName = `example.imagery.${urlItems[urlItems.length - 1]}`;
|
||||
|
||||
return {
|
||||
name: name,
|
||||
name,
|
||||
utc: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
||||
local: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
||||
url: IMAGE_SAMPLES[Math.floor(timestamp / IMAGE_DELAY) % IMAGE_SAMPLES.length]
|
||||
url,
|
||||
imageDownloadName
|
||||
};
|
||||
}
|
||||
|
||||
@ -139,6 +144,14 @@ define([
|
||||
hints: {
|
||||
image: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Image Download Name',
|
||||
key: 'imageDownloadName',
|
||||
format: 'imageDownloadName',
|
||||
hints: {
|
||||
imageDownloadName: 1
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -88,7 +88,6 @@
|
||||
openmct.install(openmct.plugins.ExampleImagery());
|
||||
openmct.install(openmct.plugins.PlanLayout());
|
||||
openmct.install(openmct.plugins.Timeline());
|
||||
openmct.install(openmct.plugins.PlotVue());
|
||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||
openmct.install(openmct.plugins.AutoflowView({
|
||||
type: "telemetry.panel"
|
||||
|
@ -141,11 +141,17 @@ define(
|
||||
if (mutationResult !== false) {
|
||||
// Copy values if result was a different object
|
||||
// (either our clone or some other new thing)
|
||||
if (model !== result) {
|
||||
let modelHasChanged = _.isEqual(model, result) === false;
|
||||
if (modelHasChanged) {
|
||||
copyValues(model, result);
|
||||
}
|
||||
|
||||
model.modified = useTimestamp ? timestamp : now();
|
||||
if (modelHasChanged
|
||||
|| (useTimestamp !== undefined)
|
||||
|| (model.modified === undefined)) {
|
||||
model.modified = useTimestamp ? timestamp : now();
|
||||
}
|
||||
|
||||
notifyListeners(model);
|
||||
}
|
||||
|
||||
|
@ -460,6 +460,13 @@ define([
|
||||
this.router.destroy();
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose this project's Vue so plugins can make use of it.
|
||||
*
|
||||
* @memberof module:openmct.MCT#
|
||||
*/
|
||||
MCT.prototype.Vue = Vue;
|
||||
|
||||
MCT.prototype.plugins = plugins;
|
||||
|
||||
return MCT;
|
||||
|
@ -36,7 +36,8 @@ define([
|
||||
'./views/installLegacyViews',
|
||||
'./policies/LegacyCompositionPolicyAdapter',
|
||||
'./actions/LegacyActionAdapter',
|
||||
'./services/LegacyPersistenceAdapter'
|
||||
'./services/LegacyPersistenceAdapter',
|
||||
'./services/ExportImageService'
|
||||
], function (
|
||||
ActionDialogDecorator,
|
||||
AdapterCapability,
|
||||
@ -53,7 +54,8 @@ define([
|
||||
installLegacyViews,
|
||||
legacyCompositionPolicyAdapter,
|
||||
LegacyActionAdapter,
|
||||
LegacyPersistenceAdapter
|
||||
LegacyPersistenceAdapter,
|
||||
ExportImageService
|
||||
) {
|
||||
return {
|
||||
name: 'src/adapter',
|
||||
@ -82,6 +84,13 @@ define([
|
||||
"identifierService",
|
||||
"cacheService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "exportImageService",
|
||||
"implementation": ExportImageService,
|
||||
"depends": [
|
||||
"dialogService"
|
||||
]
|
||||
}
|
||||
],
|
||||
components: [
|
||||
|
@ -161,6 +161,7 @@ ObjectAPI.prototype.addProvider = function (namespace, provider) {
|
||||
|
||||
ObjectAPI.prototype.get = function (identifier, abortSignal) {
|
||||
let keystring = this.makeKeyString(identifier);
|
||||
|
||||
if (this.cache[keystring] !== undefined) {
|
||||
return this.cache[keystring];
|
||||
}
|
||||
@ -176,15 +177,16 @@ ObjectAPI.prototype.get = function (identifier, abortSignal) {
|
||||
throw new Error('Provider does not support get!');
|
||||
}
|
||||
|
||||
let objectPromise = provider.get(identifier, abortSignal);
|
||||
this.cache[keystring] = objectPromise;
|
||||
|
||||
return objectPromise.then(result => {
|
||||
let objectPromise = provider.get(identifier, abortSignal).then(result => {
|
||||
delete this.cache[keystring];
|
||||
result = this.applyGetInterceptors(identifier, result);
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
this.cache[keystring] = objectPromise;
|
||||
|
||||
return objectPromise;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -484,6 +486,12 @@ ObjectAPI.prototype.getOriginalPath = function (identifier, path = []) {
|
||||
});
|
||||
};
|
||||
|
||||
ObjectAPI.prototype.isObjectPathToALink = function (domainObject, objectPath) {
|
||||
return objectPath !== undefined
|
||||
&& objectPath.length > 1
|
||||
&& domainObject.location !== this.makeKeyString(objectPath[1].identifier);
|
||||
};
|
||||
|
||||
/**
|
||||
* Uniquely identifies a domain object.
|
||||
*
|
||||
|
@ -43,12 +43,16 @@ export default function LADTableSetViewProvider(openmct) {
|
||||
components: {
|
||||
LadTableSet: LadTableSet
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
domainObject
|
||||
};
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
objectPath
|
||||
},
|
||||
template: '<lad-table-set></lad-table-set>'
|
||||
template: '<lad-table-set :domain-object="domainObject"></lad-table-set>'
|
||||
});
|
||||
},
|
||||
destroy: function (element) {
|
||||
|
@ -56,7 +56,7 @@ export default {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
objectPath: {
|
||||
pathToTable: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
@ -66,20 +66,19 @@ export default {
|
||||
}
|
||||
},
|
||||
data() {
|
||||
let currentObjectPath = this.objectPath.slice();
|
||||
currentObjectPath.unshift(this.domainObject);
|
||||
|
||||
return {
|
||||
timestamp: undefined,
|
||||
value: '---',
|
||||
valueClass: '',
|
||||
currentObjectPath,
|
||||
unit: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
formattedTimestamp() {
|
||||
return this.timestamp !== undefined ? this.getFormattedTimestamp(this.timestamp) : '---';
|
||||
},
|
||||
objectPath() {
|
||||
return [this.domainObject, ...this.pathToTable];
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@ -182,7 +181,7 @@ export default {
|
||||
};
|
||||
},
|
||||
showContextMenu(event) {
|
||||
let actionCollection = this.openmct.actions.get(this.currentObjectPath, this.getView());
|
||||
let actionCollection = this.openmct.actions.get(this.objectPath, this.getView());
|
||||
let allActions = actionCollection.getActionsObject();
|
||||
let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]);
|
||||
|
||||
|
@ -33,10 +33,10 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<lad-row
|
||||
v-for="item in items"
|
||||
:key="item.key"
|
||||
:domain-object="item.domainObject"
|
||||
:object-path="objectPath"
|
||||
v-for="ladRow in items"
|
||||
:key="ladRow.key"
|
||||
:domain-object="ladRow.domainObject"
|
||||
:path-to-table="objectPath"
|
||||
:has-units="hasUnits"
|
||||
/>
|
||||
</tbody>
|
||||
|
@ -43,9 +43,10 @@
|
||||
</td>
|
||||
</tr>
|
||||
<lad-row
|
||||
v-for="telemetryObject in ladTelemetryObjects[ladTable.key]"
|
||||
:key="telemetryObject.key"
|
||||
:domain-object="telemetryObject.domainObject"
|
||||
v-for="ladRow in ladTelemetryObjects[ladTable.key]"
|
||||
:key="ladRow.key"
|
||||
:domain-object="ladRow.domainObject"
|
||||
:path-to-table="ladTable.objectPath"
|
||||
:has-units="hasUnits"
|
||||
/>
|
||||
</template>
|
||||
@ -60,7 +61,13 @@ export default {
|
||||
components: {
|
||||
LadRow
|
||||
},
|
||||
inject: ['openmct', 'domainObject'],
|
||||
inject: ['openmct', 'objectPath'],
|
||||
props: {
|
||||
domainObject: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ladTableObjects: [],
|
||||
@ -106,6 +113,7 @@ export default {
|
||||
let ladTable = {};
|
||||
ladTable.domainObject = domainObject;
|
||||
ladTable.key = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
ladTable.objectPath = [domainObject, ...this.objectPath];
|
||||
|
||||
this.$set(this.ladTelemetryObjects, ladTable.key, []);
|
||||
this.ladTableObjects.push(ladTable);
|
||||
|
@ -272,11 +272,11 @@ export default class Condition extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
requestLADConditionResult() {
|
||||
requestLADConditionResult(options) {
|
||||
let latestTimestamp;
|
||||
let criteriaResults = {};
|
||||
const criteriaRequests = this.criteria
|
||||
.map(criterion => criterion.requestLAD(this.conditionManager.telemetryObjects));
|
||||
.map(criterion => criterion.requestLAD(this.conditionManager.telemetryObjects, options));
|
||||
|
||||
return Promise.all(criteriaRequests)
|
||||
.then(results => {
|
||||
|
@ -282,7 +282,7 @@ export default class ConditionManager extends EventEmitter {
|
||||
return currentCondition;
|
||||
}
|
||||
|
||||
requestLADConditionSetOutput() {
|
||||
requestLADConditionSetOutput(options) {
|
||||
if (!this.conditions.length) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
@ -291,7 +291,7 @@ export default class ConditionManager extends EventEmitter {
|
||||
let latestTimestamp;
|
||||
let conditionResults = {};
|
||||
const conditionRequests = this.conditions
|
||||
.map(condition => condition.requestLADConditionResult());
|
||||
.map(condition => condition.requestLADConditionResult(options));
|
||||
|
||||
return Promise.all(conditionRequests)
|
||||
.then((results) => {
|
||||
|
@ -40,10 +40,10 @@ export default class ConditionSetTelemetryProvider {
|
||||
return domainObject.type === 'conditionSet';
|
||||
}
|
||||
|
||||
request(domainObject) {
|
||||
request(domainObject, options) {
|
||||
let conditionManager = this.getConditionManager(domainObject);
|
||||
|
||||
return conditionManager.requestLADConditionSetOutput()
|
||||
return conditionManager.requestLADConditionSetOutput(options)
|
||||
.then(latestOutput => {
|
||||
return latestOutput;
|
||||
});
|
||||
@ -52,7 +52,9 @@ export default class ConditionSetTelemetryProvider {
|
||||
subscribe(domainObject, callback) {
|
||||
let conditionManager = this.getConditionManager(domainObject);
|
||||
|
||||
conditionManager.on('conditionSetResultUpdated', callback);
|
||||
conditionManager.on('conditionSetResultUpdated', (data) => {
|
||||
callback(data);
|
||||
});
|
||||
|
||||
return this.destroyConditionManager.bind(this, this.openmct.objects.makeKeyString(domainObject.identifier));
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ export default class StyleRuleManager extends EventEmitter {
|
||||
if (styleConfiguration) {
|
||||
this.initialize(styleConfiguration);
|
||||
if (styleConfiguration.conditionSetIdentifier) {
|
||||
this.openmct.time.on("bounds", this.refreshData.bind(this));
|
||||
this.subscribeToConditionSet();
|
||||
} else {
|
||||
this.applyStaticStyle();
|
||||
@ -83,6 +84,25 @@ export default class StyleRuleManager extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
refreshData(bounds, isTick) {
|
||||
if (!isTick) {
|
||||
let options = {
|
||||
start: bounds.start,
|
||||
end: bounds.end,
|
||||
size: 1,
|
||||
strategy: 'latest'
|
||||
};
|
||||
this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => {
|
||||
this.openmct.telemetry.request(conditionSetDomainObject, options)
|
||||
.then(output => {
|
||||
if (output && output.length) {
|
||||
this.handleConditionSetResultUpdated(output[0]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateObjectStyleConfig(styleConfiguration) {
|
||||
if (!styleConfiguration || !styleConfiguration.conditionSetIdentifier) {
|
||||
this.initialize(styleConfiguration || {});
|
||||
@ -160,10 +180,14 @@ export default class StyleRuleManager extends EventEmitter {
|
||||
|
||||
destroy() {
|
||||
if (this.stopProvidingTelemetry) {
|
||||
|
||||
this.stopProvidingTelemetry();
|
||||
delete this.stopProvidingTelemetry;
|
||||
}
|
||||
|
||||
this.openmct.time.off("bounds", this.refreshData);
|
||||
this.openmct.editor.off('isEditing', this.toggleSubscription);
|
||||
|
||||
this.conditionSetIdentifier = undefined;
|
||||
}
|
||||
|
||||
|
@ -344,6 +344,11 @@ export default {
|
||||
const layoutItem = selectionItem[0].context.layoutItem;
|
||||
const isChildItem = selectionItem.length > 1;
|
||||
|
||||
if (!item && !layoutItem) {
|
||||
// cases where selection is used for table cells
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isChildItem) {
|
||||
domainObject = item;
|
||||
itemStyle = getApplicableStylesForItem(item);
|
||||
|
@ -147,12 +147,16 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
|
||||
this.result = evaluateResults(Object.values(this.telemetryDataCache), this.telemetry);
|
||||
}
|
||||
|
||||
requestLAD(telemetryObjects) {
|
||||
const options = {
|
||||
requestLAD(telemetryObjects, requestOptions) {
|
||||
let options = {
|
||||
strategy: 'latest',
|
||||
size: 1
|
||||
};
|
||||
|
||||
if (requestOptions !== undefined) {
|
||||
options = Object.assign(options, requestOptions);
|
||||
}
|
||||
|
||||
if (!this.isValid()) {
|
||||
return this.formatData({}, telemetryObjects);
|
||||
}
|
||||
|
@ -137,12 +137,16 @@ export default class TelemetryCriterion extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
requestLAD() {
|
||||
const options = {
|
||||
requestLAD(telemetryObjects, requestOptions) {
|
||||
let options = {
|
||||
strategy: 'latest',
|
||||
size: 1
|
||||
};
|
||||
|
||||
if (requestOptions !== undefined) {
|
||||
options = Object.assign(options, requestOptions);
|
||||
}
|
||||
|
||||
if (!this.isValid()) {
|
||||
return {
|
||||
id: this.id,
|
||||
|
@ -104,7 +104,7 @@ export function getConsolidatedStyleValues(multipleItemStyles) {
|
||||
const properties = Object.keys(styleProps);
|
||||
properties.forEach((property) => {
|
||||
const values = aggregatedStyleValues[property];
|
||||
if (values.length) {
|
||||
if (values && values.length) {
|
||||
if (values.every(value => value === values[0])) {
|
||||
styleValues[property] = values[0];
|
||||
} else {
|
||||
|
@ -269,7 +269,12 @@ export default {
|
||||
},
|
||||
subscribeToObject() {
|
||||
this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) {
|
||||
if (this.openmct.time.clock() !== undefined) {
|
||||
const key = this.openmct.time.timeSystem().key;
|
||||
const datumTimeStamp = datum[key];
|
||||
if (this.openmct.time.clock() !== undefined
|
||||
|| (datumTimeStamp
|
||||
&& (this.openmct.time.bounds().end >= datumTimeStamp))
|
||||
) {
|
||||
this.updateView(datum);
|
||||
}
|
||||
}.bind(this));
|
||||
|
@ -23,7 +23,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="c-compass"
|
||||
:style="compassDimensionsStyle"
|
||||
:style="`width: ${ sizedImageDimensions.width }px; height: ${ sizedImageDimensions.height }px`"
|
||||
>
|
||||
<CompassHUD
|
||||
v-if="hasCameraFieldOfView"
|
||||
@ -32,8 +32,9 @@
|
||||
:camera-pan="cameraPan"
|
||||
/>
|
||||
<CompassRose
|
||||
v-if="hasCameraFieldOfView"
|
||||
v-if="true"
|
||||
:heading="heading"
|
||||
:sized-image-width="sizedImageDimensions.width"
|
||||
:sun-heading="sunHeading"
|
||||
:camera-angle-of-view="cameraAngleOfView"
|
||||
:camera-pan="cameraPan"
|
||||
@ -77,6 +78,20 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sizedImageDimensions() {
|
||||
let sizedImageDimensions = {};
|
||||
if ((this.containerWidth / this.containerHeight) > this.naturalAspectRatio) {
|
||||
// container is wider than image
|
||||
sizedImageDimensions.width = this.containerHeight * this.naturalAspectRatio;
|
||||
sizedImageDimensions.height = this.containerHeight;
|
||||
} else {
|
||||
// container is taller than image
|
||||
sizedImageDimensions.width = this.containerWidth;
|
||||
sizedImageDimensions.height = this.containerWidth * this.naturalAspectRatio;
|
||||
}
|
||||
|
||||
return sizedImageDimensions;
|
||||
},
|
||||
hasCameraFieldOfView() {
|
||||
return this.cameraPan !== undefined && this.cameraAngleOfView > 0;
|
||||
},
|
||||
@ -94,25 +109,6 @@ export default {
|
||||
},
|
||||
cameraAngleOfView() {
|
||||
return CAMERA_ANGLE_OF_VIEW;
|
||||
},
|
||||
compassDimensionsStyle() {
|
||||
const containerAspectRatio = this.containerWidth / this.containerHeight;
|
||||
|
||||
let width;
|
||||
let height;
|
||||
|
||||
if (containerAspectRatio < this.naturalAspectRatio) {
|
||||
width = '100%';
|
||||
height = `${ this.containerWidth / this.naturalAspectRatio }px`;
|
||||
} else {
|
||||
width = `${ this.containerHeight * this.naturalAspectRatio }px`;
|
||||
height = '100%';
|
||||
}
|
||||
|
||||
return {
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -22,129 +22,134 @@
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="c-direction-rose"
|
||||
@click="toggleLockCompass"
|
||||
class="w-direction-rose"
|
||||
:class="compassRoseSizingClasses"
|
||||
>
|
||||
<div
|
||||
class="c-nsew"
|
||||
:style="compassRoseStyle"
|
||||
class="c-direction-rose"
|
||||
@click="toggleLockCompass"
|
||||
>
|
||||
<svg
|
||||
class="c-nsew__minor-ticks"
|
||||
viewBox="0 0 100 100"
|
||||
<div
|
||||
class="c-nsew"
|
||||
:style="compassRoseStyle"
|
||||
>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-ne"
|
||||
x="49"
|
||||
y="0"
|
||||
width="2"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-se"
|
||||
x="95"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-sw"
|
||||
x="49"
|
||||
y="95"
|
||||
width="2"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-nw"
|
||||
x="0"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
/>
|
||||
<svg
|
||||
class="c-nsew__minor-ticks"
|
||||
viewBox="0 0 100 100"
|
||||
>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-ne"
|
||||
x="49"
|
||||
y="0"
|
||||
width="2"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-se"
|
||||
x="95"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-sw"
|
||||
x="49"
|
||||
y="95"
|
||||
width="2"
|
||||
height="5"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-nw"
|
||||
x="0"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
/>
|
||||
|
||||
</svg>
|
||||
</svg>
|
||||
|
||||
<svg
|
||||
class="c-nsew__ticks"
|
||||
viewBox="0 0 100 100"
|
||||
>
|
||||
<polygon
|
||||
class="c-nsew__tick c-tick-n"
|
||||
points="50,0 57,5 43,5"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-e"
|
||||
x="95"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-w"
|
||||
x="0"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-s"
|
||||
x="49"
|
||||
y="95"
|
||||
width="2"
|
||||
height="5"
|
||||
/>
|
||||
<svg
|
||||
class="c-nsew__ticks"
|
||||
viewBox="0 0 100 100"
|
||||
>
|
||||
<polygon
|
||||
class="c-nsew__tick c-tick-n"
|
||||
points="50,0 60,10 40,10"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-e"
|
||||
x="95"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-w"
|
||||
x="0"
|
||||
y="49"
|
||||
width="5"
|
||||
height="2"
|
||||
/>
|
||||
<rect
|
||||
class="c-nsew__tick c-tick-s"
|
||||
x="49"
|
||||
y="95"
|
||||
width="2"
|
||||
height="5"
|
||||
/>
|
||||
|
||||
<text
|
||||
class="c-nsew__label c-label-n"
|
||||
text-anchor="middle"
|
||||
:transform="northTextTransform"
|
||||
>N</text>
|
||||
<text
|
||||
class="c-nsew__label c-label-e"
|
||||
text-anchor="middle"
|
||||
:transform="eastTextTransform"
|
||||
>E</text>
|
||||
<text
|
||||
class="c-nsew__label c-label-w"
|
||||
text-anchor="middle"
|
||||
:transform="southTextTransform"
|
||||
>W</text>
|
||||
<text
|
||||
class="c-nsew__label c-label-s"
|
||||
text-anchor="middle"
|
||||
:transform="westTextTransform"
|
||||
>S</text>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="hasHeading"
|
||||
class="c-spacecraft-body"
|
||||
:style="headingStyle"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="hasSunHeading"
|
||||
class="c-sun"
|
||||
:style="sunHeadingStyle"
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="c-cam-field"
|
||||
:style="cameraPanStyle"
|
||||
>
|
||||
<div class="cam-field-half cam-field-half-l">
|
||||
<div
|
||||
class="cam-field-area"
|
||||
:style="cameraFOVStyleLeftHalf"
|
||||
></div>
|
||||
<text
|
||||
class="c-nsew__label c-label-n"
|
||||
text-anchor="middle"
|
||||
:transform="northTextTransform"
|
||||
>N</text>
|
||||
<text
|
||||
class="c-nsew__label c-label-e"
|
||||
text-anchor="middle"
|
||||
:transform="eastTextTransform"
|
||||
>E</text>
|
||||
<text
|
||||
class="c-nsew__label c-label-w"
|
||||
text-anchor="middle"
|
||||
:transform="southTextTransform"
|
||||
>W</text>
|
||||
<text
|
||||
class="c-nsew__label c-label-s"
|
||||
text-anchor="middle"
|
||||
:transform="westTextTransform"
|
||||
>S</text>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="cam-field-half cam-field-half-r">
|
||||
<div
|
||||
class="cam-field-area"
|
||||
:style="cameraFOVStyleRightHalf"
|
||||
></div>
|
||||
|
||||
<div
|
||||
v-if="hasHeading"
|
||||
class="c-spacecraft-body"
|
||||
:style="headingStyle"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="hasSunHeading"
|
||||
class="c-sun"
|
||||
:style="sunHeadingStyle"
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="c-cam-field"
|
||||
:style="cameraPanStyle"
|
||||
>
|
||||
<div class="cam-field-half cam-field-half-l">
|
||||
<div
|
||||
class="cam-field-area"
|
||||
:style="cameraFOVStyleLeftHalf"
|
||||
></div>
|
||||
</div>
|
||||
<div class="cam-field-half cam-field-half-r">
|
||||
<div
|
||||
class="cam-field-area"
|
||||
:style="cameraFOVStyleRightHalf"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -155,6 +160,10 @@ import { rotate } from './utils';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
sizedImageWidth: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
heading: {
|
||||
type: Number,
|
||||
required: true
|
||||
@ -177,12 +186,24 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
north() {
|
||||
return this.lockCompass ? rotate(-this.cameraPan) : 0;
|
||||
compassRoseSizingClasses() {
|
||||
let compassRoseSizingClasses = '';
|
||||
if (this.sizedImageWidth < 300) {
|
||||
compassRoseSizingClasses = '--rose-small --rose-min';
|
||||
} else if (this.sizedImageWidth < 500) {
|
||||
compassRoseSizingClasses = '--rose-small';
|
||||
} else if (this.sizedImageWidth > 1000) {
|
||||
compassRoseSizingClasses = '--rose-max';
|
||||
}
|
||||
|
||||
return compassRoseSizingClasses;
|
||||
},
|
||||
compassRoseStyle() {
|
||||
return { transform: `rotate(${ this.north }deg)` };
|
||||
},
|
||||
north() {
|
||||
return this.lockCompass ? rotate(-this.cameraPan) : 0;
|
||||
},
|
||||
northTextTransform() {
|
||||
return this.cardinalPointsTextTransform.north;
|
||||
},
|
||||
@ -204,10 +225,10 @@ export default {
|
||||
const rotation = `rotate(${ -this.north })`;
|
||||
|
||||
return {
|
||||
north: `translate(50,15) ${ rotation }`,
|
||||
east: `translate(87,50) ${ rotation }`,
|
||||
south: `translate(13,50) ${ rotation }`,
|
||||
west: `translate(50,87) ${ rotation }`
|
||||
north: `translate(50,23) ${ rotation }`,
|
||||
east: `translate(82,50) ${ rotation }`,
|
||||
south: `translate(18,50) ${ rotation }`,
|
||||
west: `translate(50,82) ${ rotation }`
|
||||
};
|
||||
},
|
||||
hasHeading() {
|
||||
|
@ -10,6 +10,7 @@ $elemBg: rgba(black, 0.7);
|
||||
}
|
||||
|
||||
.c-compass {
|
||||
pointer-events: none; // This allows the image element to receive a browser-level context click
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
@ -20,195 +21,253 @@ $elemBg: rgba(black, 0.7);
|
||||
|
||||
/***************************** COMPASS HUD */
|
||||
.c-hud {
|
||||
// To be placed within a imagery view, in the bounding box of the image
|
||||
$m: 1px;
|
||||
$padTB: 2px;
|
||||
$padLR: $padTB;
|
||||
color: $interfaceKeyColor;
|
||||
font-size: 0.8em;
|
||||
position: absolute;
|
||||
top: $m; right: $m; left: $m;
|
||||
height: 18px;
|
||||
|
||||
svg, div {
|
||||
// To be placed within a imagery view, in the bounding box of the image
|
||||
$m: 1px;
|
||||
$padTB: 2px;
|
||||
$padLR: $padTB;
|
||||
color: $interfaceKeyColor;
|
||||
font-size: 0.8em;
|
||||
position: absolute;
|
||||
}
|
||||
top: $m;
|
||||
right: $m;
|
||||
left: $m;
|
||||
height: 18px;
|
||||
|
||||
&__display {
|
||||
height: 30px;
|
||||
pointer-events: all;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
svg, div {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&__range {
|
||||
border: 1px solid $interfaceKeyColor;
|
||||
border-top-color: transparent;
|
||||
position: absolute;
|
||||
top: 50%; right: $padLR; bottom: $padTB; left: $padLR;
|
||||
}
|
||||
&__display {
|
||||
height: 30px;
|
||||
pointer-events: all;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
[class*="__dir"] {
|
||||
// NSEW
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 1px 2px black;
|
||||
top: 50%;
|
||||
transform: translate(-50%,-50%);
|
||||
z-index: 2;
|
||||
}
|
||||
&__range {
|
||||
border: 1px solid $interfaceKeyColor;
|
||||
border-top-color: transparent;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: $padLR;
|
||||
bottom: $padTB;
|
||||
left: $padLR;
|
||||
}
|
||||
|
||||
[class*="__dir--sub"] {
|
||||
font-weight: normal;
|
||||
opacity: 0.5;
|
||||
}
|
||||
[class*="__dir"] {
|
||||
// NSEW
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 1px 2px black;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&__sun {
|
||||
$s: 10px;
|
||||
@include sun('circle farthest-side at bottom');
|
||||
bottom: $padTB + 2px;
|
||||
height: $s; width: $s*2;
|
||||
opacity: 0.8;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
}
|
||||
[class*="__dir--sub"] {
|
||||
font-weight: normal;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&__sun {
|
||||
$s: 10px;
|
||||
@include sun('circle farthest-side at bottom');
|
||||
bottom: $padTB + 2px;
|
||||
height: $s;
|
||||
width: $s*2;
|
||||
opacity: 0.8;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/***************************** COMPASS DIRECTIONS */
|
||||
.c-nsew {
|
||||
$color: $interfaceKeyColor;
|
||||
$inset: 7%;
|
||||
$tickHeightPerc: 15%;
|
||||
text-shadow: black 0 0 10px;
|
||||
top: $inset; right: $inset; bottom: $inset; left: $inset;
|
||||
z-index: 3;
|
||||
$color: $interfaceKeyColor;
|
||||
$inset: 5%;
|
||||
$tickHeightPerc: 15%;
|
||||
text-shadow: black 0 0 10px;
|
||||
top: $inset;
|
||||
right: $inset;
|
||||
bottom: $inset;
|
||||
left: $inset;
|
||||
z-index: 3;
|
||||
|
||||
&__tick,
|
||||
&__label {
|
||||
fill: $color;
|
||||
}
|
||||
&__tick,
|
||||
&__label {
|
||||
fill: $color;
|
||||
}
|
||||
|
||||
&__minor-ticks {
|
||||
opacity: 0.5;
|
||||
transform-origin: center;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
&__minor-ticks {
|
||||
opacity: 0.5;
|
||||
transform-origin: center;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
&__label {
|
||||
dominant-baseline: central;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
}
|
||||
&__label {
|
||||
dominant-baseline: central;
|
||||
font-size: 1.25em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.c-label-n {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
.c-label-n {
|
||||
font-size: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
/***************************** CAMERA FIELD ANGLE */
|
||||
.c-cam-field {
|
||||
$color: white;
|
||||
opacity: 0.2;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
|
||||
.cam-field-half {
|
||||
$color: white;
|
||||
opacity: 0.3;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
|
||||
.cam-field-area {
|
||||
background: $color;
|
||||
top: -30%;
|
||||
right: 0;
|
||||
bottom: -30%;
|
||||
left: 0;
|
||||
}
|
||||
.cam-field-half {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
// clip-paths overlap a bit to avoid a gap between halves
|
||||
&-l {
|
||||
clip-path: polygon(0 0, 50.5% 0, 50.5% 100%, 0 100%);
|
||||
.cam-field-area {
|
||||
transform-origin: left center;
|
||||
}
|
||||
}
|
||||
.cam-field-area {
|
||||
background: $color;
|
||||
top: -30%;
|
||||
right: 0;
|
||||
bottom: -30%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&-r {
|
||||
clip-path: polygon(49.5% 0, 100% 0, 100% 100%, 49.5% 100%);
|
||||
.cam-field-area {
|
||||
transform-origin: right center;
|
||||
}
|
||||
// clip-paths overlap a bit to avoid a gap between halves
|
||||
&-l {
|
||||
clip-path: polygon(0 0, 50.5% 0, 50.5% 100%, 0 100%);
|
||||
|
||||
.cam-field-area {
|
||||
transform-origin: left center;
|
||||
}
|
||||
}
|
||||
|
||||
&-r {
|
||||
clip-path: polygon(49.5% 0, 100% 0, 100% 100%, 49.5% 100%);
|
||||
|
||||
.cam-field-area {
|
||||
transform-origin: right center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/***************************** SPACECRAFT BODY */
|
||||
.c-spacecraft-body {
|
||||
$color: $interfaceKeyColor;
|
||||
$s: 30%;
|
||||
background: $color;
|
||||
border-radius: 3px;
|
||||
height: $s; width: $s;
|
||||
left: 50%; top: 50%;
|
||||
opacity: 0.4;
|
||||
transform-origin: center top;
|
||||
|
||||
&:before {
|
||||
// Direction arrow
|
||||
$color: rgba(black, 0.5);
|
||||
$arwPointerY: 60%;
|
||||
$arwBodyOffset: 25%;
|
||||
$color: $interfaceKeyColor;
|
||||
$s: 30%;
|
||||
background: $color;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 10%; right: 20%; bottom: 50%; left: 20%;
|
||||
clip-path: polygon(50% 0, 100% $arwPointerY, 100%-$arwBodyOffset $arwPointerY, 100%-$arwBodyOffset 100%, $arwBodyOffset 100%, $arwBodyOffset $arwPointerY, 0 $arwPointerY);
|
||||
}
|
||||
border-radius: 3px;
|
||||
height: $s;
|
||||
width: $s;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
opacity: 0.4;
|
||||
transform-origin: center top;
|
||||
transform: translateX(-50%); // center by default, overridden by CompassRose.vue / headingStyle()
|
||||
|
||||
&:before {
|
||||
// Direction arrow
|
||||
$color: rgba(black, 0.5);
|
||||
$arwPointerY: 60%;
|
||||
$arwBodyOffset: 25%;
|
||||
background: $color;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
right: 20%;
|
||||
bottom: 50%;
|
||||
left: 20%;
|
||||
clip-path: polygon(50% 0, 100% $arwPointerY, 100%-$arwBodyOffset $arwPointerY, 100%-$arwBodyOffset 100%, $arwBodyOffset 100%, $arwBodyOffset $arwPointerY, 0 $arwPointerY);
|
||||
}
|
||||
}
|
||||
|
||||
/***************************** DIRECTION ROSE */
|
||||
.c-direction-rose {
|
||||
$d: 100px;
|
||||
$c2: rgba(white, 0.1);
|
||||
background: $elemBg;
|
||||
background-image: radial-gradient(circle closest-side, transparent, transparent 80%, $c2);
|
||||
width: $d;
|
||||
height: $d;
|
||||
transform-origin: 0 0;
|
||||
position: absolute;
|
||||
bottom: 10px; left: 10px;
|
||||
clip-path: circle(50% at 50% 50%);
|
||||
border-radius: 100%;
|
||||
|
||||
svg, div {
|
||||
.w-direction-rose {
|
||||
$s: 10%;
|
||||
$m: 2%;
|
||||
position: absolute;
|
||||
}
|
||||
bottom: $m;
|
||||
left: $m;
|
||||
width: $s;
|
||||
padding-top: $s;
|
||||
|
||||
// Sun
|
||||
.c-sun {
|
||||
&.--rose-min {
|
||||
$s: 30px;
|
||||
width: $s;
|
||||
padding-top: $s;
|
||||
}
|
||||
|
||||
&.--rose-small {
|
||||
.c-nsew__minor-ticks,
|
||||
.c-tick-w,
|
||||
.c-tick-s,
|
||||
.c-tick-e,
|
||||
.c-label-w,
|
||||
.c-label-s,
|
||||
.c-label-e {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.c-label-n {
|
||||
font-size: 2.5em;
|
||||
}
|
||||
}
|
||||
|
||||
&.--rose-max {
|
||||
$s: 100px;
|
||||
width: $s;
|
||||
padding-top: $s;
|
||||
}
|
||||
}
|
||||
|
||||
.c-direction-rose {
|
||||
$c2: rgba(white, 0.1);
|
||||
background: $elemBg;
|
||||
background-image: radial-gradient(circle closest-side, transparent, transparent 80%, $c2);
|
||||
transform-origin: 0 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
clip-path: circle(50% at 50% 50%);
|
||||
border-radius: 100%;
|
||||
pointer-events: all;
|
||||
|
||||
&:before {
|
||||
$s: 35%;
|
||||
@include sun();
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
opacity: 0.7;
|
||||
top: 0; left: 50%;
|
||||
height:$s; width: $s;
|
||||
transform: translate(-50%, -60%);
|
||||
svg, div {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
// Sun
|
||||
.c-sun {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
&:before {
|
||||
$s: 35%;
|
||||
@include sun();
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
opacity: 0.7;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
height: $s;
|
||||
width: $s;
|
||||
transform: translate(-50%, -60%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -135,9 +135,14 @@
|
||||
:class="{ selected: focusedImageIndex === index && isPaused }"
|
||||
@click="setFocusedImage(index, thumbnailClick)"
|
||||
>
|
||||
<img class="c-thumb__image"
|
||||
:src="image.url"
|
||||
<a href=""
|
||||
:download="image.imageDownloadName"
|
||||
@click.prevent
|
||||
>
|
||||
<img class="c-thumb__image"
|
||||
:src="image.url"
|
||||
>
|
||||
</a>
|
||||
<div class="c-thumb__timestamp">{{ image.formattedTime }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -218,6 +223,9 @@ export default {
|
||||
canTrackDuration() {
|
||||
return this.openmct.time.clock() && this.timeSystem.isUTCBased;
|
||||
},
|
||||
focusedImageDownloadName() {
|
||||
return this.getImageDownloadName(this.focusedImage);
|
||||
},
|
||||
isNextDisabled() {
|
||||
let disabled = false;
|
||||
|
||||
@ -345,6 +353,7 @@ export default {
|
||||
this.imageHints = { ...this.metadata.valuesForHints(['image'])[0] };
|
||||
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.imageHints);
|
||||
this.imageDownloadNameHints = { ...this.metadata.valuesForHints(['imageDownloadName'])[0]};
|
||||
|
||||
// related telemetry keys
|
||||
this.spacecraftPositionKeys = ['positionX', 'positionY', 'positionZ'];
|
||||
@ -532,6 +541,15 @@ export default {
|
||||
// Replace ISO "T" with a space to allow wrapping
|
||||
return dateTimeStr.replace("T", " ");
|
||||
},
|
||||
getImageDownloadName(datum) {
|
||||
let imageDownloadName = '';
|
||||
if (datum) {
|
||||
const key = this.imageDownloadNameHints.key;
|
||||
imageDownloadName = datum[key];
|
||||
}
|
||||
|
||||
return imageDownloadName;
|
||||
},
|
||||
parseTime(datum) {
|
||||
if (!datum) {
|
||||
return;
|
||||
@ -655,6 +673,7 @@ export default {
|
||||
image.formattedTime = this.formatTime(datum);
|
||||
image.url = this.formatImageUrl(datum);
|
||||
image.time = datum[this.timeKey];
|
||||
image.imageDownloadName = this.getImageDownloadName(datum);
|
||||
|
||||
this.imageHistory.push(image);
|
||||
|
||||
@ -777,6 +796,9 @@ export default {
|
||||
this.focusedImageNaturalAspectRatio = undefined;
|
||||
|
||||
const img = this.$refs.focusedImage;
|
||||
if (!img) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO - should probably cache this
|
||||
img.addEventListener('load', () => {
|
||||
|
@ -91,7 +91,7 @@ export default class CouchObjectProvider {
|
||||
* persist any queued objects
|
||||
* @private
|
||||
*/
|
||||
checkResponse(response, intermediateResponse) {
|
||||
checkResponse(response, intermediateResponse, key) {
|
||||
let requestSuccess = false;
|
||||
const id = response ? response.id : undefined;
|
||||
let rev;
|
||||
@ -113,6 +113,8 @@ export default class CouchObjectProvider {
|
||||
if (this.objectQueue[id].hasNext()) {
|
||||
this.updateQueued(id);
|
||||
}
|
||||
} else {
|
||||
this.objectQueue[key].pending = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,8 +134,7 @@ export default class CouchObjectProvider {
|
||||
}
|
||||
|
||||
//Sometimes CouchDB returns the old rev which fetching the object if there is a document update in progress
|
||||
//Only update the rev if it's the first time we're getting the object from CouchDB. Subsequent revs should only be updated by updates.
|
||||
if (!this.objectQueue[key].pending && !this.objectQueue[key].rev) {
|
||||
if (!this.objectQueue[key].pending) {
|
||||
this.objectQueue[key].updateRevision(response[REV]);
|
||||
}
|
||||
|
||||
@ -458,7 +459,7 @@ export default class CouchObjectProvider {
|
||||
const queued = this.objectQueue[key].dequeue();
|
||||
let document = new CouchDocument(key, queued.model);
|
||||
this.request(key, "PUT", document).then((response) => {
|
||||
this.checkResponse(response, queued.intermediateResponse);
|
||||
this.checkResponse(response, queued.intermediateResponse, key);
|
||||
});
|
||||
|
||||
return intermediateResponse.promise;
|
||||
@ -473,7 +474,7 @@ export default class CouchObjectProvider {
|
||||
const queued = this.objectQueue[key].dequeue();
|
||||
let document = new CouchDocument(key, queued.model, this.objectQueue[key].rev);
|
||||
this.request(key, "PUT", document).then((response) => {
|
||||
this.checkResponse(response, queued.intermediateResponse);
|
||||
this.checkResponse(response, queued.intermediateResponse, key);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -65,8 +65,7 @@
|
||||
<div ref="chartContainer"
|
||||
class="gl-plot-chart-wrapper"
|
||||
>
|
||||
<mct-chart :series-config="config"
|
||||
:rectangles="rectangles"
|
||||
<mct-chart :rectangles="rectangles"
|
||||
:highlights="highlights"
|
||||
@plotReinitializeCanvas="initCanvas"
|
||||
/>
|
||||
@ -85,8 +84,8 @@
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<div class="c-button-set c-button-set--strip-h"
|
||||
:disabled="!plotHistory.length"
|
||||
<div v-if="plotHistory.length"
|
||||
class="c-button-set c-button-set--strip-h"
|
||||
>
|
||||
<button class="c-button icon-arrow-left"
|
||||
title="Restore previous pan/zoom"
|
||||
@ -99,6 +98,31 @@
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="isRealTime"
|
||||
class="c-button-set c-button-set--strip-h"
|
||||
>
|
||||
<button v-if="!isFrozen"
|
||||
class="c-button icon-pause"
|
||||
title="Pause incoming real-time data"
|
||||
@click="pause()"
|
||||
>
|
||||
</button>
|
||||
<button v-if="isFrozen"
|
||||
class="c-button icon-arrow-right pause-play is-paused"
|
||||
title="Resume displaying real-time data"
|
||||
@click="play()"
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="isTimeOutOfSync || isFrozen"
|
||||
class="c-button-set c-button-set--strip-h"
|
||||
>
|
||||
<button class="c-button icon-clock"
|
||||
title="Synchronize Time Conductor"
|
||||
@click="showSynchronizeDialog()"
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Cursor guides-->
|
||||
@ -186,10 +210,15 @@ export default {
|
||||
xKeyOptions: [],
|
||||
config: {},
|
||||
pending: 0,
|
||||
loaded: false
|
||||
isRealTime: this.openmct.time.clock() !== undefined,
|
||||
loaded: false,
|
||||
isTimeOutOfSync: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isFrozen() {
|
||||
return this.config.xAxis.get('frozen') === true && this.config.yAxis.get('frozen') === true;
|
||||
},
|
||||
plotLegendPositionClass() {
|
||||
return `plot-legend-${this.config.legend.get('position')}`;
|
||||
},
|
||||
@ -227,6 +256,7 @@ export default {
|
||||
'configuration.filters',
|
||||
this.updateFiltersAndResubscribe
|
||||
);
|
||||
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.updateStatus);
|
||||
|
||||
this.openmct.objectViews.on('clearData', this.clearData);
|
||||
this.followTimeConductor();
|
||||
@ -243,6 +273,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
followTimeConductor() {
|
||||
this.openmct.time.on('clock', this.updateRealTime);
|
||||
this.openmct.time.on('bounds', this.updateDisplayBounds);
|
||||
this.synchronized(true);
|
||||
},
|
||||
@ -371,6 +402,9 @@ export default {
|
||||
const displayRange = series.getDisplayRange(xKey);
|
||||
this.config.xAxis.set('range', displayRange);
|
||||
},
|
||||
updateRealTime(clock) {
|
||||
this.isRealTime = clock !== undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* Track latest display bounds. Forces update when not receiving ticks.
|
||||
@ -424,19 +458,30 @@ export default {
|
||||
* displays can update accordingly.
|
||||
*/
|
||||
synchronized(value) {
|
||||
const isLocalClock = this.openmct.time.clock();
|
||||
|
||||
if (typeof value !== 'undefined') {
|
||||
this._synchronized = value;
|
||||
const isUnsynced = !value && this.openmct.time.clock();
|
||||
const domainObject = this.openmct.legacyObject(this.domainObject);
|
||||
if (domainObject.getCapability('status')) {
|
||||
domainObject.getCapability('status')
|
||||
.set('timeconductor-unsynced', isUnsynced);
|
||||
}
|
||||
this.isTimeOutOfSync = value !== true;
|
||||
|
||||
const isUnsynced = isLocalClock && !value;
|
||||
this.setStatus(isUnsynced);
|
||||
}
|
||||
|
||||
return this._synchronized;
|
||||
},
|
||||
|
||||
setStatus(isNotInSync) {
|
||||
const outOfSync = isNotInSync === true
|
||||
|| this.isTimeOutOfSync === true
|
||||
|| this.isFrozen === true;
|
||||
if (outOfSync === true) {
|
||||
this.openmct.status.set(this.domainObject.identifier, 'timeconductor-unsynced');
|
||||
} else {
|
||||
this.openmct.status.set(this.domainObject.identifier, '');
|
||||
}
|
||||
},
|
||||
|
||||
initCanvas() {
|
||||
if (this.canvas) {
|
||||
this.stopListening(this.canvas);
|
||||
@ -729,7 +774,8 @@ export default {
|
||||
const ZOOM_AMT = 0.1;
|
||||
event.preventDefault();
|
||||
|
||||
if (!this.positionOverPlot) {
|
||||
if (event.wheelDelta === undefined
|
||||
|| !this.positionOverPlot) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -847,11 +893,13 @@ export default {
|
||||
freeze() {
|
||||
this.config.yAxis.set('frozen', true);
|
||||
this.config.xAxis.set('frozen', true);
|
||||
this.setStatus();
|
||||
},
|
||||
|
||||
clear() {
|
||||
this.config.yAxis.set('frozen', false);
|
||||
this.config.xAxis.set('frozen', false);
|
||||
this.setStatus();
|
||||
this.plotHistory = [];
|
||||
this.userViewportChangeEnd();
|
||||
},
|
||||
@ -881,6 +929,59 @@ export default {
|
||||
this.config.series.models[0].emit('change:yKey', yKey);
|
||||
},
|
||||
|
||||
pause() {
|
||||
this.freeze();
|
||||
},
|
||||
|
||||
play() {
|
||||
this.clear();
|
||||
},
|
||||
|
||||
showSynchronizeDialog() {
|
||||
const isLocalClock = this.openmct.time.clock();
|
||||
if (isLocalClock !== undefined) {
|
||||
const message = `
|
||||
This action will change the Time Conductor to Fixed Timespan mode with this plot view's current time bounds.
|
||||
Do you want to continue?
|
||||
`;
|
||||
|
||||
let dialog = this.openmct.overlays.dialog({
|
||||
title: 'Synchronize Time Conductor',
|
||||
iconClass: 'alert',
|
||||
size: 'fit',
|
||||
message: message,
|
||||
buttons: [
|
||||
{
|
||||
label: 'OK',
|
||||
callback: () => {
|
||||
dialog.dismiss();
|
||||
this.synchronizeTimeConductor();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: () => {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
} else {
|
||||
this.openmct.notifications.alert('Time conductor bounds have changed.');
|
||||
this.synchronizeTimeConductor();
|
||||
}
|
||||
},
|
||||
|
||||
synchronizeTimeConductor() {
|
||||
this.openmct.time.stopClock();
|
||||
const range = this.config.xAxis.get('displayRange');
|
||||
this.openmct.time.bounds({
|
||||
start: range.min,
|
||||
end: range.max
|
||||
});
|
||||
this.isTimeOutOfSync = false;
|
||||
},
|
||||
|
||||
destroy() {
|
||||
configStore.deleteStore(this.config.id);
|
||||
|
||||
@ -894,8 +995,16 @@ export default {
|
||||
this.filterObserver();
|
||||
}
|
||||
|
||||
if (this.removeStatusListener) {
|
||||
this.removeStatusListener();
|
||||
}
|
||||
|
||||
this.openmct.time.off('clock', this.updateRealTime);
|
||||
this.openmct.time.off('bounds', this.updateDisplayBounds);
|
||||
this.openmct.objectViews.off('clearData', this.clearData);
|
||||
},
|
||||
updateStatus(status) {
|
||||
this.$emit('statusUpdated', status);
|
||||
}
|
||||
}
|
||||
};
|
@ -56,6 +56,7 @@
|
||||
|
||||
<div ref="plotContainer"
|
||||
class="l-view-section u-style-receiver js-style-receiver"
|
||||
:class="{'s-status-timeconductor-unsynced': status && status === 'timeconductor-unsynced'}"
|
||||
>
|
||||
<div v-show="!!loading"
|
||||
class="c-loading--overlay loading"
|
||||
@ -64,6 +65,7 @@
|
||||
:cursor-guide="cursorGuide"
|
||||
:options="options"
|
||||
@loadingUpdated="loadingUpdated"
|
||||
@statusUpdated="setStatus"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -94,7 +96,8 @@ export default {
|
||||
// hideExportButtons: false
|
||||
cursorGuide: false,
|
||||
gridLines: !this.options.compact,
|
||||
loading: false
|
||||
loading: false,
|
||||
status: ''
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
@ -131,6 +134,9 @@ export default {
|
||||
|
||||
toggleGridLines() {
|
||||
this.gridLines = !this.gridLines;
|
||||
},
|
||||
setStatus(status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
};
|
@ -48,7 +48,7 @@ export default function PlotViewProvider(openmct) {
|
||||
name: 'Plot',
|
||||
cssClass: 'icon-telemetry',
|
||||
canView(domainObject, objectPath) {
|
||||
return isCompactView(objectPath) && hasTelemetry(domainObject, openmct);
|
||||
return hasTelemetry(domainObject, openmct);
|
||||
},
|
||||
|
||||
view: function (domainObject, objectPath) {
|
@ -166,7 +166,6 @@ export default class YAxisModel extends Model {
|
||||
* Update yAxis format, values, and label from known series.
|
||||
*/
|
||||
updateFromSeries(series) {
|
||||
this.unset('displayRange');
|
||||
const plotModel = this.plot.get('domainObject');
|
||||
const label = _.get(plotModel, 'configuration.yAxis.label');
|
||||
const sampleSeries = series.first();
|
64
src/plugins/plot/inspector/PlotOptions.vue
Normal file
64
src/plugins/plot/inspector/PlotOptions.vue
Normal file
@ -0,0 +1,64 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2020, 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.
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="canEdit">
|
||||
<plot-options-edit />
|
||||
</div>
|
||||
<div v-else>
|
||||
<plot-options-browse />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PlotOptionsBrowse from "./PlotOptionsBrowse.vue";
|
||||
import PlotOptionsEdit from "./PlotOptionsEdit.vue";
|
||||
export default {
|
||||
components: {
|
||||
PlotOptionsBrowse,
|
||||
PlotOptionsEdit
|
||||
},
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data() {
|
||||
return {
|
||||
isEditing: this.openmct.editor.isEditing()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canEdit() {
|
||||
return this.isEditing && !this.domainObject.locked;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.openmct.editor.on('isEditing', this.setEditState);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.openmct.editor.off('isEditing', this.setEditState);
|
||||
},
|
||||
methods: {
|
||||
setEditState(isEditing) {
|
||||
this.isEditing = isEditing;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
201
src/plugins/plot/inspector/PlotOptionsBrowse.vue
Normal file
201
src/plugins/plot/inspector/PlotOptionsBrowse.vue
Normal file
@ -0,0 +1,201 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2020, 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.
|
||||
-->
|
||||
<template>
|
||||
<div v-if="loaded"
|
||||
class="js-plot-options-browse"
|
||||
>
|
||||
<ul class="c-tree">
|
||||
<h2 title="Plot series display properties in this object">Plot Series</h2>
|
||||
<plot-options-item v-for="series in plotSeries"
|
||||
:key="series.key"
|
||||
:series="series"
|
||||
/>
|
||||
</ul>
|
||||
<div v-if="plotSeries.length"
|
||||
class="grid-properties"
|
||||
>
|
||||
<ul class="l-inspector-part">
|
||||
<h2 title="Y axis settings for this object">Y Axis</h2>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Manually override how the Y axis is labeled."
|
||||
>Label</div>
|
||||
<div class="grid-cell value">{{ label ? label : "Not defined" }}</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Automatically scale the Y axis to keep all values in view."
|
||||
>Autoscale</div>
|
||||
<div class="grid-cell value">
|
||||
{{ autoscale ? "Enabled: " : "Disabled" }}
|
||||
{{ autoscale ? autoscalePadding : "" }}
|
||||
</div>
|
||||
</li>
|
||||
<li v-if="!autoscale && rangeMin"
|
||||
class="grid-row"
|
||||
>
|
||||
<div class="grid-cell label"
|
||||
title="Minimum Y axis value."
|
||||
>Minimum value</div>
|
||||
<div class="grid-cell value">{{ rangeMin }}</div>
|
||||
</li>
|
||||
<li v-if="!autoscale && rangeMax"
|
||||
class="grid-row"
|
||||
>
|
||||
<div class="grid-cell label"
|
||||
title="Maximum Y axis value."
|
||||
>Maximum value</div>
|
||||
<div class="grid-cell value">{{ rangeMax }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="l-inspector-part">
|
||||
<h2 title="Legend settings for this object">Legend</h2>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="The position of the legend relative to the plot display area."
|
||||
>Position</div>
|
||||
<div class="grid-cell value capitalize">{{ position }}</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Hide the legend when the plot is small"
|
||||
>Hide when plot small</div>
|
||||
<div class="grid-cell value">{{ hideLegendWhenSmall ? "Yes" : "No" }}</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Show the legend expanded by default"
|
||||
>Expand by Default</div>
|
||||
<div class="grid-cell value">{{ expandByDefault ? "Yes" : "No" }}</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="What to display in the legend when it's collapsed."
|
||||
>Show when collapsed:</div>
|
||||
<div class="grid-cell value">{{
|
||||
valueToShowWhenCollapsed.replace('nearest', '')
|
||||
}}
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="What to display in the legend when it's expanded."
|
||||
>Show when expanded:</div>
|
||||
<div class="grid-cell value comma-list">
|
||||
<span v-if="showTimestampWhenExpanded">Timestamp</span>
|
||||
<span v-if="showValueWhenExpanded">Value</span>
|
||||
<span v-if="showMinimumWhenExpanded">Min</span>
|
||||
<span v-if="showMaximumWhenExpanded">Max</span>
|
||||
<span v-if="showUnitsWhenExpanded">Units</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PlotOptionsItem from "./PlotOptionsItem.vue";
|
||||
import configStore from "../configuration/configStore";
|
||||
import eventHelpers from "../lib/eventHelpers";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PlotOptionsItem
|
||||
},
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data() {
|
||||
return {
|
||||
config: {},
|
||||
label: '',
|
||||
autoscale: '',
|
||||
autoscalePadding: '',
|
||||
rangeMin: '',
|
||||
rangeMax: '',
|
||||
position: '',
|
||||
hideLegendWhenSmall: '',
|
||||
expandByDefault: '',
|
||||
valueToShowWhenCollapsed: '',
|
||||
showTimestampWhenExpanded: '',
|
||||
showValueWhenExpanded: '',
|
||||
showMinimumWhenExpanded: '',
|
||||
showMaximumWhenExpanded: '',
|
||||
showUnitsWhenExpanded: '',
|
||||
loaded: false,
|
||||
plotSeries: []
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
eventHelpers.extend(this);
|
||||
this.config = this.getConfig();
|
||||
this.registerListeners();
|
||||
this.initConfiguration();
|
||||
this.loaded = true;
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.stopListening();
|
||||
},
|
||||
methods: {
|
||||
initConfiguration() {
|
||||
this.label = this.config.yAxis.get('label');
|
||||
this.autoscale = this.config.yAxis.get('autoscale');
|
||||
this.autoscalePadding = this.config.yAxis.get('autoscalePadding');
|
||||
const range = this.config.yAxis.get('range');
|
||||
if (range) {
|
||||
this.rangeMin = range.min;
|
||||
this.rangeMax = range.max;
|
||||
}
|
||||
|
||||
this.position = this.config.legend.get('position');
|
||||
this.hideLegendWhenSmall = this.config.legend.get('hideLegendWhenSmall');
|
||||
this.expandByDefault = this.config.legend.get('expandByDefault');
|
||||
this.valueToShowWhenCollapsed = this.config.legend.get('valueToShowWhenCollapsed');
|
||||
this.showTimestampWhenExpanded = this.config.legend.get('showTimestampWhenExpanded');
|
||||
this.showValueWhenExpanded = this.config.legend.get('showValueWhenExpanded');
|
||||
this.showMinimumWhenExpanded = this.config.legend.get('showMinimumWhenExpanded');
|
||||
this.showMaximumWhenExpanded = this.config.legend.get('showMaximumWhenExpanded');
|
||||
this.showUnitsWhenExpanded = this.config.legend.get('showUnitsWhenExpanded');
|
||||
},
|
||||
getConfig() {
|
||||
this.configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
|
||||
return configStore.get(this.configId);
|
||||
},
|
||||
registerListeners() {
|
||||
this.config.series.forEach(this.addSeries, this);
|
||||
|
||||
this.listenTo(this.config.series, 'add', this.addSeries, this);
|
||||
this.listenTo(this.config.series, 'remove', this.resetAllSeries, this);
|
||||
},
|
||||
|
||||
addSeries(series, index) {
|
||||
this.$set(this.plotSeries, index, series);
|
||||
this.initConfiguration();
|
||||
},
|
||||
|
||||
resetAllSeries() {
|
||||
this.plotSeries = [];
|
||||
this.config.series.forEach(this.addSeries, this);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
100
src/plugins/plot/inspector/PlotOptionsEdit.vue
Normal file
100
src/plugins/plot/inspector/PlotOptionsEdit.vue
Normal file
@ -0,0 +1,100 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2020, 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.
|
||||
-->
|
||||
<template>
|
||||
<div v-if="loaded"
|
||||
class="js-plot-options-edit"
|
||||
>
|
||||
<ul class="c-tree">
|
||||
<h2 title="Display properties for this object">Plot Series</h2>
|
||||
<li v-for="series in plotSeries"
|
||||
:key="series.key"
|
||||
>
|
||||
<series-form :series="series" />
|
||||
</li>
|
||||
</ul>
|
||||
<y-axis-form v-if="plotSeries.length"
|
||||
class="grid-properties"
|
||||
:y-axis="config.yAxis"
|
||||
/>
|
||||
<ul class="l-inspector-part">
|
||||
<h2 title="Legend options">Legend</h2>
|
||||
<legend-form v-if="plotSeries.length"
|
||||
class="grid-properties"
|
||||
:legend="config.legend"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import SeriesForm from "./forms/SeriesForm.vue";
|
||||
import YAxisForm from "./forms/YAxisForm.vue";
|
||||
import LegendForm from "./forms/LegendForm.vue";
|
||||
import eventHelpers from "../lib/eventHelpers";
|
||||
import configStore from "../configuration/configStore";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LegendForm,
|
||||
SeriesForm,
|
||||
YAxisForm
|
||||
},
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data() {
|
||||
return {
|
||||
config: {},
|
||||
plotSeries: [],
|
||||
loaded: false
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
eventHelpers.extend(this);
|
||||
this.config = this.getConfig();
|
||||
this.registerListeners();
|
||||
this.loaded = true;
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.stopListening();
|
||||
},
|
||||
methods: {
|
||||
getConfig() {
|
||||
this.configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
|
||||
return configStore.get(this.configId);
|
||||
},
|
||||
registerListeners() {
|
||||
this.config.series.forEach(this.addSeries, this);
|
||||
|
||||
this.listenTo(this.config.series, 'add', this.addSeries, this);
|
||||
this.listenTo(this.config.series, 'remove', this.resetAllSeries, this);
|
||||
},
|
||||
|
||||
addSeries(series, index) {
|
||||
this.$set(this.plotSeries, index, series);
|
||||
},
|
||||
|
||||
resetAllSeries() {
|
||||
this.plotSeries = [];
|
||||
this.config.series.forEach(this.addSeries, this);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
160
src/plugins/plot/inspector/PlotOptionsItem.vue
Normal file
160
src/plugins/plot/inspector/PlotOptionsItem.vue
Normal file
@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<ul>
|
||||
<li class="c-tree__item menus-to-left"
|
||||
:class="isAliasClass"
|
||||
>
|
||||
<span class="c-disclosure-triangle is-enabled flex-elem"
|
||||
:class="expandedCssClass"
|
||||
@click="toggleExpanded"
|
||||
>
|
||||
</span>
|
||||
<div class="c-object-label"
|
||||
:class="statusClass"
|
||||
>
|
||||
<div class="c-object-label__type-icon"
|
||||
:class="getSeriesClass"
|
||||
>
|
||||
<span class="is-status__indicator"
|
||||
title="This item is missing or suspect"
|
||||
></span>
|
||||
</div>
|
||||
<div class="c-object-label__name">{{ series.domainObject.name }}</div>
|
||||
</div>
|
||||
</li>
|
||||
<li v-show="expanded"
|
||||
class="c-tree__item menus-to-left"
|
||||
>
|
||||
<ul class="grid-properties js-plot-options-browse-properties">
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="The field to be plotted as a value for this series."
|
||||
>Value</div>
|
||||
<div class="grid-cell value">
|
||||
{{ yKey }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="The rendering method to join lines for this series."
|
||||
>Line Method</div>
|
||||
<div class="grid-cell value">{{ {
|
||||
'none': 'None',
|
||||
'linear': 'Linear interpolation',
|
||||
'stepAfter': 'Step After'
|
||||
}[interpolate] }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Whether markers are displayed, and their size."
|
||||
>Markers</div>
|
||||
<div class="grid-cell value">
|
||||
{{ markerOptionsDisplayText }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Display markers visually denoting points in alarm."
|
||||
>Alarm Markers</div>
|
||||
<div class="grid-cell value">
|
||||
{{ alarmMarkers ? "Enabled" : "Disabled" }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="The plot line and marker color for this series."
|
||||
>Color</div>
|
||||
<div class="grid-cell value">
|
||||
<span class="c-color-swatch"
|
||||
:style="{
|
||||
'background': seriesHexColor
|
||||
}"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject', 'path'],
|
||||
props: {
|
||||
series: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expanded: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isAliasClass() {
|
||||
let cssClass = '';
|
||||
const domainObjectPath = [this.series.domainObject, ...this.path];
|
||||
if (this.openmct.objects.isObjectPathToALink(this.series.domainObject, domainObjectPath)) {
|
||||
cssClass = 'is-alias';
|
||||
}
|
||||
|
||||
return cssClass;
|
||||
},
|
||||
getSeriesClass() {
|
||||
let cssClass = '';
|
||||
let type = this.openmct.types.get(this.series.domainObject.type);
|
||||
if (type.definition.cssClass) {
|
||||
cssClass = `${cssClass} ${type.definition.cssClass}`;
|
||||
}
|
||||
|
||||
return cssClass;
|
||||
},
|
||||
expandedCssClass() {
|
||||
if (this.expanded === true) {
|
||||
return 'c-disclosure-triangle--expanded';
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
statusClass() {
|
||||
return (this.status) ? `is-status--${this.status}` : '';
|
||||
},
|
||||
yKey() {
|
||||
return this.series.get('yKey');
|
||||
},
|
||||
interpolate() {
|
||||
return this.series.get('interpolate');
|
||||
},
|
||||
markerOptionsDisplayText() {
|
||||
return this.series.markerOptionsDisplayText();
|
||||
},
|
||||
alarmMarkers() {
|
||||
return this.series.get('alarmMarkers');
|
||||
},
|
||||
seriesHexColor() {
|
||||
return this.series.get('color').asHexString();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.status = this.openmct.status.get(this.series.domainObject.identifier);
|
||||
this.removeStatusListener = this.openmct.status.observe(this.series.domainObject.identifier, this.setStatus);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.removeStatusListener) {
|
||||
this.removeStatusListener();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleExpanded() {
|
||||
this.expanded = !this.expanded;
|
||||
},
|
||||
setStatus(status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
56
src/plugins/plot/inspector/PlotsInspectorViewProvider.js
Normal file
56
src/plugins/plot/inspector/PlotsInspectorViewProvider.js
Normal file
@ -0,0 +1,56 @@
|
||||
|
||||
import PlotOptions from "./PlotOptions.vue";
|
||||
import Vue from 'vue';
|
||||
|
||||
export default function PlotsInspectorViewProvider(openmct) {
|
||||
return {
|
||||
key: 'plots-inspector',
|
||||
name: 'Plots Inspector View',
|
||||
canView: function (selection) {
|
||||
if (selection.length === 0 || selection[0].length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let object = selection[0][0].context.item;
|
||||
|
||||
return object
|
||||
&& object.type === 'telemetry.plot.overlay';
|
||||
},
|
||||
view: function (selection) {
|
||||
let component;
|
||||
let objectPath;
|
||||
|
||||
if (selection.length) {
|
||||
objectPath = selection[0].map((selectionItem) => {
|
||||
return selectionItem.context.item;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
PlotOptions: PlotOptions
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject: selection[0][0].context.item,
|
||||
path: objectPath
|
||||
},
|
||||
template: '<plot-options></plot-options>'
|
||||
});
|
||||
},
|
||||
destroy: function () {
|
||||
if (component) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
207
src/plugins/plot/inspector/forms/LegendForm.vue
Normal file
207
src/plugins/plot/inspector/forms/LegendForm.vue
Normal file
@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<div>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="The position of the legend relative to the plot display area."
|
||||
>Position</div>
|
||||
<div class="grid-cell value">
|
||||
<select v-model="position"
|
||||
@change="updateForm('position')"
|
||||
>
|
||||
<option value="top">Top</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="bottom">Bottom</option>
|
||||
<option value="left">Left</option>
|
||||
</select>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Hide the legend when the plot is small"
|
||||
>Hide when plot small</div>
|
||||
<div class="grid-cell value"><input v-model="hideLegendWhenSmall"
|
||||
type="checkbox"
|
||||
@change="updateForm('hideLegendWhenSmall')"
|
||||
></div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Show the legend expanded by default"
|
||||
>Expand by default</div>
|
||||
<div class="grid-cell value"><input v-model="expandByDefault"
|
||||
type="checkbox"
|
||||
@change="updateForm('expandByDefault')"
|
||||
></div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="What to display in the legend when it's collapsed."
|
||||
>When collapsed show</div>
|
||||
<div class="grid-cell value">
|
||||
<select v-model="valueToShowWhenCollapsed"
|
||||
@change="updateForm('valueToShowWhenCollapsed')"
|
||||
>
|
||||
<option value="none">Nothing</option>
|
||||
<option value="nearestTimestamp">Nearest timestamp</option>
|
||||
<option value="nearestValue">Nearest value</option>
|
||||
<option value="min">Minimum value</option>
|
||||
<option value="max">Maximum value</option>
|
||||
<option value="units">Units</option>
|
||||
</select>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="What to display in the legend when it's expanded."
|
||||
>When expanded show</div>
|
||||
<div class="grid-cell value">
|
||||
<ul>
|
||||
<li><input v-model="showTimestampWhenExpanded"
|
||||
type="checkbox"
|
||||
@change="updateForm('showTimestampWhenExpanded')"
|
||||
> Nearest timestamp</li>
|
||||
<li><input v-model="showValueWhenExpanded"
|
||||
type="checkbox"
|
||||
@change="updateForm('showValueWhenExpanded')"
|
||||
> Nearest value</li>
|
||||
<li><input v-model="showMinimumWhenExpanded"
|
||||
type="checkbox"
|
||||
@change="updateForm('showMinimumWhenExpanded')"
|
||||
> Minimum value</li>
|
||||
<li><input v-model="showMaximumWhenExpanded"
|
||||
type="checkbox"
|
||||
@change="updateForm('showMaximumWhenExpanded')"
|
||||
> Maximum value</li>
|
||||
<li><input v-model="showUnitsWhenExpanded"
|
||||
type="checkbox"
|
||||
@change="updateForm('showUnitsWhenExpanded')"
|
||||
> Units</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {coerce, objectPath, validate} from "./formUtil";
|
||||
import _ from "lodash";
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
props: {
|
||||
legend: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
position: '',
|
||||
hideLegendWhenSmall: '',
|
||||
expandByDefault: '',
|
||||
valueToShowWhenCollapsed: '',
|
||||
showTimestampWhenExpanded: '',
|
||||
showValueWhenExpanded: '',
|
||||
showMinimumWhenExpanded: '',
|
||||
showMaximumWhenExpanded: '',
|
||||
showUnitsWhenExpanded: '',
|
||||
validation: {}
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.initialize();
|
||||
this.initFormValues();
|
||||
},
|
||||
methods: {
|
||||
initialize() {
|
||||
this.fields = [
|
||||
{
|
||||
modelProp: 'position',
|
||||
objectPath: 'configuration.legend.position'
|
||||
},
|
||||
{
|
||||
modelProp: 'hideLegendWhenSmall',
|
||||
coerce: Boolean,
|
||||
objectPath: 'configuration.legend.hideLegendWhenSmall'
|
||||
},
|
||||
{
|
||||
modelProp: 'expandByDefault',
|
||||
coerce: Boolean,
|
||||
objectPath: 'configuration.legend.expandByDefault'
|
||||
},
|
||||
{
|
||||
modelProp: 'valueToShowWhenCollapsed',
|
||||
objectPath: 'configuration.legend.valueToShowWhenCollapsed'
|
||||
},
|
||||
{
|
||||
modelProp: 'showValueWhenExpanded',
|
||||
coerce: Boolean,
|
||||
objectPath: 'configuration.legend.showValueWhenExpanded'
|
||||
},
|
||||
{
|
||||
modelProp: 'showTimestampWhenExpanded',
|
||||
coerce: Boolean,
|
||||
objectPath: 'configuration.legend.showTimestampWhenExpanded'
|
||||
},
|
||||
{
|
||||
modelProp: 'showMaximumWhenExpanded',
|
||||
coerce: Boolean,
|
||||
objectPath: 'configuration.legend.showMaximumWhenExpanded'
|
||||
},
|
||||
{
|
||||
modelProp: 'showMinimumWhenExpanded',
|
||||
coerce: Boolean,
|
||||
objectPath: 'configuration.legend.showMinimumWhenExpanded'
|
||||
},
|
||||
{
|
||||
modelProp: 'showUnitsWhenExpanded',
|
||||
coerce: Boolean,
|
||||
objectPath: 'configuration.legend.showUnitsWhenExpanded'
|
||||
}
|
||||
];
|
||||
},
|
||||
initFormValues() {
|
||||
this.position = this.legend.get('position');
|
||||
this.hideLegendWhenSmall = this.legend.get('hideLegendWhenSmall');
|
||||
this.expandByDefault = this.legend.get('expandByDefault');
|
||||
this.valueToShowWhenCollapsed = this.legend.get('valueToShowWhenCollapsed');
|
||||
this.showTimestampWhenExpanded = this.legend.get('showTimestampWhenExpanded');
|
||||
this.showValueWhenExpanded = this.legend.get('showValueWhenExpanded');
|
||||
this.showMinimumWhenExpanded = this.legend.get('showMinimumWhenExpanded');
|
||||
this.showMaximumWhenExpanded = this.legend.get('showMaximumWhenExpanded');
|
||||
this.showUnitsWhenExpanded = this.legend.get('showUnitsWhenExpanded');
|
||||
},
|
||||
updateForm(formKey) {
|
||||
const newVal = this[formKey];
|
||||
const oldVal = this.legend.get(formKey);
|
||||
const formField = this.fields.find((field) => field.modelProp === formKey);
|
||||
|
||||
const path = objectPath(formField.objectPath);
|
||||
const validationResult = validate(newVal, this.legend, formField.validate);
|
||||
if (validationResult === true) {
|
||||
delete this.validation[formKey];
|
||||
} else {
|
||||
this.validation[formKey] = validationResult;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_.isEqual(coerce(newVal, formField.coerce), coerce(oldVal, formField.coerce))) {
|
||||
this.legend.set(formKey, coerce(newVal, formField.coerce));
|
||||
if (path) {
|
||||
this.openmct.objects.mutate(
|
||||
this.domainObject,
|
||||
path(this.domainObject, this.legend),
|
||||
coerce(newVal, formField.coerce)
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
setStatus(status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
347
src/plugins/plot/inspector/forms/SeriesForm.vue
Normal file
347
src/plugins/plot/inspector/forms/SeriesForm.vue
Normal file
@ -0,0 +1,347 @@
|
||||
<template>
|
||||
<ul>
|
||||
<li class="c-tree__item menus-to-left"
|
||||
:class="isAliasCss"
|
||||
>
|
||||
<span class="c-disclosure-triangle is-enabled flex-elem"
|
||||
:class="expandedCssClass"
|
||||
@click="toggleExpanded"
|
||||
>
|
||||
</span>
|
||||
<div :class="objectLabelCss">
|
||||
<div class="c-object-label__type-icon"
|
||||
:class="[seriesCss]"
|
||||
>
|
||||
<span class="is-status__indicator"
|
||||
title="This item is missing or suspect"
|
||||
></span>
|
||||
</div>
|
||||
<div class="c-object-label__name">{{ series.domainObject.name }}</div>
|
||||
</div>
|
||||
</li>
|
||||
<ul v-show="expanded"
|
||||
class="grid-properties js-plot-options-edit-properties"
|
||||
>
|
||||
<li class="grid-row">
|
||||
<!-- Value to be displayed -->
|
||||
<div class="grid-cell label"
|
||||
title="The field to be plotted as a value for this series."
|
||||
>Value</div>
|
||||
<div class="grid-cell value">
|
||||
<select v-model="yKey"
|
||||
@change="updateForm('yKey')"
|
||||
>
|
||||
<option v-for="option in yKeyOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
:selected="option.value == yKey"
|
||||
>
|
||||
{{ option.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="The rendering method to join lines for this series."
|
||||
>Line Method</div>
|
||||
<div class="grid-cell value">
|
||||
<select v-model="interpolate"
|
||||
@change="updateForm('interpolate')"
|
||||
>
|
||||
<option value="none">None</option>
|
||||
<option value="linear">Linear interpolate</option>
|
||||
<option value="stepAfter">Step after</option>
|
||||
</select>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Whether markers are displayed."
|
||||
>Markers</div>
|
||||
<div class="grid-cell value">
|
||||
<input v-model="markers"
|
||||
type="checkbox"
|
||||
@change="updateForm('markers')"
|
||||
>
|
||||
<select
|
||||
v-show="markers"
|
||||
v-model="markerShape"
|
||||
@change="updateForm('markerShape')"
|
||||
>
|
||||
<option
|
||||
v-for="option in markerShapeOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
:selected="option.value == markerShape"
|
||||
>
|
||||
{{ option.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Display markers visually denoting points in alarm."
|
||||
>Alarm Markers</div>
|
||||
<div class="grid-cell value">
|
||||
<input v-model="alarmMarkers"
|
||||
type="checkbox"
|
||||
@change="updateForm('alarmMarkers')"
|
||||
>
|
||||
</div>
|
||||
</li>
|
||||
<li v-show="markers || alarmMarkers"
|
||||
class="grid-row"
|
||||
>
|
||||
<div class="grid-cell label"
|
||||
title="The size of regular and alarm markers for this series."
|
||||
>Marker Size:</div>
|
||||
<div class="grid-cell value"><input v-model="markerSize"
|
||||
class="c-input--flex"
|
||||
type="text"
|
||||
@change="updateForm('markerSize')"
|
||||
></div>
|
||||
</li>
|
||||
<li v-show="interpolate !== 'none' || markers"
|
||||
class="grid-row"
|
||||
>
|
||||
<div class="grid-cell label"
|
||||
title="Manually set the plot line and marker color for this series."
|
||||
>Color</div>
|
||||
<div class="grid-cell value">
|
||||
<div class="c-click-swatch c-click-swatch--menu"
|
||||
@click="toggleSwatch()"
|
||||
>
|
||||
<span class="c-color-swatch"
|
||||
:style="{ background: seriesColorAsHex }"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
<div class="c-palette c-palette--color">
|
||||
<div v-show="swatchActive"
|
||||
class="c-palette__items"
|
||||
>
|
||||
<div v-for="(group, index) in colorPalette"
|
||||
:key="index"
|
||||
class="u-contents"
|
||||
>
|
||||
<div v-for="(color, colorIndex) in group"
|
||||
:key="colorIndex"
|
||||
class="c-palette__item"
|
||||
:class="{ 'selected': series.get('color').equalTo(color) }"
|
||||
:style="{ background: color.asHexString() }"
|
||||
@click="setColor(color)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { MARKER_SHAPES } from "../../draw/MarkerShapes";
|
||||
import { objectPath, validate, coerce } from "./formUtil";
|
||||
import _ from 'lodash';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject', 'path'],
|
||||
props: {
|
||||
series: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expanded: false,
|
||||
markerShapeOptions: [],
|
||||
yKey: this.series.get('yKey'),
|
||||
yKeyOptions: [],
|
||||
interpolate: this.series.get('interpolate'),
|
||||
markers: this.series.get('markers'),
|
||||
markerShape: this.series.get('markerShape'),
|
||||
alarmMarkers: this.series.get('alarmMarkers'),
|
||||
markerSize: this.series.get('markerSize'),
|
||||
validation: {},
|
||||
swatchActive: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
colorPalette() {
|
||||
return this.series.collection.palette.groups();
|
||||
},
|
||||
objectLabelCss() {
|
||||
return this.status ? `c-object-label is-status--${this.status}'` : 'c-object-label';
|
||||
},
|
||||
seriesCss() {
|
||||
let type = this.openmct.types.get(this.series.domainObject.type);
|
||||
|
||||
return type.definition.cssClass ? `c-object-label__type-icon ${type.definition.cssClass}` : `c-object-label__type-icon`;
|
||||
},
|
||||
isAliasCss() {
|
||||
let cssClass = '';
|
||||
const domainObjectPath = [this.series.domainObject, ...this.path];
|
||||
if (this.openmct.objects.isObjectPathToALink(this.series.domainObject, domainObjectPath)) {
|
||||
cssClass = 'is-alias';
|
||||
}
|
||||
|
||||
return cssClass;
|
||||
},
|
||||
expandedCssClass() {
|
||||
return this.expanded ? 'c-disclosure-triangle--expanded' : '';
|
||||
},
|
||||
seriesColorAsHex() {
|
||||
return this.series.get('color').asHexString();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initialize();
|
||||
|
||||
this.status = this.openmct.status.get(this.series.domainObject.identifier);
|
||||
this.removeStatusListener = this.openmct.status.observe(this.series.domainObject.identifier, this.setStatus);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.removeStatusListener) {
|
||||
this.removeStatusListener();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initialize: function () {
|
||||
this.fields = [
|
||||
{
|
||||
modelProp: 'yKey',
|
||||
objectPath: this.dynamicPathForKey('yKey')
|
||||
},
|
||||
{
|
||||
modelProp: 'interpolate',
|
||||
objectPath: this.dynamicPathForKey('interpolate')
|
||||
},
|
||||
{
|
||||
modelProp: 'markers',
|
||||
objectPath: this.dynamicPathForKey('markers')
|
||||
},
|
||||
{
|
||||
modelProp: 'markerShape',
|
||||
objectPath: this.dynamicPathForKey('markerShape')
|
||||
},
|
||||
{
|
||||
modelProp: 'markerSize',
|
||||
coerce: Number,
|
||||
objectPath: this.dynamicPathForKey('markerSize')
|
||||
},
|
||||
{
|
||||
modelProp: 'alarmMarkers',
|
||||
coerce: Boolean,
|
||||
objectPath: this.dynamicPathForKey('alarmMarkers')
|
||||
}
|
||||
];
|
||||
|
||||
const metadata = this.series.metadata;
|
||||
this.yKeyOptions = metadata
|
||||
.valuesForHints(['range'])
|
||||
.map(function (o) {
|
||||
return {
|
||||
name: o.key,
|
||||
value: o.key
|
||||
};
|
||||
});
|
||||
this.markerShapeOptions = Object.entries(MARKER_SHAPES)
|
||||
.map(([key, obj]) => {
|
||||
return {
|
||||
name: obj.label,
|
||||
value: key
|
||||
};
|
||||
});
|
||||
},
|
||||
dynamicPathForKey(key) {
|
||||
return function (object, model) {
|
||||
const modelIdentifier = model.get('identifier');
|
||||
const index = object.configuration.series.findIndex(s => {
|
||||
return _.isEqual(s.identifier, modelIdentifier);
|
||||
});
|
||||
|
||||
return 'configuration.series[' + index + '].' + key;
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Set the color for the current plot series. If the new color was
|
||||
* already assigned to a different plot series, then swap the colors.
|
||||
*/
|
||||
setColor: function (color) {
|
||||
const oldColor = this.series.get('color');
|
||||
const otherSeriesWithColor = this.series.collection.filter(function (s) {
|
||||
return s.get('color') === color;
|
||||
})[0];
|
||||
|
||||
this.series.set('color', color);
|
||||
|
||||
const getPath = this.dynamicPathForKey('color');
|
||||
const seriesColorPath = getPath(this.domainObject, this.series);
|
||||
|
||||
this.openmct.objects.mutate(
|
||||
this.domainObject,
|
||||
seriesColorPath,
|
||||
color.asHexString()
|
||||
);
|
||||
|
||||
if (otherSeriesWithColor) {
|
||||
otherSeriesWithColor.set('color', oldColor);
|
||||
|
||||
const otherSeriesColorPath = getPath(
|
||||
this.domainObject,
|
||||
otherSeriesWithColor
|
||||
);
|
||||
|
||||
this.openmct.objects.mutate(
|
||||
this.domainObject,
|
||||
otherSeriesColorPath,
|
||||
oldColor.asHexString()
|
||||
);
|
||||
}
|
||||
},
|
||||
toggleExpanded() {
|
||||
this.expanded = !this.expanded;
|
||||
},
|
||||
updateForm(formKey) {
|
||||
const newVal = this[formKey];
|
||||
const oldVal = this.series.get(formKey);
|
||||
const formField = this.fields.find((field) => field.modelProp === formKey);
|
||||
|
||||
const path = objectPath(formField.objectPath);
|
||||
const validationResult = validate(newVal, this.series, formField.validate);
|
||||
if (validationResult === true) {
|
||||
delete this.validation[formKey];
|
||||
} else {
|
||||
this.validation[formKey] = validationResult;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_.isEqual(coerce(newVal, formField.coerce), coerce(oldVal, formField.coerce))) {
|
||||
this.series.set(formKey, coerce(newVal, formField.coerce));
|
||||
if (path) {
|
||||
this.openmct.objects.mutate(
|
||||
this.domainObject,
|
||||
path(this.domainObject, this.series),
|
||||
coerce(newVal, formField.coerce)
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
setStatus(status) {
|
||||
this.status = status;
|
||||
},
|
||||
toggleSwatch() {
|
||||
this.swatchActive = !this.swatchActive;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
229
src/plugins/plot/inspector/forms/YAxisForm.vue
Normal file
229
src/plugins/plot/inspector/forms/YAxisForm.vue
Normal file
@ -0,0 +1,229 @@
|
||||
<template>
|
||||
<div>
|
||||
<ul class="l-inspector-part">
|
||||
<h2>Y Axis</h2>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Manually override how the Y axis is labeled."
|
||||
>Label</div>
|
||||
<div class="grid-cell value"><input v-model="label"
|
||||
class="c-input--flex"
|
||||
type="text"
|
||||
@change="updateForm('label')"
|
||||
></div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="l-inspector-part">
|
||||
<h2>Y Axis Scaling</h2>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Automatically scale the Y axis to keep all values in view."
|
||||
>Auto scale</div>
|
||||
<div class="grid-cell value"><input v-model="autoscale"
|
||||
type="checkbox"
|
||||
@change="updateForm('autoscale')"
|
||||
></div>
|
||||
</li>
|
||||
<li v-show="autoscale"
|
||||
class="grid-row"
|
||||
>
|
||||
<div class="grid-cell label"
|
||||
title="Percentage of padding above and below plotted min and max values. 0.1, 1.0, etc."
|
||||
>
|
||||
Padding</div>
|
||||
<div class="grid-cell value">
|
||||
<input v-model="autoscalePadding"
|
||||
class="c-input--flex"
|
||||
type="text"
|
||||
@change="updateForm('autoscalePadding')"
|
||||
>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul v-show="!autoscale"
|
||||
class="l-inspector-part"
|
||||
>
|
||||
<div v-show="!autoscale && validation.range"
|
||||
class="grid-span-all form-error"
|
||||
>
|
||||
{{ validation.range }}
|
||||
</div>
|
||||
<li class="grid-row force-border">
|
||||
<div class="grid-cell label"
|
||||
title="Minimum Y axis value."
|
||||
>Minimum Value</div>
|
||||
<div class="grid-cell value">
|
||||
<input v-model="rangeMin"
|
||||
class="c-input--flex"
|
||||
type="number"
|
||||
@change="updateForm('range')"
|
||||
>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Maximum Y axis value."
|
||||
>Maximum Value</div>
|
||||
<div class="grid-cell value"><input v-model="rangeMax"
|
||||
class="c-input--flex"
|
||||
type="number"
|
||||
@change="updateForm('range')"
|
||||
></div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { objectPath, validate, coerce } from "./formUtil";
|
||||
import _ from "lodash";
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
props: {
|
||||
yAxis: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
label: '',
|
||||
autoscale: '',
|
||||
autoscalePadding: '',
|
||||
rangeMin: '',
|
||||
rangeMax: '',
|
||||
validation: {}
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.initialize();
|
||||
this.initFormValues();
|
||||
},
|
||||
methods: {
|
||||
initialize: function () {
|
||||
this.fields = [
|
||||
{
|
||||
modelProp: 'label',
|
||||
objectPath: 'configuration.yAxis.label'
|
||||
},
|
||||
{
|
||||
modelProp: 'autoscale',
|
||||
coerce: Boolean,
|
||||
objectPath: 'configuration.yAxis.autoscale'
|
||||
},
|
||||
{
|
||||
modelProp: 'autoscalePadding',
|
||||
coerce: Number,
|
||||
objectPath: 'configuration.yAxis.autoscalePadding'
|
||||
},
|
||||
{
|
||||
modelProp: 'range',
|
||||
objectPath: 'configuration.yAxis.range',
|
||||
coerce: function coerceRange(range) {
|
||||
if (!range) {
|
||||
return {
|
||||
min: 0,
|
||||
max: 0
|
||||
};
|
||||
}
|
||||
|
||||
const newRange = {};
|
||||
if (typeof range.min !== 'undefined' && range.min !== null) {
|
||||
newRange.min = Number(range.min);
|
||||
}
|
||||
|
||||
if (typeof range.max !== 'undefined' && range.max !== null) {
|
||||
newRange.max = Number(range.max);
|
||||
}
|
||||
|
||||
return newRange;
|
||||
},
|
||||
validate: function validateRange(range, model) {
|
||||
if (!range) {
|
||||
return 'Need range';
|
||||
}
|
||||
|
||||
if (range.min === '' || range.min === null || typeof range.min === 'undefined') {
|
||||
return 'Must specify Minimum';
|
||||
}
|
||||
|
||||
if (range.max === '' || range.max === null || typeof range.max === 'undefined') {
|
||||
return 'Must specify Maximum';
|
||||
}
|
||||
|
||||
if (Number.isNaN(Number(range.min))) {
|
||||
return 'Minimum must be a number.';
|
||||
}
|
||||
|
||||
if (Number.isNaN(Number(range.max))) {
|
||||
return 'Maximum must be a number.';
|
||||
}
|
||||
|
||||
if (Number(range.min) > Number(range.max)) {
|
||||
return 'Minimum must be less than Maximum.';
|
||||
}
|
||||
|
||||
if (model.get('autoscale')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
];
|
||||
},
|
||||
initFormValues() {
|
||||
this.label = this.yAxis.get('label');
|
||||
this.autoscale = this.yAxis.get('autoscale');
|
||||
this.autoscalePadding = this.yAxis.get('autoscalePadding');
|
||||
const range = this.yAxis.get('range');
|
||||
if (!range) {
|
||||
this.rangeMin = undefined;
|
||||
this.rangeMax = undefined;
|
||||
} else {
|
||||
this.rangeMin = range.min;
|
||||
this.rangeMax = range.max;
|
||||
}
|
||||
},
|
||||
updateForm(formKey) {
|
||||
let newVal;
|
||||
if (formKey === 'range') {
|
||||
newVal = {
|
||||
min: this.rangeMin,
|
||||
max: this.rangeMax
|
||||
};
|
||||
} else {
|
||||
newVal = this[formKey];
|
||||
}
|
||||
|
||||
const oldVal = this.yAxis.get(formKey);
|
||||
const formField = this.fields.find((field) => field.modelProp === formKey);
|
||||
|
||||
const path = objectPath(formField.objectPath);
|
||||
const validationResult = validate(newVal, this.yAxis, formField.validate);
|
||||
if (validationResult === true) {
|
||||
delete this.validation[formKey];
|
||||
} else {
|
||||
this.validation[formKey] = validationResult;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_.isEqual(coerce(newVal, formField.coerce), coerce(oldVal, formField.coerce))) {
|
||||
this.yAxis.set(formKey, coerce(newVal, formField.coerce));
|
||||
if (path) {
|
||||
this.openmct.objects.mutate(
|
||||
this.domainObject,
|
||||
path(this.domainObject, this.yAxis),
|
||||
coerce(newVal, formField.coerce)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
29
src/plugins/plot/inspector/forms/formUtil.js
Normal file
29
src/plugins/plot/inspector/forms/formUtil.js
Normal file
@ -0,0 +1,29 @@
|
||||
export function coerce(value, coerceFunc) {
|
||||
if (coerceFunc) {
|
||||
return coerceFunc(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function validate(value, model, validateFunc) {
|
||||
if (validateFunc) {
|
||||
return validateFunc(value, model);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function objectPath(path) {
|
||||
if (path) {
|
||||
if (typeof path !== "function") {
|
||||
const staticObjectPath = path;
|
||||
|
||||
return function (object, model) {
|
||||
return staticObjectPath;
|
||||
};
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
29
src/plugins/plot/overlayPlot/OverlayPlotCompositionPolicy.js
Normal file
29
src/plugins/plot/overlayPlot/OverlayPlotCompositionPolicy.js
Normal file
@ -0,0 +1,29 @@
|
||||
export default function OverlayPlotCompositionPolicy(openmct) {
|
||||
function hasNumericTelemetry(domainObject) {
|
||||
const hasTelemetry = openmct.telemetry.isTelemetryObject(domainObject);
|
||||
if (!hasTelemetry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let metadata = openmct.telemetry.getMetadata(domainObject);
|
||||
|
||||
return metadata.values().length > 0 && hasDomainAndRange(metadata);
|
||||
}
|
||||
|
||||
function hasDomainAndRange(metadata) {
|
||||
return (metadata.valuesForHints(['range']).length > 0
|
||||
&& metadata.valuesForHints(['domain']).length > 0);
|
||||
}
|
||||
|
||||
return {
|
||||
allow: function (parent, child) {
|
||||
|
||||
if (parent.type === 'telemetry.plot.overlay'
|
||||
&& (hasNumericTelemetry(child) === false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import Plot from '../single/Plot.vue';
|
||||
import Plot from '../Plot.vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default function OverlayPlotViewProvider(openmct) {
|
||||
@ -33,11 +33,11 @@ export default function OverlayPlotViewProvider(openmct) {
|
||||
name: 'Overlay Plot',
|
||||
cssClass: 'icon-telemetry',
|
||||
canView(domainObject, objectPath) {
|
||||
return isCompactView(objectPath) && domainObject.type === 'telemetry.plot.overlay';
|
||||
return domainObject.type === 'telemetry.plot.overlay';
|
||||
},
|
||||
|
||||
canEdit(domainObject, objectPath) {
|
||||
return isCompactView(objectPath) && domainObject.type === 'telemetry.plot.overlay';
|
||||
return domainObject.type === 'telemetry.plot.overlay';
|
||||
},
|
||||
|
||||
view: function (domainObject, objectPath) {
|
@ -20,262 +20,52 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
"./src/chart/MCTChartDirective",
|
||||
"./src/plot/MCTPlotDirective",
|
||||
'./src/plot/MCTTicksDirective',
|
||||
"./src/telemetry/MCTOverlayPlot",
|
||||
"./src/telemetry/PlotController",
|
||||
"./src/telemetry/StackedPlotController",
|
||||
"./src/inspector/PlotInspector",
|
||||
"./src/inspector/PlotOptionsController",
|
||||
"./src/inspector/PlotLegendFormController",
|
||||
"./src/inspector/PlotYAxisFormController",
|
||||
"./src/inspector/PlotSeriesFormController",
|
||||
"./src/inspector/HideElementPoolDirective",
|
||||
"./src/services/ExportImageService",
|
||||
'./src/PlotViewPolicy',
|
||||
"./res/templates/plot-options.html",
|
||||
"./res/templates/plot-options-browse.html",
|
||||
"./res/templates/plot-options-edit.html",
|
||||
"./res/templates/stacked-plot.html",
|
||||
"./res/templates/plot.html"
|
||||
], function (
|
||||
MCTChartDirective,
|
||||
MCTPlotDirective,
|
||||
MCTTicksDirective,
|
||||
MCTOverlayPlot,
|
||||
PlotController,
|
||||
StackedPlotController,
|
||||
PlotInspector,
|
||||
PlotOptionsController,
|
||||
PlotLegendFormController,
|
||||
PlotYAxisFormController,
|
||||
PlotSeriesFormController,
|
||||
HideElementPool,
|
||||
ExportImageService,
|
||||
PlotViewPolicy,
|
||||
plotOptionsTemplate,
|
||||
plotOptionsBrowseTemplate,
|
||||
plotOptionsEditTemplate,
|
||||
StackedPlotTemplate,
|
||||
PlotTemplate
|
||||
) {
|
||||
import PlotViewProvider from './PlotViewProvider';
|
||||
import OverlayPlotViewProvider from './overlayPlot/OverlayPlotViewProvider';
|
||||
import StackedPlotViewProvider from './stackedPlot/StackedPlotViewProvider';
|
||||
import PlotsInspectorViewProvider from './inspector/PlotsInspectorViewProvider';
|
||||
import OverlayPlotCompositionPolicy from './overlayPlot/OverlayPlotCompositionPolicy';
|
||||
import StackedPlotCompositionPolicy from './stackedPlot/StackedPlotCompositionPolicy';
|
||||
|
||||
let installed = false;
|
||||
export default function () {
|
||||
return function install(openmct) {
|
||||
|
||||
function PlotPlugin() {
|
||||
return function install(openmct) {
|
||||
if (installed) {
|
||||
return;
|
||||
}
|
||||
openmct.types.addType('telemetry.plot.overlay', {
|
||||
key: "telemetry.plot.overlay",
|
||||
name: "Overlay Plot",
|
||||
cssClass: "icon-plot-overlay",
|
||||
description: "Combine multiple telemetry elements and view them together as a plot with common X and Y axes. Can be added to Display Layouts.",
|
||||
creatable: "true",
|
||||
initialize: function (domainObject) {
|
||||
domainObject.composition = [];
|
||||
domainObject.configuration = {
|
||||
series: [],
|
||||
yAxis: {},
|
||||
xAxis: {}
|
||||
};
|
||||
},
|
||||
priority: 891
|
||||
});
|
||||
|
||||
installed = true;
|
||||
openmct.types.addType('telemetry.plot.stacked', {
|
||||
key: "telemetry.plot.stacked",
|
||||
name: "Stacked Plot",
|
||||
cssClass: "icon-plot-stacked",
|
||||
description: "Combine multiple telemetry elements and view them together as a plot with a common X axis and individual Y axes. Can be added to Display Layouts.",
|
||||
creatable: true,
|
||||
initialize: function (domainObject) {
|
||||
domainObject.composition = [];
|
||||
domainObject.configuration = {};
|
||||
},
|
||||
priority: 890
|
||||
});
|
||||
|
||||
openmct.legacyRegistry.register("openmct/plot", {
|
||||
"name": "Plot view for telemetry, reborn",
|
||||
"extensions": {
|
||||
"policies": [
|
||||
{
|
||||
"category": "view",
|
||||
"implementation": PlotViewPolicy,
|
||||
"depends": [
|
||||
"openmct"
|
||||
]
|
||||
}
|
||||
],
|
||||
"views": [
|
||||
{
|
||||
"name": "Plot",
|
||||
"key": "plot-single",
|
||||
"cssClass": "icon-telemetry",
|
||||
"template": PlotTemplate,
|
||||
"needs": [
|
||||
"telemetry"
|
||||
],
|
||||
"delegation": false,
|
||||
"priority": "mandatory"
|
||||
},
|
||||
{
|
||||
"name": "Overlay Plot",
|
||||
"key": "overlayPlot",
|
||||
"cssClass": "icon-plot-overlay",
|
||||
"type": "telemetry.plot.overlay",
|
||||
"template": PlotTemplate,
|
||||
"editable": true
|
||||
},
|
||||
{
|
||||
"name": "Stacked Plot",
|
||||
"key": "stackedPlot",
|
||||
"cssClass": "icon-plot-stacked",
|
||||
"type": "telemetry.plot.stacked",
|
||||
"template": StackedPlotTemplate,
|
||||
"editable": true
|
||||
}
|
||||
],
|
||||
"directives": [
|
||||
{
|
||||
"key": "mctTicks",
|
||||
"implementation": MCTTicksDirective,
|
||||
"depends": []
|
||||
},
|
||||
{
|
||||
"key": "mctChart",
|
||||
"implementation": MCTChartDirective,
|
||||
"depends": [
|
||||
"$interval",
|
||||
"$log"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mctPlot",
|
||||
"implementation": MCTPlotDirective,
|
||||
"depends": [],
|
||||
"templateUrl": "templates/mct-plot.html"
|
||||
},
|
||||
{
|
||||
"key": "mctOverlayPlot",
|
||||
"implementation": MCTOverlayPlot,
|
||||
"depends": []
|
||||
},
|
||||
{
|
||||
"key": "hideElementPool",
|
||||
"implementation": HideElementPool,
|
||||
"depends": []
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "PlotController",
|
||||
"implementation": PlotController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"$element",
|
||||
"formatService",
|
||||
"openmct",
|
||||
"objectService",
|
||||
"exportImageService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "StackedPlotController",
|
||||
"implementation": StackedPlotController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"openmct",
|
||||
"objectService",
|
||||
"$element",
|
||||
"exportImageService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "PlotOptionsController",
|
||||
"implementation": PlotOptionsController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"openmct",
|
||||
"$timeout"
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "PlotLegendFormController",
|
||||
implementation: PlotLegendFormController,
|
||||
depends: [
|
||||
"$scope",
|
||||
"openmct",
|
||||
"$attrs"
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "PlotYAxisFormController",
|
||||
implementation: PlotYAxisFormController,
|
||||
depends: [
|
||||
"$scope",
|
||||
"openmct",
|
||||
"$attrs"
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "PlotSeriesFormController",
|
||||
implementation: PlotSeriesFormController,
|
||||
depends: [
|
||||
"$scope",
|
||||
"openmct",
|
||||
"$attrs"
|
||||
]
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"key": "exportImageService",
|
||||
"implementation": ExportImageService,
|
||||
"depends": [
|
||||
"dialogService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"key": "telemetry.plot.overlay",
|
||||
"name": "Overlay Plot",
|
||||
"cssClass": "icon-plot-overlay",
|
||||
"description": "Combine multiple telemetry elements and view them together as a plot with common X and Y axes. Can be added to Display Layouts.",
|
||||
"features": "creation",
|
||||
"contains": [
|
||||
{
|
||||
"has": "telemetry"
|
||||
}
|
||||
],
|
||||
"model": {
|
||||
composition: [],
|
||||
configuration: {
|
||||
series: [],
|
||||
yAxis: {},
|
||||
xAxis: {}
|
||||
}
|
||||
},
|
||||
"properties": [],
|
||||
"inspector": "plot-options",
|
||||
"priority": 891
|
||||
},
|
||||
{
|
||||
"key": "telemetry.plot.stacked",
|
||||
"name": "Stacked Plot",
|
||||
"cssClass": "icon-plot-stacked",
|
||||
"description": "Combine multiple telemetry elements and view them together as a plot with a common X axis and individual Y axes. Can be added to Display Layouts.",
|
||||
"features": "creation",
|
||||
"contains": [
|
||||
"telemetry.plot.overlay",
|
||||
{"has": "telemetry"}
|
||||
],
|
||||
"model": {
|
||||
"composition": [],
|
||||
"configuration": {}
|
||||
},
|
||||
"properties": [],
|
||||
"priority": 890
|
||||
}
|
||||
],
|
||||
"representations": [
|
||||
{
|
||||
"key": "plot-options",
|
||||
"template": plotOptionsTemplate
|
||||
},
|
||||
{
|
||||
"key": "plot-options-browse",
|
||||
"template": plotOptionsBrowseTemplate
|
||||
},
|
||||
{
|
||||
"key": "plot-options-edit",
|
||||
"template": plotOptionsEditTemplate
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
openmct.objectViews.addProvider(new StackedPlotViewProvider(openmct));
|
||||
openmct.objectViews.addProvider(new OverlayPlotViewProvider(openmct));
|
||||
openmct.objectViews.addProvider(new PlotViewProvider(openmct));
|
||||
openmct.inspectorViews.addProvider(new PlotsInspectorViewProvider(openmct));
|
||||
openmct.composition.addPolicy(new OverlayPlotCompositionPolicy(openmct).allow);
|
||||
openmct.composition.addPolicy(new StackedPlotCompositionPolicy(openmct).allow);
|
||||
};
|
||||
}
|
||||
|
||||
openmct.legacyRegistry.enable("openmct/plot");
|
||||
};
|
||||
}
|
||||
|
||||
return PlotPlugin;
|
||||
});
|
||||
|
@ -23,9 +23,11 @@
|
||||
import {createMouseEvent, createOpenMct, resetApplicationState, spyOnBuiltins} from "utils/testing";
|
||||
import PlotVuePlugin from "./plugin";
|
||||
import Vue from "vue";
|
||||
import StackedPlot from "../stackedPlot/StackedPlot.vue";
|
||||
import configStore from "@/plugins/plot/vue/single/configuration/configStore";
|
||||
import StackedPlot from "./stackedPlot/StackedPlot.vue";
|
||||
import configStore from "./configuration/configStore";
|
||||
import EventEmitter from "EventEmitter";
|
||||
import PlotOptions from "./inspector/PlotOptions.vue";
|
||||
import PlotConfigurationModel from "./configuration/PlotConfigurationModel";
|
||||
|
||||
describe("the plugin", function () {
|
||||
let element;
|
||||
@ -174,6 +176,35 @@ describe("the plugin", function () {
|
||||
expect(plotView).toBeDefined();
|
||||
});
|
||||
|
||||
it('provides an inspector view for overlay plots', () => {
|
||||
let selection = [
|
||||
[
|
||||
{
|
||||
context: {
|
||||
item: {
|
||||
id: "test-object",
|
||||
type: "telemetry.plot.overlay",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "some-key"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
context: {
|
||||
item: {
|
||||
type: 'time-strip'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
const plotInspectorView = openmct.inspectorViews.get(selection);
|
||||
expect(plotInspectorView.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("provides a stacked plot view for objects with telemetry", () => {
|
||||
const testTelemetryObject = {
|
||||
id: "test-object",
|
||||
@ -282,6 +313,46 @@ describe("the plugin", function () {
|
||||
expect(options[0].value).toBe("Some attribute");
|
||||
expect(options[1].value).toBe("Another attribute");
|
||||
});
|
||||
|
||||
it('hides the pause and play controls', () => {
|
||||
let pauseEl = element.querySelectorAll(".c-button-set .icon-pause");
|
||||
let playEl = element.querySelectorAll(".c-button-set .icon-arrow-right");
|
||||
expect(pauseEl.length).toBe(0);
|
||||
expect(playEl.length).toBe(0);
|
||||
});
|
||||
|
||||
describe('pause and play controls', () => {
|
||||
beforeEach(() => {
|
||||
openmct.time.clock('local', {
|
||||
start: -1000,
|
||||
end: 100
|
||||
});
|
||||
|
||||
return Vue.nextTick();
|
||||
});
|
||||
|
||||
it('shows the pause controls', (done) => {
|
||||
Vue.nextTick(() => {
|
||||
let pauseEl = element.querySelectorAll(".c-button-set .icon-pause");
|
||||
expect(pauseEl.length).toBe(1);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('shows the play control if plot is paused', (done) => {
|
||||
let pauseEl = element.querySelector(".c-button-set .icon-pause");
|
||||
const clickEvent = createMouseEvent("click");
|
||||
|
||||
pauseEl.dispatchEvent(clickEvent);
|
||||
Vue.nextTick(() => {
|
||||
let playEl = element.querySelectorAll(".c-button-set .is-paused");
|
||||
expect(playEl.length).toBe(1);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("The stacked plot view", () => {
|
||||
@ -578,4 +649,218 @@ describe("the plugin", function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('the inspector view', () => {
|
||||
let component;
|
||||
let viewComponentObject;
|
||||
let mockComposition;
|
||||
let testTelemetryObject;
|
||||
let selection;
|
||||
let config;
|
||||
beforeEach((done) => {
|
||||
testTelemetryObject = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "test-object"
|
||||
},
|
||||
type: "test-object",
|
||||
name: "Test Object",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "utc",
|
||||
format: "utc",
|
||||
name: "Time",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-key",
|
||||
name: "Some attribute",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-other-key",
|
||||
name: "Another attribute",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
selection = [
|
||||
[
|
||||
{
|
||||
context: {
|
||||
item: {
|
||||
id: "test-object",
|
||||
identifier: {
|
||||
key: "test-object",
|
||||
namespace: ''
|
||||
},
|
||||
type: "telemetry.plot.overlay",
|
||||
configuration: {
|
||||
series: [
|
||||
{
|
||||
identifier: {
|
||||
key: "test-object",
|
||||
namespace: ''
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
composition: []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
context: {
|
||||
item: {
|
||||
type: 'time-strip',
|
||||
identifier: {
|
||||
key: 'some-other-key',
|
||||
namespace: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
mockComposition = new EventEmitter();
|
||||
mockComposition.load = () => {
|
||||
mockComposition.emit('add', testTelemetryObject);
|
||||
|
||||
return [testTelemetryObject];
|
||||
};
|
||||
|
||||
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
|
||||
|
||||
const configId = openmct.objects.makeKeyString(selection[0][0].context.item.identifier);
|
||||
config = new PlotConfigurationModel({
|
||||
id: configId,
|
||||
domainObject: selection[0][0].context.item,
|
||||
openmct: openmct
|
||||
});
|
||||
configStore.add(configId, config);
|
||||
|
||||
let viewContainer = document.createElement('div');
|
||||
child.append(viewContainer);
|
||||
component = new Vue({
|
||||
el: viewContainer,
|
||||
components: {
|
||||
PlotOptions
|
||||
},
|
||||
provide: {
|
||||
openmct: openmct,
|
||||
domainObject: selection[0][0].context.item,
|
||||
path: [selection[0][0].context.item, selection[0][1].context.item]
|
||||
},
|
||||
template: '<plot-options/>'
|
||||
});
|
||||
|
||||
Vue.nextTick(() => {
|
||||
viewComponentObject = component.$root.$children[0];
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('in view only mode', () => {
|
||||
let browseOptionsEl;
|
||||
let editOptionsEl;
|
||||
beforeEach(() => {
|
||||
browseOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-browse');
|
||||
editOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-edit');
|
||||
});
|
||||
|
||||
it('does not show the edit options', () => {
|
||||
expect(editOptionsEl).toBeNull();
|
||||
});
|
||||
|
||||
it('shows the name', () => {
|
||||
const seriesEl = browseOptionsEl.querySelector('.c-object-label__name');
|
||||
expect(seriesEl.innerHTML).toEqual(testTelemetryObject.name);
|
||||
});
|
||||
|
||||
it('shows in collapsed mode', () => {
|
||||
const seriesEl = browseOptionsEl.querySelectorAll('.c-disclosure-triangle--expanded');
|
||||
expect(seriesEl.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('shows in expanded mode', () => {
|
||||
let expandControl = browseOptionsEl.querySelector(".c-disclosure-triangle");
|
||||
const clickEvent = createMouseEvent("click");
|
||||
expandControl.dispatchEvent(clickEvent);
|
||||
|
||||
const plotOptionsProperties = browseOptionsEl.querySelectorAll('.js-plot-options-browse-properties .grid-row');
|
||||
expect(plotOptionsProperties.length).toEqual(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('in edit mode', () => {
|
||||
let editOptionsEl;
|
||||
let browseOptionsEl;
|
||||
|
||||
beforeEach((done) => {
|
||||
viewComponentObject.setEditState(true);
|
||||
Vue.nextTick(() => {
|
||||
editOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-edit');
|
||||
browseOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-browse');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not show the browse options', () => {
|
||||
expect(browseOptionsEl).toBeNull();
|
||||
});
|
||||
|
||||
it('shows the name', () => {
|
||||
const seriesEl = editOptionsEl.querySelector('.c-object-label__name');
|
||||
expect(seriesEl.innerHTML).toEqual(testTelemetryObject.name);
|
||||
});
|
||||
|
||||
it('shows in collapsed mode', () => {
|
||||
const seriesEl = editOptionsEl.querySelectorAll('.c-disclosure-triangle--expanded');
|
||||
expect(seriesEl.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('shows in collapsed mode', () => {
|
||||
const seriesEl = editOptionsEl.querySelectorAll('.c-disclosure-triangle--expanded');
|
||||
expect(seriesEl.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('renders expanded', () => {
|
||||
const expandControl = editOptionsEl.querySelector(".c-disclosure-triangle");
|
||||
const clickEvent = createMouseEvent("click");
|
||||
expandControl.dispatchEvent(clickEvent);
|
||||
|
||||
const plotOptionsProperties = editOptionsEl.querySelectorAll(".js-plot-options-edit-properties .grid-row");
|
||||
expect(plotOptionsProperties.length).toEqual(6);
|
||||
});
|
||||
|
||||
it('shows yKeyOptions', () => {
|
||||
const expandControl = editOptionsEl.querySelector(".c-disclosure-triangle");
|
||||
const clickEvent = createMouseEvent("click");
|
||||
expandControl.dispatchEvent(clickEvent);
|
||||
|
||||
const plotOptionsProperties = editOptionsEl.querySelectorAll(".js-plot-options-edit-properties .grid-row");
|
||||
|
||||
const yKeySelection = plotOptionsProperties[0].querySelector('select');
|
||||
const options = Array.from(yKeySelection.options).map((option) => {
|
||||
return option.value;
|
||||
});
|
||||
expect(options).toEqual([testTelemetryObject.telemetry.values[1].key, testTelemetryObject.telemetry.values[2].key]);
|
||||
});
|
||||
|
||||
it('shows yAxis options', () => {
|
||||
const expandControl = editOptionsEl.querySelector(".c-disclosure-triangle");
|
||||
const clickEvent = createMouseEvent("click");
|
||||
expandControl.dispatchEvent(clickEvent);
|
||||
|
||||
const yAxisProperties = editOptionsEl.querySelectorAll("div.grid-properties:first-of-type .l-inspector-part");
|
||||
expect(yAxisProperties.length).toEqual(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@ -1,279 +0,0 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2021, 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.
|
||||
-->
|
||||
<div class="gl-plot plot-legend-{{legend.get('position')}} {{legend.get('expanded')? 'plot-legend-expanded' : 'plot-legend-collapsed'}}">
|
||||
<div class="c-plot-legend gl-plot-legend"
|
||||
ng-class="{
|
||||
'hover-on-plot': !!highlights.length,
|
||||
'is-legend-hidden': legend.get('hideLegendWhenSmall')
|
||||
}"
|
||||
>
|
||||
<div class="c-plot-legend__view-control gl-plot-legend__view-control c-disclosure-triangle is-enabled"
|
||||
ng-class="{ 'c-disclosure-triangle--expanded': legend.get('expanded') }"
|
||||
ng-click="legend.set('expanded', !legend.get('expanded'));">
|
||||
</div>
|
||||
|
||||
<div class="c-plot-legend__wrapper"
|
||||
ng-class="{ 'is-cursor-locked': !!lockHighlightPoint }">
|
||||
|
||||
<!-- COLLAPSED PLOT LEGEND -->
|
||||
<div class="plot-wrapper-collapsed-legend"
|
||||
ng-class="{'is-cursor-locked': !!lockHighlightPoint }">
|
||||
<div class="c-state-indicator__alert-cursor-lock icon-cursor-lock" title="Cursor is point locked. Click anywhere in the plot to unlock."></div>
|
||||
<div class="plot-legend-item"
|
||||
ng-class="{
|
||||
'is-status--missing': series.domainObject.status === 'missing'
|
||||
}"
|
||||
ng-repeat="series in series track by $index"
|
||||
>
|
||||
<div class="plot-series-swatch-and-name">
|
||||
<span class="plot-series-color-swatch"
|
||||
ng-style="{ 'background-color': series.get('color').asHexString() }">
|
||||
</span>
|
||||
<span class="is-status__indicator" title="This item is missing or suspect"></span>
|
||||
<span class="plot-series-name">{{ series.nameWithUnit() }}</span>
|
||||
</div>
|
||||
<div class="plot-series-value hover-value-enabled value-to-display-{{ legend.get('valueToShowWhenCollapsed') }} {{ series.closest.mctLimitState.cssClass }}"
|
||||
ng-class="{ 'cursor-hover': (legend.get('valueToShowWhenCollapsed').indexOf('nearest') != -1) }"
|
||||
ng-show="!!highlights.length && legend.get('valueToShowWhenCollapsed') !== 'none'">
|
||||
{{ legend.get('valueToShowWhenCollapsed') === 'nearestValue' ?
|
||||
series.formatY(series.closest) :
|
||||
legend.get('valueToShowWhenCollapsed') === 'nearestTimestamp' ?
|
||||
series.closest && series.formatX(series.closest) :
|
||||
series.formatY(series.get('stats')[legend.get('valueToShowWhenCollapsed') + 'Point']);
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EXPANDED PLOT LEGEND -->
|
||||
<div class="plot-wrapper-expanded-legend"
|
||||
ng-class="{'is-cursor-locked': !!lockHighlightPoint }"
|
||||
>
|
||||
<div class="c-state-indicator__alert-cursor-lock--verbose icon-cursor-lock" title="Click anywhere in the plot to unlock."> Cursor locked to point</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th ng-if="legend.get('showTimestampWhenExpanded')">
|
||||
Timestamp
|
||||
</th>
|
||||
<th ng-if="legend.get('showValueWhenExpanded')">
|
||||
Value
|
||||
</th>
|
||||
<th ng-if="legend.get('showUnitsWhenExpanded')">
|
||||
Unit
|
||||
</th>
|
||||
<th ng-if="legend.get('showMinimumWhenExpanded')"
|
||||
class="mobile-hide">
|
||||
Min
|
||||
</th>
|
||||
<th ng-if="legend.get('showMaximumWhenExpanded')"
|
||||
class="mobile-hide">
|
||||
Max
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr ng-repeat="series in series"
|
||||
class="plot-legend-item"
|
||||
ng-class="{
|
||||
'is-status--missing': series.domainObject.status === 'missing'
|
||||
}"
|
||||
>
|
||||
<td class="plot-series-swatch-and-name">
|
||||
<span class="plot-series-color-swatch"
|
||||
ng-style="{ 'background-color': series.get('color').asHexString() }">
|
||||
</span>
|
||||
<span class="is-status__indicator" title="This item is missing or suspect"></span>
|
||||
<span class="plot-series-name">{{ series.get('name') }}</span>
|
||||
</td>
|
||||
|
||||
<td ng-if="legend.get('showTimestampWhenExpanded')">
|
||||
<span class="plot-series-value cursor-hover hover-value-enabled">
|
||||
{{ series.closest && series.formatX(series.closest) }}
|
||||
</span>
|
||||
</td>
|
||||
<td ng-if="legend.get('showValueWhenExpanded')">
|
||||
<span class="plot-series-value cursor-hover hover-value-enabled"
|
||||
ng-class="series.closest.mctLimitState.cssClass">
|
||||
{{ series.formatY(series.closest) }}
|
||||
</span>
|
||||
</td>
|
||||
<td ng-if="legend.get('showUnitsWhenExpanded')">
|
||||
<span class="plot-series-value cursor-hover hover-value-enabled">
|
||||
{{ series.get('unit') }}
|
||||
</span>
|
||||
</td>
|
||||
<td ng-if="legend.get('showMinimumWhenExpanded')"
|
||||
class="mobile-hide">
|
||||
<span class="plot-series-value">
|
||||
{{ series.formatY(series.get('stats').minPoint) }}
|
||||
</span>
|
||||
</td>
|
||||
<td ng-if="legend.get('showMaximumWhenExpanded')"
|
||||
class="mobile-hide">
|
||||
<span class="plot-series-value">
|
||||
{{ series.formatY(series.get('stats').maxPoint) }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
|
||||
<div class="gl-plot-axis-area gl-plot-y has-local-controls"
|
||||
ng-style="{
|
||||
width: (tickWidth + 20) + 'px'
|
||||
}">
|
||||
|
||||
<div class="gl-plot-label gl-plot-y-label"
|
||||
ng-class="{'icon-gear': (yKeyOptions.length > 1 && series.length === 1)}"
|
||||
>{{yAxis.get('label')}}
|
||||
</div>
|
||||
|
||||
<select class="gl-plot-y-label__select local-controls--hidden"
|
||||
ng-if="yKeyOptions.length > 1 && series.length === 1"
|
||||
ng-model="yAxisLabel" ng-change="plot.toggleYAxisLabel(yAxisLabel, yKeyOptions, series[0])">
|
||||
<option ng-repeat="option in yKeyOptions"
|
||||
value="{{option.name}}"
|
||||
ng-selected="option.name === yAxisLabel">
|
||||
{{option.name}}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<mct-ticks axis="yAxis">
|
||||
<div ng-repeat="tick in ticks track by tick.value"
|
||||
class="gl-plot-tick gl-plot-y-tick-label"
|
||||
ng-style="{ top: (100 * (max - tick.value) / interval) + '%' }"
|
||||
title="{{:: tick.fullText || tick.text }}"
|
||||
style="margin-top: -0.50em; direction: ltr;">
|
||||
<span>{{:: tick.text}}</span>
|
||||
</div>
|
||||
</mct-ticks>
|
||||
</div>
|
||||
<div class="gl-plot-wrapper-display-area-and-x-axis"
|
||||
ng-style="{
|
||||
left: (tickWidth + 20) + 'px'
|
||||
}">
|
||||
|
||||
<div class="gl-plot-display-area has-local-controls has-cursor-guides">
|
||||
<div class="l-state-indicators">
|
||||
<span class="l-state-indicators__alert-no-lad t-object-alert t-alert-unsynced icon-alert-triangle"
|
||||
title="This plot is not currently displaying the latest data. Reset pan/zoom to view latest data."></span>
|
||||
</div>
|
||||
|
||||
<mct-ticks axis="xAxis">
|
||||
<div class="gl-plot-hash hash-v"
|
||||
ng-repeat="tick in ticks track by tick.value"
|
||||
ng-style="{
|
||||
right: (100 * (max - tick.value) / interval) + '%',
|
||||
height: '100%'
|
||||
}"
|
||||
ng-show="plot.gridLines"
|
||||
>
|
||||
</div>
|
||||
</mct-ticks>
|
||||
|
||||
<mct-ticks axis="yAxis">
|
||||
<div class="gl-plot-hash hash-h"
|
||||
ng-repeat="tick in ticks track by tick.value"
|
||||
ng-style="{ bottom: (100 * (tick.value - min) / interval) + '%', width: '100%' }"
|
||||
ng-show="plot.gridLines"
|
||||
>
|
||||
</div>
|
||||
</mct-ticks>
|
||||
|
||||
<mct-chart config="config"
|
||||
series="series"
|
||||
rectangles="rectangles"
|
||||
highlights="highlights"
|
||||
the-x-axis="xAxis"
|
||||
the-y-axis="yAxis">
|
||||
</mct-chart>
|
||||
|
||||
<div class="gl-plot__local-controls h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover">
|
||||
<div class="c-button-set c-button-set--strip-h">
|
||||
<button class="c-button icon-minus"
|
||||
ng-click="plot.zoom('out', 0.2)"
|
||||
title="Zoom out">
|
||||
</button>
|
||||
<button class="c-button icon-plus"
|
||||
ng-click="plot.zoom('in', 0.2)"
|
||||
title="Zoom in">
|
||||
</button>
|
||||
</div>
|
||||
<div class="c-button-set c-button-set--strip-h"
|
||||
ng-disabled="!plotHistory.length">
|
||||
<button class="c-button icon-arrow-left"
|
||||
ng-click="plot.back()"
|
||||
title="Restore previous pan/zoom">
|
||||
</button>
|
||||
<button class="c-button icon-reset"
|
||||
ng-click="plot.clear()"
|
||||
title="Reset pan/zoom">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Cursor guides-->
|
||||
<div class="c-cursor-guide--v js-cursor-guide--v"
|
||||
ng-show="plot.cursorGuide">
|
||||
</div>
|
||||
<div class="c-cursor-guide--h js-cursor-guide--h"
|
||||
ng-show="plot.cursorGuide">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gl-plot-axis-area gl-plot-x has-local-controls">
|
||||
<mct-ticks axis="xAxis">
|
||||
<div ng-repeat="tick in ticks track by tick.text"
|
||||
class="gl-plot-tick gl-plot-x-tick-label"
|
||||
ng-style="{
|
||||
left: (100 * (tick.value - min) / interval) + '%'
|
||||
}"
|
||||
ng-title=":: tick.fullText || tick.text">
|
||||
{{:: tick.text }}
|
||||
</div>
|
||||
</mct-ticks>
|
||||
|
||||
<div
|
||||
class="gl-plot-label gl-plot-x-label"
|
||||
ng-class="{'icon-gear': isEnabledXKeyToggle()}"
|
||||
>
|
||||
{{ xAxis.get('label') }}
|
||||
</div>
|
||||
|
||||
<select
|
||||
ng-show="plot.isEnabledXKeyToggle()"
|
||||
ng-model="selectedXKeyOption.key"
|
||||
ng-change="plot.toggleXKeyOption('{{selectedXKeyOption.key}}', series[0])"
|
||||
class="gl-plot-x-label__select local-controls--hidden"
|
||||
ng-options="option.key as option.name for option in xKeyOptions"
|
||||
>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
@ -1,148 +0,0 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2021, 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.
|
||||
-->
|
||||
<div ng-controller="PlotOptionsController">
|
||||
<ul class="c-tree">
|
||||
<h2 title="Plot series display properties in this object">Plot Series</h2>
|
||||
<li ng-repeat="series in config.series.models">
|
||||
<div class="c-tree__item menus-to-left">
|
||||
<span class='c-disclosure-triangle is-enabled flex-elem'
|
||||
ng-class="{ 'c-disclosure-triangle--expanded': series.expanded }"
|
||||
ng-click="series.expanded = !series.expanded">
|
||||
</span>
|
||||
<mct-representation
|
||||
class="rep-object-label c-tree__item__label"
|
||||
key="'label'"
|
||||
mct-object="series.oldObject">
|
||||
</mct-representation>
|
||||
</div>
|
||||
<ul class="grid-properties" ng-show="series.expanded">
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="The field to be plotted as a value for this series.">Value</div>
|
||||
<div class="grid-cell value">
|
||||
{{series.get('yKey')}}
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="The rendering method to join lines for this series.">Line Method</div>
|
||||
<div class="grid-cell value">{{ {
|
||||
'none': 'None',
|
||||
'linear': 'Linear interpolation',
|
||||
'stepAfter': 'Step After'
|
||||
}[series.get('interpolate')] }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Whether markers are displayed, and their size.">Markers</div>
|
||||
<div class="grid-cell value">
|
||||
{{ series.markerOptionsDisplayText() }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Display markers visually denoting points in alarm.">Alarm Markers</div>
|
||||
<div class="grid-cell value">
|
||||
{{series.get('alarmMarkers') ? "Enabled" : "Disabled"}}
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="The plot line and marker color for this series.">Color</div>
|
||||
<div class="grid-cell value">
|
||||
<span class="c-color-swatch"
|
||||
ng-style="{
|
||||
'background': series.get('color').asHexString()
|
||||
}">
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li><!-- end repeat -->
|
||||
</ul>
|
||||
<div class="grid-properties">
|
||||
<ul class="l-inspector-part">
|
||||
<h2 title="Y axis settings for this object">Y Axis</h2>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Manually override how the Y axis is labeled.">Label</div>
|
||||
<div class="grid-cell value">{{ config.yAxis.get('label') ? config.yAxis.get('label') : "Not defined" }}</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Automatically scale the Y axis to keep all values in view.">Autoscale</div>
|
||||
<div class="grid-cell value">
|
||||
{{ config.yAxis.get('autoscale') ? "Enabled: " : "Disabled" }}
|
||||
{{ config.yAxis.get('autoscale') ? (config.yAxis.get('autoscalePadding')) : ""}}
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row" ng-if="!form.yAxis.autoscale">
|
||||
<div class="grid-cell label"
|
||||
title="Minimum Y axis value.">Minimum value</div>
|
||||
<div class="grid-cell value">{{ config.yAxis.get('range').min }}</div>
|
||||
</li>
|
||||
<li class="grid-row" ng-if="!form.yAxis.autoscale">
|
||||
<div class="grid-cell label"
|
||||
title="Maximum Y axis value.">Maximum value</div>
|
||||
<div class="grid-cell value">{{ config.yAxis.get('range').max }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="l-inspector-part">
|
||||
<h2 title="Legend settings for this object">Legend</h2>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="The position of the legend relative to the plot display area.">Position</div>
|
||||
<div class="grid-cell value capitalize">{{ config.legend.get('position') }}</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Hide the legend when the plot is small">Hide when plot small</div>
|
||||
<div class="grid-cell value">{{ config.legend.get('hideLegendWhenSmall') ? "Yes" : "No" }}</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Show the legend expanded by default">Expand by Default</div>
|
||||
<div class="grid-cell value">{{ config.legend.get('expandByDefault') ? "Yes" : "No" }}</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="What to display in the legend when it's collapsed.">Show when collapsed:</div>
|
||||
<div class="grid-cell value">{{
|
||||
config.legend.get('valueToShowWhenCollapsed').replace('nearest', '')
|
||||
}}
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="What to display in the legend when it's expanded.">Show when expanded:</div>
|
||||
<div class="grid-cell value comma-list">
|
||||
<span ng-if="config.legend.get('showTimestampWhenExpanded')">Timestamp</span>
|
||||
<span ng-if="config.legend.get('showValueWhenExpanded')">Value</span>
|
||||
<span ng-if="config.legend.get('showMinimumWhenExpanded')">Min</span>
|
||||
<span ng-if="config.legend.get('showMaximumWhenExpanded')">Max</span>
|
||||
<span ng-if="config.legend.get('showUnitsWhenExpanded')">Units</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
@ -1,229 +0,0 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2021, 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.
|
||||
-->
|
||||
<div ng-controller="PlotOptionsController">
|
||||
<ul class="c-tree">
|
||||
<h2 title="Display properties for this object">Plot Series</h2>
|
||||
<li ng-repeat="series in plotSeries"
|
||||
ng-controller="PlotSeriesFormController"
|
||||
form-model="series">
|
||||
<div class="c-tree__item menus-to-left">
|
||||
<span class='c-disclosure-triangle is-enabled flex-elem'
|
||||
ng-class="{ 'c-disclosure-triangle--expanded': expanded }"
|
||||
ng-click="expanded = !expanded">
|
||||
</span>
|
||||
<mct-representation class="rep-object-label c-tree__item__label"
|
||||
key="'label'"
|
||||
mct-object="series.oldObject">
|
||||
</mct-representation>
|
||||
</div>
|
||||
<ul class="grid-properties" ng-show="expanded">
|
||||
<li class="grid-row">
|
||||
<!-- Value to be displayed -->
|
||||
<div class="grid-cell label"
|
||||
title="The field to be plotted as a value for this series.">Value</div>
|
||||
<div class="grid-cell value">
|
||||
<select ng-model="form.yKey">
|
||||
<option ng-repeat="option in yKeyOptions"
|
||||
value="{{option.value}}"
|
||||
ng-selected="option.value == form.yKey">
|
||||
{{option.name}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="The rendering method to join lines for this series.">Line Method</div>
|
||||
<div class="grid-cell value">
|
||||
<select ng-model="form.interpolate">
|
||||
<option value="none">None</option>
|
||||
<option value="linear">Linear interpolate</option>
|
||||
<option value="stepAfter">Step after</option>
|
||||
</select>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Whether markers are displayed.">Markers</div>
|
||||
<div class="grid-cell value">
|
||||
<input type="checkbox" ng-model="form.markers"/>
|
||||
<select
|
||||
ng-show="form.markers"
|
||||
ng-model="form.markerShape">
|
||||
<option
|
||||
ng-repeat="option in markerShapeOptions"
|
||||
value="{{ option.value }}"
|
||||
ng-selected="option.value == form.markerShape"
|
||||
>
|
||||
{{ option.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Display markers visually denoting points in alarm.">Alarm Markers</div>
|
||||
<div class="grid-cell value">
|
||||
<input type="checkbox" ng-model="form.alarmMarkers"/>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row" ng-show="form.markers || form.alarmMarkers">
|
||||
<div class="grid-cell label"
|
||||
title="The size of regular and alarm markers for this series.">Marker Size:</div>
|
||||
<div class="grid-cell value"><input class="c-input--flex" type="text" ng-model="form.markerSize"/></div>
|
||||
</li>
|
||||
<li class="grid-row"
|
||||
ng-controller="ClickAwayController as toggle"
|
||||
ng-show="form.interpolate !== 'none' || form.markers">
|
||||
<div class="grid-cell label"
|
||||
title="Manually set the plot line and marker color for this series.">Color</div>
|
||||
<div class="grid-cell value">
|
||||
<div class="c-click-swatch c-click-swatch--menu" ng-click="toggle.toggle()">
|
||||
<span class="c-color-swatch"
|
||||
ng-style="{ background: series.get('color').asHexString() }">
|
||||
</span>
|
||||
</div>
|
||||
<div class="c-palette c-palette--color">
|
||||
<div class="c-palette__items" ng-show="toggle.isActive()">
|
||||
<div class="u-contents" ng-repeat="group in config.series.palette.groups()">
|
||||
<div class="c-palette__item"
|
||||
ng-repeat="color in group"
|
||||
ng-class="{ 'selected': series.get('color').equalTo(color) }"
|
||||
ng-style="{ background: color.asHexString() }"
|
||||
ng-click="setColor(color)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="grid-properties"
|
||||
ng-show="!!config.series.models.length"
|
||||
ng-controller="PlotYAxisFormController"
|
||||
form-model="config.yAxis">
|
||||
<ul class="l-inspector-part">
|
||||
<h2>Y Axis</h2>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Manually override how the Y axis is labeled.">Label</div>
|
||||
<div class="grid-cell value"><input class="c-input--flex" type="text" ng-model="form.label"/></div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="l-inspector-part">
|
||||
<h2>Y Axis Scaling</h2>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Automatically scale the Y axis to keep all values in view.">Autoscale</div>
|
||||
<div class="grid-cell value"><input type="checkbox" ng-model="form.autoscale"/></div>
|
||||
</li>
|
||||
<li class="grid-row" ng-show="form.autoscale">
|
||||
<div class="grid-cell label"
|
||||
title="Percentage of padding above and below plotted min and max values. 0.1, 1.0, etc.">
|
||||
Padding</div>
|
||||
<div class="grid-cell value">
|
||||
<input class="c-input--flex" type="text" ng-model="form.autoscalePadding"/>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="l-inspector-part" ng-show="!form.autoscale">
|
||||
<div class="grid-span-all form-error"
|
||||
ng-show="!form.autoscale && validation.range">
|
||||
{{ validation.range }}
|
||||
</div>
|
||||
<li class="grid-row force-border">
|
||||
<div class="grid-cell label"
|
||||
title="Minimum Y axis value.">Minimum Value</div>
|
||||
<div class="grid-cell value">
|
||||
<input class="c-input--flex" type="number" ng-model="form.range.min"/>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Maximum Y axis value.">Maximum Value</div>
|
||||
<div class="grid-cell value"><input class="c-input--flex" type="number" ng-model="form.range.max"/></div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="grid-properties" ng-show="!!config.series.models.length">
|
||||
<ul class="l-inspector-part" ng-controller="PlotLegendFormController" form-model="config.legend">
|
||||
<h2 title="Legend options">Legend</h2>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="The position of the legend relative to the plot display area.">Position</div>
|
||||
<div class="grid-cell value">
|
||||
<select ng-model="form.position">
|
||||
<option value="top">Top</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="bottom">Bottom</option>
|
||||
<option value="left">Left</option>
|
||||
</select>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Hide the legend when the plot is small">Hide when plot small</div>
|
||||
<div class="grid-cell value"><input type="checkbox" ng-model="form.hideLegendWhenSmall"/></div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="Show the legend expanded by default">Expand by default</div>
|
||||
<div class="grid-cell value"><input type="checkbox" ng-model="form.expandByDefault"/></div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="What to display in the legend when it's collapsed.">When collapsed show</div>
|
||||
<div class="grid-cell value">
|
||||
<select ng-model="form.valueToShowWhenCollapsed">
|
||||
<option value="none">Nothing</option>
|
||||
<option value="nearestTimestamp">Nearest timestamp</option>
|
||||
<option value="nearestValue">Nearest value</option>
|
||||
<option value="min">Minimum value</option>
|
||||
<option value="max">Maximum value</option>
|
||||
<option value="units">showUnitsWhenExpanded</option>
|
||||
</select>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid-row">
|
||||
<div class="grid-cell label"
|
||||
title="What to display in the legend when it's expanded.">When expanded show</div>
|
||||
<div class="grid-cell value">
|
||||
<ul>
|
||||
<li><input type="checkbox"
|
||||
ng-model="form.showTimestampWhenExpanded"/> Nearest timestamp</li>
|
||||
<li><input type="checkbox"
|
||||
ng-model="form.showValueWhenExpanded"/> Nearest value</li>
|
||||
<li><input type="checkbox"
|
||||
ng-model="form.showMinimumWhenExpanded"/> Minimum value</li>
|
||||
<li><input type="checkbox"
|
||||
ng-model="form.showMaximumWhenExpanded"/> Maximum value</li>
|
||||
<li><input type="checkbox"
|
||||
ng-model="form.showUnitsWhenExpanded"/> Units</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
@ -1,31 +0,0 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2021, 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.
|
||||
-->
|
||||
<div ng-if="!domainObject.model.locked && domainObject.getCapability('editor').inEditContext()">
|
||||
<mct-representation key="'plot-options-edit'"
|
||||
mct-object="domainObject">
|
||||
</mct-representation>
|
||||
</div>
|
||||
<div ng-if="domainObject.model.locked || !domainObject.getCapability('editor').inEditContext()">
|
||||
<mct-representation key="'plot-options-browse'"
|
||||
mct-object="domainObject">
|
||||
</mct-representation>
|
||||
</div>
|
@ -1,58 +0,0 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2021, 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.
|
||||
-->
|
||||
<div ng-controller="PlotController as controller"
|
||||
class="c-plot holder holder-plot has-control-bar">
|
||||
<div class="c-control-bar" ng-show="!controller.hideExportButtons">
|
||||
<span class="c-button-set c-button-set--strip-h">
|
||||
<button class="c-button icon-download"
|
||||
ng-click="controller.exportPNG()"
|
||||
title="Export This View's Data as PNG">
|
||||
<span class="c-button__label">PNG</span>
|
||||
</button>
|
||||
<button class="c-button"
|
||||
ng-click="controller.exportJPG()"
|
||||
title="Export This View's Data as JPG">
|
||||
<span class="c-button__label">JPG</span>
|
||||
</button>
|
||||
</span>
|
||||
<button class="c-button icon-crosshair"
|
||||
ng-class="{ 'is-active': controller.cursorGuide }"
|
||||
ng-click="controller.toggleCursorGuide($event)"
|
||||
title="Toggle cursor guides">
|
||||
</button>
|
||||
<button class="c-button"
|
||||
ng-class="{ 'icon-grid-on': controller.gridLines, 'icon-grid-off': !controller.gridLines }"
|
||||
ng-click="controller.toggleGridLines($event)"
|
||||
title="Toggle grid lines">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="l-view-section u-style-receiver js-style-receiver">
|
||||
<div class="c-loading--overlay loading"
|
||||
ng-show="!!pending"></div>
|
||||
<mct-plot config="controller.config"
|
||||
series="series"
|
||||
the-y-axis="yAxis"
|
||||
the-x-axis="xAxis">
|
||||
</mct-plot>
|
||||
</div>
|
||||
</div>
|
@ -1,65 +0,0 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2021, 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.
|
||||
-->
|
||||
<div ng-controller="StackedPlotController as stackedPlot"
|
||||
class="c-plot c-plot--stacked holder holder-plot has-control-bar">
|
||||
<div class="c-control-bar" ng-show="!stackedPlot.hideExportButtons">
|
||||
<span class="c-button-set c-button-set--strip-h">
|
||||
<button class="c-button icon-download"
|
||||
ng-click="stackedPlot.exportPNG()"
|
||||
title="Export This View's Data as PNG">
|
||||
<span class="c-button__label">PNG</span>
|
||||
</button>
|
||||
<button class="c-button"
|
||||
ng-click="stackedPlot.exportJPG()"
|
||||
title="Export This View's Data as JPG">
|
||||
<span class="c-button__label">JPG</span>
|
||||
</button>
|
||||
</span>
|
||||
<button class="c-button icon-crosshair"
|
||||
ng-class="{ 'is-active': stackedPlot.cursorGuide }"
|
||||
ng-click="stackedPlot.toggleCursorGuide($event)"
|
||||
title="Toggle cursor guides">
|
||||
</button>
|
||||
<button class="c-button"
|
||||
ng-class="{ 'icon-grid-on': stackedPlot.gridLines, 'icon-grid-off': !stackedPlot.gridLines }"
|
||||
ng-click="stackedPlot.toggleGridLines($event)"
|
||||
title="Toggle grid lines">
|
||||
</button>
|
||||
</div>
|
||||
<div class="l-view-section u-style-receiver js-style-receiver">
|
||||
<div class="c-loading--overlay loading"
|
||||
ng-show="!!currentRequest.pending"></div>
|
||||
<div class="gl-plot child-frame u-inspectable"
|
||||
ng-repeat="telemetryObject in telemetryObjects"
|
||||
ng-class="{
|
||||
's-status-timeconductor-unsynced': telemetryObject
|
||||
.getCapability('status')
|
||||
.get('timeconductor-unsynced')
|
||||
}"
|
||||
mct-selectable="{
|
||||
item: telemetryObject.useCapability('adapter'),
|
||||
oldItem: telemetryObject
|
||||
}">
|
||||
<mct-overlay-plot domain-object="telemetryObject"></mct-overlay-plot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,68 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
function () {
|
||||
|
||||
/**
|
||||
* Policy preventing the Plot view from being made available for
|
||||
* domain objects which have non-numeric telemetry.
|
||||
* @implements {Policy.<View, DomainObject>}
|
||||
* @constructor
|
||||
* @memberof platform/features/plot
|
||||
*/
|
||||
function PlotViewPolicy(openmct) {
|
||||
this.openmct = openmct;
|
||||
}
|
||||
|
||||
PlotViewPolicy.prototype.hasNumericTelemetry = function (domainObject) {
|
||||
const adaptedObject = domainObject.useCapability('adapter');
|
||||
|
||||
if (!adaptedObject.telemetry) {
|
||||
return domainObject.hasCapability('delegation')
|
||||
&& domainObject.getCapability('delegation')
|
||||
.doesDelegateCapability('telemetry');
|
||||
}
|
||||
|
||||
const metadata = this.openmct.telemetry.getMetadata(adaptedObject);
|
||||
const rangeValues = metadata.valuesForHints(['range']);
|
||||
if (rangeValues.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !rangeValues.every(function (value) {
|
||||
return value.format === 'string';
|
||||
});
|
||||
};
|
||||
|
||||
PlotViewPolicy.prototype.allow = function (view, domainObject) {
|
||||
if (view.key === 'plot-single') {
|
||||
return this.hasNumericTelemetry(domainObject);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return PlotViewPolicy;
|
||||
}
|
||||
);
|
||||
|
@ -1,74 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'../lib/extend',
|
||||
'../lib/eventHelpers'
|
||||
], function (
|
||||
extend,
|
||||
eventHelpers
|
||||
) {
|
||||
|
||||
function MCTChartAlarmPointSet(series, chart, offset) {
|
||||
this.series = series;
|
||||
this.chart = chart;
|
||||
this.offset = offset;
|
||||
this.points = [];
|
||||
|
||||
this.listenTo(series, 'add', this.append, this);
|
||||
this.listenTo(series, 'remove', this.remove, this);
|
||||
this.listenTo(series, 'reset', this.reset, this);
|
||||
this.listenTo(series, 'destroy', this.destroy, this);
|
||||
series.data.forEach(function (point, index) {
|
||||
this.append(point, index, series);
|
||||
}, this);
|
||||
}
|
||||
|
||||
MCTChartAlarmPointSet.prototype.append = function (datum) {
|
||||
if (datum.mctLimitState) {
|
||||
this.points.push({
|
||||
x: this.offset.xVal(datum, this.series),
|
||||
y: this.offset.yVal(datum, this.series),
|
||||
datum: datum
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
MCTChartAlarmPointSet.prototype.remove = function (datum) {
|
||||
this.points = this.points.filter(function (p) {
|
||||
return p.datum !== datum;
|
||||
});
|
||||
};
|
||||
|
||||
MCTChartAlarmPointSet.prototype.reset = function () {
|
||||
this.points = [];
|
||||
};
|
||||
|
||||
MCTChartAlarmPointSet.prototype.destroy = function () {
|
||||
this.stopListening();
|
||||
};
|
||||
|
||||
eventHelpers.extend(MCTChartAlarmPointSet.prototype);
|
||||
|
||||
return MCTChartAlarmPointSet;
|
||||
|
||||
});
|
@ -1,441 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Module defining MCTChart. Created by vwoeltje on 11/12/14.
|
||||
*/
|
||||
define([
|
||||
'./MCTChartLineLinear',
|
||||
'./MCTChartLineStepAfter',
|
||||
'./MCTChartPointSet',
|
||||
'./MCTChartAlarmPointSet',
|
||||
'../draw/DrawLoader',
|
||||
'../lib/eventHelpers'
|
||||
],
|
||||
function (
|
||||
MCTChartLineLinear,
|
||||
MCTChartLineStepAfter,
|
||||
MCTChartPointSet,
|
||||
MCTChartAlarmPointSet,
|
||||
DrawLoader,
|
||||
eventHelpers
|
||||
) {
|
||||
const MARKER_SIZE = 6.0;
|
||||
const HIGHLIGHT_SIZE = MARKER_SIZE * 2.0;
|
||||
|
||||
/**
|
||||
* Offsetter adjusts x and y values by a fixed amount,
|
||||
* generally increasing the precision of the 32 bit float representation
|
||||
* required for plotting.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function MCTChartController($scope) {
|
||||
this.$onInit = () => {
|
||||
this.$scope = $scope;
|
||||
this.isDestroyed = false;
|
||||
this.lines = [];
|
||||
this.pointSets = [];
|
||||
this.alarmSets = [];
|
||||
this.offset = {};
|
||||
this.config = $scope.config;
|
||||
this.listenTo(this.$scope, '$destroy', this.destroy, this);
|
||||
this.draw = this.draw.bind(this);
|
||||
this.scheduleDraw = this.scheduleDraw.bind(this);
|
||||
this.seriesElements = new WeakMap();
|
||||
|
||||
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
|
||||
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this);
|
||||
this.listenTo(this.config.yAxis, 'change:key', this.clearOffset, this);
|
||||
this.listenTo(this.config.yAxis, 'change', this.scheduleDraw);
|
||||
this.listenTo(this.config.xAxis, 'change', this.scheduleDraw);
|
||||
this.$scope.$watch('highlights', this.scheduleDraw);
|
||||
this.$scope.$watch('rectangles', this.scheduleDraw);
|
||||
this.config.series.forEach(this.onSeriesAdd, this);
|
||||
};
|
||||
}
|
||||
|
||||
eventHelpers.extend(MCTChartController.prototype);
|
||||
|
||||
MCTChartController.$inject = ['$scope'];
|
||||
|
||||
MCTChartController.prototype.reDraw = function (mode, o, series) {
|
||||
this.changeInterpolate(mode, o, series);
|
||||
this.changeMarkers(mode, o, series);
|
||||
this.changeAlarmMarkers(mode, o, series);
|
||||
};
|
||||
|
||||
MCTChartController.prototype.onSeriesAdd = function (series) {
|
||||
this.listenTo(series, 'change:xKey', this.reDraw, this);
|
||||
this.listenTo(series, 'change:interpolate', this.changeInterpolate, this);
|
||||
this.listenTo(series, 'change:markers', this.changeMarkers, this);
|
||||
this.listenTo(series, 'change:alarmMarkers', this.changeAlarmMarkers, this);
|
||||
this.listenTo(series, 'change', this.scheduleDraw);
|
||||
this.listenTo(series, 'add', this.scheduleDraw);
|
||||
this.makeChartElement(series);
|
||||
};
|
||||
|
||||
MCTChartController.prototype.changeInterpolate = function (mode, o, series) {
|
||||
if (mode === o) {
|
||||
return;
|
||||
}
|
||||
|
||||
const elements = this.seriesElements.get(series);
|
||||
elements.lines.forEach(function (line) {
|
||||
this.lines.splice(this.lines.indexOf(line), 1);
|
||||
line.destroy();
|
||||
}, this);
|
||||
elements.lines = [];
|
||||
|
||||
const newLine = this.lineForSeries(series);
|
||||
if (newLine) {
|
||||
elements.lines.push(newLine);
|
||||
this.lines.push(newLine);
|
||||
}
|
||||
};
|
||||
|
||||
MCTChartController.prototype.changeAlarmMarkers = function (mode, o, series) {
|
||||
if (mode === o) {
|
||||
return;
|
||||
}
|
||||
|
||||
const elements = this.seriesElements.get(series);
|
||||
if (elements.alarmSet) {
|
||||
elements.alarmSet.destroy();
|
||||
this.alarmSets.splice(this.alarmSets.indexOf(elements.alarmSet), 1);
|
||||
}
|
||||
|
||||
elements.alarmSet = this.alarmPointSetForSeries(series);
|
||||
if (elements.alarmSet) {
|
||||
this.alarmSets.push(elements.alarmSet);
|
||||
}
|
||||
};
|
||||
|
||||
MCTChartController.prototype.changeMarkers = function (mode, o, series) {
|
||||
if (mode === o) {
|
||||
return;
|
||||
}
|
||||
|
||||
const elements = this.seriesElements.get(series);
|
||||
elements.pointSets.forEach(function (pointSet) {
|
||||
this.pointSets.splice(this.pointSets.indexOf(pointSet), 1);
|
||||
pointSet.destroy();
|
||||
}, this);
|
||||
elements.pointSets = [];
|
||||
|
||||
const pointSet = this.pointSetForSeries(series);
|
||||
if (pointSet) {
|
||||
elements.pointSets.push(pointSet);
|
||||
this.pointSets.push(pointSet);
|
||||
}
|
||||
};
|
||||
|
||||
MCTChartController.prototype.onSeriesRemove = function (series) {
|
||||
this.stopListening(series);
|
||||
this.removeChartElement(series);
|
||||
this.scheduleDraw();
|
||||
};
|
||||
|
||||
MCTChartController.prototype.destroy = function () {
|
||||
this.isDestroyed = true;
|
||||
this.stopListening();
|
||||
this.lines.forEach(line => line.destroy());
|
||||
DrawLoader.releaseDrawAPI(this.drawAPI);
|
||||
};
|
||||
|
||||
MCTChartController.prototype.clearOffset = function () {
|
||||
delete this.offset.x;
|
||||
delete this.offset.y;
|
||||
delete this.offset.xVal;
|
||||
delete this.offset.yVal;
|
||||
delete this.offset.xKey;
|
||||
delete this.offset.yKey;
|
||||
this.lines.forEach(function (line) {
|
||||
line.reset();
|
||||
});
|
||||
this.pointSets.forEach(function (pointSet) {
|
||||
pointSet.reset();
|
||||
});
|
||||
};
|
||||
|
||||
MCTChartController.prototype.setOffset = function (offsetPoint, index, series) {
|
||||
if (this.offset.x && this.offset.y) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offsets = {
|
||||
x: series.getXVal(offsetPoint),
|
||||
y: series.getYVal(offsetPoint)
|
||||
};
|
||||
|
||||
this.offset.x = function (x) {
|
||||
return x - offsets.x;
|
||||
}.bind(this);
|
||||
this.offset.y = function (y) {
|
||||
return y - offsets.y;
|
||||
}.bind(this);
|
||||
this.offset.xVal = function (point, pSeries) {
|
||||
return this.offset.x(pSeries.getXVal(point));
|
||||
}.bind(this);
|
||||
this.offset.yVal = function (point, pSeries) {
|
||||
return this.offset.y(pSeries.getYVal(point));
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
MCTChartController.prototype.initializeCanvas = function (canvas, overlay) {
|
||||
this.canvas = canvas;
|
||||
this.overlay = overlay;
|
||||
this.drawAPI = DrawLoader.getDrawAPI(canvas, overlay);
|
||||
if (this.drawAPI) {
|
||||
this.listenTo(this.drawAPI, 'error', this.fallbackToCanvas, this);
|
||||
}
|
||||
|
||||
return Boolean(this.drawAPI);
|
||||
};
|
||||
|
||||
MCTChartController.prototype.fallbackToCanvas = function () {
|
||||
this.stopListening(this.drawAPI);
|
||||
DrawLoader.releaseDrawAPI(this.drawAPI);
|
||||
// Have to throw away the old canvas elements and replace with new
|
||||
// canvas elements in order to get new drawing contexts.
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = this.TEMPLATE;
|
||||
const mainCanvas = div.querySelectorAll("canvas")[1];
|
||||
const overlayCanvas = div.querySelectorAll("canvas")[0];
|
||||
this.canvas.parentNode.replaceChild(mainCanvas, this.canvas);
|
||||
this.canvas = mainCanvas;
|
||||
this.overlay.parentNode.replaceChild(overlayCanvas, this.overlay);
|
||||
this.overlay = overlayCanvas;
|
||||
this.drawAPI = DrawLoader.getFallbackDrawAPI(this.canvas, this.overlay);
|
||||
this.$scope.$emit('plot:reinitializeCanvas');
|
||||
};
|
||||
|
||||
MCTChartController.prototype.removeChartElement = function (series) {
|
||||
const elements = this.seriesElements.get(series);
|
||||
|
||||
elements.lines.forEach(function (line) {
|
||||
this.lines.splice(this.lines.indexOf(line), 1);
|
||||
line.destroy();
|
||||
}, this);
|
||||
elements.pointSets.forEach(function (pointSet) {
|
||||
this.pointSets.splice(this.pointSets.indexOf(pointSet), 1);
|
||||
pointSet.destroy();
|
||||
}, this);
|
||||
if (elements.alarmSet) {
|
||||
elements.alarmSet.destroy();
|
||||
this.alarmSets.splice(this.alarmSets.indexOf(elements.alarmSet), 1);
|
||||
}
|
||||
|
||||
this.seriesElements.delete(series);
|
||||
};
|
||||
|
||||
MCTChartController.prototype.lineForSeries = function (series) {
|
||||
if (series.get('interpolate') === 'linear') {
|
||||
return new MCTChartLineLinear(
|
||||
series,
|
||||
this,
|
||||
this.offset
|
||||
);
|
||||
}
|
||||
|
||||
if (series.get('interpolate') === 'stepAfter') {
|
||||
return new MCTChartLineStepAfter(
|
||||
series,
|
||||
this,
|
||||
this.offset
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
MCTChartController.prototype.pointSetForSeries = function (series) {
|
||||
if (series.get('markers')) {
|
||||
return new MCTChartPointSet(
|
||||
series,
|
||||
this,
|
||||
this.offset
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
MCTChartController.prototype.alarmPointSetForSeries = function (series) {
|
||||
if (series.get('alarmMarkers')) {
|
||||
return new MCTChartAlarmPointSet(
|
||||
series,
|
||||
this,
|
||||
this.offset
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
MCTChartController.prototype.makeChartElement = function (series) {
|
||||
const elements = {
|
||||
lines: [],
|
||||
pointSets: []
|
||||
};
|
||||
|
||||
const line = this.lineForSeries(series);
|
||||
if (line) {
|
||||
elements.lines.push(line);
|
||||
this.lines.push(line);
|
||||
}
|
||||
|
||||
const pointSet = this.pointSetForSeries(series);
|
||||
if (pointSet) {
|
||||
elements.pointSets.push(pointSet);
|
||||
this.pointSets.push(pointSet);
|
||||
}
|
||||
|
||||
elements.alarmSet = this.alarmPointSetForSeries(series);
|
||||
if (elements.alarmSet) {
|
||||
this.alarmSets.push(elements.alarmSet);
|
||||
}
|
||||
|
||||
this.seriesElements.set(series, elements);
|
||||
};
|
||||
|
||||
MCTChartController.prototype.canDraw = function () {
|
||||
if (!this.offset.x || !this.offset.y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
MCTChartController.prototype.scheduleDraw = function () {
|
||||
if (!this.drawScheduled) {
|
||||
requestAnimationFrame(this.draw);
|
||||
this.drawScheduled = true;
|
||||
}
|
||||
};
|
||||
|
||||
MCTChartController.prototype.draw = function () {
|
||||
this.drawScheduled = false;
|
||||
if (this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.drawAPI.clear();
|
||||
if (this.canDraw()) {
|
||||
this.updateViewport();
|
||||
this.drawSeries();
|
||||
this.drawRectangles();
|
||||
this.drawHighlights();
|
||||
}
|
||||
};
|
||||
|
||||
MCTChartController.prototype.updateViewport = function () {
|
||||
const xRange = this.config.xAxis.get('displayRange');
|
||||
const yRange = this.config.yAxis.get('displayRange');
|
||||
|
||||
if (!xRange || !yRange) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dimensions = [
|
||||
xRange.max - xRange.min,
|
||||
yRange.max - yRange.min
|
||||
];
|
||||
|
||||
const origin = [
|
||||
this.offset.x(xRange.min),
|
||||
this.offset.y(yRange.min)
|
||||
];
|
||||
|
||||
this.drawAPI.setDimensions(
|
||||
dimensions,
|
||||
origin
|
||||
);
|
||||
};
|
||||
|
||||
MCTChartController.prototype.drawSeries = function () {
|
||||
this.lines.forEach(this.drawLine, this);
|
||||
this.pointSets.forEach(this.drawPoints, this);
|
||||
this.alarmSets.forEach(this.drawAlarmPoints, this);
|
||||
};
|
||||
|
||||
MCTChartController.prototype.drawAlarmPoints = function (alarmSet) {
|
||||
this.drawAPI.drawLimitPoints(
|
||||
alarmSet.points,
|
||||
alarmSet.series.get('color').asRGBAArray(),
|
||||
alarmSet.series.get('markerSize')
|
||||
);
|
||||
};
|
||||
|
||||
MCTChartController.prototype.drawPoints = function (chartElement) {
|
||||
this.drawAPI.drawPoints(
|
||||
chartElement.getBuffer(),
|
||||
chartElement.color().asRGBAArray(),
|
||||
chartElement.count,
|
||||
chartElement.series.get('markerSize'),
|
||||
chartElement.series.get('markerShape')
|
||||
);
|
||||
};
|
||||
|
||||
MCTChartController.prototype.drawLine = function (chartElement) {
|
||||
this.drawAPI.drawLine(
|
||||
chartElement.getBuffer(),
|
||||
chartElement.color().asRGBAArray(),
|
||||
chartElement.count
|
||||
);
|
||||
};
|
||||
|
||||
MCTChartController.prototype.drawHighlights = function () {
|
||||
if (this.$scope.highlights && this.$scope.highlights.length) {
|
||||
this.$scope.highlights.forEach(this.drawHighlight, this);
|
||||
}
|
||||
};
|
||||
|
||||
MCTChartController.prototype.drawHighlight = function (highlight) {
|
||||
const points = new Float32Array([
|
||||
this.offset.xVal(highlight.point, highlight.series),
|
||||
this.offset.yVal(highlight.point, highlight.series)
|
||||
]);
|
||||
|
||||
const color = highlight.series.get('color').asRGBAArray();
|
||||
const pointCount = 1;
|
||||
const shape = highlight.series.get('markerShape');
|
||||
|
||||
this.drawAPI.drawPoints(points, color, pointCount, HIGHLIGHT_SIZE, shape);
|
||||
};
|
||||
|
||||
MCTChartController.prototype.drawRectangles = function () {
|
||||
if (this.$scope.rectangles) {
|
||||
this.$scope.rectangles.forEach(this.drawRectangle, this);
|
||||
}
|
||||
};
|
||||
|
||||
MCTChartController.prototype.drawRectangle = function (rect) {
|
||||
this.drawAPI.drawSquare(
|
||||
[
|
||||
this.offset.x(rect.start.x),
|
||||
this.offset.y(rect.start.y)
|
||||
],
|
||||
[
|
||||
this.offset.x(rect.end.x),
|
||||
this.offset.y(rect.end.y)
|
||||
],
|
||||
rect.color
|
||||
);
|
||||
};
|
||||
|
||||
return MCTChartController;
|
||||
});
|
@ -1,67 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Module defining MCTChart. Created by vwoeltje on 11/12/14.
|
||||
*/
|
||||
define([
|
||||
'./MCTChartController'
|
||||
], function (
|
||||
MCTChartController
|
||||
) {
|
||||
|
||||
let TEMPLATE = "<canvas style='position: absolute; background: none; width: 100%; height: 100%;'></canvas>";
|
||||
TEMPLATE += TEMPLATE;
|
||||
|
||||
/**
|
||||
* MCTChart draws charts utilizing a drawAPI.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function MCTChart() {
|
||||
return {
|
||||
restrict: "E",
|
||||
template: TEMPLATE,
|
||||
link: function ($scope, $element, attrs, ctrl) {
|
||||
ctrl.TEMPLATE = TEMPLATE;
|
||||
const mainCanvas = $element.find("canvas")[1];
|
||||
const overlayCanvas = $element.find("canvas")[0];
|
||||
|
||||
if (ctrl.initializeCanvas(mainCanvas, overlayCanvas)) {
|
||||
ctrl.draw();
|
||||
}
|
||||
},
|
||||
controller: MCTChartController,
|
||||
scope: {
|
||||
config: "=",
|
||||
draw: "=",
|
||||
rectangles: "=",
|
||||
series: "=",
|
||||
xAxis: "=theXAxis",
|
||||
yAxis: "=theYAxis",
|
||||
highlights: "=?"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return MCTChart;
|
||||
});
|
@ -1,39 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./MCTChartSeriesElement'
|
||||
], function (
|
||||
MCTChartSeriesElement
|
||||
) {
|
||||
|
||||
const MCTChartLineLinear = MCTChartSeriesElement.extend({
|
||||
addPoint: function (point, start, count) {
|
||||
this.buffer[start] = point.x;
|
||||
this.buffer[start + 1] = point.y;
|
||||
}
|
||||
});
|
||||
|
||||
return MCTChartLineLinear;
|
||||
|
||||
});
|
||||
|
@ -1,79 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./MCTChartSeriesElement'
|
||||
], function (
|
||||
MCTChartSeriesElement
|
||||
) {
|
||||
|
||||
const MCTChartLineStepAfter = MCTChartSeriesElement.extend({
|
||||
removePoint: function (point, index, count) {
|
||||
if (index > 0 && index / 2 < this.count) {
|
||||
this.buffer[index + 1] = this.buffer[index - 1];
|
||||
}
|
||||
},
|
||||
vertexCountForPointAtIndex: function (index) {
|
||||
if (index === 0 && this.count === 0) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 4;
|
||||
},
|
||||
startIndexForPointAtIndex: function (index) {
|
||||
if (index === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 2 + ((index - 1) * 4);
|
||||
},
|
||||
addPoint: function (point, start, count) {
|
||||
if (start === 0 && this.count === 0) {
|
||||
// First point is easy.
|
||||
this.buffer[start] = point.x;
|
||||
this.buffer[start + 1] = point.y; // one point
|
||||
} else if (start === 0 && this.count > 0) {
|
||||
// Unshifting requires adding an extra point.
|
||||
this.buffer[start] = point.x;
|
||||
this.buffer[start + 1] = point.y;
|
||||
this.buffer[start + 2] = this.buffer[start + 4];
|
||||
this.buffer[start + 3] = point.y;
|
||||
} else {
|
||||
// Appending anywhere in line, insert standard two points.
|
||||
this.buffer[start] = point.x;
|
||||
this.buffer[start + 1] = this.buffer[start - 1];
|
||||
this.buffer[start + 2] = point.x;
|
||||
this.buffer[start + 3] = point.y;
|
||||
|
||||
if (start < this.count * 2) {
|
||||
// Insert into the middle, need to update the following
|
||||
// point.
|
||||
this.buffer[start + 5] = point.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return MCTChartLineStepAfter;
|
||||
|
||||
});
|
||||
|
@ -1,39 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./MCTChartSeriesElement'
|
||||
], function (
|
||||
MCTChartSeriesElement
|
||||
) {
|
||||
|
||||
const MCTChartPointSet = MCTChartSeriesElement.extend({
|
||||
addPoint: function (point, start, count) {
|
||||
this.buffer[start] = point.x;
|
||||
this.buffer[start + 1] = point.y;
|
||||
}
|
||||
});
|
||||
|
||||
return MCTChartPointSet;
|
||||
|
||||
});
|
||||
|
@ -1,164 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'../lib/extend',
|
||||
'../lib/eventHelpers'
|
||||
], function (
|
||||
extend,
|
||||
eventHelpers
|
||||
) {
|
||||
|
||||
function MCTChartSeriesElement(series, chart, offset) {
|
||||
this.series = series;
|
||||
this.chart = chart;
|
||||
this.offset = offset;
|
||||
this.buffer = new Float32Array(20000);
|
||||
this.count = 0;
|
||||
this.listenTo(series, 'add', this.append, this);
|
||||
this.listenTo(series, 'remove', this.remove, this);
|
||||
this.listenTo(series, 'reset', this.reset, this);
|
||||
this.listenTo(series, 'destroy', this.destroy, this);
|
||||
series.data.forEach(function (point, index) {
|
||||
this.append(point, index, series);
|
||||
}, this);
|
||||
}
|
||||
|
||||
MCTChartSeriesElement.extend = extend;
|
||||
|
||||
eventHelpers.extend(MCTChartSeriesElement.prototype);
|
||||
|
||||
MCTChartSeriesElement.prototype.getBuffer = function () {
|
||||
if (this.isTempBuffer) {
|
||||
this.buffer = new Float32Array(this.buffer);
|
||||
this.isTempBuffer = false;
|
||||
}
|
||||
|
||||
return this.buffer;
|
||||
};
|
||||
|
||||
MCTChartSeriesElement.prototype.color = function () {
|
||||
return this.series.get('color');
|
||||
};
|
||||
|
||||
MCTChartSeriesElement.prototype.vertexCountForPointAtIndex = function (index) {
|
||||
return 2;
|
||||
};
|
||||
|
||||
MCTChartSeriesElement.prototype.startIndexForPointAtIndex = function (index) {
|
||||
return 2 * index;
|
||||
};
|
||||
|
||||
MCTChartSeriesElement.prototype.removeSegments = function (index, count) {
|
||||
const target = index;
|
||||
const start = index + count;
|
||||
const end = this.count * 2;
|
||||
this.buffer.copyWithin(target, start, end);
|
||||
for (let zero = end - count; zero < end; zero++) {
|
||||
this.buffer[zero] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
MCTChartSeriesElement.prototype.removePoint = function (point, index, count) {
|
||||
// by default, do nothing.
|
||||
};
|
||||
|
||||
MCTChartSeriesElement.prototype.remove = function (point, index, series) {
|
||||
const vertexCount = this.vertexCountForPointAtIndex(index);
|
||||
const removalPoint = this.startIndexForPointAtIndex(index);
|
||||
|
||||
this.removeSegments(removalPoint, vertexCount);
|
||||
|
||||
this.removePoint(
|
||||
this.makePoint(point, series),
|
||||
removalPoint,
|
||||
vertexCount
|
||||
);
|
||||
this.count -= (vertexCount / 2);
|
||||
};
|
||||
|
||||
MCTChartSeriesElement.prototype.makePoint = function (point, series) {
|
||||
if (!this.offset.xVal) {
|
||||
this.chart.setOffset(point, undefined, series);
|
||||
}
|
||||
|
||||
return {
|
||||
x: this.offset.xVal(point, series),
|
||||
y: this.offset.yVal(point, series)
|
||||
};
|
||||
};
|
||||
|
||||
MCTChartSeriesElement.prototype.append = function (point, index, series) {
|
||||
const pointsRequired = this.vertexCountForPointAtIndex(index);
|
||||
const insertionPoint = this.startIndexForPointAtIndex(index);
|
||||
this.growIfNeeded(pointsRequired);
|
||||
this.makeInsertionPoint(insertionPoint, pointsRequired);
|
||||
this.addPoint(
|
||||
this.makePoint(point, series),
|
||||
insertionPoint,
|
||||
pointsRequired
|
||||
);
|
||||
this.count += (pointsRequired / 2);
|
||||
};
|
||||
|
||||
MCTChartSeriesElement.prototype.makeInsertionPoint = function (insertionPoint, pointsRequired) {
|
||||
if (this.count * 2 > insertionPoint) {
|
||||
if (!this.isTempBuffer) {
|
||||
this.buffer = Array.prototype.slice.apply(this.buffer);
|
||||
this.isTempBuffer = true;
|
||||
}
|
||||
|
||||
const target = insertionPoint + pointsRequired;
|
||||
let start = insertionPoint;
|
||||
for (; start < target; start++) {
|
||||
this.buffer.splice(start, 0, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
MCTChartSeriesElement.prototype.reset = function () {
|
||||
this.buffer = new Float32Array(20000);
|
||||
this.count = 0;
|
||||
if (this.offset.x) {
|
||||
this.series.data.forEach(function (point, index) {
|
||||
this.append(point, index, this.series);
|
||||
}, this);
|
||||
}
|
||||
};
|
||||
|
||||
MCTChartSeriesElement.prototype.growIfNeeded = function (pointsRequired) {
|
||||
const remainingPoints = this.buffer.length - this.count * 2;
|
||||
let temp;
|
||||
|
||||
if (remainingPoints <= pointsRequired) {
|
||||
temp = new Float32Array(this.buffer.length + 20000);
|
||||
temp.set(this.buffer);
|
||||
this.buffer = temp;
|
||||
}
|
||||
};
|
||||
|
||||
MCTChartSeriesElement.prototype.destroy = function () {
|
||||
this.stopListening();
|
||||
};
|
||||
|
||||
return MCTChartSeriesElement;
|
||||
});
|
@ -1,130 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'EventEmitter',
|
||||
'./Model',
|
||||
'../lib/extend',
|
||||
'../lib/eventHelpers'
|
||||
], function (
|
||||
EventEmitter,
|
||||
Model,
|
||||
extend,
|
||||
eventHelpers
|
||||
) {
|
||||
|
||||
function Collection(options) {
|
||||
if (options.models) {
|
||||
this.models = options.models.map(this.modelFn, this);
|
||||
} else {
|
||||
this.models = [];
|
||||
}
|
||||
|
||||
this.initialize(options);
|
||||
}
|
||||
|
||||
Object.assign(Collection.prototype, EventEmitter.prototype);
|
||||
eventHelpers.extend(Collection.prototype);
|
||||
|
||||
Collection.extend = extend;
|
||||
|
||||
Collection.prototype.initialize = function (options) {
|
||||
|
||||
};
|
||||
|
||||
Collection.prototype.modelClass = Model;
|
||||
|
||||
Collection.prototype.modelFn = function (model) {
|
||||
if (model instanceof this.modelClass) {
|
||||
model.collection = this;
|
||||
|
||||
return model;
|
||||
|
||||
}
|
||||
|
||||
return new this.modelClass({
|
||||
collection: this,
|
||||
model: model
|
||||
});
|
||||
};
|
||||
|
||||
Collection.prototype.first = function () {
|
||||
return this.at(0);
|
||||
};
|
||||
|
||||
Collection.prototype.forEach = function (iteree, context) {
|
||||
this.models.forEach(iteree, context);
|
||||
};
|
||||
|
||||
Collection.prototype.map = function (iteree, context) {
|
||||
return this.models.map(iteree, context);
|
||||
};
|
||||
|
||||
Collection.prototype.filter = function (iteree, context) {
|
||||
return this.models.filter(iteree, context);
|
||||
};
|
||||
|
||||
Collection.prototype.size = function () {
|
||||
return this.models.length;
|
||||
};
|
||||
|
||||
Collection.prototype.at = function (index) {
|
||||
return this.models[index];
|
||||
};
|
||||
|
||||
Collection.prototype.add = function (model) {
|
||||
model = this.modelFn(model);
|
||||
const index = this.models.length;
|
||||
this.models.push(model);
|
||||
this.emit('add', model, index);
|
||||
};
|
||||
|
||||
Collection.prototype.insert = function (model, index) {
|
||||
model = this.modelFn(model);
|
||||
this.models.splice(index, 0, model);
|
||||
this.emit('add', model, index + 1);
|
||||
};
|
||||
|
||||
Collection.prototype.indexOf = function (model) {
|
||||
return this.models.findIndex(m => m === model);
|
||||
};
|
||||
|
||||
Collection.prototype.remove = function (model) {
|
||||
const index = this.indexOf(model);
|
||||
|
||||
if (index === -1) {
|
||||
throw new Error('model not found in collection.');
|
||||
}
|
||||
|
||||
this.emit('remove', model, index);
|
||||
this.models.splice(index, 1);
|
||||
};
|
||||
|
||||
Collection.prototype.destroy = function (model) {
|
||||
this.forEach(function (m) {
|
||||
m.destroy();
|
||||
});
|
||||
this.stopListening();
|
||||
};
|
||||
|
||||
return Collection;
|
||||
});
|
@ -1,63 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
define([
|
||||
'./Model'
|
||||
], function (
|
||||
Model
|
||||
) {
|
||||
|
||||
/**
|
||||
* TODO: doc strings.
|
||||
*/
|
||||
const LegendModel = Model.extend({
|
||||
listenToSeriesCollection: function (seriesCollection) {
|
||||
this.seriesCollection = seriesCollection;
|
||||
this.listenTo(this.seriesCollection, 'add', this.setHeight, this);
|
||||
this.listenTo(this.seriesCollection, 'remove', this.setHeight, this);
|
||||
this.listenTo(this, 'change:expanded', this.setHeight, this);
|
||||
this.set('expanded', this.get('expandByDefault'));
|
||||
},
|
||||
setHeight: function () {
|
||||
const expanded = this.get('expanded');
|
||||
if (this.get('position') !== 'top') {
|
||||
this.set('height', '0px');
|
||||
} else {
|
||||
this.set('height', expanded ? (20 * (this.seriesCollection.size() + 1) + 40) + 'px' : '21px');
|
||||
}
|
||||
},
|
||||
defaults: function (options) {
|
||||
return {
|
||||
position: 'top',
|
||||
expandByDefault: false,
|
||||
hideLegendWhenSmall: false,
|
||||
valueToShowWhenCollapsed: 'nearestValue',
|
||||
showTimestampWhenExpanded: true,
|
||||
showValueWhenExpanded: true,
|
||||
showMaximumWhenExpanded: true,
|
||||
showMinimumWhenExpanded: true,
|
||||
showUnitsWhenExpanded: true
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return LegendModel;
|
||||
});
|
@ -1,103 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'lodash',
|
||||
'EventEmitter',
|
||||
'../lib/extend',
|
||||
'../lib/eventHelpers'
|
||||
], function (
|
||||
_,
|
||||
EventEmitter,
|
||||
extend,
|
||||
eventHelpers
|
||||
) {
|
||||
|
||||
function Model(options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
this.id = options.id;
|
||||
this.model = options.model;
|
||||
this.collection = options.collection;
|
||||
const defaults = this.defaults(options);
|
||||
if (!this.model) {
|
||||
this.model = options.model = defaults;
|
||||
} else {
|
||||
_.defaultsDeep(this.model, defaults);
|
||||
}
|
||||
|
||||
this.initialize(options);
|
||||
}
|
||||
|
||||
Object.assign(Model.prototype, EventEmitter.prototype);
|
||||
eventHelpers.extend(Model.prototype);
|
||||
|
||||
Model.extend = extend;
|
||||
|
||||
Model.prototype.idAttr = 'id';
|
||||
|
||||
Model.prototype.defaults = function (options) {
|
||||
return {};
|
||||
};
|
||||
|
||||
Model.prototype.initialize = function (model) {
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the model, removing all listeners and subscriptions.
|
||||
*/
|
||||
Model.prototype.destroy = function () {
|
||||
this.emit('destroy');
|
||||
this.removeAllListeners();
|
||||
};
|
||||
|
||||
Model.prototype.id = function () {
|
||||
return this.get(this.idAttr);
|
||||
};
|
||||
|
||||
Model.prototype.get = function (attribute) {
|
||||
return this.model[attribute];
|
||||
};
|
||||
|
||||
Model.prototype.has = function (attribute) {
|
||||
return _.has(this.model, attribute);
|
||||
};
|
||||
|
||||
Model.prototype.set = function (attribute, value) {
|
||||
const oldValue = this.model[attribute];
|
||||
this.model[attribute] = value;
|
||||
this.emit('change', attribute, value, oldValue, this);
|
||||
this.emit('change:' + attribute, value, oldValue, this);
|
||||
};
|
||||
|
||||
Model.prototype.unset = function (attribute) {
|
||||
const oldValue = this.model[attribute];
|
||||
delete this.model[attribute];
|
||||
this.emit('change', attribute, undefined, oldValue, this);
|
||||
this.emit('change:' + attribute, undefined, oldValue, this);
|
||||
};
|
||||
|
||||
return Model;
|
||||
});
|
@ -1,152 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./Collection',
|
||||
'./Model',
|
||||
'./SeriesCollection',
|
||||
'./XAxisModel',
|
||||
'./YAxisModel',
|
||||
'./LegendModel',
|
||||
'lodash'
|
||||
], function (
|
||||
Collection,
|
||||
Model,
|
||||
SeriesCollection,
|
||||
XAxisModel,
|
||||
YAxisModel,
|
||||
LegendModel,
|
||||
_
|
||||
) {
|
||||
|
||||
/**
|
||||
* PlotConfiguration model stores the configuration of a plot and some
|
||||
* limited state. The indiidual parts of the plot configuration model
|
||||
* handle setting defaults and updating in response to various changes.
|
||||
*
|
||||
*/
|
||||
const PlotConfigurationModel = Model.extend({
|
||||
|
||||
/**
|
||||
* Initializes all sub models and then passes references to submodels
|
||||
* to those that need it.
|
||||
*/
|
||||
initialize: function (options) {
|
||||
this.openmct = options.openmct;
|
||||
|
||||
this.xAxis = new XAxisModel({
|
||||
model: options.model.xAxis,
|
||||
plot: this,
|
||||
openmct: options.openmct
|
||||
});
|
||||
this.yAxis = new YAxisModel({
|
||||
model: options.model.yAxis,
|
||||
plot: this,
|
||||
openmct: options.openmct
|
||||
});
|
||||
this.legend = new LegendModel({
|
||||
model: options.model.legend,
|
||||
plot: this,
|
||||
openmct: options.openmct
|
||||
});
|
||||
this.series = new SeriesCollection({
|
||||
models: options.model.series,
|
||||
plot: this,
|
||||
openmct: options.openmct
|
||||
});
|
||||
|
||||
if (this.get('domainObject').type === 'telemetry.plot.overlay') {
|
||||
this.removeMutationListener = this.openmct.objects.observe(
|
||||
this.get('domainObject'),
|
||||
'*',
|
||||
this.updateDomainObject.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
this.yAxis.listenToSeriesCollection(this.series);
|
||||
this.legend.listenToSeriesCollection(this.series);
|
||||
|
||||
this.listenTo(this, 'destroy', this.onDestroy, this);
|
||||
},
|
||||
/**
|
||||
* Retrieve the persisted series config for a given identifier.
|
||||
*/
|
||||
getPersistedSeriesConfig: function (identifier) {
|
||||
const domainObject = this.get('domainObject');
|
||||
if (!domainObject.configuration || !domainObject.configuration.series) {
|
||||
return;
|
||||
}
|
||||
|
||||
return domainObject.configuration.series.filter(function (seriesConfig) {
|
||||
return seriesConfig.identifier.key === identifier.key
|
||||
&& seriesConfig.identifier.namespace === identifier.namespace;
|
||||
})[0];
|
||||
},
|
||||
/**
|
||||
* Retrieve the persisted filters for a given identifier.
|
||||
*/
|
||||
getPersistedFilters: function (identifier) {
|
||||
const domainObject = this.get('domainObject');
|
||||
const keystring = this.openmct.objects.makeKeyString(identifier);
|
||||
|
||||
if (!domainObject.configuration || !domainObject.configuration.filters) {
|
||||
return;
|
||||
}
|
||||
|
||||
return domainObject.configuration.filters[keystring];
|
||||
},
|
||||
/**
|
||||
* Update the domain object with the given value.
|
||||
*/
|
||||
updateDomainObject: function (domainObject) {
|
||||
this.set('domainObject', domainObject);
|
||||
},
|
||||
/**
|
||||
* Clean up all objects and remove all listeners.
|
||||
*/
|
||||
onDestroy: function () {
|
||||
this.xAxis.destroy();
|
||||
this.yAxis.destroy();
|
||||
this.series.destroy();
|
||||
this.legend.destroy();
|
||||
if (this.removeMutationListener) {
|
||||
this.removeMutationListener();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Return defaults, which are extracted from the passed in domain
|
||||
* object.
|
||||
*/
|
||||
defaults: function (options) {
|
||||
return {
|
||||
series: [],
|
||||
domainObject: options.domainObject,
|
||||
xAxis: {
|
||||
},
|
||||
yAxis: _.cloneDeep(_.get(options.domainObject, 'configuration.yAxis', {})),
|
||||
legend: _.cloneDeep(_.get(options.domainObject, 'configuration.legend', {}))
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return PlotConfigurationModel;
|
||||
});
|
@ -1,469 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'lodash',
|
||||
'../configuration/Model',
|
||||
'../lib/extend',
|
||||
'EventEmitter',
|
||||
'../draw/MarkerShapes'
|
||||
], function (
|
||||
_,
|
||||
Model,
|
||||
extend,
|
||||
EventEmitter,
|
||||
MARKER_SHAPES
|
||||
) {
|
||||
|
||||
/**
|
||||
* Plot series handle interpreting telemetry metadata for a single telemetry
|
||||
* object, querying for that data, and formatting it for display purposes.
|
||||
*
|
||||
* Plot series emit both collection events and model events:
|
||||
* `change` when any property changes
|
||||
* `change:<prop_name>` when a specific property changes.
|
||||
* `destroy`: when series is destroyed
|
||||
* `add`: whenever a data point is added to a series
|
||||
* `remove`: whenever a data point is removed from a series.
|
||||
* `reset`: whenever the collection is emptied.
|
||||
*
|
||||
* Plot series have the following Model properties:
|
||||
*
|
||||
* `name`: name of series.
|
||||
* `identifier`: the Open MCT identifier for the telemetry source for this
|
||||
* series.
|
||||
* `xKey`: the telemetry value key for x values fetched from this series.
|
||||
* `yKey`: the telemetry value key for y values fetched from this series.
|
||||
* `interpolate`: interpolate method, either `undefined` (no interpolation),
|
||||
* `linear` (points are connected via straight lines), or
|
||||
* `stepAfter` (points are connected by steps).
|
||||
* `markers`: boolean, whether or not this series should render with markers.
|
||||
* `markerShape`: string, shape of markers.
|
||||
* `markerSize`: number, size in pixels of markers for this series.
|
||||
* `alarmMarkers`: whether or not to display alarm markers for this series.
|
||||
* `stats`: An object that tracks the min and max y values observed in this
|
||||
* series. This property is checked and updated whenever data is
|
||||
* added.
|
||||
*
|
||||
* Plot series have the following instance properties:
|
||||
*
|
||||
* `metadata`: the Open MCT Telemetry Metadata Manager for the associated
|
||||
* telemetry point.
|
||||
* `formats`: the Open MCT format map for this telemetry point.
|
||||
*/
|
||||
const PlotSeries = Model.extend({
|
||||
constructor: function (options) {
|
||||
this.metadata = options
|
||||
.openmct
|
||||
.telemetry
|
||||
.getMetadata(options.domainObject);
|
||||
this.formats = options
|
||||
.openmct
|
||||
.telemetry
|
||||
.getFormatMap(this.metadata);
|
||||
|
||||
this.data = [];
|
||||
|
||||
this.listenTo(this, 'change:xKey', this.onXKeyChange, this);
|
||||
this.listenTo(this, 'change:yKey', this.onYKeyChange, this);
|
||||
this.persistedConfig = options.persistedConfig;
|
||||
this.filters = options.filters;
|
||||
|
||||
Model.apply(this, arguments);
|
||||
this.onXKeyChange(this.get('xKey'));
|
||||
this.onYKeyChange(this.get('yKey'));
|
||||
},
|
||||
|
||||
/**
|
||||
* Set defaults for telemetry series.
|
||||
*/
|
||||
defaults: function (options) {
|
||||
const range = this.metadata.valuesForHints(['range'])[0];
|
||||
|
||||
return {
|
||||
name: options.domainObject.name,
|
||||
unit: range.unit,
|
||||
xKey: options.collection.plot.xAxis.get('key'),
|
||||
yKey: range.key,
|
||||
markers: true,
|
||||
markerShape: 'point',
|
||||
markerSize: 2.0,
|
||||
alarmMarkers: true
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove real-time subscription when destroyed.
|
||||
*/
|
||||
onDestroy: function (model) {
|
||||
if (this.unsubscribe) {
|
||||
this.unsubscribe();
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
this.openmct = options.openmct;
|
||||
this.domainObject = options.domainObject;
|
||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject);
|
||||
this.on('destroy', this.onDestroy, this);
|
||||
},
|
||||
|
||||
locateOldObject: function (oldStyleParent) {
|
||||
return oldStyleParent.useCapability('composition')
|
||||
.then(function (children) {
|
||||
this.oldObject = children
|
||||
.filter(function (child) {
|
||||
return child.getId() === this.keyString;
|
||||
}, this)[0];
|
||||
}.bind(this));
|
||||
},
|
||||
/**
|
||||
* Fetch historical data and establish a realtime subscription. Returns
|
||||
* a promise that is resolved when all connections have been successfully
|
||||
* established.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
fetch: function (options) {
|
||||
let strategy;
|
||||
|
||||
if (this.model.interpolate !== 'none') {
|
||||
strategy = 'minmax';
|
||||
}
|
||||
|
||||
options = Object.assign({}, {
|
||||
size: 1000,
|
||||
strategy,
|
||||
filters: this.filters
|
||||
}, options || {});
|
||||
|
||||
if (!this.unsubscribe) {
|
||||
this.unsubscribe = this.openmct
|
||||
.telemetry
|
||||
.subscribe(
|
||||
this.domainObject,
|
||||
this.add.bind(this),
|
||||
{
|
||||
filters: this.filters
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/* eslint-disable you-dont-need-lodash-underscore/concat */
|
||||
return this.openmct
|
||||
.telemetry
|
||||
.request(this.domainObject, options)
|
||||
.then(function (points) {
|
||||
const newPoints = _(this.data)
|
||||
.concat(points)
|
||||
.sortBy(this.getXVal)
|
||||
.uniq(true, point => [this.getXVal(point), this.getYVal(point)].join())
|
||||
.value();
|
||||
this.reset(newPoints);
|
||||
}.bind(this));
|
||||
/* eslint-enable you-dont-need-lodash-underscore/concat */
|
||||
},
|
||||
/**
|
||||
* Update x formatter on x change.
|
||||
*/
|
||||
onXKeyChange: function (xKey) {
|
||||
const format = this.formats[xKey];
|
||||
this.getXVal = format.parse.bind(format);
|
||||
},
|
||||
/**
|
||||
* Update y formatter on change, default to stepAfter interpolation if
|
||||
* y range is an enumeration.
|
||||
*/
|
||||
onYKeyChange: function (newKey, oldKey) {
|
||||
if (newKey === oldKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const valueMetadata = this.metadata.value(newKey);
|
||||
if (!this.persistedConfig || !this.persistedConfig.interpolate) {
|
||||
if (valueMetadata.format === 'enum') {
|
||||
this.set('interpolate', 'stepAfter');
|
||||
} else {
|
||||
this.set('interpolate', 'linear');
|
||||
}
|
||||
}
|
||||
|
||||
this.evaluate = function (datum) {
|
||||
return this.limitEvaluator.evaluate(datum, valueMetadata);
|
||||
}.bind(this);
|
||||
const format = this.formats[newKey];
|
||||
this.getYVal = format.parse.bind(format);
|
||||
},
|
||||
|
||||
formatX: function (point) {
|
||||
return this.formats[this.get('xKey')].format(point);
|
||||
},
|
||||
|
||||
formatY: function (point) {
|
||||
return this.formats[this.get('yKey')].format(point);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear stats and recalculate from existing data.
|
||||
*/
|
||||
resetStats: function () {
|
||||
this.unset('stats');
|
||||
this.data.forEach(this.updateStats, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset plot series. If new data is provided, will add that
|
||||
* data to series after reset.
|
||||
*/
|
||||
reset: function (newData) {
|
||||
this.data = [];
|
||||
this.resetStats();
|
||||
this.emit('reset');
|
||||
if (newData) {
|
||||
newData.forEach(function (point) {
|
||||
this.add(point, true);
|
||||
}, this);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Return the point closest to a given x value.
|
||||
*/
|
||||
nearestPoint: function (xValue) {
|
||||
const insertIndex = this.sortedIndex(xValue);
|
||||
const lowPoint = this.data[insertIndex - 1];
|
||||
const highPoint = this.data[insertIndex];
|
||||
const indexVal = this.getXVal(xValue);
|
||||
const lowDistance = lowPoint
|
||||
? indexVal - this.getXVal(lowPoint)
|
||||
: Number.POSITIVE_INFINITY;
|
||||
const highDistance = highPoint
|
||||
? this.getXVal(highPoint) - indexVal
|
||||
: Number.POSITIVE_INFINITY;
|
||||
const nearestPoint = highDistance < lowDistance ? highPoint : lowPoint;
|
||||
|
||||
return nearestPoint;
|
||||
},
|
||||
/**
|
||||
* Override this to implement plot series loading functionality. Must return
|
||||
* a promise that is resolved when loading is completed.
|
||||
*
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
load: function (options) {
|
||||
return this.fetch(options)
|
||||
.then(function (res) {
|
||||
this.emit('load');
|
||||
|
||||
return res;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Find the insert index for a given point to maintain sort order.
|
||||
* @private
|
||||
*/
|
||||
sortedIndex: function (point) {
|
||||
return _.sortedIndexBy(this.data, point, this.getXVal);
|
||||
},
|
||||
/**
|
||||
* Update min/max stats for the series.
|
||||
* @private
|
||||
*/
|
||||
updateStats: function (point) {
|
||||
const value = this.getYVal(point);
|
||||
let stats = this.get('stats');
|
||||
let changed = false;
|
||||
if (!stats) {
|
||||
stats = {
|
||||
minValue: value,
|
||||
minPoint: point,
|
||||
maxValue: value,
|
||||
maxPoint: point
|
||||
};
|
||||
changed = true;
|
||||
} else {
|
||||
if (stats.maxValue < value) {
|
||||
stats.maxValue = value;
|
||||
stats.maxPoint = point;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (stats.minValue > value) {
|
||||
stats.minValue = value;
|
||||
stats.minPoint = point;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
this.set('stats', {
|
||||
minValue: stats.minValue,
|
||||
minPoint: stats.minPoint,
|
||||
maxValue: stats.maxValue,
|
||||
maxPoint: stats.maxPoint
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Add a point to the data array while maintaining the sort order of
|
||||
* the array and preventing insertion of points with a duplicate x
|
||||
* value. Can provide an optional argument to append a point without
|
||||
* maintaining sort order and dupe checks, which improves performance
|
||||
* when adding an array of points that are already properly sorted.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} point a telemetry datum.
|
||||
* @param {Boolean} [appendOnly] default false, if true will append
|
||||
* a point to the end without dupe checking.
|
||||
*/
|
||||
add: function (point, appendOnly) {
|
||||
let insertIndex = this.data.length;
|
||||
const currentYVal = this.getYVal(point);
|
||||
const lastYVal = this.getYVal(this.data[insertIndex - 1]);
|
||||
|
||||
if (this.isValueInvalid(currentYVal) && this.isValueInvalid(lastYVal)) {
|
||||
console.warn('[Plot] Invalid Y Values detected');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!appendOnly) {
|
||||
insertIndex = this.sortedIndex(point);
|
||||
if (this.getXVal(this.data[insertIndex]) === this.getXVal(point)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.getXVal(this.data[insertIndex - 1]) === this.getXVal(point)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateStats(point);
|
||||
point.mctLimitState = this.evaluate(point);
|
||||
this.data.splice(insertIndex, 0, point);
|
||||
this.emit('add', point, insertIndex, this);
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
isValueInvalid: function (val) {
|
||||
return Number.isNaN(val) || val === undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a point from the data array and notify listeners.
|
||||
* @private
|
||||
*/
|
||||
remove: function (point) {
|
||||
const index = this.data.indexOf(point);
|
||||
this.data.splice(index, 1);
|
||||
this.emit('remove', point, index, this);
|
||||
},
|
||||
/**
|
||||
* Purges records outside a given x range. Changes removal method based
|
||||
* on number of records to remove: for large purge, reset data and
|
||||
* rebuild array. for small purge, removes points and emits updates.
|
||||
*
|
||||
* @public
|
||||
* @param {Object} range
|
||||
* @param {number} range.min minimum x value to keep
|
||||
* @param {number} range.max maximum x value to keep.
|
||||
*/
|
||||
purgeRecordsOutsideRange: function (range) {
|
||||
const startIndex = this.sortedIndex(range.min);
|
||||
const endIndex = this.sortedIndex(range.max) + 1;
|
||||
const pointsToRemove = startIndex + (this.data.length - endIndex + 1);
|
||||
if (pointsToRemove > 0) {
|
||||
if (pointsToRemove < 1000) {
|
||||
this.data.slice(0, startIndex).forEach(this.remove, this);
|
||||
this.data.slice(endIndex, this.data.length).forEach(this.remove, this);
|
||||
this.resetStats();
|
||||
} else {
|
||||
const newData = this.data.slice(startIndex, endIndex);
|
||||
this.reset(newData);
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
/**
|
||||
* Updates filters, clears the plot series, unsubscribes and resubscribes
|
||||
* @public
|
||||
*/
|
||||
updateFiltersAndRefresh: function (updatedFilters) {
|
||||
if (updatedFilters === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let deepCopiedFilters = JSON.parse(JSON.stringify(updatedFilters));
|
||||
|
||||
if (this.filters && !_.isEqual(this.filters, deepCopiedFilters)) {
|
||||
this.filters = deepCopiedFilters;
|
||||
this.reset();
|
||||
if (this.unsubscribe) {
|
||||
this.unsubscribe();
|
||||
delete this.unsubscribe;
|
||||
}
|
||||
|
||||
this.fetch();
|
||||
} else {
|
||||
this.filters = deepCopiedFilters;
|
||||
}
|
||||
},
|
||||
getDisplayRange: function (xKey) {
|
||||
const unsortedData = this.data;
|
||||
this.data = [];
|
||||
unsortedData.forEach(point => this.add(point, false));
|
||||
|
||||
const minValue = this.getXVal(this.data[0]);
|
||||
const maxValue = this.getXVal(this.data[this.data.length - 1]);
|
||||
|
||||
return {
|
||||
min: minValue,
|
||||
max: maxValue
|
||||
};
|
||||
},
|
||||
markerOptionsDisplayText: function () {
|
||||
const showMarkers = this.get('markers');
|
||||
if (!showMarkers) {
|
||||
return "Disabled";
|
||||
}
|
||||
|
||||
const markerShapeKey = this.get('markerShape');
|
||||
const markerShape = MARKER_SHAPES[markerShapeKey].label;
|
||||
const markerSize = this.get('markerSize');
|
||||
|
||||
return `${markerShape}: ${markerSize}px`;
|
||||
},
|
||||
nameWithUnit: function () {
|
||||
let unit = this.get('unit');
|
||||
|
||||
return this.get('name') + (unit ? ' ' + unit : '');
|
||||
}
|
||||
});
|
||||
|
||||
return PlotSeries;
|
||||
|
||||
});
|
@ -1,174 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
define([
|
||||
'./PlotSeries',
|
||||
'./Collection',
|
||||
'./Model',
|
||||
'../lib/color',
|
||||
'lodash'
|
||||
], function (
|
||||
PlotSeries,
|
||||
Collection,
|
||||
Model,
|
||||
color,
|
||||
_
|
||||
) {
|
||||
|
||||
const SeriesCollection = Collection.extend({
|
||||
modelClass: PlotSeries,
|
||||
initialize: function (options) {
|
||||
this.plot = options.plot;
|
||||
this.openmct = options.openmct;
|
||||
this.palette = new color.ColorPalette();
|
||||
this.listenTo(this, 'add', this.onSeriesAdd, this);
|
||||
this.listenTo(this, 'remove', this.onSeriesRemove, this);
|
||||
this.listenTo(this.plot, 'change:domainObject', this.trackPersistedConfig, this);
|
||||
|
||||
const domainObject = this.plot.get('domainObject');
|
||||
if (domainObject.telemetry) {
|
||||
this.addTelemetryObject(domainObject);
|
||||
} else {
|
||||
this.watchTelemetryContainer(domainObject);
|
||||
}
|
||||
},
|
||||
trackPersistedConfig: function (domainObject) {
|
||||
domainObject.configuration.series.forEach(function (seriesConfig) {
|
||||
const series = this.byIdentifier(seriesConfig.identifier);
|
||||
if (series) {
|
||||
series.persistedConfig = seriesConfig;
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
watchTelemetryContainer: function (domainObject) {
|
||||
const composition = this.openmct.composition.get(domainObject);
|
||||
this.listenTo(composition, 'add', this.addTelemetryObject, this);
|
||||
this.listenTo(composition, 'remove', this.removeTelemetryObject, this);
|
||||
composition.load();
|
||||
},
|
||||
addTelemetryObject: function (domainObject, index) {
|
||||
let seriesConfig = this.plot.getPersistedSeriesConfig(domainObject.identifier);
|
||||
const filters = this.plot.getPersistedFilters(domainObject.identifier);
|
||||
const plotObject = this.plot.get('domainObject');
|
||||
|
||||
if (!seriesConfig) {
|
||||
seriesConfig = {
|
||||
identifier: domainObject.identifier
|
||||
};
|
||||
|
||||
if (plotObject.type === 'telemetry.plot.overlay') {
|
||||
this.openmct.objects.mutate(
|
||||
plotObject,
|
||||
'configuration.series[' + this.size() + ']',
|
||||
seriesConfig
|
||||
);
|
||||
seriesConfig = this.plot
|
||||
.getPersistedSeriesConfig(domainObject.identifier);
|
||||
}
|
||||
}
|
||||
|
||||
// Clone to prevent accidental mutation by ref.
|
||||
seriesConfig = JSON.parse(JSON.stringify(seriesConfig));
|
||||
|
||||
this.add(new PlotSeries({
|
||||
model: seriesConfig,
|
||||
domainObject: domainObject,
|
||||
collection: this,
|
||||
openmct: this.openmct,
|
||||
persistedConfig: this.plot
|
||||
.getPersistedSeriesConfig(domainObject.identifier),
|
||||
filters: filters
|
||||
}));
|
||||
},
|
||||
removeTelemetryObject: function (identifier) {
|
||||
const plotObject = this.plot.get('domainObject');
|
||||
if (plotObject.type === 'telemetry.plot.overlay') {
|
||||
|
||||
const persistedIndex = plotObject.configuration.series.findIndex(s => {
|
||||
return _.isEqual(identifier, s.identifier);
|
||||
});
|
||||
|
||||
const configIndex = this.models.findIndex(m => {
|
||||
return _.isEqual(m.domainObject.identifier, identifier);
|
||||
});
|
||||
|
||||
/*
|
||||
when cancelling out of edit mode, the config store and domain object are out of sync
|
||||
thus it is necesarry to check both and remove the models that are no longer in composition
|
||||
*/
|
||||
if (persistedIndex === -1) {
|
||||
this.remove(this.at(configIndex));
|
||||
} else {
|
||||
this.remove(this.at(persistedIndex));
|
||||
// Because this is triggered by a composition change, we have
|
||||
// to defer mutation of our plot object, otherwise we might
|
||||
// mutate an outdated version of the plotObject.
|
||||
setTimeout(function () {
|
||||
const newPlotObject = this.plot.get('domainObject');
|
||||
const cSeries = newPlotObject.configuration.series.slice();
|
||||
cSeries.splice(persistedIndex, 1);
|
||||
this.openmct.objects.mutate(newPlotObject, 'configuration.series', cSeries);
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
||||
},
|
||||
onSeriesAdd: function (series) {
|
||||
let seriesColor = series.get('color');
|
||||
if (seriesColor) {
|
||||
if (!(seriesColor instanceof color.Color)) {
|
||||
seriesColor = color.Color.fromHexString(seriesColor);
|
||||
series.set('color', seriesColor);
|
||||
}
|
||||
|
||||
this.palette.remove(seriesColor);
|
||||
} else {
|
||||
series.set('color', this.palette.getNextColor());
|
||||
}
|
||||
|
||||
this.listenTo(series, 'change:color', this.updateColorPalette, this);
|
||||
},
|
||||
onSeriesRemove: function (series) {
|
||||
this.palette.return(series.get('color'));
|
||||
this.stopListening(series);
|
||||
series.destroy();
|
||||
},
|
||||
updateColorPalette: function (newColor, oldColor) {
|
||||
this.palette.remove(newColor);
|
||||
const seriesWithColor = this.filter(function (series) {
|
||||
return series.get('color') === newColor;
|
||||
})[0];
|
||||
if (!seriesWithColor) {
|
||||
this.palette.return(oldColor);
|
||||
}
|
||||
},
|
||||
byIdentifier: function (identifier) {
|
||||
return this.filter(function (series) {
|
||||
const seriesIdentifier = series.get('identifier');
|
||||
|
||||
return seriesIdentifier.namespace === identifier.namespace
|
||||
&& seriesIdentifier.key === identifier.key;
|
||||
})[0];
|
||||
}
|
||||
});
|
||||
|
||||
return SeriesCollection;
|
||||
|
||||
});
|
@ -1,97 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
define([
|
||||
'./Model'
|
||||
], function (
|
||||
Model
|
||||
) {
|
||||
|
||||
/**
|
||||
* TODO: doc strings.
|
||||
*/
|
||||
const XAxisModel = Model.extend({
|
||||
initialize: function (options) {
|
||||
this.plot = options.plot;
|
||||
this.set('label', options.model.name || '');
|
||||
this.on('change:range', function (newValue, oldValue, model) {
|
||||
if (!model.get('frozen')) {
|
||||
model.set('displayRange', newValue);
|
||||
}
|
||||
});
|
||||
|
||||
this.on('change:frozen', ((frozen, oldValue, model) => {
|
||||
if (!frozen) {
|
||||
model.set('range', this.get('range'));
|
||||
}
|
||||
}));
|
||||
|
||||
if (this.get('range')) {
|
||||
this.set('range', this.get('range'));
|
||||
}
|
||||
|
||||
this.listenTo(this, 'change:key', this.changeKey, this);
|
||||
this.listenTo(this, 'resetSeries', this.resetSeries, this);
|
||||
},
|
||||
changeKey: function (newKey) {
|
||||
const series = this.plot.series.first();
|
||||
if (series) {
|
||||
const xMetadata = series.metadata.value(newKey);
|
||||
const xFormat = series.formats[newKey];
|
||||
this.set('label', xMetadata.name);
|
||||
this.set('format', xFormat.format.bind(xFormat));
|
||||
} else {
|
||||
this.set('format', function (x) {
|
||||
return x;
|
||||
});
|
||||
this.set('label', newKey);
|
||||
}
|
||||
|
||||
this.plot.series.forEach(function (plotSeries) {
|
||||
plotSeries.set('xKey', newKey);
|
||||
});
|
||||
},
|
||||
resetSeries: function () {
|
||||
this.plot.series.forEach(function (plotSeries) {
|
||||
plotSeries.reset();
|
||||
});
|
||||
},
|
||||
defaults: function (options) {
|
||||
const bounds = options.openmct.time.bounds();
|
||||
const timeSystem = options.openmct.time.timeSystem();
|
||||
const format = options.openmct.$injector.get('formatService')
|
||||
.getFormat(timeSystem.timeFormat);
|
||||
|
||||
return {
|
||||
name: timeSystem.name,
|
||||
key: timeSystem.key,
|
||||
format: format.format.bind(format),
|
||||
range: {
|
||||
min: bounds.start,
|
||||
max: bounds.end
|
||||
},
|
||||
frozen: false
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return XAxisModel;
|
||||
});
|
@ -1,244 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
define([
|
||||
'./Model',
|
||||
'lodash'
|
||||
], function (
|
||||
Model,
|
||||
_
|
||||
) {
|
||||
|
||||
/**
|
||||
* YAxis model
|
||||
*
|
||||
* TODO: docstrings.
|
||||
*
|
||||
* has the following Model properties:
|
||||
*
|
||||
* `autoscale`: boolean, whether or not to autoscale.
|
||||
* `autoscalePadding`: float, percent of padding to display in plots.
|
||||
* `displayRange`: the current display range for the x Axis.
|
||||
* `format`: the formatter for the axis.
|
||||
* `frozen`: boolean, if true, displayRange will not be updated automatically.
|
||||
* Used to temporarily disable automatic updates during user interaction.
|
||||
* `label`: label to display on axis.
|
||||
* `stats`: Min and Max Values of data, automatically updated by observing
|
||||
* plot series.
|
||||
* `values`: for enumerated types, an array of possible display values.
|
||||
* `range`: the user-configured range to use for display, when autoscale is
|
||||
* disabled.
|
||||
*
|
||||
*/
|
||||
const YAxisModel = Model.extend({
|
||||
initialize: function (options) {
|
||||
this.plot = options.plot;
|
||||
this.listenTo(this, 'change:stats', this.calculateAutoscaleExtents, this);
|
||||
this.listenTo(this, 'change:autoscale', this.toggleAutoscale, this);
|
||||
this.listenTo(this, 'change:autoscalePadding', this.updatePadding, this);
|
||||
this.listenTo(this, 'change:frozen', this.toggleFreeze, this);
|
||||
this.listenTo(this, 'change:range', this.updateDisplayRange, this);
|
||||
this.updateDisplayRange(this.get('range'));
|
||||
},
|
||||
listenToSeriesCollection: function (seriesCollection) {
|
||||
this.seriesCollection = seriesCollection;
|
||||
this.listenTo(this.seriesCollection, 'add', (series => {
|
||||
this.trackSeries(series);
|
||||
this.updateFromSeries(this.seriesCollection);
|
||||
}), this);
|
||||
this.listenTo(this.seriesCollection, 'remove', (series => {
|
||||
this.untrackSeries(series);
|
||||
this.updateFromSeries(this.seriesCollection);
|
||||
}), this);
|
||||
this.seriesCollection.forEach(this.trackSeries, this);
|
||||
this.updateFromSeries(this.seriesCollection);
|
||||
},
|
||||
updateDisplayRange: function (range) {
|
||||
if (!this.get('autoscale')) {
|
||||
this.set('displayRange', range);
|
||||
}
|
||||
},
|
||||
toggleFreeze: function (frozen) {
|
||||
if (!frozen) {
|
||||
this.toggleAutoscale(this.get('autoscale'));
|
||||
}
|
||||
},
|
||||
applyPadding: function (range) {
|
||||
let padding = Math.abs(range.max - range.min) * this.get('autoscalePadding');
|
||||
if (padding === 0) {
|
||||
padding = 1;
|
||||
}
|
||||
|
||||
return {
|
||||
min: range.min - padding,
|
||||
max: range.max + padding
|
||||
};
|
||||
},
|
||||
updatePadding: function (newPadding) {
|
||||
if (this.get('autoscale') && !this.get('frozen') && this.has('stats')) {
|
||||
this.set('displayRange', this.applyPadding(this.get('stats')));
|
||||
}
|
||||
},
|
||||
calculateAutoscaleExtents: function (newStats) {
|
||||
if (this.get('autoscale') && !this.get('frozen')) {
|
||||
if (!newStats) {
|
||||
this.unset('displayRange');
|
||||
} else {
|
||||
this.set('displayRange', this.applyPadding(newStats));
|
||||
}
|
||||
}
|
||||
},
|
||||
updateStats: function (seriesStats) {
|
||||
if (!this.has('stats')) {
|
||||
this.set('stats', {
|
||||
min: seriesStats.minValue,
|
||||
max: seriesStats.maxValue
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const stats = this.get('stats');
|
||||
let changed = false;
|
||||
if (stats.min > seriesStats.minValue) {
|
||||
changed = true;
|
||||
stats.min = seriesStats.minValue;
|
||||
}
|
||||
|
||||
if (stats.max < seriesStats.maxValue) {
|
||||
changed = true;
|
||||
stats.max = seriesStats.maxValue;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
this.set('stats', {
|
||||
min: stats.min,
|
||||
max: stats.max
|
||||
});
|
||||
}
|
||||
},
|
||||
resetStats: function () {
|
||||
this.unset('stats');
|
||||
this.seriesCollection.forEach(function (series) {
|
||||
if (series.has('stats')) {
|
||||
this.updateStats(series.get('stats'));
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
trackSeries: function (series) {
|
||||
this.listenTo(series, 'change:stats', seriesStats => {
|
||||
if (!seriesStats) {
|
||||
this.resetStats();
|
||||
} else {
|
||||
this.updateStats(seriesStats);
|
||||
}
|
||||
});
|
||||
this.listenTo(series, 'change:yKey', () => {
|
||||
this.updateFromSeries(this.seriesCollection);
|
||||
});
|
||||
},
|
||||
untrackSeries: function (series) {
|
||||
this.stopListening(series);
|
||||
this.resetStats();
|
||||
this.updateFromSeries(this.seriesCollection);
|
||||
},
|
||||
toggleAutoscale: function (autoscale) {
|
||||
if (autoscale && this.has('stats')) {
|
||||
this.set('displayRange', this.applyPadding(this.get('stats')));
|
||||
} else {
|
||||
this.set('displayRange', this.get('range'));
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Update yAxis format, values, and label from known series.
|
||||
*/
|
||||
updateFromSeries: function (series) {
|
||||
this.unset('displayRange');
|
||||
const plotModel = this.plot.get('domainObject');
|
||||
const label = _.get(plotModel, 'configuration.yAxis.label');
|
||||
const sampleSeries = series.first();
|
||||
if (!sampleSeries) {
|
||||
if (!label) {
|
||||
this.unset('label');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const yKey = sampleSeries.get('yKey');
|
||||
const yMetadata = sampleSeries.metadata.value(yKey);
|
||||
const yFormat = sampleSeries.formats[yKey];
|
||||
this.set('format', yFormat.format.bind(yFormat));
|
||||
this.set('values', yMetadata.values);
|
||||
if (!label) {
|
||||
const labelName = series.map(function (s) {
|
||||
return s.metadata.value(s.get('yKey')).name;
|
||||
}).reduce(function (a, b) {
|
||||
if (a === undefined) {
|
||||
return b;
|
||||
}
|
||||
|
||||
if (a === b) {
|
||||
return a;
|
||||
}
|
||||
|
||||
return '';
|
||||
}, undefined);
|
||||
|
||||
if (labelName) {
|
||||
this.set('label', labelName);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const labelUnits = series.map(function (s) {
|
||||
return s.metadata.value(s.get('yKey')).units;
|
||||
}).reduce(function (a, b) {
|
||||
if (a === undefined) {
|
||||
return b;
|
||||
}
|
||||
|
||||
if (a === b) {
|
||||
return a;
|
||||
}
|
||||
|
||||
return '';
|
||||
}, undefined);
|
||||
|
||||
if (labelUnits) {
|
||||
this.set('label', labelUnits);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
defaults: function (options) {
|
||||
return {
|
||||
frozen: false,
|
||||
autoscale: true,
|
||||
autoscalePadding: 0.1
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return YAxisModel;
|
||||
|
||||
});
|
@ -1,48 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
define([
|
||||
], function (
|
||||
) {
|
||||
|
||||
function ConfigStore() {
|
||||
this.store = {};
|
||||
}
|
||||
|
||||
ConfigStore.prototype.deleteStore = function (id) {
|
||||
if (this.store[id]) {
|
||||
this.store[id].destroy();
|
||||
delete this.store[id];
|
||||
}
|
||||
};
|
||||
|
||||
ConfigStore.prototype.add = function (id, config) {
|
||||
this.store[id] = config;
|
||||
};
|
||||
|
||||
ConfigStore.prototype.get = function (id) {
|
||||
return this.store[id];
|
||||
};
|
||||
|
||||
const STORE = new ConfigStore();
|
||||
|
||||
return STORE;
|
||||
});
|
@ -1,163 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'EventEmitter',
|
||||
'../lib/eventHelpers',
|
||||
'./MarkerShapes'
|
||||
], function (
|
||||
EventEmitter,
|
||||
eventHelpers,
|
||||
MARKER_SHAPES
|
||||
) {
|
||||
|
||||
/**
|
||||
* Create a new draw API utilizing the Canvas's 2D API for rendering.
|
||||
*
|
||||
* @constructor
|
||||
* @param {CanvasElement} canvas the canvas object to render upon
|
||||
* @throws {Error} an error is thrown if Canvas's 2D API is unavailab
|
||||
*/
|
||||
function Draw2D(canvas) {
|
||||
this.canvas = canvas;
|
||||
this.c2d = canvas.getContext('2d');
|
||||
this.width = canvas.width;
|
||||
this.height = canvas.height;
|
||||
this.dimensions = [this.width, this.height];
|
||||
this.origin = [0, 0];
|
||||
|
||||
if (!this.c2d) {
|
||||
throw new Error("Canvas 2d API unavailable.");
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(Draw2D.prototype, EventEmitter.prototype);
|
||||
eventHelpers.extend(Draw2D.prototype);
|
||||
|
||||
// Convert from logical to physical x coordinates
|
||||
Draw2D.prototype.x = function (v) {
|
||||
return ((v - this.origin[0]) / this.dimensions[0]) * this.width;
|
||||
};
|
||||
|
||||
// Convert from logical to physical y coordinates
|
||||
Draw2D.prototype.y = function (v) {
|
||||
return this.height
|
||||
- ((v - this.origin[1]) / this.dimensions[1]) * this.height;
|
||||
};
|
||||
|
||||
// Set the color to be used for drawing operations
|
||||
Draw2D.prototype.setColor = function (color) {
|
||||
const mappedColor = color.map(function (c, i) {
|
||||
return i < 3 ? Math.floor(c * 255) : (c);
|
||||
}).join(',');
|
||||
this.c2d.strokeStyle = "rgba(" + mappedColor + ")";
|
||||
this.c2d.fillStyle = "rgba(" + mappedColor + ")";
|
||||
};
|
||||
|
||||
Draw2D.prototype.clear = function () {
|
||||
this.width = this.canvas.width = this.canvas.offsetWidth;
|
||||
this.height = this.canvas.height = this.canvas.offsetHeight;
|
||||
this.c2d.clearRect(0, 0, this.width, this.height);
|
||||
};
|
||||
|
||||
Draw2D.prototype.setDimensions = function (newDimensions, newOrigin) {
|
||||
this.dimensions = newDimensions;
|
||||
this.origin = newOrigin;
|
||||
};
|
||||
|
||||
Draw2D.prototype.drawLine = function (buf, color, points) {
|
||||
let i;
|
||||
|
||||
this.setColor(color);
|
||||
|
||||
// Configure context to draw two-pixel-thick lines
|
||||
this.c2d.lineWidth = 1;
|
||||
|
||||
// Start a new path...
|
||||
if (buf.length > 1) {
|
||||
this.c2d.beginPath();
|
||||
this.c2d.moveTo(this.x(buf[0]), this.y(buf[1]));
|
||||
}
|
||||
|
||||
// ...and add points to it...
|
||||
for (i = 2; i < points * 2; i = i + 2) {
|
||||
this.c2d.lineTo(this.x(buf[i]), this.y(buf[i + 1]));
|
||||
}
|
||||
|
||||
// ...before finally drawing it.
|
||||
this.c2d.stroke();
|
||||
};
|
||||
|
||||
Draw2D.prototype.drawSquare = function (min, max, color) {
|
||||
const x1 = this.x(min[0]);
|
||||
const y1 = this.y(min[1]);
|
||||
const w = this.x(max[0]) - x1;
|
||||
const h = this.y(max[1]) - y1;
|
||||
|
||||
this.setColor(color);
|
||||
this.c2d.fillRect(x1, y1, w, h);
|
||||
};
|
||||
|
||||
Draw2D.prototype.drawPoints = function (
|
||||
buf,
|
||||
color,
|
||||
points,
|
||||
pointSize,
|
||||
shape
|
||||
) {
|
||||
const drawC2DShape = MARKER_SHAPES[shape].drawC2D.bind(this);
|
||||
|
||||
this.setColor(color);
|
||||
|
||||
for (let i = 0; i < points; i++) {
|
||||
drawC2DShape(
|
||||
this.x(buf[i * 2]),
|
||||
this.y(buf[i * 2 + 1]),
|
||||
pointSize
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Draw2D.prototype.drawLimitPoint = function (x, y, size) {
|
||||
this.c2d.fillRect(x + size, y, size, size);
|
||||
this.c2d.fillRect(x, y + size, size, size);
|
||||
this.c2d.fillRect(x - size, y, size, size);
|
||||
this.c2d.fillRect(x, y - size, size, size);
|
||||
};
|
||||
|
||||
Draw2D.prototype.drawLimitPoints = function (points, color, pointSize) {
|
||||
const limitSize = pointSize * 2;
|
||||
const offset = limitSize / 2;
|
||||
|
||||
this.setColor(color);
|
||||
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
this.drawLimitPoint(
|
||||
this.x(points[i].x) - offset,
|
||||
this.y(points[i].y) - offset,
|
||||
limitSize
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return Draw2D;
|
||||
});
|
@ -1,110 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[
|
||||
'./DrawWebGL',
|
||||
'./Draw2D'
|
||||
],
|
||||
function (DrawWebGL, Draw2D) {
|
||||
|
||||
const CHARTS = [
|
||||
{
|
||||
MAX_INSTANCES: 16,
|
||||
API: DrawWebGL,
|
||||
ALLOCATIONS: []
|
||||
},
|
||||
{
|
||||
MAX_INSTANCES: Number.POSITIVE_INFINITY,
|
||||
API: Draw2D,
|
||||
ALLOCATIONS: []
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Draw loader attaches a draw API to a canvas element and returns the
|
||||
* draw API.
|
||||
*/
|
||||
return {
|
||||
/**
|
||||
* Return the first draw API available. Returns
|
||||
* `undefined` if a draw API could not be constructed.
|
||||
*.
|
||||
* @param {CanvasElement} canvas - The canvas eelement to attach
|
||||
the draw API to.
|
||||
*/
|
||||
getDrawAPI: function (canvas, overlay) {
|
||||
let api;
|
||||
|
||||
CHARTS.forEach(function (CHART_TYPE) {
|
||||
if (api) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (CHART_TYPE.ALLOCATIONS.length
|
||||
>= CHART_TYPE.MAX_INSTANCES) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
api = new CHART_TYPE.API(canvas, overlay);
|
||||
CHART_TYPE.ALLOCATIONS.push(api);
|
||||
} catch (e) {
|
||||
console.warn([
|
||||
"Could not instantiate chart",
|
||||
CHART_TYPE.API.name,
|
||||
";",
|
||||
e.message
|
||||
].join(" "));
|
||||
}
|
||||
});
|
||||
|
||||
if (!api) {
|
||||
console.warn("Cannot initialize mct-chart.");
|
||||
}
|
||||
|
||||
return api;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a fallback draw api.
|
||||
*/
|
||||
getFallbackDrawAPI: function (canvas, overlay) {
|
||||
const api = new CHARTS[1].API(canvas, overlay);
|
||||
CHARTS[1].ALLOCATIONS.push(api);
|
||||
|
||||
return api;
|
||||
},
|
||||
|
||||
releaseDrawAPI: function (api) {
|
||||
CHARTS.forEach(function (CHART_TYPE) {
|
||||
if (api instanceof CHART_TYPE.API) {
|
||||
CHART_TYPE.ALLOCATIONS.splice(CHART_TYPE.ALLOCATIONS.indexOf(api), 1);
|
||||
}
|
||||
});
|
||||
if (api.destroy) {
|
||||
api.destroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
@ -1,307 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'EventEmitter',
|
||||
'../lib/eventHelpers',
|
||||
'./MarkerShapes'
|
||||
], function (
|
||||
EventEmitter,
|
||||
eventHelpers,
|
||||
MARKER_SHAPES
|
||||
) {
|
||||
|
||||
// WebGL shader sources (for drawing plain colors)
|
||||
const FRAGMENT_SHADER = `
|
||||
precision mediump float;
|
||||
uniform vec4 uColor;
|
||||
uniform int uMarkerShape;
|
||||
|
||||
void main(void) {
|
||||
gl_FragColor = uColor;
|
||||
|
||||
if (uMarkerShape > 1) {
|
||||
vec2 clipSpacePointCoord = 2.0 * gl_PointCoord - 1.0;
|
||||
|
||||
if (uMarkerShape == 2) { // circle
|
||||
float distance = length(clipSpacePointCoord);
|
||||
|
||||
if (distance > 1.0) {
|
||||
discard;
|
||||
}
|
||||
} else if (uMarkerShape == 3) { // diamond
|
||||
float distance = abs(clipSpacePointCoord.x) + abs(clipSpacePointCoord.y);
|
||||
|
||||
if (distance > 1.0) {
|
||||
discard;
|
||||
}
|
||||
} else if (uMarkerShape == 4) { // triangle
|
||||
float x = clipSpacePointCoord.x;
|
||||
float y = clipSpacePointCoord.y;
|
||||
float distance = 2.0 * x - 1.0;
|
||||
float distance2 = -2.0 * x - 1.0;
|
||||
|
||||
if (distance > y || distance2 > y) {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const VERTEX_SHADER = `
|
||||
attribute vec2 aVertexPosition;
|
||||
uniform vec2 uDimensions;
|
||||
uniform vec2 uOrigin;
|
||||
uniform float uPointSize;
|
||||
|
||||
void main(void) {
|
||||
gl_Position = vec4(2.0 * ((aVertexPosition - uOrigin) / uDimensions) - vec2(1,1), 0, 1);
|
||||
gl_PointSize = uPointSize;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* Create a draw api utilizing WebGL.
|
||||
*
|
||||
* @constructor
|
||||
* @param {CanvasElement} canvas the canvas object to render upon
|
||||
* @throws {Error} an error is thrown if WebGL is unavailable.
|
||||
*/
|
||||
function DrawWebGL(canvas, overlay) {
|
||||
this.canvas = canvas;
|
||||
this.gl = this.canvas.getContext("webgl", { preserveDrawingBuffer: true })
|
||||
|| this.canvas.getContext("experimental-webgl", { preserveDrawingBuffer: true });
|
||||
|
||||
this.overlay = overlay;
|
||||
this.c2d = overlay.getContext('2d');
|
||||
if (!this.c2d) {
|
||||
throw new Error("No canvas 2d!");
|
||||
}
|
||||
|
||||
// Ensure a context was actually available before proceeding
|
||||
if (!this.gl) {
|
||||
throw new Error("WebGL unavailable.");
|
||||
}
|
||||
|
||||
this.initContext();
|
||||
|
||||
this.listenTo(this.canvas, "webglcontextlost", this.onContextLost, this);
|
||||
}
|
||||
|
||||
Object.assign(DrawWebGL.prototype, EventEmitter.prototype);
|
||||
eventHelpers.extend(DrawWebGL.prototype);
|
||||
|
||||
DrawWebGL.prototype.onContextLost = function (event) {
|
||||
this.emit('error');
|
||||
this.isContextLost = true;
|
||||
this.destroy();
|
||||
};
|
||||
|
||||
DrawWebGL.prototype.initContext = function () {
|
||||
// Initialize shaders
|
||||
this.vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
|
||||
this.gl.shaderSource(this.vertexShader, VERTEX_SHADER);
|
||||
this.gl.compileShader(this.vertexShader);
|
||||
|
||||
this.fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
|
||||
this.gl.shaderSource(this.fragmentShader, FRAGMENT_SHADER);
|
||||
this.gl.compileShader(this.fragmentShader);
|
||||
|
||||
// Assemble vertex/fragment shaders into programs
|
||||
this.program = this.gl.createProgram();
|
||||
this.gl.attachShader(this.program, this.vertexShader);
|
||||
this.gl.attachShader(this.program, this.fragmentShader);
|
||||
this.gl.linkProgram(this.program);
|
||||
this.gl.useProgram(this.program);
|
||||
|
||||
// Get locations for attribs/uniforms from the
|
||||
// shader programs (to pass values into shaders at draw-time)
|
||||
this.aVertexPosition = this.gl.getAttribLocation(this.program, "aVertexPosition");
|
||||
this.uColor = this.gl.getUniformLocation(this.program, "uColor");
|
||||
this.uMarkerShape = this.gl.getUniformLocation(this.program, "uMarkerShape");
|
||||
this.uDimensions = this.gl.getUniformLocation(this.program, "uDimensions");
|
||||
this.uOrigin = this.gl.getUniformLocation(this.program, "uOrigin");
|
||||
this.uPointSize = this.gl.getUniformLocation(this.program, "uPointSize");
|
||||
|
||||
this.gl.enableVertexAttribArray(this.aVertexPosition);
|
||||
|
||||
// Create a buffer to holds points which will be drawn
|
||||
this.buffer = this.gl.createBuffer();
|
||||
|
||||
// Enable blending, for smoothness
|
||||
this.gl.enable(this.gl.BLEND);
|
||||
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
};
|
||||
|
||||
DrawWebGL.prototype.destroy = function () {
|
||||
this.stopListening();
|
||||
};
|
||||
|
||||
// Convert from logical to physical x coordinates
|
||||
DrawWebGL.prototype.x = function (v) {
|
||||
return ((v - this.origin[0]) / this.dimensions[0]) * this.width;
|
||||
};
|
||||
|
||||
// Convert from logical to physical y coordinates
|
||||
DrawWebGL.prototype.y = function (v) {
|
||||
return this.height
|
||||
- ((v - this.origin[1]) / this.dimensions[1]) * this.height;
|
||||
};
|
||||
|
||||
DrawWebGL.prototype.doDraw = function (drawType, buf, color, points, shape) {
|
||||
if (this.isContextLost) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shapeCode = MARKER_SHAPES[shape] ? MARKER_SHAPES[shape].drawWebGL : 0;
|
||||
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
|
||||
this.gl.bufferData(this.gl.ARRAY_BUFFER, buf, this.gl.DYNAMIC_DRAW);
|
||||
this.gl.vertexAttribPointer(this.aVertexPosition, 2, this.gl.FLOAT, false, 0, 0);
|
||||
this.gl.uniform4fv(this.uColor, color);
|
||||
this.gl.uniform1i(this.uMarkerShape, shapeCode);
|
||||
this.gl.drawArrays(drawType, 0, points);
|
||||
};
|
||||
|
||||
DrawWebGL.prototype.clear = function () {
|
||||
if (this.isContextLost) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.height = this.canvas.height = this.canvas.offsetHeight;
|
||||
this.width = this.canvas.width = this.canvas.offsetWidth;
|
||||
this.overlay.height = this.overlay.offsetHeight;
|
||||
this.overlay.width = this.overlay.offsetWidth;
|
||||
// Set the viewport size; note that we use the width/height
|
||||
// that our WebGL context reports, which may be lower
|
||||
// resolution than the canvas we requested.
|
||||
this.gl.viewport(
|
||||
0,
|
||||
0,
|
||||
this.gl.drawingBufferWidth,
|
||||
this.gl.drawingBufferHeight
|
||||
);
|
||||
this.gl.clear(this.gl.COLOR_BUFFER_BIT + this.gl.DEPTH_BUFFER_BIT);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the logical boundaries of the chart.
|
||||
* @param {number[]} dimensions the horizontal and
|
||||
* vertical dimensions of the chart
|
||||
* @param {number[]} origin the horizontal/vertical
|
||||
* origin of the chart
|
||||
*/
|
||||
DrawWebGL.prototype.setDimensions = function (dimensions, origin) {
|
||||
this.dimensions = dimensions;
|
||||
this.origin = origin;
|
||||
if (this.isContextLost) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dimensions && dimensions.length > 0
|
||||
&& origin && origin.length > 0) {
|
||||
this.gl.uniform2fv(this.uDimensions, dimensions);
|
||||
this.gl.uniform2fv(this.uOrigin, origin);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Draw the supplied buffer as a line strip (a sequence
|
||||
* of line segments), in the chosen color.
|
||||
* @param {Float32Array} buf the line strip to draw,
|
||||
* in alternating x/y positions
|
||||
* @param {number[]} color the color to use when drawing
|
||||
* the line, as an RGBA color where each element
|
||||
* is in the range of 0.0-1.0
|
||||
* @param {number} points the number of points to draw
|
||||
*/
|
||||
DrawWebGL.prototype.drawLine = function (buf, color, points) {
|
||||
if (this.isContextLost) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.doDraw(this.gl.LINE_STRIP, buf, color, points);
|
||||
};
|
||||
|
||||
/**
|
||||
* Draw the buffer as points.
|
||||
*
|
||||
*/
|
||||
DrawWebGL.prototype.drawPoints = function (buf, color, points, pointSize, shape) {
|
||||
if (this.isContextLost) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.gl.uniform1f(this.uPointSize, pointSize);
|
||||
this.doDraw(this.gl.POINTS, buf, color, points, shape);
|
||||
};
|
||||
|
||||
/**
|
||||
* Draw a rectangle extending from one corner to another,
|
||||
* in the chosen color.
|
||||
* @param {number[]} min the first corner of the rectangle
|
||||
* @param {number[]} max the opposite corner
|
||||
* @param {number[]} color the color to use when drawing
|
||||
* the rectangle, as an RGBA color where each element
|
||||
* is in the range of 0.0-1.0
|
||||
*/
|
||||
DrawWebGL.prototype.drawSquare = function (min, max, color) {
|
||||
if (this.isContextLost) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.doDraw(this.gl.TRIANGLE_FAN, new Float32Array(
|
||||
min.concat([min[0], max[1]]).concat(max).concat([max[0], min[1]])
|
||||
), color, 4);
|
||||
};
|
||||
|
||||
DrawWebGL.prototype.drawLimitPoint = function (x, y, size) {
|
||||
this.c2d.fillRect(x + size, y, size, size);
|
||||
this.c2d.fillRect(x, y + size, size, size);
|
||||
this.c2d.fillRect(x - size, y, size, size);
|
||||
this.c2d.fillRect(x, y - size, size, size);
|
||||
};
|
||||
|
||||
DrawWebGL.prototype.drawLimitPoints = function (points, color, pointSize) {
|
||||
const limitSize = pointSize * 2;
|
||||
const offset = limitSize / 2;
|
||||
|
||||
const mappedColor = color.map(function (c, i) {
|
||||
return i < 3 ? Math.floor(c * 255) : (c);
|
||||
}).join(',');
|
||||
this.c2d.strokeStyle = "rgba(" + mappedColor + ")";
|
||||
this.c2d.fillStyle = "rgba(" + mappedColor + ")";
|
||||
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
this.drawLimitPoint(
|
||||
this.x(points[i].x) - offset,
|
||||
this.y(points[i].y) - offset,
|
||||
limitSize
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return DrawWebGL;
|
||||
});
|
@ -1,90 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([], function () {
|
||||
/**
|
||||
* @label string (required) display name of shape
|
||||
* @drawWebGL integer (unique, required) index provided to WebGL Fragment Shader
|
||||
* @drawC2D function (required) canvas2d draw function
|
||||
*/
|
||||
const MARKER_SHAPES = {
|
||||
point: {
|
||||
label: 'Point',
|
||||
drawWebGL: 1,
|
||||
drawC2D: function (x, y, size) {
|
||||
const offset = size / 2;
|
||||
|
||||
this.c2d.fillRect(x - offset, y - offset, size, size);
|
||||
}
|
||||
},
|
||||
circle: {
|
||||
label: 'Circle',
|
||||
drawWebGL: 2,
|
||||
drawC2D: function (x, y, size) {
|
||||
const radius = size / 2;
|
||||
|
||||
this.c2d.beginPath();
|
||||
this.c2d.arc(x, y, radius, 0, 2 * Math.PI, false);
|
||||
this.c2d.closePath();
|
||||
this.c2d.fill();
|
||||
}
|
||||
},
|
||||
diamond: {
|
||||
label: 'Diamond',
|
||||
drawWebGL: 3,
|
||||
drawC2D: function (x, y, size) {
|
||||
const offset = size / 2;
|
||||
const top = [x, y + offset];
|
||||
const right = [x + offset, y];
|
||||
const bottom = [x, y - offset];
|
||||
const left = [x - offset, y];
|
||||
|
||||
this.c2d.beginPath();
|
||||
this.c2d.moveTo(...top);
|
||||
this.c2d.lineTo(...right);
|
||||
this.c2d.lineTo(...bottom);
|
||||
this.c2d.lineTo(...left);
|
||||
this.c2d.closePath();
|
||||
this.c2d.fill();
|
||||
}
|
||||
},
|
||||
triangle: {
|
||||
label: 'Triangle',
|
||||
drawWebGL: 4,
|
||||
drawC2D: function (x, y, size) {
|
||||
const offset = size / 2;
|
||||
const v1 = [x, y - offset];
|
||||
const v2 = [x - offset, y + offset];
|
||||
const v3 = [x + offset, y + offset];
|
||||
|
||||
this.c2d.beginPath();
|
||||
this.c2d.moveTo(...v1);
|
||||
this.c2d.lineTo(...v2);
|
||||
this.c2d.lineTo(...v3);
|
||||
this.c2d.closePath();
|
||||
this.c2d.fill();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return MARKER_SHAPES;
|
||||
});
|
@ -1,55 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define(function () {
|
||||
/**
|
||||
* Simple directive that removes the elements pool when used in the
|
||||
* inspector region. Workaround until we have better control of screen
|
||||
* regions.
|
||||
*/
|
||||
return function HideElementPoolDirective() {
|
||||
return {
|
||||
restrict: "A",
|
||||
link: function ($scope, $element) {
|
||||
let splitter = $element.parent();
|
||||
|
||||
while (splitter[0].tagName !== 'MCT-SPLIT-PANE') {
|
||||
splitter = splitter.parent();
|
||||
}
|
||||
|
||||
[
|
||||
'.split-pane-component.pane.bottom',
|
||||
'mct-splitter'
|
||||
].forEach(function (selector) {
|
||||
const element = splitter[0].querySelectorAll(selector)[0];
|
||||
element.style.maxHeight = '0px';
|
||||
element.style.minHeight = '0px';
|
||||
});
|
||||
|
||||
splitter[0]
|
||||
.querySelectorAll('.split-pane-component.pane.top')[0]
|
||||
.style
|
||||
.bottom = '0px';
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user