LinearTransform.cxx
Go to the documentation of this file.
1 
12 #include "LinearTransform.h"
13 
14 #include "axes/AxisModelBase.h"
15 #include "axes/AxisTick.h"
16 
17 #include <cmath>
18 #include <cstdio>
19 
20 using std::abs;
21 using std::max;
22 using std::vector;
23 
24 namespace hippodraw {
25 
31  : UnaryTransform ( -DBL_MAX, DBL_MAX )
32 {
33  m_name = "Linear";
34 }
35 
37 {
38 }
39 
41  : UnaryTransform ( lt )
42 {
43 }
44 
45 #ifdef CLONE_DEFECT
47 #else
49 #endif
50 {
51  return new LinearTransform ( *this );
52 }
53 
54 bool
56 isLinear () const
57 {
58  return true;
59 }
60 
61 void
63 transform ( double & ) const
64 {
65 }
66 
67 void
69 inverseTransform ( double & ) const
70 {
71 }
72 
73 void
75 transform ( std::vector < double > & ) const
76 {
77 }
78 
79 /* virtual */
80 void
82 validate ( Range & ) const
83 {
84  // Nothing to be done.
85 }
86 
87 const vector < AxisTick > &
90 {
91  setTickStep( axis );
92  setFirstTick( axis );
93 
94  return genTicks( axis );
95 }
96 
97 inline double FLT_EQUAL( double x, double y )
98 {
99  return ( (double)abs( x - y ) <= 2.0 * ( y * FLT_EPSILON + FLT_MIN ) );
100 }
101 
103 {
104  static float goodTicks[] = { 5.0, 4.0, 2.0, 1.0 };
105  int tickIndex;
106 
107  const Range & range = axis.getRange(false);
108  double rangeLength = range.length();
109 
110  double scale_factor = axis.getScaleFactor();
111  rangeLength *= scale_factor;
112  const int MIN_TICKS = 3;
113 
114  // The following algorithm determines the magnitude of the range...
115  double rmag = floor( log10( rangeLength ) );
116 
117  // ...and then decreases by one magnitude if it would allow less than
118  // 3 ticks (e.g., the range is 25000 and the magnitude is 10000. This
119  // would allow for 2.5 ticks while we in fact would rather have a
120  // magnitude of 1000, multiplied by a constant, as below).
121 
122  if( rangeLength / pow( 10.0, rmag ) < MIN_TICKS ) {
123  rmag--;
124  }
125 
126  axis.setRMag( rmag );
127 
128  double scalelow = range.low() * scale_factor;
129  double scalehigh = range.high() * scale_factor;
130 
131  // We will also need the largest magnitude for labels.
132  double pmag = max( floor( log10( abs ( scalehigh ) ) ),
133  floor( log10( abs ( scalelow ) ) ) );
134 
135  // This if statement changes the magnitude so that if the high or
136  // low is exactly a power of 10, we will give labels from
137  // [1,10] * 10^mag and not [0,1] * 10^mag.
138  if( pow( 10.0, pmag ) == scalehigh ||
139  pow( 10.0, pmag ) == scalelow ) pmag--;
140 
141  axis.setPMag( pmag );
142 
143  // Now we determine the above stated constant. The magnitude is already
144  // known, so we see what's the closest we can get to exactly 3 ticks
145  // under this magnitude. In the above example, a range of 25000 was
146  // given with a magnitude of 1000. This algorithm will recognize that
147  // 5.0 * 1000 will give 5 ticks, which is a good number to have. If
148  // the range was 12000 and magnitude 1000 then 5.0 * 1000 would give
149  // only 2 ticks, not enough. The loop would then proceed to 4.0 * 1000
150  // which will give exactly 3 ticks.
151  double tick_step = 0;
152  for( tickIndex = 0;
153  rangeLength /
154  ( tick_step = goodTicks[tickIndex] * pow( 10.0, rmag ) )
155  < MIN_TICKS;
156  tickIndex++ ){};
157 
158  axis.setTickStep( tick_step );
159 }
160 
161 
163 {
164  const Range & range = axis.getRange(true);
165  double low = range.low();
166  double tick_step = axis.getTickStep();
167 
168  // This sets the first tick as the low value rounded up to the
169  // nearest tick step. If the low value fell on a tick, then that is
170  // the first tick. Otherwise, it is the next tick inside the range
171  // of the data.
172  axis.setFirstTick( ceil( low / tick_step ) * tick_step );
173 }
174 
175 
178 const vector < AxisTick > &
181 {
182  double y = 0.0, ylabel;
183 
184  int num_ticks = 0;
185  m_ticks.clear();
186  double pmag = axis.getPMag();
187  double rmag = axis.getRMag();
188  double first_tick = axis.getFirstTick();
189  double tick_step = axis.getTickStep();
190  double scale_factor = axis.getScaleFactor();
191  double max_ticks = axis.getMaxTicks();
192 
193  // pmag will get set to 0 if it is less than or equal to 3. This
194  // is used later to determine scientific notation. However, m_rmag
195  // is still needed as the original magnitude for calculations such
196  // as decimal place notation, and rounding to nice numbers.
197 
198  // if( fabs( m_pmag ) <= 3.0 ) m_pmag = 0.0;
199  bool use_pmag = abs ( pmag ) > 3.0;
200 
201  axis.setUsePMag ( use_pmag );
202 
203  char pstr[10];
204  char labl[10];
205 
206  int decimals = 0;
207 
208  // The following if-else block decides the pstr string, which holds
209  // the number of decimal places in the label.
210 
211  // if( fabs( m_pmag ) > 3.0 ) {
212  if ( use_pmag ) {
213  // If we are greater than mag 3, we are showing scientific
214  // notation. How many decimals we show is determined by the
215  // difference between the range magnitude and the power magnitude.
216 
217  decimals = static_cast<int>( pmag - rmag );
218  // minumum 1 decimal in scientific notation
219 
220  if( !decimals ) decimals++;
221 
222  } else {
223 
224  if( rmag > 0.0 ){
225 
226  // If we are less than mag 3 and positive, then no decimal
227  // accuracy is needed.
228 
229  decimals = 0;
230 
231  } else {
232 
233  // If we are less than mag 3 and negative, then we are suddenly
234  // looking at decimal numbers not in scientific notation.
235  // Therefore we hold as many decimal places as the magnitude.
236 
237  decimals = static_cast<int>( abs( rmag ) );
238 
239  }
240 
241  }
242  // @todo decimals should never be negative here, but it does end up
243  // negative in some cases. See the "dirty fix" in Range.cxx, that
244  // dirty-fixed this problem too. But a better fix is needed.
245  if (decimals < 0) {
246  decimals = 0;
247  }
248 
249  sprintf( pstr, "%%1.%df", decimals );
250 
251  y = first_tick;
252  const Range & range = axis.getRange(false);
253  double range_high = range.high();
254  range_high *= scale_factor;
255  range_high += 100. * DBL_EPSILON;
256 
257  // while( y <= range_high || FLT_EQUAL( range_high, y ) ) {
258  while( y <= range_high ) {
259 
260  if( num_ticks >= max_ticks ) {
261 
262  // HERE So far, this has only occurred for empty histograms. The
263  //easy fix was to do nothing, but there ought to be a better
264  // way to handle this.
265 
266  return m_ticks;
267 
268  }
269 
270  // The following expression is used to round to the nearest nice
271  // number, and then return to the original magnitude.
272 
273  double value = floor( y / pow( 10.0, rmag ) + 0.5 ) *
274  pow( 10.0, rmag );
275 
276  // Now that the number is nice, we either keep the original magnitude
277  // or reduce it in order to express it in scientific notation.
278 
279  if ( use_pmag ) ylabel = value / pow( 10.0, pmag );
280  else ylabel = value;
281 
282  value /= scale_factor;
283  sprintf( labl, pstr, ylabel );
284  m_ticks.push_back( AxisTick ( value, labl ) );
285 
286  num_ticks++;
287  y += tick_step;
288 
289  }
290 
291  return m_ticks;
292 }
293 
295  const Range & limit )
296 {
297  //Because the low value, the high value, and the length value of the
298  //range were so frequently used, I added those three fields. There
299  //should be an improvement in performance.
300  double mylow, myhigh;
301 
302  //The use of a step field and of a mag field will be explained when
303  //they are first initialized.
304  double step, magnitude;
305 
306  const int N_NICE = 6;
307 #ifndef __STDC__
308  static
309 #endif
310  float nice[N_NICE] = { 1.0, 1.5, 2.0,
311  3.0, 5.0, 7.5 };
312 
313  const Range & init_range = axis.getRange ( false );
314  double low = init_range.low ();
315  double high = init_range.high ();
316 
317  if ( ( high - low ) < 10.* DBL_MIN ) { // all values in same bin
318  if ( low > 0.0 ) low *= 0.95;
319  else low *= 1.05;
320 
321  if ( low == 0. ) { // special case
322  high = low + 1000. * FLT_EPSILON; // large enough so tick algo works
323  }
324  else {
325  if ( high > 0.) high *= 1.05;
326  else high *= 0.95;
327  }
328 
329  axis.setRange ( low, high, low );
330  }
331  double range_length;
332 
333  int i;
334 
335  // This increases myhigh so that "myrange" covers the whole range
336  // and then some.
337 
338  mylow = low - 0.05*(high-low);
339  myhigh = high + 0.05*(high-low);
340 
341  range_length = myhigh - mylow;
342 
343  // We have now decided on a range. This tries to move low/high a
344  // little to end up on a nice number.
345 
346  // First checks if either end is near 0.0
347  //checking high against 22*low is equivalent to the
348  //original condition
349  if( low >= 0.0 && high > 22 * low ) {
350  Range range ( 0.0, range_length );
351  axis.setIntersectRange ( range, limit );
352  return axis.getRange( false );
353  }
354  if( high <= 0.0 && low < 22 * high ) {
355  Range range ( -range_length, 0.0 );
356  axis.setIntersectRange ( range, limit );
357  return axis.getRange( false );
358  }
359 
360  // magnitude is used to hold the magnitude of the high or low values.
361  magnitude = floor(log10(abs(range_length)));
362  float norm = range_length / pow(10., magnitude);
363  float r, x;
364 
365  float r_previous = 10;
366  for (i = 0; i < N_NICE; i++) {
367  r = abs(norm / nice[i] - 1);
368  if (r < r_previous) {
369  r_previous = r;
370  x = nice[i];
371  } else
372  break;
373  }
374  //takes as for the step one fifth of nice[i]
375  step = 0.2 * x * pow(10, magnitude - 1);
376  mylow = floor(mylow / step) * step;
377  myhigh = ceil(myhigh / step) * step;
378 
379  Range range ( mylow, myhigh, init_range.pos() );
380 
381  axis.setIntersectRange ( range, limit );
382 
383  return axis.getRange( false );
384 }
385 
386 } // namespace hippodraw
387 

Generated for HippoDraw Class Library by doxygen