From 1e4eb0105942debe27079174bd284bad5b1824f4 Mon Sep 17 00:00:00 2001 From: bigbenhur Date: Fri, 3 Jun 2016 15:54:14 +0200 Subject: [PATCH 1/4] Support auto grid min/max when using log scale, Issue #3090 --- public/app/plugins/panel/graph/graph.js | 58 ++++++++++--------- .../plugins/panel/graph/specs/graph_specs.ts | 6 +- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js index 86ad3b5f025..8019a51354b 100755 --- a/public/app/plugins/panel/graph/graph.js +++ b/public/app/plugins/panel/graph/graph.js @@ -373,42 +373,46 @@ function (angular, $, moment, _, kbn, GraphTooltip) { if (axis.logBase === 1) { return; } + if (axis.min < Number.MIN_VALUE) { + axis.min = null; + } var series, i; - var max = axis.max; + var max = axis.max, min = axis.min; - if (max === null) { - for (i = 0; i < data.length; i++) { - series = data[i]; - if (series.yaxis === axis.index) { - if (max < series.stats.max) { - max = series.stats.max; - } + for (i = 0; i < data.length; i++) { + series = data[i]; + if (series.yaxis === axis.index) { + if (max === null || max < series.stats.max) { + max = series.stats.max; + } + if (min === null || min > series.stats.min) { + min = series.stats.min; } } - if (max === void 0) { - max = Number.MAX_VALUE; - } + } + if (max === null && min === null) { + max = Math.pow(axis.logBase,+2); + min = Math.pow(axis.logBase,-2); + } else if (max === null) { + max = min*Math.pow(axis.logBase,+4); + } else if (min === null) { + min = max*Math.pow(axis.logBase,-4); } - axis.min = axis.min !== null ? axis.min : 0; - axis.ticks = [0, 1]; - var nextTick = 1; + axis.transform = function(v) { return Math.log(v) / Math.log(axis.logBase); }; + axis.inverseTransform = function (v) { return Math.pow(axis.logBase,v); }; - while (true) { - nextTick = nextTick * axis.logBase; + min = axis.inverseTransform(Math.floor(axis.transform(min))); + max = axis.inverseTransform(Math.ceil(axis.transform(max))); + + axis.min = axis.min !== null ? axis.min : min; + axis.max = axis.max !== null ? axis.max : max; + + axis.ticks = []; + var nextTick; + for (nextTick = min; nextTick <= max; nextTick *= axis.logBase) { axis.ticks.push(nextTick); - if (nextTick > max) { - break; - } - } - - if (axis.logBase === 10) { - axis.transform = function(v) { return Math.log(v+0.1); }; - axis.inverseTransform = function (v) { return Math.pow(10,v); }; - } else { - axis.transform = function(v) { return Math.log(v+0.1) / Math.log(axis.logBase); }; - axis.inverseTransform = function (v) { return Math.pow(axis.logBase,v); }; } } diff --git a/public/app/plugins/panel/graph/specs/graph_specs.ts b/public/app/plugins/panel/graph/specs/graph_specs.ts index b9c9362e5de..b383a31a976 100644 --- a/public/app/plugins/panel/graph/specs/graph_specs.ts +++ b/public/app/plugins/panel/graph/specs/graph_specs.ts @@ -172,9 +172,9 @@ describe('grafanaGraph', function() { it('should apply axis transform and ticks', function() { var axis = ctx.plotOptions.yaxes[0]; - expect(axis.transform(100)).to.be(Math.log(100+0.1)); - expect(axis.ticks[0]).to.be(0); - expect(axis.ticks[1]).to.be(1); + expect(axis.transform(100)).to.be(Math.log(100)/Math.log(10)); + expect(axis.ticks[0]).to.be(0.01); + expect(axis.ticks[1]).to.be(0.1); }); }); From 29b9d17faa05bf804eb9667810a29ab94d089885 Mon Sep 17 00:00:00 2001 From: bigbenhur Date: Fri, 3 Jun 2016 22:30:38 +0200 Subject: [PATCH 2/4] fix crash due to zero or negative data values in graph with log scale --- public/app/core/time_series2.ts | 6 ++++++ public/app/plugins/panel/graph/graph.js | 7 ++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/public/app/core/time_series2.ts b/public/app/core/time_series2.ts index dfae26fb48b..d01fe1156b3 100644 --- a/public/app/core/time_series2.ts +++ b/public/app/core/time_series2.ts @@ -97,6 +97,7 @@ export default class TimeSeries { this.stats.total = 0; this.stats.max = -Number.MAX_VALUE; this.stats.min = Number.MAX_VALUE; + this.stats.logmin = Number.MAX_VALUE; this.stats.avg = null; this.stats.current = null; this.allIsNull = true; @@ -133,6 +134,11 @@ export default class TimeSeries { if (currentValue < this.stats.min) { this.stats.min = currentValue; } + + if (currentValue < this.stats.logmin && currentValue > 0) { + this.stats.logmin = currentValue; + } + } if (currentValue !== 0) { diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js index 8019a51354b..00fec42cd6f 100755 --- a/public/app/plugins/panel/graph/graph.js +++ b/public/app/plugins/panel/graph/graph.js @@ -386,11 +386,12 @@ function (angular, $, moment, _, kbn, GraphTooltip) { if (max === null || max < series.stats.max) { max = series.stats.max; } - if (min === null || min > series.stats.min) { - min = series.stats.min; + if (min === null || min > series.stats.logmin) { + min = series.stats.logmin; } } } + if (max === null && min === null) { max = Math.pow(axis.logBase,+2); min = Math.pow(axis.logBase,-2); @@ -400,7 +401,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) { min = max*Math.pow(axis.logBase,-4); } - axis.transform = function(v) { return Math.log(v) / Math.log(axis.logBase); }; + axis.transform = function(v) { return (v < Number.MIN_VALUE) ? null : Math.log(v) / Math.log(axis.logBase); }; axis.inverseTransform = function (v) { return Math.pow(axis.logBase,v); }; min = axis.inverseTransform(Math.floor(axis.transform(min))); From ef623846c1d2d5e610d738bda49127616b5ed59d Mon Sep 17 00:00:00 2001 From: bigbenhur Date: Mon, 6 Jun 2016 10:33:35 +0200 Subject: [PATCH 3/4] further tests for log-autoscale and log-fixedscale; only generate visible ticks; minor changes to prevent crashes due to bad user input --- public/app/plugins/panel/graph/graph.js | 38 ++++++++++++------- .../plugins/panel/graph/specs/graph_specs.ts | 35 ++++++++++++++--- 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js index 00fec42cd6f..a74997d6651 100755 --- a/public/app/plugins/panel/graph/graph.js +++ b/public/app/plugins/panel/graph/graph.js @@ -376,6 +376,9 @@ function (angular, $, moment, _, kbn, GraphTooltip) { if (axis.min < Number.MIN_VALUE) { axis.min = null; } + if (axis.max < Number.MIN_VALUE) { + axis.max = null; + } var series, i; var max = axis.max, min = axis.min; @@ -392,23 +395,32 @@ function (angular, $, moment, _, kbn, GraphTooltip) { } } - if (max === null && min === null) { - max = Math.pow(axis.logBase,+2); - min = Math.pow(axis.logBase,-2); - } else if (max === null) { - max = min*Math.pow(axis.logBase,+4); - } else if (min === null) { - min = max*Math.pow(axis.logBase,-4); - } - axis.transform = function(v) { return (v < Number.MIN_VALUE) ? null : Math.log(v) / Math.log(axis.logBase); }; axis.inverseTransform = function (v) { return Math.pow(axis.logBase,v); }; - min = axis.inverseTransform(Math.floor(axis.transform(min))); - max = axis.inverseTransform(Math.ceil(axis.transform(max))); + if (max === null && min === null) { + max = axis.inverseTransform(+2); + min = axis.inverseTransform(-2); + } else if (max === null) { + max = min*axis.inverseTransform(+4); + } else if (min === null) { + min = max*axis.inverseTransform(-4); + } - axis.min = axis.min !== null ? axis.min : min; - axis.max = axis.max !== null ? axis.max : max; + if (axis.min !== null) { + min = axis.inverseTransform(Math.ceil(axis.transform(axis.min))); + } else { + min = axis.min = axis.inverseTransform(Math.floor(axis.transform(min))); + } + if (axis.max !== null) { + max = axis.inverseTransform(Math.floor(axis.transform(axis.max))); + } else { + max = axis.max = axis.inverseTransform(Math.ceil(axis.transform(max))); + } + + if (min < Number.MIN_VALUE || max < Number.MIN_VALUE) { + return; + } axis.ticks = []; var nextTick; diff --git a/public/app/plugins/panel/graph/specs/graph_specs.ts b/public/app/plugins/panel/graph/specs/graph_specs.ts index b383a31a976..339b14edb3d 100644 --- a/public/app/plugins/panel/graph/specs/graph_specs.ts +++ b/public/app/plugins/panel/graph/specs/graph_specs.ts @@ -166,15 +166,38 @@ describe('grafanaGraph', function() { }); graphScenario('when logBase is log 10', function(ctx) { - ctx.setup(function(ctrl) { + ctx.setup(function(ctrl, data) { ctrl.panel.yaxes[0].logBase = 10; + data[0] = new TimeSeries({ + datapoints: [[2000,1],[0.002,2],[0,3],[-1,4]], + alias: 'seriesAutoscale', + }); + data[0].yaxis = 1; + ctrl.panel.yaxes[1].logBase = 10; + ctrl.panel.yaxes[1].min = 0.05; + ctrl.panel.yaxes[1].max = 1500; + data[1] = new TimeSeries({ + datapoints: [[2000,1],[0.002,2],[0,3],[-1,4]], + alias: 'seriesFixedscale', + }); + data[1].yaxis = 2; }); - it('should apply axis transform and ticks', function() { - var axis = ctx.plotOptions.yaxes[0]; - expect(axis.transform(100)).to.be(Math.log(100)/Math.log(10)); - expect(axis.ticks[0]).to.be(0.01); - expect(axis.ticks[1]).to.be(0.1); + it('should apply axis transform, autoscaling (if necessary) and ticks', function() { + var axisAutoscale = ctx.plotOptions.yaxes[0]; + expect(axisAutoscale.transform(100)).to.be(2); + expect(axisAutoscale.inverseTransform(-3)).to.be(0.001); + expect(axisAutoscale.min).to.be(0.001); + expect(axisAutoscale.max).to.be(10000); + expect(axisAutoscale.ticks.length).to.be(8); + expect(axisAutoscale.ticks[0]).to.be(0.001); + expect(axisAutoscale.ticks[7]).to.be(10000); + var axisFixedscale = ctx.plotOptions.yaxes[1]; + expect(axisFixedscale.min).to.be(0.05); + expect(axisFixedscale.max).to.be(1500); + expect(axisFixedscale.ticks.length).to.be(5); + expect(axisFixedscale.ticks[0]).to.be(0.1); + expect(axisFixedscale.ticks[4]).to.be(1000); }); }); From e19b48840f61073ce3729676b0a1bdd1976c6ca2 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 10 Mar 2017 18:46:36 +0100 Subject: [PATCH 4/4] graph: merge fixes for #5278 This is an old PR so had some problems after merging in master. - Fix for min and max that were not getting passed into the applyLogScale function - Fix for when min/max were undefined rather than null - Fix for decimal ticks, as this PR populates the ticks itself then it also needs to calculate the number of decimal places too. Flot was showing the wrong number of decimal places sometimes otherwise on the y-axis. --- public/app/plugins/panel/graph/graph.ts | 29 ++++++++++++------- .../plugins/panel/graph/specs/graph_specs.ts | 9 ++++-- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 69028a9e407..06af66ddbe7 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -460,7 +460,8 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { show: panel.yaxes[0].show, index: 1, logBase: panel.yaxes[0].logBase || 1, - max: null + min: panel.yaxes[0].min ? _.toNumber(panel.yaxes[0].min) : null, + max: panel.yaxes[0].max ? _.toNumber(panel.yaxes[0].max) : null, }; options.yaxes.push(defaults); @@ -471,12 +472,13 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { secondY.show = panel.yaxes[1].show; secondY.logBase = panel.yaxes[1].logBase || 1; secondY.position = 'right'; + secondY.min = panel.yaxes[1].min ? _.toNumber(panel.yaxes[1].min) : null; + secondY.max = panel.yaxes[1].max ? _.toNumber(panel.yaxes[1].max) : null; options.yaxes.push(secondY); applyLogScale(options.yaxes[1], data); configureAxisMode(options.yaxes[1], panel.percentage && panel.stack ? "percent" : panel.yaxes[1].format); } - applyLogScale(options.yaxes[0], data); configureAxisMode(options.yaxes[0], panel.percentage && panel.stack ? "percent" : panel.yaxes[0].format); } @@ -498,10 +500,10 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { for (i = 0; i < data.length; i++) { series = data[i]; if (series.yaxis === axis.index) { - if (max === null || max < series.stats.max) { + if (!max || max < series.stats.max) { max = series.stats.max; } - if (min === null || min > series.stats.logmin) { + if (!min || min > series.stats.logmin) { min = series.stats.logmin; } } @@ -510,27 +512,27 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { axis.transform = function(v) { return (v < Number.MIN_VALUE) ? null : Math.log(v) / Math.log(axis.logBase); }; axis.inverseTransform = function (v) { return Math.pow(axis.logBase,v); }; - if (max === null && min === null) { + if (!max && !min) { max = axis.inverseTransform(+2); min = axis.inverseTransform(-2); - } else if (max === null) { + } else if (!max) { max = min*axis.inverseTransform(+4); - } else if (min === null) { + } else if (!min) { min = max*axis.inverseTransform(-4); } - if (axis.min !== null) { + if (axis.min) { min = axis.inverseTransform(Math.ceil(axis.transform(axis.min))); } else { min = axis.min = axis.inverseTransform(Math.floor(axis.transform(min))); } - if (axis.max !== null) { + if (axis.max) { max = axis.inverseTransform(Math.floor(axis.transform(axis.max))); } else { max = axis.max = axis.inverseTransform(Math.ceil(axis.transform(max))); } - if (min < Number.MIN_VALUE || max < Number.MIN_VALUE) { + if (!min || min < Number.MIN_VALUE || !max || max < Number.MIN_VALUE) { return; } @@ -539,6 +541,13 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { for (nextTick = min; nextTick <= max; nextTick *= axis.logBase) { axis.ticks.push(nextTick); } + axis.tickDecimals = decimalPlaces(min); + } + + function decimalPlaces(num) { + if (!num) { return 0; } + + return (num.toString().split('.')[1] || []).length; } function configureAxisMode(axis, format) { diff --git a/public/app/plugins/panel/graph/specs/graph_specs.ts b/public/app/plugins/panel/graph/specs/graph_specs.ts index fd958a7fe23..0d8c088f704 100644 --- a/public/app/plugins/panel/graph/specs/graph_specs.ts +++ b/public/app/plugins/panel/graph/specs/graph_specs.ts @@ -121,8 +121,8 @@ describe('grafanaGraph', function() { }); data[0].yaxis = 1; ctrl.panel.yaxes[1].logBase = 10; - ctrl.panel.yaxes[1].min = 0.05; - ctrl.panel.yaxes[1].max = 1500; + ctrl.panel.yaxes[1].min = '0.05'; + ctrl.panel.yaxes[1].max = '1500'; data[1] = new TimeSeries({ datapoints: [[2000,1],[0.002,2],[0,3],[-1,4]], alias: 'seriesFixedscale', @@ -139,12 +139,17 @@ describe('grafanaGraph', function() { expect(axisAutoscale.ticks.length).to.be(8); expect(axisAutoscale.ticks[0]).to.be(0.001); expect(axisAutoscale.ticks[7]).to.be(10000); + expect(axisAutoscale.tickDecimals).to.be(3); + + var axisFixedscale = ctx.plotOptions.yaxes[1]; expect(axisFixedscale.min).to.be(0.05); expect(axisFixedscale.max).to.be(1500); expect(axisFixedscale.ticks.length).to.be(5); expect(axisFixedscale.ticks[0]).to.be(0.1); expect(axisFixedscale.ticks[4]).to.be(1000); + expect(axisFixedscale.tickDecimals).to.be(1); + }); });