Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

LinearTransform.cxx

Go to the documentation of this file.
00001 
00012 #include "LinearTransform.h"
00013 
00014 #include "axes/AxisModelBase.h"
00015 #include "axes/AxisTick.h"
00016 
00017 #include <cmath>
00018 #include <cstdio>
00019 
00020 using std::abs;
00021 using std::max;
00022 using std::vector;
00023 
00028 LinearTransform::LinearTransform ()
00029   : UnaryTransform ( -DBL_MAX, DBL_MAX )
00030 {
00031   m_name = "Linear";
00032 }
00033 
00034 LinearTransform::~LinearTransform ()
00035 {
00036 }
00037 
00038 LinearTransform::LinearTransform ( const LinearTransform & lt )
00039   : UnaryTransform ( lt )
00040 {
00041 }
00042 
00043 #ifdef CLONE_DEFECT
00044 TransformBase   * LinearTransform::clone () const
00045 #else
00046 LinearTransform * LinearTransform::clone () const
00047 #endif 
00048 {
00049   return new LinearTransform ( *this );
00050 }
00051 
00052 bool
00053 LinearTransform::
00054 isLinear () const
00055 {
00056   return true;
00057 }
00058 
00059 void LinearTransform::transform ( double & x ) const
00060 {
00061 }
00062 
00063 void LinearTransform::inverseTransform ( double & x ) const
00064 {
00065 }
00066 
00067 void
00068 LinearTransform::
00069 transform ( std::vector < double > & x ) const
00070 {
00071 }
00072 
00073 /* virtual */
00074 void  LinearTransform::validate ( Range & range ) const
00075 {
00076   // Nothing to be done.
00077 }
00078 
00079 const vector < AxisTick > &
00080 LinearTransform::
00081 setTicks ( AxisModelBase & axis )
00082 {
00083   setTickStep( axis );
00084   setFirstTick( axis );
00085 
00086   return genTicks( axis );
00087 }
00088 
00089 inline double FLT_EQUAL( double x, double y )
00090 {
00091   return ( (double)abs( x - y ) <= 2.0 * ( y * FLT_EPSILON + FLT_MIN ) );
00092 }
00093 
00094 void LinearTransform::setTickStep( AxisModelBase & axis )
00095 {
00096   static float goodTicks[] = { 5.0, 4.0, 2.0, 1.0 };
00097   int tickIndex;
00098   
00099   const Range & range = axis.getRange(false);
00100   double rangeLength = range.length();
00101 
00102   double scale_factor = axis.getScaleFactor();
00103   rangeLength *= scale_factor;
00104   const int MIN_TICKS = 3;
00105   
00106   // The following algorithm determines the magnitude of the range...
00107   double rmag = floor( log10( rangeLength ) );
00108   
00109   // ...and then decreases by one magnitude if it would allow less than
00110   // 3 ticks (e.g., the range is 25000 and the magnitude is 10000.  This
00111   // would allow for 2.5 ticks while we in fact would rather have a
00112   // magnitude of 1000, multiplied by a constant, as below).
00113 
00114   if( rangeLength / pow( 10.0, rmag ) < MIN_TICKS ) {
00115     rmag--;
00116   }
00117 
00118   axis.setRMag( rmag );
00119   
00120   double scalelow = range.low() * scale_factor;
00121   double scalehigh = range.high() * scale_factor;
00122 
00123   // We will also need the largest magnitude for labels.
00124   double pmag = max( floor( log10( abs ( scalehigh ) ) ), 
00125                      floor( log10( abs ( scalelow ) ) ) );
00126 
00127   // This if statement changes the magnitude so that if the high or
00128   // low is exactly a power of 10, we will give labels from
00129   // [1,10] * 10^mag and not [0,1] * 10^mag.
00130   if( pow( 10.0, pmag ) == scalehigh ||
00131       pow( 10.0, pmag ) == scalelow ) pmag--;
00132 
00133   axis.setPMag( pmag );
00134   
00135   // Now we determine the above stated constant.  The magnitude is already
00136   // known, so we see what's the closest we can get to exactly 3 ticks
00137   // under this magnitude.  In the above example, a range of 25000 was
00138   // given with a magnitude of 1000.  This algorithm will recognize that
00139   // 5.0 * 1000 will give 5 ticks, which is a good number to have.  If
00140   // the range was 12000 and magnitude 1000 then 5.0 * 1000 would give
00141   // only 2 ticks, not enough.  The loop would then proceed to 4.0 * 1000
00142   // which will give exactly 3 ticks.
00143   double tick_step = 0;
00144   for( tickIndex = 0;
00145        rangeLength /
00146          ( tick_step = goodTicks[tickIndex] * pow( 10.0, rmag ) )
00147          < MIN_TICKS;
00148        tickIndex++ ){};
00149 
00150   axis.setTickStep( tick_step );
00151 }
00152 
00153 
00154 void LinearTransform::setFirstTick( AxisModelBase & axis )
00155 {
00156   const Range & range = axis.getRange(true);
00157   double low = range.low();
00158   double tick_step = axis.getTickStep();
00159 
00160   // This sets the first tick as the low value rounded up to the
00161   // nearest tick step.  If the low value fell on a tick, then that is
00162   // the first tick.  Otherwise, it is the next tick inside the range
00163   // of the data.
00164   axis.setFirstTick( ceil( low / tick_step ) * tick_step );
00165 }
00166 
00167 
00170 const vector < AxisTick > &
00171 LinearTransform::
00172 genTicks( AxisModelBase & axis )
00173 { 
00174   double y = 0.0, ylabel;
00175   
00176   int num_ticks = 0;
00177   m_ticks.clear();
00178   double pmag = axis.getPMag();
00179   double rmag = axis.getRMag();
00180   double first_tick = axis.getFirstTick();
00181   double tick_step  = axis.getTickStep();
00182   double scale_factor = axis.getScaleFactor();
00183   double max_ticks    = axis.getMaxTicks();
00184     
00185   // pmag will get set to 0 if it is less than or equal to 3.  This
00186   // is used later to determine scientific notation.  However, m_rmag
00187   // is still needed as the original magnitude for calculations such
00188   // as decimal place notation, and rounding to nice numbers.
00189   
00190   //   if( fabs( m_pmag ) <= 3.0 ) m_pmag = 0.0;
00191   bool use_pmag = abs ( pmag ) > 3.0;
00192 
00193   axis.setUsePMag ( use_pmag );
00194   
00195   char pstr[10];
00196   char labl[10];
00197 
00198   int decimals = 0;
00199 
00200   // The following if-else block decides the pstr string, which holds
00201   // the number of decimal places in the label.
00202 
00203   //   if( fabs( m_pmag ) > 3.0 ) {
00204   if ( use_pmag ) {  
00205     // If we are greater than mag 3, we are showing scientific
00206     // notation.  How many decimals we show is determined by the
00207     // difference between the range magnitude and the power magnitude.
00208   
00209     decimals = static_cast<int>( pmag - rmag );
00210     // minumum 1 decimal in scientific notation
00211     
00212     if( !decimals ) decimals++;
00213   
00214   } else {
00215     
00216     if( rmag > 0.0 ){
00217     
00218       // If we are less than mag 3 and positive, then no decimal
00219       // accuracy is needed.
00220       
00221       decimals = 0;
00222       
00223     } else {
00224     
00225       // If we are less than mag 3 and negative, then we are suddenly
00226       // looking at decimal numbers not in scientific notation.
00227       // Therefore we hold as many decimal places as the magnitude.
00228       
00229       decimals = static_cast<int>( abs( rmag ) );
00230       
00231     }
00232   
00233   }
00234   // @todo decimals should never be negative here, but it does end up
00235   //    negative in some cases. See the "dirty fix" in Range.cxx, that
00236   //    dirty-fixed this problem too. But a better fix is needed. 
00237   if (decimals < 0) {
00238     decimals = 0;
00239   }
00240   
00241   sprintf( pstr, "%%1.%df", decimals );
00242 
00243   y = first_tick;
00244   const Range & range = axis.getRange(false);
00245   double range_high = range.high();
00246   range_high *= scale_factor;
00247   range_high += 100. * DBL_EPSILON;
00248 
00249  // while( y <= range_high || FLT_EQUAL( range_high, y ) ) {
00250   while( y <= range_high ) {
00251   
00252     if( num_ticks >= max_ticks ) {
00253     
00254       // HERE So far, this has only occurred for empty histograms. The
00255       //easy fix was to do nothing, but there ought to be a better
00256       // way to handle this.
00257       
00258       return m_ticks;
00259     
00260     }
00261 
00262     // The following expression is used to round to the nearest nice
00263     // number, and then return to the original magnitude.
00264     
00265     double value = floor( y / pow( 10.0, rmag ) + 0.5 ) *
00266       pow( 10.0, rmag );
00267 
00268     // Now that the number is nice, we either keep the original magnitude
00269     // or reduce it in order to express it in scientific notation.
00270     
00271     if ( use_pmag )  ylabel = value / pow( 10.0, pmag );
00272     else ylabel = value;
00273 
00274     value /= scale_factor;
00275     sprintf( labl, pstr, ylabel );
00276     m_ticks.push_back( AxisTick ( value, labl ) );
00277     
00278     num_ticks++;
00279     y += tick_step;
00280   
00281   }
00282 
00283   return m_ticks;
00284 }
00285 
00286 const Range & LinearTransform::adjustValues ( AxisModelBase & axis,
00287                                               const Range & limit )
00288 {
00289   //Because the low value, the high value, and the length value of the
00290   //range were so frequently used, I added those three fields. There 
00291   //should be an improvement in performance.
00292   double mylow, myhigh;
00293   
00294   //The use of a step field and of a mag field will be explained when
00295   //they are first initialized.
00296   double step, magnitude;
00297   
00298   const int N_NICE = 6;
00299 #ifndef __STDC__
00300   static
00301 #endif
00302     float nice[N_NICE] = { 1.0, 2.0, 2.5,
00303                            4.0, 5.0, 7.5 };
00304 
00305   const Range & init_range = axis.getRange ( false );
00306   double low = init_range.low ();
00307   double high = init_range.high ();
00308 
00309   if ( ( high - low ) < 10.* DBL_MIN  ) {  // all values in same bin
00310     if ( low > 0.0 ) low *= 0.95;
00311     else  low *= 1.05;
00312 
00313     if ( low == 0. ) { // special case
00314       high = low + 1000. * FLT_EPSILON; // large enough so tick algo works
00315     }
00316     else {
00317       if ( high > 0.) high *= 1.05;
00318       else high *= 0.95;
00319     }
00320 
00321     axis.setRange ( low, high, low );
00322   }  
00323   double range_length;
00324   
00325   int i;
00326   
00327   // This increases myhigh so that "myrange" covers the whole range
00328   // and then some.
00329 
00330   mylow  = low  - 0.05*(high-low);
00331   myhigh = high + 0.05*(high-low);
00332 
00333   range_length = myhigh - mylow;
00334 
00335   // We have now decided on a range.  This tries to move low/high a
00336   // little to end up on a nice number.
00337 
00338   // First checks if either end is near 0.0
00339   if( low >= 0.0 && range_length > ( 1.05 * high ) ) {
00340     Range range ( 0.0, range_length );
00341     axis.setIntersectRange ( range, limit );
00342     return axis.getRange( false );
00343   }
00344   if( high <= 0.0 && -range_length < ( 1.05 * low ) ) {
00345     Range range ( -range_length, 0.0 );
00346     axis.setIntersectRange ( range, limit );
00347     return axis.getRange( false );
00348   }
00349 
00350   // magnitude is used to hold the magnitude of the high or low values.
00351 
00352   i = N_NICE - 1;
00353   if( myhigh != 0.0 )
00354     magnitude = ceil( log10( fabs( myhigh ) ) );
00355   else
00356     magnitude = ceil( log10( fabs( mylow ) ) );
00357   
00358   // What this part does is go through the low, giving it round
00359   // numbers first, but more coarse over time.
00360 
00361   do {
00362     step = nice[i] * pow( 10.0, magnitude );
00363     mylow = floor( low / step ) * step;
00364     myhigh = mylow + 1.05 * range_length;
00365     i--;
00366     if( i < 0 ) {
00367       i = N_NICE - 1;
00368       magnitude--;
00369     }
00370   } while( myhigh < high );
00371 
00372   Range range ( mylow, myhigh, init_range.pos() );  
00373 
00374   axis.setIntersectRange ( range, limit );
00375 
00376   return axis.getRange( false );
00377 }

Generated for HippoDraw-1.14.8.5 by doxygen 1.4.3