Compare commits
7 Commits
whats-up-d
...
hide-gripp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b117890f27 | ||
|
|
afc37209d2 | ||
|
|
e45b58e2a8 | ||
|
|
8b3487bdbe | ||
|
|
50719b1383 | ||
|
|
cfda067794 | ||
|
|
4847cc8931 |
@@ -156,7 +156,7 @@ async function turnOffAutoscale(page) {
|
|||||||
await page.locator('text=Unnamed Overlay Plot Snapshot >> button').nth(3).click();
|
await page.locator('text=Unnamed Overlay Plot Snapshot >> button').nth(3).click();
|
||||||
|
|
||||||
// uncheck autoscale
|
// uncheck autoscale
|
||||||
await page.locator('text=Y Axis Label Log mode Auto scale Padding >> input[type="checkbox"] >> nth=1').uncheck();
|
await page.getByRole('listitem').filter({ hasText: 'Auto scale' }).getByRole('checkbox').uncheck();
|
||||||
|
|
||||||
// save
|
// save
|
||||||
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
||||||
|
|||||||
@@ -205,7 +205,8 @@ async function enableEditMode(page) {
|
|||||||
*/
|
*/
|
||||||
async function enableLogMode(page) {
|
async function enableLogMode(page) {
|
||||||
// turn on log mode
|
// turn on log mode
|
||||||
await page.locator('text=Y Axis Label Log mode Auto scale Padding >> input[type="checkbox"]').first().check();
|
await page.getByRole('listitem').filter({ hasText: 'Log mode' }).getByRole('checkbox').check();
|
||||||
|
// await page.locator('text=Y Axis Label Log mode Auto scale Padding >> input[type="checkbox"]').first().check();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -213,7 +214,7 @@ async function enableLogMode(page) {
|
|||||||
*/
|
*/
|
||||||
async function disableLogMode(page) {
|
async function disableLogMode(page) {
|
||||||
// turn off log mode
|
// turn off log mode
|
||||||
await page.locator('text=Y Axis Label Log mode Auto scale Padding >> input[type="checkbox"]').first().uncheck();
|
await page.getByRole('listitem').filter({ hasText: 'Log mode' }).getByRole('checkbox').uncheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
116
e2e/tests/functional/plugins/plot/overlayPlot.e2e.spec.js
Normal file
116
e2e/tests/functional/plugins/plot/overlayPlot.e2e.spec.js
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Tests to verify log plot functionality. Note this test suite if very much under active development and should not
|
||||||
|
necessarily be used for reference when writing new tests in this area.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { test, expect } = require('../../../../pluginFixtures');
|
||||||
|
const { createDomainObjectWithDefaults } = require('../../../../appActions');
|
||||||
|
|
||||||
|
test.describe('Overlay Plot', () => {
|
||||||
|
test('Plot legend color is in sync with plot series color', async ({ page }) => {
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: "Overlay Plot"
|
||||||
|
});
|
||||||
|
|
||||||
|
await createDomainObjectWithDefaults(page, {
|
||||||
|
type: "Sine Wave Generator",
|
||||||
|
parent: overlayPlot.uuid
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto(overlayPlot.url);
|
||||||
|
|
||||||
|
// navigate to plot series color palette
|
||||||
|
await page.click('.l-browse-bar__actions__edit');
|
||||||
|
await page.locator('li.c-tree__item.menus-to-left .c-disclosure-triangle').click();
|
||||||
|
await page.locator('.c-click-swatch--menu').click();
|
||||||
|
await page.locator('.c-palette__item[style="background: rgb(255, 166, 61);"]').click();
|
||||||
|
|
||||||
|
// gets color for swatch located in legend
|
||||||
|
const element = await page.waitForSelector('.plot-series-color-swatch');
|
||||||
|
const color = await element.evaluate((el) => {
|
||||||
|
return window.getComputedStyle(el).getPropertyValue('background-color');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(color).toBe('rgb(255, 166, 61)');
|
||||||
|
});
|
||||||
|
test('The elements pool supports dragging series into multiple y-axis buckets', async ({ page }) => {
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: "Overlay Plot"
|
||||||
|
});
|
||||||
|
|
||||||
|
await createDomainObjectWithDefaults(page, {
|
||||||
|
type: "Sine Wave Generator",
|
||||||
|
name: 'swg a',
|
||||||
|
parent: overlayPlot.uuid
|
||||||
|
});
|
||||||
|
await createDomainObjectWithDefaults(page, {
|
||||||
|
type: "Sine Wave Generator",
|
||||||
|
name: 'swg b',
|
||||||
|
parent: overlayPlot.uuid
|
||||||
|
});
|
||||||
|
await createDomainObjectWithDefaults(page, {
|
||||||
|
type: "Sine Wave Generator",
|
||||||
|
name: 'swg c',
|
||||||
|
parent: overlayPlot.uuid
|
||||||
|
});
|
||||||
|
await createDomainObjectWithDefaults(page, {
|
||||||
|
type: "Sine Wave Generator",
|
||||||
|
name: 'swg d',
|
||||||
|
parent: overlayPlot.uuid
|
||||||
|
});
|
||||||
|
await createDomainObjectWithDefaults(page, {
|
||||||
|
type: "Sine Wave Generator",
|
||||||
|
name: 'swg e',
|
||||||
|
parent: overlayPlot.uuid
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto(overlayPlot.url);
|
||||||
|
await page.click('button[title="Edit"]');
|
||||||
|
|
||||||
|
// Expand the elements pool vertically
|
||||||
|
await page.locator('.l-pane__handle').nth(2).hover({ trial: true });
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(0, 100);
|
||||||
|
await page.mouse.up();
|
||||||
|
|
||||||
|
await page.locator('#inspector-elements-tree >> text=swg a').dragTo(page.locator('[aria-label="Element Item Group"]').nth(1));
|
||||||
|
await page.locator('#inspector-elements-tree >> text=swg c').dragTo(page.locator('[aria-label="Element Item Group"]').nth(1));
|
||||||
|
await page.locator('#inspector-elements-tree >> text=swg e').dragTo(page.locator('[aria-label="Element Item Group"]').nth(1));
|
||||||
|
await page.locator('#inspector-elements-tree >> text=swg b').dragTo(page.locator('[aria-label="Element Item Group"]').nth(2));
|
||||||
|
const elementsTree = await page.locator('#inspector-elements-tree').allInnerTexts();
|
||||||
|
expect(elementsTree.join('').split('\n')).toEqual([
|
||||||
|
"Y Axis 1",
|
||||||
|
"swg d",
|
||||||
|
"Y Axis 2",
|
||||||
|
"swg e",
|
||||||
|
"swg c",
|
||||||
|
"swg a",
|
||||||
|
"Y Axis 3",
|
||||||
|
"swg b"
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -34,23 +34,26 @@
|
|||||||
@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
|
<div
|
||||||
v-if="seriesModels.length > 0"
|
v-if="seriesModels.length"
|
||||||
:tick-width="tickWidth"
|
class="u-contents"
|
||||||
:single-series="seriesModels.length === 1"
|
>
|
||||||
:has-same-range-value="hasSameRangeValue"
|
<y-axis
|
||||||
:series-model="seriesModels[0]"
|
v-for="(yAxis, index) in yAxesIds"
|
||||||
:style="{
|
:id="yAxis.id"
|
||||||
left: (plotWidth - tickWidth) + 'px'
|
:key="`yAxis-${index}`"
|
||||||
}"
|
:multiple-left-axes="multipleLeftAxes"
|
||||||
@yKeyChanged="setYAxisKey"
|
:position="yAxis.id > 2 ? 'right' : 'left'"
|
||||||
@tickWidthChanged="onTickWidthChange"
|
:class="{'plot-yaxis-right': yAxis.id > 2}"
|
||||||
/>
|
:tick-width="yAxis.tickWidth"
|
||||||
|
:plot-left-tick-width="yAxis.id > 2 ? yAxis.tickWidth: plotLeftTickWidth"
|
||||||
|
@yKeyChanged="setYAxisKey"
|
||||||
|
@tickWidthChanged="onTickWidthChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="gl-plot-wrapper-display-area-and-x-axis"
|
class="gl-plot-wrapper-display-area-and-x-axis"
|
||||||
:style="{
|
:style="xAxisStyle"
|
||||||
left: (plotWidth + 20) + 'px'
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
|
|
||||||
<div class="gl-plot-display-area has-local-controls has-cursor-guides">
|
<div class="gl-plot-display-area has-local-controls has-cursor-guides">
|
||||||
@@ -69,9 +72,12 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<mct-ticks
|
<mct-ticks
|
||||||
|
v-for="(yAxis, index) in yAxesIds"
|
||||||
v-show="gridLines"
|
v-show="gridLines"
|
||||||
|
:key="`yAxis-gridlines-${index}`"
|
||||||
:axis-type="'yAxis'"
|
:axis-type="'yAxis'"
|
||||||
:position="'bottom'"
|
:position="'bottom'"
|
||||||
|
:axis-id="yAxis.id"
|
||||||
@plotTickWidth="onTickWidthChange"
|
@plotTickWidth="onTickWidthChange"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -214,6 +220,7 @@ import YAxis from "./axis/YAxis.vue";
|
|||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
const OFFSET_THRESHOLD = 10;
|
const OFFSET_THRESHOLD = 10;
|
||||||
|
const AXES_PADDING = 20;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -269,7 +276,6 @@ export default {
|
|||||||
altPressed: false,
|
altPressed: false,
|
||||||
highlights: [],
|
highlights: [],
|
||||||
lockHighlightPoint: false,
|
lockHighlightPoint: false,
|
||||||
tickWidth: 0,
|
|
||||||
yKeyOptions: [],
|
yKeyOptions: [],
|
||||||
yAxisLabel: '',
|
yAxisLabel: '',
|
||||||
rectangles: [],
|
rectangles: [],
|
||||||
@@ -284,12 +290,31 @@ export default {
|
|||||||
isTimeOutOfSync: false,
|
isTimeOutOfSync: false,
|
||||||
showLimitLineLabels: this.limitLineLabels,
|
showLimitLineLabels: this.limitLineLabels,
|
||||||
isFrozenOnMouseDown: false,
|
isFrozenOnMouseDown: false,
|
||||||
hasSameRangeValue: true,
|
|
||||||
cursorGuide: this.initCursorGuide,
|
cursorGuide: this.initCursorGuide,
|
||||||
gridLines: this.initGridLines
|
gridLines: this.initGridLines,
|
||||||
|
yAxes: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
xAxisStyle() {
|
||||||
|
const rightAxis = this.yAxesIds.find(yAxis => yAxis.id > 2);
|
||||||
|
const leftOffset = this.multipleLeftAxes ? 2 * AXES_PADDING : AXES_PADDING;
|
||||||
|
let style = {
|
||||||
|
left: `${this.plotLeftTickWidth + leftOffset}px`
|
||||||
|
};
|
||||||
|
|
||||||
|
if (rightAxis) {
|
||||||
|
style.right = `${rightAxis.tickWidth + AXES_PADDING}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return style;
|
||||||
|
},
|
||||||
|
yAxesIds() {
|
||||||
|
return this.yAxes.filter(yAxis => yAxis.seriesCount > 0);
|
||||||
|
},
|
||||||
|
multipleLeftAxes() {
|
||||||
|
return this.yAxes.filter(yAxis => yAxis.seriesCount > 0 && yAxis.id <= 2).length > 1;
|
||||||
|
},
|
||||||
isNestedWithinAStackedPlot() {
|
isNestedWithinAStackedPlot() {
|
||||||
const isNavigatedObject = this.openmct.router.isNavigatedObject([this.domainObject].concat(this.path));
|
const isNavigatedObject = this.openmct.router.isNavigatedObject([this.domainObject].concat(this.path));
|
||||||
|
|
||||||
@@ -312,8 +337,17 @@ export default {
|
|||||||
return 'plot-legend-collapsed';
|
return 'plot-legend-collapsed';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plotWidth() {
|
plotLeftTickWidth() {
|
||||||
return this.plotTickWidth || this.tickWidth;
|
let leftTickWidth = 0;
|
||||||
|
this.yAxes.forEach((yAxis) => {
|
||||||
|
if (yAxis.id > 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
leftTickWidth = leftTickWidth + yAxis.tickWidth;
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.plotTickWidth || leftTickWidth;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -342,6 +376,20 @@ export default {
|
|||||||
|
|
||||||
this.config = this.getConfig();
|
this.config = this.getConfig();
|
||||||
this.legend = this.config.legend;
|
this.legend = this.config.legend;
|
||||||
|
this.yAxes = [{
|
||||||
|
id: this.config.yAxis.id,
|
||||||
|
seriesCount: 0,
|
||||||
|
tickWidth: 0
|
||||||
|
}];
|
||||||
|
if (this.config.additionalYAxes) {
|
||||||
|
this.yAxes = this.yAxes.concat(this.config.additionalYAxes.map(yAxis => {
|
||||||
|
return {
|
||||||
|
id: yAxis.id,
|
||||||
|
seriesCount: 0,
|
||||||
|
tickWidth: 0
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isNestedWithinAStackedPlot) {
|
if (this.isNestedWithinAStackedPlot) {
|
||||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
@@ -383,8 +431,10 @@ export default {
|
|||||||
},
|
},
|
||||||
setTimeContext() {
|
setTimeContext() {
|
||||||
this.stopFollowingTimeContext();
|
this.stopFollowingTimeContext();
|
||||||
|
|
||||||
this.timeContext = this.openmct.time.getContextForView(this.path);
|
this.timeContext = this.openmct.time.getContextForView(this.path);
|
||||||
this.followTimeContext();
|
this.followTimeContext();
|
||||||
|
|
||||||
},
|
},
|
||||||
followTimeContext() {
|
followTimeContext() {
|
||||||
this.updateDisplayBounds(this.timeContext.bounds());
|
this.updateDisplayBounds(this.timeContext.bounds());
|
||||||
@@ -417,12 +467,13 @@ export default {
|
|||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
addSeries(series, index) {
|
addSeries(series, index) {
|
||||||
|
const yAxisId = series.get('yAxisId');
|
||||||
|
this.updateAxisUsageCount(yAxisId, 1);
|
||||||
this.$set(this.seriesModels, index, series);
|
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);
|
||||||
this.listenTo(series, 'change:yKey', () => {
|
this.listenTo(series, 'change:yKey', () => {
|
||||||
this.checkSameRangeValue();
|
|
||||||
this.loadSeriesData(series);
|
this.loadSeriesData(series);
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
@@ -430,20 +481,21 @@ export default {
|
|||||||
this.loadSeriesData(series);
|
this.loadSeriesData(series);
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
this.checkSameRangeValue();
|
|
||||||
this.loadSeriesData(series);
|
this.loadSeriesData(series);
|
||||||
},
|
},
|
||||||
|
|
||||||
checkSameRangeValue() {
|
removeSeries(plotSeries, index) {
|
||||||
this.hasSameRangeValue = this.seriesModels.every((model) => {
|
const yAxisId = plotSeries.get('yAxisId');
|
||||||
return model.get('yKey') === this.seriesModels[0].get('yKey');
|
this.updateAxisUsageCount(yAxisId, -1);
|
||||||
});
|
this.seriesModels.splice(index, 1);
|
||||||
|
this.stopListening(plotSeries);
|
||||||
},
|
},
|
||||||
|
|
||||||
removeSeries(plotSeries, index) {
|
updateAxisUsageCount(yAxisId, updateCountBy) {
|
||||||
this.seriesModels.splice(index, 1);
|
const foundYAxis = this.yAxes.find(yAxis => yAxis.id === yAxisId);
|
||||||
this.checkSameRangeValue();
|
if (foundYAxis) {
|
||||||
this.stopListening(plotSeries);
|
foundYAxis.seriesCount = foundYAxis.seriesCount + updateCountBy;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
loadSeriesData(series) {
|
loadSeriesData(series) {
|
||||||
@@ -673,6 +725,7 @@ export default {
|
|||||||
|
|
||||||
// Setup canvas etc.
|
// Setup canvas etc.
|
||||||
this.xScale = new LinearScale(this.config.xAxis.get('displayRange'));
|
this.xScale = new LinearScale(this.config.xAxis.get('displayRange'));
|
||||||
|
//TODO: handle yScale, zoom/pan for all yAxes
|
||||||
this.yScale = new LinearScale(this.config.yAxis.get('displayRange'));
|
this.yScale = new LinearScale(this.config.yAxis.get('displayRange'));
|
||||||
|
|
||||||
this.pan = undefined;
|
this.pan = undefined;
|
||||||
@@ -690,6 +743,9 @@ export default {
|
|||||||
|
|
||||||
this.listenTo(this.config.xAxis, 'change:displayRange', this.onXAxisChange, this);
|
this.listenTo(this.config.xAxis, 'change:displayRange', this.onXAxisChange, this);
|
||||||
this.listenTo(this.config.yAxis, 'change:displayRange', this.onYAxisChange, this);
|
this.listenTo(this.config.yAxis, 'change:displayRange', this.onYAxisChange, this);
|
||||||
|
this.config.additionalYAxes.forEach(yAxis => {
|
||||||
|
this.listenTo(yAxis, 'change:displayRange', this.onYAxisChange, this);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onXAxisChange(displayBounds) {
|
onXAxisChange(displayBounds) {
|
||||||
@@ -704,20 +760,24 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onTickWidthChange(width, fromDifferentObject) {
|
onTickWidthChange(data, fromDifferentObject) {
|
||||||
if (fromDifferentObject) {
|
const {width, yAxisId} = data;
|
||||||
|
if (yAxisId) {
|
||||||
|
const index = this.yAxes.findIndex(yAxis => yAxis.id === yAxisId);
|
||||||
|
if (fromDifferentObject) {
|
||||||
// Always accept tick width if it comes from a different object.
|
// Always accept tick width if it comes from a different object.
|
||||||
this.tickWidth = width;
|
this.yAxes[index].tickWidth = width;
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, only accept tick with if it's larger.
|
// Otherwise, only accept tick with if it's larger.
|
||||||
const newWidth = Math.max(width, this.tickWidth);
|
const newWidth = Math.max(width, this.yAxes[index].tickWidth);
|
||||||
if (newWidth !== this.tickWidth) {
|
if (newWidth !== this.yAxes[index].tickWidth) {
|
||||||
this.tickWidth = newWidth;
|
this.yAxes[index].tickWidth = newWidth;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const id = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
const id = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
this.$emit('plotTickWidth', this.tickWidth, id);
|
this.$emit('plotTickWidth', this.yAxes[index].tickWidth, id);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
trackMousePosition(event) {
|
trackMousePosition(event) {
|
||||||
@@ -1108,8 +1168,9 @@ export default {
|
|||||||
this.userViewportChangeEnd();
|
this.userViewportChangeEnd();
|
||||||
},
|
},
|
||||||
|
|
||||||
setYAxisKey(yKey) {
|
setYAxisKey(yKey, yAxisId) {
|
||||||
this.config.series.models[0].set('yKey', yKey);
|
const seriesForYAxis = this.config.series.models.filter((model => model.get('yAxisId') === yAxisId));
|
||||||
|
seriesForYAxis.forEach(model => model.set('yKey', yKey));
|
||||||
},
|
},
|
||||||
|
|
||||||
pause() {
|
pause() {
|
||||||
|
|||||||
@@ -103,6 +103,12 @@ export default {
|
|||||||
return 6;
|
return 6;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
axisId: {
|
||||||
|
type: Number,
|
||||||
|
default() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
position: {
|
position: {
|
||||||
required: true,
|
required: true,
|
||||||
type: String,
|
type: String,
|
||||||
@@ -145,7 +151,15 @@ export default {
|
|||||||
throw new Error('config is missing');
|
throw new Error('config is missing');
|
||||||
}
|
}
|
||||||
|
|
||||||
return config[this.axisType];
|
if (this.axisType === 'yAxis') {
|
||||||
|
if (this.axisId && this.axisId !== config.yAxis.id) {
|
||||||
|
return config.additionalYAxes.find(axis => axis.id === this.axisId);
|
||||||
|
} else {
|
||||||
|
return config.yAxis;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return config[this.axisType];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Determine whether ticks should be regenerated for a given range.
|
* Determine whether ticks should be regenerated for a given range.
|
||||||
@@ -258,7 +272,10 @@ export default {
|
|||||||
}, 0));
|
}, 0));
|
||||||
|
|
||||||
this.tickWidth = tickWidth;
|
this.tickWidth = tickWidth;
|
||||||
this.$emit('plotTickWidth', tickWidth);
|
this.$emit('plotTickWidth', {
|
||||||
|
width: tickWidth,
|
||||||
|
yAxisId: this.axisType === 'yAxis' ? this.axisId : ''
|
||||||
|
});
|
||||||
this.shouldCheckWidth = false;
|
this.shouldCheckWidth = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,10 +22,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="loaded"
|
v-if="loaded"
|
||||||
class="gl-plot-axis-area gl-plot-y has-local-controls"
|
class="gl-plot-axis-area gl-plot-y has-local-controls js-plot-y-axis"
|
||||||
:style="{
|
:style="yAxisStyle"
|
||||||
width: (tickWidth + 20) + 'px'
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -52,6 +50,7 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<mct-ticks
|
<mct-ticks
|
||||||
|
:axis-id="id"
|
||||||
:axis-type="'yAxis'"
|
:axis-type="'yAxis'"
|
||||||
class="gl-plot-ticks"
|
class="gl-plot-ticks"
|
||||||
:position="'top'"
|
:position="'top'"
|
||||||
@@ -63,6 +62,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import MctTicks from "../MctTicks.vue";
|
import MctTicks from "../MctTicks.vue";
|
||||||
import configStore from "../configuration/ConfigStore";
|
import configStore from "../configuration/ConfigStore";
|
||||||
|
import eventHelpers from "../lib/eventHelpers";
|
||||||
|
|
||||||
|
const AXIS_PADDING = 20;
|
||||||
|
const AXIS_OFFSET = 5;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -70,22 +73,10 @@ export default {
|
|||||||
},
|
},
|
||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'domainObject'],
|
||||||
props: {
|
props: {
|
||||||
singleSeries: {
|
id: {
|
||||||
type: Boolean,
|
type: Number,
|
||||||
default() {
|
default() {
|
||||||
return true;
|
return 1;
|
||||||
}
|
|
||||||
},
|
|
||||||
hasSameRangeValue: {
|
|
||||||
type: Boolean,
|
|
||||||
default() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
seriesModel: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tickWidth: {
|
tickWidth: {
|
||||||
@@ -93,37 +84,132 @@ export default {
|
|||||||
default() {
|
default() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
plotLeftTickWidth: {
|
||||||
|
type: Number,
|
||||||
|
default() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
multipleLeftAxes: {
|
||||||
|
type: Boolean,
|
||||||
|
default() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
position: {
|
||||||
|
type: String,
|
||||||
|
default() {
|
||||||
|
return 'left';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
this.seriesModels = [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
yAxisLabel: 'none',
|
yAxisLabel: 'none',
|
||||||
loaded: false
|
loaded: false,
|
||||||
|
yKeyOptions: [],
|
||||||
|
hasSameRangeValue: true,
|
||||||
|
singleSeries: true,
|
||||||
|
mainYAxisId: null,
|
||||||
|
hasAdditionalYAxes: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
canShowYAxisLabel() {
|
canShowYAxisLabel() {
|
||||||
return this.singleSeries === true || this.hasSameRangeValue === true;
|
return this.singleSeries === true || this.hasSameRangeValue === true;
|
||||||
|
},
|
||||||
|
yAxisStyle() {
|
||||||
|
let style = {
|
||||||
|
width: `${this.tickWidth + AXIS_PADDING}px`
|
||||||
|
};
|
||||||
|
const multipleAxesPadding = this.multipleLeftAxes ? AXIS_PADDING : 0;
|
||||||
|
|
||||||
|
if (this.position === 'right') {
|
||||||
|
style.left = `-${this.tickWidth + AXIS_PADDING}px`;
|
||||||
|
} else {
|
||||||
|
const thisIsTheSecondLeftAxis = (this.id - 1) > 0;
|
||||||
|
if (this.multipleLeftAxes && thisIsTheSecondLeftAxis) {
|
||||||
|
style.left = `${ this.plotLeftTickWidth - this.tickWidth - multipleAxesPadding - AXIS_OFFSET }px`;
|
||||||
|
style['border-right'] = `1px solid`;
|
||||||
|
} else {
|
||||||
|
style.left = `${this.plotLeftTickWidth - this.tickWidth + multipleAxesPadding}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return style;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.yAxis = this.getYAxisFromConfig();
|
eventHelpers.extend(this);
|
||||||
|
this.initAxisAndSeriesConfig();
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
this.setUpYAxisOptions();
|
this.setUpYAxisOptions();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getYAxisFromConfig() {
|
initAxisAndSeriesConfig() {
|
||||||
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
let config = configStore.get(configId);
|
let config = configStore.get(configId);
|
||||||
if (config) {
|
if (config) {
|
||||||
return config.yAxis;
|
this.mainYAxisId = config.yAxis.id;
|
||||||
|
this.hasAdditionalYAxes = config?.additionalYAxes.length;
|
||||||
|
if (this.id && this.id !== this.mainYAxisId) {
|
||||||
|
this.yAxis = config.additionalYAxes.find(yAxis => yAxis.id === this.id);
|
||||||
|
} else {
|
||||||
|
this.yAxis = config.yAxis;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.config = config;
|
||||||
|
this.listenTo(this.config.series, 'add', this.addSeries, this);
|
||||||
|
this.listenTo(this.config.series, 'remove', this.removeSeries, this);
|
||||||
|
this.listenTo(this.config.series, 'reorder', this.addOrRemoveSeries, this);
|
||||||
|
|
||||||
|
this.config.series.models.forEach(this.addSeries, this);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
addOrRemoveSeries(series) {
|
||||||
|
const yAxisId = this.series.get('yAxisId');
|
||||||
|
if (yAxisId === this.id) {
|
||||||
|
this.addSeries(series);
|
||||||
|
} else {
|
||||||
|
this.removeSeries(series);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addSeries(series, index) {
|
||||||
|
const yAxisId = series.get('yAxisId');
|
||||||
|
const seriesIndex = this.seriesModels.findIndex(model => this.openmct.objects.areIdsEqual(model.get('identifier'), series.get('identifier')));
|
||||||
|
|
||||||
|
if (yAxisId === this.id && seriesIndex < 0) {
|
||||||
|
this.seriesModels.push(series);
|
||||||
|
this.checkRangeValueAndSingleSeries();
|
||||||
|
this.setUpYAxisOptions();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeSeries(plotSeries) {
|
||||||
|
const seriesIndex = this.seriesModels.findIndex(model => this.openmct.objects.areIdsEqual(model.get('identifier'), plotSeries.get('identifier')));
|
||||||
|
if (seriesIndex > -1) {
|
||||||
|
this.seriesModels.splice(seriesIndex, 1);
|
||||||
|
this.checkRangeValueAndSingleSeries();
|
||||||
|
this.setUpYAxisOptions();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checkRangeValueAndSingleSeries() {
|
||||||
|
this.hasSameRangeValue = this.seriesModels.every((model) => {
|
||||||
|
return model.get('yKey') === this.seriesModels[0].get('yKey');
|
||||||
|
});
|
||||||
|
this.singleSeries = this.seriesModels.length === 1;
|
||||||
|
},
|
||||||
setUpYAxisOptions() {
|
setUpYAxisOptions() {
|
||||||
this.yKeyOptions = [];
|
this.yKeyOptions = [];
|
||||||
|
if (!this.seriesModels.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.seriesModel.metadata) {
|
const seriesModel = this.seriesModels[0];
|
||||||
this.yKeyOptions = this.seriesModel.metadata
|
if (seriesModel.metadata) {
|
||||||
|
this.yKeyOptions = seriesModel.metadata
|
||||||
.valuesForHints(['range'])
|
.valuesForHints(['range'])
|
||||||
.map(function (o) {
|
.map(function (o) {
|
||||||
return {
|
return {
|
||||||
@@ -135,22 +221,22 @@ export default {
|
|||||||
|
|
||||||
// set yAxisLabel if none is set yet
|
// set yAxisLabel if none is set yet
|
||||||
if (this.yAxisLabel === 'none') {
|
if (this.yAxisLabel === 'none') {
|
||||||
let yKey = this.seriesModel.model.yKey;
|
this.yAxisLabel = this.yAxis.get('label');
|
||||||
let yKeyModel = this.yKeyOptions.filter(o => o.key === yKey)[0];
|
|
||||||
|
|
||||||
this.yAxisLabel = yKeyModel ? yKeyModel.name : '';
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggleYAxisLabel() {
|
toggleYAxisLabel() {
|
||||||
let yAxisObject = this.yKeyOptions.filter(o => o.name === this.yAxisLabel)[0];
|
let yAxisObject = this.yKeyOptions.filter(o => o.name === this.yAxisLabel)[0];
|
||||||
|
|
||||||
if (yAxisObject) {
|
if (yAxisObject) {
|
||||||
this.$emit('yKeyChanged', yAxisObject.key);
|
this.$emit('yKeyChanged', yAxisObject.key, this.id);
|
||||||
this.yAxis.set('label', this.yAxisLabel);
|
this.yAxis.set('label', this.yAxisLabel);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onTickWidthChange(width) {
|
onTickWidthChange(data) {
|
||||||
this.$emit('tickWidthChanged', width);
|
this.$emit('tickWidthChanged', {
|
||||||
|
width: data.width,
|
||||||
|
yAxisId: this.id
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -98,7 +98,21 @@ export default {
|
|||||||
this.limitLines = [];
|
this.limitLines = [];
|
||||||
this.pointSets = [];
|
this.pointSets = [];
|
||||||
this.alarmSets = [];
|
this.alarmSets = [];
|
||||||
this.offset = {};
|
const yAxisId = this.config.yAxis.get('id');
|
||||||
|
this.offset = {
|
||||||
|
[yAxisId]: {}
|
||||||
|
};
|
||||||
|
this.listenTo(this.config.yAxis, 'change:key', this.resetOffsetAndSeriesDataForYAxis.bind(this, yAxisId), this);
|
||||||
|
this.listenTo(this.config.yAxis, 'change', this.updateLimitsAndDraw);
|
||||||
|
if (this.config.additionalYAxes.length) {
|
||||||
|
this.config.additionalYAxes.forEach(yAxis => {
|
||||||
|
const id = yAxis.get('id');
|
||||||
|
this.offset[id] = {};
|
||||||
|
this.listenTo(yAxis, 'change', this.updateLimitsAndDraw);
|
||||||
|
this.listenTo(this.config.yAxis, 'change:key', this.resetOffsetAndSeriesDataForYAxis.bind(this, id), this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.seriesElements = new WeakMap();
|
this.seriesElements = new WeakMap();
|
||||||
this.seriesLimits = new WeakMap();
|
this.seriesLimits = new WeakMap();
|
||||||
|
|
||||||
@@ -111,8 +125,7 @@ export default {
|
|||||||
|
|
||||||
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
|
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
|
||||||
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this);
|
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this);
|
||||||
this.listenTo(this.config.yAxis, 'change:key', this.clearOffset, this);
|
|
||||||
this.listenTo(this.config.yAxis, 'change', this.updateLimitsAndDraw);
|
|
||||||
this.listenTo(this.config.xAxis, 'change', this.updateLimitsAndDraw);
|
this.listenTo(this.config.xAxis, 'change', this.updateLimitsAndDraw);
|
||||||
this.config.series.forEach(this.onSeriesAdd, this);
|
this.config.series.forEach(this.onSeriesAdd, this);
|
||||||
this.$emit('chartLoaded');
|
this.$emit('chartLoaded');
|
||||||
@@ -224,25 +237,31 @@ export default {
|
|||||||
this.limitLines.forEach(line => line.destroy());
|
this.limitLines.forEach(line => line.destroy());
|
||||||
DrawLoader.releaseDrawAPI(this.drawAPI);
|
DrawLoader.releaseDrawAPI(this.drawAPI);
|
||||||
},
|
},
|
||||||
clearOffset() {
|
resetOffsetAndSeriesDataForYAxis(yAxisId) {
|
||||||
delete this.offset.x;
|
delete this.offset[yAxisId].x;
|
||||||
delete this.offset.y;
|
delete this.offset[yAxisId].y;
|
||||||
delete this.offset.xVal;
|
delete this.offset[yAxisId].xVal;
|
||||||
delete this.offset.yVal;
|
delete this.offset[yAxisId].yVal;
|
||||||
delete this.offset.xKey;
|
delete this.offset[yAxisId].xKey;
|
||||||
delete this.offset.yKey;
|
delete this.offset[yAxisId].yKey;
|
||||||
this.lines.forEach(function (line) {
|
|
||||||
|
const lines = this.lines.filter(this.matchByYAxisId.bind(this, yAxisId));
|
||||||
|
lines.forEach(function (line) {
|
||||||
line.reset();
|
line.reset();
|
||||||
});
|
});
|
||||||
this.limitLines.forEach(function (line) {
|
const limitLines = this.limitLines.filter(this.matchByYAxisId.bind(this, yAxisId));
|
||||||
|
limitLines.forEach(function (line) {
|
||||||
line.reset();
|
line.reset();
|
||||||
});
|
});
|
||||||
this.pointSets.forEach(function (pointSet) {
|
const pointSets = this.pointSets.filter(this.matchByYAxisId.bind(this, yAxisId));
|
||||||
|
pointSets.forEach(function (pointSet) {
|
||||||
pointSet.reset();
|
pointSet.reset();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setOffset(offsetPoint, index, series) {
|
setOffset(offsetPoint, index, series) {
|
||||||
if (this.offset.x && this.offset.y) {
|
const mainYAxisId = this.config.yAxis.get('id');
|
||||||
|
const yAxisId = series.get('yAxisId') || mainYAxisId;
|
||||||
|
if (this.offset[yAxisId].x && this.offset[yAxisId].y) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,19 +270,20 @@ export default {
|
|||||||
y: series.getYVal(offsetPoint)
|
y: series.getYVal(offsetPoint)
|
||||||
};
|
};
|
||||||
|
|
||||||
this.offset.x = function (x) {
|
this.offset[yAxisId].x = function (x) {
|
||||||
return x - offsets.x;
|
return x - offsets.x;
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
this.offset.y = function (y) {
|
this.offset[yAxisId].y = function (y) {
|
||||||
return y - offsets.y;
|
return y - offsets.y;
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
this.offset.xVal = function (point, pSeries) {
|
this.offset[yAxisId].xVal = function (point, pSeries) {
|
||||||
return this.offset.x(pSeries.getXVal(point));
|
return this.offset[yAxisId].x(pSeries.getXVal(point));
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
this.offset.yVal = function (point, pSeries) {
|
this.offset[yAxisId].yVal = function (point, pSeries) {
|
||||||
return this.offset.y(pSeries.getYVal(point));
|
return this.offset[yAxisId].y(pSeries.getYVal(point));
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
},
|
},
|
||||||
|
|
||||||
initializeCanvas(canvas, overlay) {
|
initializeCanvas(canvas, overlay) {
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
this.overlay = overlay;
|
this.overlay = overlay;
|
||||||
@@ -311,11 +331,15 @@ export default {
|
|||||||
this.clearLimitLines(series);
|
this.clearLimitLines(series);
|
||||||
},
|
},
|
||||||
lineForSeries(series) {
|
lineForSeries(series) {
|
||||||
|
const mainYAxisId = this.config.yAxis.get('id');
|
||||||
|
const yAxisId = series.get('yAxisId') || mainYAxisId;
|
||||||
|
let offset = this.offset[yAxisId];
|
||||||
|
|
||||||
if (series.get('interpolate') === 'linear') {
|
if (series.get('interpolate') === 'linear') {
|
||||||
return new MCTChartLineLinear(
|
return new MCTChartLineLinear(
|
||||||
series,
|
series,
|
||||||
this,
|
this,
|
||||||
this.offset
|
offset
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,33 +347,45 @@ export default {
|
|||||||
return new MCTChartLineStepAfter(
|
return new MCTChartLineStepAfter(
|
||||||
series,
|
series,
|
||||||
this,
|
this,
|
||||||
this.offset
|
offset
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
limitLineForSeries(series) {
|
limitLineForSeries(series) {
|
||||||
|
const mainYAxisId = this.config.yAxis.get('id');
|
||||||
|
const yAxisId = series.get('yAxisId') || mainYAxisId;
|
||||||
|
let offset = this.offset[yAxisId];
|
||||||
|
|
||||||
return new MCTChartAlarmLineSet(
|
return new MCTChartAlarmLineSet(
|
||||||
series,
|
series,
|
||||||
this,
|
this,
|
||||||
this.offset,
|
offset,
|
||||||
this.openmct.time.bounds()
|
this.openmct.time.bounds()
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
pointSetForSeries(series) {
|
pointSetForSeries(series) {
|
||||||
|
const mainYAxisId = this.config.yAxis.get('id');
|
||||||
|
const yAxisId = series.get('yAxisId') || mainYAxisId;
|
||||||
|
let offset = this.offset[yAxisId];
|
||||||
|
|
||||||
if (series.get('markers')) {
|
if (series.get('markers')) {
|
||||||
return new MCTChartPointSet(
|
return new MCTChartPointSet(
|
||||||
series,
|
series,
|
||||||
this,
|
this,
|
||||||
this.offset
|
offset
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
alarmPointSetForSeries(series) {
|
alarmPointSetForSeries(series) {
|
||||||
|
const mainYAxisId = this.config.yAxis.get('id');
|
||||||
|
const yAxisId = series.get('yAxisId') || mainYAxisId;
|
||||||
|
let offset = this.offset[yAxisId];
|
||||||
|
|
||||||
if (series.get('alarmMarkers')) {
|
if (series.get('alarmMarkers')) {
|
||||||
return new MCTChartAlarmPointSet(
|
return new MCTChartAlarmPointSet(
|
||||||
series,
|
series,
|
||||||
this,
|
this,
|
||||||
this.offset
|
offset
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -410,8 +446,8 @@ export default {
|
|||||||
this.seriesLimits.delete(series);
|
this.seriesLimits.delete(series);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
canDraw() {
|
canDraw(yAxisId) {
|
||||||
if (!this.offset.x || !this.offset.y) {
|
if (!this.offset[yAxisId] || !this.offset[yAxisId].x || !this.offset[yAxisId].y) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,16 +470,31 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.drawAPI.clear();
|
this.drawAPI.clear();
|
||||||
if (this.canDraw()) {
|
const mainYAxisId = this.config.yAxis.get('id');
|
||||||
this.updateViewport();
|
//There has to be at least one yAxis
|
||||||
this.drawSeries();
|
const yAxisIds = [mainYAxisId].concat(this.config.additionalYAxes.map(yAxis => yAxis.get('id')));
|
||||||
this.drawRectangles();
|
// Repeat drawing for all yAxes
|
||||||
this.drawHighlights();
|
yAxisIds.forEach((id) => {
|
||||||
}
|
if (this.canDraw(id)) {
|
||||||
|
this.updateViewport(id);
|
||||||
|
this.drawSeries(id);
|
||||||
|
this.drawRectangles(id);
|
||||||
|
this.drawHighlights(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
updateViewport() {
|
updateViewport(yAxisId) {
|
||||||
|
const mainYAxisId = this.config.yAxis.get('id');
|
||||||
const xRange = this.config.xAxis.get('displayRange');
|
const xRange = this.config.xAxis.get('displayRange');
|
||||||
const yRange = this.config.yAxis.get('displayRange');
|
let yRange;
|
||||||
|
if (yAxisId === mainYAxisId) {
|
||||||
|
yRange = this.config.yAxis.get('displayRange');
|
||||||
|
} else {
|
||||||
|
if (this.config.additionalYAxes.length) {
|
||||||
|
const yAxisForId = this.config.additionalYAxes.find(yAxis => yAxis.get('id') === yAxisId);
|
||||||
|
yRange = yAxisForId.get('displayRange');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!xRange || !yRange) {
|
if (!xRange || !yRange) {
|
||||||
return;
|
return;
|
||||||
@@ -454,9 +505,10 @@ export default {
|
|||||||
yRange.max - yRange.min
|
yRange.max - yRange.min
|
||||||
];
|
];
|
||||||
|
|
||||||
const origin = [
|
let origin;
|
||||||
this.offset.x(xRange.min),
|
origin = [
|
||||||
this.offset.y(yRange.min)
|
this.offset[yAxisId].x(xRange.min),
|
||||||
|
this.offset[yAxisId].y(yRange.min)
|
||||||
];
|
];
|
||||||
|
|
||||||
this.drawAPI.setDimensions(
|
this.drawAPI.setDimensions(
|
||||||
@@ -464,38 +516,66 @@ export default {
|
|||||||
origin
|
origin
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
drawSeries() {
|
matchByYAxisId(id, item) {
|
||||||
this.lines.forEach(this.drawLine, this);
|
const mainYAxisId = this.config.yAxis.get('id');
|
||||||
this.pointSets.forEach(this.drawPoints, this);
|
let matchesId = false;
|
||||||
this.alarmSets.forEach(this.drawAlarmPoints, this);
|
|
||||||
|
const series = item.series;
|
||||||
|
if (series) {
|
||||||
|
const seriesYAxisId = series.get('yAxisId') || mainYAxisId;
|
||||||
|
|
||||||
|
matchesId = seriesYAxisId === id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchesId;
|
||||||
|
},
|
||||||
|
drawSeries(id) {
|
||||||
|
const lines = this.lines.filter(this.matchByYAxisId.bind(this, id));
|
||||||
|
lines.forEach(this.drawLine, this);
|
||||||
|
const pointSets = this.pointSets.filter(this.matchByYAxisId.bind(this, id));
|
||||||
|
pointSets.forEach(this.drawPoints, this);
|
||||||
|
const alarmSets = this.alarmSets.filter(this.matchByYAxisId.bind(this, id));
|
||||||
|
alarmSets.forEach(this.drawAlarmPoints, this);
|
||||||
},
|
},
|
||||||
drawLimitLines() {
|
drawLimitLines() {
|
||||||
if (this.canDraw()) {
|
this.config.series.models.forEach(series => {
|
||||||
this.updateViewport();
|
const yAxisId = series.get('yAxisId');
|
||||||
|
this.drawLimitLinesForSeries(yAxisId, series);
|
||||||
if (!this.drawAPI.origin) {
|
});
|
||||||
return;
|
},
|
||||||
}
|
drawLimitLinesForSeries(yAxisId, series) {
|
||||||
|
if (!this.canDraw(yAxisId)) {
|
||||||
Array.from(this.$refs.limitArea.children).forEach((el) => el.remove());
|
return;
|
||||||
let limitPointOverlap = [];
|
|
||||||
this.limitLines.forEach((limitLine) => {
|
|
||||||
let limitContainerEl = this.$refs.limitArea;
|
|
||||||
limitLine.limits.forEach((limit) => {
|
|
||||||
const showLabels = this.showLabels(limit.seriesKey);
|
|
||||||
if (showLabels) {
|
|
||||||
const overlap = this.getLimitOverlap(limit, limitPointOverlap);
|
|
||||||
limitPointOverlap.push(overlap);
|
|
||||||
let limitLabelEl = this.getLimitLabel(limit, overlap);
|
|
||||||
limitContainerEl.appendChild(limitLabelEl);
|
|
||||||
}
|
|
||||||
|
|
||||||
let limitEl = this.getLimitElement(limit);
|
|
||||||
limitContainerEl.appendChild(limitEl);
|
|
||||||
|
|
||||||
}, this);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.updateViewport(yAxisId);
|
||||||
|
|
||||||
|
if (!this.drawAPI.origin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.from(this.$refs.limitArea.children).forEach((el) => el.remove());
|
||||||
|
let limitPointOverlap = [];
|
||||||
|
this.limitLines.forEach((limitLine) => {
|
||||||
|
let limitContainerEl = this.$refs.limitArea;
|
||||||
|
limitLine.limits.forEach((limit) => {
|
||||||
|
if (!series.includes(limit.seriesKey)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const showLabels = this.showLabels(limit.seriesKey);
|
||||||
|
if (showLabels) {
|
||||||
|
const overlap = this.getLimitOverlap(limit, limitPointOverlap);
|
||||||
|
limitPointOverlap.push(overlap);
|
||||||
|
let limitLabelEl = this.getLimitLabel(limit, overlap);
|
||||||
|
limitContainerEl.appendChild(limitLabelEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
let limitEl = this.getLimitElement(limit);
|
||||||
|
limitContainerEl.appendChild(limitEl);
|
||||||
|
|
||||||
|
}, this);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
showLabels(seriesKey) {
|
showLabels(seriesKey) {
|
||||||
return this.showLimitLineLabels.seriesKey
|
return this.showLimitLineLabels.seriesKey
|
||||||
@@ -577,22 +657,25 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
drawLine(chartElement, disconnected) {
|
drawLine(chartElement, disconnected) {
|
||||||
this.drawAPI.drawLine(
|
if (chartElement) {
|
||||||
chartElement.getBuffer(),
|
this.drawAPI.drawLine(
|
||||||
chartElement.color().asRGBAArray(),
|
chartElement.getBuffer(),
|
||||||
chartElement.count,
|
chartElement.color().asRGBAArray(),
|
||||||
disconnected
|
chartElement.count,
|
||||||
);
|
disconnected
|
||||||
},
|
);
|
||||||
drawHighlights() {
|
|
||||||
if (this.highlights && this.highlights.length) {
|
|
||||||
this.highlights.forEach(this.drawHighlight, this);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
drawHighlight(highlight) {
|
drawHighlights(yAxisId) {
|
||||||
|
if (this.highlights && this.highlights.length) {
|
||||||
|
const highlights = this.highlights.filter(this.matchByYAxisId.bind(this, yAxisId));
|
||||||
|
highlights.forEach(this.drawHighlight.bind(this, yAxisId), this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
drawHighlight(yAxisId, highlight) {
|
||||||
const points = new Float32Array([
|
const points = new Float32Array([
|
||||||
this.offset.xVal(highlight.point, highlight.series),
|
this.offset[yAxisId].xVal(highlight.point, highlight.series),
|
||||||
this.offset.yVal(highlight.point, highlight.series)
|
this.offset[yAxisId].yVal(highlight.point, highlight.series)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const color = highlight.series.get('color').asRGBAArray();
|
const color = highlight.series.get('color').asRGBAArray();
|
||||||
@@ -601,20 +684,21 @@ export default {
|
|||||||
|
|
||||||
this.drawAPI.drawPoints(points, color, pointCount, HIGHLIGHT_SIZE, shape);
|
this.drawAPI.drawPoints(points, color, pointCount, HIGHLIGHT_SIZE, shape);
|
||||||
},
|
},
|
||||||
drawRectangles() {
|
drawRectangles(yAxisId) {
|
||||||
if (this.rectangles) {
|
if (this.rectangles) {
|
||||||
this.rectangles.forEach(this.drawRectangle, this);
|
const rectangles = this.rectangles.filter(this.matchByYAxisId.bind(this, yAxisId));
|
||||||
|
rectangles.forEach(this.drawRectangle.bind(this, yAxisId), this);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
drawRectangle(rect) {
|
drawRectangle(yAxisId, rect) {
|
||||||
this.drawAPI.drawSquare(
|
this.drawAPI.drawSquare(
|
||||||
[
|
[
|
||||||
this.offset.x(rect.start.x),
|
this.offset[yAxisId].x(rect.start.x),
|
||||||
this.offset.y(rect.start.y)
|
this.offset[yAxisId].y(rect.start.y)
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
this.offset.x(rect.end.x),
|
this.offset[yAxisId].x(rect.end.x),
|
||||||
this.offset.y(rect.end.y)
|
this.offset[yAxisId].y(rect.end.y)
|
||||||
],
|
],
|
||||||
rect.color
|
rect.color
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ import XAxisModel from "./XAxisModel";
|
|||||||
import YAxisModel from "./YAxisModel";
|
import YAxisModel from "./YAxisModel";
|
||||||
import LegendModel from "./LegendModel";
|
import LegendModel from "./LegendModel";
|
||||||
|
|
||||||
|
const MAX_Y_AXES = 3;
|
||||||
|
const MAIN_Y_AXES_ID = 1;
|
||||||
|
const MAX_ADDITIONAL_AXES = MAX_Y_AXES - 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PlotConfiguration model stores the configuration of a plot and some
|
* PlotConfiguration model stores the configuration of a plot and some
|
||||||
* limited state. The indiidual parts of the plot configuration model
|
* limited state. The indiidual parts of the plot configuration model
|
||||||
@@ -58,8 +62,35 @@ export default class PlotConfigurationModel extends Model {
|
|||||||
this.yAxis = new YAxisModel({
|
this.yAxis = new YAxisModel({
|
||||||
model: options.model.yAxis,
|
model: options.model.yAxis,
|
||||||
plot: this,
|
plot: this,
|
||||||
openmct: options.openmct
|
openmct: options.openmct,
|
||||||
|
id: options.model.yAxis.id || MAIN_Y_AXES_ID
|
||||||
});
|
});
|
||||||
|
//Add any axes in addition to the main yAxis above - we must always have at least 1 y-axis
|
||||||
|
//Addition axes ids will be the MAIN_Y_AXES_ID + x where x is between 1 and MAX_ADDITIONAL_AXES
|
||||||
|
this.additionalYAxes = [];
|
||||||
|
if (Array.isArray(options.model.additionalYAxes)) {
|
||||||
|
const maxLength = Math.min(MAX_ADDITIONAL_AXES, options.model.additionalYAxes.length);
|
||||||
|
for (let yAxisCount = 0; yAxisCount < maxLength; yAxisCount++) {
|
||||||
|
const yAxis = options.model.additionalYAxes[yAxisCount];
|
||||||
|
this.additionalYAxes.push(new YAxisModel({
|
||||||
|
model: yAxis,
|
||||||
|
plot: this,
|
||||||
|
openmct: options.openmct,
|
||||||
|
id: yAxis.id || (MAIN_Y_AXES_ID + yAxisCount + 1)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the saved options config doesn't include information about all the additional axes, we initialize the remaining here
|
||||||
|
for (let axesCount = this.additionalYAxes.length; axesCount < MAX_ADDITIONAL_AXES; axesCount++) {
|
||||||
|
this.additionalYAxes.push(new YAxisModel({
|
||||||
|
plot: this,
|
||||||
|
openmct: options.openmct,
|
||||||
|
id: MAIN_Y_AXES_ID + axesCount + 1
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
// end add additional axes
|
||||||
|
|
||||||
this.legend = new LegendModel({
|
this.legend = new LegendModel({
|
||||||
model: options.model.legend,
|
model: options.model.legend,
|
||||||
plot: this,
|
plot: this,
|
||||||
@@ -81,6 +112,9 @@ export default class PlotConfigurationModel extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.yAxis.listenToSeriesCollection(this.series);
|
this.yAxis.listenToSeriesCollection(this.series);
|
||||||
|
this.additionalYAxes.forEach(yAxis => {
|
||||||
|
yAxis.listenToSeriesCollection(this.series);
|
||||||
|
});
|
||||||
this.legend.listenToSeriesCollection(this.series);
|
this.legend.listenToSeriesCollection(this.series);
|
||||||
|
|
||||||
this.listenTo(this, 'destroy', this.onDestroy, this);
|
this.listenTo(this, 'destroy', this.onDestroy, this);
|
||||||
@@ -145,6 +179,7 @@ export default class PlotConfigurationModel extends Model {
|
|||||||
domainObject: options.domainObject,
|
domainObject: options.domainObject,
|
||||||
xAxis: {},
|
xAxis: {},
|
||||||
yAxis: _.cloneDeep(options.domainObject.configuration?.yAxis ?? {}),
|
yAxis: _.cloneDeep(options.domainObject.configuration?.yAxis ?? {}),
|
||||||
|
additionalYAxes: _.cloneDeep(options.domainObject.configuration?.additionalYAxes ?? []),
|
||||||
legend: _.cloneDeep(options.domainObject.configuration?.legend ?? {})
|
legend: _.cloneDeep(options.domainObject.configuration?.legend ?? {})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,8 @@ export default class PlotSeries extends Model {
|
|||||||
markerShape: 'point',
|
markerShape: 'point',
|
||||||
markerSize: 2.0,
|
markerSize: 2.0,
|
||||||
alarmMarkers: true,
|
alarmMarkers: true,
|
||||||
limitLines: false
|
limitLines: false,
|
||||||
|
yAxisId: options.model.yAxisId || 1
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,18 +135,44 @@ export default class YAxisModel extends Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
resetStats() {
|
resetStats() {
|
||||||
|
//TODO: do we need the series id here?
|
||||||
this.unset('stats');
|
this.unset('stats');
|
||||||
this.seriesCollection.forEach(series => {
|
this.getSeriesForYAxis(this.seriesCollection).forEach(series => {
|
||||||
if (series.has('stats')) {
|
if (series.has('stats')) {
|
||||||
this.updateStats(series.get('stats'));
|
this.updateStats(series.get('stats'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
getSeriesForYAxis(seriesCollection) {
|
||||||
|
return seriesCollection.filter(series => {
|
||||||
|
const seriesYAxisId = series.get('yAxisId') || 1;
|
||||||
|
|
||||||
|
return seriesYAxisId === this.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getYAxisForId(id) {
|
||||||
|
const plotModel = this.plot.get('domainObject');
|
||||||
|
let yAxis;
|
||||||
|
if (this.id === 1) {
|
||||||
|
yAxis = plotModel.configuration?.yAxis;
|
||||||
|
} else {
|
||||||
|
if (plotModel.configuration?.additionalYAxes) {
|
||||||
|
yAxis = plotModel.configuration.additionalYAxes.find(additionalYAxis => additionalYAxis.id === id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return yAxis;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @param {import('./PlotSeries').default} series
|
* @param {import('./PlotSeries').default} series
|
||||||
*/
|
*/
|
||||||
trackSeries(series) {
|
trackSeries(series) {
|
||||||
this.listenTo(series, 'change:stats', seriesStats => {
|
this.listenTo(series, 'change:stats', seriesStats => {
|
||||||
|
if (series.get('yAxisId') !== this.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!seriesStats) {
|
if (!seriesStats) {
|
||||||
this.resetStats();
|
this.resetStats();
|
||||||
} else {
|
} else {
|
||||||
@@ -154,6 +180,10 @@ export default class YAxisModel extends Model {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.listenTo(series, 'change:yKey', () => {
|
this.listenTo(series, 'change:yKey', () => {
|
||||||
|
if (series.get('yAxisId') !== this.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.updateFromSeries(this.seriesCollection);
|
this.updateFromSeries(this.seriesCollection);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -252,14 +282,40 @@ export default class YAxisModel extends Model {
|
|||||||
// Update the series collection labels and formatting
|
// Update the series collection labels and formatting
|
||||||
this.updateFromSeries(this.seriesCollection);
|
this.updateFromSeries(this.seriesCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For a given series collection, get the metadata of the current yKey for each series.
|
||||||
|
* Then return first available value of the given property from the metadata.
|
||||||
|
* @param {import('./SeriesCollection').default} series
|
||||||
|
* @param {String} property
|
||||||
|
*/
|
||||||
|
getMetadataValueByProperty(series, property) {
|
||||||
|
return series.map(s => (s.metadata ? s.metadata.value(s.get('yKey'))[property] : ''))
|
||||||
|
.reduce((a, b) => {
|
||||||
|
if (a === undefined) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a === b) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}, undefined);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Update yAxis format, values, and label from known series.
|
* Update yAxis format, values, and label from known series.
|
||||||
* @param {import('./SeriesCollection').default} seriesCollection
|
* @param {import('./SeriesCollection').default} seriesCollection
|
||||||
*/
|
*/
|
||||||
updateFromSeries(seriesCollection) {
|
updateFromSeries(seriesCollection) {
|
||||||
const plotModel = this.plot.get('domainObject');
|
const seriesForThisYAxis = this.getSeriesForYAxis(seriesCollection);
|
||||||
const label = plotModel.configuration?.yAxis?.label;
|
if (!seriesForThisYAxis.length) {
|
||||||
const sampleSeries = seriesCollection.first();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const yAxis = this.getYAxisForId(this.id);
|
||||||
|
const label = yAxis?.label;
|
||||||
|
const sampleSeries = seriesForThisYAxis[0];
|
||||||
if (!sampleSeries || !sampleSeries.metadata) {
|
if (!sampleSeries || !sampleSeries.metadata) {
|
||||||
if (!label) {
|
if (!label) {
|
||||||
this.unset('label');
|
this.unset('label');
|
||||||
@@ -279,41 +335,17 @@ export default class YAxisModel extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.set('values', yMetadata.values);
|
this.set('values', yMetadata.values);
|
||||||
|
|
||||||
if (!label) {
|
if (!label) {
|
||||||
const labelName = seriesCollection
|
const labelName = this.getMetadataValueByProperty(seriesForThisYAxis, 'name');
|
||||||
.map(s => (s.metadata ? s.metadata.value(s.get('yKey')).name : ''))
|
|
||||||
.reduce((a, b) => {
|
|
||||||
if (a === undefined) {
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a === b) {
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}, undefined);
|
|
||||||
|
|
||||||
if (labelName) {
|
if (labelName) {
|
||||||
this.set('label', labelName);
|
this.set('label', labelName);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const labelUnits = seriesCollection
|
//if the name is not available, set the units as the label
|
||||||
.map(s => (s.metadata ? s.metadata.value(s.get('yKey')).units : ''))
|
const labelUnits = this.getMetadataValueByProperty(seriesForThisYAxis, 'units');
|
||||||
.reduce((a, b) => {
|
|
||||||
if (a === undefined) {
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a === b) {
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}, undefined);
|
|
||||||
|
|
||||||
if (labelUnits) {
|
if (labelUnits) {
|
||||||
this.set('label', labelUnits);
|
this.set('label', labelUnits);
|
||||||
|
|
||||||
@@ -331,7 +363,8 @@ export default class YAxisModel extends Model {
|
|||||||
frozen: false,
|
frozen: false,
|
||||||
autoscale: true,
|
autoscale: true,
|
||||||
logMode: options.model?.logMode ?? false,
|
logMode: options.model?.logMode ?? false,
|
||||||
autoscalePadding: 0.1
|
autoscalePadding: 0.1,
|
||||||
|
id: options.id
|
||||||
|
|
||||||
// 'range' is not specified here, it is undefined at first. When the
|
// 'range' is not specified here, it is undefined at first. When the
|
||||||
// user turns off autoscale, the current 'displayRange' is used for
|
// user turns off autoscale, the current 'displayRange' is used for
|
||||||
|
|||||||
@@ -36,20 +36,21 @@
|
|||||||
/>
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
<div
|
<div
|
||||||
v-if="plotSeries.length"
|
v-if="plotSeries.length && !isStackedPlotObject"
|
||||||
class="grid-properties"
|
class="grid-properties"
|
||||||
>
|
>
|
||||||
<ul
|
<ul
|
||||||
v-if="!isStackedPlotObject"
|
v-for="(yAxis, index) in yAxesWithSeries"
|
||||||
|
:key="`yAxis-${index}`"
|
||||||
class="l-inspector-part js-yaxis-properties"
|
class="l-inspector-part js-yaxis-properties"
|
||||||
>
|
>
|
||||||
<h2 title="Y axis settings for this object">Y Axis</h2>
|
<h2 title="Y axis settings for this object">Y Axis {{ yAxis.id }}</h2>
|
||||||
<li class="grid-row">
|
<li class="grid-row">
|
||||||
<div
|
<div
|
||||||
class="grid-cell label"
|
class="grid-cell label"
|
||||||
title="Manually override how the Y axis is labeled."
|
title="Manually override how the Y axis is labeled."
|
||||||
>Label</div>
|
>Label</div>
|
||||||
<div class="grid-cell value">{{ label ? label : "Not defined" }}</div>
|
<div class="grid-cell value">{{ yAxis.label ? yAxis.label : "Not defined" }}</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="grid-row">
|
<li class="grid-row">
|
||||||
<div
|
<div
|
||||||
@@ -57,7 +58,7 @@
|
|||||||
title="Enable log mode."
|
title="Enable log mode."
|
||||||
>Log mode</div>
|
>Log mode</div>
|
||||||
<div class="grid-cell value">
|
<div class="grid-cell value">
|
||||||
{{ logMode ? "Enabled" : "Disabled" }}
|
{{ yAxis.logMode ? "Enabled" : "Disabled" }}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="grid-row">
|
<li class="grid-row">
|
||||||
@@ -66,32 +67,36 @@
|
|||||||
title="Automatically scale the Y axis to keep all values in view."
|
title="Automatically scale the Y axis to keep all values in view."
|
||||||
>Auto scale</div>
|
>Auto scale</div>
|
||||||
<div class="grid-cell value">
|
<div class="grid-cell value">
|
||||||
{{ autoscale ? "Enabled: " + autoscalePadding : "Disabled" }}
|
{{ yAxis.autoscale ? "Enabled: " + yAxis.autoscalePadding : "Disabled" }}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="!autoscale && rangeMin"
|
v-if="!yAxis.autoscale && yAxis.rangeMin"
|
||||||
class="grid-row"
|
class="grid-row"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="grid-cell label"
|
class="grid-cell label"
|
||||||
title="Minimum Y axis value."
|
title="Minimum Y axis value."
|
||||||
>Minimum value</div>
|
>Minimum value</div>
|
||||||
<div class="grid-cell value">{{ rangeMin }}</div>
|
<div class="grid-cell value">{{ yAxis.rangeMin }}</div>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="!autoscale && rangeMax"
|
v-if="!yAxis.autoscale && yAxis.rangeMax"
|
||||||
class="grid-row"
|
class="grid-row"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="grid-cell label"
|
class="grid-cell label"
|
||||||
title="Maximum Y axis value."
|
title="Maximum Y axis value."
|
||||||
>Maximum value</div>
|
>Maximum value</div>
|
||||||
<div class="grid-cell value">{{ rangeMax }}</div>
|
<div class="grid-cell value">{{ yAxis.rangeMax }}</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="plotSeries.length && (isStackedPlotObject || !isNestedWithinAStackedPlot)"
|
||||||
|
class="grid-properties"
|
||||||
|
>
|
||||||
<ul
|
<ul
|
||||||
v-if="isStackedPlotObject || !isNestedWithinAStackedPlot"
|
|
||||||
class="l-inspector-part js-legend-properties"
|
class="l-inspector-part js-legend-properties"
|
||||||
>
|
>
|
||||||
<h2 title="Legend settings for this object">Legend</h2>
|
<h2 title="Legend settings for this object">Legend</h2>
|
||||||
@@ -157,12 +162,6 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
config: {},
|
config: {},
|
||||||
label: '',
|
|
||||||
autoscale: '',
|
|
||||||
logMode: false,
|
|
||||||
autoscalePadding: '',
|
|
||||||
rangeMin: '',
|
|
||||||
rangeMax: '',
|
|
||||||
position: '',
|
position: '',
|
||||||
hideLegendWhenSmall: '',
|
hideLegendWhenSmall: '',
|
||||||
expandByDefault: '',
|
expandByDefault: '',
|
||||||
@@ -173,7 +172,8 @@ export default {
|
|||||||
showMaximumWhenExpanded: '',
|
showMaximumWhenExpanded: '',
|
||||||
showUnitsWhenExpanded: '',
|
showUnitsWhenExpanded: '',
|
||||||
loaded: false,
|
loaded: false,
|
||||||
plotSeries: []
|
plotSeries: [],
|
||||||
|
yAxes: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -182,13 +182,18 @@ export default {
|
|||||||
},
|
},
|
||||||
isStackedPlotObject() {
|
isStackedPlotObject() {
|
||||||
return this.path.find((pathObject, pathObjIndex) => pathObjIndex === 0 && pathObject.type === 'telemetry.plot.stacked');
|
return this.path.find((pathObject, pathObjIndex) => pathObjIndex === 0 && pathObject.type === 'telemetry.plot.stacked');
|
||||||
|
},
|
||||||
|
yAxesWithSeries() {
|
||||||
|
return this.yAxes.filter(yAxis => yAxis.seriesCount > 0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
eventHelpers.extend(this);
|
eventHelpers.extend(this);
|
||||||
this.config = this.getConfig();
|
this.config = this.getConfig();
|
||||||
|
this.initYAxesConfiguration();
|
||||||
|
|
||||||
this.registerListeners();
|
this.registerListeners();
|
||||||
this.initConfiguration();
|
this.initLegendConfiguration();
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -196,18 +201,38 @@ export default {
|
|||||||
this.stopListening();
|
this.stopListening();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initConfiguration() {
|
initYAxesConfiguration() {
|
||||||
if (this.config) {
|
if (this.config) {
|
||||||
this.label = this.config.yAxis.get('label');
|
let range = this.config.yAxis.get('range');
|
||||||
this.autoscale = this.config.yAxis.get('autoscale');
|
|
||||||
this.logMode = this.config.yAxis.get('logMode');
|
|
||||||
this.autoscalePadding = this.config.yAxis.get('autoscalePadding');
|
|
||||||
const range = this.config.yAxis.get('range');
|
|
||||||
if (range) {
|
|
||||||
this.rangeMin = range.min;
|
|
||||||
this.rangeMax = range.max;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
this.yAxes.push({
|
||||||
|
id: this.config.yAxis.id,
|
||||||
|
seriesCount: 0,
|
||||||
|
label: this.config.yAxis.get('label'),
|
||||||
|
autoscale: this.config.yAxis.get('autoscale'),
|
||||||
|
logMode: this.config.yAxis.get('logMode'),
|
||||||
|
autoscalePadding: this.config.yAxis.get('autoscalePadding'),
|
||||||
|
rangeMin: range ? range.min : '',
|
||||||
|
rangeMax: range ? range.max : ''
|
||||||
|
});
|
||||||
|
this.config.additionalYAxes.forEach(yAxis => {
|
||||||
|
range = yAxis.get('range');
|
||||||
|
|
||||||
|
this.yAxes.push({
|
||||||
|
id: yAxis.id,
|
||||||
|
seriesCount: 0,
|
||||||
|
label: yAxis.get('label'),
|
||||||
|
autoscale: yAxis.get('autoscale'),
|
||||||
|
logMode: yAxis.get('logMode'),
|
||||||
|
autoscalePadding: yAxis.get('autoscalePadding'),
|
||||||
|
rangeMin: range ? range.min : '',
|
||||||
|
rangeMax: range ? range.max : ''
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initLegendConfiguration() {
|
||||||
|
if (this.config) {
|
||||||
this.position = this.config.legend.get('position');
|
this.position = this.config.legend.get('position');
|
||||||
this.hideLegendWhenSmall = this.config.legend.get('hideLegendWhenSmall');
|
this.hideLegendWhenSmall = this.config.legend.get('hideLegendWhenSmall');
|
||||||
this.expandByDefault = this.config.legend.get('expandByDefault');
|
this.expandByDefault = this.config.legend.get('expandByDefault');
|
||||||
@@ -229,18 +254,44 @@ export default {
|
|||||||
this.config.series.forEach(this.addSeries, this);
|
this.config.series.forEach(this.addSeries, this);
|
||||||
|
|
||||||
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.resetAllSeries, this);
|
this.listenTo(this.config.series, 'remove', this.removeSeries, this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setYAxisLabel(yAxisId) {
|
||||||
|
const found = this.yAxes.find(yAxis => yAxis.id === yAxisId);
|
||||||
|
if (found && found.seriesCount > 0) {
|
||||||
|
const mainYAxisId = this.config.yAxis.id;
|
||||||
|
if (mainYAxisId === yAxisId) {
|
||||||
|
found.label = this.config.yAxis.get('label');
|
||||||
|
} else {
|
||||||
|
const additionalYAxis = this.config.additionalYAxes.find(axis => axis.id === yAxisId);
|
||||||
|
if (additionalYAxis) {
|
||||||
|
found.label = additionalYAxis.get('label');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addSeries(series, index) {
|
addSeries(series, index) {
|
||||||
|
const yAxisId = series.get('yAxisId');
|
||||||
|
this.updateAxisUsageCount(yAxisId, 1);
|
||||||
this.$set(this.plotSeries, index, series);
|
this.$set(this.plotSeries, index, series);
|
||||||
this.initConfiguration();
|
this.setYAxisLabel(yAxisId);
|
||||||
},
|
},
|
||||||
|
|
||||||
resetAllSeries() {
|
removeSeries(plotSeries, index) {
|
||||||
this.plotSeries = [];
|
const yAxisId = plotSeries.get('yAxisId');
|
||||||
this.config.series.forEach(this.addSeries, this);
|
this.updateAxisUsageCount(yAxisId, -1);
|
||||||
|
this.plotSeries.splice(index, 1);
|
||||||
|
this.setYAxisLabel(yAxisId);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateAxisUsageCount(yAxisId, updateCount) {
|
||||||
|
const foundYAxis = this.yAxes.find(yAxis => yAxis.id === yAxisId);
|
||||||
|
if (foundYAxis) {
|
||||||
|
foundYAxis.seriesCount = foundYAxis.seriesCount + updateCount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,8 +40,10 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<y-axis-form
|
<y-axis-form
|
||||||
v-if="plotSeries.length && !isStackedPlotObject"
|
v-for="(yAxisId, index) in yAxesIds"
|
||||||
class="grid-properties"
|
:id="yAxisId.id"
|
||||||
|
:key="`yAxis-${index}`"
|
||||||
|
class="grid-properties js-yaxis-grid-properties"
|
||||||
:y-axis="config.yAxis"
|
:y-axis="config.yAxis"
|
||||||
@seriesUpdated="updateSeriesConfigForObject"
|
@seriesUpdated="updateSeriesConfigForObject"
|
||||||
/>
|
/>
|
||||||
@@ -76,6 +78,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
config: {},
|
config: {},
|
||||||
|
yAxes: [],
|
||||||
plotSeries: [],
|
plotSeries: [],
|
||||||
loaded: false
|
loaded: false
|
||||||
};
|
};
|
||||||
@@ -86,11 +89,27 @@ export default {
|
|||||||
},
|
},
|
||||||
isStackedPlotObject() {
|
isStackedPlotObject() {
|
||||||
return this.path.find((pathObject, pathObjIndex) => pathObjIndex === 0 && pathObject.type === 'telemetry.plot.stacked');
|
return this.path.find((pathObject, pathObjIndex) => pathObjIndex === 0 && pathObject.type === 'telemetry.plot.stacked');
|
||||||
|
},
|
||||||
|
yAxesIds() {
|
||||||
|
return !this.isStackedPlotObject && this.yAxes.filter(yAxis => yAxis.seriesCount > 0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
eventHelpers.extend(this);
|
eventHelpers.extend(this);
|
||||||
this.config = this.getConfig();
|
this.config = this.getConfig();
|
||||||
|
this.yAxes = [{
|
||||||
|
id: this.config.yAxis.id,
|
||||||
|
seriesCount: 0
|
||||||
|
}];
|
||||||
|
if (this.config.additionalYAxes) {
|
||||||
|
this.yAxes = this.yAxes.concat(this.config.additionalYAxes.map(yAxis => {
|
||||||
|
return {
|
||||||
|
id: yAxis.id,
|
||||||
|
seriesCount: 0
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
this.registerListeners();
|
this.registerListeners();
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
},
|
},
|
||||||
@@ -107,16 +126,47 @@ export default {
|
|||||||
this.config.series.forEach(this.addSeries, this);
|
this.config.series.forEach(this.addSeries, this);
|
||||||
|
|
||||||
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.resetAllSeries, this);
|
this.listenTo(this.config.series, 'remove', this.removeSeries, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
findYAxisForId(yAxisId) {
|
||||||
|
return this.yAxes.find(yAxis => yAxis.id === yAxisId);
|
||||||
|
},
|
||||||
|
|
||||||
|
setYAxisLabel(yAxisId) {
|
||||||
|
const found = this.findYAxisForId(yAxisId);
|
||||||
|
if (found && found.seriesCount > 0) {
|
||||||
|
const mainYAxisId = this.config.yAxis.id;
|
||||||
|
if (mainYAxisId === yAxisId) {
|
||||||
|
found.label = this.config.yAxis.get('label');
|
||||||
|
} else {
|
||||||
|
const additionalYAxis = this.config.additionalYAxes.find(axis => axis.id === yAxisId);
|
||||||
|
if (additionalYAxis) {
|
||||||
|
found.label = additionalYAxis.get('label');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addSeries(series, index) {
|
addSeries(series, index) {
|
||||||
|
const yAxisId = series.get('yAxisId');
|
||||||
|
this.updateAxisUsageCount(yAxisId, 1);
|
||||||
this.$set(this.plotSeries, index, series);
|
this.$set(this.plotSeries, index, series);
|
||||||
|
this.setYAxisLabel(yAxisId);
|
||||||
},
|
},
|
||||||
|
|
||||||
resetAllSeries() {
|
removeSeries(series, index) {
|
||||||
this.plotSeries = [];
|
const yAxisId = series.get('yAxisId');
|
||||||
this.config.series.forEach(this.addSeries, this);
|
this.updateAxisUsageCount(yAxisId, -1);
|
||||||
|
this.plotSeries.splice(index, 1);
|
||||||
|
this.setYAxisLabel(yAxisId);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateAxisUsageCount(yAxisId, updateCount) {
|
||||||
|
const foundYAxis = this.findYAxisForId(yAxisId);
|
||||||
|
if (foundYAxis) {
|
||||||
|
foundYAxis.seriesCount = foundYAxis.seriesCount + updateCount;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateSeriesConfigForObject(config) {
|
updateSeriesConfigForObject(config) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div v-if="loaded">
|
||||||
<ul class="l-inspector-part">
|
<ul class="l-inspector-part">
|
||||||
<h2>Y Axis</h2>
|
<h2>Y Axis {{ id }}</h2>
|
||||||
<li class="grid-row">
|
<li class="grid-row">
|
||||||
<div
|
<div
|
||||||
class="grid-cell label"
|
class="grid-cell label"
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
<!-- eslint-disable-next-line vue/html-self-closing -->
|
<!-- eslint-disable-next-line vue/html-self-closing -->
|
||||||
<input
|
<input
|
||||||
v-model="logMode"
|
v-model="logMode"
|
||||||
|
class="js-log-mode-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@change="updateForm('logMode')"
|
@change="updateForm('logMode')"
|
||||||
/>
|
/>
|
||||||
@@ -103,52 +104,72 @@
|
|||||||
<script>
|
<script>
|
||||||
import { objectPath } from "./formUtil";
|
import { objectPath } from "./formUtil";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import eventHelpers from "../../lib/eventHelpers";
|
||||||
|
import configStore from "../../configuration/ConfigStore";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'domainObject'],
|
||||||
props: {
|
props: {
|
||||||
yAxis: {
|
id: {
|
||||||
type: Object,
|
type: Number,
|
||||||
default() {
|
required: true
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
yAxis: null,
|
||||||
label: '',
|
label: '',
|
||||||
autoscale: '',
|
autoscale: '',
|
||||||
logMode: false,
|
logMode: false,
|
||||||
autoscalePadding: '',
|
autoscalePadding: '',
|
||||||
rangeMin: '',
|
rangeMin: '',
|
||||||
rangeMax: '',
|
rangeMax: '',
|
||||||
validationErrors: {}
|
validationErrors: {},
|
||||||
|
loaded: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.initialize();
|
eventHelpers.extend(this);
|
||||||
|
this.getConfig();
|
||||||
|
this.loaded = true;
|
||||||
|
this.initFields();
|
||||||
this.initFormValues();
|
this.initFormValues();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initialize: function () {
|
getConfig() {
|
||||||
|
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
|
const config = configStore.get(configId);
|
||||||
|
if (config) {
|
||||||
|
const mainYAxisId = config.yAxis.id;
|
||||||
|
this.isAdditionalYAxis = this.id !== mainYAxisId;
|
||||||
|
if (this.isAdditionalYAxis) {
|
||||||
|
this.additionalYAxes = config.additionalYAxes;
|
||||||
|
this.yAxis = config.additionalYAxes.find(yAxis => yAxis.id === this.id);
|
||||||
|
} else {
|
||||||
|
this.yAxis = config.yAxis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initFields() {
|
||||||
|
const prefix = `configuration.${this.getPrefix()}`;
|
||||||
this.fields = {
|
this.fields = {
|
||||||
label: {
|
label: {
|
||||||
objectPath: 'configuration.yAxis.label'
|
objectPath: `${prefix}.label`
|
||||||
},
|
},
|
||||||
autoscale: {
|
autoscale: {
|
||||||
coerce: Boolean,
|
coerce: Boolean,
|
||||||
objectPath: 'configuration.yAxis.autoscale'
|
objectPath: `${prefix}.autoscale`
|
||||||
},
|
},
|
||||||
autoscalePadding: {
|
autoscalePadding: {
|
||||||
coerce: Number,
|
coerce: Number,
|
||||||
objectPath: 'configuration.yAxis.autoscalePadding'
|
objectPath: `${prefix}.autoscalePadding`
|
||||||
},
|
},
|
||||||
logMode: {
|
logMode: {
|
||||||
coerce: Boolean,
|
coerce: Boolean,
|
||||||
objectPath: 'configuration.yAxis.logMode'
|
objectPath: `${prefix}.logMode`
|
||||||
},
|
},
|
||||||
range: {
|
range: {
|
||||||
objectPath: 'configuration.yAxis.range',
|
objectPath: `${prefix}.range'`,
|
||||||
coerce: function coerceRange(range) {
|
coerce: function coerceRange(range) {
|
||||||
const newRange = {
|
const newRange = {
|
||||||
min: -1,
|
min: -1,
|
||||||
@@ -202,6 +223,25 @@ export default {
|
|||||||
this.rangeMin = range?.min;
|
this.rangeMin = range?.min;
|
||||||
this.rangeMax = range?.max;
|
this.rangeMax = range?.max;
|
||||||
},
|
},
|
||||||
|
getPrefix() {
|
||||||
|
let prefix = 'yAxis';
|
||||||
|
if (this.isAdditionalYAxis) {
|
||||||
|
let index = -1;
|
||||||
|
if (this.additionalYAxes) {
|
||||||
|
index = this.additionalYAxes.findIndex((yAxis) => {
|
||||||
|
return yAxis.id === this.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix = `additionalYAxes[${index}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefix;
|
||||||
|
},
|
||||||
updateForm(formKey) {
|
updateForm(formKey) {
|
||||||
let newVal;
|
let newVal;
|
||||||
if (formKey === 'range') {
|
if (formKey === 'range') {
|
||||||
@@ -231,18 +271,42 @@ export default {
|
|||||||
this.yAxis.set(formKey, newVal);
|
this.yAxis.set(formKey, newVal);
|
||||||
// Then we mutate the domain object configuration to persist the settings
|
// Then we mutate the domain object configuration to persist the settings
|
||||||
if (path) {
|
if (path) {
|
||||||
if (!this.domainObject.configuration || !this.domainObject.configuration.series) {
|
if (this.isAdditionalYAxis) {
|
||||||
this.$emit('seriesUpdated', {
|
if (this.domainObject.configuration && this.domainObject.configuration.series) {
|
||||||
identifier: this.domainObject.identifier,
|
//update the id
|
||||||
path: `yAxis.${formKey}`,
|
this.openmct.objects.mutate(
|
||||||
value: newVal
|
this.domainObject,
|
||||||
});
|
`configuration.${this.getPrefix()}.id`,
|
||||||
|
this.id
|
||||||
|
);
|
||||||
|
//update the yAxes values
|
||||||
|
this.openmct.objects.mutate(
|
||||||
|
this.domainObject,
|
||||||
|
path(this.domainObject, this.yAxis),
|
||||||
|
newVal
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.$emit('seriesUpdated', {
|
||||||
|
identifier: this.domainObject.identifier,
|
||||||
|
path: `${this.getPrefix()}.${formKey}`,
|
||||||
|
id: this.id,
|
||||||
|
value: newVal
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.openmct.objects.mutate(
|
if (this.domainObject.configuration && this.domainObject.configuration.series) {
|
||||||
this.domainObject,
|
this.openmct.objects.mutate(
|
||||||
path(this.domainObject, this.yAxis),
|
this.domainObject,
|
||||||
newVal
|
path(this.domainObject, this.yAxis),
|
||||||
);
|
newVal
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.$emit('seriesUpdated', {
|
||||||
|
identifier: this.domainObject.identifier,
|
||||||
|
path: `${this.getPrefix()}.${formKey}`,
|
||||||
|
value: newVal
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
504
src/plugins/plot/overlayPlot/pluginSpec.js
Normal file
504
src/plugins/plot/overlayPlot/pluginSpec.js
Normal file
@@ -0,0 +1,504 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import {createMouseEvent, createOpenMct, resetApplicationState, spyOnBuiltins} from "utils/testing";
|
||||||
|
import PlotVuePlugin from "../plugin";
|
||||||
|
import Vue from "vue";
|
||||||
|
import Plot from "../Plot.vue";
|
||||||
|
import configStore from "../configuration/ConfigStore";
|
||||||
|
import EventEmitter from "EventEmitter";
|
||||||
|
import PlotOptions from "../inspector/PlotOptions.vue";
|
||||||
|
|
||||||
|
describe("the plugin", function () {
|
||||||
|
let element;
|
||||||
|
let child;
|
||||||
|
let openmct;
|
||||||
|
let telemetryPromise;
|
||||||
|
let telemetryPromiseResolve;
|
||||||
|
let mockObjectPath;
|
||||||
|
let overlayPlotObject = {
|
||||||
|
identifier: {
|
||||||
|
namespace: "",
|
||||||
|
key: "test-plot"
|
||||||
|
},
|
||||||
|
type: "telemetry.plot.overlay",
|
||||||
|
name: "Test Overlay Plot",
|
||||||
|
composition: [],
|
||||||
|
configuration: {
|
||||||
|
series: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach((done) => {
|
||||||
|
mockObjectPath = [
|
||||||
|
{
|
||||||
|
name: 'mock folder',
|
||||||
|
type: 'fake-folder',
|
||||||
|
identifier: {
|
||||||
|
key: 'mock-folder',
|
||||||
|
namespace: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mock parent folder',
|
||||||
|
type: 'time-strip',
|
||||||
|
identifier: {
|
||||||
|
key: 'mock-parent-folder',
|
||||||
|
namespace: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const testTelemetry = [
|
||||||
|
{
|
||||||
|
'utc': 1,
|
||||||
|
'some-key': 'some-value 1',
|
||||||
|
'some-other-key': 'some-other-value 1',
|
||||||
|
'some-key2': 'some-value2 1',
|
||||||
|
'some-other-key2': 'some-other-value2 1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'utc': 2,
|
||||||
|
'some-key': 'some-value 2',
|
||||||
|
'some-other-key': 'some-other-value 2',
|
||||||
|
'some-key2': 'some-value2 2',
|
||||||
|
'some-other-key2': 'some-other-value2 2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'utc': 3,
|
||||||
|
'some-key': 'some-value 3',
|
||||||
|
'some-other-key': 'some-other-value 3',
|
||||||
|
'some-key2': 'some-value2 2',
|
||||||
|
'some-other-key2': 'some-other-value2 2'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const timeSystem = {
|
||||||
|
timeSystemKey: 'utc',
|
||||||
|
bounds: {
|
||||||
|
start: 0,
|
||||||
|
end: 4
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
openmct = createOpenMct(timeSystem);
|
||||||
|
|
||||||
|
telemetryPromise = new Promise((resolve) => {
|
||||||
|
telemetryPromiseResolve = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
spyOn(openmct.telemetry, 'request').and.callFake(() => {
|
||||||
|
telemetryPromiseResolve(testTelemetry);
|
||||||
|
|
||||||
|
return telemetryPromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
openmct.install(new PlotVuePlugin());
|
||||||
|
|
||||||
|
element = document.createElement("div");
|
||||||
|
element.style.width = "640px";
|
||||||
|
element.style.height = "480px";
|
||||||
|
child = document.createElement("div");
|
||||||
|
child.style.width = "640px";
|
||||||
|
child.style.height = "480px";
|
||||||
|
element.appendChild(child);
|
||||||
|
document.body.appendChild(element);
|
||||||
|
|
||||||
|
spyOn(window, 'ResizeObserver').and.returnValue({
|
||||||
|
observe() {},
|
||||||
|
unobserve() {},
|
||||||
|
disconnect() {}
|
||||||
|
});
|
||||||
|
|
||||||
|
openmct.types.addType("test-object", {
|
||||||
|
creatable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
spyOnBuiltins(["requestAnimationFrame"]);
|
||||||
|
window.requestAnimationFrame.and.callFake((callBack) => {
|
||||||
|
callBack();
|
||||||
|
});
|
||||||
|
|
||||||
|
openmct.router.path = [overlayPlotObject];
|
||||||
|
openmct.on("start", done);
|
||||||
|
openmct.startHeadless();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach((done) => {
|
||||||
|
openmct.time.timeSystem('utc', {
|
||||||
|
start: 0,
|
||||||
|
end: 1
|
||||||
|
});
|
||||||
|
configStore.deleteAll();
|
||||||
|
resetApplicationState(openmct).then(done).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
openmct.router.path = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("the plot views", () => {
|
||||||
|
it("provides an overlay plot view for objects with telemetry", () => {
|
||||||
|
const testTelemetryObject = {
|
||||||
|
id: "test-object",
|
||||||
|
type: "telemetry.plot.overlay",
|
||||||
|
telemetry: {
|
||||||
|
values: [{
|
||||||
|
key: "some-key"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const applicableViews = openmct.objectViews.get(testTelemetryObject, mockObjectPath);
|
||||||
|
let plotView = applicableViews.find((viewProvider) => viewProvider.key === "plot-overlay");
|
||||||
|
expect(plotView).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("The overlay plot view with multiple axes", () => {
|
||||||
|
let testTelemetryObject;
|
||||||
|
let testTelemetryObject2;
|
||||||
|
let config;
|
||||||
|
let component;
|
||||||
|
let mockComposition;
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
component.$destroy();
|
||||||
|
openmct.router.path = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testTelemetryObject = {
|
||||||
|
identifier: {
|
||||||
|
namespace: "",
|
||||||
|
key: "test-object"
|
||||||
|
},
|
||||||
|
type: "test-object",
|
||||||
|
name: "Test Object",
|
||||||
|
telemetry: {
|
||||||
|
values: [{
|
||||||
|
key: "utc",
|
||||||
|
format: "utc",
|
||||||
|
name: "Time",
|
||||||
|
hints: {
|
||||||
|
domain: 1
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: "some-key",
|
||||||
|
name: "Some attribute",
|
||||||
|
hints: {
|
||||||
|
range: 1
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: "some-other-key",
|
||||||
|
name: "Another attribute",
|
||||||
|
hints: {
|
||||||
|
range: 2
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
testTelemetryObject2 = {
|
||||||
|
identifier: {
|
||||||
|
namespace: "",
|
||||||
|
key: "test-object2"
|
||||||
|
},
|
||||||
|
type: "test-object",
|
||||||
|
name: "Test Object2",
|
||||||
|
telemetry: {
|
||||||
|
values: [{
|
||||||
|
key: "utc",
|
||||||
|
format: "utc",
|
||||||
|
name: "Time",
|
||||||
|
hints: {
|
||||||
|
domain: 1
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: "some-key2",
|
||||||
|
name: "Some attribute2",
|
||||||
|
hints: {
|
||||||
|
range: 1
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: "some-other-key2",
|
||||||
|
name: "Another attribute2",
|
||||||
|
hints: {
|
||||||
|
range: 2
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
overlayPlotObject.composition = [
|
||||||
|
{
|
||||||
|
identifier: testTelemetryObject.identifier
|
||||||
|
},
|
||||||
|
{
|
||||||
|
identifier: testTelemetryObject2.identifier
|
||||||
|
}
|
||||||
|
];
|
||||||
|
overlayPlotObject.configuration.series = [
|
||||||
|
{
|
||||||
|
identifier: testTelemetryObject.identifier,
|
||||||
|
yAxisId: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
identifier: testTelemetryObject2.identifier,
|
||||||
|
yAxisId: 3
|
||||||
|
}
|
||||||
|
];
|
||||||
|
overlayPlotObject.configuration.additionalYAxes = [
|
||||||
|
{
|
||||||
|
label: 'Test Object Label',
|
||||||
|
id: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Test Object 2 Label',
|
||||||
|
id: 3
|
||||||
|
}
|
||||||
|
];
|
||||||
|
mockComposition = new EventEmitter();
|
||||||
|
mockComposition.load = () => {
|
||||||
|
mockComposition.emit('add', testTelemetryObject);
|
||||||
|
mockComposition.emit('add', testTelemetryObject2);
|
||||||
|
|
||||||
|
return [testTelemetryObject, testTelemetryObject2];
|
||||||
|
};
|
||||||
|
|
||||||
|
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
|
||||||
|
|
||||||
|
let viewContainer = document.createElement("div");
|
||||||
|
child.append(viewContainer);
|
||||||
|
component = new Vue({
|
||||||
|
el: viewContainer,
|
||||||
|
components: {
|
||||||
|
Plot
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct: openmct,
|
||||||
|
domainObject: overlayPlotObject,
|
||||||
|
composition: openmct.composition.get(overlayPlotObject),
|
||||||
|
path: [overlayPlotObject]
|
||||||
|
},
|
||||||
|
template: '<plot ref="plotComponent"></plot>'
|
||||||
|
});
|
||||||
|
|
||||||
|
return telemetryPromise
|
||||||
|
.then(Vue.nextTick())
|
||||||
|
.then(() => {
|
||||||
|
const configId = openmct.objects.makeKeyString(overlayPlotObject.identifier);
|
||||||
|
config = configStore.get(configId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Renders multiple Y-axis for the telemetry objects", (done) => {
|
||||||
|
config.yAxis.set('displayRange', {
|
||||||
|
min: 10,
|
||||||
|
max: 20
|
||||||
|
});
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
let yAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-y .gl-plot-tick-wrapper");
|
||||||
|
expect(yAxisElement.length).toBe(2);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('the inspector view', () => {
|
||||||
|
let inspectorComponent;
|
||||||
|
let viewComponentObject;
|
||||||
|
let selection;
|
||||||
|
beforeEach((done) => {
|
||||||
|
selection = [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
item: {
|
||||||
|
id: overlayPlotObject.identifier.key,
|
||||||
|
identifier: overlayPlotObject.identifier,
|
||||||
|
type: overlayPlotObject.type,
|
||||||
|
configuration: overlayPlotObject.configuration,
|
||||||
|
composition: overlayPlotObject.composition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
let viewContainer = document.createElement('div');
|
||||||
|
child.append(viewContainer);
|
||||||
|
inspectorComponent = new Vue({
|
||||||
|
el: viewContainer,
|
||||||
|
components: {
|
||||||
|
PlotOptions
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct: openmct,
|
||||||
|
domainObject: selection[0][0].context.item,
|
||||||
|
path: [selection[0][0].context.item]
|
||||||
|
},
|
||||||
|
template: '<plot-options/>'
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
viewComponentObject = inspectorComponent.$root.$children[0];
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
openmct.router.path = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('in edit mode', () => {
|
||||||
|
let editOptionsEl;
|
||||||
|
|
||||||
|
beforeEach((done) => {
|
||||||
|
viewComponentObject.setEditState(true);
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
editOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-edit');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows multiple yAxis options', () => {
|
||||||
|
const yAxisProperties = editOptionsEl.querySelectorAll(".js-yaxis-grid-properties .l-inspector-part h2");
|
||||||
|
expect(yAxisProperties.length).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('saves yAxis options', () => {
|
||||||
|
//toggle log mode and save
|
||||||
|
config.additionalYAxes[1].set('displayRange', {
|
||||||
|
min: 10,
|
||||||
|
max: 20
|
||||||
|
});
|
||||||
|
const yAxisProperties = editOptionsEl.querySelectorAll(".js-log-mode-input");
|
||||||
|
const clickEvent = createMouseEvent("click");
|
||||||
|
yAxisProperties[1].dispatchEvent(clickEvent);
|
||||||
|
|
||||||
|
expect(config.additionalYAxes[1].get('logMode')).toEqual(true);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("The overlay plot view with single axes", () => {
|
||||||
|
let testTelemetryObject;
|
||||||
|
let config;
|
||||||
|
let component;
|
||||||
|
let mockComposition;
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
component.$destroy();
|
||||||
|
openmct.router.path = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testTelemetryObject = {
|
||||||
|
identifier: {
|
||||||
|
namespace: "",
|
||||||
|
key: "test-object"
|
||||||
|
},
|
||||||
|
type: "test-object",
|
||||||
|
name: "Test Object",
|
||||||
|
telemetry: {
|
||||||
|
values: [{
|
||||||
|
key: "utc",
|
||||||
|
format: "utc",
|
||||||
|
name: "Time",
|
||||||
|
hints: {
|
||||||
|
domain: 1
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: "some-key",
|
||||||
|
name: "Some attribute",
|
||||||
|
hints: {
|
||||||
|
range: 1
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: "some-other-key",
|
||||||
|
name: "Another attribute",
|
||||||
|
hints: {
|
||||||
|
range: 2
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
overlayPlotObject.composition = [
|
||||||
|
{
|
||||||
|
identifier: testTelemetryObject.identifier
|
||||||
|
}
|
||||||
|
];
|
||||||
|
overlayPlotObject.configuration.series = [
|
||||||
|
{
|
||||||
|
identifier: testTelemetryObject.identifier
|
||||||
|
}
|
||||||
|
];
|
||||||
|
mockComposition = new EventEmitter();
|
||||||
|
mockComposition.load = () => {
|
||||||
|
mockComposition.emit('add', testTelemetryObject);
|
||||||
|
|
||||||
|
return [testTelemetryObject];
|
||||||
|
};
|
||||||
|
|
||||||
|
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
|
||||||
|
|
||||||
|
let viewContainer = document.createElement("div");
|
||||||
|
child.append(viewContainer);
|
||||||
|
component = new Vue({
|
||||||
|
el: viewContainer,
|
||||||
|
components: {
|
||||||
|
Plot
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct: openmct,
|
||||||
|
domainObject: overlayPlotObject,
|
||||||
|
composition: openmct.composition.get(overlayPlotObject),
|
||||||
|
path: [overlayPlotObject]
|
||||||
|
},
|
||||||
|
template: '<plot ref="plotComponent"></plot>'
|
||||||
|
});
|
||||||
|
|
||||||
|
return telemetryPromise
|
||||||
|
.then(Vue.nextTick())
|
||||||
|
.then(() => {
|
||||||
|
const configId = openmct.objects.makeKeyString(overlayPlotObject.identifier);
|
||||||
|
config = configStore.get(configId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Renders single Y-axis for the telemetry object", (done) => {
|
||||||
|
config.yAxis.set('displayRange', {
|
||||||
|
min: 10,
|
||||||
|
max: 20
|
||||||
|
});
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
let yAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-y .gl-plot-tick-wrapper");
|
||||||
|
expect(yAxisElement.length).toBe(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -28,6 +28,8 @@ import EventEmitter from "EventEmitter";
|
|||||||
import PlotOptions from "./inspector/PlotOptions.vue";
|
import PlotOptions from "./inspector/PlotOptions.vue";
|
||||||
import PlotConfigurationModel from "./configuration/PlotConfigurationModel";
|
import PlotConfigurationModel from "./configuration/PlotConfigurationModel";
|
||||||
|
|
||||||
|
const TEST_KEY_ID = 'test-key';
|
||||||
|
|
||||||
describe("the plugin", function () {
|
describe("the plugin", function () {
|
||||||
let element;
|
let element;
|
||||||
let child;
|
let child;
|
||||||
@@ -404,6 +406,20 @@ describe("the plugin", function () {
|
|||||||
expect(options[1].value).toBe("Another attribute");
|
expect(options[1].value).toBe("Another attribute");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Updates the Y-axis label when changed", () => {
|
||||||
|
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
|
||||||
|
const config = configStore.get(configId);
|
||||||
|
const yAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-y")[0].__vue__;
|
||||||
|
config.yAxis.seriesCollection.models.forEach((plotSeries) => {
|
||||||
|
expect(plotSeries.model.yKey).toBe('some-key');
|
||||||
|
});
|
||||||
|
|
||||||
|
yAxisElement.$emit('yKeyChanged', TEST_KEY_ID, 1);
|
||||||
|
config.yAxis.seriesCollection.models.forEach((plotSeries) => {
|
||||||
|
expect(plotSeries.model.yKey).toBe(TEST_KEY_ID);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('hides the pause and play controls', () => {
|
it('hides the pause and play controls', () => {
|
||||||
let pauseEl = element.querySelectorAll(".c-button-set .icon-pause");
|
let pauseEl = element.querySelectorAll(".c-button-set .icon-pause");
|
||||||
let playEl = element.querySelectorAll(".c-button-set .icon-arrow-right");
|
let playEl = element.querySelectorAll(".c-button-set .icon-arrow-right");
|
||||||
|
|||||||
@@ -593,6 +593,8 @@ mct-plot {
|
|||||||
.plot-legend-left .gl-plot-legend { margin-right: $interiorMargin; }
|
.plot-legend-left .gl-plot-legend { margin-right: $interiorMargin; }
|
||||||
.plot-legend-right .gl-plot-legend { margin-left: $interiorMargin; }
|
.plot-legend-right .gl-plot-legend { margin-left: $interiorMargin; }
|
||||||
|
|
||||||
|
.gl-plot .plot-yaxis-right.gl-plot-y { margin-left: 100%; }
|
||||||
|
|
||||||
.gl-plot,
|
.gl-plot,
|
||||||
.c-plot {
|
.c-plot {
|
||||||
&.plot-legend-collapsed .plot-wrapper-expanded-legend { display: none; }
|
&.plot-legend-collapsed .plot-wrapper-expanded-legend { display: none; }
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
draggable="true"
|
draggable="true"
|
||||||
@dragstart="emitDragStartEvent"
|
@dragstart="emitDragStartEvent"
|
||||||
@dragenter="onDragenter"
|
@dragenter="onDragenter"
|
||||||
@dragover="onDragover"
|
@dragover.prevent
|
||||||
@dragleave="onDragleave"
|
@dragleave="onDragleave"
|
||||||
@drop="emitDropEvent"
|
@drop="emitDropEvent"
|
||||||
>
|
>
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
v-if="showGrippy"
|
||||||
class="c-elements-pool__grippy c-grippy c-grippy--vertical-drag"
|
class="c-elements-pool__grippy c-grippy c-grippy--vertical-drag"
|
||||||
></span>
|
></span>
|
||||||
<object-label
|
<object-label
|
||||||
@@ -81,6 +82,10 @@ export default {
|
|||||||
},
|
},
|
||||||
allowDrop: {
|
allowDrop: {
|
||||||
type: Boolean
|
type: Boolean
|
||||||
|
},
|
||||||
|
showGrippy: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -93,11 +98,8 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onDragover(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
},
|
|
||||||
emitDropEvent(event) {
|
emitDropEvent(event) {
|
||||||
this.$emit('drop-custom', this.index);
|
this.$emit('drop-custom', event);
|
||||||
this.hover = false;
|
this.hover = false;
|
||||||
},
|
},
|
||||||
emitDragStartEvent(event) {
|
emitDragStartEvent(event) {
|
||||||
|
|||||||
101
src/ui/inspector/ElementItemGroup.vue
Normal file
101
src/ui/inspector/ElementItemGroup.vue
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="c-elements-pool__group"
|
||||||
|
:class="{
|
||||||
|
'hover': hover
|
||||||
|
}"
|
||||||
|
:allow-drop="allowDrop"
|
||||||
|
@dragover.prevent
|
||||||
|
@dragenter="onDragEnter"
|
||||||
|
@dragleave.stop="onDragLeave"
|
||||||
|
@drop="emitDrop"
|
||||||
|
>
|
||||||
|
<ul>
|
||||||
|
<div>
|
||||||
|
<span class="c-elements-pool__grippy c-grippy c-grippy--vertical-drag"></span>
|
||||||
|
<div
|
||||||
|
class="c-tree__item__type-icon c-object-label__type-icon"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="is-status__indicator"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="c-tree__item__name c-object-label__name"
|
||||||
|
aria-label="Element Item Group"
|
||||||
|
>
|
||||||
|
{{ label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<slot></slot>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
parentObject: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
default: () => {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
allowDrop: {
|
||||||
|
type: Boolean
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dragCounter: 0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
hover() {
|
||||||
|
return this.dragCounter > 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
emitDrop(event) {
|
||||||
|
this.dragCounter = 0;
|
||||||
|
this.$emit('drop-group', event);
|
||||||
|
},
|
||||||
|
onDragEnter(event) {
|
||||||
|
this.dragCounter++;
|
||||||
|
},
|
||||||
|
onDragLeave(event) {
|
||||||
|
this.dragCounter--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -65,8 +65,8 @@ import ElementItem from './ElementItem.vue';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
'Search': Search,
|
Search,
|
||||||
'ElementItem': ElementItem
|
ElementItem
|
||||||
},
|
},
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
@@ -56,7 +56,12 @@
|
|||||||
handle="before"
|
handle="before"
|
||||||
label="Elements"
|
label="Elements"
|
||||||
>
|
>
|
||||||
<elements-pool />
|
<plot-elements-pool
|
||||||
|
v-if="isOverlayPlot"
|
||||||
|
/>
|
||||||
|
<elements-pool
|
||||||
|
v-else
|
||||||
|
/>
|
||||||
</pane>
|
</pane>
|
||||||
</multipane>
|
</multipane>
|
||||||
<multipane
|
<multipane
|
||||||
@@ -83,6 +88,7 @@
|
|||||||
import multipane from '../layout/multipane.vue';
|
import multipane from '../layout/multipane.vue';
|
||||||
import pane from '../layout/pane.vue';
|
import pane from '../layout/pane.vue';
|
||||||
import ElementsPool from './ElementsPool.vue';
|
import ElementsPool from './ElementsPool.vue';
|
||||||
|
import PlotElementsPool from './PlotElementsPool.vue';
|
||||||
import Location from './Location.vue';
|
import Location from './Location.vue';
|
||||||
import Properties from './details/Properties.vue';
|
import Properties from './details/Properties.vue';
|
||||||
import ObjectName from './ObjectName.vue';
|
import ObjectName from './ObjectName.vue';
|
||||||
@@ -99,6 +105,7 @@ export default {
|
|||||||
multipane,
|
multipane,
|
||||||
pane,
|
pane,
|
||||||
ElementsPool,
|
ElementsPool,
|
||||||
|
PlotElementsPool,
|
||||||
Properties,
|
Properties,
|
||||||
ObjectName,
|
ObjectName,
|
||||||
Location,
|
Location,
|
||||||
@@ -118,6 +125,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
hasComposition: false,
|
hasComposition: false,
|
||||||
showStyles: false,
|
showStyles: false,
|
||||||
|
isOverlayPlot: false,
|
||||||
tabbedViews: [{
|
tabbedViews: [{
|
||||||
key: '__properties',
|
key: '__properties',
|
||||||
name: 'Properties'
|
name: 'Properties'
|
||||||
@@ -151,6 +159,7 @@ export default {
|
|||||||
let parentObject = selection[0][0].context.item;
|
let parentObject = selection[0][0].context.item;
|
||||||
|
|
||||||
this.hasComposition = Boolean(parentObject && this.openmct.composition.get(parentObject));
|
this.hasComposition = Boolean(parentObject && this.openmct.composition.get(parentObject));
|
||||||
|
this.isOverlayPlot = selection[0][0].context.item.type === 'telemetry.plot.overlay';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
refreshTabs(selection) {
|
refreshTabs(selection) {
|
||||||
|
|||||||
330
src/ui/inspector/PlotElementsPool.vue
Normal file
330
src/ui/inspector/PlotElementsPool.vue
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="c-elements-pool">
|
||||||
|
<Search
|
||||||
|
class="c-elements-pool__search"
|
||||||
|
:value="currentSearch"
|
||||||
|
@input="applySearch"
|
||||||
|
@clear="applySearch"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="c-elements-pool__elements"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
v-if="hasElements"
|
||||||
|
id="inspector-elements-tree"
|
||||||
|
class="c-tree c-elements-pool__tree"
|
||||||
|
>
|
||||||
|
<element-item-group
|
||||||
|
v-for="(yAxis, index) in yAxes"
|
||||||
|
:key="`element-group-yaxis-${yAxis.id}`"
|
||||||
|
:parent-object="parentObject"
|
||||||
|
:allow-drop="allowDrop"
|
||||||
|
:label="`Y Axis ${yAxis.id}`"
|
||||||
|
@drop-group="moveTo($event, 0, yAxis.id)"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
class="js-first-place"
|
||||||
|
@drop="moveTo($event, 0, yAxis.id)"
|
||||||
|
></li>
|
||||||
|
<element-item
|
||||||
|
v-for="(element, elemIndex) in yAxis.elements"
|
||||||
|
:key="element.identifier.key"
|
||||||
|
:index="elemIndex"
|
||||||
|
:element-object="element"
|
||||||
|
:parent-object="parentObject"
|
||||||
|
:allow-drop="allowDrop"
|
||||||
|
:show-grippy="false"
|
||||||
|
@dragstart-custom="moveFrom($event, yAxis.id)"
|
||||||
|
@drop-custom="moveTo($event, index, yAxis.id)"
|
||||||
|
/>
|
||||||
|
<li
|
||||||
|
v-if="yAxis.elements.length > 0"
|
||||||
|
class="js-last-place"
|
||||||
|
@drop="moveTo($event, yAxis.elements.length, yAxis.id)"
|
||||||
|
></li>
|
||||||
|
</element-item-group>
|
||||||
|
</ul>
|
||||||
|
<div
|
||||||
|
v-if="!hasElements"
|
||||||
|
>
|
||||||
|
No contained elements
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import _ from 'lodash';
|
||||||
|
import Search from '../components/search.vue';
|
||||||
|
import ElementItem from './ElementItem.vue';
|
||||||
|
import ElementItemGroup from './ElementItemGroup.vue';
|
||||||
|
import configStore from '../../plugins/plot/configuration/ConfigStore';
|
||||||
|
|
||||||
|
const Y_AXIS_1 = 1;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Search,
|
||||||
|
ElementItemGroup,
|
||||||
|
ElementItem
|
||||||
|
},
|
||||||
|
inject: ['openmct'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
yAxes: [],
|
||||||
|
isEditing: this.openmct.editor.isEditing(),
|
||||||
|
parentObject: undefined,
|
||||||
|
currentSearch: '',
|
||||||
|
selection: [],
|
||||||
|
contextClickTracker: {},
|
||||||
|
allowDrop: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
hasElements() {
|
||||||
|
for (const yAxis of this.yAxes) {
|
||||||
|
if (yAxis.elements.length > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const selection = this.openmct.selection.get();
|
||||||
|
if (selection && selection.length > 0) {
|
||||||
|
this.showSelection(selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.openmct.selection.on('change', this.showSelection);
|
||||||
|
this.openmct.editor.on('isEditing', this.setEditState);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
this.openmct.editor.off('isEditing', this.setEditState);
|
||||||
|
this.openmct.selection.off('change', this.showSelection);
|
||||||
|
|
||||||
|
this.unlistenComposition();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setEditState(isEditing) {
|
||||||
|
this.isEditing = isEditing;
|
||||||
|
this.showSelection(this.openmct.selection.get());
|
||||||
|
},
|
||||||
|
showSelection(selection) {
|
||||||
|
if (_.isEqual(this.selection, selection)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selection = selection;
|
||||||
|
this.elementsCache = {};
|
||||||
|
this.listeners = [];
|
||||||
|
this.parentObject = selection && selection[0] && selection[0][0].context.item;
|
||||||
|
|
||||||
|
this.unlistenComposition();
|
||||||
|
|
||||||
|
if (this.parentObject) {
|
||||||
|
this.setYAxisIds();
|
||||||
|
this.composition = this.openmct.composition.get(this.parentObject);
|
||||||
|
|
||||||
|
if (this.composition) {
|
||||||
|
this.composition.load();
|
||||||
|
this.registerCompositionListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unlistenComposition() {
|
||||||
|
if (this.compositionUnlistener) {
|
||||||
|
this.compositionUnlistener();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
registerCompositionListeners() {
|
||||||
|
this.composition.on('add', this.addElement);
|
||||||
|
this.composition.on('remove', this.removeElement);
|
||||||
|
this.composition.on('reorder', this.reorderElements);
|
||||||
|
|
||||||
|
this.compositionUnlistener = () => {
|
||||||
|
this.composition.off('add', this.addElement);
|
||||||
|
this.composition.off('remove', this.removeElement);
|
||||||
|
this.composition.off('reorder', this.reorderElements);
|
||||||
|
delete this.compositionUnlistener;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
setYAxisIds() {
|
||||||
|
const configId = this.openmct.objects.makeKeyString(this.parentObject.identifier);
|
||||||
|
this.config = configStore.get(configId);
|
||||||
|
this.yAxes.push({
|
||||||
|
id: this.config.yAxis.id,
|
||||||
|
elements: this.parentObject.configuration.series.filter(
|
||||||
|
series => series.yAxisId === this.config.yAxis.id
|
||||||
|
)
|
||||||
|
});
|
||||||
|
if (this.config.additionalYAxes) {
|
||||||
|
this.config.additionalYAxes.forEach(yAxis => {
|
||||||
|
this.yAxes.push({
|
||||||
|
id: yAxis.id,
|
||||||
|
elements: this.parentObject.configuration.series.filter(
|
||||||
|
series => series.yAxisId === yAxis.id
|
||||||
|
)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addElement(element) {
|
||||||
|
// Get the index of the corresponding element in the series list
|
||||||
|
const seriesIndex = this.parentObject.configuration.series.findIndex(
|
||||||
|
series => this.openmct.objects.areIdsEqual(series.identifier, element.identifier)
|
||||||
|
);
|
||||||
|
const keyString = this.openmct.objects.makeKeyString(element.identifier);
|
||||||
|
|
||||||
|
const wasDraggedOntoPlot = this.parentObject.configuration.series[seriesIndex].yAxisId === undefined;
|
||||||
|
const yAxisId = wasDraggedOntoPlot
|
||||||
|
? Y_AXIS_1
|
||||||
|
: this.parentObject.configuration.series[seriesIndex].yAxisId;
|
||||||
|
|
||||||
|
if (wasDraggedOntoPlot) {
|
||||||
|
const insertIndex = this.yAxes[0].elements.length;
|
||||||
|
// Insert the element at the end of the first YAxis bucket
|
||||||
|
this.composition.reorder(seriesIndex, insertIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the element in the cache and set its yAxisId
|
||||||
|
this.elementsCache[keyString] = JSON.parse(JSON.stringify(element));
|
||||||
|
if (this.elementsCache[keyString].yAxisId !== yAxisId) {
|
||||||
|
// Mutate the YAxisId on the domainObject itself
|
||||||
|
this.updateCacheAndMutate(element, yAxisId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.applySearch(this.currentSearch);
|
||||||
|
},
|
||||||
|
reorderElements() {
|
||||||
|
this.applySearch(this.currentSearch);
|
||||||
|
},
|
||||||
|
removeElement(identifier) {
|
||||||
|
const keyString = this.openmct.objects.makeKeyString(identifier);
|
||||||
|
delete this.elementsCache[keyString];
|
||||||
|
this.applySearch(this.currentSearch);
|
||||||
|
},
|
||||||
|
applySearch(input) {
|
||||||
|
this.currentSearch = input;
|
||||||
|
this.yAxes.forEach(yAxis => {
|
||||||
|
yAxis.elements = this.filterForSearchAndAxis(input, yAxis.id);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
filterForSearchAndAxis(input, yAxisId) {
|
||||||
|
return this.parentObject.composition.map((id) =>
|
||||||
|
this.elementsCache[this.openmct.objects.makeKeyString(id)]
|
||||||
|
).filter((element) => {
|
||||||
|
return element !== undefined
|
||||||
|
&& element.name.toLowerCase().search(input) !== -1
|
||||||
|
&& element.yAxisId === yAxisId;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
moveFrom(elementIndex, groupIndex) {
|
||||||
|
this.allowDrop = true;
|
||||||
|
this.moveFromIndex = elementIndex;
|
||||||
|
this.moveFromYAxisId = groupIndex;
|
||||||
|
},
|
||||||
|
moveTo(event, moveToIndex, moveToYAxisId) {
|
||||||
|
// FIXME: If the user starts the drag by clicking outside of the <object-label/> element,
|
||||||
|
// domain object information will not be set on the dataTransfer data. To prevent errors,
|
||||||
|
// we simply short-circuit here if the data is not set.
|
||||||
|
const serializedDomainObject = event.dataTransfer.getData('openmct/composable-domain-object');
|
||||||
|
if (!serializedDomainObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const domainObject = JSON.parse(serializedDomainObject);
|
||||||
|
this.updateCacheAndMutate(domainObject, moveToYAxisId);
|
||||||
|
|
||||||
|
const moveFromIndex = this.moveFromIndex;
|
||||||
|
|
||||||
|
this.moveAndReorderElement(moveFromIndex, moveToIndex, moveToYAxisId);
|
||||||
|
},
|
||||||
|
updateCacheAndMutate(domainObject, yAxisId) {
|
||||||
|
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
|
const index = this.parentObject.configuration.series.findIndex(
|
||||||
|
series => series.identifier.key === domainObject.identifier.key
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle the case of dragging an element directly into the Elements Pool
|
||||||
|
if (!this.elementsCache[keyString]) {
|
||||||
|
// Update the series list locally so our CompositionAdd handler can
|
||||||
|
// take care of the rest.
|
||||||
|
this.parentObject.configuration.series.push({
|
||||||
|
identifier: domainObject.identifier,
|
||||||
|
yAxisId
|
||||||
|
});
|
||||||
|
this.composition.add(domainObject);
|
||||||
|
this.elementsCache[keyString] = JSON.parse(JSON.stringify(domainObject));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.elementsCache[keyString].yAxisId = yAxisId;
|
||||||
|
const shouldMutate = this.parentObject.configuration.series?.[index]?.yAxisId !== yAxisId;
|
||||||
|
if (shouldMutate) {
|
||||||
|
this.openmct.objects.mutate(
|
||||||
|
this.parentObject,
|
||||||
|
`configuration.series[${index}].yAxisId`,
|
||||||
|
yAxisId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
moveAndReorderElement(moveFromIndex, moveToIndex, moveToYAxisId) {
|
||||||
|
if (!this.allowDrop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the corresponding indexes of the from/to yAxes in the yAxes list
|
||||||
|
const moveFromYAxisIndex = this.yAxes.findIndex(yAxis => yAxis.id === this.moveFromYAxisId);
|
||||||
|
const moveToYAxisIndex = this.yAxes.findIndex(yAxis => yAxis.id === moveToYAxisId);
|
||||||
|
|
||||||
|
// Calculate the actual indexes of the elements in the composition array
|
||||||
|
// based on which bucket and index they are being moved from/to.
|
||||||
|
// Then, trigger a composition reorder.
|
||||||
|
for (let yAxisId = 0; yAxisId < moveFromYAxisIndex; yAxisId++) {
|
||||||
|
const lesserYAxisBucketLength = this.yAxes[yAxisId].elements.length;
|
||||||
|
// Add the lengths of preceding buckets to calculate the actual 'from' index
|
||||||
|
moveFromIndex = moveFromIndex + lesserYAxisBucketLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let yAxisId = 0; yAxisId < moveToYAxisIndex; yAxisId++) {
|
||||||
|
const greaterYAxisBucketLength = this.yAxes[yAxisId].elements.length;
|
||||||
|
// Add the lengths of subsequent buckets to calculate the actual 'to' index
|
||||||
|
moveToIndex = moveToIndex + greaterYAxisBucketLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust the index by 1 if we're moving from one bucket to another
|
||||||
|
if (this.moveFromYAxisId !== moveToYAxisId && moveToIndex > 0) {
|
||||||
|
moveToIndex--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reorder the composition array according to the calculated indexes
|
||||||
|
this.composition.reorder(moveFromIndex, moveToIndex);
|
||||||
|
|
||||||
|
this.allowDrop = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -21,6 +21,11 @@
|
|||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__group {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
&__elements {
|
&__elements {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|||||||
Reference in New Issue
Block a user