Nice Label Algorithm for Charts with Minimal Ticks

I need to calculate Ticklabels and Tickrange for charts manually.

I know the "standard" algorithm for good ticks (see http://books.google.de/books?id=fvA7zLEFWZgC&pg=PA61&lpg=PA61&redir_esc=y#v=onepage&q&f=false ), and I also know this Java implementation .

The problem is that ticks are "too smart" with this algorithm. This means that the algorithm determines how many ticks should be displayed. My requirement is that there are always 5 Ticks, but they must, of course, be “pretty.” A naive approach would be to get the maximum value, divide by 5 and multiply by the number of ticks. The values ​​here are - of course - not optimal, and the ticks are pretty ugly.

Does anyone know a solution to the problem or have a hint to describe a formal algorithm?

+23
java math algorithm charts
Dec 14 '11 at 15:15
source share
15 answers

You should be able to use the Java implementation with minor fixes.

Change maxticks to 5.

Change the value of 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 } 

Disclaimer: Please note that I have not tested this, so you may need to tweak it to look good. My proposed solution adds extra space at the top of the chart so that there is always room for 5 ticks. In some cases, this may look ugly.

+4
Dec 14 '11 at 15:41
source share

I am the author of the " Optimal Scaling Algorithm on the Chart Axis ". It used to be hosted on trollop.org, but recently I switched to domain / blog servers. In any case, I will post content here for easy access.

I was working on an Android graphics application for assignment and ran into a problem when it came to presenting the chart in a well-scaled format. I spent some time trying to create this algorithm myself, and came terribly close, but in the end I found an example of pseudo-code in a book called Gemstone Graphics, Volume 1 by Andrew S. Glassner. An excellent description of the problem is provided in the Nice Numbers for Graph Labels chapter:

When creating a graph using a computer, it is advisable to designate x and y with "good" numbers: simple decimal numbers. For example, if the data range is from 105 to 543, we probably want to build a range from 100 to 600 and put marks every 100 units. Or if the data range is from 2.04 to 2.16, we will probably build a range from 2.00 to 2.20 in tick steps of 0.05. People can choose such "good" numbers, but there are no simplified algorithms. The naive choice of labels the algorithm takes a range of data and divides it into n equal intervals, but usually this leads to ugly labels. Here we describe a simple way to generate bright graph labels.

The primary observation is that the "most beautiful" numbers in decimal form are 1, 2, and 5, as well as all tenfold numbers of these numbers. We will use only such numbers for the tick interval and mark ticks in multiple tick intervals ...

I used the pseudo-code example in this book to create the following class in 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(); } } 

Then we can use the above code as follows:

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

which will then output beautifully formatted numbers for use in any application for which you need to create pretty scales. = D

 Tick Spacing: 0.05 Nice Minimum: -0.1 Nice Maximum: 0.2 
+57
May 03 '13 at 16:19
source share

I have converted the above java code to Python as per my requirement.

  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
Jun 06 '13 at 10:00
source share

Here is the javascript version:

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

Enjoy it!

+4
Aug 01 '16 at 15:37
source share

Here is the same thing in 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 

Using:

 YFRNiceScale* niceScale = [[YFRNiceScale alloc] initWithMin:0 andMax:500]; NSLog(@"Nice: %@", niceScale); 
+2
Aug 28 '13 at 11:20
source share

I found this thread while writing some php, so now the same code is available in php too!

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

code>

+2
Sep 25 '13 at 20:13
source share

I needed this algorithm converted to C #, so here it is ...

 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
Feb 02 '15 at 19:09
source share

Since everyone and his dogs publish translations into other popular languages, here is my version for the Nimrod programming language . I also added handling cases when the number of ticks is less than two:

 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 

This is a version of a split comment. The full text can be read on GitHub , integrated into my project.

+1
May 14 '14 at 18:31
source share

This is the version of 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) } } 
+1
Aug 26 '15 at 12:10
source share

Here is the C ++ version. As a bonus, you get a function that returns the minimum number of decimal points to display label marks on the axis.

Header file:

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

And the CPP file:

 /** * 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))); } 
+1
Dec 03 '16 at 4:10
source share

Here is the version of 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() } } 
+1
Nov 07 '17 at 20:02
source share

This is a version of 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 
0
Jul 15 '16 at 3:46
source share

Here it is in 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); } 
0
Sep 09 '17 at 23:51 on
source share

C # code is better organized here.

 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(); } } 
0
Dec 17 '18 at 6:24
source share

A much better and simpler algorithm for fast ones. The size is fixed, the values ​​are not hardcoded:

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

Verified only on positive numbers.

0
Mar 17 '19 at 21:44
source share



All Articles