mirror of
https://github.com/nasa/openmct.git
synced 2025-02-21 09:52:04 +00:00
[Plots] Allow changing x-axis metadata (#3177)
* allow change of x-axis metadata in single plots * only enable x key toggle when appropriate * prevent x-axis toggle if data does not exist for new x-axis key * reset x-axis selection on bounds change
This commit is contained in:
parent
18b2a270c9
commit
0da5409092
@ -240,9 +240,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gl-plot-axis-area gl-plot-x">
|
<div class="gl-plot-axis-area gl-plot-x has-local-controls">
|
||||||
<mct-ticks axis="xAxis">
|
<mct-ticks axis="xAxis">
|
||||||
<div ng-repeat="tick in ticks track by tick.value"
|
<div ng-repeat="tick in ticks track by tick.text"
|
||||||
class="gl-plot-tick gl-plot-x-tick-label"
|
class="gl-plot-tick gl-plot-x-tick-label"
|
||||||
ng-style="{
|
ng-style="{
|
||||||
left: (100 * (tick.value - min) / interval) + '%'
|
left: (100 * (tick.value - min) / interval) + '%'
|
||||||
@ -252,9 +252,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</mct-ticks>
|
</mct-ticks>
|
||||||
|
|
||||||
<div class="gl-plot-label gl-plot-x-label">
|
<div
|
||||||
|
class="gl-plot-label gl-plot-x-label"
|
||||||
|
ng-class="{'icon-gear': isEnabledXKeyToggle()}"
|
||||||
|
>
|
||||||
{{ xAxis.get('label') }}
|
{{ xAxis.get('label') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<select
|
||||||
|
ng-show="plot.isEnabledXKeyToggle()"
|
||||||
|
ng-model="selectedXKeyOption.key"
|
||||||
|
ng-change="plot.toggleXKeyOption('{{selectedXKeyOption.key}}', series[0])"
|
||||||
|
class="gl-plot-x-label__select local-controls--hidden"
|
||||||
|
ng-options="option.key as option.name for option in xKeyOptions"
|
||||||
|
>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -66,7 +66,6 @@ function (
|
|||||||
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
|
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
|
||||||
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this);
|
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this);
|
||||||
this.listenTo(this.config.yAxis, 'change:key', this.clearOffset, this);
|
this.listenTo(this.config.yAxis, 'change:key', this.clearOffset, this);
|
||||||
this.listenTo(this.config.xAxis, 'change:key', this.clearOffset, this);
|
|
||||||
this.listenTo(this.config.yAxis, 'change', this.scheduleDraw);
|
this.listenTo(this.config.yAxis, 'change', this.scheduleDraw);
|
||||||
this.listenTo(this.config.xAxis, 'change', this.scheduleDraw);
|
this.listenTo(this.config.xAxis, 'change', this.scheduleDraw);
|
||||||
this.$scope.$watch('highlights', this.scheduleDraw);
|
this.$scope.$watch('highlights', this.scheduleDraw);
|
||||||
@ -79,7 +78,14 @@ function (
|
|||||||
|
|
||||||
MCTChartController.$inject = ['$scope'];
|
MCTChartController.$inject = ['$scope'];
|
||||||
|
|
||||||
|
MCTChartController.prototype.reDraw = function (mode, o, series) {
|
||||||
|
this.changeInterpolate(mode, o, series);
|
||||||
|
this.changeMarkers(mode, o, series);
|
||||||
|
this.changeAlarmMarkers(mode, o, series);
|
||||||
|
};
|
||||||
|
|
||||||
MCTChartController.prototype.onSeriesAdd = function (series) {
|
MCTChartController.prototype.onSeriesAdd = function (series) {
|
||||||
|
this.listenTo(series, 'change:xKey', this.reDraw, this);
|
||||||
this.listenTo(series, 'change:interpolate', this.changeInterpolate, this);
|
this.listenTo(series, 'change:interpolate', this.changeInterpolate, this);
|
||||||
this.listenTo(series, 'change:markers', this.changeMarkers, this);
|
this.listenTo(series, 'change:markers', this.changeMarkers, this);
|
||||||
this.listenTo(series, 'change:alarmMarkers', this.changeAlarmMarkers, this);
|
this.listenTo(series, 'change:alarmMarkers', this.changeAlarmMarkers, this);
|
||||||
|
@ -428,6 +428,19 @@ define([
|
|||||||
this.filters = deepCopiedFilters;
|
this.filters = deepCopiedFilters;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getDisplayRange: function (xKey) {
|
||||||
|
const unsortedData = this.data;
|
||||||
|
this.data = [];
|
||||||
|
unsortedData.forEach(point => this.add(point, false));
|
||||||
|
|
||||||
|
const minValue = this.getXVal(this.data[0]);
|
||||||
|
const maxValue = this.getXVal(this.data[this.data.length - 1]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
min: minValue,
|
||||||
|
max: maxValue
|
||||||
|
};
|
||||||
|
},
|
||||||
markerOptionsDisplayText: function () {
|
markerOptionsDisplayText: function () {
|
||||||
const showMarkers = this.get('markers');
|
const showMarkers = this.get('markers');
|
||||||
if (!showMarkers) {
|
if (!showMarkers) {
|
||||||
|
@ -49,6 +49,7 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.listenTo(this, 'change:key', this.changeKey, this);
|
this.listenTo(this, 'change:key', this.changeKey, this);
|
||||||
|
this.listenTo(this, 'resetSeries', this.resetSeries, this);
|
||||||
},
|
},
|
||||||
changeKey: function (newKey) {
|
changeKey: function (newKey) {
|
||||||
const series = this.plot.series.first();
|
const series = this.plot.series.first();
|
||||||
@ -66,6 +67,10 @@ define([
|
|||||||
|
|
||||||
this.plot.series.forEach(function (plotSeries) {
|
this.plot.series.forEach(function (plotSeries) {
|
||||||
plotSeries.set('xKey', newKey);
|
plotSeries.set('xKey', newKey);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resetSeries: function () {
|
||||||
|
this.plot.series.forEach(function (plotSeries) {
|
||||||
plotSeries.reset();
|
plotSeries.reset();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -102,12 +102,32 @@ define([
|
|||||||
this.listenTo(this.$scope, 'plot:tickWidth', this.onTickWidthChange, this);
|
this.listenTo(this.$scope, 'plot:tickWidth', this.onTickWidthChange, this);
|
||||||
this.listenTo(this.$scope, 'plot:highlight:set', this.onPlotHighlightSet, this);
|
this.listenTo(this.$scope, 'plot:highlight:set', this.onPlotHighlightSet, this);
|
||||||
this.listenTo(this.$scope, 'plot:reinitializeCanvas', this.initCanvas, this);
|
this.listenTo(this.$scope, 'plot:reinitializeCanvas', this.initCanvas, this);
|
||||||
|
this.listenTo(this.config.xAxis, 'resetSeries', this.setUpXAxisOptions, this);
|
||||||
this.listenTo(this.config.xAxis, 'change:displayRange', this.onXAxisChange, this);
|
this.listenTo(this.config.xAxis, 'change:displayRange', this.onXAxisChange, this);
|
||||||
this.listenTo(this.config.yAxis, 'change:displayRange', this.onYAxisChange, this);
|
this.listenTo(this.config.yAxis, 'change:displayRange', this.onYAxisChange, this);
|
||||||
|
|
||||||
|
this.setUpXAxisOptions();
|
||||||
this.setUpYAxisOptions();
|
this.setUpYAxisOptions();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MCTPlotController.prototype.setUpXAxisOptions = function () {
|
||||||
|
const xAxisKey = this.config.xAxis.get('key');
|
||||||
|
|
||||||
|
if (this.$scope.series.length === 1) {
|
||||||
|
let metadata = this.$scope.series[0].metadata;
|
||||||
|
|
||||||
|
this.$scope.xKeyOptions = metadata
|
||||||
|
.valuesForHints(['domain'])
|
||||||
|
.map(function (o) {
|
||||||
|
return {
|
||||||
|
name: o.name,
|
||||||
|
key: o.key
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.$scope.selectedXKeyOption = this.getXKeyOption(xAxisKey);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
MCTPlotController.prototype.setUpYAxisOptions = function () {
|
MCTPlotController.prototype.setUpYAxisOptions = function () {
|
||||||
if (this.$scope.series.length === 1) {
|
if (this.$scope.series.length === 1) {
|
||||||
let metadata = this.$scope.series[0].metadata;
|
let metadata = this.$scope.series[0].metadata;
|
||||||
@ -534,6 +554,32 @@ define([
|
|||||||
this.cursorGuide = !this.cursorGuide;
|
this.cursorGuide = !this.cursorGuide;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MCTPlotController.prototype.getXKeyOption = function (key) {
|
||||||
|
return this.$scope.xKeyOptions.find(option => option.key === key);
|
||||||
|
};
|
||||||
|
|
||||||
|
MCTPlotController.prototype.isEnabledXKeyToggle = function () {
|
||||||
|
const isSinglePlot = this.$scope.xKeyOptions && this.$scope.xKeyOptions.length > 1 && this.$scope.series.length === 1;
|
||||||
|
const isFrozen = this.config.xAxis.get('frozen');
|
||||||
|
const inRealTimeMode = this.config.openmct.time.clock();
|
||||||
|
|
||||||
|
return isSinglePlot && !isFrozen && !inRealTimeMode;
|
||||||
|
};
|
||||||
|
|
||||||
|
MCTPlotController.prototype.toggleXKeyOption = function (lastXKey, series) {
|
||||||
|
const selectedXKey = this.$scope.selectedXKeyOption.key;
|
||||||
|
const dataForSelectedXKey = series.data
|
||||||
|
? series.data[0][selectedXKey]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (dataForSelectedXKey !== undefined) {
|
||||||
|
this.config.xAxis.set('key', selectedXKey);
|
||||||
|
} else {
|
||||||
|
this.config.openmct.notifications.error('Cannot change x-axis view as no data exists for this view type.');
|
||||||
|
this.$scope.selectedXKeyOption.key = lastXKey;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
MCTPlotController.prototype.toggleYAxisLabel = function (label, options, series) {
|
MCTPlotController.prototype.toggleYAxisLabel = function (label, options, series) {
|
||||||
let yAxisObject = options.filter(o => o.name === label)[0];
|
let yAxisObject = options.filter(o => o.name === label)[0];
|
||||||
|
|
||||||
|
@ -126,6 +126,7 @@ define([
|
|||||||
this.tickUpdate = false;
|
this.tickUpdate = false;
|
||||||
this.listenTo(this.axis, 'change:displayRange', this.updateTicks, this);
|
this.listenTo(this.axis, 'change:displayRange', this.updateTicks, this);
|
||||||
this.listenTo(this.axis, 'change:format', this.updateTicks, this);
|
this.listenTo(this.axis, 'change:format', this.updateTicks, this);
|
||||||
|
this.listenTo(this.axis, 'change:key', this.updateTicksForceRegeneration, this);
|
||||||
this.listenTo(this.$scope, '$destroy', this.stopListening, this);
|
this.listenTo(this.$scope, '$destroy', this.stopListening, this);
|
||||||
this.updateTicks();
|
this.updateTicks();
|
||||||
};
|
};
|
||||||
@ -137,12 +138,19 @@ define([
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether ticks should be regenerated for a given range.
|
* Determine whether ticks should be regenerated for a given range.
|
||||||
* Ticks are updated a) if they don't exist, b) if the existing ticks are
|
* Ticks are updated
|
||||||
* outside of given range, or c) if the range exceeds the size of the tick
|
* a) if they don't exist,
|
||||||
* range by more than one tick step.
|
* b) if existing ticks are outside of given range,
|
||||||
|
* c) if range exceeds size of tick range by more than one tick step,
|
||||||
|
* d) if forced to regenerate (ex. changing x-axis metadata).
|
||||||
|
*
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
MCTTicksController.prototype.shouldRegenerateTicks = function (range) {
|
MCTTicksController.prototype.shouldRegenerateTicks = function (range, forceRegeneration) {
|
||||||
|
if (forceRegeneration) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.tickRange || !this.$scope.ticks || !this.$scope.ticks.length) {
|
if (!this.tickRange || !this.$scope.ticks || !this.$scope.ticks.length) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -175,7 +183,11 @@ define([
|
|||||||
return ticks(range.min, range.max, number);
|
return ticks(range.min, range.max, number);
|
||||||
};
|
};
|
||||||
|
|
||||||
MCTTicksController.prototype.updateTicks = function () {
|
MCTTicksController.prototype.updateTicksForceRegeneration = function () {
|
||||||
|
this.updateTicks(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
MCTTicksController.prototype.updateTicks = function (forceRegeneration = false) {
|
||||||
const range = this.axis.get('displayRange');
|
const range = this.axis.get('displayRange');
|
||||||
if (!range) {
|
if (!range) {
|
||||||
delete this.$scope.min;
|
delete this.$scope.min;
|
||||||
@ -196,7 +208,7 @@ define([
|
|||||||
this.$scope.min = range.min;
|
this.$scope.min = range.min;
|
||||||
this.$scope.max = range.max;
|
this.$scope.max = range.max;
|
||||||
this.$scope.interval = Math.abs(range.min - range.max);
|
this.$scope.interval = Math.abs(range.min - range.max);
|
||||||
if (this.shouldRegenerateTicks(range)) {
|
if (this.shouldRegenerateTicks(range, forceRegeneration)) {
|
||||||
let newTicks = this.getTicks();
|
let newTicks = this.getTicks();
|
||||||
this.tickRange = {
|
this.tickRange = {
|
||||||
min: Math.min.apply(Math, newTicks),
|
min: Math.min.apply(Math, newTicks),
|
||||||
|
@ -90,7 +90,7 @@ define([
|
|||||||
|
|
||||||
PlotController.prototype.followTimeConductor = function () {
|
PlotController.prototype.followTimeConductor = function () {
|
||||||
this.listenTo(this.openmct.time, 'bounds', this.updateDisplayBounds, this);
|
this.listenTo(this.openmct.time, 'bounds', this.updateDisplayBounds, this);
|
||||||
this.listenTo(this.openmct.time, 'timeSystem', this.onTimeSystemChange, this);
|
this.listenTo(this.openmct.time, 'timeSystem', this.syncXAxisToTimeSystem, this);
|
||||||
this.synchronized(true);
|
this.synchronized(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -134,6 +134,9 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
PlotController.prototype.addSeries = function (series) {
|
PlotController.prototype.addSeries = function (series) {
|
||||||
|
this.listenTo(series, 'change:xKey', (xKey) => {
|
||||||
|
this.setDisplayRange(series, xKey);
|
||||||
|
}, this);
|
||||||
this.listenTo(series, 'change:yKey', () => {
|
this.listenTo(series, 'change:yKey', () => {
|
||||||
this.loadSeriesData(series);
|
this.loadSeriesData(series);
|
||||||
}, this);
|
}, this);
|
||||||
@ -145,6 +148,15 @@ define([
|
|||||||
this.loadSeriesData(series);
|
this.loadSeriesData(series);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
PlotController.prototype.setDisplayRange = function (series, xKey) {
|
||||||
|
if (this.config.series.models.length !== 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayRange = series.getDisplayRange(xKey);
|
||||||
|
this.config.xAxis.set('range', displayRange);
|
||||||
|
};
|
||||||
|
|
||||||
PlotController.prototype.removeSeries = function (plotSeries) {
|
PlotController.prototype.removeSeries = function (plotSeries) {
|
||||||
this.stopListening(plotSeries);
|
this.stopListening(plotSeries);
|
||||||
};
|
};
|
||||||
@ -165,8 +177,9 @@ define([
|
|||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
||||||
PlotController.prototype.onTimeSystemChange = function (timeSystem) {
|
PlotController.prototype.syncXAxisToTimeSystem = function (timeSystem) {
|
||||||
this.config.xAxis.set('key', timeSystem.key);
|
this.config.xAxis.set('key', timeSystem.key);
|
||||||
|
this.config.xAxis.emit('resetSeries');
|
||||||
};
|
};
|
||||||
|
|
||||||
PlotController.prototype.destroy = function () {
|
PlotController.prototype.destroy = function () {
|
||||||
@ -189,7 +202,8 @@ define([
|
|||||||
plotSeries.load({
|
plotSeries.load({
|
||||||
size: this.$element[0].offsetWidth,
|
size: this.$element[0].offsetWidth,
|
||||||
start: range.min,
|
start: range.min,
|
||||||
end: range.max
|
end: range.max,
|
||||||
|
domain: this.config.xAxis.get('key')
|
||||||
})
|
})
|
||||||
.then(this.stopLoading());
|
.then(this.stopLoading());
|
||||||
if (purge) {
|
if (purge) {
|
||||||
@ -202,10 +216,18 @@ define([
|
|||||||
* Track latest display bounds. Forces update when not receiving ticks.
|
* Track latest display bounds. Forces update when not receiving ticks.
|
||||||
*/
|
*/
|
||||||
PlotController.prototype.updateDisplayBounds = function (bounds, isTick) {
|
PlotController.prototype.updateDisplayBounds = function (bounds, isTick) {
|
||||||
|
|
||||||
|
const xAxisKey = this.config.xAxis.get('key');
|
||||||
|
const timeSystem = this.openmct.time.timeSystem();
|
||||||
const newRange = {
|
const newRange = {
|
||||||
min: bounds.start,
|
min: bounds.start,
|
||||||
max: bounds.end
|
max: bounds.end
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (xAxisKey !== timeSystem.key) {
|
||||||
|
this.syncXAxisToTimeSystem(timeSystem);
|
||||||
|
}
|
||||||
|
|
||||||
this.config.xAxis.set('range', newRange);
|
this.config.xAxis.set('range', newRange);
|
||||||
if (!isTick) {
|
if (!isTick) {
|
||||||
this.skipReloadOnInteraction = true;
|
this.skipReloadOnInteraction = true;
|
||||||
|
@ -27,7 +27,7 @@ mct-plot {
|
|||||||
/*********************** STACKED PLOT LAYOUT */
|
/*********************** STACKED PLOT LAYOUT */
|
||||||
.is-editing {
|
.is-editing {
|
||||||
.gl-plot.child-frame {
|
.gl-plot.child-frame {
|
||||||
&:hover {
|
@include hover {
|
||||||
background: rgba($editUIColorBg, 0.1);
|
background: rgba($editUIColorBg, 0.1);
|
||||||
box-shadow: inset rgba($editUIColorBg, 0.3) 0 0 0 1px;
|
box-shadow: inset rgba($editUIColorBg, 0.3) 0 0 0 1px;
|
||||||
}
|
}
|
||||||
@ -52,6 +52,7 @@ mct-plot {
|
|||||||
.c-control-bar {
|
.c-control-bar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.gl-plot-x-label__select,
|
||||||
.gl-plot-y-label__select {
|
.gl-plot-y-label__select {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -172,6 +173,14 @@ mct-plot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.gl-plot-x {
|
||||||
|
@include hover {
|
||||||
|
.gl-plot-x-label__select {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.gl-plot-coords {
|
.gl-plot-coords {
|
||||||
@ -217,11 +226,19 @@ mct-plot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gl-plot-x-label__select {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 0;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
.gl-plot-y-label__select {
|
.gl-plot-y-label__select {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
left: 20px;
|
left: 0;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user