From 4fa22e21585de18b626ba6ee5c2f2cb4e9a01d0f Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 15 Sep 2016 22:03:54 +0300 Subject: [PATCH] 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 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. --- public/app/plugins/panel/graph/graph.js | 71 ++++++++++++++ .../plugins/panel/graph/specs/graph_specs.ts | 95 +++++++++++++++++++ public/app/plugins/panel/graph/tab_axes.html | 4 +- 3 files changed, 168 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js index bb51c88cd10..69154ffed65 100755 --- a/public/app/plugins/panel/graph/graph.js +++ b/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); diff --git a/public/app/plugins/panel/graph/specs/graph_specs.ts b/public/app/plugins/panel/graph/specs/graph_specs.ts index 633832d4866..10c81b7212d 100644 --- a/public/app/plugins/panel/graph/specs/graph_specs.ts +++ b/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); + }); + }); + }); }); diff --git a/public/app/plugins/panel/graph/tab_axes.html b/public/app/plugins/panel/graph/tab_axes.html index bb051649424..e85e266dd7d 100644 --- a/public/app/plugins/panel/graph/tab_axes.html +++ b/public/app/plugins/panel/graph/tab_axes.html @@ -22,11 +22,11 @@
- +
- +