# source:nanovis/trunk/Axis.cpp@5722

Last change on this file since 5722 was 5478, checked in by ldelgass, 4 years ago

Begin refactoring axis/grid in nanovis: remove unused features, make the style
more C++

• Property svn:eol-style set to `native`
File size: 8.3 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:
6 *   George A. Howlett <gah@purdue.edu>
7 *   Leif Delgass <ldelgass@purdue.edu>
8 */
9#include <string>
10
11#include <stdlib.h>
12#include <stdio.h>
13#include <math.h>
14#include <float.h>
15#include <string.h>
16
17#include "Axis.h"
18#include "Trace.h"
19
20using namespace nv;
21
22inline double EXP10(double x) {
23    return pow(10.0, x);
24}
25
26inline int ROUND(double x) {
27    return (int)round(x);
28}
29
30inline double UROUND(double x, double u) {
31    return (ROUND((x)/(u)))*u;
32}
33
34inline double UCEIL(double x, double u) {
35    return (ceil((x)/(u)))*u;
36}
37
38inline double UFLOOR(double x, double u) {
39    return (floor((x)/(u)))*u;
40}
41
42/**
43 * Reference: Paul Heckbert, "Nice Numbers for Graph Labels",
44 *            Graphics Gems, pp 61-63.
45 *
46 * Finds a "nice" number approximately equal to x.
47 */
48static double
49niceNum(double x,
50        int round)              /* If non-zero, round. Otherwise take ceiling
51                                 * of value. */
52{
53    double expt;                /* Exponent of x */
54    double frac;                /* Fractional part of x */
55    double nice;                /* Nice, rounded fraction */
56
57    expt = floor(log10(x));
58    frac = x / EXP10(expt);     /* between 1 and 10 */
59    if (round) {
60        if (frac < 1.5) {
61            nice = 1.0;
62        } else if (frac < 3.0) {
63            nice = 2.0;
64        } else if (frac < 7.0) {
65            nice = 5.0;
66        } else {
67            nice = 10.0;
68        }
69    } else {
70        if (frac <= 1.0) {
71            nice = 1.0;
72        } else if (frac <= 2.0) {
73            nice = 2.0;
74        } else if (frac <= 5.0) {
75            nice = 5.0;
76        } else {
77            nice = 10.0;
78        }
79    }
80    return nice * EXP10(expt);
81}
82
83void
84Ticks::setTicks()
85{
86    _numTicks = 0;
87    _ticks = new float[_nSteps];
88    // Start from smallest axis tick
89    double value = _initial;
90    for (unsigned int i = 0; i < _nSteps; i++) {
91        value = _initial + (_step * i);
92        _ticks[i] = UROUND(value, _step);
93    }
94    _numTicks = _nSteps;
95}
96
97Axis::Axis(const char *title) :
98    _title(title),
99    _tightMin(false),
100    _tightMax(false),
101    _reqStep(0.0),
102    _valueMin(DBL_MAX),
103    _valueMax(-DBL_MAX),
104    _min(DBL_MAX),
105    _max(-DBL_MAX),
106    _major(5),
107    _minor(2)
108{
109}
110
111/**
112 * \brief Determines if a value lies within a given range.
113 *
114 * The value is normalized by the current axis range. If the normalized
115 * value is between [0.0,1.0] then it's in range.  The value is compared
116 * to 0 and 1., where 0.0 is the minimum and 1.0 is the maximum.
117 * DBL_EPSILON is the smallest number that can be represented on the host
118 * machine, such that (1.0 + epsilon) != 1.0.
119 *
120 * Please note, *max* can't equal *min*.
121 *
122 * \return If the value is within the interval [min,max], 1 is returned; 0
123 * otherwise.
124 */
125bool
126Axis::inRange(double x)
127{
128    if ((_max - _min) < DBL_EPSILON) {
129        return (fabs(_max - x) >= DBL_EPSILON);
130    } else {
131        x = map(x);
132        return ((x >= -DBL_EPSILON) && ((x - 1.0) < DBL_EPSILON));
133    }
134}
135
136void
137Axis::fixRange(double min, double max)
138{
139    if (min == DBL_MAX) {
140        min = 0.0;
141    }
142    if (max == -DBL_MAX) {
143        max = 1.0;
144    }
145    if (min >= max) {
146        // No range, so pick a default
147        if (min == 0.0) {
148            min = 0.0, max = 1.0;
149        } else {
150            max = min + (fabs(min) * 0.1);
151        }
152    }
153
154    _valueMin = min;
155    _valueMax = max;
156}
157
158/**
159 * \brief Determine the units of a linear scaled axis.
160 *
161 * The axis limits are either the range of the data values mapped
162 * to the axis (autoscaled), or the values specified by the -min
163 * and -max options (manual).
164 *
165 * If autoscaled, the smallest and largest major ticks will
166 * encompass the range of data values.  If the -loose option is
167 * selected, the next outer ticks are choosen.  If tight, the
168 * ticks are at or inside of the data limits are used.
169 *
170 * If manually set, the ticks are at or inside the data limits
171 * are used.  This makes sense for zooming.  You want the
172 * selected range to represent the next limit, not something a
173 * bit bigger.
174 *
175 * Note: I added an "always" value to the -loose option to force
176 * the manually selected axes to be loose. It's probably
177 * not a good idea.
178 *
179 * <pre>
180 *          maxY
181 *            |    units = magnitude (of least significant digit)
182 *            |    high  = largest unit tick < max axis value
183 *      high _|    low   = smallest unit tick > min axis value
184 *            |
185 *            |    range = high - low
186 *            |    # ticks = greatest factor of range/units
187 *           _|
188 *        U   |
189 *        n   |
190 *        i   |
191 *        t  _|
192 *            |
193 *            |
194 *            |
195 *       low _|
196 *            |
197 *            |_minX________________maxX__
198 *            |   |       |      |       |
199 *     minY  low                        high
200 *           minY
201 *
202 *      numTicks = Number of ticks
203 *      min = Minimum value of axis
204 *      max = Maximum value of axis
205 *      range = Range of values (max - min)
206 *
207 * </pre>
208 *
209 * Side Effects:
210 *      The axis tick information is set.  The actual tick values will
211 *      be generated later.
212 */
213void
214Axis::linearScale()
215{
216    double tickMin = 0., tickMax = 0.;
217    double step = 1.0;
218    unsigned int nTicks = 0;
219
220    if (_valueMin < _valueMax) {
221        double range = _valueMax - _valueMin;
222        /* Calculate the major tick stepping. */
223        if (_reqStep > 0.0) {
224            /* An interval was designated by the user.  Keep scaling it until
225             * it fits comfortably within the current range of the axis.  */
226            step = _reqStep;
227            while ((2 * step) >= range) {
228                step *= 0.5;
229            }
230        } else {
231            range = niceNum(range, 0);
232            step = niceNum(range / _major.reqNumTicks, 1);
233        }
234
235        /* Find the outer tick values. Add 0.0 to prevent getting -0.0. */
236        tickMin = floor(_valueMin / step) * step + 0.0;
237        tickMax = ceil(_valueMax / step) * step + 0.0;
238
239        nTicks = ROUND((tickMax - tickMin) / step) + 1;
240    }
241    _major.setValues(tickMin, step, nTicks);
242
243    /*
244     * The limits of the axis are either the range of the data ("tight") or at
245     * the next outer tick interval ("loose").  The looseness or tightness has
246     * to do with how the axis fits the range of data values.
247     */
248    _min = _tightMin ? _valueMin : tickMin;
249    _max = _tightMax ? _valueMax : tickMax;
250
251    /* Now calculate the minor tick step and number. */
252
253    if (_minor.reqNumTicks > 0) {
254        nTicks = _minor.reqNumTicks - 1;
255        step = 1.0 / (nTicks + 1);
256    } else {
257        nTicks = 0;             /* No minor ticks. */
258        step = 0.5;             /* Don't set the minor tick interval to
259                                 * 0.0. It makes the GenerateTicks routine
260                                 * create minor log-scale tick marks.  */
261    }
262    _minor.setValues(step, step, nTicks);
263}
264
265/**
266 * \brief This sets the data range of the axis.
267 */
268void
269Axis::setRange(double min, double max)
270{
271    // set valueMin/Max (overridden by requested min/max if present)
272    fixRange(min, max);
273    // Set steps
274    linearScale();
275    // Fill ticks float arrays based on spacing
276    _major.sweepTicks();
277    _minor.sweepTicks();
278    // Copy to linked lists
279    makeTicks();
280}
281
282/**
283 * \brief Copy tick values to major/minor linked lists, skipping duplicate values
284 */
285void
286Axis::makeTicks()
287{
288    _major.reset();
289    _minor.reset();
290    for (int i = 0; i < _major.numTicks(); i++) {
291        double t1 = _major.tick(i);
292        /* Minor ticks */
293        for (int j = 0; j < _minor.numTicks(); j++) {
294            double t2 = t1 + (_major.step() * _minor.tick(j));
295            if (!inRange(t2)) {
296                continue;
297            }
298            if (t1 == t2) {
299                continue;        // Don't add duplicate minor ticks.
300            }
301            _minor.append(t2);
302        }
303        if (!inRange(t1)) {
304            continue;
305        }
306        _major.append(t1);
307    }
308}
309
310/**
311 * \brief Map world coordinate [_min,_max] to normalized coordinate [0,1]
312 */
313double
314Axis::map(double x)
315{
316    x = (x - _min) / (_max - _min);
317    return x;
318}
319
320/**
321 * \brief Map normalized coordinate [0,1] to world coordinate [_min,_max]
322 */
323double
324Axis::invMap(double x)
325{
326    x = (x * (_max - _min)) + _min;
327    return x;
328}
Note: See TracBrowser for help on using the repository browser.