在图上“好”的网格线间隔的algorithm

我需要一个相当聪明的algorithm来为图表(图表)提供“漂亮的”网格线。

例如,假定一个条形图的值为10,30,72和60.您知道:

最小值:10最大值:72范围:62

第一个问题是:你从什么开始? 在这种情况下,0将是直观的值,但这不会阻止其他数据集,所以我猜测:

网格最小值应该是0或低于范围内数据的最小值的“好”值。 或者,可以指定。

网格最大值应该是范围内最大值以上的“好”值。 或者,可以指定它(例如,如果显示百分比,则可能需要0到100,而不考虑实际值)。

该范围内的网格线(滴答)的数量应该是指定的或在给定范围内的数字(例如3-8),这样的值是“好”(即整数),并最大限度地使用图表区域。 在我们的例子中,80将是一个明智的最大值,因为这将使用90%的图表高度(72/80),而100会创build更多的浪费空间。

任何人都知道这个好的algorithm? 语言是无关紧要的,因为我会在我需要的地方实施它。

CPAN 在这里提供了一个实现(请参阅源代码链接)

另请参阅graphics轴的Tickmarkalgorithm

仅供参考,与您的样品数据:

  • 枫:Min = 8,Max = 74,Labels = 10,20,..,60,70,Ticks = 10,12,14,… 70,72
  • MATLAB:Min = 10,Max = 80,Labels = 10,20 ,, ..,60,80

我用蛮力的方法做了这个。 首先,找出可以放入空间的刻度线的最大数量。 将总值的范围除以滴答的数量; 这是滴答的最小间距。 现在计算对数基底10的底面,得到刻度的大小,除以这个值。 您应该以1到10的范围内的值来结束。只需select大于或等于该值的整数,然后再乘以之前计算的对数。 这是您的最后滴答间隔。

Python中的示例:

import math def BestTick(largest, mostticks): minimum = largest / mostticks magnitude = 10 ** math.floor(math.log(minimum, 10)) residual = minimum / magnitude if residual > 5: tick = 10 * magnitude elif residual > 2: tick = 5 * magnitude elif residual > 1: tick = 2 * magnitude else: tick = magnitude return tick 

编辑:你可以自由地改变“好”间隔的select。 一位评论者似乎对所提供的select不满,因为实际的蜱数量可以比最大值小2.5倍。 这里有一个细微的修改,定义了一个很好的时间间隔表。 在这个例子中,我已经扩展了select,以便滴答的数量不会less于最大值的3/5。

 import bisect def BestTick2(largest, mostticks): minimum = largest / mostticks magnitude = 10 ** math.floor(math.log(minimum, 10)) residual = minimum / magnitude # this table must begin with 1 and end with 10 table = [1, 1.5, 2, 3, 5, 7, 10] tick = table[bisect.bisect_right(table, residual)] if residual < 10 else 10 return tick * magnitude 

有两件事问题:

  1. 确定涉及的数量级,和
  2. 围绕一些方便。

你可以使用对数来处理第一部分:

 range = max - min; exponent = int(log(range)); // See comment below. magnitude = pow(10, exponent); 

所以,例如,如果您的范围是从50 – 1200,指数是3,数量级是1000。

然后通过决定你的网格中有多less个细分来处理第二部分:

 value_per_division = magnitude / subdivisions; 

这是一个粗略的计算,因为指数已被截断为一个整数。 您可能需要调整指数计算以更好地处理边界条件, 例如 ,如果最终得到太多细分,则可以舍入而不是采用int()

