mirror of
https://github.com/nasa/openmct.git
synced 2025-06-15 05:38:12 +00:00
Plots - remove legacy code (#3814)
* Remove Lecacy plot code * Adds tests for plots inspector * Set range max and min to undefined instead of 0
This commit is contained in:
@ -88,7 +88,6 @@
|
|||||||
openmct.install(openmct.plugins.ExampleImagery());
|
openmct.install(openmct.plugins.ExampleImagery());
|
||||||
openmct.install(openmct.plugins.PlanLayout());
|
openmct.install(openmct.plugins.PlanLayout());
|
||||||
openmct.install(openmct.plugins.Timeline());
|
openmct.install(openmct.plugins.Timeline());
|
||||||
openmct.install(openmct.plugins.PlotVue());
|
|
||||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||||
openmct.install(openmct.plugins.AutoflowView({
|
openmct.install(openmct.plugins.AutoflowView({
|
||||||
type: "telemetry.panel"
|
type: "telemetry.panel"
|
||||||
|
@ -36,7 +36,8 @@ define([
|
|||||||
'./views/installLegacyViews',
|
'./views/installLegacyViews',
|
||||||
'./policies/LegacyCompositionPolicyAdapter',
|
'./policies/LegacyCompositionPolicyAdapter',
|
||||||
'./actions/LegacyActionAdapter',
|
'./actions/LegacyActionAdapter',
|
||||||
'./services/LegacyPersistenceAdapter'
|
'./services/LegacyPersistenceAdapter',
|
||||||
|
'./services/ExportImageService'
|
||||||
], function (
|
], function (
|
||||||
ActionDialogDecorator,
|
ActionDialogDecorator,
|
||||||
AdapterCapability,
|
AdapterCapability,
|
||||||
@ -53,7 +54,8 @@ define([
|
|||||||
installLegacyViews,
|
installLegacyViews,
|
||||||
legacyCompositionPolicyAdapter,
|
legacyCompositionPolicyAdapter,
|
||||||
LegacyActionAdapter,
|
LegacyActionAdapter,
|
||||||
LegacyPersistenceAdapter
|
LegacyPersistenceAdapter,
|
||||||
|
ExportImageService
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
name: 'src/adapter',
|
name: 'src/adapter',
|
||||||
@ -82,6 +84,13 @@ define([
|
|||||||
"identifierService",
|
"identifierService",
|
||||||
"cacheService"
|
"cacheService"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "exportImageService",
|
||||||
|
"implementation": ExportImageService,
|
||||||
|
"depends": [
|
||||||
|
"dialogService"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
components: [
|
components: [
|
||||||
|
@ -24,13 +24,6 @@ define([
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Remove this when plots Angular implementation is deprecated
|
|
||||||
let parent = selection[0].length > 1 && selection[0][1].context.item;
|
|
||||||
if (parent && parent.type === 'time-strip') {
|
|
||||||
return (selectionContext.item.type === typeDefinition.key)
|
|
||||||
&& (typeDefinition.key !== 'telemetry.plot.overlay');
|
|
||||||
}
|
|
||||||
|
|
||||||
return selectionContext.item.type === typeDefinition.key;
|
return selectionContext.item.type === typeDefinition.key;
|
||||||
},
|
},
|
||||||
view: function (selection) {
|
view: function (selection) {
|
||||||
|
@ -486,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.
|
* Uniquely identifies a domain object.
|
||||||
*
|
*
|
||||||
|
@ -65,8 +65,7 @@
|
|||||||
<div ref="chartContainer"
|
<div ref="chartContainer"
|
||||||
class="gl-plot-chart-wrapper"
|
class="gl-plot-chart-wrapper"
|
||||||
>
|
>
|
||||||
<mct-chart :series-config="config"
|
<mct-chart :rectangles="rectangles"
|
||||||
:rectangles="rectangles"
|
|
||||||
:highlights="highlights"
|
:highlights="highlights"
|
||||||
@plotReinitializeCanvas="initCanvas"
|
@plotReinitializeCanvas="initCanvas"
|
||||||
/>
|
/>
|
@ -48,7 +48,7 @@ export default function PlotViewProvider(openmct) {
|
|||||||
name: 'Plot',
|
name: 'Plot',
|
||||||
cssClass: 'icon-telemetry',
|
cssClass: 'icon-telemetry',
|
||||||
canView(domainObject, objectPath) {
|
canView(domainObject, objectPath) {
|
||||||
return isCompactView(objectPath) && hasTelemetry(domainObject, openmct);
|
return hasTelemetry(domainObject, openmct);
|
||||||
},
|
},
|
||||||
|
|
||||||
view: function (domainObject, objectPath) {
|
view: function (domainObject, objectPath) {
|
@ -166,7 +166,6 @@ export default class YAxisModel extends Model {
|
|||||||
* Update yAxis format, values, and label from known series.
|
* Update yAxis format, values, and label from known series.
|
||||||
*/
|
*/
|
||||||
updateFromSeries(series) {
|
updateFromSeries(series) {
|
||||||
this.unset('displayRange');
|
|
||||||
const plotModel = this.plot.get('domainObject');
|
const plotModel = this.plot.get('domainObject');
|
||||||
const label = _.get(plotModel, 'configuration.yAxis.label');
|
const label = _.get(plotModel, 'configuration.yAxis.label');
|
||||||
const sampleSeries = series.first();
|
const sampleSeries = series.first();
|
@ -31,8 +31,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import PlotOptionsBrowse from "@/plugins/plot/vue/inspector/PlotOptionsBrowse.vue";
|
import PlotOptionsBrowse from "./PlotOptionsBrowse.vue";
|
||||||
import PlotOptionsEdit from "@/plugins/plot/vue/inspector/PlotOptionsEdit.vue";
|
import PlotOptionsEdit from "./PlotOptionsEdit.vue";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
PlotOptionsBrowse,
|
PlotOptionsBrowse,
|
@ -20,7 +20,7 @@
|
|||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<div v-if="config && loaded"
|
<div v-if="loaded"
|
||||||
class="js-plot-options-browse"
|
class="js-plot-options-browse"
|
||||||
>
|
>
|
||||||
<ul class="c-tree">
|
<ul class="c-tree">
|
||||||
@ -30,7 +30,9 @@
|
|||||||
:series="series"
|
:series="series"
|
||||||
/>
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="grid-properties">
|
<div v-if="plotSeries.length"
|
||||||
|
class="grid-properties"
|
||||||
|
>
|
||||||
<ul class="l-inspector-part">
|
<ul class="l-inspector-part">
|
||||||
<h2 title="Y axis settings for this object">Y Axis</h2>
|
<h2 title="Y axis settings for this object">Y Axis</h2>
|
||||||
<li class="grid-row">
|
<li class="grid-row">
|
||||||
@ -113,8 +115,8 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import PlotOptionsItem from "./PlotOptionsItem.vue";
|
import PlotOptionsItem from "./PlotOptionsItem.vue";
|
||||||
import configStore from "../single/configuration/configStore";
|
import configStore from "../configuration/configStore";
|
||||||
import eventHelpers from "../single/lib/eventHelpers";
|
import eventHelpers from "../lib/eventHelpers";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -123,7 +125,7 @@ export default {
|
|||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'domainObject'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
config: undefined,
|
config: {},
|
||||||
label: '',
|
label: '',
|
||||||
autoscale: '',
|
autoscale: '',
|
||||||
autoscalePadding: '',
|
autoscalePadding: '',
|
||||||
@ -145,8 +147,8 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
eventHelpers.extend(this);
|
eventHelpers.extend(this);
|
||||||
this.config = this.getConfig();
|
this.config = this.getConfig();
|
||||||
this.initConfiguration();
|
|
||||||
this.registerListeners();
|
this.registerListeners();
|
||||||
|
this.initConfiguration();
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
@ -186,7 +188,8 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
addSeries(series, index) {
|
addSeries(series, index) {
|
||||||
this.plotSeries[index] = series;
|
this.$set(this.plotSeries, index, series);
|
||||||
|
this.initConfiguration();
|
||||||
},
|
},
|
||||||
|
|
||||||
resetAllSeries() {
|
resetAllSeries() {
|
@ -20,7 +20,7 @@
|
|||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<div v-if="config && loaded"
|
<div v-if="loaded"
|
||||||
class="js-plot-options-edit"
|
class="js-plot-options-edit"
|
||||||
>
|
>
|
||||||
<ul class="c-tree">
|
<ul class="c-tree">
|
||||||
@ -31,13 +31,13 @@
|
|||||||
<series-form :series="series" />
|
<series-form :series="series" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<y-axis-form v-show="!!plotSeries.length"
|
<y-axis-form v-if="plotSeries.length"
|
||||||
class="grid-properties"
|
class="grid-properties"
|
||||||
:y-axis="config.yAxis"
|
:y-axis="config.yAxis"
|
||||||
/>
|
/>
|
||||||
<ul class="l-inspector-part">
|
<ul class="l-inspector-part">
|
||||||
<h2 title="Legend options">Legend</h2>
|
<h2 title="Legend options">Legend</h2>
|
||||||
<legend-form v-show="!!plotSeries.length"
|
<legend-form v-if="plotSeries.length"
|
||||||
class="grid-properties"
|
class="grid-properties"
|
||||||
:legend="config.legend"
|
:legend="config.legend"
|
||||||
/>
|
/>
|
||||||
@ -45,11 +45,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import SeriesForm from "@/plugins/plot/vue/inspector/forms/SeriesForm.vue";
|
import SeriesForm from "./forms/SeriesForm.vue";
|
||||||
import YAxisForm from "@/plugins/plot/vue/inspector/forms/YAxisForm.vue";
|
import YAxisForm from "./forms/YAxisForm.vue";
|
||||||
import LegendForm from "@/plugins/plot/vue/inspector/forms/LegendForm.vue";
|
import LegendForm from "./forms/LegendForm.vue";
|
||||||
import eventHelpers from "@/plugins/plot/vue/single/lib/eventHelpers";
|
import eventHelpers from "../lib/eventHelpers";
|
||||||
import configStore from "@/plugins/plot/vue/single/configuration/configStore";
|
import configStore from "../configuration/configStore";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -88,7 +88,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
addSeries(series, index) {
|
addSeries(series, index) {
|
||||||
this.plotSeries[index] = series;
|
this.$set(this.plotSeries, index, series);
|
||||||
},
|
},
|
||||||
|
|
||||||
resetAllSeries() {
|
resetAllSeries() {
|
@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<ul>
|
<ul>
|
||||||
<li class="c-tree__item menus-to-left">
|
<li class="c-tree__item menus-to-left"
|
||||||
|
:class="isAliasClass"
|
||||||
|
>
|
||||||
<span class="c-disclosure-triangle is-enabled flex-elem"
|
<span class="c-disclosure-triangle is-enabled flex-elem"
|
||||||
:class="expandedCssClass"
|
:class="expandedCssClass"
|
||||||
@click="toggleExpanded"
|
@click="toggleExpanded"
|
||||||
@ -78,7 +80,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct'],
|
inject: ['openmct', 'domainObject', 'path'],
|
||||||
props: {
|
props: {
|
||||||
series: {
|
series: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -93,17 +95,20 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
getSeriesClass() {
|
isAliasClass() {
|
||||||
let cssClass = '';
|
let cssClass = '';
|
||||||
let legacyObject = this.openmct.legacyObject(this.series.domainObject);
|
const domainObjectPath = [this.series.domainObject, ...this.path];
|
||||||
let location = legacyObject.getCapability('location');
|
if (this.openmct.objects.isObjectPathToALink(this.series.domainObject, domainObjectPath)) {
|
||||||
if (location && location.isLink()) {
|
cssClass = 'is-alias';
|
||||||
cssClass = 'l-icon-link';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let type = legacyObject.getCapability('type');
|
return cssClass;
|
||||||
if (type) {
|
},
|
||||||
cssClass = `${cssClass} ${type.getCssClass()}`;
|
getSeriesClass() {
|
||||||
|
let cssClass = '';
|
||||||
|
let type = this.openmct.types.get(this.series.domainObject.type);
|
||||||
|
if (type.definition.cssClass) {
|
||||||
|
cssClass = `${cssClass} ${type.definition.cssClass}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cssClass;
|
return cssClass;
|
@ -11,16 +11,20 @@ export default function PlotsInspectorViewProvider(openmct) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let parent = selection[0].length > 1 && selection[0][1].context.item;
|
|
||||||
let object = selection[0][0].context.item;
|
let object = selection[0][0].context.item;
|
||||||
|
|
||||||
return parent
|
return object
|
||||||
&& parent.type === 'time-strip'
|
|
||||||
&& object
|
|
||||||
&& object.type === 'telemetry.plot.overlay';
|
&& object.type === 'telemetry.plot.overlay';
|
||||||
},
|
},
|
||||||
view: function (selection) {
|
view: function (selection) {
|
||||||
let component;
|
let component;
|
||||||
|
let objectPath;
|
||||||
|
|
||||||
|
if (selection.length) {
|
||||||
|
objectPath = selection[0].map((selectionItem) => {
|
||||||
|
return selectionItem.context.item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
show: function (element) {
|
show: function (element) {
|
||||||
@ -31,7 +35,8 @@ export default function PlotsInspectorViewProvider(openmct) {
|
|||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
openmct,
|
openmct,
|
||||||
domainObject: openmct.selection.get()[0][0].context.item
|
domainObject: selection[0][0].context.item,
|
||||||
|
path: objectPath
|
||||||
},
|
},
|
||||||
template: '<plot-options></plot-options>'
|
template: '<plot-options></plot-options>'
|
||||||
});
|
});
|
@ -83,7 +83,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import {coerce, objectPath, validate} from "@/plugins/plot/vue/inspector/forms/formUtil";
|
import {coerce, objectPath, validate} from "./formUtil";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
export default {
|
export default {
|
@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<ul>
|
<ul>
|
||||||
<li class="c-tree__item menus-to-left">
|
<li class="c-tree__item menus-to-left"
|
||||||
|
:class="isAliasCss"
|
||||||
|
>
|
||||||
<span class="c-disclosure-triangle is-enabled flex-elem"
|
<span class="c-disclosure-triangle is-enabled flex-elem"
|
||||||
:class="expandedCssClass"
|
:class="expandedCssClass"
|
||||||
@click="toggleExpanded"
|
@click="toggleExpanded"
|
||||||
@ -8,7 +10,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<div :class="objectLabelCss">
|
<div :class="objectLabelCss">
|
||||||
<div class="c-object-label__type-icon"
|
<div class="c-object-label__type-icon"
|
||||||
:class="[seriesCss, linkCss]"
|
:class="[seriesCss]"
|
||||||
>
|
>
|
||||||
<span class="is-status__indicator"
|
<span class="is-status__indicator"
|
||||||
title="This item is missing or suspect"
|
title="This item is missing or suspect"
|
||||||
@ -142,12 +144,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { MARKER_SHAPES } from "../../single/draw/MarkerShapes";
|
import { MARKER_SHAPES } from "../../draw/MarkerShapes";
|
||||||
import { objectPath, validate, coerce } from "./formUtil";
|
import { objectPath, validate, coerce } from "./formUtil";
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'domainObject', 'path'],
|
||||||
props: {
|
props: {
|
||||||
series: {
|
series: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -179,17 +181,15 @@ export default {
|
|||||||
return this.status ? `c-object-label is-status--${this.status}'` : 'c-object-label';
|
return this.status ? `c-object-label is-status--${this.status}'` : 'c-object-label';
|
||||||
},
|
},
|
||||||
seriesCss() {
|
seriesCss() {
|
||||||
let legacyObject = this.openmct.legacyObject(this.series.domainObject);
|
let type = this.openmct.types.get(this.series.domainObject.type);
|
||||||
let type = legacyObject.getCapability('type');
|
|
||||||
|
|
||||||
return type ? `c-object-label__type-icon ${type.getCssClass()}` : `c-object-label__type-icon`;
|
return type.definition.cssClass ? `c-object-label__type-icon ${type.definition.cssClass}` : `c-object-label__type-icon`;
|
||||||
},
|
},
|
||||||
linkCss() {
|
isAliasCss() {
|
||||||
let cssClass = '';
|
let cssClass = '';
|
||||||
let legacyObject = this.openmct.legacyObject(this.series.domainObject);
|
const domainObjectPath = [this.series.domainObject, ...this.path];
|
||||||
let location = legacyObject.getCapability('location');
|
if (this.openmct.objects.isObjectPathToALink(this.series.domainObject, domainObjectPath)) {
|
||||||
if (location && location.isLink()) {
|
cssClass = 'is-alias';
|
||||||
cssClass = 'l-icon-link';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cssClass;
|
return cssClass;
|
@ -180,7 +180,10 @@ export default {
|
|||||||
this.autoscale = this.yAxis.get('autoscale');
|
this.autoscale = this.yAxis.get('autoscale');
|
||||||
this.autoscalePadding = this.yAxis.get('autoscalePadding');
|
this.autoscalePadding = this.yAxis.get('autoscalePadding');
|
||||||
const range = this.yAxis.get('range');
|
const range = this.yAxis.get('range');
|
||||||
if (range) {
|
if (!range) {
|
||||||
|
this.rangeMin = undefined;
|
||||||
|
this.rangeMax = undefined;
|
||||||
|
} else {
|
||||||
this.rangeMin = range.min;
|
this.rangeMin = range.min;
|
||||||
this.rangeMax = range.max;
|
this.rangeMax = range.max;
|
||||||
}
|
}
|
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.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import Plot from '../single/Plot.vue';
|
import Plot from '../Plot.vue';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
export default function OverlayPlotViewProvider(openmct) {
|
export default function OverlayPlotViewProvider(openmct) {
|
||||||
@ -33,11 +33,11 @@ export default function OverlayPlotViewProvider(openmct) {
|
|||||||
name: 'Overlay Plot',
|
name: 'Overlay Plot',
|
||||||
cssClass: 'icon-telemetry',
|
cssClass: 'icon-telemetry',
|
||||||
canView(domainObject, objectPath) {
|
canView(domainObject, objectPath) {
|
||||||
return isCompactView(objectPath) && domainObject.type === 'telemetry.plot.overlay';
|
return domainObject.type === 'telemetry.plot.overlay';
|
||||||
},
|
},
|
||||||
|
|
||||||
canEdit(domainObject, objectPath) {
|
canEdit(domainObject, objectPath) {
|
||||||
return isCompactView(objectPath) && domainObject.type === 'telemetry.plot.overlay';
|
return domainObject.type === 'telemetry.plot.overlay';
|
||||||
},
|
},
|
||||||
|
|
||||||
view: function (domainObject, objectPath) {
|
view: function (domainObject, objectPath) {
|
@ -20,262 +20,52 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([
|
import PlotViewProvider from './PlotViewProvider';
|
||||||
"./src/chart/MCTChartDirective",
|
import OverlayPlotViewProvider from './overlayPlot/OverlayPlotViewProvider';
|
||||||
"./src/plot/MCTPlotDirective",
|
import StackedPlotViewProvider from './stackedPlot/StackedPlotViewProvider';
|
||||||
'./src/plot/MCTTicksDirective',
|
import PlotsInspectorViewProvider from './inspector/PlotsInspectorViewProvider';
|
||||||
"./src/telemetry/MCTOverlayPlot",
|
import OverlayPlotCompositionPolicy from './overlayPlot/OverlayPlotCompositionPolicy';
|
||||||
"./src/telemetry/PlotController",
|
import StackedPlotCompositionPolicy from './stackedPlot/StackedPlotCompositionPolicy';
|
||||||
"./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
|
|
||||||
) {
|
|
||||||
|
|
||||||
let installed = false;
|
export default function () {
|
||||||
|
return function install(openmct) {
|
||||||
|
|
||||||
function PlotPlugin() {
|
openmct.types.addType('telemetry.plot.overlay', {
|
||||||
return function install(openmct) {
|
key: "telemetry.plot.overlay",
|
||||||
if (installed) {
|
name: "Overlay Plot",
|
||||||
return;
|
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", {
|
openmct.objectViews.addProvider(new StackedPlotViewProvider(openmct));
|
||||||
"name": "Plot view for telemetry, reborn",
|
openmct.objectViews.addProvider(new OverlayPlotViewProvider(openmct));
|
||||||
"extensions": {
|
openmct.objectViews.addProvider(new PlotViewProvider(openmct));
|
||||||
"policies": [
|
openmct.inspectorViews.addProvider(new PlotsInspectorViewProvider(openmct));
|
||||||
{
|
openmct.composition.addPolicy(new OverlayPlotCompositionPolicy(openmct).allow);
|
||||||
"category": "view",
|
openmct.composition.addPolicy(new StackedPlotCompositionPolicy(openmct).allow);
|
||||||
"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.legacyRegistry.enable("openmct/plot");
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return PlotPlugin;
|
|
||||||
});
|
|
||||||
|
@ -23,11 +23,11 @@
|
|||||||
import {createMouseEvent, createOpenMct, resetApplicationState, spyOnBuiltins} from "utils/testing";
|
import {createMouseEvent, createOpenMct, resetApplicationState, spyOnBuiltins} from "utils/testing";
|
||||||
import PlotVuePlugin from "./plugin";
|
import PlotVuePlugin from "./plugin";
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import StackedPlot from "../stackedPlot/StackedPlot.vue";
|
import StackedPlot from "./stackedPlot/StackedPlot.vue";
|
||||||
import configStore from "@/plugins/plot/vue/single/configuration/configStore";
|
import configStore from "./configuration/configStore";
|
||||||
import EventEmitter from "EventEmitter";
|
import EventEmitter from "EventEmitter";
|
||||||
import PlotOptions from "../inspector/PlotOptions.vue";
|
import PlotOptions from "./inspector/PlotOptions.vue";
|
||||||
import PlotConfigurationModel from "@/plugins/plot/vue/single/configuration/PlotConfigurationModel";
|
import PlotConfigurationModel from "./configuration/PlotConfigurationModel";
|
||||||
|
|
||||||
describe("the plugin", function () {
|
describe("the plugin", function () {
|
||||||
let element;
|
let element;
|
@ -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';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
});
|
|
@ -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.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'./Region'
|
|
||||||
],
|
|
||||||
function (Region) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the a default Inspector region. Captured in a class to
|
|
||||||
* allow for modular extension and customization of regions based on
|
|
||||||
* the typical case.
|
|
||||||
* @memberOf platform/commonUI/regions
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function InspectorRegion() {
|
|
||||||
Region.call(this, {'name': 'Inspector'});
|
|
||||||
|
|
||||||
this.buildRegion();
|
|
||||||
}
|
|
||||||
|
|
||||||
InspectorRegion.prototype = Object.create(Region.prototype);
|
|
||||||
InspectorRegion.prototype.constructor = Region;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
InspectorRegion.prototype.buildRegion = function () {
|
|
||||||
const metadataRegion = {
|
|
||||||
name: 'metadata',
|
|
||||||
title: 'Metadata Region',
|
|
||||||
// Which modes should the region part be visible in? If
|
|
||||||
// nothing provided here, then assumed that part is visible
|
|
||||||
// in both. The visibility or otherwise of a region part
|
|
||||||
// should be decided by a policy. In this case, 'modes' is a
|
|
||||||
// shortcut that is used by the EditableRegionPolicy.
|
|
||||||
modes: ['browse', 'edit'],
|
|
||||||
content: {
|
|
||||||
key: 'object-properties'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.addRegion(new Region(metadataRegion), 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
return InspectorRegion;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,41 +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([
|
|
||||||
'./Region'
|
|
||||||
], function (
|
|
||||||
Region
|
|
||||||
) {
|
|
||||||
|
|
||||||
const PlotBrowseRegion = new Region({
|
|
||||||
name: "plot-options",
|
|
||||||
title: "Plot Options",
|
|
||||||
modes: ['browse'],
|
|
||||||
content: {
|
|
||||||
key: "plot-options-browse"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return PlotBrowseRegion;
|
|
||||||
|
|
||||||
});
|
|
@ -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 openmct 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 openmct 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([
|
|
||||||
'./Region'
|
|
||||||
], function (
|
|
||||||
Region
|
|
||||||
) {
|
|
||||||
|
|
||||||
const PlotEditRegion = new Region({
|
|
||||||
name: "plot-options",
|
|
||||||
title: "Plot Options",
|
|
||||||
modes: ['edit'],
|
|
||||||
content: {
|
|
||||||
key: "plot-options-edit"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return PlotEditRegion;
|
|
||||||
});
|
|
@ -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([
|
|
||||||
'./InspectorRegion',
|
|
||||||
'./PlotBrowseRegion',
|
|
||||||
'./PlotEditRegion'
|
|
||||||
], function (
|
|
||||||
InspectorRegion,
|
|
||||||
PlotBrowseRegion,
|
|
||||||
PlotEditRegion
|
|
||||||
) {
|
|
||||||
|
|
||||||
const plotInspector = new InspectorRegion();
|
|
||||||
|
|
||||||
plotInspector.addRegion(PlotBrowseRegion);
|
|
||||||
plotInspector.addRegion(PlotEditRegion);
|
|
||||||
|
|
||||||
return plotInspector;
|
|
||||||
});
|
|
@ -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([
|
|
||||||
'./PlotModelFormController'
|
|
||||||
], function (
|
|
||||||
PlotModelFormController
|
|
||||||
) {
|
|
||||||
|
|
||||||
const PlotLegendFormController = PlotModelFormController.extend({
|
|
||||||
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'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
return PlotLegendFormController;
|
|
||||||
});
|
|
||||||
|
|
@ -1,181 +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/eventHelpers',
|
|
||||||
'../lib/extend',
|
|
||||||
'lodash'
|
|
||||||
], function (
|
|
||||||
eventHelpers,
|
|
||||||
extend,
|
|
||||||
_
|
|
||||||
) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic type for model controllers. Should be extended to build a form
|
|
||||||
* for a specific model.
|
|
||||||
*/
|
|
||||||
function PlotModelFormController($scope, openmct, attrs) {
|
|
||||||
this.$scope = $scope;
|
|
||||||
this.openmct = openmct;
|
|
||||||
this.attrs = attrs;
|
|
||||||
|
|
||||||
if (this.isReady()) {
|
|
||||||
this.initializeScope();
|
|
||||||
} else {
|
|
||||||
this.$scope.$watch(this.isReady.bind(this), function (isReady) {
|
|
||||||
if (isReady) {
|
|
||||||
this.initializeScope();
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PlotModelFormController.extend = extend;
|
|
||||||
eventHelpers.extend(PlotModelFormController.prototype);
|
|
||||||
|
|
||||||
PlotModelFormController.prototype.isReady = function () {
|
|
||||||
return Boolean(this.$scope.formDomainObject)
|
|
||||||
&& Boolean(this.$scope.$eval(this.attrs.formModel));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize scope is called when the formDomainObject has been set.
|
|
||||||
* This may be deferred until after the controller construction in cases
|
|
||||||
* where the object has not yet loaded.
|
|
||||||
*/
|
|
||||||
PlotModelFormController.prototype.initializeScope = function () {
|
|
||||||
this.domainObject = this.$scope.formDomainObject;
|
|
||||||
this.model = this.$scope.$eval(this.attrs.formModel);
|
|
||||||
|
|
||||||
this.unlisten = this.openmct.objects.observe(
|
|
||||||
this.domainObject,
|
|
||||||
'*',
|
|
||||||
this.updateDomainObject.bind(this)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.$scope.form = {};
|
|
||||||
this.$scope.validation = {};
|
|
||||||
|
|
||||||
this.listenTo(this.$scope, '$destroy', this.destroy, this);
|
|
||||||
this.initialize();
|
|
||||||
this.initForm();
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotModelFormController.prototype.updateDomainObject = function (domainObject) {
|
|
||||||
this.domainObject = domainObject;
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotModelFormController.prototype.destroy = function () {
|
|
||||||
this.stopListening();
|
|
||||||
this.model.stopListening(this.$scope);
|
|
||||||
this.unlisten();
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotModelFormController.prototype.fields = [];
|
|
||||||
|
|
||||||
/** override for custom initializer **/
|
|
||||||
PlotModelFormController.prototype.initialize = function () {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotModelFormController.prototype.initForm = function () {
|
|
||||||
this.fields.forEach(function (field) {
|
|
||||||
this.linkFields(
|
|
||||||
field.modelProp,
|
|
||||||
field.formProp,
|
|
||||||
field.coerce,
|
|
||||||
field.validate,
|
|
||||||
field.objectPath
|
|
||||||
);
|
|
||||||
}, this);
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotModelFormController.prototype.linkFields = function (
|
|
||||||
prop,
|
|
||||||
formProp,
|
|
||||||
coerce,
|
|
||||||
validate,
|
|
||||||
objectPath
|
|
||||||
) {
|
|
||||||
if (!formProp) {
|
|
||||||
formProp = prop;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formPath = 'form.' + formProp;
|
|
||||||
let self = this;
|
|
||||||
|
|
||||||
if (!coerce) {
|
|
||||||
coerce = function (v) {
|
|
||||||
return v;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validate) {
|
|
||||||
validate = function () {
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (objectPath && (typeof objectPath !== "function")) {
|
|
||||||
const staticObjectPath = objectPath;
|
|
||||||
objectPath = function () {
|
|
||||||
return staticObjectPath;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.listenTo(this.model, 'change:' + prop, function (newVal, oldVal) {
|
|
||||||
if (!_.isEqual(coerce(_.get(self.$scope, formPath)), coerce(newVal))) {
|
|
||||||
_.set(self.$scope, formPath, coerce(newVal));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.model.listenTo(this.$scope, 'change:' + formPath, (newVal, oldVal) => {
|
|
||||||
const validationResult = validate(newVal, this.model);
|
|
||||||
if (validationResult === true) {
|
|
||||||
delete this.$scope.validation[formProp];
|
|
||||||
} else {
|
|
||||||
this.$scope.validation[formProp] = validationResult;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_.isEqual(coerce(newVal), coerce(this.model.get(prop)))) {
|
|
||||||
return; // Don't trigger excessive mutations.
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_.isEqual(coerce(newVal), coerce(oldVal))) {
|
|
||||||
this.model.set(prop, coerce(newVal));
|
|
||||||
if (objectPath) {
|
|
||||||
this.openmct.objects.mutate(
|
|
||||||
this.domainObject,
|
|
||||||
objectPath(this.domainObject, this.model),
|
|
||||||
coerce(newVal)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_.set(this.$scope, formPath, coerce(this.model.get(prop)));
|
|
||||||
};
|
|
||||||
|
|
||||||
return PlotModelFormController;
|
|
||||||
});
|
|
||||||
|
|
@ -1,93 +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([
|
|
||||||
'../configuration/configStore',
|
|
||||||
'../lib/eventHelpers',
|
|
||||||
'objectUtils'
|
|
||||||
], function (
|
|
||||||
configStore,
|
|
||||||
eventHelpers,
|
|
||||||
objectUtils
|
|
||||||
) {
|
|
||||||
|
|
||||||
function PlotOptionsController($scope, openmct, $timeout) {
|
|
||||||
this.$scope = $scope;
|
|
||||||
this.openmct = openmct;
|
|
||||||
this.$timeout = $timeout;
|
|
||||||
|
|
||||||
this.configId = $scope.domainObject.getId();
|
|
||||||
this.setUpScope();
|
|
||||||
}
|
|
||||||
|
|
||||||
eventHelpers.extend(PlotOptionsController.prototype);
|
|
||||||
|
|
||||||
PlotOptionsController.prototype.updateDomainObject = function (domainObject) {
|
|
||||||
this.domainObject = domainObject;
|
|
||||||
this.$scope.formDomainObject = domainObject;
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotOptionsController.prototype.destroy = function () {
|
|
||||||
this.stopListening();
|
|
||||||
this.unlisten();
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotOptionsController.prototype.setUpScope = function () {
|
|
||||||
const config = configStore.get(this.configId);
|
|
||||||
if (!config) {
|
|
||||||
this.$timeout(this.setUpScope.bind(this));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.config = this.$scope.config = config;
|
|
||||||
this.$scope.plotSeries = [];
|
|
||||||
|
|
||||||
this.updateDomainObject(this.config.get('domainObject'));
|
|
||||||
this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject.bind(this));
|
|
||||||
|
|
||||||
this.listenTo(this.$scope, '$destroy', this.destroy, this);
|
|
||||||
this.listenTo(config.series, 'add', this.addSeries, this);
|
|
||||||
this.listenTo(config.series, 'remove', this.resetAllSeries, this);
|
|
||||||
|
|
||||||
config.series.forEach(this.addSeries, this);
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotOptionsController.prototype.addSeries = function (series, index) {
|
|
||||||
this.$timeout(function () {
|
|
||||||
this.$scope.plotSeries[index] = series;
|
|
||||||
series.locateOldObject(this.$scope.domainObject);
|
|
||||||
}.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotOptionsController.prototype.resetAllSeries = function (series, index) {
|
|
||||||
this.$timeout(function () {
|
|
||||||
this.$scope.plotSeries = [];
|
|
||||||
this.$timeout(function () {
|
|
||||||
this.config.series.forEach(this.addSeries, this);
|
|
||||||
}.bind(this));
|
|
||||||
}.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
return PlotOptionsController;
|
|
||||||
});
|
|
||||||
|
|
@ -1,139 +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([
|
|
||||||
'./PlotModelFormController',
|
|
||||||
'../draw/MarkerShapes',
|
|
||||||
'lodash'
|
|
||||||
], function (
|
|
||||||
PlotModelFormController,
|
|
||||||
MARKER_SHAPES,
|
|
||||||
_
|
|
||||||
) {
|
|
||||||
|
|
||||||
function 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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const PlotSeriesFormController = PlotModelFormController.extend({
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.model.get('color');
|
|
||||||
const otherSeriesWithColor = this.model.collection.filter(function (s) {
|
|
||||||
return s.get('color') === color;
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
this.model.set('color', color);
|
|
||||||
|
|
||||||
const getPath = dynamicPathForKey('color');
|
|
||||||
const seriesColorPath = getPath(this.domainObject, this.model);
|
|
||||||
|
|
||||||
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()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populate scope with options and add setColor callback.
|
|
||||||
*/
|
|
||||||
initialize: function () {
|
|
||||||
this.$scope.setColor = this.setColor.bind(this);
|
|
||||||
|
|
||||||
const metadata = this.model.metadata;
|
|
||||||
this.$scope.yKeyOptions = metadata
|
|
||||||
.valuesForHints(['range'])
|
|
||||||
.map(function (o) {
|
|
||||||
return {
|
|
||||||
name: o.key,
|
|
||||||
value: o.key
|
|
||||||
};
|
|
||||||
});
|
|
||||||
this.$scope.markerShapeOptions = Object.entries(MARKER_SHAPES)
|
|
||||||
.map(([key, obj]) => {
|
|
||||||
return {
|
|
||||||
name: obj.label,
|
|
||||||
value: key
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
modelProp: 'yKey',
|
|
||||||
objectPath: dynamicPathForKey('yKey')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
modelProp: 'interpolate',
|
|
||||||
objectPath: dynamicPathForKey('interpolate')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
modelProp: 'markers',
|
|
||||||
objectPath: dynamicPathForKey('markers')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
modelProp: 'markerShape',
|
|
||||||
objectPath: dynamicPathForKey('markerShape')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
modelProp: 'markerSize',
|
|
||||||
coerce: Number,
|
|
||||||
objectPath: dynamicPathForKey('markerSize')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
modelProp: 'alarmMarkers',
|
|
||||||
coerce: Boolean,
|
|
||||||
objectPath: dynamicPathForKey('alarmMarkers')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
return PlotSeriesFormController;
|
|
||||||
});
|
|
||||||
|
|
@ -1,104 +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([
|
|
||||||
'./PlotModelFormController'
|
|
||||||
], function (
|
|
||||||
PlotModelFormController
|
|
||||||
) {
|
|
||||||
|
|
||||||
const PlotYAxisFormController = PlotModelFormController.extend({
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
return PlotYAxisFormController;
|
|
||||||
});
|
|
||||||
|
|
@ -1,99 +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 () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typeDef {object} PartContents
|
|
||||||
* @property {string} key If the part is defined as a
|
|
||||||
* representation, the key corresponding to the representation.
|
|
||||||
* @memberOf platform/commonUI/regions
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typeDef {object} RegionConfiguration
|
|
||||||
* @property {string} name A unique name for this region part
|
|
||||||
* @property {PartContents} [content] the details of the region being
|
|
||||||
* defined
|
|
||||||
* @property {Array<string>} [modes] the modes that this region
|
|
||||||
* should be included in. Options are 'edit' and 'browse'. By
|
|
||||||
* default, will be included in both. Inclusion of regions is
|
|
||||||
* determined by policies of category 'region'. By default, the
|
|
||||||
* {EditableRegionPolicy} will be applied.
|
|
||||||
* @memberOf platform/commonUI/regions
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the interface for a screen region. A screen region is a
|
|
||||||
* section of the browse an edit screens for an object. Regions are
|
|
||||||
* declared in object type definitions.
|
|
||||||
* @memberOf platform/commonUI/regions
|
|
||||||
* @abstract
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function Region(configuration) {
|
|
||||||
configuration = configuration || {};
|
|
||||||
this.name = configuration.name;
|
|
||||||
this.content = configuration.content;
|
|
||||||
this.modes = configuration.modes;
|
|
||||||
|
|
||||||
this.regions = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a sub-region to this region.
|
|
||||||
* @param {Region} region the part to add
|
|
||||||
* @param {number} [index] the position to insert the region. By default
|
|
||||||
* will add to the end
|
|
||||||
*/
|
|
||||||
Region.prototype.addRegion = function (region, index) {
|
|
||||||
if (index) {
|
|
||||||
this.regions.splice(index, 0, region);
|
|
||||||
} else {
|
|
||||||
this.regions.push(region);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a sub-region from this region.
|
|
||||||
* @param {Region | number | strnig} region The region to
|
|
||||||
* remove. If a number, will remove the region at that index. If a
|
|
||||||
* string, will remove the region with the matching name. If an
|
|
||||||
* object, will attempt to remove that object from the Region
|
|
||||||
*/
|
|
||||||
Region.prototype.removeRegion = function (region) {
|
|
||||||
if (typeof region === 'number') {
|
|
||||||
this.regions.splice(region, 1);
|
|
||||||
} else if (typeof region === 'string') {
|
|
||||||
this.regions = this.regions.filter(function (thisRegion) {
|
|
||||||
return thisRegion.name !== region;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.regions.splice(this.regions.indexOf(region), 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return Region;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,208 +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 () {
|
|
||||||
|
|
||||||
const COLOR_PALETTE = [
|
|
||||||
[0x20, 0xB2, 0xAA],
|
|
||||||
[0x9A, 0xCD, 0x32],
|
|
||||||
[0xFF, 0x8C, 0x00],
|
|
||||||
[0xD2, 0xB4, 0x8C],
|
|
||||||
[0x40, 0xE0, 0xD0],
|
|
||||||
[0x41, 0x69, 0xFF],
|
|
||||||
[0xFF, 0xD7, 0x00],
|
|
||||||
[0x6A, 0x5A, 0xCD],
|
|
||||||
[0xEE, 0x82, 0xEE],
|
|
||||||
[0xCC, 0x99, 0x66],
|
|
||||||
[0x99, 0xCC, 0xCC],
|
|
||||||
[0x66, 0xCC, 0x33],
|
|
||||||
[0xFF, 0xCC, 0x00],
|
|
||||||
[0xFF, 0x66, 0x33],
|
|
||||||
[0xCC, 0x66, 0xFF],
|
|
||||||
[0xFF, 0x00, 0x66],
|
|
||||||
[0xFF, 0xFF, 0x00],
|
|
||||||
[0x80, 0x00, 0x80],
|
|
||||||
[0x00, 0x86, 0x8B],
|
|
||||||
[0x00, 0x8A, 0x00],
|
|
||||||
[0xFF, 0x00, 0x00],
|
|
||||||
[0x00, 0x00, 0xFF],
|
|
||||||
[0xF5, 0xDE, 0xB3],
|
|
||||||
[0xBC, 0x8F, 0x8F],
|
|
||||||
[0x46, 0x82, 0xB4],
|
|
||||||
[0xFF, 0xAF, 0xAF],
|
|
||||||
[0x43, 0xCD, 0x80],
|
|
||||||
[0xCD, 0xC1, 0xC5],
|
|
||||||
[0xA0, 0x52, 0x2D],
|
|
||||||
[0x64, 0x95, 0xED]
|
|
||||||
];
|
|
||||||
|
|
||||||
function isDefaultColor(color) {
|
|
||||||
const a = color.asIntegerArray();
|
|
||||||
|
|
||||||
return COLOR_PALETTE.some(function (b) {
|
|
||||||
return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A representation of a color that allows conversions between different
|
|
||||||
* formats.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function Color(integerArray) {
|
|
||||||
this.integerArray = integerArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
Color.fromHexString = function (hexString) {
|
|
||||||
if (!/#([0-9a-fA-F]{2}){2}/.test(hexString)) {
|
|
||||||
throw new Error(
|
|
||||||
'Invalid input "'
|
|
||||||
+ hexString
|
|
||||||
+ '". Hex string must be in CSS format e.g. #00FF00'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Color([
|
|
||||||
parseInt(hexString.slice(1, 3), 16),
|
|
||||||
parseInt(hexString.slice(3, 5), 16),
|
|
||||||
parseInt(hexString.slice(5, 7), 16)
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return color as a three element array of RGB values, where each value
|
|
||||||
* is a integer in the range of 0-255.
|
|
||||||
*
|
|
||||||
* @return {number[]} the color, as integer RGB values
|
|
||||||
*/
|
|
||||||
Color.prototype.asIntegerArray = function () {
|
|
||||||
return this.integerArray.map(function (c) {
|
|
||||||
return c;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return color as a string using #-prefixed six-digit RGB hex notation
|
|
||||||
* (e.g. #FF0000). See http://www.w3.org/TR/css3-color/#rgb-color.
|
|
||||||
*
|
|
||||||
* @return {string} the color, as a style-friendly string
|
|
||||||
*/
|
|
||||||
|
|
||||||
Color.prototype.asHexString = function () {
|
|
||||||
return '#' + this.integerArray.map(function (c) {
|
|
||||||
return (c < 16 ? '0' : '') + c.toString(16);
|
|
||||||
}).join('');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return color as a RGBA float array.
|
|
||||||
*
|
|
||||||
* This format is present specifically to support use with
|
|
||||||
* WebGL, which expects colors of that form.
|
|
||||||
*
|
|
||||||
* @return {number[]} the color, as floating-point RGBA values
|
|
||||||
*/
|
|
||||||
Color.prototype.asRGBAArray = function () {
|
|
||||||
return this.integerArray.map(function (c) {
|
|
||||||
return c / 255.0;
|
|
||||||
}).concat([1]);
|
|
||||||
};
|
|
||||||
|
|
||||||
Color.prototype.equalTo = function (otherColor) {
|
|
||||||
return this.asHexString() === otherColor.asHexString();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A color palette stores a set of colors and allows for different
|
|
||||||
* methods of color allocation.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ColorPalette() {
|
|
||||||
const allColors = this.allColors = COLOR_PALETTE.map(function (color) {
|
|
||||||
return new Color(color);
|
|
||||||
});
|
|
||||||
this.colorGroups = [[], [], []];
|
|
||||||
for (let i = 0; i < allColors.length; i++) {
|
|
||||||
this.colorGroups[i % 3].push(allColors[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
ColorPalette.prototype.groups = function () {
|
|
||||||
return this.colorGroups;
|
|
||||||
};
|
|
||||||
|
|
||||||
ColorPalette.prototype.reset = function () {
|
|
||||||
this.availableColors = this.allColors.slice();
|
|
||||||
};
|
|
||||||
|
|
||||||
ColorPalette.prototype.remove = function (color) {
|
|
||||||
this.availableColors = this.availableColors.filter(function (c) {
|
|
||||||
return !c.equalTo(color);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
ColorPalette.prototype.return = function (color) {
|
|
||||||
if (isDefaultColor(color)) {
|
|
||||||
this.availableColors.unshift(color);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ColorPalette.prototype.getByHexString = function (hexString) {
|
|
||||||
const color = Color.fromHexString(hexString);
|
|
||||||
|
|
||||||
return color;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Color} the next unused color in the palette. If all colors
|
|
||||||
* have been allocated, it will wrap around.
|
|
||||||
*/
|
|
||||||
ColorPalette.prototype.getNextColor = function () {
|
|
||||||
if (!this.availableColors.length) {
|
|
||||||
console.warn('Color Palette empty, reusing colors!');
|
|
||||||
this.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.availableColors.shift();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} index the index of the color to return. An index
|
|
||||||
* value larger than the size of the index will wrap around.
|
|
||||||
* @returns {Color}
|
|
||||||
*/
|
|
||||||
ColorPalette.prototype.getColor = function (index) {
|
|
||||||
return this.colors[index % this.colors.length];
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
Color: Color,
|
|
||||||
ColorPalette: ColorPalette
|
|
||||||
};
|
|
||||||
});
|
|
@ -1,104 +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.
|
|
||||||
*****************************************************************************/
|
|
||||||
/*jscs:disable disallowDanglingUnderscores */
|
|
||||||
|
|
||||||
define([
|
|
||||||
|
|
||||||
], function (
|
|
||||||
|
|
||||||
) {
|
|
||||||
|
|
||||||
const helperFunctions = {
|
|
||||||
listenTo: function (object, event, callback, context) {
|
|
||||||
if (!this._listeningTo) {
|
|
||||||
this._listeningTo = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const listener = {
|
|
||||||
object: object,
|
|
||||||
event: event,
|
|
||||||
callback: callback,
|
|
||||||
context: context,
|
|
||||||
_cb: context ? callback.bind(context) : callback
|
|
||||||
};
|
|
||||||
if (object.$watch && event.indexOf('change:') === 0) {
|
|
||||||
const scopePath = event.replace('change:', '');
|
|
||||||
listener.unlisten = object.$watch(scopePath, listener._cb, true);
|
|
||||||
} else if (object.$on) {
|
|
||||||
listener.unlisten = object.$on(event, listener._cb);
|
|
||||||
} else if (object.addEventListener) {
|
|
||||||
object.addEventListener(event, listener._cb);
|
|
||||||
} else {
|
|
||||||
object.on(event, listener._cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._listeningTo.push(listener);
|
|
||||||
},
|
|
||||||
|
|
||||||
stopListening: function (object, event, callback, context) {
|
|
||||||
if (!this._listeningTo) {
|
|
||||||
this._listeningTo = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
this._listeningTo.filter(function (listener) {
|
|
||||||
if (object && object !== listener.object) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event && event !== listener.event) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callback && callback !== listener.callback) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context && context !== listener.context) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.map(function (listener) {
|
|
||||||
if (listener.unlisten) {
|
|
||||||
listener.unlisten();
|
|
||||||
} else if (listener.object.removeEventListener) {
|
|
||||||
listener.object.removeEventListener(listener.event, listener._cb);
|
|
||||||
} else {
|
|
||||||
listener.object.off(listener.event, listener._cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
return listener;
|
|
||||||
})
|
|
||||||
.forEach(function (listener) {
|
|
||||||
this._listeningTo.splice(this._listeningTo.indexOf(listener), 1);
|
|
||||||
}, this);
|
|
||||||
},
|
|
||||||
|
|
||||||
extend: function (object) {
|
|
||||||
object.listenTo = helperFunctions.listenTo;
|
|
||||||
object.stopListening = helperFunctions.stopListening;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return helperFunctions;
|
|
||||||
});
|
|
@ -1,70 +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.
|
|
||||||
*****************************************************************************/
|
|
||||||
/*jscs:disable disallowDanglingUnderscores */
|
|
||||||
|
|
||||||
define([
|
|
||||||
|
|
||||||
], function (
|
|
||||||
|
|
||||||
) {
|
|
||||||
|
|
||||||
function extend(props) {
|
|
||||||
// eslint-disable-next-line no-invalid-this
|
|
||||||
const parent = this;
|
|
||||||
|
|
||||||
let child;
|
|
||||||
let Surrogate;
|
|
||||||
|
|
||||||
if (props && Object.prototype.hasOwnProperty.call(props, 'constructor')) {
|
|
||||||
child = props.constructor;
|
|
||||||
} else {
|
|
||||||
child = function () {
|
|
||||||
// eslint-disable-next-line no-invalid-this
|
|
||||||
return parent.apply(this, arguments);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(parent).forEach(function copyStaticProperties(propKey) {
|
|
||||||
child[propKey] = parent[propKey];
|
|
||||||
});
|
|
||||||
|
|
||||||
// Surrogate allows inheriting from parent without invoking constructor.
|
|
||||||
Surrogate = function () {
|
|
||||||
this.constructor = child;
|
|
||||||
};
|
|
||||||
|
|
||||||
Surrogate.prototype = parent.prototype;
|
|
||||||
child.prototype = new Surrogate();
|
|
||||||
|
|
||||||
if (props) {
|
|
||||||
Object.keys(props).forEach(function copyInstanceProperties(key) {
|
|
||||||
child.prototype[key] = props[key];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
child.__super__ = parent.prototype;
|
|
||||||
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
|
|
||||||
return extend;
|
|
||||||
});
|
|
@ -1,88 +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.
|
|
||||||
*****************************************************************************/
|
|
||||||
/*jscs:disable disallowDanglingUnderscores */
|
|
||||||
|
|
||||||
define([
|
|
||||||
|
|
||||||
], function (
|
|
||||||
|
|
||||||
) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A scale has an input domain and an output range. It provides functions
|
|
||||||
* `scale` return the range value associated with a domain value.
|
|
||||||
* `invert` return the domain value associated with range value.
|
|
||||||
*/
|
|
||||||
function LinearScale(domain) {
|
|
||||||
this.domain(domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
LinearScale.prototype.domain = function (newDomain) {
|
|
||||||
if (newDomain) {
|
|
||||||
this._domain = newDomain;
|
|
||||||
this._domainDenominator = newDomain.max - newDomain.min;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._domain;
|
|
||||||
};
|
|
||||||
|
|
||||||
LinearScale.prototype.range = function (newRange) {
|
|
||||||
if (newRange) {
|
|
||||||
this._range = newRange;
|
|
||||||
this._rangeDenominator = newRange.max - newRange.min;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._range;
|
|
||||||
};
|
|
||||||
|
|
||||||
LinearScale.prototype.scale = function (domainValue) {
|
|
||||||
if (!this._domain || !this._range) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const domainOffset = domainValue - this._domain.min;
|
|
||||||
const rangeFraction = domainOffset - this._domainDenominator;
|
|
||||||
const rangeOffset = rangeFraction * this._rangeDenominator;
|
|
||||||
const rangeValue = rangeOffset + this._range.min;
|
|
||||||
|
|
||||||
return rangeValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
LinearScale.prototype.invert = function (rangeValue) {
|
|
||||||
if (!this._domain || !this._range) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rangeOffset = rangeValue - this._range.min;
|
|
||||||
const domainFraction = rangeOffset / this._rangeDenominator;
|
|
||||||
const domainOffset = domainFraction * this._domainDenominator;
|
|
||||||
const domainValue = domainOffset + this._domain.min;
|
|
||||||
|
|
||||||
return domainValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
return LinearScale;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
@ -1,616 +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([
|
|
||||||
'./LinearScale',
|
|
||||||
'../lib/eventHelpers'
|
|
||||||
], function (
|
|
||||||
LinearScale,
|
|
||||||
eventHelpers
|
|
||||||
) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MCTPlotController handles user interactions with the plot canvas.
|
|
||||||
* It supports pan and zoom, implements zoom history, and supports locating
|
|
||||||
* values near the cursor.
|
|
||||||
*/
|
|
||||||
function MCTPlotController($scope, $element, $window) {
|
|
||||||
this.$onInit = () => {
|
|
||||||
this.$scope = $scope;
|
|
||||||
this.$scope.config = this.config;
|
|
||||||
this.$scope.plot = this;
|
|
||||||
this.$element = $element;
|
|
||||||
this.$window = $window;
|
|
||||||
|
|
||||||
this.xScale = new LinearScale(this.config.xAxis.get('displayRange'));
|
|
||||||
this.yScale = new LinearScale(this.config.yAxis.get('displayRange'));
|
|
||||||
|
|
||||||
this.pan = undefined;
|
|
||||||
this.marquee = undefined;
|
|
||||||
|
|
||||||
this.chartElementBounds = undefined;
|
|
||||||
this.tickUpdate = false;
|
|
||||||
|
|
||||||
this.$scope.plotHistory = this.plotHistory = [];
|
|
||||||
this.listenTo(this.$scope, 'plot:clearHistory', this.clear, this);
|
|
||||||
|
|
||||||
this.initialize();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
MCTPlotController.$inject = ['$scope', '$element', '$window'];
|
|
||||||
|
|
||||||
eventHelpers.extend(MCTPlotController.prototype);
|
|
||||||
|
|
||||||
MCTPlotController.prototype.initCanvas = function () {
|
|
||||||
if (this.$canvas) {
|
|
||||||
this.stopListening(this.$canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$canvas = this.$element.find('canvas');
|
|
||||||
|
|
||||||
this.listenTo(this.$canvas, 'mousemove', this.trackMousePosition, this);
|
|
||||||
this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this);
|
|
||||||
this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this);
|
|
||||||
this.listenTo(this.$canvas, 'wheel', this.wheelZoom, this);
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.initialize = function () {
|
|
||||||
this.$canvas = this.$element.find('canvas');
|
|
||||||
|
|
||||||
this.listenTo(this.$canvas, 'mousemove', this.trackMousePosition, this);
|
|
||||||
this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this);
|
|
||||||
this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this);
|
|
||||||
this.listenTo(this.$canvas, 'wheel', this.wheelZoom, this);
|
|
||||||
|
|
||||||
this.$scope.rectangles = [];
|
|
||||||
this.$scope.tickWidth = 0;
|
|
||||||
|
|
||||||
this.$scope.xAxis = this.config.xAxis;
|
|
||||||
this.$scope.yAxis = this.config.yAxis;
|
|
||||||
this.$scope.series = this.config.series.models;
|
|
||||||
this.$scope.legend = this.config.legend;
|
|
||||||
|
|
||||||
this.$scope.yAxisLabel = this.config.yAxis.get('label');
|
|
||||||
|
|
||||||
this.cursorGuideVertical = this.$element[0].querySelector('.js-cursor-guide--v');
|
|
||||||
this.cursorGuideHorizontal = this.$element[0].querySelector('.js-cursor-guide--h');
|
|
||||||
this.cursorGuide = false;
|
|
||||||
|
|
||||||
this.gridLines = true;
|
|
||||||
|
|
||||||
this.listenTo(this.$scope, 'cursorguide', this.toggleCursorGuide, this);
|
|
||||||
this.listenTo(this.$scope, 'toggleGridLines', this.toggleGridLines, this);
|
|
||||||
|
|
||||||
this.listenTo(this.$scope, '$destroy', this.destroy, this);
|
|
||||||
this.listenTo(this.$scope, 'plot:tickWidth', this.onTickWidthChange, this);
|
|
||||||
this.listenTo(this.$scope, 'plot:highlight:set', this.onPlotHighlightSet, this);
|
|
||||||
this.listenTo(this.$scope, 'plot:reinitializeCanvas', this.initCanvas, this);
|
|
||||||
this.listenTo(this.config.xAxis, 'resetSeries', this.setUpXAxisOptions, this);
|
|
||||||
this.listenTo(this.config.xAxis, 'change:displayRange', this.onXAxisChange, this);
|
|
||||||
this.listenTo(this.config.yAxis, 'change:displayRange', this.onYAxisChange, this);
|
|
||||||
|
|
||||||
this.setUpXAxisOptions();
|
|
||||||
this.setUpYAxisOptions();
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.setUpXAxisOptions = function () {
|
|
||||||
const xAxisKey = this.config.xAxis.get('key');
|
|
||||||
|
|
||||||
if (this.$scope.series.length === 1) {
|
|
||||||
let metadata = this.$scope.series[0].metadata;
|
|
||||||
|
|
||||||
this.$scope.xKeyOptions = metadata
|
|
||||||
.valuesForHints(['domain'])
|
|
||||||
.map(function (o) {
|
|
||||||
return {
|
|
||||||
name: o.name,
|
|
||||||
key: o.key
|
|
||||||
};
|
|
||||||
});
|
|
||||||
this.$scope.selectedXKeyOption = this.getXKeyOption(xAxisKey);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.setUpYAxisOptions = function () {
|
|
||||||
if (this.$scope.series.length === 1) {
|
|
||||||
let metadata = this.$scope.series[0].metadata;
|
|
||||||
|
|
||||||
this.$scope.yKeyOptions = metadata
|
|
||||||
.valuesForHints(['range'])
|
|
||||||
.map(function (o) {
|
|
||||||
return {
|
|
||||||
name: o.name,
|
|
||||||
key: o.key
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// set yAxisLabel if none is set yet
|
|
||||||
if (this.$scope.yAxisLabel === 'none') {
|
|
||||||
let yKey = this.$scope.series[0].model.yKey;
|
|
||||||
let yKeyModel = this.$scope.yKeyOptions.filter(o => o.key === yKey)[0];
|
|
||||||
|
|
||||||
this.$scope.yAxisLabel = yKeyModel.name;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.$scope.yKeyOptions = undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.onXAxisChange = function (displayBounds) {
|
|
||||||
if (displayBounds) {
|
|
||||||
this.xScale.domain(displayBounds);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.onYAxisChange = function (displayBounds) {
|
|
||||||
if (displayBounds) {
|
|
||||||
this.yScale.domain(displayBounds);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.onTickWidthChange = function ($event, width) {
|
|
||||||
if ($event.targetScope.domainObject !== this.$scope.domainObject) {
|
|
||||||
// Always accept tick width if it comes from a different object.
|
|
||||||
this.$scope.tickWidth = width;
|
|
||||||
} else {
|
|
||||||
// Otherwise, only accept tick with if it's larger.
|
|
||||||
const newWidth = Math.max(width, this.$scope.tickWidth);
|
|
||||||
if (newWidth !== this.$scope.tickWidth) {
|
|
||||||
this.$scope.tickWidth = newWidth;
|
|
||||||
this.$scope.$digest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.trackMousePosition = function ($event) {
|
|
||||||
this.trackChartElementBounds($event);
|
|
||||||
this.xScale.range({
|
|
||||||
min: 0,
|
|
||||||
max: this.chartElementBounds.width
|
|
||||||
});
|
|
||||||
this.yScale.range({
|
|
||||||
min: 0,
|
|
||||||
max: this.chartElementBounds.height
|
|
||||||
});
|
|
||||||
|
|
||||||
this.positionOverElement = {
|
|
||||||
x: $event.clientX - this.chartElementBounds.left,
|
|
||||||
y: this.chartElementBounds.height
|
|
||||||
- ($event.clientY - this.chartElementBounds.top)
|
|
||||||
};
|
|
||||||
|
|
||||||
this.positionOverPlot = {
|
|
||||||
x: this.xScale.invert(this.positionOverElement.x),
|
|
||||||
y: this.yScale.invert(this.positionOverElement.y)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.cursorGuide) {
|
|
||||||
this.updateCrosshairs($event);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.highlightValues(this.positionOverPlot.x);
|
|
||||||
this.updateMarquee();
|
|
||||||
this.updatePan();
|
|
||||||
this.$scope.$digest();
|
|
||||||
$event.preventDefault();
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.updateCrosshairs = function ($event) {
|
|
||||||
this.cursorGuideVertical.style.left = ($event.clientX - this.chartElementBounds.x) + 'px';
|
|
||||||
this.cursorGuideHorizontal.style.top = ($event.clientY - this.chartElementBounds.y) + 'px';
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.trackChartElementBounds = function ($event) {
|
|
||||||
if ($event.target === this.$canvas[1]) {
|
|
||||||
this.chartElementBounds = $event.target.getBoundingClientRect();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.onPlotHighlightSet = function ($e, point) {
|
|
||||||
if (point === this.highlightPoint) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.highlightValues(point);
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.highlightValues = function (point) {
|
|
||||||
this.highlightPoint = point;
|
|
||||||
this.$scope.$emit('plot:highlight:update', point);
|
|
||||||
if (this.$scope.lockHighlightPoint) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!point) {
|
|
||||||
this.$scope.highlights = [];
|
|
||||||
this.$scope.series.forEach(series => delete series.closest);
|
|
||||||
} else {
|
|
||||||
this.$scope.highlights = this.$scope.series
|
|
||||||
.filter(series => series.data.length > 0)
|
|
||||||
.map(series => {
|
|
||||||
series.closest = series.nearestPoint(point);
|
|
||||||
|
|
||||||
return {
|
|
||||||
series: series,
|
|
||||||
point: series.closest
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$scope.$digest();
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.untrackMousePosition = function () {
|
|
||||||
this.positionOverElement = undefined;
|
|
||||||
this.positionOverPlot = undefined;
|
|
||||||
this.highlightValues();
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.onMouseDown = function ($event) {
|
|
||||||
// do not monitor drag events on browser context click
|
|
||||||
if (event.ctrlKey) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.listenTo(this.$window, 'mouseup', this.onMouseUp, this);
|
|
||||||
this.listenTo(this.$window, 'mousemove', this.trackMousePosition, this);
|
|
||||||
if (event.altKey) {
|
|
||||||
return this.startPan($event);
|
|
||||||
} else {
|
|
||||||
return this.startMarquee($event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.onMouseUp = function ($event) {
|
|
||||||
this.stopListening(this.$window, 'mouseup', this.onMouseUp, this);
|
|
||||||
this.stopListening(this.$window, 'mousemove', this.trackMousePosition, this);
|
|
||||||
|
|
||||||
if (this.isMouseClick()) {
|
|
||||||
this.$scope.lockHighlightPoint = !this.$scope.lockHighlightPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.pan) {
|
|
||||||
return this.endPan($event);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.marquee) {
|
|
||||||
return this.endMarquee($event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.isMouseClick = function () {
|
|
||||||
if (!this.marquee) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { start, end } = this.marquee;
|
|
||||||
|
|
||||||
return start.x === end.x && start.y === end.y;
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.updateMarquee = function () {
|
|
||||||
if (!this.marquee) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.marquee.end = this.positionOverPlot;
|
|
||||||
this.marquee.endPixels = this.positionOverElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.startMarquee = function ($event) {
|
|
||||||
this.$canvas.removeClass('plot-drag');
|
|
||||||
this.$canvas.addClass('plot-marquee');
|
|
||||||
|
|
||||||
this.trackMousePosition($event);
|
|
||||||
if (this.positionOverPlot) {
|
|
||||||
this.freeze();
|
|
||||||
this.marquee = {
|
|
||||||
startPixels: this.positionOverElement,
|
|
||||||
endPixels: this.positionOverElement,
|
|
||||||
start: this.positionOverPlot,
|
|
||||||
end: this.positionOverPlot,
|
|
||||||
color: [1, 1, 1, 0.5]
|
|
||||||
};
|
|
||||||
this.$scope.rectangles.push(this.marquee);
|
|
||||||
this.trackHistory();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.endMarquee = function () {
|
|
||||||
const startPixels = this.marquee.startPixels;
|
|
||||||
const endPixels = this.marquee.endPixels;
|
|
||||||
const marqueeDistance = Math.sqrt(
|
|
||||||
Math.pow(startPixels.x - endPixels.x, 2)
|
|
||||||
+ Math.pow(startPixels.y - endPixels.y, 2)
|
|
||||||
);
|
|
||||||
// Don't zoom if mouse moved less than 7.5 pixels.
|
|
||||||
if (marqueeDistance > 7.5) {
|
|
||||||
this.$scope.xAxis.set('displayRange', {
|
|
||||||
min: Math.min(this.marquee.start.x, this.marquee.end.x),
|
|
||||||
max: Math.max(this.marquee.start.x, this.marquee.end.x)
|
|
||||||
});
|
|
||||||
this.$scope.yAxis.set('displayRange', {
|
|
||||||
min: Math.min(this.marquee.start.y, this.marquee.end.y),
|
|
||||||
max: Math.max(this.marquee.start.y, this.marquee.end.y)
|
|
||||||
});
|
|
||||||
this.$scope.$emit('user:viewport:change:end');
|
|
||||||
} else {
|
|
||||||
// A history entry is created by startMarquee, need to remove
|
|
||||||
// if marquee zoom doesn't occur.
|
|
||||||
this.plotHistory.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$scope.rectangles = [];
|
|
||||||
this.marquee = undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.zoom = function (zoomDirection, zoomFactor) {
|
|
||||||
const currentXaxis = this.$scope.xAxis.get('displayRange');
|
|
||||||
const currentYaxis = this.$scope.yAxis.get('displayRange');
|
|
||||||
|
|
||||||
// when there is no plot data, the ranges can be undefined
|
|
||||||
// in which case we should not perform zoom
|
|
||||||
if (!currentXaxis || !currentYaxis) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.freeze();
|
|
||||||
this.trackHistory();
|
|
||||||
|
|
||||||
const xAxisDist = (currentXaxis.max - currentXaxis.min) * zoomFactor;
|
|
||||||
const yAxisDist = (currentYaxis.max - currentYaxis.min) * zoomFactor;
|
|
||||||
|
|
||||||
if (zoomDirection === 'in') {
|
|
||||||
this.$scope.xAxis.set('displayRange', {
|
|
||||||
min: currentXaxis.min + xAxisDist,
|
|
||||||
max: currentXaxis.max - xAxisDist
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$scope.yAxis.set('displayRange', {
|
|
||||||
min: currentYaxis.min + yAxisDist,
|
|
||||||
max: currentYaxis.max - yAxisDist
|
|
||||||
});
|
|
||||||
} else if (zoomDirection === 'out') {
|
|
||||||
this.$scope.xAxis.set('displayRange', {
|
|
||||||
min: currentXaxis.min - xAxisDist,
|
|
||||||
max: currentXaxis.max + xAxisDist
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$scope.yAxis.set('displayRange', {
|
|
||||||
min: currentYaxis.min - yAxisDist,
|
|
||||||
max: currentYaxis.max + yAxisDist
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$scope.$emit('user:viewport:change:end');
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.wheelZoom = function (event) {
|
|
||||||
const ZOOM_AMT = 0.1;
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (!this.positionOverPlot) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isPinchToZoom = event.ctrlKey === true;
|
|
||||||
let isZoomIn = event.wheelDelta < 0;
|
|
||||||
let isZoomOut = event.wheelDelta >= 0;
|
|
||||||
|
|
||||||
//Flip the zoom direction if this is pinch to zoom
|
|
||||||
if (isPinchToZoom) {
|
|
||||||
if (isZoomIn === true) {
|
|
||||||
isZoomOut = true;
|
|
||||||
isZoomIn = false;
|
|
||||||
} else if (isZoomOut === true) {
|
|
||||||
isZoomIn = true;
|
|
||||||
isZoomOut = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let xDisplayRange = this.$scope.xAxis.get('displayRange');
|
|
||||||
let yDisplayRange = this.$scope.yAxis.get('displayRange');
|
|
||||||
|
|
||||||
// when there is no plot data, the ranges can be undefined
|
|
||||||
// in which case we should not perform zoom
|
|
||||||
if (!xDisplayRange || !yDisplayRange) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.freeze();
|
|
||||||
window.clearTimeout(this.stillZooming);
|
|
||||||
|
|
||||||
let xAxisDist = (xDisplayRange.max - xDisplayRange.min);
|
|
||||||
let yAxisDist = (yDisplayRange.max - yDisplayRange.min);
|
|
||||||
let xDistMouseToMax = xDisplayRange.max - this.positionOverPlot.x;
|
|
||||||
let xDistMouseToMin = this.positionOverPlot.x - xDisplayRange.min;
|
|
||||||
let yDistMouseToMax = yDisplayRange.max - this.positionOverPlot.y;
|
|
||||||
let yDistMouseToMin = this.positionOverPlot.y - yDisplayRange.min;
|
|
||||||
let xAxisMaxDist = xDistMouseToMax / xAxisDist;
|
|
||||||
let xAxisMinDist = xDistMouseToMin / xAxisDist;
|
|
||||||
let yAxisMaxDist = yDistMouseToMax / yAxisDist;
|
|
||||||
let yAxisMinDist = yDistMouseToMin / yAxisDist;
|
|
||||||
|
|
||||||
let plotHistoryStep;
|
|
||||||
|
|
||||||
if (!plotHistoryStep) {
|
|
||||||
plotHistoryStep = {
|
|
||||||
x: xDisplayRange,
|
|
||||||
y: yDisplayRange
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isZoomIn) {
|
|
||||||
|
|
||||||
this.$scope.xAxis.set('displayRange', {
|
|
||||||
min: xDisplayRange.min + ((xAxisDist * ZOOM_AMT) * xAxisMinDist),
|
|
||||||
max: xDisplayRange.max - ((xAxisDist * ZOOM_AMT) * xAxisMaxDist)
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$scope.yAxis.set('displayRange', {
|
|
||||||
min: yDisplayRange.min + ((yAxisDist * ZOOM_AMT) * yAxisMinDist),
|
|
||||||
max: yDisplayRange.max - ((yAxisDist * ZOOM_AMT) * yAxisMaxDist)
|
|
||||||
});
|
|
||||||
} else if (isZoomOut) {
|
|
||||||
|
|
||||||
this.$scope.xAxis.set('displayRange', {
|
|
||||||
min: xDisplayRange.min - ((xAxisDist * ZOOM_AMT) * xAxisMinDist),
|
|
||||||
max: xDisplayRange.max + ((xAxisDist * ZOOM_AMT) * xAxisMaxDist)
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$scope.yAxis.set('displayRange', {
|
|
||||||
min: yDisplayRange.min - ((yAxisDist * ZOOM_AMT) * yAxisMinDist),
|
|
||||||
max: yDisplayRange.max + ((yAxisDist * ZOOM_AMT) * yAxisMaxDist)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.stillZooming = window.setTimeout(function () {
|
|
||||||
this.plotHistory.push(plotHistoryStep);
|
|
||||||
plotHistoryStep = undefined;
|
|
||||||
this.$scope.$emit('user:viewport:change:end');
|
|
||||||
}.bind(this), 250);
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.startPan = function ($event) {
|
|
||||||
this.$canvas.addClass('plot-drag');
|
|
||||||
this.$canvas.removeClass('plot-marquee');
|
|
||||||
|
|
||||||
this.trackMousePosition($event);
|
|
||||||
this.freeze();
|
|
||||||
this.pan = {
|
|
||||||
start: this.positionOverPlot
|
|
||||||
};
|
|
||||||
$event.preventDefault();
|
|
||||||
this.trackHistory();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.updatePan = function () {
|
|
||||||
// calculate offset between points. Apply that offset to viewport.
|
|
||||||
if (!this.pan) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dX = this.pan.start.x - this.positionOverPlot.x;
|
|
||||||
const dY = this.pan.start.y - this.positionOverPlot.y;
|
|
||||||
const xRange = this.config.xAxis.get('displayRange');
|
|
||||||
const yRange = this.config.yAxis.get('displayRange');
|
|
||||||
|
|
||||||
this.config.xAxis.set('displayRange', {
|
|
||||||
min: xRange.min + dX,
|
|
||||||
max: xRange.max + dX
|
|
||||||
});
|
|
||||||
this.config.yAxis.set('displayRange', {
|
|
||||||
min: yRange.min + dY,
|
|
||||||
max: yRange.max + dY
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.trackHistory = function () {
|
|
||||||
this.plotHistory.push({
|
|
||||||
x: this.config.xAxis.get('displayRange'),
|
|
||||||
y: this.config.yAxis.get('displayRange')
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.endPan = function () {
|
|
||||||
this.pan = undefined;
|
|
||||||
this.$scope.$emit('user:viewport:change:end');
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.freeze = function () {
|
|
||||||
this.config.yAxis.set('frozen', true);
|
|
||||||
this.config.xAxis.set('frozen', true);
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.clear = function () {
|
|
||||||
this.config.yAxis.set('frozen', false);
|
|
||||||
this.config.xAxis.set('frozen', false);
|
|
||||||
this.$scope.plotHistory = this.plotHistory = [];
|
|
||||||
this.$scope.$emit('user:viewport:change:end');
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.back = function () {
|
|
||||||
const previousAxisRanges = this.plotHistory.pop();
|
|
||||||
if (this.plotHistory.length === 0) {
|
|
||||||
this.clear();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.config.xAxis.set('displayRange', previousAxisRanges.x);
|
|
||||||
this.config.yAxis.set('displayRange', previousAxisRanges.y);
|
|
||||||
this.$scope.$emit('user:viewport:change:end');
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.destroy = function () {
|
|
||||||
this.stopListening();
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.toggleCursorGuide = function ($event) {
|
|
||||||
this.cursorGuide = !this.cursorGuide;
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.toggleGridLines = function ($event) {
|
|
||||||
this.gridLines = !this.gridLines;
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.getXKeyOption = function (key) {
|
|
||||||
return this.$scope.xKeyOptions.find(option => option.key === key);
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.isEnabledXKeyToggle = function () {
|
|
||||||
const isSinglePlot = this.$scope.xKeyOptions && this.$scope.xKeyOptions.length > 1 && this.$scope.series.length === 1;
|
|
||||||
const isFrozen = this.config.xAxis.get('frozen');
|
|
||||||
const inRealTimeMode = this.config.openmct.time.clock();
|
|
||||||
|
|
||||||
return isSinglePlot && !isFrozen && !inRealTimeMode;
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.toggleXKeyOption = function (lastXKey, series) {
|
|
||||||
const selectedXKey = this.$scope.selectedXKeyOption.key;
|
|
||||||
const dataForSelectedXKey = series.data
|
|
||||||
? series.data[0][selectedXKey]
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (dataForSelectedXKey !== undefined) {
|
|
||||||
this.config.xAxis.set('key', selectedXKey);
|
|
||||||
} else {
|
|
||||||
this.config.openmct.notifications.error('Cannot change x-axis view as no data exists for this view type.');
|
|
||||||
this.$scope.selectedXKeyOption.key = lastXKey;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTPlotController.prototype.toggleYAxisLabel = function (label, options, series) {
|
|
||||||
let yAxisObject = options.filter(o => o.name === label)[0];
|
|
||||||
|
|
||||||
if (yAxisObject) {
|
|
||||||
series.emit('change:yKey', yAxisObject.key);
|
|
||||||
this.config.yAxis.set('label', label);
|
|
||||||
this.$scope.yAxisLabel = label;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return MCTPlotController;
|
|
||||||
});
|
|
@ -1,46 +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([
|
|
||||||
'./MCTPlotController',
|
|
||||||
'../../res/templates/mct-plot.html'
|
|
||||||
], function (
|
|
||||||
MCTPlotController,
|
|
||||||
PlotTemplate
|
|
||||||
) {
|
|
||||||
|
|
||||||
function MCTPlot() {
|
|
||||||
|
|
||||||
return {
|
|
||||||
restrict: "E",
|
|
||||||
template: PlotTemplate,
|
|
||||||
controller: MCTPlotController,
|
|
||||||
controllerAs: 'mctPlotController',
|
|
||||||
bindToController: {
|
|
||||||
config: "="
|
|
||||||
},
|
|
||||||
scope: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return MCTPlot;
|
|
||||||
});
|
|
@ -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.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
'lodash',
|
|
||||||
'../lib/eventHelpers'
|
|
||||||
], function (
|
|
||||||
_,
|
|
||||||
eventHelpers
|
|
||||||
) {
|
|
||||||
const e10 = Math.sqrt(50);
|
|
||||||
const e5 = Math.sqrt(10);
|
|
||||||
const e2 = Math.sqrt(2);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Nicely formatted tick steps from d3-array.
|
|
||||||
*/
|
|
||||||
function tickStep(start, stop, count) {
|
|
||||||
const step0 = Math.abs(stop - start) / Math.max(0, count);
|
|
||||||
let step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10));
|
|
||||||
const error = step0 / step1;
|
|
||||||
if (error >= e10) {
|
|
||||||
step1 *= 10;
|
|
||||||
} else if (error >= e5) {
|
|
||||||
step1 *= 5;
|
|
||||||
} else if (error >= e2) {
|
|
||||||
step1 *= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return stop < start ? -step1 : step1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the precision (number of decimals) of a step. Used to round
|
|
||||||
* ticks to precise values.
|
|
||||||
*/
|
|
||||||
function getPrecision(step) {
|
|
||||||
const exponential = step.toExponential();
|
|
||||||
const i = exponential.indexOf('e');
|
|
||||||
if (i === -1) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let precision = Math.max(0, -(Number(exponential.slice(i + 1))));
|
|
||||||
|
|
||||||
if (precision > 20) {
|
|
||||||
precision = 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
return precision;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Linear tick generation from d3-array.
|
|
||||||
*/
|
|
||||||
function ticks(start, stop, count) {
|
|
||||||
const step = tickStep(start, stop, count);
|
|
||||||
const precision = getPrecision(step);
|
|
||||||
|
|
||||||
return _.range(
|
|
||||||
Math.ceil(start / step) * step,
|
|
||||||
Math.floor(stop / step) * step + step / 2, // inclusive
|
|
||||||
step
|
|
||||||
).map(function round(tick) {
|
|
||||||
return Number(tick.toFixed(precision));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function commonPrefix(a, b) {
|
|
||||||
const maxLen = Math.min(a.length, b.length);
|
|
||||||
let breakpoint = 0;
|
|
||||||
for (let i = 0; i < maxLen; i++) {
|
|
||||||
if (a[i] !== b[i]) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a[i] === ' ') {
|
|
||||||
breakpoint = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.slice(0, breakpoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
function commonSuffix(a, b) {
|
|
||||||
const maxLen = Math.min(a.length, b.length);
|
|
||||||
let breakpoint = 0;
|
|
||||||
for (let i = 0; i <= maxLen; i++) {
|
|
||||||
if (a[a.length - i] !== b[b.length - i]) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('. '.indexOf(a[a.length - i]) !== -1) {
|
|
||||||
breakpoint = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.slice(a.length - breakpoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
function MCTTicksController($scope, $element) {
|
|
||||||
this.$onInit = () => {
|
|
||||||
this.$scope = $scope;
|
|
||||||
this.$element = $element;
|
|
||||||
|
|
||||||
this.tickCount = 4;
|
|
||||||
this.tickUpdate = false;
|
|
||||||
this.listenTo(this.axis, 'change:displayRange', this.updateTicks, this);
|
|
||||||
this.listenTo(this.axis, 'change:format', this.updateTicks, this);
|
|
||||||
this.listenTo(this.axis, 'change:key', this.updateTicksForceRegeneration, this);
|
|
||||||
this.listenTo(this.$scope, '$destroy', this.stopListening, this);
|
|
||||||
this.updateTicks();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
MCTTicksController.$inject = ['$scope', '$element'];
|
|
||||||
|
|
||||||
eventHelpers.extend(MCTTicksController.prototype);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine whether ticks should be regenerated for a given range.
|
|
||||||
* Ticks are updated
|
|
||||||
* a) if they don't exist,
|
|
||||||
* b) if existing ticks are outside of given range,
|
|
||||||
* c) if range exceeds size of tick range by more than one tick step,
|
|
||||||
* d) if forced to regenerate (ex. changing x-axis metadata).
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
MCTTicksController.prototype.shouldRegenerateTicks = function (range, forceRegeneration) {
|
|
||||||
if (forceRegeneration) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.tickRange || !this.$scope.ticks || !this.$scope.ticks.length) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.tickRange.max > range.max || this.tickRange.min < range.min) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Math.abs(range.max - this.tickRange.max) > this.tickRange.step) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Math.abs(this.tickRange.min - range.min) > this.tickRange.step) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTTicksController.prototype.getTicks = function () {
|
|
||||||
const number = this.tickCount;
|
|
||||||
const clampRange = this.axis.get('values');
|
|
||||||
const range = this.axis.get('displayRange');
|
|
||||||
if (clampRange) {
|
|
||||||
return clampRange.filter(function (value) {
|
|
||||||
return value <= range.max && value >= range.min;
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ticks(range.min, range.max, number);
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTTicksController.prototype.updateTicksForceRegeneration = function () {
|
|
||||||
this.updateTicks(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTTicksController.prototype.updateTicks = function (forceRegeneration = false) {
|
|
||||||
const range = this.axis.get('displayRange');
|
|
||||||
if (!range) {
|
|
||||||
delete this.$scope.min;
|
|
||||||
delete this.$scope.max;
|
|
||||||
delete this.$scope.interval;
|
|
||||||
delete this.tickRange;
|
|
||||||
delete this.$scope.ticks;
|
|
||||||
delete this.shouldCheckWidth;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const format = this.axis.get('format');
|
|
||||||
if (!format) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$scope.min = range.min;
|
|
||||||
this.$scope.max = range.max;
|
|
||||||
this.$scope.interval = Math.abs(range.min - range.max);
|
|
||||||
if (this.shouldRegenerateTicks(range, forceRegeneration)) {
|
|
||||||
let newTicks = this.getTicks();
|
|
||||||
this.tickRange = {
|
|
||||||
min: Math.min.apply(Math, newTicks),
|
|
||||||
max: Math.max.apply(Math, newTicks),
|
|
||||||
step: newTicks[1] - newTicks[0]
|
|
||||||
};
|
|
||||||
|
|
||||||
newTicks = newTicks
|
|
||||||
.map(function (tickValue) {
|
|
||||||
return {
|
|
||||||
value: tickValue,
|
|
||||||
text: format(tickValue)
|
|
||||||
};
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
if (newTicks.length && typeof newTicks[0].text === 'string') {
|
|
||||||
const tickText = newTicks.map(function (t) {
|
|
||||||
return t.text;
|
|
||||||
});
|
|
||||||
const prefix = tickText.reduce(commonPrefix);
|
|
||||||
const suffix = tickText.reduce(commonSuffix);
|
|
||||||
newTicks.forEach(function (t, i) {
|
|
||||||
t.fullText = t.text;
|
|
||||||
if (suffix.length) {
|
|
||||||
t.text = t.text.slice(prefix.length, -suffix.length);
|
|
||||||
} else {
|
|
||||||
t.text = t.text.slice(prefix.length);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$scope.ticks = newTicks;
|
|
||||||
this.shouldCheckWidth = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scheduleTickUpdate();
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTTicksController.prototype.scheduleTickUpdate = function () {
|
|
||||||
if (this.tickUpdate) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tickUpdate = true;
|
|
||||||
setTimeout(this.doTickUpdate.bind(this), 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTTicksController.prototype.doTickUpdate = function () {
|
|
||||||
if (this.shouldCheckWidth) {
|
|
||||||
this.$scope.$digest();
|
|
||||||
const element = this.$element[0];
|
|
||||||
const tickElements = element.querySelectorAll('.gl-plot-tick > span');
|
|
||||||
|
|
||||||
const tickWidth = Number([].reduce.call(tickElements, function (memo, first) {
|
|
||||||
return Math.max(memo, first.offsetWidth);
|
|
||||||
}, 0));
|
|
||||||
|
|
||||||
this.$scope.tickWidth = tickWidth;
|
|
||||||
this.$scope.$emit('plot:tickWidth', tickWidth);
|
|
||||||
this.shouldCheckWidth = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$scope.$digest();
|
|
||||||
this.tickUpdate = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
return MCTTicksController;
|
|
||||||
});
|
|
@ -1,43 +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([
|
|
||||||
'./MCTTicksController'
|
|
||||||
], function (
|
|
||||||
MCTTicksController
|
|
||||||
) {
|
|
||||||
|
|
||||||
function MCTTicksDirective() {
|
|
||||||
return {
|
|
||||||
priority: 1000,
|
|
||||||
restrict: "E",
|
|
||||||
scope: true,
|
|
||||||
controllerAs: 'ticksController',
|
|
||||||
controller: MCTTicksController,
|
|
||||||
bindToController: {
|
|
||||||
axis: '='
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return MCTTicksDirective;
|
|
||||||
});
|
|
@ -1,37 +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([
|
|
||||||
'../../res/templates/plot.html'
|
|
||||||
], function (
|
|
||||||
PlotTemplate
|
|
||||||
) {
|
|
||||||
return function MCTOverlayPlot() {
|
|
||||||
return {
|
|
||||||
restrict: "E",
|
|
||||||
template: PlotTemplate,
|
|
||||||
scope: {
|
|
||||||
domainObject: "="
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
});
|
|
@ -1,342 +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.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/*jscs:disable disallowDanglingUnderscores */
|
|
||||||
|
|
||||||
define([
|
|
||||||
'lodash',
|
|
||||||
'../configuration/PlotConfigurationModel',
|
|
||||||
'../configuration/configStore',
|
|
||||||
'../lib/eventHelpers'
|
|
||||||
], function (
|
|
||||||
_,
|
|
||||||
PlotConfigurationModel,
|
|
||||||
configStore,
|
|
||||||
eventHelpers
|
|
||||||
) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
TODO: Need to separate off plot configuration and specifying of defaults,
|
|
||||||
is part of onDomainObjectChange as it can be triggered by mutation.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller for a plot.
|
|
||||||
*
|
|
||||||
* @constructor.
|
|
||||||
*/
|
|
||||||
function PlotController(
|
|
||||||
$scope,
|
|
||||||
$element,
|
|
||||||
formatService,
|
|
||||||
openmct,
|
|
||||||
objectService,
|
|
||||||
exportImageService
|
|
||||||
) {
|
|
||||||
|
|
||||||
this.$scope = $scope;
|
|
||||||
this.$element = $element;
|
|
||||||
this.formatService = formatService;
|
|
||||||
this.openmct = openmct;
|
|
||||||
this.objectService = objectService;
|
|
||||||
this.exportImageService = exportImageService;
|
|
||||||
this.cursorGuide = false;
|
|
||||||
this.gridLines = true;
|
|
||||||
|
|
||||||
$scope.pending = 0;
|
|
||||||
|
|
||||||
this.clearData = this.clearData.bind(this);
|
|
||||||
|
|
||||||
this.listenTo($scope, 'user:viewport:change:end', this.onUserViewportChangeEnd, this);
|
|
||||||
this.listenTo($scope, '$destroy', this.destroy, this);
|
|
||||||
this.listenTo($scope, 'clearData', this.clearData);
|
|
||||||
|
|
||||||
this.config = this.getConfig(this.$scope.domainObject);
|
|
||||||
this.listenTo(this.config.series, 'add', this.addSeries, this);
|
|
||||||
this.listenTo(this.config.series, 'remove', this.removeSeries, this);
|
|
||||||
this.config.series.forEach(this.addSeries, this);
|
|
||||||
|
|
||||||
this.followTimeConductor();
|
|
||||||
|
|
||||||
this.newStyleDomainObject = $scope.domainObject.useCapability('adapter');
|
|
||||||
this.keyString = this.openmct.objects.makeKeyString(this.newStyleDomainObject.identifier);
|
|
||||||
|
|
||||||
this.filterObserver = this.openmct.objects.observe(
|
|
||||||
this.newStyleDomainObject,
|
|
||||||
'configuration.filters',
|
|
||||||
this.updateFiltersAndResubscribe.bind(this)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
eventHelpers.extend(PlotController.prototype);
|
|
||||||
|
|
||||||
PlotController.prototype.followTimeConductor = function () {
|
|
||||||
this.listenTo(this.openmct.time, 'bounds', this.updateDisplayBounds, this);
|
|
||||||
this.listenTo(this.openmct.time, 'timeSystem', this.syncXAxisToTimeSystem, this);
|
|
||||||
this.synchronized(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotController.prototype.loadSeriesData = function (series) {
|
|
||||||
if (this.$element[0].offsetWidth === 0) {
|
|
||||||
this.scheduleLoad(series);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.startLoading();
|
|
||||||
const options = {
|
|
||||||
size: this.$element[0].offsetWidth,
|
|
||||||
domain: this.config.xAxis.get('key')
|
|
||||||
};
|
|
||||||
|
|
||||||
series.load(options)
|
|
||||||
.then(this.stopLoading.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotController.prototype.scheduleLoad = function (series) {
|
|
||||||
if (!this.scheduledLoads) {
|
|
||||||
this.startLoading();
|
|
||||||
this.scheduledLoads = [];
|
|
||||||
this.checkForSize = setInterval(function () {
|
|
||||||
if (this.$element[0].offsetWidth === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.stopLoading();
|
|
||||||
this.scheduledLoads.forEach(this.loadSeriesData, this);
|
|
||||||
delete this.scheduledLoads;
|
|
||||||
clearInterval(this.checkForSize);
|
|
||||||
delete this.checkForSize;
|
|
||||||
}.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.scheduledLoads.indexOf(series) === -1) {
|
|
||||||
this.scheduledLoads.push(series);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotController.prototype.addSeries = function (series) {
|
|
||||||
this.listenTo(series, 'change:xKey', (xKey) => {
|
|
||||||
this.setDisplayRange(series, xKey);
|
|
||||||
}, this);
|
|
||||||
this.listenTo(series, 'change:yKey', () => {
|
|
||||||
this.loadSeriesData(series);
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
this.listenTo(series, 'change:interpolate', () => {
|
|
||||||
this.loadSeriesData(series);
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
this.loadSeriesData(series);
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotController.prototype.setDisplayRange = function (series, xKey) {
|
|
||||||
if (this.config.series.models.length !== 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const displayRange = series.getDisplayRange(xKey);
|
|
||||||
this.config.xAxis.set('range', displayRange);
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotController.prototype.removeSeries = function (plotSeries) {
|
|
||||||
this.stopListening(plotSeries);
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotController.prototype.getConfig = function (domainObject) {
|
|
||||||
const configId = domainObject.getId();
|
|
||||||
let config = configStore.get(configId);
|
|
||||||
if (!config) {
|
|
||||||
const newDomainObject = domainObject.useCapability('adapter');
|
|
||||||
config = new PlotConfigurationModel({
|
|
||||||
id: configId,
|
|
||||||
domainObject: newDomainObject,
|
|
||||||
openmct: this.openmct
|
|
||||||
});
|
|
||||||
configStore.add(configId, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotController.prototype.syncXAxisToTimeSystem = function (timeSystem) {
|
|
||||||
this.config.xAxis.set('key', timeSystem.key);
|
|
||||||
this.config.xAxis.emit('resetSeries');
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotController.prototype.destroy = function () {
|
|
||||||
configStore.deleteStore(this.config.id);
|
|
||||||
|
|
||||||
this.stopListening();
|
|
||||||
if (this.checkForSize) {
|
|
||||||
clearInterval(this.checkForSize);
|
|
||||||
delete this.checkForSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.filterObserver) {
|
|
||||||
this.filterObserver();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotController.prototype.loadMoreData = function (range, purge) {
|
|
||||||
this.config.series.forEach(plotSeries => {
|
|
||||||
this.startLoading();
|
|
||||||
plotSeries.load({
|
|
||||||
size: this.$element[0].offsetWidth,
|
|
||||||
start: range.min,
|
|
||||||
end: range.max,
|
|
||||||
domain: this.config.xAxis.get('key')
|
|
||||||
})
|
|
||||||
.then(this.stopLoading());
|
|
||||||
if (purge) {
|
|
||||||
plotSeries.purgeRecordsOutsideRange(range);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track latest display bounds. Forces update when not receiving ticks.
|
|
||||||
*/
|
|
||||||
PlotController.prototype.updateDisplayBounds = function (bounds, isTick) {
|
|
||||||
|
|
||||||
const xAxisKey = this.config.xAxis.get('key');
|
|
||||||
const timeSystem = this.openmct.time.timeSystem();
|
|
||||||
const newRange = {
|
|
||||||
min: bounds.start,
|
|
||||||
max: bounds.end
|
|
||||||
};
|
|
||||||
|
|
||||||
if (xAxisKey !== timeSystem.key) {
|
|
||||||
this.syncXAxisToTimeSystem(timeSystem);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.config.xAxis.set('range', newRange);
|
|
||||||
if (!isTick) {
|
|
||||||
this.skipReloadOnInteraction = true;
|
|
||||||
this.$scope.$broadcast('plot:clearHistory');
|
|
||||||
this.skipReloadOnInteraction = false;
|
|
||||||
this.loadMoreData(newRange, true);
|
|
||||||
} else {
|
|
||||||
// Drop any data that is more than 1x (max-min) before min.
|
|
||||||
// Limit these purges to once a second.
|
|
||||||
if (!this.nextPurge || this.nextPurge < Date.now()) {
|
|
||||||
const keepRange = {
|
|
||||||
min: newRange.min - (newRange.max - newRange.min),
|
|
||||||
max: newRange.max
|
|
||||||
};
|
|
||||||
this.config.series.forEach(function (series) {
|
|
||||||
series.purgeRecordsOutsideRange(keepRange);
|
|
||||||
});
|
|
||||||
this.nextPurge = Date.now() + 1000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotController.prototype.startLoading = function () {
|
|
||||||
this.$scope.pending += 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotController.prototype.stopLoading = function () {
|
|
||||||
this.$scope.$evalAsync(() => {
|
|
||||||
this.$scope.pending -= 1;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter/setter for "synchronized" value. If not synchronized and
|
|
||||||
* time conductor is in clock mode, will mark objects as unsynced so that
|
|
||||||
* displays can update accordingly.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
PlotController.prototype.synchronized = function (value) {
|
|
||||||
if (typeof value !== 'undefined') {
|
|
||||||
this._synchronized = value;
|
|
||||||
const isUnsynced = !value && this.openmct.time.clock();
|
|
||||||
if (this.$scope.domainObject.getCapability('status')) {
|
|
||||||
this.$scope.domainObject.getCapability('status')
|
|
||||||
.set('timeconductor-unsynced', isUnsynced);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._synchronized;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle end of user viewport change: load more data for current display
|
|
||||||
* bounds, and mark view as synchronized if bounds match configured bounds.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
PlotController.prototype.onUserViewportChangeEnd = function () {
|
|
||||||
const xDisplayRange = this.config.xAxis.get('displayRange');
|
|
||||||
const xRange = this.config.xAxis.get('range');
|
|
||||||
|
|
||||||
if (!this.skipReloadOnInteraction) {
|
|
||||||
this.loadMoreData(xDisplayRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.synchronized(xRange.min === xDisplayRange.min
|
|
||||||
&& xRange.max === xDisplayRange.max);
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotController.prototype.updateFiltersAndResubscribe = function (updatedFilters) {
|
|
||||||
this.config.series.forEach(function (series) {
|
|
||||||
series.updateFiltersAndRefresh(updatedFilters[series.keyString]);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotController.prototype.clearData = function () {
|
|
||||||
this.config.series.forEach(function (series) {
|
|
||||||
series.reset();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Export view as JPG.
|
|
||||||
*/
|
|
||||||
PlotController.prototype.exportJPG = function () {
|
|
||||||
const plotElement = this.$element.children()[1];
|
|
||||||
|
|
||||||
this.exportImageService.exportJPG(plotElement, 'plot.jpg', 'export-plot');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Export view as PNG.
|
|
||||||
*/
|
|
||||||
PlotController.prototype.exportPNG = function () {
|
|
||||||
const plotElement = this.$element.children()[1];
|
|
||||||
|
|
||||||
this.exportImageService.exportPNG(plotElement, 'plot.png', 'export-plot');
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotController.prototype.toggleCursorGuide = function ($event) {
|
|
||||||
this.cursorGuide = !this.cursorGuide;
|
|
||||||
this.$scope.$broadcast('cursorguide', $event);
|
|
||||||
};
|
|
||||||
|
|
||||||
PlotController.prototype.toggleGridLines = function ($event) {
|
|
||||||
this.gridLines = !this.gridLines;
|
|
||||||
this.$scope.$broadcast('toggleGridLines', $event);
|
|
||||||
};
|
|
||||||
|
|
||||||
return PlotController;
|
|
||||||
|
|
||||||
});
|
|
@ -1,167 +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 StackedPlotController($scope, openmct, objectService, $element, exportImageService) {
|
|
||||||
let tickWidth = 0;
|
|
||||||
let composition;
|
|
||||||
let currentRequest;
|
|
||||||
let unlisten;
|
|
||||||
let tickWidthMap = {};
|
|
||||||
|
|
||||||
this.$element = $element;
|
|
||||||
this.exportImageService = exportImageService;
|
|
||||||
this.$scope = $scope;
|
|
||||||
this.cursorGuide = false;
|
|
||||||
|
|
||||||
$scope.telemetryObjects = [];
|
|
||||||
|
|
||||||
function onDomainObjectChange(domainObject) {
|
|
||||||
const thisRequest = {
|
|
||||||
pending: 0
|
|
||||||
};
|
|
||||||
const telemetryObjects = $scope.telemetryObjects = [];
|
|
||||||
const thisTickWidthMap = {};
|
|
||||||
|
|
||||||
currentRequest = thisRequest;
|
|
||||||
$scope.currentRequest = thisRequest;
|
|
||||||
tickWidthMap = thisTickWidthMap;
|
|
||||||
|
|
||||||
if (unlisten) {
|
|
||||||
unlisten();
|
|
||||||
unlisten = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addChild(child) {
|
|
||||||
const id = openmct.objects.makeKeyString(child.identifier);
|
|
||||||
const legacyObject = openmct.legacyObject(child);
|
|
||||||
|
|
||||||
thisTickWidthMap[id] = 0;
|
|
||||||
telemetryObjects.push(legacyObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeChild(childIdentifier) {
|
|
||||||
const id = openmct.objects.makeKeyString(childIdentifier);
|
|
||||||
delete thisTickWidthMap[id];
|
|
||||||
const childObj = telemetryObjects.filter(function (c) {
|
|
||||||
return c.getId() === id;
|
|
||||||
})[0];
|
|
||||||
if (childObj) {
|
|
||||||
const index = telemetryObjects.indexOf(childObj);
|
|
||||||
telemetryObjects.splice(index, 1);
|
|
||||||
$scope.$broadcast('plot:tickWidth', Math.max(...Object.values(tickWidthMap)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function compositionReorder(reorderPlan) {
|
|
||||||
let oldComposition = telemetryObjects.slice();
|
|
||||||
|
|
||||||
reorderPlan.forEach((reorder) => {
|
|
||||||
telemetryObjects[reorder.newIndex] = oldComposition[reorder.oldIndex];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
thisRequest.pending += 1;
|
|
||||||
|
|
||||||
openmct.objects.get(domainObject.getId())
|
|
||||||
.then(function (obj) {
|
|
||||||
thisRequest.pending -= 1;
|
|
||||||
if (thisRequest !== currentRequest) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
composition = openmct.composition.get(obj);
|
|
||||||
composition.on('add', addChild);
|
|
||||||
composition.on('remove', removeChild);
|
|
||||||
composition.on('reorder', compositionReorder);
|
|
||||||
composition.load();
|
|
||||||
unlisten = function () {
|
|
||||||
composition.off('add', addChild);
|
|
||||||
composition.off('remove', removeChild);
|
|
||||||
composition.off('reorder', compositionReorder);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onCompositionChange(newComp, oldComp) {
|
|
||||||
if (newComp !== oldComp) {
|
|
||||||
|
|
||||||
$scope.telemetryObjects = [];
|
|
||||||
|
|
||||||
objectService.getObjects(newComp).then(function (objects) {
|
|
||||||
newComp.forEach(function (id) {
|
|
||||||
$scope.telemetryObjects.push(objects[id]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.$watch('domainObject', onDomainObjectChange);
|
|
||||||
$scope.$watch('domainObject.getModel().composition', onCompositionChange);
|
|
||||||
|
|
||||||
$scope.$on('plot:tickWidth', function ($e, width) {
|
|
||||||
const plotId = $e.targetScope.domainObject.getId();
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(tickWidthMap, plotId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
tickWidthMap[plotId] = Math.max(width, tickWidthMap[plotId]);
|
|
||||||
const newTickWidth = Math.max(...Object.values(tickWidthMap));
|
|
||||||
if (newTickWidth !== tickWidth || width !== tickWidth) {
|
|
||||||
tickWidth = newTickWidth;
|
|
||||||
$scope.$broadcast('plot:tickWidth', tickWidth);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$on('plot:highlight:update', function ($e, point) {
|
|
||||||
$scope.$broadcast('plot:highlight:set', point);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
StackedPlotController.prototype.exportJPG = function () {
|
|
||||||
this.hideExportButtons = true;
|
|
||||||
this.exportImageService.exportJPG(this.$element[0], 'stacked-plot.jpg', 'export-plot')
|
|
||||||
.finally(function () {
|
|
||||||
this.hideExportButtons = false;
|
|
||||||
}.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
StackedPlotController.prototype.exportPNG = function () {
|
|
||||||
this.hideExportButtons = true;
|
|
||||||
this.exportImageService.exportPNG(this.$element[0], 'stacked-plot.png', 'export-plot')
|
|
||||||
.finally(function () {
|
|
||||||
this.hideExportButtons = false;
|
|
||||||
}.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
StackedPlotController.prototype.toggleCursorGuide = function ($event) {
|
|
||||||
this.cursorGuide = !this.cursorGuide;
|
|
||||||
this.$scope.$broadcast('cursorguide', $event);
|
|
||||||
};
|
|
||||||
|
|
||||||
StackedPlotController.prototype.toggleGridLines = function ($event) {
|
|
||||||
this.gridLines = !this.gridLines;
|
|
||||||
this.$scope.$broadcast('toggleGridLines', $event);
|
|
||||||
};
|
|
||||||
|
|
||||||
return StackedPlotController;
|
|
||||||
});
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user