Merge branch 'master' of https://github.com/nasa/openmct into eslint_update

This commit is contained in:
Hill, John (ARC-TI)[KBR Wyle Services, LLC] 2024-07-23 07:35:43 -07:00
commit c213952f42
20 changed files with 461 additions and 284 deletions

View File

@ -0,0 +1,69 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, 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 percySnapshot from '@percy/playwright';
import fs from 'fs';
import { createDomainObjectWithDefaults, createPlanFromJSON } from '../../appActions.js';
import { scanForA11yViolations, test } from '../../avpFixtures.js';
import { waitForAnimations } from '../../baseFixtures.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
import { setBoundsToSpanAllActivities } from '../../helper/planningUtils.js';
const examplePlanSmall2 = JSON.parse(
fs.readFileSync(new URL('../../test-data/examplePlans/ExamplePlan_Small2.json', import.meta.url))
);
test.describe('Visual - Time Strip @a11y', () => {
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
});
test('Time Strip View', async ({ page, theme }) => {
const timeStrip = await createDomainObjectWithDefaults(page, {
type: 'Time Strip',
name: 'Time Strip Visual Test'
});
await createPlanFromJSON(page, {
json: examplePlanSmall2,
parent: timeStrip.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
parent: timeStrip.uuid
});
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
//This will indirectly modify the url such that the SWG is not rendered
await setBoundsToSpanAllActivities(page, examplePlanSmall2, timeStrip.url);
//TODO Find a way to set the "now" activity line
//This will stabilize the state of the test and allow the SWG to render as empty
await waitForAnimations(page.getByLabel('Plot Canvas'));
await percySnapshot(page, `Time Strip View (theme: ${theme}) - With SWG and Plan`);
});
});
test.afterEach(async ({ page }, testInfo) => {
await scanForA11yViolations(page, testInfo.title);
});

View File