我使用以下algorithm。 这与其他人发布的类似,但它是C#中的第一个例子。

 public static class AxisUtil { public static float CalcStepSize(float range, float targetSteps) { // calculate an initial guess at step size var tempStep = range/targetSteps; // get the magnitude of the step size var mag = (float)Math.Floor(Math.Log10(tempStep)); var magPow = (float)Math.Pow(10, mag); // calculate most significant digit of the new step size var magMsd = (int)(tempStep/magPow + 0.5); // promote the MSD to either 1, 2, or 5 if (magMsd > 5) magMsd = 10; else if (magMsd > 2) magMsd = 5; else if (magMsd > 1) magMsd = 2; return magMsd*magPow; } } 

以下是JavaScript中的另一个实现:

 var ln10 = Math.log(10); var calcStepSize = function(range, targetSteps) { // calculate an initial guess at step size var tempStep = range / targetSteps; // get the magnitude of the step size var mag = Math.floor(Math.log(tempStep) / ln10); var magPow = Math.pow(10, mag); // calculate most significant digit of the new step size var magMsd = Math.round(tempStep / magPow + 0.5); // promote the MSD to either 1, 2, or 5 if (magMsd > 5.0) magMsd = 10.0; else if (magMsd > 2.0) magMsd = 5.0; else if (magMsd > 1.0) magMsd = 2.0; return magMsd * magPow; }; 

我写了一个objective-c方法来返回一个好的轴比例和好的滴答声,给出你的数据集的最小值和最大值:

 - (NSArray*)niceAxis:(double)minValue :(double)maxValue { double min_ = 0, max_ = 0, min = minValue, max = maxValue, power = 0, factor = 0, tickWidth, minAxisValue = 0, maxAxisValue = 0; NSArray *factorArray = [NSArray arrayWithObjects:@"0.0f",@"1.2f",@"2.5f",@"5.0f",@"10.0f",nil]; NSArray *scalarArray = [NSArray arrayWithObjects:@"0.2f",@"0.2f",@"0.5f",@"1.0f",@"2.0f",nil]; // calculate x-axis nice scale and ticks // 1. min_ if (min == 0) { min_ = 0; } else if (min > 0) { min_ = MAX(0, min-(max-min)/100); } else { min_ = min-(max-min)/100; } // 2. max_ if (max == 0) { if (min == 0) { max_ = 1; } else { max_ = 0; } } else if (max < 0) { max_ = MIN(0, max+(max-min)/100); } else { max_ = max+(max-min)/100; } // 3. power power = log(max_ - min_) / log(10); // 4. factor factor = pow(10, power - floor(power)); // 5. nice ticks for (NSInteger i = 0; factor > [[factorArray objectAtIndex:i]doubleValue] ; i++) { tickWidth = [[scalarArray objectAtIndex:i]doubleValue] * pow(10, floor(power)); } // 6. min-axisValues minAxisValue = tickWidth * floor(min_/tickWidth); // 7. min-axisValues maxAxisValue = tickWidth * floor((max_/tickWidth)+1); // 8. create NSArray to return NSArray *niceAxisValues = [NSArray arrayWithObjects:[NSNumber numberWithDouble:minAxisValue], [NSNumber numberWithDouble:maxAxisValue],[NSNumber numberWithDouble:tickWidth], nil]; return niceAxisValues; } 

你可以这样调用方法:

 NSArray *niceYAxisValues = [self niceAxis:-maxy :maxy]; 

并让你的轴设置:

 double minYAxisValue = [[niceYAxisValues objectAtIndex:0]doubleValue]; double maxYAxisValue = [[niceYAxisValues objectAtIndex:1]doubleValue]; double ticksYAxis = [[niceYAxisValues objectAtIndex:2]doubleValue]; 

以防万一你想限制轴蜱的数量这样做:

 NSInteger maxNumberOfTicks = 9; NSInteger numberOfTicks = valueXRange / ticksXAxis; NSInteger newNumberOfTicks = floor(numberOfTicks / (1 + floor(numberOfTicks/(maxNumberOfTicks+0.5)))); double newTicksXAxis = ticksXAxis * (1 + floor(numberOfTicks/(maxNumberOfTicks+0.5))); 

代码的第一部分是基于我在这里计算出的计算好的graphics轴比例和与Excelgraphics类似的刻度。 它适用于所有types的数据集。 这里是一个iPhone实现的例子:

在这里输入图像说明

从上面的标记,在C#中稍微更完整的Util类。 这也计算出合适的第一个和最后一个滴答。

 public class AxisAssists { public double Tick { get; private set; } public AxisAssists(double aTick) { Tick = aTick; } public AxisAssists(double range, int mostticks) { var minimum = range / mostticks; var magnitude = Math.Pow(10.0, (Math.Floor(Math.Log(minimum) / Math.Log(10)))); var residual = minimum / magnitude; if (residual > 5) { Tick = 10 * magnitude; } else if (residual > 2) { Tick = 5 * magnitude; } else if (residual > 1) { Tick = 2 * magnitude; } else { Tick = magnitude; } } public double GetClosestTickBelow(double v) { return Tick* Math.Floor(v / Tick); } public double GetClosestTickAbove(double v) { return Tick * Math.Ceiling(v / Tick); } } With ability to create an instance ,but if you just want calculate and throw it away: double tickX = new AxisAssists(aMaxX - aMinX, 8).Tick; 

另一个想法是让轴的范围是值的范围,但把刻度线放在适当的位置。即7到22做:

 [ -   -   -  |  -   -   -   -  |  -   -   -   -  |  -   - ]
        10 15 20

至于select滴答间隔,我会build议任何数量的forms10 ^ x * i / n,其中我<n和0 <n <10.生成此列表,并对其sorting,您可以find最大的数字小于value_per_division(如在adam_liss中)使用二进制search。

我是“ 图表轴上的最佳缩放algorithm ”的作者。 它曾经托pipe在trollop.org,但我最近移动了域/博客引擎。

请看我对相关问题的回答 。

这里是我写的一个javascript函数来将网格间隔(max-min)/gridLinesNumber为美丽的值。 它适用于任何数字,请参阅要点 ,详细说明如何使用以及如何调用它。

 var ceilAbs = function(num, to, bias) { if (to == undefined) to = [-2, -5, -10] if (bias == undefined) bias = 0 var numAbs = Math.abs(num) - bias var exp = Math.floor( Math.log10(numAbs) ) if (typeof to == 'number') { return Math.sign(num) * to * Math.ceil(numAbs/to) + bias } var mults = to.filter(function(value) {return value > 0}) to = to.filter(function(value) {return value < 0}).map(Math.abs) var m = Math.abs(numAbs) * Math.pow(10, -exp) var mRounded = Infinity for (var i=0; i<mults.length; i++) { var candidate = mults[i] * Math.ceil(m / mults[i]) if (candidate < mRounded) mRounded = candidate } for (var i=0; i<to.length; i++) { if (to[i] >= m && to[i] < mRounded) mRounded = to[i] } return Math.sign(num) * mRounded * Math.pow(10, exp) + bias } 

调用ceilAbs(number, [0.5])为不同的数字将整数像这样:

 301573431.1193228 -> 350000000 14127.786597236991 -> 15000 -63105746.17236853 -> -65000000 -718854.2201183736 -> -750000 -700660.340487957 -> -750000 0.055717507097870114 -> 0.06 0.0008068701205775142 -> 0.00085 -8.66660070605576 -> -9 -400.09256079792976 -> -450 0.0011740548815578223 -> 0.0015 -5.3003294346854085e-8 -> -6e-8 -0.00005815960629843176 -> -0.00006 -742465964.5184875 -> -750000000 -81289225.90985894 -> -85000000 0.000901771713513881 -> 0.00095 -652726598.5496342 -> -700000000 -0.6498901364393532 -> -0.65 0.9978325804695487 -> 1 5409.4078950583935 -> 5500 26906671.095639467 -> 30000000 

看看小提琴来试验代码。 答案中的代码,要点和提琴略有不同我正在使用答案中给出的。

从这里已经可用的答案中得到很多灵感,这里是我在C中的实现。请注意, ndex数组中有一些扩展。

 float findNiceDelta(float maxvalue, int count) { float step = maxvalue/count, order = powf(10, floorf(log10(step))), delta = (int)(step/order + 0.5); static float ndex[] = {1, 1.5, 2, 2.5, 5, 10}; static int ndexLenght = sizeof(ndex)/sizeof(float); for(int i = ndexLenght - 2; i > 0; --i) if(delta > ndex[i]) return ndex[i + 1] * order; return delta*order; } 

在R中使用

 tickSize <- function(range,minCount){ logMaxTick <- log10(range/minCount) exponent <- floor(logMaxTick) mantissa <- 10^(logMaxTick-exponent) af <- c(1,2,5) # allowed factors mantissa <- af[findInterval(mantissa,af)] return(mantissa*10^exponent) } 

范围参数是域的最大最小值。

如果你正在试图让VB.NET图表看起来正确,那么我已经使用了Adam Liss的例子,但是确保当你设置最小和最大比例值时,你将它们从一个十进制的variables(不是types单或双),否则刻度值最终被设置为8位小数。 举个例子,我有1个图表,我把min Y轴的值设置为0.0001,最大的Y轴的值设置为0.002。 如果我将这些值传递给图表对象作为单打,我得到的刻度值为0.00048000001697801,0.000860000036482233 ….而如果我将这些值作为小数传递给图表对象,我得到好的刻度值为0.00048,0.00086 …. ..