Fix plots performance (#4092)

* Fix no mutating props violation for Browsebar and StyleEditor
* Separate plot series data from the configuration (like it should be!)
This commit is contained in:
Shefali Joshi
2021-08-16 14:21:09 -07:00
committed by GitHub
parent 359e7377ac
commit 6dde54bd25
10 changed files with 86 additions and 63 deletions

View File

@ -25,16 +25,16 @@
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]" :class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
> >
<plot-legend :cursor-locked="!!lockHighlightPoint" <plot-legend :cursor-locked="!!lockHighlightPoint"
:series="config.series.models" :series="seriesModels"
:highlights="highlights" :highlights="highlights"
:legend="config.legend" :legend="legend"
@legendHoverChanged="legendHoverChanged" @legendHoverChanged="legendHoverChanged"
/> />
<div class="plot-wrapper-axis-and-display-area flex-elem grows"> <div class="plot-wrapper-axis-and-display-area flex-elem grows">
<y-axis v-if="config.series.models.length > 0" <y-axis v-if="seriesModels.length > 0"
:tick-width="tickWidth" :tick-width="tickWidth"
:single-series="config.series.models.length === 1" :single-series="seriesModels.length === 1"
:series-model="config.series.models[0]" :series-model="seriesModels[0]"
@yKeyChanged="setYAxisKey" @yKeyChanged="setYAxisKey"
@tickWidthChanged="onTickWidthChange" @tickWidthChanged="onTickWidthChange"
/> />
@ -141,8 +141,8 @@
> >
</div> </div>
</div> </div>
<x-axis v-if="config.series.models.length > 0 && !options.compact" <x-axis v-if="seriesModels.length > 0 && !options.compact"
:series-model="config.series.models[0]" :series-model="seriesModels[0]"
/> />
</div> </div>
@ -213,7 +213,8 @@ export default {
plotHistory: [], plotHistory: [],
selectedXKeyOption: {}, selectedXKeyOption: {},
xKeyOptions: [], xKeyOptions: [],
config: {}, seriesModels: [],
legend: {},
pending: 0, pending: 0,
isRealTime: this.openmct.time.clock() !== undefined, isRealTime: this.openmct.time.clock() !== undefined,
loaded: false, loaded: false,
@ -239,18 +240,13 @@ export default {
watch: { watch: {
plotTickWidth(newTickWidth) { plotTickWidth(newTickWidth) {
this.onTickWidthChange(newTickWidth, true); this.onTickWidthChange(newTickWidth, true);
},
gridLines(newGridLines) {
this.setGridLinesVisibility(newGridLines);
},
cursorGuide(newCursorGuide) {
this.setCursorGuideVisibility(newCursorGuide);
} }
}, },
mounted() { mounted() {
eventHelpers.extend(this); eventHelpers.extend(this);
this.config = this.getConfig(); this.config = this.getConfig();
this.legend = this.config.legend;
this.listenTo(this.config.series, 'add', this.addSeries, this); this.listenTo(this.config.series, 'add', this.addSeries, this);
this.listenTo(this.config.series, 'remove', this.removeSeries, this); this.listenTo(this.config.series, 'remove', this.removeSeries, this);
@ -290,14 +286,18 @@ export default {
config = new PlotConfigurationModel({ config = new PlotConfigurationModel({
id: configId, id: configId,
domainObject: this.domainObject, domainObject: this.domainObject,
openmct: this.openmct openmct: this.openmct,
callback: (data) => {
this.data = data;
}
}); });
configStore.add(configId, config); configStore.add(configId, config);
} }
return config; return config;
}, },
addSeries(series) { addSeries(series, index) {
this.$set(this.seriesModels, index, series);
this.listenTo(series, 'change:xKey', (xKey) => { this.listenTo(series, 'change:xKey', (xKey) => {
this.setDisplayRange(series, xKey); this.setDisplayRange(series, xKey);
}, this); }, this);
@ -377,11 +377,8 @@ export default {
}, },
stopLoading() { stopLoading() {
//TODO: Is Vue.$nextTick ok to replace $scope.$evalAsync?
this.$nextTick().then(() => {
this.pending -= 1; this.pending -= 1;
this.updateLoading(); this.updateLoading();
});
}, },
updateLoading() { updateLoading() {
@ -507,7 +504,7 @@ export default {
}, },
initialize() { initialize() {
_.debounce(this.handleWindowResize, 400); this.handleWindowResize = _.debounce(this.handleWindowResize, 500);
this.plotContainerResizeObserver = new ResizeObserver(this.handleWindowResize); this.plotContainerResizeObserver = new ResizeObserver(this.handleWindowResize);
this.plotContainerResizeObserver.observe(this.$parent.$refs.plotWrapper); this.plotContainerResizeObserver.observe(this.$parent.$refs.plotWrapper);
@ -623,7 +620,7 @@ export default {
this.config.series.models.forEach(series => delete series.closest); this.config.series.models.forEach(series => delete series.closest);
} else { } else {
this.highlights = this.config.series.models this.highlights = this.config.series.models
.filter(series => series.data.length > 0) .filter(series => series.getSeriesData().length > 0)
.map(series => { .map(series => {
series.closest = series.nearestPoint(point); series.closest = series.nearestPoint(point);
@ -927,14 +924,6 @@ export default {
this.userViewportChangeEnd(); this.userViewportChangeEnd();
}, },
setCursorGuideVisibility(cursorGuide) {
this.cursorGuide = cursorGuide === true;
},
setGridLinesVisibility(gridLines) {
this.gridLines = gridLines === true;
},
setYAxisKey(yKey) { setYAxisKey(yKey) {
this.config.series.models[0].set('yKey', yKey); this.config.series.models[0].set('yKey', yKey);
}, },

View File

@ -106,8 +106,9 @@ export default {
}, },
toggleXKeyOption() { toggleXKeyOption() {
const selectedXKey = this.selectedXKeyOptionKey; const selectedXKey = this.selectedXKeyOptionKey;
const dataForSelectedXKey = this.seriesModel.data const seriesData = this.seriesModel.getSeriesData();
? this.seriesModel.data[0][selectedXKey] const dataForSelectedXKey = seriesData
? seriesData[0][selectedXKey]
: undefined; : undefined;
if (dataForSelectedXKey !== undefined) { if (dataForSelectedXKey !== undefined) {

View File

@ -36,7 +36,7 @@ export default class MCTChartAlarmPointSet {
this.listenTo(series, 'reset', this.reset, this); this.listenTo(series, 'reset', this.reset, this);
this.listenTo(series, 'destroy', this.destroy, this); this.listenTo(series, 'destroy', this.destroy, this);
series.data.forEach(function (point, index) { this.series.getSeriesData().forEach(function (point, index) {
this.append(point, index, series); this.append(point, index, series);
}, this); }, this);
} }

View File

@ -36,7 +36,7 @@ export default class MCTChartSeriesElement {
this.listenTo(series, 'remove', this.remove, this); this.listenTo(series, 'remove', this.remove, this);
this.listenTo(series, 'reset', this.reset, this); this.listenTo(series, 'reset', this.reset, this);
this.listenTo(series, 'destroy', this.destroy, this); this.listenTo(series, 'destroy', this.destroy, this);
series.data.forEach(function (point, index) { this.series.getSeriesData().forEach(function (point, index) {
this.append(point, index, series); this.append(point, index, series);
}, this); }, this);
} }
@ -133,7 +133,7 @@ export default class MCTChartSeriesElement {
this.buffer = new Float32Array(20000); this.buffer = new Float32Array(20000);
this.count = 0; this.count = 0;
if (this.offset.x) { if (this.offset.x) {
this.series.data.forEach(function (point, index) { this.series.getSeriesData().forEach(function (point, index) {
this.append(point, index, this.series); this.append(point, index, this.series);
}, this); }, this);
} }

View File

@ -107,6 +107,7 @@ export default class PlotConfigurationModel extends Model {
updateDomainObject(domainObject) { updateDomainObject(domainObject) {
this.set('domainObject', domainObject); this.set('domainObject', domainObject);
} }
/** /**
* Clean up all objects and remove all listeners. * Clean up all objects and remove all listeners.
*/ */

View File

@ -22,6 +22,7 @@
import _ from 'lodash'; import _ from 'lodash';
import Model from "./Model"; import Model from "./Model";
import { MARKER_SHAPES } from '../draw/MarkerShapes'; import { MARKER_SHAPES } from '../draw/MarkerShapes';
import configStore from "../configuration/configStore";
/** /**
* Plot series handle interpreting telemetry metadata for a single telemetry * Plot series handle interpreting telemetry metadata for a single telemetry
@ -62,7 +63,6 @@ import { MARKER_SHAPES } from '../draw/MarkerShapes';
export default class PlotSeries extends Model { export default class PlotSeries extends Model {
constructor(options) { constructor(options) {
super(options); super(options);
this.data = [];
this.listenTo(this, 'change:xKey', this.onXKeyChange, this); this.listenTo(this, 'change:xKey', this.onXKeyChange, this);
this.listenTo(this, 'change:yKey', this.onYKeyChange, this); this.listenTo(this, 'change:yKey', this.onYKeyChange, this);
@ -115,6 +115,8 @@ export default class PlotSeries extends Model {
this.openmct = options.openmct; this.openmct = options.openmct;
this.domainObject = options.domainObject; this.domainObject = options.domainObject;
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier); this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.dataStoreId = `data-${options.collection.plot.id}-${this.keyString}`;
this.updateSeriesData([]);
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject); this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject);
this.limitDefinition = this.openmct.telemetry.limitDefinition(options.domainObject); this.limitDefinition = this.openmct.telemetry.limitDefinition(options.domainObject);
this.limits = []; this.limits = [];
@ -182,7 +184,8 @@ export default class PlotSeries extends Model {
.telemetry .telemetry
.request(this.domainObject, options) .request(this.domainObject, options)
.then(function (points) { .then(function (points) {
const newPoints = _(this.data) const data = this.getSeriesData();
const newPoints = _(data)
.concat(points) .concat(points)
.sortBy(this.getXVal) .sortBy(this.getXVal)
.uniq(true, point => [this.getXVal(point), this.getYVal(point)].join()) .uniq(true, point => [this.getXVal(point), this.getYVal(point)].join())
@ -236,7 +239,7 @@ export default class PlotSeries extends Model {
*/ */
resetStats() { resetStats() {
this.unset('stats'); this.unset('stats');
this.data.forEach(this.updateStats, this); this.getSeriesData().forEach(this.updateStats, this);
} }
/** /**
@ -244,7 +247,7 @@ export default class PlotSeries extends Model {
* data to series after reset. * data to series after reset.
*/ */
reset(newData) { reset(newData) {
this.data = []; this.updateSeriesData([]);
this.resetStats(); this.resetStats();
this.emit('reset'); this.emit('reset');
if (newData) { if (newData) {
@ -258,8 +261,9 @@ export default class PlotSeries extends Model {
*/ */
nearestPoint(xValue) { nearestPoint(xValue) {
const insertIndex = this.sortedIndex(xValue); const insertIndex = this.sortedIndex(xValue);
const lowPoint = this.data[insertIndex - 1]; const data = this.getSeriesData();
const highPoint = this.data[insertIndex]; const lowPoint = data[insertIndex - 1];
const highPoint = data[insertIndex];
const indexVal = this.getXVal(xValue); const indexVal = this.getXVal(xValue);
const lowDistance = lowPoint const lowDistance = lowPoint
? indexVal - this.getXVal(lowPoint) ? indexVal - this.getXVal(lowPoint)
@ -292,7 +296,7 @@ export default class PlotSeries extends Model {
* @private * @private
*/ */
sortedIndex(point) { sortedIndex(point) {
return _.sortedIndexBy(this.data, point, this.getXVal); return _.sortedIndexBy(this.getSeriesData(), point, this.getXVal);
} }
/** /**
* Update min/max stats for the series. * Update min/max stats for the series.
@ -346,9 +350,10 @@ export default class PlotSeries extends Model {
* a point to the end without dupe checking. * a point to the end without dupe checking.
*/ */
add(point, appendOnly) { add(point, appendOnly) {
let insertIndex = this.data.length; let data = this.getSeriesData();
let insertIndex = data.length;
const currentYVal = this.getYVal(point); const currentYVal = this.getYVal(point);
const lastYVal = this.getYVal(this.data[insertIndex - 1]); const lastYVal = this.getYVal(data[insertIndex - 1]);
if (this.isValueInvalid(currentYVal) && this.isValueInvalid(lastYVal)) { if (this.isValueInvalid(currentYVal) && this.isValueInvalid(lastYVal)) {
console.warn('[Plot] Invalid Y Values detected'); console.warn('[Plot] Invalid Y Values detected');
@ -358,18 +363,19 @@ export default class PlotSeries extends Model {
if (!appendOnly) { if (!appendOnly) {
insertIndex = this.sortedIndex(point); insertIndex = this.sortedIndex(point);
if (this.getXVal(this.data[insertIndex]) === this.getXVal(point)) { if (this.getXVal(data[insertIndex]) === this.getXVal(point)) {
return; return;
} }
if (this.getXVal(this.data[insertIndex - 1]) === this.getXVal(point)) { if (this.getXVal(data[insertIndex - 1]) === this.getXVal(point)) {
return; return;
} }
} }
this.updateStats(point); this.updateStats(point);
point.mctLimitState = this.evaluate(point); point.mctLimitState = this.evaluate(point);
this.data.splice(insertIndex, 0, point); data.splice(insertIndex, 0, point);
this.updateSeriesData(data);
this.emit('add', point, insertIndex, this); this.emit('add', point, insertIndex, this);
} }
@ -386,8 +392,10 @@ export default class PlotSeries extends Model {
* @private * @private
*/ */
remove(point) { remove(point) {
const index = this.data.indexOf(point); let data = this.getSeriesData();
this.data.splice(index, 1); const index = data.indexOf(point);
data.splice(index, 1);
this.updateSeriesData(data);
this.emit('remove', point, index, this); this.emit('remove', point, index, this);
} }
/** /**
@ -403,14 +411,16 @@ export default class PlotSeries extends Model {
purgeRecordsOutsideRange(range) { purgeRecordsOutsideRange(range) {
const startIndex = this.sortedIndex(range.min); const startIndex = this.sortedIndex(range.min);
const endIndex = this.sortedIndex(range.max) + 1; const endIndex = this.sortedIndex(range.max) + 1;
const pointsToRemove = startIndex + (this.data.length - endIndex + 1); let data = this.getSeriesData();
const pointsToRemove = startIndex + (data.length - endIndex + 1);
if (pointsToRemove > 0) { if (pointsToRemove > 0) {
if (pointsToRemove < 1000) { if (pointsToRemove < 1000) {
this.data.slice(0, startIndex).forEach(this.remove, this); data.slice(0, startIndex).forEach(this.remove, this);
this.data.slice(endIndex, this.data.length).forEach(this.remove, this); data.slice(endIndex, data.length).forEach(this.remove, this);
this.updateSeriesData(data);
this.resetStats(); this.resetStats();
} else { } else {
const newData = this.data.slice(startIndex, endIndex); const newData = this.getSeriesData().slice(startIndex, endIndex);
this.reset(newData); this.reset(newData);
} }
} }
@ -441,12 +451,13 @@ export default class PlotSeries extends Model {
} }
} }
getDisplayRange(xKey) { getDisplayRange(xKey) {
const unsortedData = this.data; const unsortedData = this.getSeriesData();
this.data = []; this.updateSeriesData([]);
unsortedData.forEach(point => this.add(point, false)); unsortedData.forEach(point => this.add(point, false));
const minValue = this.getXVal(this.data[0]); let data = this.getSeriesData();
const maxValue = this.getXVal(this.data[this.data.length - 1]); const minValue = this.getXVal(data[0]);
const maxValue = this.getXVal(data[data.length - 1]);
return { return {
min: minValue, min: minValue,
@ -470,4 +481,18 @@ export default class PlotSeries extends Model {
return this.get('name') + (unit ? ' ' + unit : ''); return this.get('name') + (unit ? ' ' + unit : '');
} }
/**
* Update the series data with the given value.
*/
updateSeriesData(data) {
configStore.add(this.dataStoreId, data);
}
/**
* Update the series data with the given value.
*/
getSeriesData() {
return configStore.get(this.dataStoreId) || [];
}
} }

View File

@ -25,7 +25,10 @@ function ConfigStore() {
ConfigStore.prototype.deleteStore = function (id) { ConfigStore.prototype.deleteStore = function (id) {
if (this.store[id]) { if (this.store[id]) {
if (this.store[id].destroy) {
this.store[id].destroy(); this.store[id].destroy();
}
delete this.store[id]; delete this.store[id];
} }
}; };

View File

@ -176,7 +176,9 @@ DrawWebGL.prototype.doDraw = function (drawType, buf, color, points, shape) {
this.gl.vertexAttribPointer(this.aVertexPosition, 2, this.gl.FLOAT, false, 0, 0); this.gl.vertexAttribPointer(this.aVertexPosition, 2, this.gl.FLOAT, false, 0, 0);
this.gl.uniform4fv(this.uColor, color); this.gl.uniform4fv(this.uColor, color);
this.gl.uniform1i(this.uMarkerShape, shapeCode); this.gl.uniform1i(this.uMarkerShape, shapeCode);
if (points !== 0) {
this.gl.drawArrays(drawType, 0, points); this.gl.drawArrays(drawType, 0, points);
}
}; };
DrawWebGL.prototype.clear = function () { DrawWebGL.prototype.clear = function () {

View File

@ -715,14 +715,15 @@ describe("the plugin", function () {
}); });
it("Adds a new point to the plot", (done) => { it("Adds a new point to the plot", (done) => {
let originalLength = config.series.models[0].data.length; let originalLength = config.series.models[0].getSeriesData().length;
config.series.models[0].add({ config.series.models[0].add({
utc: 2, utc: 2,
'some-key': 1, 'some-key': 1,
'some-other-key': 2 'some-other-key': 2
}); });
Vue.nextTick(() => { Vue.nextTick(() => {
expect(config.series.models[0].data.length).toEqual(originalLength + 1); const seriesData = config.series.models[0].getSeriesData();
expect(seriesData.length).toEqual(originalLength + 1);
done(); done();
}); });
}); });

View File

@ -233,6 +233,7 @@ export default {
}, },
mounted: function () { mounted: function () {
document.addEventListener('click', this.closeViewAndSaveMenu); document.addEventListener('click', this.closeViewAndSaveMenu);
this.promptUserbeforeNavigatingAway = this.promptUserbeforeNavigatingAway.bind(this);
window.addEventListener('beforeunload', this.promptUserbeforeNavigatingAway); window.addEventListener('beforeunload', this.promptUserbeforeNavigatingAway);
this.openmct.editor.on('isEditing', (isEditing) => { this.openmct.editor.on('isEditing', (isEditing) => {
@ -253,7 +254,7 @@ export default {
} }
document.removeEventListener('click', this.closeViewAndSaveMenu); document.removeEventListener('click', this.closeViewAndSaveMenu);
window.removeEventListener('click', this.promptUserbeforeNavigatingAway); window.removeEventListener('beforeunload', this.promptUserbeforeNavigatingAway);
}, },
methods: { methods: {
toggleSaveMenu() { toggleSaveMenu() {