[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> </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>

View File

@ -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);

View File

@ -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) {

View File

@ -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();
}); });
}, },

View File

@ -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];

View File

@ -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),

View File

@ -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;

View File

@ -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;
} }