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