source: nanovis/branches/1.1/Axis.cpp @ 4879

Last change on this file since 4879 was 3502, checked in by ldelgass, 11 years ago

Add basic VTK structured points reader to nanovis, update copyright dates.

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