mirror of
https://github.com/nasa/openmct.git
synced 2024-12-18 20:57:53 +00:00
* DRAFT - alignment for axes * Use alignmentContext to manage tick widths instead of passing around as props * Remove log statements * Add ability to remove alignment widths for a given y axis * Fix computation of left margin and width of plan when in the timestrip * Remove excess padding when there is no left y axis * Use alignment composable to adjust left margin and width of time system axis * Fix now marker visibility * refactor: use built in `Map()` data structure * refactor: improve readability and conciseness * docs: improve jsdocs * refactor: move jsdoc typedefs to bottom of file * refactor: axis to use vue reactivity * fix: return alignment as an object of refs * alignmentMap needs to be shared state, move it out of the useAlignment composable. * Fix now marker offset * Add new visual test for time strips * update with animation stabilization * Fix failing test due to changed injected property (path -> objectPath) * change injected property from path to objectPath * Fix spelling * Remove unused arguments to function call --------- Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov> Co-authored-by: Hill, John (ARC-TI)[KBR Wyle Services, LLC] <john.c.hill@nasa.gov>
This commit is contained in:
parent
762762945d
commit
1fae0a6ad5
69
e2e/tests/visual-a11y/planning-timestrip.visual.spec.js
Normal file
69
e2e/tests/visual-a11y/planning-timestrip.visual.spec.js
Normal 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);
|
||||
});
|
@ -74,7 +74,7 @@ export default {
|
||||
provide() {
|
||||
return {
|
||||
domainObject: this.telemetryObject,
|
||||
path: this.path,
|
||||
objectPath: this.path,
|
||||
renderWhenVisible: this.renderWhenVisible
|
||||
};
|
||||
},
|
||||
|
@ -25,7 +25,7 @@
|
||||
{{ heading }}
|
||||
</template>
|
||||
<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">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="currentColor" />
|
||||
<line
|
||||
@ -92,13 +92,19 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const AXES_PADDING = 20;
|
||||
|
||||
import { inject } from 'vue';
|
||||
|
||||
import SwimLane from '@/ui/components/swim-lane/SwimLane.vue';
|
||||
|
||||
import { useAlignment } from '../../../ui/composables/alignmentContext.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SwimLane
|
||||
},
|
||||
inject: ['openmct', 'domainObject'],
|
||||
inject: ['openmct', 'domainObject', 'path'],
|
||||
props: {
|
||||
activities: {
|
||||
type: Array,
|
||||
@ -136,11 +142,46 @@ export default {
|
||||
}
|
||||
},
|
||||
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() {
|
||||
return {
|
||||
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: {
|
||||
setSelectionForActivity(activity, event) {
|
||||
event.stopPropagation();
|
||||
|
@ -33,14 +33,9 @@
|
||||
v-for="(yAxis, index) in yAxesIds"
|
||||
:id="yAxis.id"
|
||||
:key="`yAxis-${yAxis.id}-${index}`"
|
||||
:has-multiple-left-axes="hasMultipleLeftAxes"
|
||||
:position="yAxis.id > 2 ? 'right' : 'left'"
|
||||
: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"
|
||||
@plot-y-tick-width="onYTickWidthChange"
|
||||
@toggle-axis-visibility="toggleSeriesForYAxis"
|
||||
/>
|
||||
</div>
|
||||
@ -66,7 +61,6 @@
|
||||
:axis-type="'yAxis'"
|
||||
:position="'bottom'"
|
||||
:axis-id="yAxis.id"
|
||||
@plot-tick-width="onYTickWidthChange"
|
||||
/>
|
||||
|
||||
<div
|
||||
@ -178,9 +172,10 @@
|
||||
import Flatbush from 'flatbush';
|
||||
import _ from 'lodash';
|
||||
import { useEventBus } from 'utils/useEventBus';
|
||||
import { toRaw } from 'vue';
|
||||
import { inject, toRaw } from 'vue';
|
||||
|
||||
import { MODES } from '../../api/time/constants';
|
||||
import { useAlignment } from '../../ui/composables/alignmentContext.js';
|
||||
import TagEditorClassNames from '../inspectorViews/annotations/tags/TagEditorClassNames.js';
|
||||
import XAxis from './axis/XAxis.vue';
|
||||
import YAxis from './axis/YAxis.vue';
|
||||
@ -201,7 +196,7 @@ export default {
|
||||
MctTicks,
|
||||
MctChart
|
||||
},
|
||||
inject: ['openmct', 'domainObject', 'path', 'renderWhenVisible'],
|
||||
inject: ['openmct', 'domainObject', 'objectPath', 'renderWhenVisible'],
|
||||
props: {
|
||||
options: {
|
||||
type: Object,
|
||||
@ -223,16 +218,6 @@ export default {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
parentYTickWidth: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
leftTickWidth: 0,
|
||||
rightTickWidth: 0,
|
||||
hasMultipleLeftAxes: false
|
||||
};
|
||||
}
|
||||
},
|
||||
limitLineLabels: {
|
||||
type: Object,
|
||||
default() {
|
||||
@ -252,15 +237,26 @@ export default {
|
||||
'grid-lines',
|
||||
'loading-complete',
|
||||
'loading-updated',
|
||||
'plot-y-tick-width',
|
||||
'highlights',
|
||||
'lock-highlight-point',
|
||||
'status-updated'
|
||||
],
|
||||
setup() {
|
||||
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 {
|
||||
EventBus
|
||||
EventBus,
|
||||
alignmentData,
|
||||
resetAlignment
|
||||
};
|
||||
},
|
||||
data() {
|
||||
@ -292,15 +288,16 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
xAxisStyle() {
|
||||
const rightAxis = this.yAxesIds.find((yAxis) => yAxis.id > 2);
|
||||
const leftOffset = this.hasMultipleLeftAxes ? 2 * AXES_PADDING : AXES_PADDING;
|
||||
let leftOffset = 0;
|
||||
if (this.alignmentData.leftWidth) {
|
||||
leftOffset = this.alignmentData.multiple ? 2 * AXES_PADDING : AXES_PADDING;
|
||||
}
|
||||
let style = {
|
||||
left: `${this.plotLeftTickWidth + leftOffset}px`
|
||||
left: `${this.alignmentData.leftWidth + leftOffset}px`
|
||||
};
|
||||
const parentRightAxisWidth = this.parentYTickWidth.rightTickWidth;
|
||||
|
||||
if (parentRightAxisWidth || rightAxis) {
|
||||
style.right = `${(parentRightAxisWidth || rightAxis.tickWidth) + AXES_PADDING}px`;
|
||||
if (this.alignmentData.rightWidth) {
|
||||
style.right = `${this.alignmentData.rightWidth + AXES_PADDING}px`;
|
||||
}
|
||||
|
||||
return style;
|
||||
@ -308,20 +305,16 @@ export default {
|
||||
yAxesIds() {
|
||||
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() {
|
||||
const isNavigatedObject = this.openmct.router.isNavigatedObject(
|
||||
[this.domainObject].concat(this.path)
|
||||
[this.domainObject].concat(this.objectPath)
|
||||
);
|
||||
|
||||
return (
|
||||
!isNavigatedObject &&
|
||||
this.path.find((pathObject, pathObjIndex) => pathObject.type === 'telemetry.plot.stacked')
|
||||
this.objectPath.find(
|
||||
(pathObject, pathObjIndex) => pathObject.type === 'telemetry.plot.stacked'
|
||||
)
|
||||
);
|
||||
},
|
||||
isFrozen() {
|
||||
@ -331,24 +324,6 @@ export default {
|
||||
// only allow annotations viewing/editing if plot is paused or in fixed time mode
|
||||
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() {
|
||||
return this.pending === 0 && this.loaded;
|
||||
}
|
||||
@ -381,8 +356,7 @@ export default {
|
||||
this.yAxes = [
|
||||
{
|
||||
id: this.config.yAxis.id,
|
||||
seriesCount: 0,
|
||||
tickWidth: 0
|
||||
seriesCount: 0
|
||||
}
|
||||
];
|
||||
if (this.config.additionalYAxes) {
|
||||
@ -390,8 +364,7 @@ export default {
|
||||
this.config.additionalYAxes.map((yAxis) => {
|
||||
return {
|
||||
id: yAxis.id,
|
||||
seriesCount: 0,
|
||||
tickWidth: 0
|
||||
seriesCount: 0
|
||||
};
|
||||
})
|
||||
);
|
||||
@ -425,6 +398,7 @@ export default {
|
||||
});
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.resetAlignment();
|
||||
this.abortController.abort();
|
||||
this.openmct.selection.off('change', this.updateSelection);
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
@ -549,7 +523,7 @@ export default {
|
||||
},
|
||||
setTimeContext() {
|
||||
this.stopFollowingTimeContext();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.path);
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
this.followTimeContext();
|
||||
},
|
||||
followTimeContext() {
|
||||
@ -605,14 +579,6 @@ export default {
|
||||
updateTicksAndSeriesForYAxis(newAxisId, oldAxisId) {
|
||||
this.updateAxisUsageCount(oldAxisId, -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) {
|
||||
@ -1019,49 +985,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 }) {
|
||||
//if toggling to visible, re-fetch the data for the series that are part of this y Axis
|
||||
if (visible === true) {
|
||||
@ -1311,7 +1234,7 @@ export default {
|
||||
item: this.domainObject
|
||||
}
|
||||
});
|
||||
this.path.forEach((pathObject, index) => {
|
||||
this.objectPath.forEach((pathObject, index) => {
|
||||
selection.push({
|
||||
element: this.openmct.layout.$refs.browseObject.$el,
|
||||
context: {
|
||||
|
@ -73,6 +73,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { inject } from 'vue';
|
||||
|
||||
import { useAlignment } from '../../ui/composables/alignmentContext.js';
|
||||
import configStore from './configuration/ConfigStore.js';
|
||||
import eventHelpers from './lib/eventHelpers.js';
|
||||
import { getFormattedTicks, getLogTicks, ticks } from './tickUtils.js';
|
||||
@ -80,7 +83,7 @@ import { getFormattedTicks, getLogTicks, ticks } from './tickUtils.js';
|
||||
const SECONDARY_TICK_NUMBER = 2;
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
inject: ['openmct', 'domainObject', 'objectPath'],
|
||||
props: {
|
||||
axisType: {
|
||||
type: String,
|
||||
@ -111,6 +114,18 @@ export default {
|
||||
}
|
||||
},
|
||||
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() {
|
||||
return {
|
||||
ticks: []
|
||||
@ -132,6 +147,10 @@ export default {
|
||||
this.updateTicks();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.removeAlignment({
|
||||
yAxisId: this.axisId,
|
||||
updateObjectPath: this.objectPath
|
||||
});
|
||||
this.stopListening();
|
||||
},
|
||||
methods: {
|
||||
@ -279,6 +298,14 @@ export default {
|
||||
width: tickWidth,
|
||||
yAxisId: this.axisType === 'yAxis' ? this.axisId : ''
|
||||
});
|
||||
if (this.axisType === 'yAxis') {
|
||||
this.updateAlignment({
|
||||
width: tickWidth,
|
||||
yAxisId: this.axisId,
|
||||
updateObjectPath: this.objectPath
|
||||
});
|
||||
}
|
||||
|
||||
this.shouldCheckWidth = false;
|
||||
}
|
||||
}
|
||||
|
@ -45,14 +45,12 @@
|
||||
:init-cursor-guide="cursorGuide"
|
||||
:options="options"
|
||||
:limit-line-labels="limitLineLabelsProp"
|
||||
:parent-y-tick-width="parentYTickWidth"
|
||||
:color-palette="colorPalette"
|
||||
@loading-updated="loadingUpdated"
|
||||
@status-updated="setStatus"
|
||||
@config-loaded="updateReady"
|
||||
@lock-highlight-point="lockHighlightPointUpdated"
|
||||
@highlights="highlightsUpdated"
|
||||
@plot-y-tick-width="onYTickWidthChange"
|
||||
@cursor-guide="onCursorGuideChange"
|
||||
@grid-lines="onGridLinesChange"
|
||||
>
|
||||
@ -85,7 +83,7 @@ export default {
|
||||
PlotLegend
|
||||
},
|
||||
mixins: [stalenessMixin],
|
||||
inject: ['openmct', 'domainObject'],
|
||||
inject: ['openmct', 'domainObject', 'objectPath'],
|
||||
props: {
|
||||
options: {
|
||||
type: Object,
|
||||
@ -119,16 +117,6 @@ export default {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
parentYTickWidth: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
leftTickWidth: 0,
|
||||
rightTickWidth: 0,
|
||||
hasMultipleLeftAxes: false
|
||||
};
|
||||
}
|
||||
},
|
||||
hideLegend: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
@ -142,7 +130,6 @@ export default {
|
||||
'grid-lines',
|
||||
'highlights',
|
||||
'config-loaded',
|
||||
'plot-y-tick-width',
|
||||
'cursor-guide'
|
||||
],
|
||||
data() {
|
||||
@ -261,9 +248,6 @@ export default {
|
||||
this.configReady = ready;
|
||||
this.$emit('config-loaded', ...arguments);
|
||||
},
|
||||
onYTickWidthChange() {
|
||||
this.$emit('plot-y-tick-width', ...arguments);
|
||||
},
|
||||
onCursorGuideChange() {
|
||||
this.$emit('cursor-guide', ...arguments);
|
||||
},
|
||||
|
@ -76,7 +76,7 @@ export default function PlotViewProvider(openmct) {
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
path: objectPath,
|
||||
objectPath,
|
||||
renderWhenVisible
|
||||
},
|
||||
data() {
|
||||
|
@ -71,6 +71,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { inject } from 'vue';
|
||||
|
||||
import { useAlignment } from '../../../ui/composables/alignmentContext.js';
|
||||
import configStore from '../configuration/ConfigStore.js';
|
||||
import eventHelpers from '../lib/eventHelpers.js';
|
||||
import MctTicks from '../MctTicks.vue';
|
||||
@ -81,7 +84,7 @@ export default {
|
||||
components: {
|
||||
MctTicks
|
||||
},
|
||||
inject: ['openmct', 'domainObject'],
|
||||
inject: ['openmct', 'domainObject', 'objectPath'],
|
||||
props: {
|
||||
id: {
|
||||
type: Number,
|
||||
@ -89,30 +92,6 @@ export default {
|
||||
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: {
|
||||
type: String,
|
||||
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() {
|
||||
return {
|
||||
yAxisLabel: 'none',
|
||||
@ -131,7 +118,8 @@ export default {
|
||||
mainYAxisId: null,
|
||||
hasAdditionalYAxes: false,
|
||||
seriesColors: [],
|
||||
visible: true
|
||||
visible: true,
|
||||
selfTickWidth: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -143,19 +131,20 @@ export default {
|
||||
},
|
||||
yAxisStyle() {
|
||||
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') {
|
||||
style.left = `-${this.tickWidth + AXIS_PADDING}px`;
|
||||
style.left = `-${this.selfTickWidth + AXIS_PADDING}px`;
|
||||
} else {
|
||||
const thisIsTheSecondLeftAxis = this.id - 1 > 0;
|
||||
if (this.hasMultipleLeftAxes && thisIsTheSecondLeftAxis) {
|
||||
style.left = `${this.plotLeftTickWidth - this.usedTickWidth - this.tickWidth}px`;
|
||||
if (this.alignmentData.multiple && thisIsTheSecondLeftAxis) {
|
||||
const otherAxisWidth = this.alignmentData.leftWidth - this.selfTickWidth;
|
||||
style.left = `${this.alignmentData.leftWidth - otherAxisWidth - this.selfTickWidth}px`;
|
||||
style['border-right'] = `1px solid`;
|
||||
} 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) {
|
||||
this.$emit('plot-y-tick-width', {
|
||||
width: data.width,
|
||||
yAxisId: this.id
|
||||
});
|
||||
this.selfTickWidth = data.width;
|
||||
},
|
||||
toggleSeriesVisibility() {
|
||||
this.visible = !this.visible;
|
||||
|
@ -111,7 +111,7 @@ const HANDLED_ATTRIBUTES = {
|
||||
|
||||
export default {
|
||||
components: { LimitLine, LimitLabel },
|
||||
inject: ['openmct', 'domainObject', 'path', 'renderWhenVisible'],
|
||||
inject: ['openmct', 'domainObject', 'objectPath', 'renderWhenVisible'],
|
||||
props: {
|
||||
rectangles: {
|
||||
type: Array,
|
||||
|
@ -58,7 +58,7 @@ export default function OverlayPlotViewProvider(openmct) {
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
path: objectPath,
|
||||
objectPath,
|
||||
renderWhenVisible
|
||||
},
|
||||
data() {
|
||||
|
@ -315,7 +315,7 @@ describe('the plugin', function () {
|
||||
openmct,
|
||||
domainObject: overlayPlotObject,
|
||||
composition,
|
||||
path: [overlayPlotObject],
|
||||
objectPath: [overlayPlotObject],
|
||||
renderWhenVisible
|
||||
},
|
||||
template: '<plot ref="plotComponent"></plot>'
|
||||
@ -507,7 +507,7 @@ describe('the plugin', function () {
|
||||
openmct: openmct,
|
||||
domainObject: overlayPlotObject,
|
||||
composition,
|
||||
path: [overlayPlotObject],
|
||||
objectPath: [overlayPlotObject],
|
||||
renderWhenVisible
|
||||
},
|
||||
template: '<plot ref="plotComponent"></plot>'
|
||||
|
@ -48,9 +48,7 @@
|
||||
:color-palette="colorPalette"
|
||||
:cursor-guide="cursorGuide"
|
||||
:show-limit-line-labels="showLimitLineLabels"
|
||||
:parent-y-tick-width="maxTickWidth"
|
||||
:hide-legend="showLegendsForChildren === false"
|
||||
@plot-y-tick-width="onYTickWidthChange"
|
||||
@loading-updated="loadingUpdated"
|
||||
@cursor-guide="onCursorGuideChange"
|
||||
@grid-lines="onGridLinesChange"
|
||||
@ -63,9 +61,12 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { inject } from 'vue';
|
||||
|
||||
import ColorPalette from '@/ui/color/ColorPalette';
|
||||
|
||||
import ImageExporter from '../../../exporters/ImageExporter.js';
|
||||
import { useAlignment } from '../../../ui/composables/alignmentContext.js';
|
||||
import configStore from '../configuration/ConfigStore.js';
|
||||
import PlotConfigurationModel from '../configuration/PlotConfigurationModel.js';
|
||||
import PlotLegend from '../legend/PlotLegend.vue';
|
||||
@ -77,7 +78,7 @@ export default {
|
||||
StackedPlotItem,
|
||||
PlotLegend
|
||||
},
|
||||
inject: ['openmct', 'domainObject', 'path', 'renderWhenVisible'],
|
||||
inject: ['openmct', 'domainObject', 'objectPath', 'renderWhenVisible'],
|
||||
props: {
|
||||
options: {
|
||||
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() {
|
||||
return {
|
||||
hideExportButtons: false,
|
||||
@ -93,7 +106,6 @@ export default {
|
||||
gridLines: true,
|
||||
configLoaded: {},
|
||||
compositionObjects: [],
|
||||
tickWidthMap: {},
|
||||
loaded: false,
|
||||
lockHighlightPoint: false,
|
||||
highlights: [],
|
||||
@ -123,28 +135,6 @@ export default {
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -209,6 +199,7 @@ export default {
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
this.resetAlignment();
|
||||
this.composition.off('add', this.addChild);
|
||||
this.composition.off('remove', this.removeChild);
|
||||
this.composition.off('reorder', this.compositionReorder);
|
||||
@ -226,11 +217,6 @@ export default {
|
||||
|
||||
const id = this.openmct.objects.makeKeyString(child.identifier);
|
||||
|
||||
this.tickWidthMap[id] = {
|
||||
leftTickWidth: 0,
|
||||
rightTickWidth: 0
|
||||
};
|
||||
|
||||
this.compositionObjects.push({
|
||||
object: child,
|
||||
keyString: id
|
||||
@ -241,8 +227,6 @@ export default {
|
||||
removeChild(childIdentifier) {
|
||||
const id = this.openmct.objects.makeKeyString(childIdentifier);
|
||||
|
||||
delete this.tickWidthMap[id];
|
||||
|
||||
const childObj = this.compositionObjects.filter((c) => {
|
||||
const identifier = c.keyString;
|
||||
|
||||
@ -283,12 +267,8 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
resetTelemetryAndTicks(domainObject) {
|
||||
resetTelemetry(domainObject) {
|
||||
this.compositionObjects = [];
|
||||
this.tickWidthMap = {
|
||||
leftTickWidth: 0,
|
||||
rightTickWidth: 0
|
||||
};
|
||||
},
|
||||
|
||||
exportJPG() {
|
||||
@ -313,19 +293,6 @@ export default {
|
||||
}.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) {
|
||||
this.showLimitLineLabels = data;
|
||||
},
|
||||
|
@ -27,14 +27,12 @@
|
||||
:limit-line-labels="showLimitLineLabels"
|
||||
:grid-lines="gridLines"
|
||||
:cursor-guide="cursorGuide"
|
||||
:parent-y-tick-width="parentYTickWidth"
|
||||
:options="options"
|
||||
:color-palette="colorPalette"
|
||||
:class="isStale && 'is-stale'"
|
||||
@config-loaded="onConfigLoaded"
|
||||
@lock-highlight-point="onLockHighlightPointUpdated"
|
||||
@highlights="onHighlightsUpdated"
|
||||
@plot-y-tick-width="onYTickWidthChange"
|
||||
@cursor-guide="onCursorGuideChange"
|
||||
@grid-lines="onGridLinesChange"
|
||||
/>
|
||||
@ -53,7 +51,7 @@ export default {
|
||||
Plot
|
||||
},
|
||||
mixins: [conditionalStylesMixin, stalenessMixin],
|
||||
inject: ['openmct', 'domainObject', 'path', 'renderWhenVisible'],
|
||||
inject: ['openmct', 'domainObject', 'objectPath', 'renderWhenVisible'],
|
||||
provide() {
|
||||
return {
|
||||
openmct: this.openmct,
|
||||
@ -97,16 +95,6 @@ export default {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
parentYTickWidth: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
leftTickWidth: 0,
|
||||
rightTickWidth: 0,
|
||||
hasMultipleLeftAxes: false
|
||||
};
|
||||
}
|
||||
},
|
||||
hideLegend: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
@ -201,9 +189,6 @@ export default {
|
||||
onConfigLoaded() {
|
||||
this.$emit('config-loaded', ...arguments);
|
||||
},
|
||||
onYTickWidthChange() {
|
||||
this.$emit('plot-y-tick-width', ...arguments);
|
||||
},
|
||||
onCursorGuideChange() {
|
||||
this.$emit('cursor-guide', ...arguments);
|
||||
},
|
||||
|
@ -60,7 +60,7 @@ export default function StackedPlotViewProvider(openmct) {
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
path: objectPath,
|
||||
objectPath,
|
||||
renderWhenVisible
|
||||
},
|
||||
data() {
|
||||
|
@ -24,7 +24,7 @@ import StyleRuleManager from '@/plugins/condition/StyleRuleManager';
|
||||
import { STYLE_CONSTANTS } from '@/plugins/condition/utils/constants';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject', 'path'],
|
||||
inject: ['openmct', 'domainObject', 'objectPath'],
|
||||
data() {
|
||||
return {
|
||||
objectStyle: undefined
|
||||
|
@ -330,7 +330,7 @@ describe('the plugin', function () {
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject: stackedPlotObject,
|
||||
path: [stackedPlotObject],
|
||||
objectPath: [stackedPlotObject],
|
||||
renderWhenVisible
|
||||
},
|
||||
template: '<stacked-plot ref="stackedPlotRef"></stacked-plot>'
|
||||
|
@ -49,10 +49,12 @@
|
||||
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
import { inject } from 'vue';
|
||||
|
||||
import SwimLane from '@/ui/components/swim-lane/SwimLane.vue';
|
||||
|
||||
import TimelineAxis from '../../ui/components/TimeSystemAxis.vue';
|
||||
import { useAlignment } from '../../ui/composables/alignmentContext.js';
|
||||
import { getValidatedData, getValidatedGroups } from '../plan/util.js';
|
||||
import TimelineObjectView from './TimelineObjectView.vue';
|
||||
|
||||
@ -69,7 +71,19 @@ export default {
|
||||
TimelineAxis,
|
||||
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() {
|
||||
return {
|
||||
items: [],
|
||||
@ -80,6 +94,7 @@ export default {
|
||||
};
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.resetAlignment();
|
||||
this.composition.off('add', this.addItem);
|
||||
this.composition.off('remove', this.removeItem);
|
||||
this.composition.off('reorder', this.reorder);
|
||||
@ -105,7 +120,7 @@ export default {
|
||||
addItem(domainObject) {
|
||||
let type = this.openmct.types.get(domainObject.type) || unknownObjectType;
|
||||
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;
|
||||
if (domainObject.type === 'plan') {
|
||||
const planData = getValidatedData(domainObject);
|
||||
@ -195,7 +210,7 @@ export default {
|
||||
setTimeContext() {
|
||||
this.stopFollowingTimeContext();
|
||||
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
this.timeContext = this.openmct.time.getContextForView(this.path);
|
||||
this.getTimeSystems();
|
||||
this.updateViewBounds();
|
||||
this.timeContext.on('boundsChanged', this.updateViewBounds);
|
||||
|
@ -51,8 +51,8 @@ export default function TimelineViewProvider(openmct) {
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
composition: openmct.composition.get(domainObject),
|
||||
objectPath
|
||||
path: objectPath,
|
||||
composition: openmct.composition.get(domainObject)
|
||||
},
|
||||
template: '<timeline-view-layout></timeline-view-layout>'
|
||||
},
|
||||
|
@ -21,28 +21,32 @@
|
||||
-->
|
||||
<template>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const AXES_PADDING = 20;
|
||||
|
||||
import { axisTop } from 'd3-axis';
|
||||
import { scaleLinear, scaleUtc } from 'd3-scale';
|
||||
import { select } from 'd3-selection';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { inject, onMounted, reactive, ref } from 'vue';
|
||||
|
||||
import utcMultiTimeFormat from '@/plugins/timeConductor/utcMultiTimeFormat';
|
||||
|
||||
import { useAlignment } from '../composables/alignmentContext';
|
||||
import { useResizeObserver } from '../composables/resize';
|
||||
|
||||
//TODO: UI direction needed for the following property values
|
||||
const PADDING = 1;
|
||||
const PIXELS_PER_TICK = 100;
|
||||
const PIXELS_PER_TICK_WIDE = 200;
|
||||
//This offset needs to be re-considered
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
inject: ['openmct', 'domainObject', 'path'],
|
||||
props: {
|
||||
bounds: {
|
||||
type: Object,
|
||||
@ -67,31 +71,64 @@ export default {
|
||||
default() {
|
||||
return 'svg';
|
||||
}
|
||||
},
|
||||
offset: {
|
||||
type: Number,
|
||||
default() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const axisHolder = ref(null);
|
||||
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(() => {
|
||||
startObserving(axisHolder.value);
|
||||
});
|
||||
|
||||
const domainObject = inject('domainObject');
|
||||
const objectPath = inject('path');
|
||||
const openmct = inject('openmct');
|
||||
const { alignment: alignmentData } = useAlignment(domainObject, objectPath, openmct);
|
||||
|
||||
return {
|
||||
axisHolder,
|
||||
containerSize
|
||||
containerSize,
|
||||
alignmentData,
|
||||
svgWidth,
|
||||
svgHeight,
|
||||
axisTransform,
|
||||
nowMarkerStyle,
|
||||
openmct
|
||||
};
|
||||
},
|
||||
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) {
|
||||
this.setDimensions();
|
||||
this.drawAxis(newBounds, this.timeSystem);
|
||||
this.updateNowMarker();
|
||||
},
|
||||
timeSystem(newTimeSystem) {
|
||||
this.setDimensions();
|
||||
this.drawAxis(this.bounds, newTimeSystem);
|
||||
this.updateNowMarker();
|
||||
},
|
||||
contentHeight() {
|
||||
this.updateNowMarker();
|
||||
@ -109,16 +146,10 @@ export default {
|
||||
}
|
||||
|
||||
this.container = select(this.axisHolder);
|
||||
this.svgElement = this.container.append('svg:svg');
|
||||
// draw x axis with labels. CSS is used to position them.
|
||||
this.axisElement = this.svgElement
|
||||
.append('g')
|
||||
.attr('class', 'axis')
|
||||
.attr('font-size', '1.3em')
|
||||
.attr('transform', 'translate(0,20)');
|
||||
this.svgElement = this.container.select('svg');
|
||||
this.axisElement = this.svgElement.select('g.axis');
|
||||
|
||||
this.setDimensions();
|
||||
this.drawAxis(this.bounds, this.timeSystem);
|
||||
this.refresh();
|
||||
this.resize();
|
||||
},
|
||||
unmounted() {
|
||||
@ -126,33 +157,37 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
resize() {
|
||||
if (this.axisHolder.clientWidth !== this.width) {
|
||||
this.setDimensions();
|
||||
this.drawAxis(this.bounds, this.timeSystem);
|
||||
this.updateNowMarker();
|
||||
if (this.axisHolder.clientWidth - this.alignmentOffset !== this.width) {
|
||||
this.refresh();
|
||||
}
|
||||
},
|
||||
refresh() {
|
||||
this.setDimensions();
|
||||
this.drawAxis(this.bounds, this.timeSystem);
|
||||
this.updateNowMarker();
|
||||
},
|
||||
updateNowMarker() {
|
||||
let nowMarker = this.$el.querySelector('.nowMarker');
|
||||
const nowMarker = this.$el.querySelector('.nowMarker');
|
||||
if (nowMarker) {
|
||||
nowMarker.classList.remove('hidden');
|
||||
nowMarker.style.height = this.contentHeight + 'px';
|
||||
this.nowMarkerStyle.height = this.contentHeight + 'px';
|
||||
const nowTimeStamp = this.openmct.time.now();
|
||||
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() {
|
||||
this.width = this.axisHolder.clientWidth;
|
||||
this.offsetWidth = this.width - this.offset;
|
||||
|
||||
this.width = this.axisHolder.clientWidth - (this.alignmentOffset ?? 0);
|
||||
this.height = Math.round(this.axisHolder.getBoundingClientRect().height);
|
||||
|
||||
if (this.useSVG) {
|
||||
this.svgElement.attr('width', this.width);
|
||||
this.svgElement.attr('height', this.height);
|
||||
this.svgWidth = this.width;
|
||||
this.svgHeight = this.height;
|
||||
} else {
|
||||
this.svgElement.attr('height', 50);
|
||||
this.svgHeight = 50;
|
||||
}
|
||||
},
|
||||
drawAxis(bounds, timeSystem) {
|
||||
@ -180,16 +215,16 @@ export default {
|
||||
this.xScale.domain([bounds.start, bounds.end]);
|
||||
}
|
||||
|
||||
this.xScale.range([PADDING, this.offsetWidth - PADDING * 2]);
|
||||
this.xScale.range([PADDING, this.width - PADDING * 2]);
|
||||
},
|
||||
setAxis() {
|
||||
this.xAxis = axisTop(this.xScale);
|
||||
this.xAxis.tickFormat(utcMultiTimeFormat);
|
||||
|
||||
if (this.width > 1800) {
|
||||
this.xAxis.ticks(this.offsetWidth / PIXELS_PER_TICK_WIDE);
|
||||
this.xAxis.ticks(this.width / PIXELS_PER_TICK_WIDE);
|
||||
} else {
|
||||
this.xAxis.ticks(this.offsetWidth / PIXELS_PER_TICK);
|
||||
this.xAxis.ticks(this.width / PIXELS_PER_TICK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
145
src/ui/composables/alignmentContext.js
Normal file
145
src/ui/composables/alignmentContext.js
Normal 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.
|
||||
*/
|
Loading…
Reference in New Issue
Block a user