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

Generated for HippoDraw Class Library by doxygen