37

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

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

问题是,使用这种算法,滴答声“太聪明了”。这意味着,算法决定应该显示多少刻度。我的要求是,总是有 5 个刻度,但这些当然应该是“漂亮的”。天真的方法是获取最大值,除以 5 并乘以刻度数。这里的值 - 当然 - 不是最优的,而且刻度非常难看。

有谁知道问题的解决方案或有正式算法描述的提示?

4

19 回答 19

84

我是“图表轴上的最佳缩放算法”的作者。它曾经托管在 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,以及这些数字的所有 10 次方。我们将仅使用这些数字作为刻度间距,并将刻度线放置在刻度间距的倍数处......

我使用本书中的伪代码示例在 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
于 2013-05-03T16:19:35.530 回答
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();
}

享受!

于 2016-08-01T15:37:55.703 回答
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
于 2013-06-06T10:00:02.647 回答
4

这是斯威夫特版本:

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)
    }
}
于 2015-08-26T12:10:16.503 回答
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)));
}
于 2016-12-03T04:10:25.803 回答
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 个刻度腾出空间。在某些情况下,这可能看起来很难看。

于 2011-12-14T15:41:51.293 回答
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);
于 2013-08-28T23:20:48.527 回答
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;
  }

}

于 2013-09-25T20:13:35.447 回答
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;
    }

}
于 2015-02-02T19:09:00.243 回答
2

这是在 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);
}
于 2017-09-09T23:51:19.797 回答
1

由于每个人和他的狗都在发布其他流行语言的翻译,这里是我的Nim 编程语言版本。我还添加了对刻度数少于两个的情况的处理:

import math, strutils

const
  defaultMaxTicks = 10

type NiceScale = object
  minPoint: float
  maxPoint: float
  maxTicks: int
  tickSpacing: float
  niceMin: float
  niceMax: float

proc ff(x: float): string =
  result = x.formatFloat(ffDecimal, 3)

proc `$`*(x: NiceScale): string =
  result = "Input minPoint: " & x.minPoint.ff &
    "\nInput maxPoint: " & x.maxPoint.ff &
    "\nInput maxTicks: " & $x.maxTicks &
    "\nOutput niceMin: " & x.niceMin.ff &
    "\nOutput niceMax: " & x.niceMax.ff &
    "\nOutput tickSpacing: " & x.tickSpacing.ff &
    "\n"

proc calculate*(x: var NiceScale)

proc init*(x: var NiceScale; minPoint, maxPoint: float;
    maxTicks = defaultMaxTicks) =
  x.minPoint = minPoint
  x.maxPoint = maxPoint
  x.maxTicks = maxTicks
  x.calculate

proc initScale*(minPoint, maxPoint: float;
    maxTicks = defaultMaxTicks): NiceScale =
  result.init(minPoint, maxPoint, maxTicks)

proc niceNum(scaleRange: float; doRound: bool): float =
  var
    exponent: float ## Exponent of scaleRange.
    fraction: float ## Fractional part of scaleRange.
    niceFraction: float ## Nice, rounded fraction.

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

  if doRound:
    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 * pow(10, exponent)

proc calculate*(x: var NiceScale) =
  assert x.maxPoint > x.minPoint, "Wrong input range!"
  assert x.maxTicks >= 0, "Sorry, can't have imaginary ticks!"
  let scaleRange = niceNum(x.maxPoint - x.minPoint, false)
  if x.maxTicks < 2:
    x.niceMin = floor(x.minPoint)
    x.niceMax = ceil(x.maxPoint)
    x.tickSpacing = (x.niceMax - x.niceMin) /
      (if x.maxTicks == 1: 2.0 else: 1.0)
  else:
    x.tickSpacing = niceNum(scaleRange / (float(x.maxTicks - 1)), true)
    x.niceMin = floor(x.minPoint / x.tickSpacing) * x.tickSpacing
    x.niceMax = ceil(x.maxPoint / x.tickSpacing) * x.tickSpacing

when isMainModule:
  var s = initScale(57.2, 103.3)
  echo s

这是评论剥离版本。完整的可以在集成到我的项目中的GitHub 上阅读。

于 2014-05-14T18:31:01.750 回答
1

这是 Kotlin 版本!

import java.lang.Math.*

/**
 * Instantiates a new instance of the NiceScale class.
 *
 * @param min Double The minimum data point.
 * @param max Double The maximum data point.
 */
class NiceScale(private var minPoint: Double, private var maxPoint: Double) {

