Sync time conductor with plots time range (#3843)

* Adds play and pause functionality for plots (not for legacy plots)

* Add time conductor sync gesture to actions. Also fix status css.

Co-authored-by: charlesh88 <charles.f.hacskaylo@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
Shefali Joshi 2021-05-12 10:57:54 -07:00 committed by GitHub
parent dacec48aec
commit 633bac2ed5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 191 additions and 22 deletions

View File

@ -85,8 +85,8 @@
>
</button>
</div>
<div class="c-button-set c-button-set--strip-h"
:disabled="!plotHistory.length"
<div v-if="plotHistory.length"
class="c-button-set c-button-set--strip-h"
>
<button class="c-button icon-arrow-left"
title="Restore previous pan/zoom"
@ -99,6 +99,31 @@
>
</button>
</div>
<div v-if="isRealTime"
class="c-button-set c-button-set--strip-h"
>
<button v-if="!isFrozen"
class="c-button icon-pause"
title="Pause incoming real-time data"
@click="pause()"
>
</button>
<button v-if="isFrozen"
class="c-button icon-arrow-right pause-play is-paused"
title="Resume displaying real-time data"
@click="play()"
>
</button>
</div>
<div v-if="isTimeOutOfSync || isFrozen"
class="c-button-set c-button-set--strip-h"
>
<button class="c-button icon-clock"
title="Synchronize Time Conductor"
@click="showSynchronizeDialog()"
>
</button>
</div>
</div>
<!--Cursor guides-->
@ -186,10 +211,15 @@ export default {
xKeyOptions: [],
config: {},
pending: 0,
loaded: false
isRealTime: this.openmct.time.clock() !== undefined,
loaded: false,
isTimeOutOfSync: false
};
},
computed: {
isFrozen() {
return this.config.xAxis.get('frozen') === true && this.config.yAxis.get('frozen') === true;
},
plotLegendPositionClass() {
return `plot-legend-${this.config.legend.get('position')}`;
},
@ -227,6 +257,7 @@ export default {
'configuration.filters',
this.updateFiltersAndResubscribe
);
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.updateStatus);
this.openmct.objectViews.on('clearData', this.clearData);
this.followTimeConductor();
@ -243,6 +274,7 @@ export default {
},
methods: {
followTimeConductor() {
this.openmct.time.on('clock', this.updateRealTime);
this.openmct.time.on('bounds', this.updateDisplayBounds);
this.synchronized(true);
},
@ -371,6 +403,9 @@ export default {
const displayRange = series.getDisplayRange(xKey);
this.config.xAxis.set('range', displayRange);
},
updateRealTime(clock) {
this.isRealTime = clock !== undefined;
},
/**
* Track latest display bounds. Forces update when not receiving ticks.
@ -424,19 +459,30 @@ export default {
* displays can update accordingly.
*/
synchronized(value) {
const isLocalClock = this.openmct.time.clock();
if (typeof value !== 'undefined') {
this._synchronized = value;
const isUnsynced = !value && this.openmct.time.clock();
const domainObject = this.openmct.legacyObject(this.domainObject);
if (domainObject.getCapability('status')) {
domainObject.getCapability('status')
.set('timeconductor-unsynced', isUnsynced);
}
this.isTimeOutOfSync = value !== true;
const isUnsynced = isLocalClock && !value;
this.setStatus(isUnsynced);
}
return this._synchronized;
},
setStatus(isNotInSync) {
const outOfSync = isNotInSync === true
|| this.isTimeOutOfSync === true
|| this.isFrozen === true;
if (outOfSync === true) {
this.openmct.status.set(this.domainObject.identifier, 'timeconductor-unsynced');
} else {
this.openmct.status.set(this.domainObject.identifier, '');
}
},
initCanvas() {
if (this.canvas) {
this.stopListening(this.canvas);
@ -729,7 +775,8 @@ export default {
const ZOOM_AMT = 0.1;
event.preventDefault();
if (!this.positionOverPlot) {
if (event.wheelDelta === undefined
|| !this.positionOverPlot) {
return;
}
@ -847,11 +894,13 @@ export default {
freeze() {
this.config.yAxis.set('frozen', true);
this.config.xAxis.set('frozen', true);
this.setStatus();
},
clear() {
this.config.yAxis.set('frozen', false);
this.config.xAxis.set('frozen', false);
this.setStatus();
this.plotHistory = [];
this.userViewportChangeEnd();
},
@ -881,6 +930,59 @@ export default {
this.config.series.models[0].emit('change:yKey', yKey);
},
pause() {
this.freeze();
},
play() {
this.clear();
},
showSynchronizeDialog() {
const isLocalClock = this.openmct.time.clock();
if (isLocalClock !== undefined) {
const message = `
This action will change the Time Conductor to Fixed Timespan mode with this plot view's current time bounds.
Do you want to continue?
`;
let dialog = this.openmct.overlays.dialog({
title: 'Synchronize Time Conductor',
iconClass: 'alert',
size: 'fit',
message: message,
buttons: [
{
label: 'OK',
callback: () => {
dialog.dismiss();
this.synchronizeTimeConductor();
}
},
{
label: 'Cancel',
callback: () => {
dialog.dismiss();
}
}
]
});
} else {
this.openmct.notifications.alert('Time conductor bounds have changed.');
this.synchronizeTimeConductor();
}
},
synchronizeTimeConductor() {
this.openmct.time.stopClock();
const range = this.config.xAxis.get('displayRange');
this.openmct.time.bounds({
start: range.min,
end: range.max
});
this.isTimeOutOfSync = false;
},
destroy() {
configStore.deleteStore(this.config.id);
@ -894,8 +996,16 @@ export default {
this.filterObserver();
}
if (this.removeStatusListener) {
this.removeStatusListener();
}
this.openmct.time.off('clock', this.updateRealTime);
this.openmct.time.off('bounds', this.updateDisplayBounds);
this.openmct.objectViews.off('clearData', this.clearData);
},
updateStatus(status) {
this.$emit('statusUpdated', status);
}
}
};

View File

@ -56,6 +56,7 @@
<div ref="plotContainer"
class="l-view-section u-style-receiver js-style-receiver"
:class="{'s-status-timeconductor-unsynced': status && status === 'timeconductor-unsynced'}"
>
<div v-show="!!loading"
class="c-loading--overlay loading"
@ -64,6 +65,7 @@
:cursor-guide="cursorGuide"
:options="options"
@loadingUpdated="loadingUpdated"
@statusUpdated="setStatus"
/>
</div>
</div>
@ -94,7 +96,8 @@ export default {
// hideExportButtons: false
cursorGuide: false,
gridLines: !this.options.compact,
loading: false
loading: false,
status: ''
};
},
mounted() {
@ -131,6 +134,9 @@ export default {
toggleGridLines() {
this.gridLines = !this.gridLines;
},
setStatus(status) {
this.status = status;
}
}
};

View File

@ -313,6 +313,46 @@ describe("the plugin", function () {
expect(options[0].value).toBe("Some attribute");
expect(options[1].value).toBe("Another attribute");
});
it('hides the pause and play controls', () => {
let pauseEl = element.querySelectorAll(".c-button-set .icon-pause");
let playEl = element.querySelectorAll(".c-button-set .icon-arrow-right");
expect(pauseEl.length).toBe(0);
expect(playEl.length).toBe(0);
});
describe('pause and play controls', () => {
beforeEach(() => {
openmct.time.clock('local', {
start: -1000,
end: 100
});
return Vue.nextTick();
});
it('shows the pause controls', (done) => {
Vue.nextTick(() => {
let pauseEl = element.querySelectorAll(".c-button-set .icon-pause");
expect(pauseEl.length).toBe(1);
done();
});
});
it('shows the play control if plot is paused', (done) => {
let pauseEl = element.querySelector(".c-button-set .icon-pause");
const clickEvent = createMouseEvent("click");
pauseEl.dispatchEvent(clickEvent);
Vue.nextTick(() => {
let playEl = element.querySelectorAll(".c-button-set .is-paused");
expect(playEl.length).toBe(1);
done();
});
});
});
});
describe("The stacked plot view", () => {

View File

@ -90,6 +90,7 @@ export default {
const onTickWidthChange = this.onTickWidthChange;
const loadingUpdated = this.loadingUpdated;
const setStatus = this.setStatus;
const openmct = this.openmct;
const object = this.object;
@ -111,17 +112,23 @@ export default {
return {
...getProps(),
onTickWidthChange,
loadingUpdated
loadingUpdated,
setStatus
};
},
template: '<div ref="plotWrapper" class="l-view-section u-style-receiver js-style-receiver"><div v-show="!!loading" class="c-loading--overlay loading"></div><mct-plot :grid-lines="gridLines" :cursor-guide="cursorGuide" :plot-tick-width="plotTickWidth" :options="options" @plotTickWidth="onTickWidthChange" @loadingUpdated="loadingUpdated"/></div>'
template: '<div ref="plotWrapper" class="l-view-section u-style-receiver js-style-receiver" :data-status="status" :class="{\'s-status-timeconductor-unsynced\': status && status === \'timeconductor-unsynced\'}"><div v-show="!!loading" class="c-loading--overlay loading"></div><mct-plot :grid-lines="gridLines" :cursor-guide="cursorGuide" :plot-tick-width="plotTickWidth" :options="options" @plotTickWidth="onTickWidthChange" @statusUpdated="setStatus" @loadingUpdated="loadingUpdated"/></div>'
});
},
onTickWidthChange() {
this.$emit('plotTickWidth', ...arguments);
},
setStatus(status) {
this.status = status;
this.updateComponentProp('status', status);
},
loadingUpdated(loaded) {
this.loading = loaded;
this.updateComponentProp('loading', loaded);
},
getProps() {
return {
@ -129,7 +136,8 @@ export default {
cursorGuide: this.cursorGuide,
plotTickWidth: this.plotTickWidth,
loading: this.loading,
options: this.options
options: this.options,
status: this.status
};
}
}

View File

@ -638,6 +638,16 @@
box-shadow: $shdw;
}
@mixin smallerControlButtons() {
.c-click-icon,
.c-button,
.c-icon-button {
// Shrink buttons a bit when they appear in containers
font-size: 0.9em;
padding: 4px;
}
}
@mixin wrappedInput() {
// An input that is wrapped. Optionally includes a __label or icon element.
// Based on .c-search.

View File

@ -129,14 +129,7 @@
}
}
.c-click-icon,
.c-button,
.c-icon-button {
// Shrink buttons a bit when they appear in a frame
border-radius: $smallCr !important;
font-size: 0.9em;
padding: 5px;
}
@include smallerControlButtons;
&.has-complex-content {
> .c-so-view__view-large { display: block; }

View File

@ -22,5 +22,7 @@
.c-plan {
display: contents;
}
@include smallerControlButtons;
}
}