Nice Numbers

Keith Vetter 2006-09-01 : I was recently searching this wiki for routines for drawing charts when I saw in two different places two similar, but very clever, routines that I thought deserved a page to themselves.

They both solve the problem for graphs of determining the limits of an axis so that it encompasses the range of values of the graph but also begin and end on Nice Numbers. For example, with a range of, say, 5-86 you'd like the axis to range from 0-100.

KPV Oct 10, 2006 : added another version from tklib's plotchart.


The first algorithm is from Chart generation support by Dave Griffin.

 #
 # nice_number
 #
 #   Reference: Paul Heckbert, "Nice Numbers for Graph Labels",
 #          Graphics Gems, pp 61-63.
 #
 #   Finds a "nice" number approximately equal to x.
 #
 #   Args: x -- target number
 #         round -- If non-zero, round. Otherwise take ceiling of value.
 
 proc nice_number {x {round 0}} {
 
    #   expt -- Exponent of x
    #   frac -- Fractional part of x
    #   nice -- Nice, rounded fraction
 
    set expt [expr {floor(log10($x))}]
    set frac [expr {$x / pow(10.0, double($expt))}]
    if ($round) {
        if {$frac < 1.5} {
            set nice 1.0
        } elseif {$frac < 3.0} {
            set nice 2.0
        } elseif {$frac < 7.0} {
            set nice 5.0
        } else {
            set nice 10.0
        }
    } else {
        if {$frac <= 1.0} {
            set nice  1.0
        } elseif {$frac <= 2.0} {
            set nice  2.0
        } elseif {$frac <= 5.0} {
            set nice 5.0
        } else {
            set nice 10.0
        }
    }
   return [expr {$nice * pow(10.0, double($expt))}]
 }

The second routine is from A little bar chart by the ubiquitous Richard Suchenwirth:

 # An interesting sub-challenge was to round numbers very roughly,
 # to 1 or maximally 2 significant digits - by default rounding up,
 # add "-" to round down:}
 proc Roughly {n {sgn +}} {
    regexp {(.+)e([+-])0*(.+)} [format %e $n] -> mant sign exp
    set exp [expr $sign$exp]
    if {abs($mant)<1.5} {
        set mant [expr {$mant*10}]
        incr exp -1
    }
    set t [expr round($mant $sgn 0.49)*pow(10,$exp)]
    expr {$exp>=0? int($t): $t}
 }

tklib plotchart has a similar routine that also computes a good tick interval.

 package require plotchart
 foreach {niceMin niceMax niceTicks} [::plotchart::determineScale $min $max] break

 # Test:

 catch {console show}
 catch {wm withdraw .}

 foreach x {7 11 22 33 44 77} {
   puts "$x nice_number: [nice_number $x]"
   puts "$x Roughly    : [Roughly     $x] \n"
 }

HJG Test added.