mirror of
https://github.com/nasa/openmct.git
synced 2025-02-20 09:26:45 +00:00
Type annotations (#4789)
* add some types to XAxisModel * some more type defs and small code tweaks while getting familiar with plots * more type annotations and a few small tweaks * more type annotations and small tweaks to make types show * add mocha types * Add karma and jasmine, too * further simplify plot canvas creation * further simplify plot canvas creation * update types, avoid runtime behavior in type definition that breaks SeriesCollection * undo the changes to MctChart, improve it later * lint fix Co-authored-by: unlikelyzero <jchill2@gmail.com> Co-authored-by: John Hill <john.c.hill@nasa.gov> Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
This commit is contained in:
parent
d30ec4c757
commit
651e61954c
@ -8,6 +8,11 @@
|
||||
"@percy/cli": "1.0.0-beta.76",
|
||||
"@percy/playwright": "1.0.1",
|
||||
"@playwright/test": "1.19.2",
|
||||
"@types/eventemitter3": "^1.0.0",
|
||||
"@types/jasmine": "^3.10.3",
|
||||
"@types/karma": "^6.3.2",
|
||||
"@types/lodash": "^4.14.178",
|
||||
"@types/mocha": "^9.1.0",
|
||||
"allure-playwright": "2.0.0-beta.15",
|
||||
"babel-loader": "8.2.3",
|
||||
"babel-plugin-istanbul": "6.1.1",
|
||||
|
@ -365,3 +365,7 @@ class TimeContext extends EventEmitter {
|
||||
}
|
||||
|
||||
export default TimeContext;
|
||||
|
||||
/**
|
||||
@typedef {{start: number, end: number}} Bounds
|
||||
*/
|
||||
|
@ -661,7 +661,7 @@ export default {
|
||||
this.positionOverElement = {
|
||||
x: event.clientX - this.chartElementBounds.left,
|
||||
y: this.chartElementBounds.height
|
||||
- (event.clientY - this.chartElementBounds.top)
|
||||
- (event.clientY - this.chartElementBounds.top)
|
||||
};
|
||||
|
||||
this.positionOverPlot = {
|
||||
|
@ -112,6 +112,10 @@ export default {
|
||||
mounted() {
|
||||
eventHelpers.extend(this);
|
||||
|
||||
if (!this.axisType) {
|
||||
throw new Error("axis-type prop expected");
|
||||
}
|
||||
|
||||
this.axis = this.getAxisFromConfig();
|
||||
|
||||
this.tickCount = 4;
|
||||
@ -126,15 +130,16 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
getAxisFromConfig() {
|
||||
if (!this.axisType) {
|
||||
return;
|
||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
|
||||
/** @type {import('./configuration/PlotConfigurationModel').default} */
|
||||
let config = configStore.get(configId);
|
||||
|
||||
if (!config) {
|
||||
throw new Error('config is missing');
|
||||
}
|
||||
|
||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
let config = configStore.get(configId);
|
||||
if (config) {
|
||||
return config[this.axisType];
|
||||
}
|
||||
return config[this.axisType];
|
||||
},
|
||||
/**
|
||||
* Determine whether ticks should be regenerated for a given range.
|
||||
@ -210,8 +215,8 @@ export default {
|
||||
if (this.shouldRegenerateTicks(range, forceRegeneration)) {
|
||||
let newTicks = this.getTicks();
|
||||
this.tickRange = {
|
||||
min: Math.min.apply(Math, newTicks),
|
||||
max: Math.max.apply(Math, newTicks),
|
||||
min: Math.min(...newTicks),
|
||||
max: Math.max(...newTicks),
|
||||
step: newTicks[1] - newTicks[0]
|
||||
};
|
||||
|
||||
|
@ -23,6 +23,9 @@
|
||||
import eventHelpers from '../lib/eventHelpers';
|
||||
|
||||
export default class MCTChartAlarmLineSet {
|
||||
/**
|
||||
* @param {Bounds} bounds
|
||||
*/
|
||||
constructor(series, chart, offset, bounds) {
|
||||
this.series = series;
|
||||
this.chart = chart;
|
||||
@ -40,6 +43,9 @@ export default class MCTChartAlarmLineSet {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Bounds} bounds
|
||||
*/
|
||||
updateBounds(bounds) {
|
||||
this.bounds = bounds;
|
||||
this.getLimitPoints(this.series);
|
||||
@ -106,3 +112,7 @@ export default class MCTChartAlarmLineSet {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@typedef {import('@/api/time/TimeContext').Bounds} Bounds
|
||||
*/
|
||||
|
@ -23,7 +23,7 @@
|
||||
import MCTChartSeriesElement from './MCTChartSeriesElement';
|
||||
|
||||
export default class MCTChartLineLinear extends MCTChartSeriesElement {
|
||||
addPoint(point, start, count) {
|
||||
addPoint(point, start) {
|
||||
this.buffer[start] = point.x;
|
||||
this.buffer[start + 1] = point.y;
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ export default class MCTChartLineStepAfter extends MCTChartSeriesElement {
|
||||
return 2 + ((index - 1) * 4);
|
||||
}
|
||||
|
||||
addPoint(point, start, count) {
|
||||
addPoint(point, start) {
|
||||
if (start === 0 && this.count === 0) {
|
||||
// First point is easy.
|
||||
this.buffer[start] = point.x;
|
||||
|
@ -24,7 +24,7 @@ import MCTChartSeriesElement from './MCTChartSeriesElement';
|
||||
|
||||
// TODO: Is this needed? This is identical to MCTChartLineLinear. Why is it a different class?
|
||||
export default class MCTChartPointSet extends MCTChartSeriesElement {
|
||||
addPoint(point, start, count) {
|
||||
addPoint(point, start) {
|
||||
this.buffer[start] = point.x;
|
||||
this.buffer[start + 1] = point.y;
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
import eventHelpers from '../lib/eventHelpers';
|
||||
|
||||
/** @abstract */
|
||||
export default class MCTChartSeriesElement {
|
||||
constructor(series, chart, offset) {
|
||||
this.series = series;
|
||||
@ -72,9 +73,11 @@ export default class MCTChartSeriesElement {
|
||||
}
|
||||
}
|
||||
|
||||
removePoint(point, index, count) {
|
||||
// by default, do nothing.
|
||||
}
|
||||
/** @abstract */
|
||||
removePoint(index) {}
|
||||
|
||||
/** @abstract */
|
||||
addPoint(point, index) {}
|
||||
|
||||
remove(point, index, series) {
|
||||
const vertexCount = this.vertexCountForPointAtIndex(index);
|
||||
@ -155,3 +158,18 @@ export default class MCTChartSeriesElement {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** @typedef {any} TODO */
|
||||
|
||||
/** @typedef {import('../configuration/PlotSeries').default} PlotSeries */
|
||||
|
||||
/**
|
||||
@typedef {{
|
||||
x: (x: number) => number
|
||||
y: (y: number) => number
|
||||
xVal: (point: Point, pSeries: PlotSeries) => number
|
||||
yVal: (point: Point, pSeries: PlotSeries) => number
|
||||
xKey: TODO
|
||||
yKey: TODO
|
||||
}} Offset
|
||||
*/
|
||||
|
@ -21,11 +21,17 @@
|
||||
*****************************************************************************/
|
||||
import Model from './Model';
|
||||
|
||||
/**
|
||||
* @template {object} T
|
||||
* @template {object} O
|
||||
* @extends {Model<T, O>}
|
||||
*/
|
||||
export default class Collection extends Model {
|
||||
/** @type {Constructor} */
|
||||
modelClass = Model;
|
||||
|
||||
initialize(options) {
|
||||
super.initialize(options);
|
||||
this.modelClass = Model;
|
||||
if (options.models) {
|
||||
this.models = options.models.map(this.modelFn, this);
|
||||
} else {
|
||||
@ -107,3 +113,7 @@ export default class Collection extends Model {
|
||||
this.stopListening();
|
||||
}
|
||||
}
|
||||
|
||||
/** @typedef {any} TODO */
|
||||
|
||||
/** @typedef {new (...args: any[]) => object} Constructor */
|
||||
|
@ -19,32 +19,47 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
function ConfigStore() {
|
||||
this.store = {};
|
||||
}
|
||||
class ConfigStore {
|
||||
/** @type {Record<string, Destroyable>} */
|
||||
store = {};
|
||||
|
||||
ConfigStore.prototype.deleteStore = function (id) {
|
||||
if (this.store[id]) {
|
||||
if (this.store[id].destroy) {
|
||||
this.store[id].destroy();
|
||||
/**
|
||||
@param {string} id
|
||||
*/
|
||||
deleteStore(id) {
|
||||
const obj = this.store[id];
|
||||
|
||||
if (obj) {
|
||||
if (obj.destroy) {
|
||||
obj.destroy();
|
||||
}
|
||||
|
||||
delete this.store[id];
|
||||
}
|
||||
|
||||
delete this.store[id];
|
||||
}
|
||||
};
|
||||
|
||||
ConfigStore.prototype.deleteAll = function () {
|
||||
Object.keys(this.store).forEach(id => this.deleteStore(id));
|
||||
};
|
||||
deleteAll() {
|
||||
Object.keys(this.store).forEach(id => this.deleteStore(id));
|
||||
}
|
||||
|
||||
ConfigStore.prototype.add = function (id, config) {
|
||||
this.store[id] = config;
|
||||
};
|
||||
/**
|
||||
@param {string} id
|
||||
@param {any} config
|
||||
*/
|
||||
add(id, config) {
|
||||
this.store[id] = config;
|
||||
}
|
||||
|
||||
ConfigStore.prototype.get = function (id) {
|
||||
return this.store[id];
|
||||
};
|
||||
/**
|
||||
@param {string} id
|
||||
*/
|
||||
get(id) {
|
||||
return this.store[id];
|
||||
}
|
||||
}
|
||||
|
||||
const STORE = new ConfigStore();
|
||||
|
||||
export default STORE;
|
||||
|
||||
/** @typedef {{destroy?(): void}} Destroyable */
|
||||
|
@ -42,7 +42,10 @@ export default class LegendModel extends Model {
|
||||
}
|
||||
}
|
||||
|
||||
defaults(options) {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
defaultModel(options) {
|
||||
return {
|
||||
position: 'top',
|
||||
expandByDefault: false,
|
||||
|
@ -20,11 +20,18 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import eventHelpers from "../lib/eventHelpers";
|
||||
import _ from 'lodash';
|
||||
|
||||
/**
|
||||
* @template {object} T
|
||||
* @template {object} O
|
||||
*/
|
||||
export default class Model extends EventEmitter {
|
||||
/**
|
||||
* @param {ModelOptions<T, O>} options
|
||||
*/
|
||||
constructor(options) {
|
||||
super();
|
||||
|
||||
@ -35,10 +42,14 @@ export default class Model extends EventEmitter {
|
||||
options = {};
|
||||
}
|
||||
|
||||
// FIXME: this.id is defined as a method further below, but here it is
|
||||
// assigned a possibly-undefined value. Is this code unused?
|
||||
this.id = options.id;
|
||||
|
||||
/** @type {ModelType<T>} */
|
||||
this.model = options.model;
|
||||
this.collection = options.collection;
|
||||
const defaults = this.defaults(options);
|
||||
const defaults = this.defaultModel(options);
|
||||
if (!this.model) {
|
||||
this.model = options.model = defaults;
|
||||
} else {
|
||||
@ -46,14 +57,23 @@ export default class Model extends EventEmitter {
|
||||
}
|
||||
|
||||
this.initialize(options);
|
||||
|
||||
/** @type {keyof ModelType<T> } */
|
||||
this.idAttr = 'id';
|
||||
}
|
||||
|
||||
defaults(options) {
|
||||
/**
|
||||
* @param {ModelOptions<T, O>} options
|
||||
* @returns {ModelType<T>}
|
||||
*/
|
||||
defaultModel(options) {
|
||||
return {};
|
||||
}
|
||||
|
||||
initialize(model) {
|
||||
/**
|
||||
* @param {ModelOptions<T, O>} options
|
||||
*/
|
||||
initialize(options) {
|
||||
|
||||
}
|
||||
|
||||
@ -69,14 +89,29 @@ export default class Model extends EventEmitter {
|
||||
return this.get(this.idAttr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {keyof ModelType<T>} K
|
||||
* @param {K} attribute
|
||||
* @returns {ModelType<T>[K]}
|
||||
*/
|
||||
get(attribute) {
|
||||
return this.model[attribute];
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {keyof ModelType<T>} K
|
||||
* @param {K} attribute
|
||||
* @returns boolean
|
||||
*/
|
||||
has(attribute) {
|
||||
return _.has(this.model, attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {keyof ModelType<T>} K
|
||||
* @param {K} attribute
|
||||
* @param {ModelType<T>[K]} value
|
||||
*/
|
||||
set(attribute, value) {
|
||||
const oldValue = this.model[attribute];
|
||||
this.model[attribute] = value;
|
||||
@ -84,6 +119,10 @@ export default class Model extends EventEmitter {
|
||||
this.emit('change:' + attribute, value, oldValue, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {keyof ModelType<T>} K
|
||||
* @param {K} attribute
|
||||
*/
|
||||
unset(attribute) {
|
||||
const oldValue = this.model[attribute];
|
||||
delete this.model[attribute];
|
||||
@ -91,3 +130,26 @@ export default class Model extends EventEmitter {
|
||||
this.emit('change:' + attribute, undefined, oldValue, this);
|
||||
}
|
||||
}
|
||||
|
||||
/** @typedef {any} TODO */
|
||||
|
||||
/** @typedef {TODO} OpenMCT */
|
||||
|
||||
/**
|
||||
@template {object} T
|
||||
@typedef {{
|
||||
id?: string
|
||||
} & T} ModelType
|
||||
*/
|
||||
|
||||
/**
|
||||
@template {object} T
|
||||
@template {object} O
|
||||
@typedef {{
|
||||
model?: ModelType<T>
|
||||
models?: T[]
|
||||
openmct: OpenMCT
|
||||
id?: string
|
||||
[k: string]: unknown
|
||||
} & O} ModelOptions
|
||||
*/
|
||||
|
@ -26,20 +26,30 @@ import SeriesCollection from "./SeriesCollection";
|
||||
import XAxisModel from "./XAxisModel";
|
||||
import YAxisModel from "./YAxisModel";
|
||||
import LegendModel from "./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.
|
||||
*
|
||||
* @extends {Model<PlotConfigModelType, PlotConfigModelOptions>}
|
||||
*/
|
||||
export default class PlotConfigurationModel extends Model {
|
||||
/**
|
||||
* Initializes all sub models and then passes references to submodels
|
||||
* to those that need it.
|
||||
*
|
||||
* @override
|
||||
* @param {import('./Model').ModelOptions<PlotConfigModelType, PlotConfigModelOptions>} options
|
||||
*/
|
||||
initialize(options) {
|
||||
this.openmct = options.openmct;
|
||||
|
||||
// This is a type assertion for TypeScript, this error is never thrown in practice.
|
||||
if (!options.model) {
|
||||
throw new Error('Not a collection model.');
|
||||
}
|
||||
|
||||
this.xAxis = new XAxisModel({
|
||||
model: options.model.xAxis,
|
||||
plot: this,
|
||||
@ -76,6 +86,8 @@ export default class PlotConfigurationModel extends Model {
|
||||
}
|
||||
/**
|
||||
* Retrieve the persisted series config for a given identifier.
|
||||
* @param {import('./PlotSeries').Identifier} identifier
|
||||
* @returns {import('./PlotSeries').PlotSeriesModelType=}
|
||||
*/
|
||||
getPersistedSeriesConfig(identifier) {
|
||||
const domainObject = this.get('domainObject');
|
||||
@ -123,15 +135,48 @@ export default class PlotConfigurationModel extends Model {
|
||||
/**
|
||||
* Return defaults, which are extracted from the passed in domain
|
||||
* object.
|
||||
* @override
|
||||
* @param {import('./Model').ModelOptions<PlotConfigModelType, PlotConfigModelOptions>} options
|
||||
*/
|
||||
defaults(options) {
|
||||
defaultModel(options) {
|
||||
return {
|
||||
series: [],
|
||||
domainObject: options.domainObject,
|
||||
xAxis: {
|
||||
},
|
||||
yAxis: _.cloneDeep(_.get(options.domainObject, 'configuration.yAxis', {})),
|
||||
legend: _.cloneDeep(_.get(options.domainObject, 'configuration.legend', {}))
|
||||
xAxis: {},
|
||||
yAxis: _.cloneDeep(options.domainObject.configuration?.yAxis ?? {}),
|
||||
legend: _.cloneDeep(options.domainObject.configuration?.legend ?? {})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** @typedef {any} TODO */
|
||||
|
||||
/** @typedef {import('./PlotSeries').default} PlotSeries */
|
||||
|
||||
/**
|
||||
@typedef {{
|
||||
configuration: {
|
||||
series: import('./PlotSeries').PlotSeriesModelType[]
|
||||
}
|
||||
}} SomeDomainObject_NeedsName
|
||||
*/
|
||||
|
||||
/**
|
||||
@typedef {{
|
||||
xAxis: import('./XAxisModel').XAxisModelType
|
||||
yAxis: import('./YAxisModel').YAxisModelType
|
||||
legend: TODO
|
||||
series: PlotSeries[]
|
||||
domainObject: SomeDomainObject_NeedsName
|
||||
}} PlotConfigModelType
|
||||
*/
|
||||
|
||||
/** @typedef {TODO} SomeOtherDomainObject */
|
||||
|
||||
/**
|
||||
TODO: Is SomeOtherDomainObject the same domain object as with SomeDomainObject_NeedsName?
|
||||
@typedef {{
|
||||
plot: import('./PlotConfigurationModel').default
|
||||
domainObject: SomeOtherDomainObject
|
||||
}} PlotConfigModelOptions
|
||||
*/
|
||||
|
@ -59,9 +59,15 @@ import configStore from "../configuration/ConfigStore";
|
||||
* `metadata`: the Open MCT Telemetry Metadata Manager for the associated
|
||||
* telemetry point.
|
||||
* `formats`: the Open MCT format map for this telemetry point.
|
||||
*
|
||||
* @extends {Model<PlotSeriesModelType, PlotSeriesModelOptions>}
|
||||
*/
|
||||
export default class PlotSeries extends Model {
|
||||
/**
|
||||
@param {import('./Model').ModelOptions<PlotSeriesModelType, PlotSeriesModelOptions>} options
|
||||
*/
|
||||
constructor(options) {
|
||||
|
||||
super(options);
|
||||
|
||||
this.listenTo(this, 'change:xKey', this.onXKeyChange, this);
|
||||
@ -76,8 +82,10 @@ export default class PlotSeries extends Model {
|
||||
|
||||
/**
|
||||
* Set defaults for telemetry series.
|
||||
* @param {import('./Model').ModelOptions<PlotSeriesModelType, PlotSeriesModelOptions>} options
|
||||
* @override
|
||||
*/
|
||||
defaults(options) {
|
||||
defaultModel(options) {
|
||||
this.metadata = options
|
||||
.openmct
|
||||
.telemetry
|
||||
@ -109,13 +117,21 @@ export default class PlotSeries extends Model {
|
||||
|
||||
/**
|
||||
* Remove real-time subscription when destroyed.
|
||||
* @override
|
||||
*/
|
||||
onDestroy(model) {
|
||||
destroy() {
|
||||
super.destroy();
|
||||
|
||||
if (this.unsubscribe) {
|
||||
this.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set defaults for telemetry series.
|
||||
* @override
|
||||
* @param {import('./Model').ModelOptions<PlotSeriesModelType, PlotSeriesModelOptions>} options
|
||||
*/
|
||||
initialize(options) {
|
||||
this.openmct = options.openmct;
|
||||
this.domainObject = options.domainObject;
|
||||
@ -136,9 +152,11 @@ export default class PlotSeries extends Model {
|
||||
|
||||
});
|
||||
this.openmct.time.on('bounds', this.updateLimits);
|
||||
this.on('destroy', this.onDestroy, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Bounds} bounds
|
||||
*/
|
||||
updateLimits(bounds) {
|
||||
this.emit('limitBounds', bounds);
|
||||
}
|
||||
@ -188,7 +206,7 @@ export default class PlotSeries extends Model {
|
||||
return this.openmct
|
||||
.telemetry
|
||||
.request(this.domainObject, options)
|
||||
.then(function (points) {
|
||||
.then((points) => {
|
||||
const data = this.getSeriesData();
|
||||
const newPoints = _(data)
|
||||
.concat(points)
|
||||
@ -196,7 +214,7 @@ export default class PlotSeries extends Model {
|
||||
.uniq(true, point => [this.getXVal(point), this.getYVal(point)].join())
|
||||
.value();
|
||||
this.reset(newPoints);
|
||||
}.bind(this))
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('Error fetching data', error);
|
||||
});
|
||||
@ -501,8 +519,55 @@ export default class PlotSeries extends Model {
|
||||
|
||||
/**
|
||||
* Update the series data with the given value.
|
||||
* @returns {Array<{
|
||||
cos: number
|
||||
sin: number
|
||||
mctLimitState: {
|
||||
cssClass: string
|
||||
high: number
|
||||
low: {sin: number, cos: number}
|
||||
name: string
|
||||
}
|
||||
utc: number
|
||||
wavelength: number
|
||||
yesterday: number
|
||||
}>}
|
||||
*/
|
||||
getSeriesData() {
|
||||
return configStore.get(this.dataStoreId) || [];
|
||||
}
|
||||
}
|
||||
|
||||
/** @typedef {any} TODO */
|
||||
|
||||
/** @typedef {{key: string, namespace: string}} Identifier */
|
||||
|
||||
/**
|
||||
@typedef {{
|
||||
identifier: Identifier
|
||||
name: string
|
||||
unit: string
|
||||
xKey: string
|
||||
yKey: string
|
||||
markers: boolean
|
||||
markerShape: keyof typeof MARKER_SHAPES
|
||||
markerSize: number
|
||||
alarmMarkers: boolean
|
||||
limitLines: boolean
|
||||
interpolate: boolean
|
||||
stats: TODO
|
||||
}} PlotSeriesModelType
|
||||
*/
|
||||
|
||||
/**
|
||||
@typedef {{
|
||||
model: PlotSeriesModelType
|
||||
collection: import('./SeriesCollection').default
|
||||
persistedConfig: PlotSeriesModelType
|
||||
filters: TODO
|
||||
}} PlotSeriesModelOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
@typedef {import('@/api/time/TimeContext').Bounds} Bounds
|
||||
*/
|
||||
|
@ -26,8 +26,14 @@ import Collection from "./Collection";
|
||||
import Color from "@/ui/color/Color";
|
||||
import ColorPalette from "@/ui/color/ColorPalette";
|
||||
|
||||
/**
|
||||
* @extends {Collection<SeriesCollectionModelType, SeriesCollectionOptions>}
|
||||
*/
|
||||
export default class SeriesCollection extends Collection {
|
||||
|
||||
/**
|
||||
@override
|
||||
@param {import('./Model').ModelOptions<SeriesCollectionModelType, SeriesCollectionOptions>} options
|
||||
*/
|
||||
initialize(options) {
|
||||
super.initialize(options);
|
||||
this.modelClass = PlotSeries;
|
||||
@ -83,11 +89,15 @@ export default class SeriesCollection extends Collection {
|
||||
// Clone to prevent accidental mutation by ref.
|
||||
seriesConfig = JSON.parse(JSON.stringify(seriesConfig));
|
||||
|
||||
if (!seriesConfig) {
|
||||
throw "not possible";
|
||||
}
|
||||
|
||||
this.add(new PlotSeries({
|
||||
model: seriesConfig,
|
||||
domainObject: domainObject,
|
||||
collection: this,
|
||||
openmct: this.openmct,
|
||||
collection: this,
|
||||
persistedConfig: this.plot
|
||||
.getPersistedSeriesConfig(domainObject.identifier),
|
||||
filters: filters
|
||||
@ -163,3 +173,13 @@ export default class SeriesCollection extends Collection {
|
||||
})[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@typedef {PlotSeries} SeriesCollectionModelType
|
||||
*/
|
||||
|
||||
/**
|
||||
@typedef {{
|
||||
plot: import('./PlotConfigurationModel').default
|
||||
}} SeriesCollectionOptions
|
||||
*/
|
||||
|
@ -20,22 +20,38 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import Model from "./Model";
|
||||
|
||||
/**
|
||||
* TODO: doc strings.
|
||||
*/
|
||||
* @extends {Model<XAxisModelType, XAxisModelOptions>}
|
||||
*/
|
||||
export default class XAxisModel extends Model {
|
||||
// Despite providing template types to the Model class, we still need to
|
||||
// re-define the type of the following initialize() method's options arg. Tracking
|
||||
// issue for this: https://github.com/microsoft/TypeScript/issues/32082
|
||||
// When they fix it, we can remove the `@param` we have here.
|
||||
/**
|
||||
* @override
|
||||
* @param {import('./Model').ModelOptions<XAxisModelType, XAxisModelOptions>} options
|
||||
*/
|
||||
initialize(options) {
|
||||
this.plot = options.plot;
|
||||
|
||||
// This is a type assertion for TypeScript, this error is not thrown in practice.
|
||||
if (!options.model) {
|
||||
throw new Error('Not a collection model.');
|
||||
}
|
||||
|
||||
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:range', (newValue) => {
|
||||
if (!this.get('frozen')) {
|
||||
this.set('displayRange', newValue);
|
||||
}
|
||||
});
|
||||
|
||||
this.on('change:frozen', ((frozen, oldValue, model) => {
|
||||
this.on('change:frozen', ((frozen) => {
|
||||
if (!frozen) {
|
||||
model.set('range', this.get('range'));
|
||||
this.set('range', this.get('range'));
|
||||
}
|
||||
}));
|
||||
|
||||
@ -45,6 +61,10 @@ export default class XAxisModel extends Model {
|
||||
|
||||
this.listenTo(this, 'change:key', this.changeKey, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} newKey
|
||||
*/
|
||||
changeKey(newKey) {
|
||||
const series = this.plot.series.first();
|
||||
if (series) {
|
||||
@ -68,12 +88,17 @@ export default class XAxisModel extends Model {
|
||||
plotSeries.reset();
|
||||
});
|
||||
}
|
||||
defaults(options) {
|
||||
/**
|
||||
* @param {import('./Model').ModelOptions<XAxisModelType, XAxisModelOptions>} options
|
||||
* @override
|
||||
*/
|
||||
defaultModel(options) {
|
||||
const bounds = options.openmct.time.bounds();
|
||||
const timeSystem = options.openmct.time.timeSystem();
|
||||
const format = options.openmct.telemetry.getFormatter(timeSystem.timeFormat);
|
||||
|
||||
return {
|
||||
/** @type {XAxisModelType} */
|
||||
const defaultModel = {
|
||||
name: timeSystem.name,
|
||||
key: timeSystem.key,
|
||||
format: format.format.bind(format),
|
||||
@ -83,5 +108,42 @@ export default class XAxisModel extends Model {
|
||||
},
|
||||
frozen: false
|
||||
};
|
||||
|
||||
return defaultModel;
|
||||
}
|
||||
}
|
||||
|
||||
/** @typedef {any} TODO */
|
||||
|
||||
/** @typedef {TODO} OpenMCT */
|
||||
|
||||
/**
|
||||
@typedef {{
|
||||
min: number
|
||||
max: number
|
||||
}} NumberRange
|
||||
*/
|
||||
|
||||
/**
|
||||
@typedef {import("./Model").ModelType<{
|
||||
range: NumberRange
|
||||
displayRange: NumberRange
|
||||
frozen: boolean
|
||||
label: string
|
||||
format: (n: number) => string
|
||||
values: Array<TODO>
|
||||
}>} AxisModelType
|
||||
*/
|
||||
|
||||
/**
|
||||
@typedef {AxisModelType & {
|
||||
name: string
|
||||
key: string
|
||||
}} XAxisModelType
|
||||
*/
|
||||
|
||||
/**
|
||||
@typedef {{
|
||||
plot: import('./PlotConfigurationModel').default
|
||||
}} XAxisModelOptions
|
||||
*/
|
||||
|
@ -23,27 +23,32 @@ import _ from 'lodash';
|
||||
import Model from './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.
|
||||
*
|
||||
*/
|
||||
* 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.
|
||||
*
|
||||
* @extends {Model<YAxisModelType, YAxisModelOptions>}
|
||||
*/
|
||||
export default class YAxisModel extends Model {
|
||||
/**
|
||||
* @override
|
||||
* @param {import('./Model').ModelOptions<YAxisModelType, YAxisModelOptions>} options
|
||||
*/
|
||||
initialize(options) {
|
||||
this.plot = options.plot;
|
||||
this.listenTo(this, 'change:stats', this.calculateAutoscaleExtents, this);
|
||||
@ -53,6 +58,9 @@ export default class YAxisModel extends Model {
|
||||
this.listenTo(this, 'change:range', this.updateDisplayRange, this);
|
||||
this.updateDisplayRange(this.get('range'));
|
||||
}
|
||||
/**
|
||||
* @param {import('./SeriesCollection').default} seriesCollection
|
||||
*/
|
||||
listenToSeriesCollection(seriesCollection) {
|
||||
this.seriesCollection = seriesCollection;
|
||||
this.listenTo(this.seriesCollection, 'add', (series => {
|
||||
@ -138,6 +146,9 @@ export default class YAxisModel extends Model {
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
/**
|
||||
* @param {import('./PlotSeries').default} series
|
||||
*/
|
||||
trackSeries(series) {
|
||||
this.listenTo(series, 'change:stats', seriesStats => {
|
||||
if (!seriesStats) {
|
||||
@ -163,12 +174,13 @@ export default class YAxisModel extends Model {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Update yAxis format, values, and label from known series.
|
||||
*/
|
||||
updateFromSeries(series) {
|
||||
* Update yAxis format, values, and label from known series.
|
||||
* @param {import('./SeriesCollection').default} seriesCollection
|
||||
*/
|
||||
updateFromSeries(seriesCollection) {
|
||||
const plotModel = this.plot.get('domainObject');
|
||||
const label = _.get(plotModel, 'configuration.yAxis.label');
|
||||
const sampleSeries = series.first();
|
||||
const sampleSeries = seriesCollection.first();
|
||||
if (!sampleSeries) {
|
||||
if (!label) {
|
||||
this.unset('label');
|
||||
@ -183,7 +195,7 @@ export default class YAxisModel extends Model {
|
||||
this.set('format', yFormat.format.bind(yFormat));
|
||||
this.set('values', yMetadata.values);
|
||||
if (!label) {
|
||||
const labelName = series.map(function (s) {
|
||||
const labelName = seriesCollection.map(function (s) {
|
||||
return s.metadata ? s.metadata.value(s.get('yKey')).name : '';
|
||||
}).reduce(function (a, b) {
|
||||
if (a === undefined) {
|
||||
@ -203,7 +215,7 @@ export default class YAxisModel extends Model {
|
||||
return;
|
||||
}
|
||||
|
||||
const labelUnits = series.map(function (s) {
|
||||
const labelUnits = seriesCollection.map(function (s) {
|
||||
return s.metadata ? s.metadata.value(s.get('yKey')).units : '';
|
||||
}).reduce(function (a, b) {
|
||||
if (a === undefined) {
|
||||
@ -224,7 +236,13 @@ export default class YAxisModel extends Model {
|
||||
}
|
||||
}
|
||||
}
|
||||
defaults(options) {
|
||||
/**
|
||||
* @override
|
||||
* @param {import('./Model').ModelOptions<YAxisModelType, YAxisModelOptions>} options
|
||||
* @returns {YAxisModelType}
|
||||
*/
|
||||
defaultModel(options) {
|
||||
// @ts-ignore incomplete YAxisModelType object used for default value.
|
||||
return {
|
||||
frozen: false,
|
||||
autoscale: true,
|
||||
@ -232,3 +250,20 @@ export default class YAxisModel extends Model {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** @typedef {any} TODO */
|
||||
|
||||
/**
|
||||
@typedef {import('./XAxisModel').AxisModelType & {
|
||||
autoscale: boolean
|
||||
autoscalePadding: number
|
||||
stats: import('./XAxisModel').NumberRange
|
||||
values: Array<TODO>
|
||||
}} YAxisModelType
|
||||
*/
|
||||
|
||||
/**
|
||||
@typedef {{
|
||||
plot: import('./PlotConfigurationModel').default
|
||||
}} YAxisModelOptions
|
||||
*/
|
||||
|
@ -110,6 +110,7 @@ DrawWebGL.prototype.onContextLost = function (event) {
|
||||
this.emit('error');
|
||||
this.isContextLost = true;
|
||||
this.destroy();
|
||||
// TODO re-initialize and re-draw on context restored
|
||||
};
|
||||
|
||||
DrawWebGL.prototype.initContext = function () {
|
||||
|
@ -90,3 +90,10 @@ const helperFunctions = {
|
||||
};
|
||||
|
||||
export default helperFunctions;
|
||||
|
||||
/**
|
||||
@typedef {{
|
||||
listenTo: (object: any, event: any, callback: any, context: any) => void
|
||||
stopListening: (object: any, event: any, callback: any, context: any) => void
|
||||
}} EventHelpers
|
||||
*/
|
||||
|
@ -7,7 +7,14 @@
|
||||
"allowJs": true,
|
||||
"checkJs": false,
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"noImplicitOverride": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node"
|
||||
"moduleResolution": "node",
|
||||
|
||||
"paths": {
|
||||
// matches the alias in webpack config, so that types for those imports are visible.
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user