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