1# scaling.tcl --
2#    Make a nice scale for the axes in the Plotchart package
3#
4
5namespace eval ::Plotchart {
6   namespace export determineScale
7
8   #
9   # Try and load the math::fuzzy package for better
10   # comparisons
11   #
12   if { [catch {
13            package require math::fuzzy
14            namespace import ::math::fuzzy::tlt
15            namespace import ::math::fuzzy::tgt
16         }] } {
17      proc tlt {a b} {
18         expr {$a < $b }
19      }
20      proc tgt {a b} {
21         expr {$a > $b }
22      }
23   }
24}
25
26# determineScaleFromList --
27#    Determine nice values for an axis from a list of values
28#
29# Arguments:
30#    values    List of values
31#    inverted  Whether to return values for an inverted axis (1) or not (0)
32#              Defaults to 0.
33# Result:
34#    A list of three values, a nice minimum and maximum
35#    and stepsize
36# Note:
37#    Missing values (empty strings) are allowed in the list of values
38#
39proc ::Plotchart::determineScaleFromList { values {inverted 0} } {
40
41    set xmin {}
42    set xmax {}
43
44    foreach v $values {
45        if { $v == {} } {
46            continue
47        }
48        if { $xmin == {} || $xmin > $v } {
49            set xmin $v
50        }
51        if { $xmax == {} || $xmax < $v } {
52            set xmax $v
53        }
54    }
55
56    return [determineScale $xmin $xmax $inverted]
57}
58
59# determineScale --
60#    Determine nice values for an axis from the given extremes
61#
62# Arguments:
63#    xmin      Minimum value
64#    xmax      Maximum value
65#    inverted  Whether to return values for an inverted axis (1) or not (0)
66#              Defaults to 0.
67# Result:
68#    A list of three values, a nice minimum and maximum
69#    and stepsize
70# Note:
71#    xmin is assumed to be smaller or equal xmax
72#
73proc ::Plotchart::determineScale { xmin xmax {inverted 0} } {
74   set dx [expr {abs($xmax-$xmin)}]
75
76   if { $dx == 0.0 } {
77      if { $xmin == 0.0 } {
78         return [list -0.1 0.1 0.1]
79      } else {
80         set dx [expr {0.2*abs($xmax)}]
81         set xmin [expr {$xmin-0.5*$dx}]
82         set xmax [expr {$xmin+0.5*$dx}]
83      }
84   }
85
86   #
87   # Very small ranges (relatively speaking) cause problems
88   # The range must be at least 1.0e-8
89   #
90   if { $dx < 0.5e-8*(abs($xmin)+abs($xmax)) } {
91       set xmean [expr {0.5*($xmin+$xmax)}]
92       set dx    [expr {1.0e-8*$xmean}]
93       set xmin  [expr {$xmean - 0.5*$dx}]
94       set xmax  [expr {$xmean + 0.5*$dx}]
95   }
96
97   #
98   # Determine the factor of 10 so that dx falls within the range 1-10
99   #
100   set expon  [expr {int(log10($dx))}]
101   set factor [expr {pow(10.0,$expon)}]
102
103   set dx     [expr {$dx/$factor}]
104
105   foreach {limit step} {1.4 0.2 2.0 0.5 5.0 1.0 10.0 2.0} {
106      if { $dx < $limit } {
107         break
108      }
109   }
110
111   set fmin    [expr {$xmin/$factor/$step}]
112   set fmax    [expr {$xmax/$factor/$step}]
113#  if { abs($fmin) > 1.0e10 } {
114#      set fmin [expr {$fmin > 0.0 ? 1.0e10 : -1.0e10}]
115#  }
116#  if { abs($fmax) > 1.0e10 } {
117#      set fmax [expr {$fmax > 0.0 ? 1.0e10 : -1.0e10}]
118#  }
119   set nicemin [expr {$step*$factor*wide($fmin)}]
120   set nicemax [expr {$step*$factor*wide($fmax)}]
121
122   if { [tlt $nicemax $xmax] } {
123      set nicemax [expr {$nicemax+$step*$factor}]
124   }
125   if { [tgt $nicemin $xmin] } {
126      set nicemin [expr {$nicemin-$step*$factor}]
127   }
128
129   if { !$inverted } {
130       return [list $nicemin $nicemax [expr {$step*$factor}]]
131   } else {
132       return [list $nicemax $nicemin [expr {-$step*$factor}]]
133   }
134}
135
136# determineTimeScale --
137#    Determine nice date/time values for an axis from the given extremes
138#
139# Arguments:
140#    tmin      Minimum date/time
141#    tmax      Maximum date/time
142# Result:
143#    A list of three values, a nice minimum and maximum
144#    and stepsize
145# Note:
146#    tmin is assumed to be smaller or equal tmax
147#
148proc ::Plotchart::determineTimeScale { tmin tmax } {
149    set ttmin [clock scan $tmin]
150    set ttmax [clock scan $tmax]
151
152    set dt [expr {abs($ttmax-$ttmin)}]
153
154    if { $dt == 0.0 } {
155        set dt 86400.0
156        set ttmin [expr {$ttmin-$dt}]
157        set ttmax [expr {$ttmin+$dt}]
158    }
159
160    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} {
161        if { $dt/86400.0 < $limit } {
162            break
163        }
164    }
165
166    set nicemin [expr {$step*floor($ttmin/$step)}]
167    set nicemax [expr {$step*floor($ttmax/$step)}]
168
169    if { $nicemax < $ttmax } {
170        set nicemax [expr {$nicemax+$step}]
171    }
172    if { $nicemin > $ttmin } {
173        set nicemin [expr {$nicemin-$step}]
174    }
175
176    set nicemin [expr {int($nicemin)}]
177    set nicemax [expr {int($nicemax)}]
178
179    return [list [clock format $nicemin -format "%Y-%m-%d %H:%M:%S"] \
180                 [clock format $nicemax -format "%Y-%m-%d %H:%M:%S"] \
181                 $step]
182}
183
184if 0 {
185    #
186    # Some simple test cases
187    #
188    namespace import ::Plotchart::determineScale
189    puts [determineScale 0.1 1.0]
190    puts [determineScale 0.001 0.01]
191    puts [determineScale -0.2 0.9]
192    puts [determineScale -0.25 0.85]
193    puts [determineScale -0.25 0.7999]
194    puts [determineScale 10001 10010]
195    puts [determineScale 10001 10015]
196}
197if 0 {
198    puts [::Plotchart::determineTimeScale "2007-01-15" "2007-01-16"]
199    puts [::Plotchart::determineTimeScale "2007-03-15" "2007-06-16"]
200}
201