    private var maxTicks = 15.0
    private var range: Double = 0.0
    var niceMin: Double = 0.0
    var niceMax: Double = 0.0
    var tickSpacing: Double = 0.0

    init {
        calculate()
    }

    /**
     * Calculate and update values for tick spacing and nice
     * minimum and maximum data points on the axis.
     */
    private fun 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 Double The data range.
     * @param round Boolean Whether to round the result.
     * @return Double A "nice" number to be used for the data range.
     */
    private fun niceNum(range: Double, round: Boolean): Double {
        /** Exponent of range  */
        val exponent: Double = floor(log10(range))
        /** Fractional part of range  */
        val fraction: Double
        /** Nice, rounded fraction  */
        val niceFraction: Double

        fraction = range / pow(10.0, exponent)

        niceFraction = if (round) {
            when {
                fraction < 1.5 -> 1.0
                fraction < 3 -> 2.0
                fraction < 7 -> 5.0
                else -> 10.0
            }
        } else {
            when {
                fraction <= 1 -> 1.0
                fraction <= 2 -> 2.0
                fraction <= 5 -> 5.0
                else -> 10.0
            }
        }

        return niceFraction * pow(10.0, exponent)
    }

    /**
     * Sets the minimum and maximum data points.
     *
     * @param minPoint Double The minimum data point.
     * @param maxPoint Double The maximum data point.
     */
    fun setMinMaxPoints(minPoint: Double, maxPoint: Double) {
        this.minPoint = minPoint
        this.maxPoint = maxPoint
        calculate()
    }

    /**
     * Sets maximum number of tick marks we're comfortable with.
     *
     * @param maxTicks Double The maximum number of tick marks.
     */
    fun setMaxTicks(maxTicks: Double) {
        this.maxTicks = maxTicks
        calculate()
    }
}
于 2017-11-07T20:02:45.890 回答
1

这是一个组织得更好的 C# 代码。

public class NiceScale
{

    public double NiceMin { get; set; }
    public double NiceMax { get; set; }
    public double TickSpacing { get; private set; }

    private double _minPoint;
    private double _maxPoint;
    private double _maxTicks = 5;
    private double _range;

    /**
     * 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)
    {
        _minPoint = min;
        _maxPoint = max;
        Calculate();
    }

    /**
     * Calculate and update values for tick spacing and nice
     * minimum and maximum data points on the axis.
     */
    private void Calculate()
    {
        _range = NiceNum(_maxPoint - _minPoint, false);
        TickSpacing = NiceNum(_range / (_maxTicks - 1), true);
        NiceMin = Math.Floor(_minPoint / TickSpacing) * TickSpacing;
        NiceMax = Math.Ceiling(_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, bool 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)
    {
        _minPoint = minPoint;
        _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)
    {
        _maxTicks = maxTicks;
        Calculate();
    }
}
于 2018-12-17T06:24:55.927 回答
1

//Swift,更紧凑

 
public struct NiceScale
{
var minPoint: Double
var maxPoint: Double
var maxTicks = 10
var tickSpacing: Double { niceNum(range: range / Double(maxTicks - 1), round: true) }
var range: Double { niceNum(range: maxPoint - minPoint, round: false) }
var niceMin: Double { floor(minPoint / tickSpacing) * tickSpacing }
var niceMax: Double { ceil(maxPoint / tickSpacing) * tickSpacing }

// min the minimum data point on the axis // max the maximum data point on the axis init( min: Double, max: Double, maxTicks: Int = 10) { minPoint = min maxPoint = max self.maxTicks = maxTicks } /** * Returns a "nice" number approximately equal to range Rounds * the number if round = true Takes the ceiling * if round = false range the data range * round whether to round the result * return a "nice" number to be used for the data range */ func niceNum( range: Double, round: Bool) -> Double { let exponent: Double = floor(log10(range)) // exponent of range let fraction: Double = range / pow(10, exponent) // fractional part of range var niceFraction: Double = 10.0 // nice, rounded fraction if round { if fraction < 1.5 { niceFraction = 1 } else if fraction < 3 { niceFraction = 2 } else if fraction < 7 { niceFraction = 5 } } else { if fraction <= 1 { niceFraction = 1 } else if fraction <= 2 { niceFraction = 2 } else if fraction <= 5 { niceFraction = 5 } } return niceFraction * pow(10, exponent) } static func testNiceScale() { var numScale = NiceScale(min: -0.085, max: 0.173) print("Tick Spacing:\t \( numScale.tickSpacing)") print("Nice Minimum:\t\( numScale.niceMin)") print("Nice Maximum:\t\( numScale.niceMax)") numScale = NiceScale(min: 4.44, max: 7.962) print("nice num:\t\( numScale.niceNum(range: 7.962 - 4.44, round: false))") print("Tick Spacing:\t\( numScale.tickSpacing)") print("Nice Minimum:\t\( numScale.niceMin)") print("Nice Maximum:\t\( numScale.niceMax)") }

} <\pre> <\代码>

于 2021-01-31T15:48:23.467 回答
0

这是 VB.NET 版本。

Public Class NiceScale

Private minPoint As Double
Private maxPoint As Double
Private maxTicks As Double = 10
Private tickSpacing
Private range As Double
Private niceMin As Double
Private niceMax As Double

Public Sub New(min As Double, max As Double)
    minPoint = min
    maxPoint = max
    calculate()
End Sub

Private Sub calculate()
    range = niceNum(maxPoint - minPoint, False)
    tickSpacing = niceNum(range / (maxTicks - 1), True)
    niceMin = Math.Floor(minPoint / tickSpacing) * tickSpacing
    niceMax = Math.Ceiling(maxPoint / tickSpacing) * tickSpacing
End Sub

Private Function niceNum(range As Double, round As Boolean) As Double
    Dim exponent As Double '/** exponent of range */
    Dim fraction As Double '/** fractional part of range */
    Dim niceFraction As Double '/** nice, rounded fraction */

