mirror of
https://github.com/nasa/openmct.git
synced 2025-06-19 15:43:48 +00:00
Remove large series models from reactive data in plots (#6961)
* remove series object from highlights * remove series models from legend reactive data * drawing all annotations at once is way faster * fix multi annotations * lots of reactive things depending on config * make annotation lookup faster * lint * readd perf test * address PR comments * fix highlight typo --------- Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com> Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
This commit is contained in:
@ -32,7 +32,7 @@ const {
|
|||||||
waitForPlotsToRender
|
waitForPlotsToRender
|
||||||
} = require('../../appActions');
|
} = require('../../appActions');
|
||||||
|
|
||||||
test.describe.fixme('Plot Tagging Performance', () => {
|
test.describe('Plot Tagging Performance', () => {
|
||||||
/**
|
/**
|
||||||
* Given a canvas and a set of points, tags the points on the canvas.
|
* Given a canvas and a set of points, tags the points on the canvas.
|
||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
|
@ -78,8 +78,8 @@
|
|||||||
:rectangles="rectangles"
|
:rectangles="rectangles"
|
||||||
:highlights="highlights"
|
:highlights="highlights"
|
||||||
:show-limit-line-labels="limitLineLabels"
|
:show-limit-line-labels="limitLineLabels"
|
||||||
:annotated-points="annotatedPoints"
|
:annotated-points-by-series="annotatedPointsBySeries"
|
||||||
:annotation-selections="annotationSelections"
|
:annotation-selections-by-series="annotationSelectionsBySeries"
|
||||||
:hidden-y-axis-ids="hiddenYAxisIds"
|
:hidden-y-axis-ids="hiddenYAxisIds"
|
||||||
:annotation-viewing-and-editing-allowed="annotationViewingAndEditingAllowed"
|
:annotation-viewing-and-editing-allowed="annotationViewingAndEditingAllowed"
|
||||||
@plotReinitializeCanvas="initCanvas"
|
@plotReinitializeCanvas="initCanvas"
|
||||||
@ -245,9 +245,9 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
altPressed: false,
|
altPressed: false,
|
||||||
|
annotatedPointsBySeries: {},
|
||||||
highlights: [],
|
highlights: [],
|
||||||
annotatedPoints: [],
|
annotationSelectionsBySeries: {},
|
||||||
annotationSelections: [],
|
|
||||||
annotationsEverLoaded: false,
|
annotationsEverLoaded: false,
|
||||||
lockHighlightPoint: false,
|
lockHighlightPoint: false,
|
||||||
yKeyOptions: [],
|
yKeyOptions: [],
|
||||||
@ -256,8 +256,6 @@ export default {
|
|||||||
plotHistory: [],
|
plotHistory: [],
|
||||||
selectedXKeyOption: {},
|
selectedXKeyOption: {},
|
||||||
xKeyOptions: [],
|
xKeyOptions: [],
|
||||||
seriesModels: [],
|
|
||||||
legend: {},
|
|
||||||
pending: 0,
|
pending: 0,
|
||||||
isRealTime: this.openmct.time.isRealTime(),
|
isRealTime: this.openmct.time.isRealTime(),
|
||||||
loaded: false,
|
loaded: false,
|
||||||
@ -346,6 +344,8 @@ export default {
|
|||||||
this.abortController = new AbortController();
|
this.abortController = new AbortController();
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.seriesModels = [];
|
||||||
|
this.config = {};
|
||||||
this.yAxisIdVisibility = {};
|
this.yAxisIdVisibility = {};
|
||||||
this.offsetWidth = 0;
|
this.offsetWidth = 0;
|
||||||
|
|
||||||
@ -357,7 +357,6 @@ export default {
|
|||||||
this.setTimeContext = this.setTimeContext.bind(this);
|
this.setTimeContext = this.setTimeContext.bind(this);
|
||||||
|
|
||||||
this.config = this.getConfig();
|
this.config = this.getConfig();
|
||||||
this.legend = this.config.legend;
|
|
||||||
this.yAxes = [
|
this.yAxes = [
|
||||||
{
|
{
|
||||||
id: this.config.yAxis.id,
|
id: this.config.yAxis.id,
|
||||||
@ -455,7 +454,7 @@ export default {
|
|||||||
const clickedOption = event.target.closest('.js-autocomplete-options') !== null;
|
const clickedOption = event.target.closest('.js-autocomplete-options') !== null;
|
||||||
if (!clickedInsidePlot && !clickedInsideInspector && !clickedOption) {
|
if (!clickedInsidePlot && !clickedInsideInspector && !clickedOption) {
|
||||||
this.rectangles = [];
|
this.rectangles = [];
|
||||||
this.annotationSelections = [];
|
this.annotationSelectionsBySeries = {};
|
||||||
this.selectPlot();
|
this.selectPlot();
|
||||||
document.body.removeEventListener('click', this.cancelSelection);
|
document.body.removeEventListener('click', this.cancelSelection);
|
||||||
}
|
}
|
||||||
@ -639,7 +638,7 @@ export default {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (rawAnnotationsForPlot) {
|
if (rawAnnotationsForPlot) {
|
||||||
this.annotatedPoints = this.findAnnotationPoints(rawAnnotationsForPlot);
|
this.annotatedPointsBySeries = this.findAnnotationPoints(rawAnnotationsForPlot);
|
||||||
}
|
}
|
||||||
this.annotationsEverLoaded = true;
|
this.annotationsEverLoaded = true;
|
||||||
},
|
},
|
||||||
@ -795,7 +794,7 @@ export default {
|
|||||||
};
|
};
|
||||||
this.config.xAxis.set('range', newRange);
|
this.config.xAxis.set('range', newRange);
|
||||||
if (!isTick) {
|
if (!isTick) {
|
||||||
this.annotatedPoints = [];
|
this.annotatedPointsBySeries = {};
|
||||||
this.clearPanZoomHistory();
|
this.clearPanZoomHistory();
|
||||||
this.synchronizeIfBoundsMatch();
|
this.synchronizeIfBoundsMatch();
|
||||||
this.loadMoreData(newRange, true);
|
this.loadMoreData(newRange, true);
|
||||||
@ -1157,7 +1156,7 @@ export default {
|
|||||||
series.closest = series.nearestPoint(point);
|
series.closest = series.nearestPoint(point);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
series: series,
|
seriesKeyString: series.keyString,
|
||||||
point: series.closest
|
point: series.closest
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -1245,7 +1244,7 @@ export default {
|
|||||||
|
|
||||||
startMarquee(event, annotationEvent) {
|
startMarquee(event, annotationEvent) {
|
||||||
this.rectangles = [];
|
this.rectangles = [];
|
||||||
this.annotationSelections = [];
|
this.annotationSelectionsBySeries = {};
|
||||||
this.canvas.classList.remove('plot-drag');
|
this.canvas.classList.remove('plot-drag');
|
||||||
this.canvas.classList.add('plot-marquee');
|
this.canvas.classList.add('plot-marquee');
|
||||||
|
|
||||||
@ -1273,7 +1272,10 @@ export default {
|
|||||||
|
|
||||||
const nearbyAnnotations = this.gatherNearbyAnnotations();
|
const nearbyAnnotations = this.gatherNearbyAnnotations();
|
||||||
|
|
||||||
if (this.annotationViewingAndEditingAllowed && this.annotationSelections.length) {
|
if (
|
||||||
|
this.annotationViewingAndEditingAllowed &&
|
||||||
|
Object.keys(this.annotationSelectionsBySeries).length
|
||||||
|
) {
|
||||||
//no annotations were found, but we are adding some now
|
//no annotations were found, but we are adding some now
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1353,20 +1355,18 @@ export default {
|
|||||||
|
|
||||||
document.body.addEventListener('click', this.cancelSelection);
|
document.body.addEventListener('click', this.cancelSelection);
|
||||||
},
|
},
|
||||||
selectNewPlotAnnotations(boundingBoxPerYAxis, pointsInBox, event) {
|
selectNewPlotAnnotations(boundingBoxPerYAxis, pointsInBoxBySeries, event) {
|
||||||
let targetDomainObjects = {};
|
let targetDomainObjects = {};
|
||||||
let targetDetails = {};
|
let targetDetails = {};
|
||||||
let annotations = [];
|
let annotations = [];
|
||||||
pointsInBox.forEach((pointInBox) => {
|
Object.keys(pointsInBoxBySeries).forEach((seriesKey) => {
|
||||||
if (pointInBox.length) {
|
const seriesModel = this.getSeries(seriesKey);
|
||||||
const seriesID = pointInBox[0].series.keyString;
|
const boundingBoxWithId = boundingBoxPerYAxis.find(
|
||||||
const boundingBoxWithId = boundingBoxPerYAxis.find(
|
(box) => box.id === seriesModel.get('yAxisId')
|
||||||
(box) => box.id === pointInBox[0].series.get('yAxisId')
|
);
|
||||||
);
|
targetDetails[seriesKey] = boundingBoxWithId?.boundingBox;
|
||||||
targetDetails[seriesID] = boundingBoxWithId?.boundingBox;
|
|
||||||
|
|
||||||
targetDomainObjects[seriesID] = pointInBox[0].series.domainObject;
|
targetDomainObjects[seriesKey] = seriesModel.domainObject;
|
||||||
}
|
|
||||||
});
|
});
|
||||||
this.selectPlotAnnotations({
|
this.selectPlotAnnotations({
|
||||||
targetDetails,
|
targetDetails,
|
||||||
@ -1375,7 +1375,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
findAnnotationPoints(rawAnnotations) {
|
findAnnotationPoints(rawAnnotations) {
|
||||||
const annotationsByPoints = [];
|
const annotationsBySeries = {};
|
||||||
rawAnnotations.forEach((rawAnnotation) => {
|
rawAnnotations.forEach((rawAnnotation) => {
|
||||||
if (rawAnnotation.targets) {
|
if (rawAnnotation.targets) {
|
||||||
const targetValues = Object.values(rawAnnotation.targets);
|
const targetValues = Object.values(rawAnnotation.targets);
|
||||||
@ -1390,6 +1390,9 @@ export default {
|
|||||||
if (!series) {
|
if (!series) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!annotationsBySeries[seriesId]) {
|
||||||
|
annotationsBySeries[seriesId] = [];
|
||||||
|
}
|
||||||
|
|
||||||
boundingBoxPerYAxis.push({
|
boundingBoxPerYAxis.push({
|
||||||
id: series.get('yAxisId'),
|
id: series.get('yAxisId'),
|
||||||
@ -1397,15 +1400,23 @@ export default {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const pointsInBox = this.getPointsInBox(boundingBoxPerYAxis, rawAnnotation);
|
const pointsInBoxBySeries = this.getPointsInBoxBySeries(
|
||||||
if (pointsInBox && pointsInBox.length) {
|
boundingBoxPerYAxis,
|
||||||
annotationsByPoints.push(pointsInBox.flat());
|
rawAnnotation
|
||||||
|
);
|
||||||
|
if (pointsInBoxBySeries && Object.values(pointsInBoxBySeries).length) {
|
||||||
|
Object.keys(pointsInBoxBySeries).forEach((seriesKeyString) => {
|
||||||
|
const pointsInBox = pointsInBoxBySeries[seriesKeyString];
|
||||||
|
if (pointsInBox && pointsInBox.length) {
|
||||||
|
annotationsBySeries[seriesKeyString].push(...pointsInBox);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return annotationsByPoints.flat();
|
return annotationsBySeries;
|
||||||
},
|
},
|
||||||
searchWithFlatbush(seriesData, seriesModel, boundingBox) {
|
searchWithFlatbush(seriesData, seriesModel, boundingBox) {
|
||||||
const flatbush = new Flatbush(seriesData.length);
|
const flatbush = new Flatbush(seriesData.length);
|
||||||
@ -1425,9 +1436,15 @@ export default {
|
|||||||
|
|
||||||
return rangeResults;
|
return rangeResults;
|
||||||
},
|
},
|
||||||
getPointsInBox(boundingBoxPerYAxis, rawAnnotation) {
|
getSeries(keyStringToFind) {
|
||||||
|
const foundSeries = this.seriesModels.find((series) => {
|
||||||
|
return series.keyString === keyStringToFind;
|
||||||
|
});
|
||||||
|
return foundSeries;
|
||||||
|
},
|
||||||
|
getPointsInBoxBySeries(boundingBoxPerYAxis, rawAnnotation) {
|
||||||
// load series models in KD-Trees
|
// load series models in KD-Trees
|
||||||
const seriesKDTrees = [];
|
const searchResultsBySeries = {};
|
||||||
this.seriesModels.forEach((seriesModel) => {
|
this.seriesModels.forEach((seriesModel) => {
|
||||||
const boundingBoxWithId = boundingBoxPerYAxis.find(
|
const boundingBoxWithId = boundingBoxPerYAxis.find(
|
||||||
(box) => box.id === seriesModel.get('yAxisId')
|
(box) => box.id === seriesModel.get('yAxisId')
|
||||||
@ -1440,16 +1457,15 @@ export default {
|
|||||||
|
|
||||||
const seriesData = seriesModel.getSeriesData();
|
const seriesData = seriesModel.getSeriesData();
|
||||||
if (seriesData && seriesData.length) {
|
if (seriesData && seriesData.length) {
|
||||||
const searchResults = [];
|
searchResultsBySeries[seriesModel.keyString] = [];
|
||||||
const rangeResults = this.searchWithFlatbush(seriesData, seriesModel, boundingBox);
|
const rangeResults = this.searchWithFlatbush(seriesData, seriesModel, boundingBox);
|
||||||
rangeResults.forEach((id) => {
|
rangeResults.forEach((id) => {
|
||||||
const seriesDatum = seriesData[id];
|
const seriesDatum = seriesData[id];
|
||||||
if (seriesDatum) {
|
if (seriesDatum) {
|
||||||
const result = {
|
const result = {
|
||||||
series: seriesModel,
|
|
||||||
point: seriesDatum
|
point: seriesDatum
|
||||||
};
|
};
|
||||||
searchResults.push(result);
|
searchResultsBySeries[seriesModel.keyString].push(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rawAnnotation) {
|
if (rawAnnotation) {
|
||||||
@ -1463,13 +1479,10 @@ export default {
|
|||||||
seriesDatum.annotationsById[annotationKeyString] = rawAnnotation;
|
seriesDatum.annotationsById[annotationKeyString] = rawAnnotation;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (searchResults.length) {
|
|
||||||
seriesKDTrees.push(searchResults);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return seriesKDTrees;
|
return searchResultsBySeries;
|
||||||
},
|
},
|
||||||
endAnnotationMarquee(event) {
|
endAnnotationMarquee(event) {
|
||||||
const boundingBoxPerYAxis = [];
|
const boundingBoxPerYAxis = [];
|
||||||
@ -1490,13 +1503,13 @@ export default {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const pointsInBox = this.getPointsInBox(boundingBoxPerYAxis);
|
const pointsInBoxBySeries = this.getPointsInBoxBySeries(boundingBoxPerYAxis);
|
||||||
if (!pointsInBox) {
|
if (!pointsInBoxBySeries || Object.values(pointsInBoxBySeries).length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.annotationSelections = pointsInBox.flat();
|
this.annotationSelectionsBySeries = pointsInBoxBySeries;
|
||||||
this.selectNewPlotAnnotations(boundingBoxPerYAxis, pointsInBox, event);
|
this.selectNewPlotAnnotations(boundingBoxPerYAxis, this.annotationSelectionsBySeries, event);
|
||||||
},
|
},
|
||||||
endZoomMarquee() {
|
endZoomMarquee() {
|
||||||
const startPixels = this.marquee.startPixels;
|
const startPixels = this.marquee.startPixels;
|
||||||
|
@ -101,16 +101,16 @@ export default {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
annotatedPoints: {
|
annotatedPointsBySeries: {
|
||||||
type: Array,
|
type: Object,
|
||||||
default() {
|
default() {
|
||||||
return [];
|
return {};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
annotationSelections: {
|
annotationSelectionsBySeries: {
|
||||||
type: Array,
|
type: Object,
|
||||||
default() {
|
default() {
|
||||||
return [];
|
return {};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
showLimitLineLabels: {
|
showLimitLineLabels: {
|
||||||
@ -143,17 +143,15 @@ export default {
|
|||||||
},
|
},
|
||||||
deep: true
|
deep: true
|
||||||
},
|
},
|
||||||
annotatedPoints: {
|
annotatedPointsBySeries: {
|
||||||
handler() {
|
handler() {
|
||||||
this.scheduleDraw();
|
this.scheduleDraw();
|
||||||
},
|
}
|
||||||
deep: true
|
|
||||||
},
|
},
|
||||||
annotationSelections: {
|
annotationSelectionsBySeries: {
|
||||||
handler() {
|
handler() {
|
||||||
this.scheduleDraw();
|
this.scheduleDraw();
|
||||||
},
|
}
|
||||||
deep: true
|
|
||||||
},
|
},
|
||||||
rectangles: {
|
rectangles: {
|
||||||
handler() {
|
handler() {
|
||||||
@ -177,6 +175,7 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
eventHelpers.extend(this);
|
eventHelpers.extend(this);
|
||||||
|
this.seriesModels = [];
|
||||||
this.config = this.getConfig();
|
this.config = this.getConfig();
|
||||||
this.isDestroyed = false;
|
this.isDestroyed = false;
|
||||||
this.lines = [];
|
this.lines = [];
|
||||||
@ -256,7 +255,8 @@ export default {
|
|||||||
this.changeAlarmMarkers(newXKey, oldXKey, series);
|
this.changeAlarmMarkers(newXKey, oldXKey, series);
|
||||||
this.changeLimitLines(newXKey, oldXKey, series);
|
this.changeLimitLines(newXKey, oldXKey, series);
|
||||||
},
|
},
|
||||||
onSeriesAdd(series) {
|
onSeriesAdd(series, index) {
|
||||||
|
this.seriesModels[index] = series;
|
||||||
this.listenTo(series, `change:${HANDLED_ATTRIBUTES.xKey}`, this.reDraw, this);
|
this.listenTo(series, `change:${HANDLED_ATTRIBUTES.xKey}`, this.reDraw, this);
|
||||||
this.listenTo(
|
this.listenTo(
|
||||||
series,
|
series,
|
||||||
@ -280,10 +280,15 @@ export default {
|
|||||||
this.makeChartElement(series);
|
this.makeChartElement(series);
|
||||||
this.makeLimitLines(series);
|
this.makeLimitLines(series);
|
||||||
},
|
},
|
||||||
onSeriesRemove(series) {
|
onSeriesRemove(seriesToRemove) {
|
||||||
this.stopListening(series);
|
this.stopListening(seriesToRemove);
|
||||||
this.removeChartElement(series);
|
this.removeChartElement(seriesToRemove);
|
||||||
this.scheduleDraw();
|
this.scheduleDraw();
|
||||||
|
|
||||||
|
const seriesIndexToRemove = this.seriesModels.findIndex(
|
||||||
|
(series) => series.keyString === seriesToRemove.keyString
|
||||||
|
);
|
||||||
|
this.seriesModels.splice(seriesIndexToRemove, 1);
|
||||||
},
|
},
|
||||||
onAddPoint(point, insertIndex, series) {
|
onAddPoint(point, insertIndex, series) {
|
||||||
const mainYAxisId = this.config.yAxis.get('id');
|
const mainYAxisId = this.config.yAxis.get('id');
|
||||||
@ -642,8 +647,8 @@ export default {
|
|||||||
this.drawHighlights(id);
|
this.drawHighlights(id);
|
||||||
// only draw these in fixed time mode or plot is paused
|
// only draw these in fixed time mode or plot is paused
|
||||||
if (this.annotationViewingAndEditingAllowed) {
|
if (this.annotationViewingAndEditingAllowed) {
|
||||||
this.drawAnnotatedPoints(id);
|
this.prepareToDrawAnnotatedPoints(id);
|
||||||
this.drawAnnotationSelections(id);
|
this.prepareToDrawAnnotationSelections(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -698,6 +703,7 @@ export default {
|
|||||||
pointSets.forEach(this.drawPoints, this);
|
pointSets.forEach(this.drawPoints, this);
|
||||||
const alarmSets = this.alarmSets.filter(this.matchByYAxisId.bind(this, id));
|
const alarmSets = this.alarmSets.filter(this.matchByYAxisId.bind(this, id));
|
||||||
alarmSets.forEach(this.drawAlarmPoints, this);
|
alarmSets.forEach(this.drawAlarmPoints, this);
|
||||||
|
//console.timeEnd('📈 drawSeries');
|
||||||
},
|
},
|
||||||
updateLimitLines() {
|
updateLimitLines() {
|
||||||
Array.from(this.$refs.limitArea.children).forEach((el) => el.remove());
|
Array.from(this.$refs.limitArea.children).forEach((el) => el.remove());
|
||||||
@ -827,82 +833,110 @@ export default {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
drawAnnotatedPoints(yAxisId) {
|
prepareToDrawAnnotatedPoints(yAxisId) {
|
||||||
// we should do this by series, and then plot all the points at once instead
|
if (this.annotatedPointsBySeries && Object.values(this.annotatedPointsBySeries).length) {
|
||||||
// of doing it one by one
|
const uniquePointsToDraw = new Set();
|
||||||
if (this.annotatedPoints && this.annotatedPoints.length) {
|
|
||||||
const uniquePointsToDraw = [];
|
|
||||||
|
|
||||||
const annotatedPoints = this.annotatedPoints.filter(
|
Object.keys(this.annotatedPointsBySeries).forEach((seriesKeyString) => {
|
||||||
this.matchByYAxisId.bind(this, yAxisId)
|
const seriesModel = this.getSeries(seriesKeyString);
|
||||||
);
|
const matchesYAxis = this.matchByYAxisId(yAxisId, { series: seriesModel });
|
||||||
annotatedPoints.forEach((annotatedPoint) => {
|
if (!matchesYAxis) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// annotation points are all within range (checked in MctPlot with FlatBush), so we don't need to check
|
// annotation points are all within range (checked in MctPlot with FlatBush), so we don't need to check
|
||||||
const canvasXValue = this.offset[yAxisId].xVal(
|
const annotatedPointBuffer = new Float32Array(
|
||||||
annotatedPoint.point,
|
this.annotatedPointsBySeries[seriesKeyString].length * 2
|
||||||
annotatedPoint.series
|
|
||||||
);
|
);
|
||||||
const canvasYValue = this.offset[yAxisId].yVal(
|
Object.values(this.annotatedPointsBySeries[seriesKeyString]).forEach(
|
||||||
annotatedPoint.point,
|
(annotatedPoint, index) => {
|
||||||
annotatedPoint.series
|
const canvasXValue = this.offset[yAxisId].xVal(annotatedPoint.point, seriesModel);
|
||||||
|
const canvasYValue = this.offset[yAxisId].yVal(annotatedPoint.point, seriesModel);
|
||||||
|
const drawnPointKey = `${canvasXValue}|${canvasYValue}`;
|
||||||
|
if (!uniquePointsToDraw.has(drawnPointKey)) {
|
||||||
|
annotatedPointBuffer[index * 2] = canvasXValue;
|
||||||
|
annotatedPointBuffer[index * 2 + 1] = canvasYValue;
|
||||||
|
uniquePointsToDraw.add(drawnPointKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
const pointToDraw = new Float32Array([canvasXValue, canvasYValue]);
|
this.drawAnnotatedPoints(seriesModel, annotatedPointBuffer);
|
||||||
const drawnPoint = uniquePointsToDraw.some((rawPoint) => {
|
});
|
||||||
return rawPoint[0] === pointToDraw[0] && rawPoint[1] === pointToDraw[1];
|
}
|
||||||
});
|
},
|
||||||
if (!drawnPoint) {
|
drawAnnotatedPoints(seriesModel, annotatedPointBuffer) {
|
||||||
uniquePointsToDraw.push(pointToDraw);
|
if (annotatedPointBuffer && seriesModel) {
|
||||||
this.drawAnnotatedPoint(annotatedPoint, pointToDraw);
|
const color = seriesModel.get('color').asRGBAArray();
|
||||||
|
// set transparency
|
||||||
|
color[3] = 0.15;
|
||||||
|
const pointCount = annotatedPointBuffer.length / 2;
|
||||||
|
const shape = seriesModel.get('markerShape');
|
||||||
|
|
||||||
|
this.drawAPI.drawPoints(annotatedPointBuffer, color, pointCount, ANNOTATION_SIZE, shape);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
prepareToDrawAnnotationSelections(yAxisId) {
|
||||||
|
if (
|
||||||
|
this.annotationSelectionsBySeries &&
|
||||||
|
Object.keys(this.annotationSelectionsBySeries).length
|
||||||
|
) {
|
||||||
|
Object.keys(this.annotationSelectionsBySeries).forEach((seriesKeyString) => {
|
||||||
|
const seriesModel = this.getSeries(seriesKeyString);
|
||||||
|
const matchesYAxis = this.matchByYAxisId(yAxisId, { series: seriesModel });
|
||||||
|
if (matchesYAxis) {
|
||||||
|
const annotationSelectionBuffer = new Float32Array(
|
||||||
|
this.annotationSelectionsBySeries[seriesKeyString].length * 2
|
||||||
|
);
|
||||||
|
Object.values(this.annotationSelectionsBySeries[seriesKeyString]).forEach(
|
||||||
|
(annotatedSelectedPoint, index) => {
|
||||||
|
const canvasXValue = this.offset[yAxisId].xVal(
|
||||||
|
annotatedSelectedPoint.point,
|
||||||
|
seriesModel
|
||||||
|
);
|
||||||
|
const canvasYValue = this.offset[yAxisId].yVal(
|
||||||
|
annotatedSelectedPoint.point,
|
||||||
|
seriesModel
|
||||||
|
);
|
||||||
|
annotationSelectionBuffer[index * 2] = canvasXValue;
|
||||||
|
annotationSelectionBuffer[index * 2 + 1] = canvasYValue;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.drawAnnotationSelections(seriesModel, annotationSelectionBuffer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
drawAnnotatedPoint(annotatedPoint, pointToDraw) {
|
drawAnnotationSelections(seriesModel, annotationSelectionBuffer) {
|
||||||
if (annotatedPoint.point && annotatedPoint.series) {
|
|
||||||
const color = annotatedPoint.series.get('color').asRGBAArray();
|
|
||||||
// set transparency
|
|
||||||
color[3] = 0.15;
|
|
||||||
const pointCount = 1;
|
|
||||||
const shape = annotatedPoint.series.get('markerShape');
|
|
||||||
|
|
||||||
this.drawAPI.drawPoints(pointToDraw, color, pointCount, ANNOTATION_SIZE, shape);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
drawAnnotationSelections(yAxisId) {
|
|
||||||
if (this.annotationSelections && this.annotationSelections.length) {
|
|
||||||
const annotationSelections = this.annotationSelections.filter(
|
|
||||||
this.matchByYAxisId.bind(this, yAxisId)
|
|
||||||
);
|
|
||||||
annotationSelections.forEach(this.drawAnnotationSelection.bind(this, yAxisId), this);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
drawAnnotationSelection(yAxisId, annotationSelection) {
|
|
||||||
const points = new Float32Array([
|
|
||||||
this.offset[yAxisId].xVal(annotationSelection.point, annotationSelection.series),
|
|
||||||
this.offset[yAxisId].yVal(annotationSelection.point, annotationSelection.series)
|
|
||||||
]);
|
|
||||||
|
|
||||||
const color = [255, 255, 255, 1]; // white
|
const color = [255, 255, 255, 1]; // white
|
||||||
const pointCount = 1;
|
const pointCount = annotationSelectionBuffer.length / 2;
|
||||||
const shape = annotationSelection.series.get('markerShape');
|
const shape = seriesModel.get('markerShape');
|
||||||
|
|
||||||
this.drawAPI.drawPoints(points, color, pointCount, ANNOTATION_SIZE, shape);
|
this.drawAPI.drawPoints(annotationSelectionBuffer, color, pointCount, ANNOTATION_SIZE, shape);
|
||||||
},
|
},
|
||||||
drawHighlights(yAxisId) {
|
drawHighlights(yAxisId) {
|
||||||
if (this.highlights && this.highlights.length) {
|
if (this.highlights && this.highlights.length) {
|
||||||
const highlights = this.highlights.filter(this.matchByYAxisId.bind(this, yAxisId));
|
const highlights = this.highlights.filter((highlight) => {
|
||||||
|
const series = this.getSeries(highlight.seriesKeyString);
|
||||||
|
return this.matchByYAxisId.bind(yAxisId, { series });
|
||||||
|
});
|
||||||
highlights.forEach(this.drawHighlight.bind(this, yAxisId), this);
|
highlights.forEach(this.drawHighlight.bind(this, yAxisId), this);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getSeries(keyStringToFind) {
|
||||||
|
const foundSeries = this.seriesModels.find((series) => {
|
||||||
|
return series.keyString === keyStringToFind;
|
||||||
|
});
|
||||||
|
return foundSeries;
|
||||||
|
},
|
||||||
drawHighlight(yAxisId, highlight) {
|
drawHighlight(yAxisId, highlight) {
|
||||||
|
const series = this.getSeries(highlight.seriesKeyString);
|
||||||
const points = new Float32Array([
|
const points = new Float32Array([
|
||||||
this.offset[yAxisId].xVal(highlight.point, highlight.series),
|
this.offset[yAxisId].xVal(highlight.point, series),
|
||||||
this.offset[yAxisId].yVal(highlight.point, highlight.series)
|
this.offset[yAxisId].yVal(highlight.point, series)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const color = highlight.series.get('color').asRGBAArray();
|
const color = series.get('color').asRGBAArray();
|
||||||
const pointCount = 1;
|
const pointCount = 1;
|
||||||
const shape = highlight.series.get('markerShape');
|
const shape = series.get('markerShape');
|
||||||
|
|
||||||
this.drawAPI.drawPoints(points, color, pointCount, HIGHLIGHT_SIZE, shape);
|
this.drawAPI.drawPoints(points, color, pointCount, HIGHLIGHT_SIZE, shape);
|
||||||
},
|
},
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
:key="`${seriesObject.keyString}-${seriesIndex}-collapsed`"
|
:key="`${seriesObject.keyString}-${seriesIndex}-collapsed`"
|
||||||
:highlights="highlights"
|
:highlights="highlights"
|
||||||
:value-to-show-when-collapsed="valueToShowWhenCollapsed"
|
:value-to-show-when-collapsed="valueToShowWhenCollapsed"
|
||||||
:series-object="seriesObject"
|
:series-key-string="seriesObject.keyString"
|
||||||
@legendHoverChanged="legendHoverChanged"
|
@legendHoverChanged="legendHoverChanged"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -72,7 +72,7 @@
|
|||||||
<plot-legend-item-expanded
|
<plot-legend-item-expanded
|
||||||
v-for="(seriesObject, seriesIndex) in seriesModels"
|
v-for="(seriesObject, seriesIndex) in seriesModels"
|
||||||
:key="`${seriesObject.keyString}-${seriesIndex}-expanded`"
|
:key="`${seriesObject.keyString}-${seriesIndex}-expanded`"
|
||||||
:series-object="seriesObject"
|
:series-key-string="seriesObject.keyString"
|
||||||
:highlights="highlights"
|
:highlights="highlights"
|
||||||
@legendHoverChanged="legendHoverChanged"
|
@legendHoverChanged="legendHoverChanged"
|
||||||
/>
|
/>
|
||||||
|
@ -71,12 +71,9 @@ export default {
|
|||||||
mixins: [stalenessMixin, tooltipHelpers],
|
mixins: [stalenessMixin, tooltipHelpers],
|
||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'domainObject'],
|
||||||
props: {
|
props: {
|
||||||
seriesObject: {
|
seriesKeyString: {
|
||||||
type: Object,
|
type: String,
|
||||||
required: true,
|
required: true
|
||||||
default() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
highlights: {
|
highlights: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@ -112,7 +109,7 @@ export default {
|
|||||||
highlights: {
|
highlights: {
|
||||||
handler(newHighlights) {
|
handler(newHighlights) {
|
||||||
const highlightedObject = newHighlights.find(
|
const highlightedObject = newHighlights.find(
|
||||||
(highlight) => highlight.series.keyString === this.seriesObject.keyString
|
(highlight) => highlight.seriesKeyString === this.seriesKeyString
|
||||||
);
|
);
|
||||||
if (newHighlights.length === 0 || highlightedObject) {
|
if (newHighlights.length === 0 || highlightedObject) {
|
||||||
this.initialize(highlightedObject);
|
this.initialize(highlightedObject);
|
||||||
@ -122,28 +119,14 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.seriesModels = [];
|
||||||
eventHelpers.extend(this);
|
eventHelpers.extend(this);
|
||||||
this.config = this.getConfig();
|
this.config = this.getConfig();
|
||||||
|
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
|
||||||
|
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this);
|
||||||
|
this.config.series.forEach(this.onSeriesAdd, this);
|
||||||
this.legend = this.config.legend;
|
this.legend = this.config.legend;
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
this.listenTo(
|
|
||||||
this.seriesObject,
|
|
||||||
'change:color',
|
|
||||||
(newColor) => {
|
|
||||||
this.updateColor(newColor);
|
|
||||||
},
|
|
||||||
this
|
|
||||||
);
|
|
||||||
this.listenTo(
|
|
||||||
this.seriesObject,
|
|
||||||
'change:name',
|
|
||||||
() => {
|
|
||||||
this.updateName();
|
|
||||||
},
|
|
||||||
this
|
|
||||||
);
|
|
||||||
this.subscribeToStaleness(this.seriesObject.domainObject);
|
|
||||||
this.initialize();
|
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.stopListening();
|
this.stopListening();
|
||||||
@ -154,8 +137,44 @@ export default {
|
|||||||
|
|
||||||
return configStore.get(configId);
|
return configStore.get(configId);
|
||||||
},
|
},
|
||||||
|
onSeriesAdd(series, index) {
|
||||||
|
this.seriesModels[index] = series;
|
||||||
|
if (series.keyString === this.seriesKeyString) {
|
||||||
|
this.listenTo(
|
||||||
|
series,
|
||||||
|
'change:color',
|
||||||
|
(newColor) => {
|
||||||
|
this.updateColor(newColor);
|
||||||
|
},
|
||||||
|
this
|
||||||
|
);
|
||||||
|
this.listenTo(
|
||||||
|
series,
|
||||||
|
'change:name',
|
||||||
|
() => {
|
||||||
|
this.updateName();
|
||||||
|
},
|
||||||
|
this
|
||||||
|
);
|
||||||
|
this.subscribeToStaleness(series.domainObject);
|
||||||
|
this.initialize();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSeriesRemove(seriesToRemove) {
|
||||||
|
const seriesIndexToRemove = this.seriesModels.findIndex(
|
||||||
|
(series) => series.keyString === seriesToRemove.keyString
|
||||||
|
);
|
||||||
|
this.seriesModels.splice(seriesIndexToRemove, 1);
|
||||||
|
},
|
||||||
|
getSeries(keyStringToFind) {
|
||||||
|
const foundSeries = this.seriesModels.find((series) => {
|
||||||
|
return series.keyString === keyStringToFind;
|
||||||
|
});
|
||||||
|
return foundSeries;
|
||||||
|
},
|
||||||
initialize(highlightedObject) {
|
initialize(highlightedObject) {
|
||||||
const seriesObject = highlightedObject?.series || this.seriesObject;
|
const seriesKeyStringToUse = highlightedObject?.seriesKeyString || this.seriesKeyString;
|
||||||
|
const seriesObject = this.getSeries(seriesKeyStringToUse);
|
||||||
|
|
||||||
this.isMissing = seriesObject.domainObject.status === 'missing';
|
this.isMissing = seriesObject.domainObject.status === 'missing';
|
||||||
this.colorAsHexString = seriesObject.get('color').asHexString();
|
this.colorAsHexString = seriesObject.get('color').asHexString();
|
||||||
@ -187,7 +206,8 @@ export default {
|
|||||||
this.colorAsHexString = newColor.asHexString();
|
this.colorAsHexString = newColor.asHexString();
|
||||||
},
|
},
|
||||||
updateName() {
|
updateName() {
|
||||||
this.nameWithUnit = this.seriesObject.nameWithUnit();
|
const seriesObject = this.getSeries(this.seriesKeyString);
|
||||||
|
this.nameWithUnit = seriesObject.nameWithUnit();
|
||||||
},
|
},
|
||||||
toggleHover(hover) {
|
toggleHover(hover) {
|
||||||
this.hover = hover;
|
this.hover = hover;
|
||||||
@ -195,15 +215,16 @@ export default {
|
|||||||
'legendHoverChanged',
|
'legendHoverChanged',
|
||||||
this.hover
|
this.hover
|
||||||
? {
|
? {
|
||||||
seriesKey: this.seriesObject.keyString
|
seriesKey: this.seriesKeyString
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
async showToolTip() {
|
async showToolTip() {
|
||||||
|
const seriesObject = this.getSeries(this.seriesKeyString);
|
||||||
const { BELOW } = this.openmct.tooltips.TOOLTIP_LOCATIONS;
|
const { BELOW } = this.openmct.tooltips.TOOLTIP_LOCATIONS;
|
||||||
this.buildToolTip(
|
this.buildToolTip(
|
||||||
await this.getTelemetryPathString(this.seriesObject.domainObject.identifier),
|
await this.getTelemetryPathString(seriesObject.domainObject.identifier),
|
||||||
BELOW,
|
BELOW,
|
||||||
'series'
|
'series'
|
||||||
);
|
);
|
||||||
|
@ -86,12 +86,9 @@ export default {
|
|||||||
mixins: [stalenessMixin, tooltipHelpers],
|
mixins: [stalenessMixin, tooltipHelpers],
|
||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'domainObject'],
|
||||||
props: {
|
props: {
|
||||||
seriesObject: {
|
seriesKeyString: {
|
||||||
type: Object,
|
type: String,
|
||||||
required: true,
|
required: true
|
||||||
default() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
highlights: {
|
highlights: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@ -135,7 +132,7 @@ export default {
|
|||||||
highlights: {
|
highlights: {
|
||||||
handler(newHighlights) {
|
handler(newHighlights) {
|
||||||
const highlightedObject = newHighlights.find(
|
const highlightedObject = newHighlights.find(
|
||||||
(highlight) => highlight.series.keyString === this.seriesObject.keyString
|
(highlight) => highlight.seriesKeyString === this.seriesKeyString
|
||||||
);
|
);
|
||||||
if (newHighlights.length === 0 || highlightedObject) {
|
if (newHighlights.length === 0 || highlightedObject) {
|
||||||
this.initialize(highlightedObject);
|
this.initialize(highlightedObject);
|
||||||
@ -145,40 +142,62 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.seriesModels = [];
|
||||||
eventHelpers.extend(this);
|
eventHelpers.extend(this);
|
||||||
this.config = this.getConfig();
|
this.config = this.getConfig();
|
||||||
|
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
|
||||||
|
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this);
|
||||||
|
this.config.series.forEach(this.onSeriesAdd, this);
|
||||||
this.legend = this.config.legend;
|
this.legend = this.config.legend;
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
this.listenTo(
|
|
||||||
this.seriesObject,
|
|
||||||
'change:color',
|
|
||||||
(newColor) => {
|
|
||||||
this.updateColor(newColor);
|
|
||||||
},
|
|
||||||
this
|
|
||||||
);
|
|
||||||
this.listenTo(
|
|
||||||
this.seriesObject,
|
|
||||||
'change:name',
|
|
||||||
() => {
|
|
||||||
this.updateName();
|
|
||||||
},
|
|
||||||
this
|
|
||||||
);
|
|
||||||
this.subscribeToStaleness(this.seriesObject.domainObject);
|
|
||||||
this.initialize();
|
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.stopListening();
|
this.stopListening();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onSeriesAdd(series, index) {
|
||||||
|
this.seriesModels[index] = series;
|
||||||
|
if (series.keyString === this.seriesKeyString) {
|
||||||
|
this.listenTo(
|
||||||
|
series,
|
||||||
|
'change:color',
|
||||||
|
(newColor) => {
|
||||||
|
this.updateColor(newColor);
|
||||||
|
},
|
||||||
|
this
|
||||||
|
);
|
||||||
|
this.listenTo(
|
||||||
|
series,
|
||||||
|
'change:name',
|
||||||
|
() => {
|
||||||
|
this.updateName();
|
||||||
|
},
|
||||||
|
this
|
||||||
|
);
|
||||||
|
this.subscribeToStaleness(series.domainObject);
|
||||||
|
this.initialize();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSeriesRemove(seriesToRemove) {
|
||||||
|
const seriesIndexToRemove = this.seriesModels.findIndex(
|
||||||
|
(series) => series.keyString === seriesToRemove.keyString
|
||||||
|
);
|
||||||
|
this.seriesModels.splice(seriesIndexToRemove, 1);
|
||||||
|
},
|
||||||
|
getSeries(keyStringToFind) {
|
||||||
|
const foundSeries = this.seriesModels.find((series) => {
|
||||||
|
return series.keyString === keyStringToFind;
|
||||||
|
});
|
||||||
|
return foundSeries;
|
||||||
|
},
|
||||||
getConfig() {
|
getConfig() {
|
||||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
|
|
||||||
return configStore.get(configId);
|
return configStore.get(configId);
|
||||||
},
|
},
|
||||||
initialize(highlightedObject) {
|
initialize(highlightedObject) {
|
||||||
const seriesObject = highlightedObject?.series || this.seriesObject;
|
const seriesKeyStringToUse = highlightedObject?.seriesKeyString || this.seriesKeyString;
|
||||||
|
const seriesObject = this.getSeries(seriesKeyStringToUse);
|
||||||
|
|
||||||
this.isMissing = seriesObject.domainObject.status === 'missing';
|
this.isMissing = seriesObject.domainObject.status === 'missing';
|
||||||
this.colorAsHexString = seriesObject.get('color').asHexString();
|
this.colorAsHexString = seriesObject.get('color').asHexString();
|
||||||
@ -209,22 +228,20 @@ export default {
|
|||||||
updateColor(newColor) {
|
updateColor(newColor) {
|
||||||
this.colorAsHexString = newColor.asHexString();
|
this.colorAsHexString = newColor.asHexString();
|
||||||
},
|
},
|
||||||
updateName(newName) {
|
updateName() {
|
||||||
this.nameWithUnit = this.seriesObject.nameWithUnit();
|
const seriesObject = this.getSeries(this.seriesKeyString);
|
||||||
|
this.nameWithUnit = seriesObject.nameWithUnit();
|
||||||
},
|
},
|
||||||
toggleHover(hover) {
|
toggleHover(hover) {
|
||||||
this.hover = hover;
|
this.hover = hover;
|
||||||
this.$emit('legendHoverChanged', {
|
this.$emit('legendHoverChanged', {
|
||||||
seriesKey: this.hover ? this.seriesObject.keyString : ''
|
seriesKey: this.hover ? this.seriesKeyString : ''
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async showToolTip() {
|
async showToolTip() {
|
||||||
const { BELOW } = this.openmct.tooltips.TOOLTIP_LOCATIONS;
|
const { BELOW } = this.openmct.tooltips.TOOLTIP_LOCATIONS;
|
||||||
this.buildToolTip(
|
const seriesIdentifier = this.openmct.objects.parseKeyString(this.seriesKeyString);
|
||||||
await this.getTelemetryPathString(this.seriesObject.domainObject.identifier),
|
this.buildToolTip(await this.getTelemetryPathString(seriesIdentifier), BELOW, 'seriesName');
|
||||||
BELOW,
|
|
||||||
'seriesName'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user