From 1b79e179707f2f6d220e295a1474fef3b9e8b36a Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 5 Jun 2017 14:55:59 +0200 Subject: [PATCH] graph: better generation of y-axis ticks for log-scale If there are too many ticks generated for the y-axis (which can occur for log scale 2, with a small y-min and a large max), then the ticks will be regenerated using larger jumps between the ticks. This also handles the case when y-min is set to 0. Previously, y-min of 0 was ignored as zero is not a valid value for log scale. Now the tick generator approximates zero by setting min to 0.1. Ref #8244 Ref #8516 --- public/app/plugins/panel/graph/graph.ts | 38 +++++++++++-- .../plugins/panel/graph/specs/graph_specs.ts | 54 +++++++++++++++++++ 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 726f8be5628..408d89854cb 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -506,6 +506,8 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { return; } + const minSetToZero = axis.min === 0; + if (axis.min < Number.MIN_VALUE) { axis.min = null; } @@ -556,10 +558,17 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { } if (Number.isFinite(min) && Number.isFinite(max)) { - axis.ticks = []; - var nextTick; - for (nextTick = min; nextTick <= max; nextTick *= axis.logBase) { - axis.ticks.push(nextTick); + if (minSetToZero) { + axis.min = 0.1; + min = 1; + } + + axis.ticks = generateTicksForLogScaleYAxis(min, max, axis.logBase); + if (minSetToZero) { + axis.ticks.unshift(0.1); + } + if (axis.ticks[axis.ticks.length - 1] > axis.max) { + axis.max = axis.ticks[axis.ticks.length - 1]; } axis.tickDecimals = decimalPlaces(min); } else { @@ -567,7 +576,28 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { delete axis.min; delete axis.max; } + } + function generateTicksForLogScaleYAxis(min, max, logBase) { + let ticks = []; + + var nextTick; + for (nextTick = min; nextTick <= max; nextTick *= logBase) { + ticks.push(nextTick); + } + + const maxNumTicks = Math.ceil(ctrl.height/25); + const numTicks = ticks.length; + if (numTicks > maxNumTicks) { + const factor = Math.ceil(numTicks/maxNumTicks) * logBase; + ticks = []; + + for (nextTick = min; nextTick <= (max * factor); nextTick *= factor) { + ticks.push(nextTick); + } + } + + return ticks; } function decimalPlaces(num) { diff --git a/public/app/plugins/panel/graph/specs/graph_specs.ts b/public/app/plugins/panel/graph/specs/graph_specs.ts index 9595c3dd544..40cfdb2fcdd 100644 --- a/public/app/plugins/panel/graph/specs/graph_specs.ts +++ b/public/app/plugins/panel/graph/specs/graph_specs.ts @@ -176,6 +176,60 @@ describe('grafanaGraph', function() { }); }); + // y-min set 0 is a special case for log scale, + // this approximates it by setting min to 0.1 + graphScenario('when logBase is log 10 and y-min is set to 0 and auto min is > 0.1', function(ctx) { + ctx.setup(function(ctrl, data) { + ctrl.panel.yaxes[0].logBase = 10; + ctrl.panel.yaxes[0].min = '0'; + data[0] = new TimeSeries({ + datapoints: [[2000,1],[4 ,2],[500,3],[3000,4]], + alias: 'seriesAutoscale', + }); + data[0].yaxis = 1; + }); + + it('should set min to 0.1 and add a tick for 0.1 and tickDecimals to be 0', 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.1); + expect(axisAutoscale.max).to.be(10000); + expect(axisAutoscale.ticks.length).to.be(6); + expect(axisAutoscale.ticks[0]).to.be(0.1); + expect(axisAutoscale.ticks[5]).to.be(10000); + expect(axisAutoscale.tickDecimals).to.be(0); + }); + }); + + graphScenario('when logBase is log 2 and y-min is set to 0 and num of ticks exceeds max', function(ctx) { + ctx.setup(function(ctrl, data) { + const heightForApprox5Ticks = 125; + ctrl.height = heightForApprox5Ticks; + ctrl.panel.yaxes[0].logBase = 2; + ctrl.panel.yaxes[0].min = '0'; + data[0] = new TimeSeries({ + datapoints: [[2000,1],[4 ,2],[500,3],[3000,4], [10000,5], [100000,6]], + alias: 'seriesAutoscale', + }); + data[0].yaxis = 1; + }); + + it('should regenerate ticks so that if fits on the y-axis', function() { + var axisAutoscale = ctx.plotOptions.yaxes[0]; + expect(axisAutoscale.min).to.be(0.1); + expect(axisAutoscale.ticks.length).to.be(8); + expect(axisAutoscale.ticks[0]).to.be(0.1); + expect(axisAutoscale.ticks[7]).to.be(262144); + expect(axisAutoscale.max).to.be(262144); + expect(axisAutoscale.tickDecimals).to.be(0); + }); + + it('should set axis max to be max tick value', function() { + expect(ctx.plotOptions.yaxes[0].max).to.be(262144); + }); + }); + graphScenario('dashed lines options', function(ctx) { ctx.setup(function(ctrl) { ctrl.panel.lines = true;