Преглед изворни кода

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.
Alexander Zobnin пре 9 година
родитељ
комит
4fa22e2158

+ 71 - 0
public/app/plugins/panel/graph/graph.js

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

+ 95 - 0
public/app/plugins/panel/graph/specs/graph_specs.ts

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

+ 2 - 2
public/app/plugins/panel/graph/tab_axes.html

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