[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:
David Tsay 2020-09-14 08:14:24 -07:00 committed by GitHub
parent 18b2a270c9
commit 0da5409092
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 148 additions and 15 deletions

View File

@ -240,9 +240,9 @@
</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">
<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"
ng-style="{
left: (100 * (tick.value - min) / interval) + '%'
@ -252,9 +252,21 @@
</div>
</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') }}
</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>

View File

@ -66,7 +66,6 @@ function (
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, 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.xAxis, 'change', this.scheduleDraw);
this.$scope.$watch('highlights', this.scheduleDraw);
@ -79,7 +78,14 @@ function (
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) {
this.listenTo(series, 'change:xKey', this.reDraw, this);
this.listenTo(series, 'change:interpolate', this.changeInterpolate, this);
this.listenTo(series, 'change:markers', this.changeMarkers, this);
this.listenTo(series, 'change:alarmMarkers', this.changeAlarmMarkers, this);

View File

@ -428,6 +428,19 @@ define([
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 () {
const showMarkers = this.get('markers');
if (!showMarkers) {

View File

@ -49,6 +49,7 @@ define([
}
this.listenTo(this, 'change:key', this.changeKey, this);
this.listenTo(this, 'resetSeries', this.resetSeries, this);
},
changeKey: function (newKey) {
const series = this.plot.series.first();
@ -66,6 +67,10 @@ define([
this.plot.series.forEach(function (plotSeries) {
plotSeries.set('xKey', newKey);
});
},
resetSeries: function () {
this.plot.series.forEach(function (plotSeries) {
plotSeries.reset();
});
},

View File

@ -102,12 +102,32 @@ define([
this.listenTo(this.$scope, 'plot:tickWidth', this.onTickWidthChange, this);
this.listenTo(this.$scope, 'plot:highlight:set', this.onPlotHighlightSet, 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.yAxis, 'change:displayRange', this.onYAxisChange, this);
this.setUpXAxisOptions();
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 () {
if (this.$scope.series.length === 1) {
let metadata = this.$scope.series[0].metadata;
@ -534,6 +554,32 @@ define([
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) {
let yAxisObject = options.filter(o => o.name === label)[0];

View File

@ -126,6 +126,7 @@ define([
this.tickUpdate = false;
this.listenTo(this.axis, 'change:displayRange', 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.updateTicks();
};
@ -137,12 +138,19 @@ define([
/**
* 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
* outside of given range, or c) if the range exceeds the size of the tick
* range by more than one tick step.
* Ticks are updated
* a) if they don't exist,
* 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
*/
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) {
return true;
}
@ -175,7 +183,11 @@ define([
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');
if (!range) {
delete this.$scope.min;
@ -196,7 +208,7 @@ define([
this.$scope.min = range.min;
this.$scope.max = range.max;
this.$scope.interval = Math.abs(range.min - range.max);
if (this.shouldRegenerateTicks(range)) {
if (this.shouldRegenerateTicks(range, forceRegeneration)) {
let newTicks = this.getTicks();
this.tickRange = {
min: Math.min.apply(Math, newTicks),

View File

@ -90,7 +90,7 @@ define([
PlotController.prototype.followTimeConductor = function () {
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);
};
@ -134,6 +134,9 @@ define([
};
PlotController.prototype.addSeries = function (series) {
this.listenTo(series, 'change:xKey', (xKey) => {
this.setDisplayRange(series, xKey);
}, this);
this.listenTo(series, 'change:yKey', () => {
this.loadSeriesData(series);
}, this);
@ -145,6 +148,15 @@ define([
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) {
this.stopListening(plotSeries);
};
@ -165,8 +177,9 @@ define([
return config;
};
PlotController.prototype.onTimeSystemChange = function (timeSystem) {
PlotController.prototype.syncXAxisToTimeSystem = function (timeSystem) {
this.config.xAxis.set('key', timeSystem.key);
this.config.xAxis.emit('resetSeries');
};
PlotController.prototype.destroy = function () {
@ -189,7 +202,8 @@ define([
plotSeries.load({
size: this.$element[0].offsetWidth,
start: range.min,
end: range.max
end: range.max,
domain: this.config.xAxis.get('key')
})
.then(this.stopLoading());
if (purge) {
@ -202,10 +216,18 @@ define([
* Track latest display bounds. Forces update when not receiving ticks.
*/
PlotController.prototype.updateDisplayBounds = function (bounds, isTick) {
const xAxisKey = this.config.xAxis.get('key');
const timeSystem = this.openmct.time.timeSystem();
const newRange = {
min: bounds.start,
max: bounds.end
};
if (xAxisKey !== timeSystem.key) {
this.syncXAxisToTimeSystem(timeSystem);
}
this.config.xAxis.set('range', newRange);
if (!isTick) {
this.skipReloadOnInteraction = true;

View File

@ -27,7 +27,7 @@ mct-plot {
/*********************** STACKED PLOT LAYOUT */
.is-editing {
.gl-plot.child-frame {
&:hover {
@include hover {
background: rgba($editUIColorBg, 0.1);
box-shadow: inset rgba($editUIColorBg, 0.3) 0 0 0 1px;
}
@ -52,6 +52,7 @@ mct-plot {
.c-control-bar {
display: none;
}
.gl-plot-x-label__select,
.gl-plot-y-label__select {
display: none;
}
@ -172,6 +173,14 @@ mct-plot {
}
}
}
&.gl-plot-x {
@include hover {
.gl-plot-x-label__select {
display: block;
}
}
}
}
.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 {
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 20px;
left: 0;
z-index: 10;
}