@ -74,7 +74,7 @@ export default {
provide() { provide() {
return { return {
domainObject: this.telemetryObject, domainObject: this.telemetryObject,
path: this.path, objectPath: this.path,
renderWhenVisible: this.renderWhenVisible renderWhenVisible: this.renderWhenVisible
}; };
}, },

View File

@ -25,7 +25,7 @@
{{ heading }} {{ heading }}
</template> </template>
<template #object> <template #object>
<svg :height="height" :width="width"> <svg :height="height" :width="svgWidth" :style="alignmentStyle">
<symbol id="activity-bar-bg" :height="rowHeight" width="2" preserveAspectRatio="none"> <symbol id="activity-bar-bg" :height="rowHeight" width="2" preserveAspectRatio="none">
<rect x="0" y="0" width="100%" height="100%" fill="currentColor" /> <rect x="0" y="0" width="100%" height="100%" fill="currentColor" />
<line <line
@ -92,13 +92,19 @@
</template> </template>
<script> <script>
const AXES_PADDING = 20;
import { inject } from 'vue';
import SwimLane from '@/ui/components/swim-lane/SwimLane.vue'; import SwimLane from '@/ui/components/swim-lane/SwimLane.vue';
import { useAlignment } from '../../../ui/composables/alignmentContext.js';
export default { export default {
components: { components: {
SwimLane SwimLane
}, },
inject: ['openmct', 'domainObject'], inject: ['openmct', 'domainObject', 'path'],
props: { props: {
activities: { activities: {
type: Array, type: Array,
@ -136,11 +142,46 @@ export default {
} }
}, },
emits: ['activity-selected'], emits: ['activity-selected'],
setup() {
const domainObject = inject('domainObject');
const path = inject('path');
const openmct = inject('openmct');
const { alignment: alignmentData, reset: resetAlignment } = useAlignment(
domainObject,
path,
openmct
);
return { alignmentData, resetAlignment };
},
data() { data() {
return { return {
lineHeight: 10 lineHeight: 10
}; };
}, },
computed: {
alignmentStyle() {
let leftOffset = 0;
if (this.alignmentData.leftWidth) {
leftOffset = this.alignmentData.multiple ? 2 * AXES_PADDING : AXES_PADDING;
}
return {
marginLeft: `${this.alignmentData.leftWidth + leftOffset}px`
};
},
svgWidth() {
// Reduce the width by left axis width, then take off the right yaxis width as well
let leftOffset = 0;
if (this.alignmentData.leftWidth) {
leftOffset = this.alignmentData.multiple ? 2 * AXES_PADDING : AXES_PADDING;
}
const rightOffset = this.alignmentData.rightWidth ? AXES_PADDING : 0;
return (
this.width -
(this.alignmentData.leftWidth + leftOffset + this.alignmentData.rightWidth + rightOffset)
);
}
},
methods: { methods: {
setSelectionForActivity(activity, event) { setSelectionForActivity(activity, event) {
event.stopPropagation(); event.stopPropagation();

View File

@ -33,14 +33,9 @@
v-for="(yAxis, index) in yAxesIds" v-for="(yAxis, index) in yAxesIds"
:id="yAxis.id" :id="yAxis.id"
:key="`yAxis-${yAxis.id}-${index}`" :key="`yAxis-${yAxis.id}-${index}`"
:has-multiple-left-axes="hasMultipleLeftAxes"
:position="yAxis.id > 2 ? 'right' : 'left'" :position="yAxis.id > 2 ? 'right' : 'left'"
:class="{ 'plot-yaxis-right': yAxis.id > 2 }" :class="{ 'plot-yaxis-right': yAxis.id > 2 }"
:tick-width="yAxis.tickWidth"
:used-tick-width="plotFirstLeftTickWidth"
:plot-left-tick-width="yAxis.id > 2 ? yAxis.tickWidth : plotLeftTickWidth"
@y-key-changed="setYAxisKey" @y-key-changed="setYAxisKey"
@plot-y-tick-width="onYTickWidthChange"
@toggle-axis-visibility="toggleSeriesForYAxis" @toggle-axis-visibility="toggleSeriesForYAxis"
/> />
</div> </div>
@ -66,7 +61,6 @@
:axis-type="'yAxis'" :axis-type="'yAxis'"
:position="'bottom'" :position="'bottom'"
:axis-id="yAxis.id" :axis-id="yAxis.id"
@plot-tick-width="onYTickWidthChange"
/> />
<div <div
@ -191,9 +185,10 @@
import Flatbush from 'flatbush'; import Flatbush from 'flatbush';
import _ from 'lodash'; import _ from 'lodash';
import { useEventBus } from 'utils/useEventBus'; import { useEventBus } from 'utils/useEventBus';
import { toRaw } from 'vue'; import { inject, toRaw } from 'vue';
import { MODES } from '../../api/time/constants'; import { MODES } from '../../api/time/constants';
import { useAlignment } from '../../ui/composables/alignmentContext.js';
import TagEditorClassNames from '../inspectorViews/annotations/tags/TagEditorClassNames.js'; import TagEditorClassNames from '../inspectorViews/annotations/tags/TagEditorClassNames.js';
import XAxis from './axis/XAxis.vue'; import XAxis from './axis/XAxis.vue';
import YAxis from './axis/YAxis.vue'; import YAxis from './axis/YAxis.vue';
@ -214,7 +209,7 @@ export default {
MctTicks, MctTicks,
MctChart MctChart
}, },
inject: ['openmct', 'domainObject', 'path', 'renderWhenVisible'], inject: ['openmct', 'domainObject', 'objectPath', 'renderWhenVisible'],
props: { props: {
options: { options: {
type: Object, type: Object,
@ -236,16 +231,6 @@ export default {
return false; return false;
} }
}, },
parentYTickWidth: {
type: Object,
default() {
return {
leftTickWidth: 0,
rightTickWidth: 0,
hasMultipleLeftAxes: false
};
}
},
limitLineLabels: { limitLineLabels: {
type: Object, type: Object,
default() { default() {
@ -265,15 +250,26 @@ export default {
'grid-lines', 'grid-lines',
'loading-complete', 'loading-complete',
'loading-updated', 'loading-updated',
'plot-y-tick-width',
'highlights', 'highlights',
'lock-highlight-point', 'lock-highlight-point',
'status-updated' 'status-updated'
], ],
setup() { setup() {
const { EventBus } = useEventBus(); const { EventBus } = useEventBus();
const domainObject = inject('domainObject');
const objectPath = inject('objectPath');
const openmct = inject('openmct');
const { alignment: alignmentData, reset: resetAlignment } = useAlignment(
domainObject,
objectPath,
openmct
);
return { return {
EventBus EventBus,
alignmentData,
resetAlignment
}; };
}, },
data() { data() {
@ -305,15 +301,16 @@ export default {
}, },
computed: { computed: {
xAxisStyle() { xAxisStyle() {
const rightAxis = this.yAxesIds.find((yAxis) => yAxis.id > 2); let leftOffset = 0;
const leftOffset = this.hasMultipleLeftAxes ? 2 * AXES_PADDING : AXES_PADDING; if (this.alignmentData.leftWidth) {
leftOffset = this.alignmentData.multiple ? 2 * AXES_PADDING : AXES_PADDING;
}
let style = { let style = {
left: `${this.plotLeftTickWidth + leftOffset}px` left: `${this.alignmentData.leftWidth + leftOffset}px`
}; };
const parentRightAxisWidth = this.parentYTickWidth.rightTickWidth;
if (parentRightAxisWidth || rightAxis) { if (this.alignmentData.rightWidth) {
style.right = `${(parentRightAxisWidth || rightAxis.tickWidth) + AXES_PADDING}px`; style.right = `${this.alignmentData.rightWidth + AXES_PADDING}px`;
} }
return style; return style;
@ -321,20 +318,16 @@ export default {
yAxesIds() { yAxesIds() {
return this.yAxes.filter((yAxis) => yAxis.seriesCount > 0); return this.yAxes.filter((yAxis) => yAxis.seriesCount > 0);
}, },
hasMultipleLeftAxes() {
return (
this.parentYTickWidth.hasMultipleLeftAxes ||
this.yAxes.filter((yAxis) => yAxis.seriesCount > 0 && yAxis.id <= 2).length > 1
);
},
isNestedWithinAStackedPlot() { isNestedWithinAStackedPlot() {
const isNavigatedObject = this.openmct.router.isNavigatedObject( const isNavigatedObject = this.openmct.router.isNavigatedObject(
[this.domainObject].concat(this.path) [this.domainObject].concat(this.objectPath)
); );
return ( return (
!isNavigatedObject && !isNavigatedObject &&
this.path.find((pathObject, pathObjIndex) => pathObject.type === 'telemetry.plot.stacked') this.objectPath.find(
(pathObject, pathObjIndex) => pathObject.type === 'telemetry.plot.stacked'
)
); );
}, },
isFrozen() { isFrozen() {
@ -344,24 +337,6 @@ export default {
// only allow annotations viewing/editing if plot is paused or in fixed time mode // only allow annotations viewing/editing if plot is paused or in fixed time mode
return this.isFrozen || !this.isRealTime; return this.isFrozen || !this.isRealTime;
}, },
plotFirstLeftTickWidth() {
const firstYAxis = this.yAxes.find((yAxis) => yAxis.id === 1);
return firstYAxis ? firstYAxis.tickWidth : 0;
},
plotLeftTickWidth() {
let leftTickWidth = 0;
this.yAxes.forEach((yAxis) => {
if (yAxis.id > 2) {
return;
}
leftTickWidth = leftTickWidth + yAxis.tickWidth;
});
const parentLeftTickWidth = this.parentYTickWidth.leftTickWidth;
return parentLeftTickWidth || leftTickWidth;
},
seriesDataLoaded() { seriesDataLoaded() {
return this.pending === 0 && this.loaded; return this.pending === 0 && this.loaded;
} }
@ -394,8 +369,7 @@ export default {
this.yAxes = [ this.yAxes = [
{ {
id: this.config.yAxis.id, id: this.config.yAxis.id,
seriesCount: 0, seriesCount: 0
tickWidth: 0
} }
]; ];
if (this.config.additionalYAxes) { if (this.config.additionalYAxes) {
@ -403,8 +377,7 @@ export default {
this.config.additionalYAxes.map((yAxis) => { this.config.additionalYAxes.map((yAxis) => {
return { return {
id: yAxis.id, id: yAxis.id,
seriesCount: 0, seriesCount: 0
tickWidth: 0
}; };
}) })
); );
@ -438,6 +411,7 @@ export default {
}); });
}, },
beforeUnmount() { beforeUnmount() {
this.resetAlignment();
this.abortController.abort(); this.abortController.abort();
this.openmct.selection.off('change', this.updateSelection); this.openmct.selection.off('change', this.updateSelection);
document.removeEventListener('keydown', this.handleKeyDown); document.removeEventListener('keydown', this.handleKeyDown);
@ -562,7 +536,7 @@ export default {
}, },
setTimeContext() { setTimeContext() {
this.stopFollowingTimeContext(); this.stopFollowingTimeContext();
this.timeContext = this.openmct.time.getContextForView(this.path); this.timeContext = this.openmct.time.getContextForView(this.objectPath);
this.followTimeContext(); this.followTimeContext();
}, },
followTimeContext() { followTimeContext() {
@ -618,14 +592,6 @@ export default {
updateTicksAndSeriesForYAxis(newAxisId, oldAxisId) { updateTicksAndSeriesForYAxis(newAxisId, oldAxisId) {
this.updateAxisUsageCount(oldAxisId, -1); this.updateAxisUsageCount(oldAxisId, -1);
this.updateAxisUsageCount(newAxisId, 1); this.updateAxisUsageCount(newAxisId, 1);
const foundYAxis = this.yAxes.find((yAxis) => yAxis.id === oldAxisId);
if (foundYAxis.seriesCount === 0) {
this.onYTickWidthChange({
width: foundYAxis.tickWidth,
yAxisId: foundYAxis.id
});
}
}, },
updateAxisUsageCount(yAxisId, updateCountBy) { updateAxisUsageCount(yAxisId, updateCountBy) {
@ -1032,49 +998,6 @@ export default {
} }
}, },
/**
* Aggregate widths of all left and right y axes and send them up to any parent plots
* @param {Object} tickWidthWithYAxisId - the width and yAxisId of the tick bar
* @param fromDifferentObject
*/
onYTickWidthChange(tickWidthWithYAxisId, fromDifferentObject) {
const { width, yAxisId } = tickWidthWithYAxisId;
if (yAxisId) {
const index = this.yAxes.findIndex((yAxis) => yAxis.id === yAxisId);
if (fromDifferentObject) {
// Always accept tick width if it comes from a different object.
this.yAxes[index].tickWidth = width;
} else {
// Otherwise, only accept tick with if it's larger.
const newWidth = Math.max(width, this.yAxes[index].tickWidth);
if (width !== this.yAxes[index].tickWidth) {
this.yAxes[index].tickWidth = newWidth;
}
}
const id = this.openmct.objects.makeKeyString(this.domainObject.identifier);
const leftTickWidth = this.yAxes
.filter((yAxis) => yAxis.id < 3)
.reduce((previous, current) => {
return previous + current.tickWidth;
}, 0);
const rightTickWidth = this.yAxes
.filter((yAxis) => yAxis.id > 2)
.reduce((previous, current) => {
return previous + current.tickWidth;
}, 0);
this.$emit(
'plot-y-tick-width',
{
hasMultipleLeftAxes: this.hasMultipleLeftAxes,
leftTickWidth,
rightTickWidth
},
id
);
}
},
toggleSeriesForYAxis({ id, visible }) { toggleSeriesForYAxis({ id, visible }) {
//if toggling to visible, re-fetch the data for the series that are part of this y Axis //if toggling to visible, re-fetch the data for the series that are part of this y Axis
if (visible === true) { if (visible === true) {
@ -1324,7 +1247,7 @@ export default {
item: this.domainObject item: this.domainObject
} }
}); });
this.path.forEach((pathObject, index) => { this.objectPath.forEach((pathObject, index) => {
selection.push({ selection.push({
element: this.openmct.layout.$refs.browseObject.$el, element: this.openmct.layout.$refs.browseObject.$el,
context: { context: {

View File

@ -73,6 +73,9 @@
</template> </template>
<script> <script>
import { inject } from 'vue';
import { useAlignment } from '../../ui/composables/alignmentContext.js';
import configStore from './configuration/ConfigStore.js'; import configStore from './configuration/ConfigStore.js';
import eventHelpers from './lib/eventHelpers.js'; import eventHelpers from './lib/eventHelpers.js';
import { getFormattedTicks, getLogTicks, ticks } from './tickUtils.js'; import { getFormattedTicks, getLogTicks, ticks } from './tickUtils.js';
@ -80,7 +83,7 @@ import { getFormattedTicks, getLogTicks, ticks } from './tickUtils.js';
const SECONDARY_TICK_NUMBER = 2; const SECONDARY_TICK_NUMBER = 2;
export default { export default {
inject: ['openmct', 'domainObject'], inject: ['openmct', 'domainObject', 'objectPath'],
props: { props: {
axisType: { axisType: {
type: String, type: String,
@ -111,6 +114,18 @@ export default {
} }
}, },
emits: ['plot-tick-width'], emits: ['plot-tick-width'],
setup() {
const domainObject = inject('domainObject');
const objectPath = inject('objectPath');
const openmct = inject('openmct');
const { update: updateAlignment, remove: removeAlignment } = useAlignment(
domainObject,
objectPath,
openmct
);
return { updateAlignment, removeAlignment };
},
data() { data() {
return { return {
ticks: [] ticks: []
@ -132,6 +147,10 @@ export default {
this.updateTicks(); this.updateTicks();
}, },
beforeUnmount() { beforeUnmount() {
this.removeAlignment({
yAxisId: this.axisId,
updateObjectPath: this.objectPath
});
this.stopListening(); this.stopListening();
}, },
methods: { methods: {
@ -279,6 +298,14 @@ export default {
width: tickWidth, width: tickWidth,
yAxisId: this.axisType === 'yAxis' ? this.axisId : '' yAxisId: this.axisType === 'yAxis' ? this.axisId : ''
}); });
if (this.axisType === 'yAxis') {
this.updateAlignment({
width: tickWidth,
yAxisId: this.axisId,
updateObjectPath: this.objectPath
});
}
this.shouldCheckWidth = false; this.shouldCheckWidth = false;
} }
} }

View File

@ -45,14 +45,12 @@
:init-cursor-guide="cursorGuide" :init-cursor-guide="cursorGuide"
:options="options" :options="options"
:limit-line-labels="limitLineLabelsProp" :limit-line-labels="limitLineLabelsProp"
:parent-y-tick-width="parentYTickWidth"
:color-palette="colorPalette" :color-palette="colorPalette"
@loading-updated="loadingUpdated" @loading-updated="loadingUpdated"
@status-updated="setStatus" @status-updated="setStatus"
@config-loaded="updateReady" @config-loaded="updateReady"
@lock-highlight-point="lockHighlightPointUpdated" @lock-highlight-point="lockHighlightPointUpdated"
@highlights="highlightsUpdated" @highlights="highlightsUpdated"
@plot-y-tick-width="onYTickWidthChange"
@cursor-guide="onCursorGuideChange" @cursor-guide="onCursorGuideChange"
@grid-lines="onGridLinesChange" @grid-lines="onGridLinesChange"
> >
@ -85,7 +83,7 @@ export default {
PlotLegend PlotLegend
}, },
mixins: [stalenessMixin], mixins: [stalenessMixin],
inject: ['openmct', 'domainObject'], inject: ['openmct', 'domainObject', 'objectPath'],
props: { props: {
options: { options: {
type: Object, type: Object,
@ -119,16 +117,6 @@ export default {
return undefined; return undefined;
} }
}, },
parentYTickWidth: {
type: Object,
default() {
return {
leftTickWidth: 0,
rightTickWidth: 0,
hasMultipleLeftAxes: false
};
}
},
hideLegend: { hideLegend: {
type: Boolean, type: Boolean,
default() { default() {
@ -142,7 +130,6 @@ export default {
'grid-lines', 'grid-lines',
'highlights', 'highlights',
'config-loaded', 'config-loaded',
'plot-y-tick-width',
'cursor-guide' 'cursor-guide'
], ],
data() { data() {
@ -261,9 +248,6 @@ export default {
this.configReady = ready; this.configReady = ready;
this.$emit('config-loaded', ...arguments); this.$emit('config-loaded', ...arguments);
}, },
onYTickWidthChange() {
this.$emit('plot-y-tick-width', ...arguments);
},
onCursorGuideChange() { onCursorGuideChange() {
this.$emit('cursor-guide', ...arguments); this.$emit('cursor-guide', ...arguments);
}, },

View File

@ -76,7 +76,7 @@ export default function PlotViewProvider(openmct) {
provide: { provide: {
openmct, openmct,
domainObject, domainObject,
path: objectPath, objectPath,
renderWhenVisible renderWhenVisible
}, },
data() { data() {

View File

@ -71,6 +71,9 @@
</template> </template>
<script> <script>
import { inject } from 'vue';
import { useAlignment } from '../../../ui/composables/alignmentContext.js';
import configStore from '../configuration/ConfigStore.js'; import configStore from '../configuration/ConfigStore.js';
import eventHelpers from '../lib/eventHelpers.js'; import eventHelpers from '../lib/eventHelpers.js';
import MctTicks from '../MctTicks.vue'; import MctTicks from '../MctTicks.vue';
@ -81,7 +84,7 @@ export default {
components: { components: {
MctTicks MctTicks
}, },
inject: ['openmct', 'domainObject'], inject: ['openmct', 'domainObject', 'objectPath'],
props: { props: {
id: { id: {
type: Number, type: Number,
@ -89,30 +92,6 @@ export default {
return 1; return 1;
} }
}, },
tickWidth: {
type: Number,
default() {
return 0;
}
},
plotLeftTickWidth: {
type: Number,
default() {
return 0;
}
},
usedTickWidth: {
type: Number,
default() {
return 0;
}
},
hasMultipleLeftAxes: {
type: Boolean,
default() {
return false;
}
},
position: { position: {
type: String, type: String,
default() { default() {
@ -120,7 +99,15 @@ export default {
} }
} }
}, },
emits: ['plot-y-tick-width', 'toggle-axis-visibility', 'y-key-changed'], emits: ['toggle-axis-visibility', 'y-key-changed'],
setup() {
const domainObject = inject('domainObject');
const objectPath = inject('objectPath');
const openmct = inject('openmct');
const { alignment: alignmentData } = useAlignment(domainObject, objectPath, openmct);
return { alignmentData };
},
data() { data() {
return { return {
yAxisLabel: 'none', yAxisLabel: 'none',
@ -131,7 +118,8 @@ export default {
mainYAxisId: null, mainYAxisId: null,
hasAdditionalYAxes: false, hasAdditionalYAxes: false,
seriesColors: [], seriesColors: [],
visible: true visible: true,
selfTickWidth: 0
}; };
}, },
computed: { computed: {
@ -143,19 +131,20 @@ export default {
}, },
yAxisStyle() { yAxisStyle() {
let style = { let style = {
width: `${this.tickWidth + AXIS_PADDING}px` width: `${this.selfTickWidth + AXIS_PADDING}px`
}; };
const multipleAxesPadding = this.hasMultipleLeftAxes ? AXIS_PADDING : 0; const multipleAxesPadding = this.alignmentData.multiple ? AXIS_PADDING : 0;
if (this.position === 'right') { if (this.position === 'right') {
style.left = `-${this.tickWidth + AXIS_PADDING}px`; style.left = `-${this.selfTickWidth + AXIS_PADDING}px`;
} else { } else {
const thisIsTheSecondLeftAxis = this.id - 1 > 0; const thisIsTheSecondLeftAxis = this.id - 1 > 0;
if (this.hasMultipleLeftAxes && thisIsTheSecondLeftAxis) { if (this.alignmentData.multiple && thisIsTheSecondLeftAxis) {
style.left = `${this.plotLeftTickWidth - this.usedTickWidth - this.tickWidth}px`; const otherAxisWidth = this.alignmentData.leftWidth - this.selfTickWidth;
style.left = `${this.alignmentData.leftWidth - otherAxisWidth - this.selfTickWidth}px`;
style['border-right'] = `1px solid`; style['border-right'] = `1px solid`;
} else { } else {
style.left = `${this.plotLeftTickWidth - this.tickWidth + multipleAxesPadding}px`; style.left = `${this.alignmentData.leftWidth - this.selfTickWidth + multipleAxesPadding}px`;
} }
} }
@ -265,10 +254,7 @@ export default {
} }
}, },
onTickWidthChange(data) { onTickWidthChange(data) {
this.$emit('plot-y-tick-width', { this.selfTickWidth = data.width;
width: data.width,
yAxisId: this.id
});
}, },
toggleSeriesVisibility() { toggleSeriesVisibility() {
this.visible = !this.visible; this.visible = !this.visible;

View File

@ -111,7 +111,7 @@ const HANDLED_ATTRIBUTES = {
export default { export default {
components: { LimitLine, LimitLabel }, components: { LimitLine, LimitLabel },
inject: ['openmct', 'domainObject', 'path', 'renderWhenVisible'], inject: ['openmct', 'domainObject', 'objectPath', 'renderWhenVisible'],
props: { props: {
rectangles: { rectangles: {
type: Array, type: Array,

View File

@ -58,7 +58,7 @@ export default function OverlayPlotViewProvider(openmct) {
provide: { provide: {
openmct, openmct,
domainObject, domainObject,
path: objectPath, objectPath,
renderWhenVisible renderWhenVisible
}, },
data() { data() {

View File

@ -315,7 +315,7 @@ describe('the plugin', function () {
openmct, openmct,
domainObject: overlayPlotObject, domainObject: overlayPlotObject,
composition, composition,
path: [overlayPlotObject], objectPath: [overlayPlotObject],
renderWhenVisible renderWhenVisible
}, },
template: '<plot ref="plotComponent"></plot>' template: '<plot ref="plotComponent"></plot>'
@ -507,7 +507,7 @@ describe('the plugin', function () {
openmct: openmct, openmct: openmct,
domainObject: overlayPlotObject, domainObject: overlayPlotObject,
composition, composition,
path: [overlayPlotObject], objectPath: [overlayPlotObject],
renderWhenVisible renderWhenVisible
}, },
template: '<plot ref="plotComponent"></plot>' template: '<plot ref="plotComponent"></plot>'

View File

@ -48,9 +48,7 @@
:color-palette="colorPalette" :color-palette="colorPalette"
:cursor-guide="cursorGuide" :cursor-guide="cursorGuide"
:show-limit-line-labels="showLimitLineLabels" :show-limit-line-labels="showLimitLineLabels"
:parent-y-tick-width="maxTickWidth"
:hide-legend="showLegendsForChildren === false" :hide-legend="showLegendsForChildren === false"
@plot-y-tick-width="onYTickWidthChange"
@loading-updated="loadingUpdated" @loading-updated="loadingUpdated"
@cursor-guide="onCursorGuideChange" @cursor-guide="onCursorGuideChange"
@grid-lines="onGridLinesChange" @grid-lines="onGridLinesChange"
@ -63,9 +61,12 @@
</template> </template>
<script> <script>
import { inject } from 'vue';
import ColorPalette from '@/ui/color/ColorPalette'; import ColorPalette from '@/ui/color/ColorPalette';
import ImageExporter from '../../../exporters/ImageExporter.js'; import ImageExporter from '../../../exporters/ImageExporter.js';
import { useAlignment } from '../../../ui/composables/alignmentContext.js';
import configStore from '../configuration/ConfigStore.js'; import configStore from '../configuration/ConfigStore.js';
import PlotConfigurationModel from '../configuration/PlotConfigurationModel.js'; import PlotConfigurationModel from '../configuration/PlotConfigurationModel.js';
import PlotLegend from '../legend/PlotLegend.vue'; import PlotLegend from '../legend/PlotLegend.vue';
@ -77,7 +78,7 @@ export default {
StackedPlotItem, StackedPlotItem,
PlotLegend PlotLegend
}, },
inject: ['openmct', 'domainObject', 'path', 'renderWhenVisible'], inject: ['openmct', 'domainObject', 'objectPath', 'renderWhenVisible'],
props: { props: {
options: { options: {
type: Object, type: Object,
@ -86,6 +87,18 @@ export default {
} }
} }
}, },
setup() {
const domainObject = inject('domainObject');
const objectPath = inject('objectPath');
const openmct = inject('openmct');
const { alignment: alignmentData, reset: resetAlignment } = useAlignment(
domainObject,
objectPath,
openmct
);
return { alignmentData, resetAlignment };
},
data() { data() {
return { return {
hideExportButtons: false, hideExportButtons: false,
@ -93,7 +106,6 @@ export default {
gridLines: true, gridLines: true,
configLoaded: {}, configLoaded: {},
compositionObjects: [], compositionObjects: [],
tickWidthMap: {},
loaded: false, loaded: false,
lockHighlightPoint: false, lockHighlightPoint: false,
highlights: [], highlights: [],
@ -123,28 +135,6 @@ export default {
} }
return legendExpandedStateClass; return legendExpandedStateClass;
},
/**
* Returns the maximum width of the left and right y axes ticks of this stacked plots children
* @returns {{rightTickWidth: number, leftTickWidth: number, hasMultipleLeftAxes: boolean}}
*/
maxTickWidth() {
const tickWidthValues = Object.values(this.tickWidthMap);
const maxLeftTickWidth = Math.max(
...tickWidthValues.map((tickWidthItem) => tickWidthItem.leftTickWidth)
);
const maxRightTickWidth = Math.max(
...tickWidthValues.map((tickWidthItem) => tickWidthItem.rightTickWidth)
);
const hasMultipleLeftAxes = tickWidthValues.some(
(tickWidthItem) => tickWidthItem.hasMultipleLeftAxes === true
);
return {
leftTickWidth: maxLeftTickWidth,
rightTickWidth: maxRightTickWidth,
hasMultipleLeftAxes
};
} }
}, },
beforeUnmount() { beforeUnmount() {
@ -209,6 +199,7 @@ export default {
} }
}, },
destroy() { destroy() {
this.resetAlignment();
this.composition.off('add', this.addChild); this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild); this.composition.off('remove', this.removeChild);
this.composition.off('reorder', this.compositionReorder); this.composition.off('reorder', this.compositionReorder);
@ -226,11 +217,6 @@ export default {
const id = this.openmct.objects.makeKeyString(child.identifier); const id = this.openmct.objects.makeKeyString(child.identifier);
this.tickWidthMap[id] = {
leftTickWidth: 0,
rightTickWidth: 0
};
this.compositionObjects.push({ this.compositionObjects.push({
object: child, object: child,
keyString: id keyString: id
@ -241,8 +227,6 @@ export default {
removeChild(childIdentifier) { removeChild(childIdentifier) {
const id = this.openmct.objects.makeKeyString(childIdentifier); const id = this.openmct.objects.makeKeyString(childIdentifier);
delete this.tickWidthMap[id];
const childObj = this.compositionObjects.filter((c) => { const childObj = this.compositionObjects.filter((c) => {
const identifier = c.keyString; const identifier = c.keyString;
@ -283,12 +267,8 @@ export default {
}); });
}, },
resetTelemetryAndTicks(domainObject) { resetTelemetry(domainObject) {
this.compositionObjects = []; this.compositionObjects = [];
this.tickWidthMap = {
leftTickWidth: 0,
rightTickWidth: 0
};
}, },
exportJPG() { exportJPG() {
@ -313,19 +293,6 @@ export default {
}.bind(this) }.bind(this)
); );
}, },
/**
* @typedef {Object} PlotYTickData
* @property {number} leftTickWidth the width of the ticks for all the y axes on the left of the plot.
* @property {number} rightTickWidth the width of the ticks for all the y axes on the right of the plot.
* @property {boolean} hasMultipleLeftAxes whether or not there is more than one left y axis.
*/
onYTickWidthChange(data, plotId) {
if (!Object.prototype.hasOwnProperty.call(this.tickWidthMap, plotId)) {
return;
}
this.tickWidthMap[plotId] = data;
},
legendHoverChanged(data) { legendHoverChanged(data) {
this.showLimitLineLabels = data; this.showLimitLineLabels = data;
}, },

View File

@ -27,14 +27,12 @@
:limit-line-labels="showLimitLineLabels" :limit-line-labels="showLimitLineLabels"
:grid-lines="gridLines" :grid-lines="gridLines"
:cursor-guide="cursorGuide" :cursor-guide="cursorGuide"
:parent-y-tick-width="parentYTickWidth"
:options="options" :options="options"
:color-palette="colorPalette" :color-palette="colorPalette"
:class="isStale && 'is-stale'" :class="isStale && 'is-stale'"
@config-loaded="onConfigLoaded" @config-loaded="onConfigLoaded"
@lock-highlight-point="onLockHighlightPointUpdated" @lock-highlight-point="onLockHighlightPointUpdated"
@highlights="onHighlightsUpdated" @highlights="onHighlightsUpdated"
@plot-y-tick-width="onYTickWidthChange"
@cursor-guide="onCursorGuideChange" @cursor-guide="onCursorGuideChange"
@grid-lines="onGridLinesChange" @grid-lines="onGridLinesChange"
/> />
@ -53,7 +51,7 @@ export default {
Plot Plot
}, },
mixins: [conditionalStylesMixin, stalenessMixin], mixins: [conditionalStylesMixin, stalenessMixin],
inject: ['openmct', 'domainObject', 'path', 'renderWhenVisible'], inject: ['openmct', 'domainObject', 'objectPath', 'renderWhenVisible'],
provide() { provide() {
return { return {
openmct: this.openmct, openmct: this.openmct,
@ -97,16 +95,6 @@ export default {
return undefined; return undefined;
} }
}, },
parentYTickWidth: {
type: Object,
default() {
return {
leftTickWidth: 0,
rightTickWidth: 0,
hasMultipleLeftAxes: false
};
}
},
hideLegend: { hideLegend: {
type: Boolean, type: Boolean,
default() { default() {
@ -201,9 +189,6 @@ export default {
onConfigLoaded() { onConfigLoaded() {
this.$emit('config-loaded', ...arguments); this.$emit('config-loaded', ...arguments);
}, },
onYTickWidthChange() {
this.$emit('plot-y-tick-width', ...arguments);
},
onCursorGuideChange() { onCursorGuideChange() {
this.$emit('cursor-guide', ...arguments); this.$emit('cursor-guide', ...arguments);
}, },

View File

@ -60,7 +60,7 @@ export default function StackedPlotViewProvider(openmct) {
provide: { provide: {
openmct, openmct,
domainObject, domainObject,
path: objectPath, objectPath,
renderWhenVisible renderWhenVisible
}, },
data() { data() {

View File

@ -24,7 +24,7 @@ import StyleRuleManager from '@/plugins/condition/StyleRuleManager';
import { STYLE_CONSTANTS } from '@/plugins/condition/utils/constants'; import { STYLE_CONSTANTS } from '@/plugins/condition/utils/constants';
export default { export default {
inject: ['openmct', 'domainObject', 'path'], inject: ['openmct', 'domainObject', 'objectPath'],
data() { data() {
return { return {
objectStyle: undefined objectStyle: undefined

View File

@ -330,7 +330,7 @@ describe('the plugin', function () {
provide: { provide: {
openmct, openmct,
domainObject: stackedPlotObject, domainObject: stackedPlotObject,
path: [stackedPlotObject], objectPath: [stackedPlotObject],
renderWhenVisible renderWhenVisible
}, },
template: '<stacked-plot ref="stackedPlotRef"></stacked-plot>' template: '<stacked-plot ref="stackedPlotRef"></stacked-plot>'

View File

@ -49,10 +49,12 @@
<script> <script>
import _ from 'lodash'; import _ from 'lodash';
import { inject } from 'vue';
import SwimLane from '@/ui/components/swim-lane/SwimLane.vue'; import SwimLane from '@/ui/components/swim-lane/SwimLane.vue';
import TimelineAxis from '../../ui/components/TimeSystemAxis.vue'; import TimelineAxis from '../../ui/components/TimeSystemAxis.vue';
import { useAlignment } from '../../ui/composables/alignmentContext.js';
import { getValidatedData, getValidatedGroups } from '../plan/util.js'; import { getValidatedData, getValidatedGroups } from '../plan/util.js';
import TimelineObjectView from './TimelineObjectView.vue'; import TimelineObjectView from './TimelineObjectView.vue';
@ -69,7 +71,19 @@ export default {
TimelineAxis, TimelineAxis,
SwimLane SwimLane
}, },
inject: ['openmct', 'domainObject', 'composition', 'objectPath'], inject: ['openmct', 'domainObject', 'path', 'composition'],
setup() {
const domainObject = inject('domainObject');
const path = inject('path');
const openmct = inject('openmct');
const { alignment: alignmentData, reset: resetAlignment } = useAlignment(
domainObject,
path,
openmct
);
return { alignmentData, resetAlignment };
},
data() { data() {
return { return {
items: [], items: [],
@ -80,6 +94,7 @@ export default {
}; };
}, },
beforeUnmount() { beforeUnmount() {
this.resetAlignment();
this.composition.off('add', this.addItem); this.composition.off('add', this.addItem);
this.composition.off('remove', this.removeItem); this.composition.off('remove', this.removeItem);
this.composition.off('reorder', this.reorder); this.composition.off('reorder', this.reorder);
@ -105,7 +120,7 @@ export default {
addItem(domainObject) { addItem(domainObject) {
let type = this.openmct.types.get(domainObject.type) || unknownObjectType; let type = this.openmct.types.get(domainObject.type) || unknownObjectType;
let keyString = this.openmct.objects.makeKeyString(domainObject.identifier); let keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
let objectPath = [domainObject].concat(this.objectPath.slice()); let objectPath = [domainObject].concat(this.path.slice());
let rowCount = 0; let rowCount = 0;
if (domainObject.type === 'plan') { if (domainObject.type === 'plan') {
const planData = getValidatedData(domainObject); const planData = getValidatedData(domainObject);
@ -195,7 +210,7 @@ export default {
setTimeContext() { setTimeContext() {
this.stopFollowingTimeContext(); this.stopFollowingTimeContext();
this.timeContext = this.openmct.time.getContextForView(this.objectPath); this.timeContext = this.openmct.time.getContextForView(this.path);
this.getTimeSystems(); this.getTimeSystems();
this.updateViewBounds(); this.updateViewBounds();
this.timeContext.on('boundsChanged', this.updateViewBounds); this.timeContext.on('boundsChanged', this.updateViewBounds);

View File

@ -51,8 +51,8 @@ export default function TimelineViewProvider(openmct) {
provide: { provide: {
openmct, openmct,
domainObject, domainObject,
composition: openmct.composition.get(domainObject), path: objectPath,
objectPath composition: openmct.composition.get(domainObject)
}, },
template: '<timeline-view-layout></timeline-view-layout>' template: '<timeline-view-layout></timeline-view-layout>'
}, },

View File

@ -21,28 +21,32 @@
--> -->
<template> <template>
<div ref="axisHolder" class="c-timesystem-axis"> <div ref="axisHolder" class="c-timesystem-axis">
<div class="nowMarker"><span class="icon-arrow-down"></span></div> <div class="nowMarker" :style="nowMarkerStyle"><span class="icon-arrow-down"></span></div>
<svg :width="svgWidth" :height="svgHeight">
<g class="axis" font-size="1.3em" :transform="axisTransform"></g>
</svg>
</div> </div>
</template> </template>
<script> <script>
const AXES_PADDING = 20;
import { axisTop } from 'd3-axis'; import { axisTop } from 'd3-axis';
import { scaleLinear, scaleUtc } from 'd3-scale'; import { scaleLinear, scaleUtc } from 'd3-scale';
import { select } from 'd3-selection'; import { select } from 'd3-selection';
import { onMounted, ref } from 'vue'; import { inject, onMounted, reactive, ref } from 'vue';
import utcMultiTimeFormat from '@/plugins/timeConductor/utcMultiTimeFormat'; import utcMultiTimeFormat from '@/plugins/timeConductor/utcMultiTimeFormat';
import { useAlignment } from '../composables/alignmentContext';
import { useResizeObserver } from '../composables/resize'; import { useResizeObserver } from '../composables/resize';
//TODO: UI direction needed for the following property values
const PADDING = 1; const PADDING = 1;
const PIXELS_PER_TICK = 100; const PIXELS_PER_TICK = 100;
const PIXELS_PER_TICK_WIDE = 200; const PIXELS_PER_TICK_WIDE = 200;
//This offset needs to be re-considered
export default { export default {
inject: ['openmct', 'domainObject'], inject: ['openmct', 'domainObject', 'path'],
props: { props: {
bounds: { bounds: {
type: Object, type: Object,
@ -67,31 +71,64 @@ export default {
default() { default() {
return 'svg'; return 'svg';
} }
},
offset: {
type: Number,
default() {
return 0;
}
} }
}, },
setup() { setup() {
const axisHolder = ref(null); const axisHolder = ref(null);
const { size: containerSize, startObserving } = useResizeObserver(); const { size: containerSize, startObserving } = useResizeObserver();
const svgWidth = ref(0);
const svgHeight = ref(0);
const axisTransform = ref('translate(0,20)');
const nowMarkerStyle = reactive({
height: '0px',
left: '0px'
});
onMounted(() => { onMounted(() => {
startObserving(axisHolder.value); startObserving(axisHolder.value);
}); });
const domainObject = inject('domainObject');
const objectPath = inject('path');
const openmct = inject('openmct');
const { alignment: alignmentData } = useAlignment(domainObject, objectPath, openmct);
return { return {
axisHolder, axisHolder,
containerSize containerSize,
alignmentData,
svgWidth,
svgHeight,
axisTransform,
nowMarkerStyle,
openmct
}; };
}, },
watch: { watch: {
alignmentData: {
handler() {
let leftOffset = 0;
if (this.alignmentData.leftWidth) {
leftOffset = this.alignmentData.multiple ? 2 * AXES_PADDING : AXES_PADDING;
}
this.axisTransform = `translate(${this.alignmentData.leftWidth + leftOffset}, 20)`;
const rightOffset = this.alignmentData.rightWidth ? AXES_PADDING : 0;
this.alignmentOffset =
this.alignmentData.leftWidth + leftOffset + this.alignmentData.rightWidth + rightOffset;
this.refresh();
},
deep: true
},
bounds(newBounds) { bounds(newBounds) {
this.setDimensions();
this.drawAxis(newBounds, this.timeSystem); this.drawAxis(newBounds, this.timeSystem);
this.updateNowMarker();
}, },
timeSystem(newTimeSystem) { timeSystem(newTimeSystem) {
this.setDimensions();
this.drawAxis(this.bounds, newTimeSystem); this.drawAxis(this.bounds, newTimeSystem);
this.updateNowMarker();
}, },
contentHeight() { contentHeight() {
this.updateNowMarker(); this.updateNowMarker();
@ -109,16 +146,10 @@ export default {
} }
this.container = select(this.axisHolder); this.container = select(this.axisHolder);
this.svgElement = this.container.append('svg:svg'); this.svgElement = this.container.select('svg');
// draw x axis with labels. CSS is used to position them. this.axisElement = this.svgElement.select('g.axis');
this.axisElement = this.svgElement
.append('g')
.attr('class', 'axis')
.attr('font-size', '1.3em')
.attr('transform', 'translate(0,20)');
this.setDimensions(); this.refresh();
this.drawAxis(this.bounds, this.timeSystem);
this.resize(); this.resize();
}, },
unmounted() { unmounted() {
@ -126,33 +157,37 @@ export default {
}, },
methods: { methods: {
resize() { resize() {
if (this.axisHolder.clientWidth !== this.width) { if (this.axisHolder.clientWidth - this.alignmentOffset !== this.width) {
this.refresh();
}
},
refresh() {
this.setDimensions(); this.setDimensions();
this.drawAxis(this.bounds, this.timeSystem); this.drawAxis(this.bounds, this.timeSystem);
this.updateNowMarker(); this.updateNowMarker();
}
}, },
updateNowMarker() { updateNowMarker() {
let nowMarker = this.$el.querySelector('.nowMarker'); const nowMarker = this.$el.querySelector('.nowMarker');
if (nowMarker) { if (nowMarker) {
nowMarker.classList.remove('hidden'); nowMarker.classList.remove('hidden');
nowMarker.style.height = this.contentHeight + 'px'; this.nowMarkerStyle.height = this.contentHeight + 'px';
const nowTimeStamp = this.openmct.time.now(); const nowTimeStamp = this.openmct.time.now();
const now = this.xScale(nowTimeStamp); const now = this.xScale(nowTimeStamp);
nowMarker.style.left = now + this.offset + 'px'; this.nowMarkerStyle.left = `${now + this.alignmentOffset}px`;
if (now > this.width) {
nowMarker.classList.add('hidden');
}
} }
}, },
setDimensions() { setDimensions() {
this.width = this.axisHolder.clientWidth; this.width = this.axisHolder.clientWidth - (this.alignmentOffset ?? 0);
this.offsetWidth = this.width - this.offset;
this.height = Math.round(this.axisHolder.getBoundingClientRect().height); this.height = Math.round(this.axisHolder.getBoundingClientRect().height);
if (this.useSVG) { if (this.useSVG) {
this.svgElement.attr('width', this.width); this.svgWidth = this.width;
this.svgElement.attr('height', this.height); this.svgHeight = this.height;
} else { } else {
this.svgElement.attr('height', 50); this.svgHeight = 50;
} }
}, },
drawAxis(bounds, timeSystem) { drawAxis(bounds, timeSystem) {
@ -180,16 +215,16 @@ export default {
this.xScale.domain([bounds.start, bounds.end]); this.xScale.domain([bounds.start, bounds.end]);
} }
this.xScale.range([PADDING, this.offsetWidth - PADDING * 2]); this.xScale.range([PADDING, this.width - PADDING * 2]);
}, },
setAxis() { setAxis() {
this.xAxis = axisTop(this.xScale); this.xAxis = axisTop(this.xScale);
this.xAxis.tickFormat(utcMultiTimeFormat); this.xAxis.tickFormat(utcMultiTimeFormat);
if (this.width > 1800) { if (this.width > 1800) {
this.xAxis.ticks(this.offsetWidth / PIXELS_PER_TICK_WIDE); this.xAxis.ticks(this.width / PIXELS_PER_TICK_WIDE);
} else { } else {
this.xAxis.ticks(this.offsetWidth / PIXELS_PER_TICK); this.xAxis.ticks(this.width / PIXELS_PER_TICK);
} }
} }
} }

View File

@ -0,0 +1,145 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, 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.
*****************************************************************************/
/* eslint-disable func-style */
import { reactive } from 'vue';
/** @type {Map<string, Alignment>} */
const alignmentMap = new Map();
/**
* Manages alignment for multiple y axes given an object path.
* This is a Vue composition API utility function.
* @param {Object} targetObject - The target to attach the event listener to.
* @param {ObjectPath} objectPath - The path of the target object.
* @param {import('../../../openmct.js').OpenMCT} openmct - The open mct API.
* @returns {Object} An object containing alignment data and methods to update, remove, and reset alignment.
*/
export function useAlignment(targetObject, objectPath, openmct) {
/**
* Get the alignment key for the given path.
* @returns {string|undefined} The alignment key if found, otherwise undefined.
*/
const getAlignmentKeyForPath = () => {
const keys = Array.from(alignmentMap.keys());
return objectPath
.map((domainObject) => openmct.objects.makeKeyString(domainObject.identifier))
.reverse()
.find((keyString) => keys.includes(keyString));
};
// Use the furthest ancestor's alignment if it exists, otherwise, use your own
let alignmentKey =
getAlignmentKeyForPath() || openmct.objects.makeKeyString(targetObject.identifier);
if (!alignmentMap.has(alignmentKey)) {
alignmentMap.set(
alignmentKey,
reactive({
leftWidth: 0,
rightWidth: 0,
multiple: false,
axes: {}
})
);
}
/**
* Reset any alignment data for the given key.
*/
const reset = () => {
const key = getAlignmentKeyForPath();
if (key && alignmentMap.has(key)) {
alignmentMap.delete(key);
}
};
/**
* Given the axes ids and widths, calculate the max left and right widths and whether or not multiple left axes exist.
*/
const processAlignment = () => {
const alignment = alignmentMap.get(alignmentKey);
const axesKeys = Object.keys(alignment.axes);
const leftAxes = axesKeys.filter((axis) => axis <= 2);
const rightAxes = axesKeys.filter((axis) => axis > 2);
alignment.leftWidth = leftAxes.reduce((sum, axis) => sum + (alignment.axes[axis] || 0), 0);
alignment.rightWidth = rightAxes.reduce((sum, axis) => sum + (alignment.axes[axis] || 0), 0);
alignment.multiple = leftAxes.length > 1;
};
/**
* @typedef {Object} RemoveParams
* @property {number} yAxisId - The ID of the y-axis to remove.
* @property {ObjectPath} [updateObjectPath] - The path of the object to update.
*/
/**
* Unregister y-axis from width calculations.
* @param {RemoveParams} param0 - The object containing yAxisId and updateObjectPath.
*/
const remove = ({ yAxisId, updateObjectPath } = {}) => {
const key = getAlignmentKeyForPath();
if (key) {
const alignment = alignmentMap.get(alignmentKey);
if (alignment.axes[yAxisId] !== undefined) {
delete alignment.axes[yAxisId];
}
processAlignment();
}
};
/**
* @typedef {Object} UpdateParams
* @property {number} width - The width of the y-axis.
* @property {number} yAxisId - The ID of the y-axis to update.
* @property {ObjectPath} [updateObjectPath] - The path of the object to update.
*/
/**
* Update widths of a y axis given the id and path. The path is used to determine which ancestor should hold the alignment.
* @param {UpdateParams} param0 - The object containing width, yAxisId, and updateObjectPath.
*/
const update = ({ width, yAxisId, updateObjectPath } = {}) => {
const key = getAlignmentKeyForPath();
if (key) {
const alignment = alignmentMap.get(alignmentKey);
if (alignment.axes[yAxisId] === undefined || width > alignment.axes[yAxisId]) {
alignment.axes[yAxisId] = width;
}
processAlignment();
}
};
return { alignment: alignmentMap.get(alignmentKey), update, remove, reset };
}
/**
* @typedef {import('../../api/objects/ObjectAPI.js').DomainObject[]} ObjectPath
*/
/**
* @typedef {Object} Alignment
* @property {number} leftWidth - The total width of the left axes.
* @property {number} rightWidth - The total width of the right axes.
* @property {boolean} multiple - Indicates if there are multiple left axes.
* @property {Object.<string, number>} axes - A map of axis IDs to their widths.
*/