Алгоритм Nice Label для диаграмм с минимальными тиками

Мне нужно вычислить Ticklabels и Tickrange для диаграмм вручную.

Я знаю "стандартный" алгоритм для хороших тиков (см. http://books.google.de/books?id=fvA7zLEFWZgC&pg=PA61&lpg=PA61&redir_esc=y#v=onepage&q&f=false), и я также знаю эта реализация Java.

Проблема в том, что с этим алгоритмом тики "слишком умны". Это означает, что алгоритм определяет, сколько тиков должно отображаться. Мое требование состоит в том, что всегда есть 5 Ticks, но они должны, конечно, быть "хорошенькими". Наивный подход состоял бы в том, чтобы получить максимальное значение, разделить на 5 и умножить на количество тиков. Значения здесь - конечно - не оптимальные, а тики довольно уродливые.

Кто-нибудь знает решение проблемы или имеет подсказку для описания формального алгоритма?

Ответ 1

Вы должны иметь возможность использовать реализацию Java с небольшими исправлениями.

Измените maxticks на 5.

Измените значение mehod:

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 тиков. В некоторых случаях это может выглядеть уродливым.

Ответ 2

Я автор " Алгоритм оптимального масштабирования на оси диаграммы". Раньше он был размещен на trollop.org, но недавно я перешел на серверы доменов/блогов. Во всяком случае, я буду размещать содержимое здесь для облегчения доступа.

Я работал над графическим приложением Android для назначения и столкнулся с проблемой, когда дело доходило до представления диаграммы в хорошо масштабированном формате. Я потратил некоторое время, пытаясь самостоятельно создать этот алгоритм, и пришел ужасно близко, но в конце концов я нашел пример псевдокода в книге под названием "Графика драгоценных камней, том 1" Эндрю С. Гласснера. Отличное описание проблемы приведено в главе " Nice Numbers for Graph Labels":

При создании графика с помощью компьютера желательно обозначить 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

Ответ 3

Я преобразовал выше 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

Ответ 4

Вот версия 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();
}

Наслаждайтесь!

Ответ 5

Вот то же самое в 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);

Ответ 6

Я нашел этот поток при написании некоторого 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;
  }

}

код >

Ответ 7

Мне нужен был этот алгоритм, преобразованный в С#, поэтому вот он...

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

}

Ответ 8

Поскольку все и его собаки публикуют перевод на другие популярные языки, вот моя версия для языка программирования Nimrod. Я также добавил обработку случаев, когда количество тиков меньше двух:

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, интегрированном в мой проект.

Ответ 9

Это версия 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)
    }
}

Ответ 10

Здесь версия С++. В качестве бонуса вы получаете функцию, которая возвращает минимальное количество десятичных точек, чтобы отображать метки меток на оси.

Файл заголовка:

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

Ответ 11

Здесь версия Котлина!

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

Ответ 12

Это версия 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

Ответ 13

Здесь он находится в 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);
}

Ответ 14

Здесь лучше организован код С#.

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

Ответ 15

Гораздо лучше и проще алгоритм на быстрых. Размер фиксирован, значения не "жестко закодированы":

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

Проверено только на положительных числах.