    exponent = Math.Floor(Math.Log10(range))
    fraction = range / Math.Pow(10, exponent)

    If round Then
        If (fraction < 1.5) Then
            niceFraction = 1
        ElseIf (fraction < 3) Then
            niceFraction = 2
        ElseIf (fraction < 7) Then
            niceFraction = 5
        Else
            niceFraction = 10
        End If
    Else
        If (fraction <= 1) Then
            niceFraction = 1
        ElseIf (fraction <= 2) Then
            niceFraction = 2
        ElseIf (fraction <= 5) Then
            niceFraction = 5
        Else
            niceFraction = 10
        End If
    End If

    Return niceFraction * Math.Pow(10, exponent)
End Function

Public Sub setMinMaxPoints(minPoint As Double, maxPoint As Double)
    minPoint = minPoint
    maxPoint = maxPoint
    calculate()
End Sub

Public Sub setMaxTicks(maxTicks As Double)
    maxTicks = maxTicks
    calculate()
End Sub

Public Function getTickSpacing() As Double
    Return tickSpacing
End Function

Public Function getNiceMin() As Double
    Return niceMin
End Function

Public Function getNiceMax() As Double
    Return niceMax
End Function

End Class
于 2016-07-15T03:46:30.910 回答
0

Swift 上更好更简单的算法。大小是固定的,值不是“硬编码”的:

class NiceNumbers {
    /// Returns nice range of specified size. Result min <= min argument, result max >= max argument.
    static func getRange(forMin minInt: Int, max maxInt: Int, ofSize size: Int) -> [Int] {
        let niceMinInt = getMinCloseToZero(min: minInt, max: maxInt)
        let step = Double(maxInt - niceMinInt) / Double(size - 1)
        let niceStepInt = Int(get(for: step, min: false))

        var result = [Int]()
        result.append(niceMinInt)
        for i in 1...size - 1 {
            result.append(niceMinInt + i * Int(niceStepInt))
        }
        return result
    }

    /// Returns nice min or zero if it is much smaller than max.
    static func getMinCloseToZero(min: Int, max: Int) -> Int {
        let nice = get(for: Double(min), min: true)
        return nice <= (Double(max) / 10) ? 0 : Int(nice)
    }

    /// Get nice number. If min is true returns smaller number, if false - bigger one.
    static func get(for number: Double, min: Bool) -> Double {
        if number == 0 { return 0 }
        let exponent = floor(log10(number)) - (min ? 0 : 1)
        let fraction = number / pow(10, exponent)
        let niceFraction = min ? floor(fraction) : ceil(fraction)
        return niceFraction * pow(10, exponent)
    }
}

仅对正数进行测试。

于 2019-03-17T21:44:41.240 回答
0

飞镖/颤振版本:

import 'dart:math';

void main() {
  double min = 3, max = 28;

  var scale = NiceScale(min, max, 5);

  print("Range: $min-$max; Max Point: ${scale.niceMax}; Min Point: ${scale.niceMin}; Steps: ${scale.tickSpacing};");
}

class NiceScale {
  double _niceMin, _niceMax;
  double _tickSpacing;

