Refactor: flexible Y-Min and Y-Max settings (#6051)

* Feature: Flexible Y-Min and Y-Max settings.

Y-Min and Y-Max is now string.
New usage for both Y-Min and Y-Max (Where X is a real number):
  >X Y-Max/Y-Max is auto if data is above X else X
  <X Y-Max/Y-Min is auto if data is below X else X
  =X Y-Max/Y-Min is scaled to current value +/- X
  ~X Y-Max/Y-Min is caled to average value +/- X

Example: Y-Min: <100 Y-Max: >200
If all points are within 100 and 200: Y-Min is 100 and Y-Max is 200
If some points are above 200: Y-Min is 100 and Y-Max is auto
If some points are below 100: Y-Min is auto and Y-Max is 200
if some points are below 100 and above 200: Y-Min and Y-Max is auto

Tests for new settings added

* Use parseFloat instead of implicit conversion

* feat(flexible_y-min/max): refactor.

* feat(flexible_y-min/max): added more tests.
This commit is contained in:
Alexander Zobnin 2016-09-15 22:03:54 +03:00 committed by Torkel Ödegaard
parent a95808a7c3
commit 4fa22e2158
3 changed files with 168 additions and 2 deletions

View File

@ -339,6 +339,75 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholdManExports) {
};
}
//Override min/max to provide more flexible autoscaling
function autoscaleSpanOverride(yaxis, data, options) {
var expr;
if (yaxis.min != null && data != null) {
expr = parseThresholdExpr(yaxis.min);
options.min = autoscaleYAxisMin(expr, data.stats);
}
if (yaxis.max != null && data != null) {
expr = parseThresholdExpr(yaxis.max);
options.max = autoscaleYAxisMax(expr, data.stats);
}
}
function parseThresholdExpr(expr) {
var match, operator, value, precision;
match = expr.match(/\s*([<=>~]*)\W*(\d+(\.\d+)?)/);
if (match) {
operator = match[1];
value = parseFloat(match[2]);
//Precision based on input
precision = match[3] ? match[3].length - 1 : 0;
return {
operator: operator,
value: value,
precision: precision
};
} else {
return undefined;
}
}
function autoscaleYAxisMax(expr, dataStats) {
var operator = expr.operator,
value = expr.value,
precision = expr.precision;
if (operator === ">") {
return dataStats.max < value ? value : null;
} else if (operator === "<") {
return dataStats.max > value ? value : null;
} else if (operator === "~") {
return kbn.roundValue(dataStats.avg + value, precision);
} else if (operator === "=") {
return kbn.roundValue(dataStats.current + value, precision);
} else if (!operator && !isNaN(value)) {
return kbn.roundValue(value, precision);
} else {
return null;
}
}
function autoscaleYAxisMin(expr, dataStats) {
var operator = expr.operator,
value = expr.value,
precision = expr.precision;
if (operator === ">") {
return dataStats.min < value ? value : null;
} else if (operator === "<") {
return dataStats.min > value ? value : null;
} else if (operator === "~") {
return kbn.roundValue(dataStats.avg - value, precision);
} else if (operator === "=") {
return kbn.roundValue(dataStats.current - value, precision);
} else if (!operator && !isNaN(value)) {
return kbn.roundValue(value, precision);
} else {
return null;
}
}
function configureAxisOptions(data, options) {
var defaults = {
position: 'left',
@ -349,6 +418,7 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholdManExports) {
max: panel.percentage && panel.stack ? 100 : panel.yaxes[0].max,
};
autoscaleSpanOverride(panel.yaxes[0], data[0], defaults);
options.yaxes.push(defaults);
if (_.find(data, {yaxis: 2})) {
@ -359,6 +429,7 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholdManExports) {
secondY.position = 'right';
secondY.min = panel.yaxes[1].min;
secondY.max = panel.percentage && panel.stack ? 100 : panel.yaxes[1].max;
autoscaleSpanOverride(panel.yaxes[1], data[1], secondY);
options.yaxes.push(secondY);
applyLogScale(options.yaxes[1], data);

View File

@ -218,4 +218,99 @@ describe('grafanaGraph', function() {
});
}, 10);
graphScenario('when using flexible Y-Min and Y-Max settings', function(ctx) {
describe('and Y-Min is <100 and Y-Max is >200 and values within range', function() {
ctx.setup(function(ctrl, data) {
ctrl.panel.yaxes[0].min = '<100';
ctrl.panel.yaxes[0].max = '>200';
data[0] = new TimeSeries({
datapoints: [[120,10],[160,20]],
alias: 'series1',
});
});
it('should set min to 100 and max to 200', function() {
expect(ctx.plotOptions.yaxes[0].min).to.be(100);
expect(ctx.plotOptions.yaxes[0].max).to.be(200);
});
});
describe('and Y-Min is <100 and Y-Max is >200 and values outside range', function() {
ctx.setup(function(ctrl, data) {
ctrl.panel.yaxes[0].min = '<100';
ctrl.panel.yaxes[0].max = '>200';
data[0] = new TimeSeries({
datapoints: [[99,10],[201,20]],
alias: 'series1',
});
});
it('should set min to auto and max to auto', function() {
expect(ctx.plotOptions.yaxes[0].min).to.be(null);
expect(ctx.plotOptions.yaxes[0].max).to.be(null);
});
});
describe('and Y-Min is =10.5 and Y-Max is =10.5', function() {
ctx.setup(function(ctrl, data) {
ctrl.panel.yaxes[0].min = '=10.5';
ctrl.panel.yaxes[0].max = '=10.5';
data[0] = new TimeSeries({
datapoints: [[100,10],[120,20], [110,30]],
alias: 'series1',
});
});
it('should set min to last value + 10.5 and max to last value + 10.5', function() {
expect(ctx.plotOptions.yaxes[0].min).to.be(99.5);
expect(ctx.plotOptions.yaxes[0].max).to.be(120.5);
});
});
describe('and Y-Min is ~10.5 and Y-Max is ~10.5', function() {
ctx.setup(function(ctrl, data) {
ctrl.panel.yaxes[0].min = '~10.5';
ctrl.panel.yaxes[0].max = '~10.5';
data[0] = new TimeSeries({
datapoints: [[102,10],[104,20], [110,30]], //Also checks precision
alias: 'series1',
});
});
it('should set min to average value + 10.5 and max to average value + 10.5', function() {
expect(ctx.plotOptions.yaxes[0].min).to.be(94.8);
expect(ctx.plotOptions.yaxes[0].max).to.be(115.8);
});
});
});
graphScenario('when using regular Y-Min and Y-Max settings', function(ctx) {
describe('and Y-Min is 100 and Y-Max is 200', function() {
ctx.setup(function(ctrl, data) {
ctrl.panel.yaxes[0].min = '100';
ctrl.panel.yaxes[0].max = '200';
data[0] = new TimeSeries({
datapoints: [[120,10],[160,20]],
alias: 'series1',
});
});
it('should set min to 100 and max to 200', function() {
expect(ctx.plotOptions.yaxes[0].min).to.be(100);
expect(ctx.plotOptions.yaxes[0].max).to.be(200);
});
});
describe('and Y-Min is 0 and Y-Max is 0', function() {
ctx.setup(function(ctrl, data) {
ctrl.panel.yaxes[0].min = '0';
ctrl.panel.yaxes[0].max = '0';
data[0] = new TimeSeries({
datapoints: [[120,10],[160,20]],
alias: 'series1',
});
});
it('should set min to 0 and max to 0', function() {
expect(ctx.plotOptions.yaxes[0].min).to.be(0);
expect(ctx.plotOptions.yaxes[0].max).to.be(0);
});
});
});
});

View File

@ -22,11 +22,11 @@
<div class="gf-form-inline">
<div class="gf-form max-width-10">
<label class="gf-form-label width-5">Y-Min</label>
<input type="number" class="gf-form-input" placeholder="auto" empty-to-null ng-model="yaxis.min" ng-change="ctrl.render()" ng-model-onblur>
<input type="text" class="gf-form-input" placeholder="auto" empty-to-null ng-model="yaxis.min" ng-change="ctrl.render()" ng-model-onblur>
</div>
<div class="gf-form max-width-10">
<label class="gf-form-label width-5">Y-Max</label>
<input type="number" class="gf-form-input" placeholder="auto" empty-to-null ng-model="yaxis.max" ng-change="ctrl.render()" ng-model-onblur>
<input type="text" class="gf-form-input" placeholder="auto" empty-to-null ng-model="yaxis.max" ng-change="ctrl.render()" ng-model-onblur>
</div>
</div>