### 图上“漂亮”网格线间隔的算法

#### 16 个答案:

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

``````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. 回合方便。
3. 您可以使用对数处理第一部分：

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

因此，例如，如果您的范围是50 - 1200，则指数为3，幅度为1000。

然后通过决定你的网格中需要多少个细分来处理第二部分：

``````value_per_division = magnitude / subdivisions;
``````

这是一个粗略计算，因为指数已被截断为整数。您可能想要调整指数计算以更好地处理边界条件，例如。通过舍入而不是在最终得到太多细分的情况下取`int()`

``````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;
}
}
``````

CPAN提供了一个实现here（参见源链接）

• Maple：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

``````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) / Math.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;
};
``````

``````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;
``````

``````- (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)));
``````

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

``````steps = [numpy.round(x) for x in np.linspace(min, max, num=num_of_steps)]
``````

forcePlotZero更改了四舍五入最大值的方式，因此它将始终是一个很好的倍数，然后返回零。示例：

<身体>

forcePlotZero = false 最小值：-104最大值：240 最小值：-160最大值：240 intervalCount：5 intervalSize：100
forcePlotZero = true 最小值：-104最大值：240 最小值：-200最大值：300 intervalCount：6 intervalSize：100
forcePlotZero = false 最小值：40最大值：1240 最小值：0最大值：1300 intervalCount：14 intervalSize：100
forcePlotZero = false 最小值：200最大值：240 最小值：190最大值：240 intervalCount：6 intervalSize：10
forcePlotZero = false 最小值：0.7最大值：1.12 最小值：0.6最大值：1.2 intervalCount：7 intervalSize：0.1
forcePlotZero = false 最低：-70.5最高：-12.5 记住：-80最大：-10 intervalCount：8 intervalSize：10
``````func getMaxMinIntervals(max float64, min float64, forcePlotZero bool) (maxRounded float64, minRounded float64, intervalCount float64, intervalSize float64) {

//STEP 1: start off determining the maxRounded value for the axis
precision := 0.0
precisionDampener := 0.0 //adjusts to prevent 235 going to 300, instead dampens the scaling to get 240
epsilon := 0.0000001
if math.Abs(max) >= 0 && math.Abs(max) < 2 {
precision = math.Floor(-math.Log10(epsilon + math.Abs(max) - math.Floor(math.Abs(max)))) //counting number of zeros between decimal point and rightward digits
precisionDampener = 1
precision = precision + precisionDampener
} else if math.Abs(max) >= 2 && math.Abs(max) < 100 {
precision = math.Ceil(math.Log10(math.Abs(max)+1)) * -1 //else count number of digits before decimal point
precisionDampener = 1
precision = precision + precisionDampener
} else {
precision = math.Ceil(math.Log10(math.Abs(max)+1)) * -1 //else count number of digits before decimal point
precisionDampener = 2
if forcePlotZero == true {
precisionDampener = 1
}
precision = precision + precisionDampener
}

useThisFactorForIntervalCalculation := 0.0 // this is needed because intervals are calculated from the max value with a zero origin, this uses range for min - max
if max < 0 {
maxRounded = (math.Floor(math.Abs(max)*(math.Pow10(int(precision)))) / math.Pow10(int(precision)) * -1)
useThisFactorForIntervalCalculation = (math.Floor(math.Abs(max)*(math.Pow10(int(precision)))) / math.Pow10(int(precision))) + ((math.Ceil(math.Abs(min)*(math.Pow10(int(precision)))) / math.Pow10(int(precision))) * -1)
} else {
maxRounded = math.Ceil(max*(math.Pow10(int(precision)))) / math.Pow10(int(precision))
useThisFactorForIntervalCalculation = maxRounded
}

minNumberOfIntervals := 2.0
maxNumberOfIntervals := 19.0
intervalSize = 0.001
intervalCount = minNumberOfIntervals

//STEP 2: get interval size (the step size on the axis)
for {
if math.Abs(useThisFactorForIntervalCalculation)/intervalSize < minNumberOfIntervals || math.Abs(useThisFactorForIntervalCalculation)/intervalSize > maxNumberOfIntervals {
intervalSize = intervalSize * 10
} else {
break
}
}

//STEP 3: check that intervals are not too large, safety for max and min values that are close together (240, 220 etc)
for {
if max-min < intervalSize {
intervalSize = intervalSize / 10
} else {
break
}
}

//STEP 4: now we can get minRounded by adding the interval size to 0 till we get to the point where another increment would make cumulative increments > min, opposite for negative in
minRounded = 0.0

if min >= 0 {
for {
if minRounded < min {
minRounded = minRounded + intervalSize
} else {
minRounded = minRounded - intervalSize
break
}
}
} else {
minRounded = maxRounded //keep going down, decreasing by the interval size till minRounded < min
for {
if minRounded > min {
minRounded = minRounded - intervalSize

} else {
break
}
}
}

//STEP 5: get number of intervals to draw
intervalCount = (maxRounded - minRounded) / intervalSize
intervalCount = math.Ceil(intervalCount) + 1 // include the origin as an interval

//STEP 6: Check that the intervalCount isn't too high
if intervalCount-1 >= (intervalSize * 2) && intervalCount > maxNumberOfIntervals {
intervalCount = math.Ceil(intervalCount / 2)
intervalSize *= 2
}

return}
``````

``````import numpy as np

def create_ticks(lo,hi):
s = 10**(np.floor(np.log10(hi - lo)))
start = s * np.floor(lo / s)
end = s * np.ceil(hi / s)
ticks = [start]
t = start
while (t <  end):
ticks += [t]
t = t + s

return ticks
``````

``````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;
}
``````

``````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)
}
``````

``````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
}
``````

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