source: trunk/packages/vizservers/nanovis/Axis.cpp @ 2720

Last change on this file since 2720 was 2096, checked in by ldelgass, 14 years ago

Normalize line endings, set eol-style to native on *.cpp, *.h files

  • Property svn:eol-style set to native
File size: 14.2 KB
Line 
1#include <stdlib.h>
2#include <stdio.h>
3#include <math.h>
4#include <float.h>
5#include <string.h>
6#include "Axis.h"
7
8NaN _NaN;
9
10inline bool DEFINED(double x) {
11    return !isnan(x);
12}
13
14inline double EXP10(double x) {
15    return pow(10.0, x);
16}
17
18inline int ROUND(double x) {
19    return round(x);
20}
21
22inline double UROUND(double x, double u) {
23    return (ROUND((x)/(u)))*u;
24}
25
26inline double UCEIL(double x, double u) {
27    return (ceil((x)/(u)))*u;
28}
29
30inline double UFLOOR(double x, double u) {
31    return (floor((x)/(u)))*u;
32}
33
34/*
35 * ----------------------------------------------------------------------
36 *
37 * NiceNum --
38 *
39 *      Reference: Paul Heckbert, "Nice Numbers for Graph Labels",
40 *                 Graphics Gems, pp 61-63. 
41 *
42 *      Finds a "nice" number approximately equal to x.
43 *
44 * ----------------------------------------------------------------------
45 */
46static double
47NiceNum(
48    double x,
49    int round)                  /* If non-zero, round. Otherwise take ceiling
50                                 * of value. */
51{
52    double expt;                /* Exponent of x */
53    double frac;                /* Fractional part of x */
54    double nice;                /* Nice, rounded fraction */
55
56    expt = floor(log10(x));
57    frac = x / EXP10(expt);     /* between 1 and 10 */
58    if (round) {
59        if (frac < 1.5) {
60            nice = 1.0;
61        } else if (frac < 3.0) {
62            nice = 2.0;
63        } else if (frac < 7.0) {
64            nice = 5.0;
65        } else {
66            nice = 10.0;
67        }
68    } else {
69        if (frac <= 1.0) {
70            nice = 1.0;
71        } else if (frac <= 2.0) {
72            nice = 2.0;
73        } else if (frac <= 5.0) {
74            nice = 5.0;
75        } else {
76            nice = 10.0;
77        }
78    }
79    return nice * EXP10(expt);
80}
81
82void
83Ticks::SetTicks(void)
84{
85    numTicks_ = 0;
86    ticks_ = new float[nSteps_];
87    if (step_ == 0.0) {
88        /* Hack: A zero step indicates to use log values. */
89        unsigned int i;
90        /* Precomputed log10 values [1..10] */
91        static double logTable[] = {
92            0.0,
93            0.301029995663981,
94            0.477121254719662,
95            0.602059991327962,
96            0.698970004336019,
97            0.778151250383644,
98            0.845098040014257,
99            0.903089986991944,
100            0.954242509439325,
101            1.0
102        };
103        for (i = 0; i < nSteps_; i++) {
104            ticks_[i] = logTable[i];
105        }
106    } else {
107        double value;
108        unsigned int i;
109   
110        value = initial_;       /* Start from smallest axis tick */
111        for (i = 0; i < nSteps_; i++) {
112            value = initial_ + (step_ * i);
113            ticks_[i] = UROUND(value, step_);
114        }
115    }
116    numTicks_ = nSteps_;
117}
118
119Axis::Axis(const char *axisName) :
120    major_(5), minor_(2)
121{
122    name_ = NULL;
123    name(axisName);
124    units_ = NULL;
125    title_ = NULL;
126    valueMin_ = DBL_MAX, valueMax_ = -DBL_MAX;
127    min_ = DBL_MAX, max_ = -DBL_MAX;
128    reqMin_ = reqMax_ = _NaN;
129    range_ = 0.0, scale_ = 0.0;
130    reqStep_ = 0.0;
131    flags_ = AUTOSCALE;
132}
133
134/*
135 * ----------------------------------------------------------------------
136 *
137 * InRange --
138 *
139 *      Determines if a value lies within a given range.
140 *
141 *      The value is normalized by the current axis range. If the normalized
142 *      value is between [0.0..1.0] then it's in range.  The value is compared
143 *      to 0 and 1., where 0.0 is the minimum and 1.0 is the maximum.
144 *      DBL_EPSILON is the smallest number that can be represented on the host
145 *      machine, such that (1.0 + epsilon) != 1.0.
146 *
147 *      Please note, *max* can't equal *min*.
148 *
149 * Results:
150 *      If the value is within the interval [min..max], 1 is returned; 0
151 *      otherwise.
152 *
153 * ----------------------------------------------------------------------
154 */
155bool
156Axis::InRange(double x)
157{
158    if (range_ < DBL_EPSILON) {
159        return (fabs(max_ - x) >= DBL_EPSILON);
160    } else {
161        x = (x - min_) * scale_;
162        return ((x >= -DBL_EPSILON) && ((x - 1.0) < DBL_EPSILON));
163    }
164}
165
166void
167Axis::FixRange(double min, double max)
168{
169    if (min == DBL_MAX) {
170        if (DEFINED(reqMin_)) {
171            min = reqMin_;
172        } else {
173            min = (flags_ & LOGSCALE) ? 0.001 : 0.0;
174        }
175    }
176    if (max == -DBL_MAX) {
177        if (DEFINED(reqMax_)) {
178            max = reqMax_;
179        } else {
180            max = 1.0;
181        }
182    }
183    if (min >= max) {
184        /*
185         * There is no range of data (i.e. min is not less than max), so
186         * manufacture one.
187         */
188        if (min == 0.0) {
189            min = 0.0, max = 1.0;
190        } else {
191            max = min + (fabs(min) * 0.1);
192        }
193    }
194
195    /*   
196     * The axis limits are either the current data range or overridden by the
197     * values selected by the user with the -min or -max options.
198     */
199    valueMin_ = (DEFINED(reqMin_)) ? reqMin_ : min;
200    valueMax_ = (DEFINED(reqMax_)) ? reqMax_ : max;
201    if (valueMax_ < valueMin_) {
202        /*   
203         * If the limits still don't make sense, it's because one limit
204         * configuration option (-min or -max) was set and the other default
205         * (based upon the data) is too small or large.  Remedy this by making
206         * up a new min or max from the user-defined limit.
207         */
208        if (!DEFINED(reqMin_)) {
209            valueMin_ = valueMax_ - (fabs(valueMax_) * 0.1);
210        }
211        if (!DEFINED(reqMax_)) {
212            valueMax_ = valueMin_ + (fabs(valueMax_) * 0.1);
213        }
214    }
215}
216
217/*
218 * ----------------------------------------------------------------------
219 *
220 * LogScale --
221 *
222 *      Determine the range and units of a log scaled axis.
223 *
224 *      Unless the axis limits are specified, the axis is scaled
225 *      automatically, where the smallest and largest major ticks encompass
226 *      the range of actual data values.  When an axis limit is specified,
227 *      that value represents the smallest(min)/largest(max) value in the
228 *      displayed range of values.
229 *
230 *      Both manual and automatic scaling are affected by the step used.  By
231 *      default, the step is the largest power of ten to divide the range in
232 *      more than one piece.
233 *
234 *      Automatic scaling:
235 *      Find the smallest number of units which contain the range of values.
236 *      The minimum and maximum major tick values will be represent the
237 *      range of values for the axis. This greatest number of major ticks
238 *      possible is 10.
239 *
240 *      Manual scaling:
241 *      Make the minimum and maximum data values the represent the range of
242 *      the values for the axis.  The minimum and maximum major ticks will be
243 *      inclusive of this range.  This provides the largest area for plotting
244 *      and the expected results when the axis min and max values have be set
245 *      by the user (.e.g zooming).  The maximum number of major ticks is 20.
246 *
247 *      For log scale, there's the possibility that the minimum and
248 *      maximum data values are the same magnitude.  To represent the
249 *      points properly, at least one full decade should be shown.
250 *      However, if you zoom a log scale plot, the results should be
251 *      predictable. Therefore, in that case, show only minor ticks.
252 *      Lastly, there should be an appropriate way to handle numbers
253 *      <=0.
254 *
255 *          maxY
256 *            |    units = magnitude (of least significant digit)
257 *            |    high  = largest unit tick < max axis value
258 *      high _|    low   = smallest unit tick > min axis value
259 *            |
260 *            |    range = high - low
261 *            |    # ticks = greatest factor of range/units
262 *           _|
263 *        U   |
264 *        n   |
265 *        i   |
266 *        t  _|
267 *            |
268 *            |
269 *            |
270 *       low _|
271 *            |
272 *            |_minX________________maxX__
273 *            |   |       |      |       |
274 *     minY  low                        high
275 *           minY
276 *
277 *
278 *      numTicks = Number of ticks
279 *      min = Minimum value of axis
280 *      max = Maximum value of axis
281 *      range    = Range of values (max - min)
282 *
283 *      If the number of decades is greater than ten, it is assumed
284 *      that the full set of log-style ticks can't be drawn properly.
285 *
286 * Results:
287 *      None
288 *
289 * ---------------------------------------------------------------------- */
290void
291Axis::LogScale()
292{
293    double range;
294    double tickMin, tickMax;
295    double majorStep, minorStep;
296    int nMajor, nMinor;
297    double min, max;
298
299    nMajor = nMinor = 0;
300    /* Suppress compiler warnings. */
301    majorStep = minorStep = 0.0;
302    tickMin = tickMax = _NaN;
303    min = valueMin_, max = valueMax_;
304    if (min < max) {
305        min = (min != 0.0) ? log10(fabs(min)) : 0.0;
306        max = (max != 0.0) ? log10(fabs(max)) : 1.0;
307
308        tickMin = floor(min);
309        tickMax = ceil(max);
310        range = tickMax - tickMin;
311       
312        if (range > 10) {
313            /* There are too many decades to display a major tick at every
314             * decade.  Instead, treat the axis as a linear scale.  */
315            range = NiceNum(range, 0);
316            majorStep = NiceNum(range / major_.reqNumTicks, 1);
317            tickMin = UFLOOR(tickMin, majorStep);
318            tickMax = UCEIL(tickMax, majorStep);
319            nMajor = (int)((tickMax - tickMin) / majorStep) + 1;
320            minorStep = EXP10(floor(log10(majorStep)));
321            if (minorStep == majorStep) {
322                nMinor = 4, minorStep = 0.2;
323            } else {
324                nMinor = ROUND(majorStep / minorStep) - 1;
325            }
326        } else {
327            if (tickMin == tickMax) {
328                tickMax++;
329            }
330            majorStep = 1.0;
331            nMajor = (int)(tickMax - tickMin + 1); /* FIXME: Check this. */
332           
333            minorStep = 0.0;    /* This is a special hack to pass
334                                 * information to the SetTicks
335                                 * method. An interval of 0.0 indicates
336                                 *      1) this is a minor sweep and
337                                 *      2) the axis is log scale. 
338                                 */
339            nMinor = 10;
340        }
341        if ((flags_ & TIGHT_MIN) || (DEFINED(reqMin_))) {
342            tickMin = min;
343            nMajor++;
344        }
345        if ((flags_ & TIGHT_MAX) || (DEFINED(reqMax_))) {
346            tickMax = max;
347        }
348    }
349    major_.SetValues(majorStep, nMajor, floor(tickMin));
350    minor_.SetValues(minorStep, nMinor, minorStep);
351    min_ = tickMin;
352    max_ = tickMax;
353    range_ = max_ - min_;
354    scale_ = 1.0 / range_;
355}
356
357/*
358 * ----------------------------------------------------------------------
359 *
360 * Axis::LinearScale --
361 *
362 *      Determine the units of a linear scaled axis.
363 *
364 *      The axis limits are either the range of the data values mapped
365 *      to the axis (autoscaled), or the values specified by the -min
366 *      and -max options (manual).
367 *
368 *      If autoscaled, the smallest and largest major ticks will
369 *      encompass the range of data values.  If the -loose option is
370 *      selected, the next outer ticks are choosen.  If tight, the
371 *      ticks are at or inside of the data limits are used.
372 *
373 *      If manually set, the ticks are at or inside the data limits
374 *      are used.  This makes sense for zooming.  You want the
375 *      selected range to represent the next limit, not something a
376 *      bit bigger.
377 *
378 *      Note: I added an "always" value to the -loose option to force
379 *            the manually selected axes to be loose. It's probably
380 *            not a good idea.
381 *
382 *          maxY
383 *            |    units = magnitude (of least significant digit)
384 *            |    high  = largest unit tick < max axis value
385 *      high _|    low   = smallest unit tick > min axis value
386 *            |
387 *            |    range = high - low
388 *            |    # ticks = greatest factor of range/units
389 *           _|
390 *        U   |
391 *        n   |
392 *        i   |
393 *        t  _|
394 *            |
395 *            |
396 *            |
397 *       low _|
398 *            |
399 *            |_minX________________maxX__
400 *            |   |       |      |       |
401 *     minY  low                        high
402 *           minY
403 *
404 *      numTicks = Number of ticks
405 *      min = Minimum value of axis
406 *      max = Maximum value of axis
407 *      range    = Range of values (max - min)
408 *
409 * Results:
410 *      None.
411 *
412 * Side Effects:
413 *      The axis tick information is set.  The actual tick values will
414 *      be generated later.
415 *
416 * ----------------------------------------------------------------------
417 */
418void
419Axis::LinearScale()
420{
421    double step;
422    double tickMin, tickMax;
423    unsigned int nTicks;
424
425    nTicks = 0;
426    step = 1.0;
427    /* Suppress compiler warning. */
428    tickMin = tickMax = 0.0;
429    if (valueMin_ < valueMax_) {
430        double range;
431
432        range = valueMax_ - valueMin_;
433        /* Calculate the major tick stepping. */
434        if (reqStep_ > 0.0) {
435            /* An interval was designated by the user.  Keep scaling it until
436             * it fits comfortably within the current range of the axis.  */
437            step = reqStep_;
438            while ((2 * step) >= range) {
439                step *= 0.5;
440            }
441        } else {
442            range = NiceNum(range, 0);
443            step = NiceNum(range / major_.reqNumTicks, 1);
444        }
445       
446        /* Find the outer tick values. Add 0.0 to prevent getting -0.0. */
447        tickMin = floor(valueMin_ / step) * step + 0.0;
448        tickMax = ceil(valueMax_ / step) * step + 0.0;
449       
450        nTicks = ROUND((tickMax - tickMin) / step) + 1;
451    }
452    major_.SetValues(tickMin, step, nTicks);
453
454    /*
455     * The limits of the axis are either the range of the data ("tight") or at
456     * the next outer tick interval ("loose").  The looseness or tightness has
457     * to do with how the axis fits the range of data values.  This option is
458     * overridden when the user sets an axis limit (by either -min or -max
459     * option).  The axis limit is always at the selected limit (otherwise we
460     * assume that user would have picked a different number).
461     */
462    min_ = ((flags_ & TIGHT_MIN)||(DEFINED(reqMin_))) ? valueMin_ : tickMin;
463    max_ = ((flags_ & TIGHT_MAX)||(DEFINED(reqMax_))) ? valueMax_ : tickMax;
464    range_ = max_ - min_;
465    scale_ = 1.0 / range_;
466
467    /* Now calculate the minor tick step and number. */
468
469    if ((minor_.reqNumTicks > 0) && (minor_.autoscale())) {
470        nTicks = minor_.reqNumTicks - 1;
471        step = 1.0 / (nTicks + 1);
472    } else {
473        nTicks = 0;             /* No minor ticks. */
474        step = 0.5;             /* Don't set the minor tick interval to
475                                 * 0.0. It makes the GenerateTicks routine
476                                 * create minor log-scale tick marks.  */
477    }
478    minor_.SetValues(step, step, nTicks);
479}
480
481
482void
483Axis::SetScale(double min, double max)
484{
485    FixRange(min, max);
486    if (flags_ & LOGSCALE) {
487        LogScale();
488    } else {
489        LinearScale();
490    }
491    major_.SweepTicks();
492    minor_.SweepTicks();
493    MakeTicks();
494}
495
496void
497Axis::MakeTicks(void)
498{
499    major_.Reset();
500    minor_.Reset();
501    int i;
502    for (i = 0; i < major_.numTicks(); i++) {
503        double t1, t2;
504        int j;
505       
506        t1 = major_.tick(i);
507        /* Minor ticks */
508        for (j = 0; j < minor_.numTicks(); j++) {
509            t2 = t1 + (major_.step() * minor_.tick(j));
510            if (!InRange(t2)) {
511                continue;
512            }
513            if (t1 == t2) {
514                continue;       // Don't add duplicate minor ticks.
515            }
516            minor_.Append(t2);
517        }
518        if (!InRange(t1)) {
519            continue;
520        }
521        major_.Append(t1);
522    }
523}
524
525double
526Axis::Map(double x)
527{
528    if ((flags_ & LOGSCALE) && (x != 0.0)) {
529        x = log10(fabs(x));
530    }
531    /* Map graph coordinate to normalized coordinates [0..1] */
532    x = (x - min_) * scale_;
533    if (flags_ & DESCENDING) {
534        x = 1.0 - x;
535    }
536    return x;
537}
538
539double
540Axis::InvMap(double x)
541{
542    if (flags_ & DESCENDING) {
543        x = 1.0 - x;
544    }
545    x = (x * range_) + min_;
546    if (flags_ & LOGSCALE) {
547        x = EXP10(x);
548    }
549    return x;
550}
Note: See TracBrowser for help on using the repository browser.