最小刻度线图表的优秀标签算法

40

我需要手动计算图表的Ticklabels和Tickrange。

我知道“标准”算法可以生成漂亮的刻度线(参见http://books.google.de/books?id=fvA7zLEFWZgC&pg=PA61&lpg=PA61&redir_esc=y#v=onepage&q&f=false),我也知道这个Java实现

问题是,这个算法生成的刻度线有点“太聪明”了。也就是说,算法决定了应该显示多少个刻度线。我的要求是始终有5个刻度线,但这些刻度线当然应该是漂亮的。朴素的方法是取最大值,除以5,再乘以刻度线数。这里的值显然不是最优的,而且刻度线也不够美观。

请问有人知道解决这个问题的方法或者有一个正式的算法描述提示吗?


最小值和最大值总是刻度吗?还是最低/最高刻度应该是下一个较小/较高的好数字? - Thomas
@Thomas 在我的情况下,最小的刻度始终为0,最高的刻度应该是下一个更高的好数字。 - Dominik Obermaier
+1,因为它帮助我解决了类似的问题。不过你有没有见过日期刻度线的实现?这可能会有些棘手。例如主要增量为月份,次要增量为周数——由于月份可变,无法按周来划分月份。 - Dr. Andrew Burnett-Thompson
1
可能是为图表的Y轴选择一个吸引人的线性刻度的重复问题。 - Clinton Pierce
22个回答

92
我是“图表坐标轴最优缩放算法”的作者。它以前托管在trollop.org上,但我最近更换了域名/博客引擎。无论如何,我将在这里发布内容,以便更容易地访问。
我一直在为一个Android图表应用程序而工作,并遇到了一个问题,即如何以良好的比例格式呈现图表。我花了一些时间试图自己创建这个算法,并且接近成功,但最终我在Andrew S. Glassner的书《Graphics Gems,Volume 1》中找到了一个伪代码示例。在“图形标签的漂亮数字”章节中,给出了该问题的优秀描述:
在计算机上创建图形时,希望使用“好看”的数字:简单的十进制数字来标记x和y轴。例如,如果数据范围是105到543,我们可能希望将范围从100到600绘制出来,并且每100个单位放置一个刻度标记。或者,如果数据范围是2.04到2.16,我们可能会绘制从2.00到2.20的范围,并以0.05的间隔放置刻度标记。人类擅长选择这样的“好看”数字,但简单的算法不行。朴素的标签选择算法将数据范围分成n个等间隔,但这通常会导致刻度标签难看。我们在这里描述了一种生成好看的图形标签的简单方法。
主要观察结果是,在十进制中,“最好看”的数字是1、2和5,以及这些数字的十倍数。我们仅使用此类数字作为刻度间距,并在刻度间距的倍数处放置刻度标记...
我使用本书中的伪代码示例来创建了以下Java类:
public class NiceScale {

  private double minPoint;
  private double maxPoint;
  private double maxTicks = 10;
  private double tickSpacing;
  private double range;
  private double niceMin;
  private double niceMax;

  /**
   * Instantiates a new instance of the NiceScale class.
   *
   * @param min the minimum data point on the axis
   * @param max the maximum data point on the axis
   */
  public NiceScale(double min, double max) {
    this.minPoint = min;
    this.maxPoint = max;
    calculate();
  }

  /**
   * Calculate and update values for tick spacing and nice
   * minimum and maximum data points on the axis.
   */
  private void calculate() {
    this.range = niceNum(maxPoint - minPoint, false);
    this.tickSpacing = niceNum(range / (maxTicks - 1), true);
    this.niceMin =
      Math.floor(minPoint / tickSpacing) * tickSpacing;
    this.niceMax =
      Math.ceil(maxPoint / tickSpacing) * tickSpacing;
  }

  /**
   * Returns a "nice" number approximately equal to range Rounds
   * the number if round = true Takes the ceiling if round = false.
   *
   * @param range the data range
   * @param round whether to round the result
   * @return a "nice" number to be used for the data range
   */
  private double niceNum(double range, boolean round) {
    double exponent; /** exponent of range */
    double fraction; /** fractional part of range */
    double niceFraction; /** nice, rounded fraction */

    exponent = Math.floor(Math.log10(range));
    fraction = range / Math.pow(10, exponent);

    if (round) {
      if (fraction < 1.5)
        niceFraction = 1;
      else if (fraction < 3)
        niceFraction = 2;
      else if (fraction < 7)
        niceFraction = 5;
      else
        niceFraction = 10;
    } else {
      if (fraction <= 1)
        niceFraction = 1;
      else if (fraction <= 2)
        niceFraction = 2;
      else if (fraction <= 5)
        niceFraction = 5;
      else
        niceFraction = 10;
    }

    return niceFraction * Math.pow(10, exponent);
  }

  /**
   * Sets the minimum and maximum data points for the axis.
   *
   * @param minPoint the minimum data point on the axis
   * @param maxPoint the maximum data point on the axis
   */
  public void setMinMaxPoints(double minPoint, double maxPoint) {
    this.minPoint = minPoint;
    this.maxPoint = maxPoint;
    calculate();
  }

  /**
   * Sets maximum number of tick marks we're comfortable with
   *
   * @param maxTicks the maximum number of tick marks for the axis
   */
  public void setMaxTicks(double maxTicks) {
    this.maxTicks = maxTicks;
    calculate();
  }
}

我们可以像这样使用上述代码:
NiceScale numScale = new NiceScale(-0.085, 0.173);

System.out.println("Tick Spacing:\t" + numScale.getTickSpacing());
System.out.println("Nice Minimum:\t" + numScale.getNiceMin());
System.out.println("Nice Maximum:\t" + numScale.getNiceMax());

那么,这将输出漂亮格式的数字,以便您可以在需要创建漂亮刻度的任何应用程序中使用。=D

Tick Spacing: 0.05
Nice Minimum: -0.1
Nice Maximum: 0.2

2
我已经用JavaScript实现了这个功能,它的表现非常好,但是我遇到了一个例外情况。将maxTicks设置为20,minPoint设置为0,maxPoint设置为5。我发现需要25个刻度才能填满从0到25的范围,而不是最大的20个。我认为通过在niceNum函数中将<=改为<来解决了这个问题,但我并不完全确定。 - Ceres
1
我怀疑你现在看不到这个信息,但是你的网站好像挂了。 - scrblnrd3
你的算法对于正数可以正常运行,但是如果最大值和最小值都是负数,那么对数函数就无法工作。在这种情况下,你会考虑什么替代方案? - Debanjan Chakraborty
1
@DebanjanChakraborty 范围仍将为正数。例如:min = -10max = -5,则 range = max - min = 5。但是,如果您想要反转 y 轴,则会得到负范围。要处理这种情况,只需在对数表达式中取范围的绝对值即可。 - Klas Lindbäck
3
@AlexanderDanilov,您说Incongruous的答案对您的应用程序“效果很差”,因此我期望在您下面的回答中看到一些问题的描述! @AlexanderDanilov表示Incongruous的回答在他的应用程序中表现不佳,因此期望在他的回答中描述问题。 - JohnK
显示剩余3条评论

10

这是JavaScript版:

var minPoint;
var maxPoint;
var maxTicks = 10;
var tickSpacing;
var range;
var niceMin;
var niceMax;

/**
 * Instantiates a new instance of the NiceScale class.
 *
 *  min the minimum data point on the axis
 *  max the maximum data point on the axis
 */
function niceScale( min, max) {
    minPoint = min;
    maxPoint = max;
    calculate();
    return {
        tickSpacing: tickSpacing,
        niceMinimum: niceMin,
        niceMaximum: niceMax
    };
}



/**
 * Calculate and update values for tick spacing and nice
 * minimum and maximum data points on the axis.
 */
function calculate() {
    range = niceNum(maxPoint - minPoint, false);
    tickSpacing = niceNum(range / (maxTicks - 1), true);
    niceMin =
      Math.floor(minPoint / tickSpacing) * tickSpacing;
    niceMax =
      Math.ceil(maxPoint / tickSpacing) * tickSpacing;
}

/**
 * Returns a "nice" number approximately equal to range Rounds
 * the number if round = true Takes the ceiling if round = false.
 *
 *  localRange the data range
 *  round whether to round the result
 *  a "nice" number to be used for the data range
 */
function niceNum( localRange,  round) {
    var exponent; /** exponent of localRange */
    var fraction; /** fractional part of localRange */
    var niceFraction; /** nice, rounded fraction */

    exponent = Math.floor(Math.log10(localRange));
    fraction = localRange / Math.pow(10, exponent);

    if (round) {
        if (fraction < 1.5)
            niceFraction = 1;
        else if (fraction < 3)
            niceFraction = 2;
        else if (fraction < 7)
            niceFraction = 5;
        else
            niceFraction = 10;
    } else {
        if (fraction <= 1)
            niceFraction = 1;
        else if (fraction <= 2)
            niceFraction = 2;
        else if (fraction <= 5)
            niceFraction = 5;
        else
            niceFraction = 10;
    }

    return niceFraction * Math.pow(10, exponent);
}

/**
 * Sets the minimum and maximum data points for the axis.
 *
 *  minPoint the minimum data point on the axis
 *  maxPoint the maximum data point on the axis
 */
function setMinMaxPoints( localMinPoint,  localMaxPoint) {
    minPoint = localMinPoint;
    maxPoint = localMaxoint;
    calculate();
}

/**
 * Sets maximum number of tick marks we're comfortable with
 *
 *  maxTicks the maximum number of tick marks for the axis
 */
function setMaxTicks(localMaxTicks) {
    maxTicks = localMaxTicks;
    calculate();
}

享受!


7

根据我的需求,我已将上述Java代码转换为Python代码。

 import math

  class NiceScale:
    def __init__(self, minv,maxv):
        self.maxTicks = 6
        self.tickSpacing = 0
        self.lst = 10
        self.niceMin = 0
        self.niceMax = 0
        self.minPoint = minv
        self.maxPoint = maxv
        self.calculate()

    def calculate(self):
        self.lst = self.niceNum(self.maxPoint - self.minPoint, False)
        self.tickSpacing = self.niceNum(self.lst / (self.maxTicks - 1), True)
        self.niceMin = math.floor(self.minPoint / self.tickSpacing) * self.tickSpacing
        self.niceMax = math.ceil(self.maxPoint / self.tickSpacing) * self.tickSpacing

    def niceNum(self, lst, rround):
        self.lst = lst
        exponent = 0 # exponent of range */
        fraction = 0 # fractional part of range */
        niceFraction = 0 # nice, rounded fraction */

        exponent = math.floor(math.log10(self.lst));
        fraction = self.lst / math.pow(10, exponent);

        if (self.lst):
            if (fraction < 1.5):
                niceFraction = 1
            elif (fraction < 3):
                niceFraction = 2
            elif (fraction < 7):
                niceFraction = 5;
            else:
                niceFraction = 10;
        else :
            if (fraction <= 1):
                niceFraction = 1
            elif (fraction <= 2):
                niceFraction = 2
            elif (fraction <= 5):
                niceFraction = 5
            else:
                niceFraction = 10

        return niceFraction * math.pow(10, exponent)

    def setMinMaxPoints(self, minPoint, maxPoint):
          self.minPoint = minPoint
          self.maxPoint = maxPoint
          self.calculate()

    def setMaxTicks(self, maxTicks):
        self.maxTicks = maxTicks;
        self.calculate()

a=NiceScale(14024, 17756)
print "a.lst ", a.lst
print "a.maxPoint ", a.maxPoint
print "a.maxTicks ", a.maxTicks
print "a.minPoint ", a.minPoint
print "a.niceMax ", a.niceMax
print "a.niceMin ", a.niceMin
print "a.tickSpacing ", a.tickSpacing

3
在niceNum函数中,这行代码"if (self.lst):"应为"if (rround):"。 - user1114907

4

这是TypeScript版本的代码!

/**
 * Calculate and update values for tick spacing and nice
 * minimum and maximum data points on the axis.
 */
function calculateTicks(maxTicks: number, minPoint: number, maxPoint: number): [number, number, number] {
    let range = niceNum(maxPoint - minPoint, false);
    let tickSpacing = niceNum(range / (maxTicks - 1), true);
    let niceMin = Math.floor(minPoint / tickSpacing) * tickSpacing;
    let niceMax = Math.ceil(maxPoint / tickSpacing) * tickSpacing;
    let tickCount = range / tickSpacing;
    return [tickCount, niceMin, niceMax];
}

/**
 * Returns a "nice" number approximately equal to range Rounds
 * the number if round = true Takes the ceiling if round = false.
 *
 * @param range the data range
 * @param round whether to round the result
 * @return a "nice" number to be used for the data range
 */
function niceNum(range: number, round: boolean): number {
    let exponent: number;
    /** exponent of range */
    let fraction: number;
    /** fractional part of range */
    let niceFraction: number;
    /** nice, rounded fraction */

    exponent = Math.floor(Math.log10(range));
    fraction = range / Math.pow(10, exponent);

    if (round) {
        if (fraction < 1.5)
            niceFraction = 1;
        else if (fraction < 3)
            niceFraction = 2;
        else if (fraction < 7)
            niceFraction = 5;
        else
            niceFraction = 10;
    } else {
        if (fraction <= 1)
            niceFraction = 1;
        else if (fraction <= 2)
            niceFraction = 2;
        else if (fraction <= 5)
            niceFraction = 5;
        else
            niceFraction = 10;
    }

    return niceFraction * Math.pow(10, exponent);
}

1
calculateTicks 应该返回 [tickSpacing, niceMin, niceMax] 而不是 [tickCount, niceMin, niceMax]。 - andyb

4

以下是 Swift 版本:

class NiceScale {
    private var minPoint: Double
    private var maxPoint: Double
    private var maxTicks = 10
    private(set) var tickSpacing: Double = 0
    private(set) var range: Double = 0
    private(set) var niceMin: Double = 0
    private(set) var niceMax: Double = 0

    init(min: Double, max: Double) {
        minPoint = min
        maxPoint = max
        calculate()
    }

    func setMinMaxPoints(min: Double, max: Double) {
        minPoint = min
        maxPoint = max
        calculate()
    }

    private func calculate() {
        range = niceNum(maxPoint - minPoint, round: false)
        tickSpacing = niceNum(range / Double((maxTicks - 1)), round: true)
        niceMin = floor(minPoint / tickSpacing) * tickSpacing
        niceMax = floor(maxPoint / tickSpacing) * tickSpacing
    }

    private func niceNum(range: Double, round: Bool) -> Double {
        let exponent = floor(log10(range))
        let fraction = range / pow(10, exponent)
        let niceFraction: Double

        if round {
            if fraction <= 1.5 {
                niceFraction = 1
            } else if fraction <= 3 {
                niceFraction = 2
            } else if fraction <= 7 {
                niceFraction = 5
            } else {
                niceFraction = 10
            }
        } else {
            if fraction <= 1 {
                niceFraction = 1
            } else if fraction <= 2 {
                niceFraction = 2
            } else if fraction <= 5 {
                niceFraction = 5
            } else {
                niceFraction = 10
            }
        }

        return niceFraction * pow(10, exponent)
    }
}

原始的Java版本使用Math.ceil来计算niceMax。但是这个Swift版本使用了floor,我认为这一定是一个错误。 - Hiron
感谢提供 Swift 版本!我需要在以下代码中添加参数标签“range”: range = niceNum(range: maxPoint - minPoint, round: false) - David
我需要将Java示例转换为Swift,所以我认为我会进行下一步并编写一个单元测试,以查看我们对“好”的定义是否匹配。 - David
很高兴我正在添加单元测试:发现了这个差异 test_NiceGridLinesValues():XCTAssertEqual失败:(“0.2”)不等于(“0.15000000000000002”) 忽略双精度问题...输入最大值0.173将被一个niceMax的0.15截断! - David
确实,将Hiron的“ceil”替换为“floor”可以使测试通过。 - David

4
这是 C++ 版本。作为奖励,您将获得一个函数,用于返回在坐标轴上显示刻度标签所需的最小十进制位数。
头文件为:
class NiceScale 
{   public:

    float minPoint;
    float maxPoint;
    float maxTicks;
    float tickSpacing;
    float range;
    float niceMin;
    float niceMax;

    public:
    NiceScale()
    {   maxTicks = 10;
    }

    /**
    * Instantiates a new instance of the NiceScale class.
    *
    * @param min the minimum data point on the axis
    * @param max the maximum data point on the axis
    */
    NiceScale(float min, float max) 
    {   minPoint = min;
        maxPoint = max;
        calculate();
    }

    /**
    * Calculate and update values for tick spacing and nice
    * minimum and maximum data points on the axis.
    */
    void calculate() ;

    /**
    * Returns a "nice" number approximately equal to range Rounds
    * the number if round = true Takes the ceiling if round = false.
    *
    * @param range the data range
    * @param round whether to round the result
    * @return a "nice" number to be used for the data range
    */
    float niceNum(float range, boolean round) ;

    /**
    * Sets the minimum and maximum data points for the axis.
    *
    * @param minPoint the minimum data point on the axis
    * @param maxPoint the maximum data point on the axis
    */
    void setMinMaxPoints(float minPoint, float maxPoint) ;

    /**
    * Sets maximum number of tick marks we're comfortable with
    *
    * @param maxTicks the maximum number of tick marks for the axis
    */
    void setMaxTicks(float maxTicks) ;
    int decimals(void);
};

下面是 CPP 文件:

/**
* Calculate and update values for tick spacing and nice
* minimum and maximum data points on the axis.
*/
void NiceScale::calculate() 
{
    range = niceNum(maxPoint - minPoint, false);
    tickSpacing = niceNum(range / (maxTicks - 1), true);
    niceMin = floor(minPoint / tickSpacing) * tickSpacing;
    niceMax = ceil(maxPoint / tickSpacing) * tickSpacing;
}

/**
* Returns a "nice" number approximately equal to range 
  Rounds the number if round = true Takes the ceiling if round = false.
*
* @param range the data range
* @param round whether to round the result
* @return a "nice" number to be used for the data range
*/
float NiceScale::niceNum(float range, boolean round) 
{   float exponent; /** exponent of range */
    float fraction; /** fractional part of range */
    float niceFraction; /** nice, rounded fraction */

    exponent = floor(log10(range));
    fraction = range / pow(10.f, exponent);

    if (round) 
    {   if (fraction < 1.5)
            niceFraction = 1;
        else if (fraction < 3)
            niceFraction = 2;
        else if (fraction < 7)
            niceFraction = 5;
        else
            niceFraction = 10;
    } 
    else 
    {   if (fraction <= 1)
            niceFraction = 1;
        else if (fraction <= 2)
            niceFraction = 2;
        else if (fraction <= 5)
            niceFraction = 5;
        else
            niceFraction = 10;
    }

    return niceFraction * pow(10, exponent);
}

/**
* Sets the minimum and maximum data points for the axis.
*
* @param minPoint the minimum data point on the axis
* @param maxPoint the maximum data point on the axis
*/
void NiceScale::setMinMaxPoints(float minPoint, float maxPoint) 
{
    this->minPoint = minPoint;
    this->maxPoint = maxPoint;
    calculate();
}

/**
* Sets maximum number of tick marks we're comfortable with
*
* @param maxTicks the maximum number of tick marks for the axis
*/
void NiceScale::setMaxTicks(float maxTicks) 
{
    this->maxTicks = maxTicks;
    calculate();
}

// minimum number of decimals in tick labels
// use in sprintf statement:
// sprintf(buf, "%.*f", decimals(), tickValue);
int NiceScale::decimals(void)
{
    float logTickX = log10(tickSpacing);
    if(logTickX >= 0)
        return 0;
    return (int)(abs(floor(logTickX)));
}

3

你应该能够通过进行少量修正来使用Java实现。

将maxticks更改为5。

将计算方法更改为:

private void calculate() {
        this.range = niceNum(maxPoint - minPoint, false);
        this.tickSpacing = niceNum(range / (maxTicks - 1), true);
        this.niceMin =
            Math.floor(minPoint / tickSpacing) * tickSpacing;
        this.niceMax = this.niceMin + tickSpacing * (maxticks - 1); // Always display maxticks
    }

免责声明:请注意,我没有测试过这个代码,所以您可能需要进行一些微调才能使其看起来好看。我的建议是在图表顶部添加额外的空间,以始终为5个刻度留出空间。在某些情况下,这可能看起来很丑陋。


3

这里是用Objective C实现的同样代码

YFRNiceScale.h

#import <Foundation/Foundation.h>

@interface YFRNiceScale : NSObject

@property (nonatomic, readonly) CGFloat minPoint;
@property (nonatomic, readonly) CGFloat maxPoint;
@property (nonatomic, readonly) CGFloat maxTicks;
@property (nonatomic, readonly) CGFloat tickSpacing;
@property (nonatomic, readonly) CGFloat range;
@property (nonatomic, readonly) CGFloat niceRange;
@property (nonatomic, readonly) CGFloat niceMin;
@property (nonatomic, readonly) CGFloat niceMax;


- (id) initWithMin: (CGFloat) min andMax: (CGFloat) max;
- (id) initWithNSMin: (NSDecimalNumber*) min andNSMax: (NSDecimalNumber*) max;

@end

YFRNiceScale.m

#import "YFRNiceScale.h"

@implementation YFRNiceScale

@synthesize minPoint = _minPoint;
@synthesize maxPoint = _maxPoint;
@synthesize maxTicks = _maxTicks;
@synthesize tickSpacing = _tickSpacing;
@synthesize range = _range;
@synthesize niceRange = _niceRange;
@synthesize niceMin = _niceMin;
@synthesize niceMax = _niceMax;

- (id)init {
    self = [super init];
    if (self) {

    }
    return self;
}

- (id) initWithMin: (CGFloat) min andMax: (CGFloat) max {

    if (self) {
        _maxTicks = 10;
        _minPoint = min;
        _maxPoint = max;
        [self calculate];
    }
    return [self init];
}

- (id) initWithNSMin: (NSDecimalNumber*) min andNSMax: (NSDecimalNumber*) max {

    if (self) {
        _maxTicks = 10;
        _minPoint = [min doubleValue];
        _maxPoint = [max doubleValue];
        [self calculate];
    }
    return [self init];
}


/**
 * Calculate and update values for tick spacing and nice minimum and maximum
 * data points on the axis.
 */

- (void) calculate {
    _range = [self niceNumRange: (_maxPoint-_minPoint) roundResult:NO];
    _tickSpacing = [self niceNumRange: (_range / (_maxTicks - 1)) roundResult:YES];
    _niceMin = floor(_minPoint / _tickSpacing) * _tickSpacing;
    _niceMax = ceil(_maxPoint / _tickSpacing) * _tickSpacing;

    _niceRange = _niceMax - _niceMin;
}


/**
 * Returns a "nice" number approximately equal to range Rounds the number if
 * round = true Takes the ceiling if round = false.
 *
 * @param range
 *            the data range
 * @param round
 *            whether to round the result
 * @return a "nice" number to be used for the data range
 */
- (CGFloat) niceNumRange:(CGFloat) aRange roundResult:(BOOL) round {
    CGFloat exponent;
    CGFloat fraction;
    CGFloat niceFraction;

    exponent = floor(log10(aRange));
    fraction = aRange / pow(10, exponent);

    if (round) {
        if (fraction < 1.5) {
            niceFraction = 1;
        } else if (fraction < 3) {
            niceFraction = 2;
        } else if (fraction < 7) {
            niceFraction = 5;
        } else {
            niceFraction = 10;
        }

    } else {
        if (fraction <= 1) {
            niceFraction = 1;
        } else if (fraction <= 2) {
            niceFraction = 2;
        } else if (fraction <= 5) {
            niceFraction = 2;
        } else {
            niceFraction = 10;
        }
    }

    return niceFraction * pow(10, exponent);
}

- (NSString*) description {
    return [NSString stringWithFormat:@"NiceScale [minPoint=%.2f, maxPoint=%.2f, maxTicks=%.2f, tickSpacing=%.2f, range=%.2f, niceMin=%.2f, niceMax=%.2f]", _minPoint, _maxPoint, _maxTicks, _tickSpacing, _range, _niceMin, _niceMax ];
}

@end

使用方法:

YFRNiceScale* niceScale = [[YFRNiceScale alloc] initWithMin:0 andMax:500];
NSLog(@"Nice: %@", niceScale);

2
我需要将这个算法转换为C#,所以在这里呈现...
public static class NiceScale {

    public static void Calculate(double min, double max, int maxTicks, out double range, out double tickSpacing, out double niceMin, out double niceMax) {
        range = niceNum(max - min, false);
        tickSpacing = niceNum(range / (maxTicks - 1), true);
        niceMin = Math.Floor(min / tickSpacing) * tickSpacing;
        niceMax = Math.Ceiling(max / tickSpacing) * tickSpacing;
    }

    private static double niceNum(double range, bool round) {
        double pow = Math.Pow(10, Math.Floor(Math.Log10(range)));
        double fraction = range / pow;

        double niceFraction;
        if (round) {
            if (fraction < 1.5) {
                niceFraction = 1;
            } else if (fraction < 3) {
                niceFraction = 2;
            } else if (fraction < 7) {
                niceFraction = 5;
            } else {
                niceFraction = 10;
            }
        } else {
            if (fraction <= 1) {
                niceFraction = 1;
            } else if (fraction <= 2) {
                niceFraction = 2;
            } else if (fraction <= 5) {
                niceFraction = 5;
            } else {
                niceFraction = 10;
            }
        }

        return niceFraction * pow;
    }

}

2

我在编写PHP时发现了这个线程,所以现在相同的代码也可以用PHP实现!

class CNiceScale {

  private $minPoint;
  private $maxPoint;
  private $maxTicks = 10;
  private $tickSpacing;
  private $range;
  private $niceMin;
  private $niceMax;

  public function setScale($min, $max) {
    $this->minPoint = $min;
    $this->maxPoint = $max;
    $this->calculate();
  }

  private function calculate() {
    $this->range = $this->niceNum($this->maxPoint - $this->minPoint, false);
    $this->tickSpacing = $this->niceNum($this->range / ($this->maxTicks - 1), true);
    $this->niceMin = floor($this->minPoint / $this->tickSpacing) * $this->tickSpacing;
    $this->niceMax = ceil($this->maxPoint / $this->tickSpacing) * $this->tickSpacing;
  }

  private function niceNum($range, $round) {
    $exponent; /** exponent of range */
    $fraction; /** fractional part of range */
    $niceFraction; /** nice, rounded fraction */

    $exponent = floor(log10($range));
    $fraction = $range / pow(10, $exponent);

    if ($round) {
      if ($fraction < 1.5)
        $niceFraction = 1;
      else if ($fraction < 3)
        $niceFraction = 2;
      else if ($fraction < 7)
        $niceFraction = 5;
      else
        $niceFraction = 10;
    } else {
      if ($fraction <= 1)
        $niceFraction = 1;
      else if ($fraction <= 2)
        $niceFraction = 2;
      else if ($fraction <= 5)
        $niceFraction = 5;
      else
        $niceFraction = 10;
    }

    return $niceFraction * pow(10, $exponent);
  }

  public function setMinMaxPoints($minPoint, $maxPoint) {
    $this->minPoint = $minPoint;
    $this->maxPoint = $maxPoint;
    $this->calculate();
  }

  public function setMaxTicks($maxTicks) {
    $this->maxTicks = $maxTicks;
    $this->calculate();
  }

  public function getTickSpacing() {
    return $this->tickSpacing;
  }

  public function getNiceMin() {
    return $this->niceMin;
  }

  public function getNiceMax() {
    return $this->niceMax;
  }

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接