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

Last change on this file since 2921 was 2871, checked in by ldelgass, 12 years ago

formatting/style changes only

  • Property svn:eol-style set to native
File size: 14.2 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2#include <stdlib.h>
3#include <stdio.h>
4#include <math.h>
5#include <float.h>
6#include <string.h>
7
8#include "Axis.h"
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 (int)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()
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    _name(axisName),
121    _flags(AUTOSCALE),
122    _title(NULL),
123    _units(NULL),
124    _valueMin(DBL_MAX),
125    _valueMax(-DBL_MAX),
126    _reqMin(NAN),
127    _reqMax(NAN),
128    _min(DBL_MAX),
129    _max(-DBL_MAX),
130    _range(0.0),
131    _scale(0.0),
132    _reqStep(0.0),
133    _major(5),
134    _minor(2)
135{
136}
137
138/*
139 * ----------------------------------------------------------------------
140 *
141 * InRange --
142 *
143 *      Determines if a value lies within a given range.
144 *
145 *      The value is normalized by the current axis range. If the normalized
146 *      value is between [0.0..1.0] then it's in range.  The value is compared
147 *      to 0 and 1., where 0.0 is the minimum and 1.0 is the maximum.
148 *      DBL_EPSILON is the smallest number that can be represented on the host
149 *      machine, such that (1.0 + epsilon) != 1.0.
150 *
151 *      Please note, *max* can't equal *min*.
152 *
153 * Results:
154 *      If the value is within the interval [min..max], 1 is returned; 0
155 *      otherwise.
156 *
157 * ----------------------------------------------------------------------
158 */
159bool
160Axis::InRange(double x)
161{
162    if (_range < DBL_EPSILON) {
163        return (fabs(_max - x) >= DBL_EPSILON);
164    } else {
165        x = (x - _min) * _scale;
166        return ((x >= -DBL_EPSILON) && ((x - 1.0) < DBL_EPSILON));
167    }
168}
169
170void
171Axis::FixRange(double min, double max)
172{
173    if (min == DBL_MAX) {
174        if (DEFINED(_reqMin)) {
175            min = _reqMin;
176        } else {
177            min = (_flags & LOGSCALE) ? 0.001 : 0.0;
178        }
179    }
180    if (max == -DBL_MAX) {
181        if (DEFINED(_reqMax)) {
182            max = _reqMax;
183        } else {
184            max = 1.0;
185        }
186    }
187    if (min >= max) {
188        /*
189         * There is no range of data (i.e. min is not less than max), so
190         * manufacture one.
191         */
192        if (min == 0.0) {
193            min = 0.0, max = 1.0;
194        } else {
195            max = min + (fabs(min) * 0.1);
196        }
197    }
198
199    /*   
200     * The axis limits are either the current data range or overridden by the
201     * values selected by the user with the -min or -max options.
202     */
203    _valueMin = (DEFINED(_reqMin)) ? _reqMin : min;
204    _valueMax = (DEFINED(_reqMax)) ? _reqMax : max;
205    if (_valueMax < _valueMin) {
206        /*   
207         * If the limits still don't make sense, it's because one limit
208         * configuration option (-min or -max) was set and the other default
209         * (based upon the data) is too small or large.  Remedy this by making
210         * up a new min or max from the user-defined limit.
211         */
212        if (!DEFINED(_reqMin)) {
213            _valueMin = _valueMax - (fabs(_valueMax) * 0.1);
214        }
215        if (!DEFINED(_reqMax)) {
216            _valueMax = _valueMin + (fabs(_valueMax) * 0.1);
217        }
218    }
219}
220
221/*
222 * ----------------------------------------------------------------------
223 *
224 * LogScale --
225 *
226 *      Determine the range and units of a log scaled axis.
227 *
228 *      Unless the axis limits are specified, the axis is scaled
229 *      automatically, where the smallest and largest major ticks encompass
230 *      the range of actual data values.  When an axis limit is specified,
231 *      that value represents the smallest(min)/largest(max) value in the
232 *      displayed range of values.
233 *
234 *      Both manual and automatic scaling are affected by the step used.  By
235 *      default, the step is the largest power of ten to divide the range in
236 *      more than one piece.
237 *
238 *      Automatic scaling:
239 *      Find the smallest number of units which contain the range of values.
240 *      The minimum and maximum major tick values will be represent the
241 *      range of values for the axis. This greatest number of major ticks
242 *      possible is 10.
243 *
244 *      Manual scaling:
245 *      Make the minimum and maximum data values the represent the range of
246 *      the values for the axis.  The minimum and maximum major ticks will be
247 *      inclusive of this range.  This provides the largest area for plotting
248 *      and the expected results when the axis min and max values have be set
249 *      by the user (.e.g zooming).  The maximum number of major ticks is 20.
250 *
251 *      For log scale, there's the possibility that the minimum and
252 *      maximum data values are the same magnitude.  To represent the
253 *      points properly, at least one full decade should be shown.
254 *      However, if you zoom a log scale plot, the results should be
255 *      predictable. Therefore, in that case, show only minor ticks.
256 *      Lastly, there should be an appropriate way to handle numbers
257 *      <=0.
258 *
259 *          maxY
260 *            |    units = magnitude (of least significant digit)
261 *            |    high  = largest unit tick < max axis value
262 *      high _|    low   = smallest unit tick > min axis value
263 *            |
264 *            |    range = high - low
265 *            |    # ticks = greatest factor of range/units
266 *           _|
267 *        U   |
268 *        n   |
269 *        i   |
270 *        t  _|
271 *            |
272 *            |
273 *            |
274 *       low _|
275 *            |
276 *            |_minX________________maxX__
277 *            |   |       |      |       |
278 *     minY  low                        high
279 *           minY
280 *
281 *
282 *      numTicks = Number of ticks
283 *      min = Minimum value of axis
284 *      max = Maximum value of axis
285 *      range    = Range of values (max - min)
286 *
287 *      If the number of decades is greater than ten, it is assumed
288 *      that the full set of log-style ticks can't be drawn properly.
289 *
290 * Results:
291 *      None
292 *
293 * ---------------------------------------------------------------------- */
294void
295Axis::LogScale()
296{
297    double range;
298    double tickMin, tickMax;
299    double majorStep, minorStep;
300    int nMajor, nMinor;
301    double min, max;
302
303    nMajor = nMinor = 0;
304    /* Suppress compiler warnings. */
305    majorStep = minorStep = 0.0;
306    tickMin = tickMax = NAN;
307    min = _valueMin, max = _valueMax;
308    if (min < max) {
309        min = (min != 0.0) ? log10(fabs(min)) : 0.0;
310        max = (max != 0.0) ? log10(fabs(max)) : 1.0;
311
312        tickMin = floor(min);
313        tickMax = ceil(max);
314        range = tickMax - tickMin;
315       
316        if (range > 10) {
317            /* There are too many decades to display a major tick at every
318             * decade.  Instead, treat the axis as a linear scale.  */
319            range = NiceNum(range, 0);
320            majorStep = NiceNum(range / _major.reqNumTicks, 1);
321            tickMin = UFLOOR(tickMin, majorStep);
322            tickMax = UCEIL(tickMax, majorStep);
323            nMajor = (int)((tickMax - tickMin) / majorStep) + 1;
324            minorStep = EXP10(floor(log10(majorStep)));
325            if (minorStep == majorStep) {
326                nMinor = 4, minorStep = 0.2;
327            } else {
328                nMinor = ROUND(majorStep / minorStep) - 1;
329            }
330        } else {
331            if (tickMin == tickMax) {
332                tickMax++;
333            }
334            majorStep = 1.0;
335            nMajor = (int)(tickMax - tickMin + 1); /* FIXME: Check this. */
336           
337            minorStep = 0.0;    /* This is a special hack to pass
338                                 * information to the SetTicks
339                                 * method. An interval of 0.0 indicates
340                                 *      1) this is a minor sweep and
341                                 *      2) the axis is log scale. 
342                                 */
343            nMinor = 10;
344        }
345        if ((_flags & TIGHT_MIN) || (DEFINED(_reqMin))) {
346            tickMin = min;
347            nMajor++;
348        }
349        if ((_flags & TIGHT_MAX) || (DEFINED(_reqMax))) {
350            tickMax = max;
351        }
352    }
353    _major.SetValues(floor(tickMin), majorStep, nMajor);
354    _minor.SetValues(minorStep, minorStep, nMinor);
355    _min = tickMin;
356    _max = tickMax;
357    _range = _max - _min;
358    _scale = 1.0 / _range;
359}
360
361/*
362 * ----------------------------------------------------------------------
363 *
364 * Axis::LinearScale --
365 *
366 *      Determine the units of a linear scaled axis.
367 *
368 *      The axis limits are either the range of the data values mapped
369 *      to the axis (autoscaled), or the values specified by the -min
370 *      and -max options (manual).
371 *
372 *      If autoscaled, the smallest and largest major ticks will
373 *      encompass the range of data values.  If the -loose option is
374 *      selected, the next outer ticks are choosen.  If tight, the
375 *      ticks are at or inside of the data limits are used.
376 *
377 *      If manually set, the ticks are at or inside the data limits
378 *      are used.  This makes sense for zooming.  You want the
379 *      selected range to represent the next limit, not something a
380 *      bit bigger.
381 *
382 *      Note: I added an "always" value to the -loose option to force
383 *            the manually selected axes to be loose. It's probably
384 *            not a good idea.
385 *
386 *          maxY
387 *            |    units = magnitude (of least significant digit)
388 *            |    high  = largest unit tick < max axis value
389 *      high _|    low   = smallest unit tick > min axis value
390 *            |
391 *            |    range = high - low
392 *            |    # ticks = greatest factor of range/units
393 *           _|
394 *        U   |
395 *        n   |
396 *        i   |
397 *        t  _|
398 *            |
399 *            |
400 *            |
401 *       low _|
402 *            |
403 *            |_minX________________maxX__
404 *            |   |       |      |       |
405 *     minY  low                        high
406 *           minY
407 *
408 *      numTicks = Number of ticks
409 *      min = Minimum value of axis
410 *      max = Maximum value of axis
411 *      range    = Range of values (max - min)
412 *
413 * Results:
414 *      None.
415 *
416 * Side Effects:
417 *      The axis tick information is set.  The actual tick values will
418 *      be generated later.
419 *
420 * ----------------------------------------------------------------------
421 */
422void
423Axis::LinearScale()
424{
425    double step;
426    double tickMin, tickMax;
427    unsigned int nTicks;
428
429    nTicks = 0;
430    step = 1.0;
431    /* Suppress compiler warning. */
432    tickMin = tickMax = 0.0;
433    if (_valueMin < _valueMax) {
434        double range;
435
436        range = _valueMax - _valueMin;
437        /* Calculate the major tick stepping. */
438        if (_reqStep > 0.0) {
439            /* An interval was designated by the user.  Keep scaling it until
440             * it fits comfortably within the current range of the axis.  */
441            step = _reqStep;
442            while ((2 * step) >= range) {
443                step *= 0.5;
444            }
445        } else {
446            range = NiceNum(range, 0);
447            step = NiceNum(range / _major.reqNumTicks, 1);
448        }
449       
450        /* Find the outer tick values. Add 0.0 to prevent getting -0.0. */
451        tickMin = floor(_valueMin / step) * step + 0.0;
452        tickMax = ceil(_valueMax / step) * step + 0.0;
453       
454        nTicks = ROUND((tickMax - tickMin) / step) + 1;
455    }
456    _major.SetValues(tickMin, step, nTicks);
457
458    /*
459     * The limits of the axis are either the range of the data ("tight") or at
460     * the next outer tick interval ("loose").  The looseness or tightness has
461     * to do with how the axis fits the range of data values.  This option is
462     * overridden when the user sets an axis limit (by either -min or -max
463     * option).  The axis limit is always at the selected limit (otherwise we
464     * assume that user would have picked a different number).
465     */
466    _min = ((_flags & TIGHT_MIN)||(DEFINED(_reqMin))) ? _valueMin : tickMin;
467    _max = ((_flags & TIGHT_MAX)||(DEFINED(_reqMax))) ? _valueMax : tickMax;
468    _range = _max - _min;
469    _scale = 1.0 / _range;
470
471    /* Now calculate the minor tick step and number. */
472
473    if ((_minor.reqNumTicks > 0) && (_minor.autoscale())) {
474        nTicks = _minor.reqNumTicks - 1;
475        step = 1.0 / (nTicks + 1);
476    } else {
477        nTicks = 0;             /* No minor ticks. */
478        step = 0.5;             /* Don't set the minor tick interval to
479                                 * 0.0. It makes the GenerateTicks routine
480                                 * create minor log-scale tick marks.  */
481    }
482    _minor.SetValues(step, step, nTicks);
483}
484
485
486void
487Axis::SetScale(double min, double max)
488{
489    FixRange(min, max);
490    if (_flags & LOGSCALE) {
491        LogScale();
492    } else {
493        LinearScale();
494    }
495    _major.SweepTicks();
496    _minor.SweepTicks();
497    MakeTicks();
498}
499
500void
501Axis::MakeTicks()
502{
503    _major.Reset();
504    _minor.Reset();
505    int i;
506    for (i = 0; i < _major.numTicks(); i++) {
507        double t1, t2;
508        int j;
509       
510        t1 = _major.tick(i);
511        /* Minor ticks */
512        for (j = 0; j < _minor.numTicks(); j++) {
513            t2 = t1 + (_major.step() * _minor.tick(j));
514            if (!InRange(t2)) {
515                continue;
516            }
517            if (t1 == t2) {
518                continue;       // Don't add duplicate minor ticks.
519            }
520            _minor.Append(t2);
521        }
522        if (!InRange(t1)) {
523            continue;
524        }
525        _major.Append(t1);
526    }
527}
528
529double
530Axis::Map(double x)
531{
532    if ((_flags & LOGSCALE) && (x != 0.0)) {
533        x = log10(fabs(x));
534    }
535    /* Map graph coordinate to normalized coordinates [0..1] */
536    x = (x - _min) * _scale;
537    if (_flags & DESCENDING) {
538        x = 1.0 - x;
539    }
540    return x;
541}
542
543double
544Axis::InvMap(double x)
545{
546    if (_flags & DESCENDING) {
547        x = 1.0 - x;
548    }
549    x = (x * _range) + _min;
550    if (_flags & LOGSCALE) {
551        x = EXP10(x);
552    }
553    return x;
554}
Note: See TracBrowser for help on using the repository browser.