# scaling.tcl --
#    Make a nice scale for the axes in the Plotchart package
#

namespace eval ::Plotchart {
   namespace export determineScale

   #
   # Try and load the math::fuzzy package for better
   # comparisons
   #
   if { [catch {
            package require math::fuzzy
            namespace import ::math::fuzzy::tlt
            namespace import ::math::fuzzy::tgt
         }] } {
      proc tlt {a b} {
         expr {$a < $b }
      }
      proc tgt {a b} {
         expr {$a > $b }
      }
   }
}

# determineScaleFromList --
#    Determine nice values for an axis from a list of values
#
# Arguments:
#    values    List of values
#    inverted  Whether to return values for an inverted axis (1) or not (0)
#              Defaults to 0.
# Result:
#    A list of three values, a nice minimum and maximum
#    and stepsize
# Note:
#    Missing values (empty strings) are allowed in the list of values
#
proc ::Plotchart::determineScaleFromList { values {inverted 0} } {

    set xmin {}
    set xmax {}

    foreach v $values {
        if { $v == {} } {
            continue
        }
        if { $xmin == {} || $xmin > $v } {
            set xmin $v
        }
        if { $xmax == {} || $xmax < $v } {
            set xmax $v
        }
    }

    return [determineScale $xmin $xmax $inverted]
}

# determineScale --
#    Determine nice values for an axis from the given extremes
#
# Arguments:
#    xmin      Minimum value
#    xmax      Maximum value
#    inverted  Whether to return values for an inverted axis (1) or not (0)
#              Defaults to 0.
# Result:
#    A list of three values, a nice minimum and maximum
#    and stepsize
# Note:
#    xmin is assumed to be smaller or equal xmax
#
proc ::Plotchart::determineScale { xmin xmax {inverted 0} } {
   set dx [expr {abs($xmax-$xmin)}]

   if { $dx == 0.0 } {
      if { $xmin == 0.0 } {
         return [list -0.1 0.1 0.1]
      } else {
         set dx [expr {0.2*abs($xmax)}]
         set xmin [expr {$xmin-0.5*$dx}]
         set xmax [expr {$xmin+0.5*$dx}]
      }
   }

   #
   # Very small ranges (relatively speaking) cause problems
   # The range must be at least 1.0e-8
   #
   if { $dx < 0.5e-8*(abs($xmin)+abs($xmax)) } {
       set xmean [expr {0.5*($xmin+$xmax)}]
       set dx    [expr {1.0e-8*$xmean}]
       set xmin  [expr {$xmean - 0.5*$dx}]
       set xmax  [expr {$xmean + 0.5*$dx}]
   }

   #
   # Determine the factor of 10 so that dx falls within the range 1-10
   #
   set expon  [expr {int(log10($dx))}]
   set factor [expr {pow(10.0,$expon)}]

   set dx     [expr {$dx/$factor}]

   foreach {limit step} {1.4 0.2 2.0 0.5 5.0 1.0 10.0 2.0} {
      if { $dx < $limit } {
         break
      }
   }

   set fmin    [expr {$xmin/$factor/$step}]
   set fmax    [expr {$xmax/$factor/$step}]
#  if { abs($fmin) > 1.0e10 } {
#      set fmin [expr {$fmin > 0.0 ? 1.0e10 : -1.0e10}]
#  }
#  if { abs($fmax) > 1.0e10 } {
#      set fmax [expr {$fmax > 0.0 ? 1.0e10 : -1.0e10}]
#  }
   set nicemin [expr {$step*$factor*wide($fmin)}]
   set nicemax [expr {$step*$factor*wide($fmax)}]

   if { [tlt $nicemax $xmax] } {
      set nicemax [expr {$nicemax+$step*$factor}]
   }
   if { [tgt $nicemin $xmin] } {
      set nicemin [expr {$nicemin-$step*$factor}]
   }

   if { !$inverted } {
       return [list $nicemin $nicemax [expr {$step*$factor}]]
   } else {
       return [list $nicemax $nicemin [expr {-$step*$factor}]]
   }
}

# determineTimeScale --
#    Determine nice date/time values for an axis from the given extremes
#
# Arguments:
#    tmin      Minimum date/time
#    tmax      Maximum date/time
# Result:
#    A list of three values, a nice minimum and maximum
#    and stepsize
# Note:
#    tmin is assumed to be smaller or equal tmax
#
proc ::Plotchart::determineTimeScale { tmin tmax } {
    set ttmin [clock scan $tmin]
    set ttmax [clock scan $tmax]

    set dt [expr {abs($ttmax-$ttmin)}]

    if { $dt == 0.0 } {
        set dt 86400.0
        set ttmin [expr {$ttmin-$dt}]
        set ttmax [expr {$ttmin+$dt}]
    }

    foreach {limit step} {2.0 0.5 5.0 1.0 10.0 2.0 50.0 7.0 300.0 30.0 1.0e10 365.0} {
        if { $dt/86400.0 < $limit } {
            break
        }
    }

    set nicemin [expr {$step*floor($ttmin/$step)}]
    set nicemax [expr {$step*floor($ttmax/$step)}]

    if { $nicemax < $ttmax } {
        set nicemax [expr {$nicemax+$step}]
    }
    if { $nicemin > $ttmin } {
        set nicemin [expr {$nicemin-$step}]
    }

    set nicemin [expr {int($nicemin)}]
    set nicemax [expr {int($nicemax)}]

    return [list [clock format $nicemin -format "%Y-%m-%d %H:%M:%S"] \
                 [clock format $nicemax -format "%Y-%m-%d %H:%M:%S"] \
                 $step]
}

if 0 {
    #
    # Some simple test cases
    #
    namespace import ::Plotchart::determineScale
    puts [determineScale 0.1 1.0]
    puts [determineScale 0.001 0.01]
    puts [determineScale -0.2 0.9]
    puts [determineScale -0.25 0.85]
    puts [determineScale -0.25 0.7999]
    puts [determineScale 10001 10010]
    puts [determineScale 10001 10015]
}
if 0 {
    puts [::Plotchart::determineTimeScale "2007-01-15" "2007-01-16"]
    puts [::Plotchart::determineTimeScale "2007-03-15" "2007-06-16"]
}
