From 651e61954c08fb7f85a2407b7c7e02c2aa3aad73 Mon Sep 17 00:00:00 2001 From: Joe Pea Date: Tue, 29 Mar 2022 14:39:49 -0700 Subject: [PATCH] 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 Co-authored-by: John Hill Co-authored-by: Jamie V --- package.json | 5 ++ src/api/time/TimeContext.js | 4 + src/plugins/plot/MctPlot.vue | 2 +- src/plugins/plot/MctTicks.vue | 23 +++-- .../plot/chart/MCTChartAlarmLineSet.js | 10 +++ src/plugins/plot/chart/MCTChartLineLinear.js | 2 +- .../plot/chart/MCTChartLineStepAfter.js | 2 +- src/plugins/plot/chart/MCTChartPointSet.js | 2 +- .../plot/chart/MCTChartSeriesElement.js | 24 ++++- src/plugins/plot/configuration/Collection.js | 12 ++- src/plugins/plot/configuration/ConfigStore.js | 53 +++++++---- src/plugins/plot/configuration/LegendModel.js | 5 +- src/plugins/plot/configuration/Model.js | 70 ++++++++++++++- .../configuration/PlotConfigurationModel.js | 55 ++++++++++-- src/plugins/plot/configuration/PlotSeries.js | 75 ++++++++++++++-- .../plot/configuration/SeriesCollection.js | 24 ++++- src/plugins/plot/configuration/XAxisModel.js | 80 +++++++++++++++-- src/plugins/plot/configuration/YAxisModel.js | 89 +++++++++++++------ src/plugins/plot/draw/DrawWebGL.js | 1 + src/plugins/plot/lib/eventHelpers.js | 7 ++ tsconfig.json | 9 +- 21 files changed, 464 insertions(+), 90 deletions(-) diff --git a/package.json b/package.json index 9e20f9a04b..81ac6c284b 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/api/time/TimeContext.js b/src/api/time/TimeContext.js index 6111ca7aa8..29f95b72a8 100644 --- a/src/api/time/TimeContext.js +++ b/src/api/time/TimeContext.js @@ -365,3 +365,7 @@ class TimeContext extends EventEmitter { } export default TimeContext; + +/** +@typedef {{start: number, end: number}} Bounds +*/ diff --git a/src/plugins/plot/MctPlot.vue b/src/plugins/plot/MctPlot.vue index d05302a647..f7f743324b 100644 --- a/src/plugins/plot/MctPlot.vue +++ b/src/plugins/plot/MctPlot.vue @@ -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 = { diff --git a/src/plugins/plot/MctTicks.vue b/src/plugins/plot/MctTicks.vue index 87e6159999..d264bdaf9b 100644 --- a/src/plugins/plot/MctTicks.vue +++ b/src/plugins/plot/MctTicks.vue @@ -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] }; diff --git a/src/plugins/plot/chart/MCTChartAlarmLineSet.js b/src/plugins/plot/chart/MCTChartAlarmLineSet.js index 422d93e763..3514941840 100644 --- a/src/plugins/plot/chart/MCTChartAlarmLineSet.js +++ b/src/plugins/plot/chart/MCTChartAlarmLineSet.js @@ -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 +*/ diff --git a/src/plugins/plot/chart/MCTChartLineLinear.js b/src/plugins/plot/chart/MCTChartLineLinear.js index 07529f6ce1..7226abaa67 100644 --- a/src/plugins/plot/chart/MCTChartLineLinear.js +++ b/src/plugins/plot/chart/MCTChartLineLinear.js @@ -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; } diff --git a/src/plugins/plot/chart/MCTChartLineStepAfter.js b/src/plugins/plot/chart/MCTChartLineStepAfter.js index ad9f299469..0f5957b0da 100644 --- a/src/plugins/plot/chart/MCTChartLineStepAfter.js +++ b/src/plugins/plot/chart/MCTChartLineStepAfter.js @@ -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; diff --git a/src/plugins/plot/chart/MCTChartPointSet.js b/src/plugins/plot/chart/MCTChartPointSet.js index 81b095c810..b7bb40ec9f 100644 --- a/src/plugins/plot/chart/MCTChartPointSet.js +++ b/src/plugins/plot/chart/MCTChartPointSet.js @@ -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; } diff --git a/src/plugins/plot/chart/MCTChartSeriesElement.js b/src/plugins/plot/chart/MCTChartSeriesElement.js index d7b7210eba..296968bdbe 100644 --- a/src/plugins/plot/chart/MCTChartSeriesElement.js +++ b/src/plugins/plot/chart/MCTChartSeriesElement.js @@ -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 +*/ diff --git a/src/plugins/plot/configuration/Collection.js b/src/plugins/plot/configuration/Collection.js index 2781fc07e9..57101ff2d8 100644 --- a/src/plugins/plot/configuration/Collection.js +++ b/src/plugins/plot/configuration/Collection.js @@ -21,11 +21,17 @@ *****************************************************************************/ import Model from './Model'; +/** + * @template {object} T + * @template {object} O + * @extends {Model} + */ 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 */ diff --git a/src/plugins/plot/configuration/ConfigStore.js b/src/plugins/plot/configuration/ConfigStore.js index 055acec733..fe1a1a1277 100644 --- a/src/plugins/plot/configuration/ConfigStore.js +++ b/src/plugins/plot/configuration/ConfigStore.js @@ -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} */ + 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 */ diff --git a/src/plugins/plot/configuration/LegendModel.js b/src/plugins/plot/configuration/LegendModel.js index 486d7c4b62..0f66f81af5 100644 --- a/src/plugins/plot/configuration/LegendModel.js +++ b/src/plugins/plot/configuration/LegendModel.js @@ -42,7 +42,10 @@ export default class LegendModel extends Model { } } - defaults(options) { + /** + * @override + */ + defaultModel(options) { return { position: 'top', expandByDefault: false, diff --git a/src/plugins/plot/configuration/Model.js b/src/plugins/plot/configuration/Model.js index 20ee1817be..1370a64a87 100644 --- a/src/plugins/plot/configuration/Model.js +++ b/src/plugins/plot/configuration/Model.js @@ -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} 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} */ 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 } */ this.idAttr = 'id'; } - defaults(options) { + /** + * @param {ModelOptions} options + * @returns {ModelType} + */ + defaultModel(options) { return {}; } - initialize(model) { + /** + * @param {ModelOptions} options + */ + initialize(options) { } @@ -69,14 +89,29 @@ export default class Model extends EventEmitter { return this.get(this.idAttr); } + /** + * @template {keyof ModelType} K + * @param {K} attribute + * @returns {ModelType[K]} + */ get(attribute) { return this.model[attribute]; } + /** + * @template {keyof ModelType} K + * @param {K} attribute + * @returns boolean + */ has(attribute) { return _.has(this.model, attribute); } + /** + * @template {keyof ModelType} K + * @param {K} attribute + * @param {ModelType[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} 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 + models?: T[] + openmct: OpenMCT + id?: string + [k: string]: unknown +} & O} ModelOptions +*/ diff --git a/src/plugins/plot/configuration/PlotConfigurationModel.js b/src/plugins/plot/configuration/PlotConfigurationModel.js index cd8436e41d..2a1de87e3b 100644 --- a/src/plugins/plot/configuration/PlotConfigurationModel.js +++ b/src/plugins/plot/configuration/PlotConfigurationModel.js @@ -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} */ 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} 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} 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 +*/ diff --git a/src/plugins/plot/configuration/PlotSeries.js b/src/plugins/plot/configuration/PlotSeries.js index c866f4bbda..fc355a6bfc 100644 --- a/src/plugins/plot/configuration/PlotSeries.js +++ b/src/plugins/plot/configuration/PlotSeries.js @@ -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} */ export default class PlotSeries extends Model { + /** + @param {import('./Model').ModelOptions} 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} 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} 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 +*/ diff --git a/src/plugins/plot/configuration/SeriesCollection.js b/src/plugins/plot/configuration/SeriesCollection.js index 4f8efdf79c..33160a8fa3 100644 --- a/src/plugins/plot/configuration/SeriesCollection.js +++ b/src/plugins/plot/configuration/SeriesCollection.js @@ -26,8 +26,14 @@ import Collection from "./Collection"; import Color from "@/ui/color/Color"; import ColorPalette from "@/ui/color/ColorPalette"; +/** + * @extends {Collection} + */ export default class SeriesCollection extends Collection { - + /** + @override + @param {import('./Model').ModelOptions} 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 +*/ diff --git a/src/plugins/plot/configuration/XAxisModel.js b/src/plugins/plot/configuration/XAxisModel.js index cb65d91e59..da313528d3 100644 --- a/src/plugins/plot/configuration/XAxisModel.js +++ b/src/plugins/plot/configuration/XAxisModel.js @@ -20,22 +20,38 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ import Model from "./Model"; + /** - * TODO: doc strings. - */ + * @extends {Model} + */ 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} 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} 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 +}>} AxisModelType +*/ + +/** +@typedef {AxisModelType & { + name: string + key: string +}} XAxisModelType +*/ + +/** +@typedef {{ + plot: import('./PlotConfigurationModel').default +}} XAxisModelOptions +*/ diff --git a/src/plugins/plot/configuration/YAxisModel.js b/src/plugins/plot/configuration/YAxisModel.js index 7326a9628e..f10ac83bbd 100644 --- a/src/plugins/plot/configuration/YAxisModel.js +++ b/src/plugins/plot/configuration/YAxisModel.js @@ -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} + */ export default class YAxisModel extends Model { + /** + * @override + * @param {import('./Model').ModelOptions} 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} 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 +}} YAxisModelType +*/ + +/** +@typedef {{ + plot: import('./PlotConfigurationModel').default +}} YAxisModelOptions +*/ diff --git a/src/plugins/plot/draw/DrawWebGL.js b/src/plugins/plot/draw/DrawWebGL.js index 334b2810de..6ab1e4d510 100644 --- a/src/plugins/plot/draw/DrawWebGL.js +++ b/src/plugins/plot/draw/DrawWebGL.js @@ -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 () { diff --git a/src/plugins/plot/lib/eventHelpers.js b/src/plugins/plot/lib/eventHelpers.js index 124d28b366..2b0ad0b570 100644 --- a/src/plugins/plot/lib/eventHelpers.js +++ b/src/plugins/plot/lib/eventHelpers.js @@ -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 +*/ diff --git a/tsconfig.json b/tsconfig.json index 98cd3b2518..ee9b1e1e37 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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/*"] + } } }