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

Last change on this file since 2974 was 2946, checked in by ldelgass, 12 years ago

Need to strdup() name in Axis ctor.

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