  double get tickSpacing { return _tickSpacing; }
  double get niceMin{ return _niceMin; }
  double get niceMax{ return _niceMax; }

  double _minPoint, _maxPoint;
  double _maxTicks;
  double _range;

  NiceScale(double minP, double maxP, double maxTicks){
    this._minPoint = minP;
    this._maxPoint = maxP;
    this._maxTicks = maxTicks;
    _calculate();
  }

  void _calculate(){
    _range = _niceNum(_maxPoint - _minPoint, false);
    _tickSpacing = _niceNum(_range / (_maxTicks - 1), true);
    _niceMin = _calcMin();
    _niceMax = _calcMax();
  }

  double _calcMin() {
    int floored = (_minPoint / _tickSpacing).floor();
    return floored * _tickSpacing;
  }

  double _calcMax() {
    int ceiled = (_maxPoint / _tickSpacing).ceil();
    return ceiled * _tickSpacing;
  }

  double _niceNum(double range, bool round){
    double exponent; /** exponent of range */
    double fraction; /** fractional part of range */
    double niceFraction; /** nice, rounded fraction */

    exponent = (log(range)/ln10).floor().toDouble();
    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);
  }
}
于 2020-09-02T16:08:29.013 回答
0

这是一个 Ruby 版本

class NiceScale
  
  attr_accessor :min_point, :max_point
  attr_reader :tick_spacing, :nice_min, :nice_max
  
  def initialize(options = {})
    @min_point = options[:min_point]
    @max_point = options[:max_point]
    @max_ticks = [(options[:max_ticks] || 5), 2].max
    
    self.calculate
  end
  
  def calculate
    range = nice_num(@max_point - @min_point, false)
    @tick_spacing = nice_num(range / (@max_ticks - 1))
    @nice_min = (@min_point / tick_spacing).floor * tick_spacing
    @nice_max = (@max_point / tick_spacing).floor * tick_spacing
  end
  
  private
  
  def nice_num(num, round = true)
    num = num.to_f
    exponent = num > 0 ? Math.log10(num).floor : 0
    fraction = num / (10 ** exponent)
    
    if round
      if fraction < 1.5
        nice_fraction = 1
      elsif fraction < 3
        nice_fraction = 2
      elsif fraction < 7
        nice_fraction = 5
      else
        nice_fraction = 10
      end
    else
      if fraction <= 1
        nice_fraction = 1
      elsif fraction <= 2
        nice_fraction = 2
      elsif fraction <= 5
        nice_fraction = 5
      else
        nice_fraction = 10
      end
    end
    
    nice_fraction.to_f * (10 ** exponent)
  end
end
于 2021-07-08T08:51:37.647 回答
0

另一个 JS 版本:

   const getTicks = {
                minPoint: 0,
                maxPoint: 10,
                maxTicks: 10,
                tickSpacing: 1,
                range: 1,
                niceMin: 1,
                niceMax: 1,
                niceScale(min, max) {
                    this.minPoint = min;
                    this.maxPoint = max;
                    this.calculate();
                    return {
                        tickSpacing: this.tickSpacing,
                        niceMinimum: this.niceMin,
                        niceMaximum: this.niceMax
                    };
                },
                calculate() {
                    this.range = this.niceNum(this.maxPoint - this.minPoint, false);
                    this.tickSpacing = this.niceNum(this.range / (this.maxTicks - 1), true);
                    this.niceMin = Math.floor(this.minPoint / this.tickSpacing) * this.tickSpacing;
                    this.niceMax = Math.ceil(this.maxPoint / this.tickSpacing) * this.tickSpacing;
                },
                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);
                },
                setMinMaxPoints(localMinPoint, localMaxPoint) {
                    this.minPoint = localMinPoint;
                    this.maxPoint = localMaxPoint;
                    this.calculate();
                },
                setMaxTicks(localMaxTicks) {
                    this.maxTicks = localMaxTicks;
                    this.calculate();
                }
            }

只是之前输入的代码,将它嵌入到一个 JS 对象中(我不喜欢全局变量)。它可以这样使用:

const tickScale = getTicks.niceScale(minValue, maxValue);
    
for (let i = tickScale.niceMinimum; i < tickScale.niceMaximum; i+=tickScale.tickSpacing) {
      // Whatever you want to do
    }

我发现这种方式非常有用,并且更容易与其余代码隔离开来。

于 2021-10-18T08:46:29.500 回答