LogTransform.cxx

Go to the documentation of this file.
00001 
00012 #ifdef _MSC_VER
00013 #include "msdevstudio/MSconfig.h"
00014 #endif
00015 
00016 #include "transforms/LogTransform.h"
00017 
00018 #include "axes/AxisModelBase.h"
00019 #include "axes/AxisTick.h"
00020 
00021 #include <algorithm>
00022 #include <vector>
00023 
00024 #include <cmath>
00025 #include <cstdio>
00026 
00027 #include <cassert>
00028 
00029 #ifdef ITERATOR_MEMBER_DEFECT
00030 using namespace std;
00031 #else
00032 using std::abs;
00033 using std::log10;
00034 using std::pow;
00035 using std::transform;
00036 using std::vector;
00037 #endif
00038 
00039 using namespace hippodraw;
00040 
00041 LogTransform::LogTransform ()
00042   : UnaryTransform ( 10 * DBL_MIN, DBL_MAX )
00043 {
00044   m_name = "Log";
00045 }
00046 
00047 LogTransform::~LogTransform ()
00048 {
00049 }
00050 
00051 LogTransform::LogTransform ( const LogTransform & lt )
00052   : UnaryTransform ( lt )
00053 {
00054 }
00055 
00056 #ifdef CLONE_DEFECT
00057 TransformBase * LogTransform::clone () const
00058 #else
00059 LogTransform  * LogTransform::clone () const
00060 #endif
00061 {
00062   return new LogTransform ( *this );
00063 }
00064 
00065 bool
00066 LogTransform::
00067 isLinear () const
00068 {
00069   return false;
00070 }
00071 
00072 void LogTransform::transform ( double & x ) const
00073 {
00074   x = log10 ( x );
00075 }
00076 
00077 void LogTransform::inverseTransform ( double & x ) const
00078 {
00079   x = pow ( 10.0, x );
00080 }
00081 
00082 void
00083 LogTransform::
00084 transform ( std::vector < double > & x ) const
00085 {
00086 //  #ifdef TRANSFORM_DEFECT
00087   vector< double >::iterator first = x.begin();
00088   for ( ; first != x.end(); ++first ) {
00089     *first = log10 ( *first );
00090   }
00091 //  #else
00092 
00093   // The following doesn't work for MS BC++ nor gcc 3.0.  It does work
00094   // with older versions of gcc and Sun CC, however.  So, who is right?
00095 
00096 //    std::transform ( x.begin (), x.end (), x.begin (), log10 );
00097 //  #endif
00098 }
00099 
00100 /* virtual */
00101 void LogTransform::validate ( Range & range ) const
00102 {
00103   double lo = range.low ();
00104   double hi = range.high ();
00105 
00106   if ( hi <= 0.0 ) hi = 1.0;
00107 //   if ( lo <= 0.0 ) lo = hi - 0.99 * hi;
00108   if ( lo <= 0.0 ) lo = range.pos();
00109 
00110   range.setLow ( lo );
00111   range.setHigh ( hi );
00112 }
00113 
00114 /* virtual */
00115 const vector < AxisTick > &
00116 LogTransform::
00117 setTicks ( AxisModelBase & axis )
00118 {
00119   setTickStep( axis );
00120   setFirstTick( axis );
00121 
00122   return genTicks( axis );
00123 }
00124 
00125 void LogTransform::setTickStep( AxisModelBase & axis )
00126 {
00127   const Range & range = axis.getRange(true);
00128   double low = range.low();
00129   double high = range.high();
00130   double rangeMag = high / low;
00131   
00132   // The following algorithm determines the magnitude of the range.
00133   double rmag = floor( log10( rangeMag ) );
00134 
00135   // This is used to determine the first tick.
00136   double pmag = ceil( log10( low ) );
00137 
00138   // Now we find the magnitude between ticks, getting the minimum
00139   // number of ticks without going below 4.
00140   double tmag = floor( rmag / 3.0 );
00141 
00142   double tick_step = pow( 10.0, tmag );
00143 
00144   axis.setRMag( rmag );
00145   axis.setPMag( pmag );
00146   axis.setTickStep( tick_step );
00147   
00148 }
00149 
00150 void LogTransform::setFirstTick( AxisModelBase & axis )
00151 {
00152   const Range & range = axis.getRange(true);
00153   double low = range.low();
00154   
00155   //double high = range.high();
00156   
00157   // This sets the first tick as the low value rounded up to the
00158   // nearest power of 10.
00159   double pmag = axis.getPMag();
00160   double first_tick = pow( 10.0, pmag );
00161   double tmp = 0.0;
00162   while( ( tmp = prevStep( first_tick, axis )) >= low ) {
00163     first_tick = tmp;
00164   }
00165 
00166   axis.setFirstTick( first_tick );
00167 }
00168 
00173 inline double FLT_EQUAL( double x, double y )
00174 {
00175   return ( (double)abs( x - y ) <= 2.0 * ( y * FLT_EPSILON + FLT_MIN ) );
00176 }
00177 
00181 const vector < AxisTick > &
00182 LogTransform::
00183 genTicks ( AxisModelBase & axis )
00184 {
00185   double ylabel;
00186 
00187   int num_ticks = 0;
00188 
00189   m_ticks.clear ();
00190   
00191   // mag is used for scientific notation.
00192   double mag;
00193 
00194   char pstr[10];
00195   char labl[10];
00196   
00197   double first_tick = axis.getFirstTick();
00198   double tick_step  = axis.getTickStep();
00199   double scale_factor = axis.getScaleFactor();
00200   double max_ticks    = axis.getMaxTicks();
00201   const Range & range = axis.getRange(true);
00202 
00203   double range_low = range.low();
00204   double range_high = range.high();
00205 
00206   double last_tick = first_tick;//pow( 10.0, ceil( log10( range_high ) ) );
00207   double tmp = 0.0;
00208   while ( ( tmp = nextStep( last_tick, axis ) ) <= range_high ) {
00209     last_tick = tmp;
00210   }
00211 
00212   // The flag for scientific notation activation.
00213   bool sci_not = 
00214     ( floor( log10( last_tick ) ) > 3.0 ) ||
00215     ( floor ( log10 ( first_tick ) ) ) < -3.0;
00216   if( sci_not ) {
00217     sprintf( pstr, "%%1.0fe%%d" );
00218   }
00219 
00220   double value = first_tick / tick_step;
00221   bool fresh = true;
00222   while( value <= range_high || FLT_EQUAL( range_high, value ) ) {
00223     if( num_ticks >= max_ticks ) {
00224       // So far, this has only occurred for empty histograms. The
00225       //easy fix was to do nothing, but there ought to be a better
00226       // way to handle this.
00227       return m_ticks;
00228     }
00229 
00230     if( !fresh ) {
00231       value = nextStep( value, axis );
00232     } else {
00233       value *= tick_step;
00234       fresh = false;
00235     }
00236 
00237     if( value > range_high ) break;
00238     if( sci_not ) {
00239       mag = floor( log10( value ) );
00240       ylabel = value / pow( 10.0, mag );
00241       sprintf( pstr, "%%1.0fe%%d" );
00242       sprintf( labl, pstr, ylabel, static_cast<int>( mag ) );
00243     } else {
00244       ylabel = value;
00245       double tmp = floor( log10( value ) );
00246       if( tmp > 0.0 ) tmp = 0.0;
00247       tmp = fabs( tmp );
00248       sprintf( pstr, "%%1.%df", static_cast<int>( tmp ) );
00249       sprintf( labl, pstr, ylabel );
00250     } 
00251     
00252     double y = value / scale_factor;
00253     m_ticks.push_back( AxisTick ( y, labl ) );
00254     
00255     num_ticks++;
00256   }
00257 
00258   if ( num_ticks < 3 )
00259     {
00260 
00261       m_ticks.clear();
00262       
00263       double xx = (log10(range_high) - log10(range_low)) / 4;
00264       double yy = log10(range_low);
00265     
00266       for(int i=1; i<4; i++)
00267         {
00268         
00269           value = pow (10.0, xx * i + yy);
00270           
00271           if( value > range_high ) continue;
00272           
00273           double tmp = floor( log10( value ) );
00274           if( tmp > 0.0 ) tmp = 0.0;
00275           tmp = fabs( tmp );
00276           if (tmp == 0.0)
00277             {
00278               value = floor(value);
00279             }
00280       
00281           sprintf( pstr, "%%1.%df", static_cast<int>( tmp ) );
00282           sprintf( labl, pstr, value); 
00283       
00284           double y = value / scale_factor;
00285           
00286           m_ticks.push_back( AxisTick ( y, labl ) );
00287         }
00288     }
00289 
00290   return m_ticks;
00291 }
00292 
00293 double LogTransform::nextStep ( double current, AxisModelBase & axis )
00294 {
00295   double tick_step = axis.getTickStep(); // Must already be called
00296   if( tick_step == 1.0 ) {
00297     int bottom = static_cast<int>( current /
00298                                  pow( 10.0, floor( log10( current ) ) ) );
00299     // Look! I used a switch statement in C++!!!!!  What this does
00300     // is go through and add the intermediate 2 and 5 ticks if the
00301     // powers of 10 alone would not have given the minimum number of
00302     // ticks.  m_tick_step is completely ignored if the flag is
00303     // true, since it is assumed to be 0.
00304     
00307     switch( bottom ) {
00308     case 1:
00309       current *= 2.0;
00310       break;
00311     case 2:
00312       current /= 2.0;
00313       current *= 5.0;
00314       break;
00315     case 3:
00316       current /= 4.0;
00317       current *= 5.0;
00318       break;
00319     case 4: // a 5 becomes a 4 sometimes because of round of error
00320     case 5:
00321       current *= 2.0;
00322       break;
00323     default:
00324       current *= 2.0;
00325 //       assert ( false );
00326     }
00327   } else {
00328     current *= tick_step;
00329   }
00330   return current;
00331 }
00332 
00335 double LogTransform::prevStep ( double current, AxisModelBase & axis )
00336 {
00337   double tick_step = axis.getTickStep(); // It must already be called.
00338   if( tick_step == 1.0 ) {
00339     int base = static_cast<int>( current /
00340                                  pow( 10.0, floor( log10( current ) ) ) );
00341     // Look! I used a switch statement in C++!!!!!  What this does
00342     // is go through and add the intermediate 2 and 5 ticks if the
00343     // powers of 10 alone would not have given the minimum number of
00344     // ticks.  m_tick_step is completely ignored if the flag is
00345     // true, since it is assumed to be 0.
00346     
00347     switch( base ) {
00348     case 1:
00349       current /= 2.0;
00350       break;
00351     case 2:
00352       current /= 2.0;
00353       break;
00354     case 4:
00355       current /= 5.0;
00356       current *= 2.0;
00357       break;
00358     case 5:
00359       current /= 5.0;
00360       current *= 2.0;
00361       break;
00362     default:
00363       assert ( false );
00364     }
00365   } else {
00366     current /= tick_step;
00367   }
00368   return current;
00369 }
00370 
00371 const Range &
00372 LogTransform::adjustValues ( AxisModelBase & axis, const Range & limit )
00373 {
00374   //Because the low value, the high value, and the length value of the
00375   //range were so frequently used, I added those three fields. There 
00376   //should be an improvement in performance.
00377   
00378   double mylow, myhigh;
00379 
00380   // We want to make sure that this is autoscaled. Therefore, to 
00381   // be on the safe side we set the minimum range to 0, so that 
00382   // the minimum positive value is used.
00383   //   Range log( 0.0, getRange().high(), getRange().pos() );
00384   //   setRange( log );
00385   
00386   adjustLogValues( axis );
00387   setTickStep( axis ); // For tick steps are needed by nextStep() & prevStep().
00388 
00389   const Range & init_range = axis.getRange(false);
00390   double low = init_range.low();
00391   double high = init_range.high();
00392 
00393   myhigh = mylow = pow( 10.0, axis.getPMag() );
00394 
00395   // This decreases mylow so that "myrange" covers the whole range
00396   // and then some.
00397   double scale_factor = axis.getScaleFactor();
00398   while( mylow >= low * scale_factor ) {
00399     mylow = prevStep( mylow, axis );
00400   }
00401   
00402   // This increases myhigh so that "myrange" covers the whole range
00403   // and then some.
00404   while( myhigh <= high * scale_factor ) {
00405     myhigh = nextStep( myhigh, axis );
00406   }
00407   
00408   // If the range has a magnitude < 10.0, reduce the minimum of the
00409   // range by one tick mark.
00410   if( myhigh / mylow < 10.0 ) {
00411     mylow = prevStep( mylow, axis );
00412   }
00413   
00414   // If the range still has a magnitude < 10.0, increase the maximum
00415   // of the range by one tick mark until the magnitude is 10.0.
00416   while( myhigh / mylow < 10.0 ) {
00417     myhigh = nextStep( myhigh, axis );
00418   }
00419 
00420   myhigh /= scale_factor;
00421   mylow  /= scale_factor;
00422 
00423   Range new_range ( mylow, myhigh, init_range.pos() );
00424   
00425   // Compare the newrange with init_range. If new range is too wide
00426   // compared to init_range, then do not set newrange.
00427 
00428   double new_width = new_range.length();
00429   double init_width = init_range.length();
00430   
00431   if ( new_width > init_width * 10 ){  // This 10 is a hack. Could be any
00432                                        // decent number.
00433     if ( low < 0 ) {
00434       low *= 1.05;    // This 5% is also a hack.
00435     }
00436     else{
00437       low *= 0.95;
00438     }
00439     
00440     if ( high < 0 ){
00441       high *= 0.95;
00442     }
00443     else{
00444       high *= 1.05;
00445     }
00446     
00447     Range newRange ( low, high, init_range.pos() );
00448     axis.setIntersectRange ( newRange, limit );
00449     return axis.getRange( false );
00450   
00451   }
00452 
00453   axis.setIntersectRange ( new_range, limit );
00454 
00455   return axis.getRange( false );
00456 }
00457 
00458 
00459 const Range &  LogTransform::adjustLogValues ( AxisModelBase & axis )
00460 {
00461   const Range & r = axis.getRange( false );
00462   double low = r.low();
00463   double high = r.high();
00464   double pos = r.pos();
00465   
00466   if( low > 0.0 ) return r;
00467 
00468   if( pos == high ) { // Will give no range
00469     double l = pos / 10.0;
00470     double h = pos * 10.0;
00471     axis.setRange ( l, h, pos );
00472 
00473     return axis.getRange( false );
00474   }
00475   if( pos == DBL_MAX || pos <= 0.0 ) { // No positive values!!!
00476     axis.setRange ( 0.01, 100.0, 1.0 );
00477     return axis.getRange( false );
00478   }
00479   if ( low <= 0.0 ) axis.setRange ( 0.5 * pos, high, pos );
00480   else  axis.setRange ( pos, high, pos );
00481 
00482   return axis.getRange( false );
00483 }

Generated for HippoDraw Class Library by doxygen