annotate base/ScaleTickIntervals.h @ 1412:b7a9edee85e0 scale-ticks

Change loop to something that feels more correct, though it makes no difference to the tests here. More tests, one failing.
author Chris Cannam
date Thu, 04 May 2017 08:32:41 +0100
parents 1f0d071e7ce6
children c6fa111b4553
rev   line source
Chris@1407 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@1407 2
Chris@1407 3 /*
Chris@1407 4 Sonic Visualiser
Chris@1407 5 An audio file viewer and annotation editor.
Chris@1407 6 Centre for Digital Music, Queen Mary, University of London.
Chris@1407 7 This file copyright 2006-2017 Chris Cannam and QMUL.
Chris@1407 8
Chris@1407 9 This program is free software; you can redistribute it and/or
Chris@1407 10 modify it under the terms of the GNU General Public License as
Chris@1407 11 published by the Free Software Foundation; either version 2 of the
Chris@1407 12 License, or (at your option) any later version. See the file
Chris@1407 13 COPYING included with this distribution for more information.
Chris@1407 14 */
Chris@1407 15
Chris@1407 16 #ifndef SV_SCALE_TICK_INTERVALS_H
Chris@1407 17 #define SV_SCALE_TICK_INTERVALS_H
Chris@1407 18
Chris@1407 19 #include <string>
Chris@1407 20 #include <vector>
Chris@1407 21 #include <cmath>
Chris@1407 22
Chris@1412 23 #define DEBUG_SCALE_TICK_INTERVALS 1
Chris@1411 24
Chris@1411 25 #ifdef DEBUG_SCALE_TICK_INTERVALS
Chris@1407 26 #include <iostream>
Chris@1411 27 #endif
Chris@1407 28
Chris@1407 29 class ScaleTickIntervals
Chris@1407 30 {
Chris@1407 31 public:
Chris@1407 32 struct Range {
Chris@1407 33 double min; // start of value range
Chris@1407 34 double max; // end of value range
Chris@1412 35 int n; // number of divisions (approximate only)
Chris@1407 36 };
Chris@1407 37
Chris@1407 38 struct Tick {
Chris@1407 39 double value; // value this tick represents
Chris@1407 40 std::string label; // value as written
Chris@1407 41 };
Chris@1407 42
Chris@1407 43 struct Ticks {
Chris@1407 44 double initial; // value of first tick
Chris@1407 45 double spacing; // increment between ticks
Chris@1407 46 double roundTo; // what all displayed values should be rounded to
Chris@1407 47 bool fixed; // whether to use fixed precision (%f rather than %e)
Chris@1407 48 int precision; // number of dp (%f) or sf (%e)
Chris@1407 49 std::vector<Tick> ticks; // computed tick values and labels
Chris@1407 50 };
Chris@1407 51
Chris@1407 52 static Ticks linear(Range r) {
Chris@1407 53
Chris@1407 54 if (r.n < 1) {
Chris@1407 55 return {};
Chris@1407 56 }
Chris@1407 57 if (r.max < r.min) {
Chris@1407 58 return linear({ r.max, r.min, r.n });
Chris@1407 59 }
Chris@1407 60
Chris@1407 61 double inc = (r.max - r.min) / r.n;
Chris@1407 62 if (inc == 0) {
Chris@1411 63 #ifdef DEBUG_SCALE_TICK_INTERVALS
Chris@1411 64 std::cerr << "inc == 0, using trivial range" << std::endl;
Chris@1411 65 #endif
Chris@1411 66 double roundTo = r.min;
Chris@1411 67 if (roundTo <= 0.0) {
Chris@1411 68 roundTo = 1.0;
Chris@1411 69 }
Chris@1411 70 Ticks t { r.min, 1.0, roundTo, true, 1, {} };
Chris@1407 71 explode(r, t);
Chris@1407 72 return t;
Chris@1407 73 }
Chris@1408 74
Chris@1408 75 double digInc = log10(inc);
Chris@1408 76 double digMax = log10(fabs(r.max));
Chris@1408 77 double digMin = log10(fabs(r.min));
Chris@1408 78
Chris@1409 79 int precInc = int(trunc(digInc));
Chris@1409 80 if (double(precInc) != digInc) {
Chris@1409 81 precInc -= 1;
Chris@1409 82 }
Chris@1408 83
Chris@1408 84 bool fixed = false;
Chris@1408 85 if (precInc > -4 && precInc < 4) {
Chris@1408 86 fixed = true;
Chris@1408 87 } else if ((digMax >= -3.0 && digMax <= 2.0) &&
Chris@1408 88 (digMin >= -3.0 && digMin <= 3.0)) {
Chris@1408 89 fixed = true;
Chris@1408 90 }
Chris@1408 91
Chris@1408 92 int precRange = int(ceil(digMax - digInc));
Chris@1408 93
Chris@1408 94 int prec = 1;
Chris@1408 95
Chris@1408 96 if (fixed) {
Chris@1410 97 if (precInc < 0) {
Chris@1410 98 prec = -precInc;
Chris@1410 99 } else if (precInc > 0) {
Chris@1410 100 prec = 0;
Chris@1408 101 }
Chris@1408 102 } else {
Chris@1408 103 prec = precRange;
Chris@1408 104 }
Chris@1408 105
Chris@1411 106 double roundTo = pow(10.0, precInc);
Chris@1411 107
Chris@1411 108 #ifdef DEBUG_SCALE_TICK_INTERVALS
Chris@1408 109 std::cerr << "\nmin = " << r.min << ", max = " << r.max << ", n = " << r.n
Chris@1408 110 << ", inc = " << inc << std::endl;
Chris@1408 111 std::cerr << "digMax = " << digMax << ", digInc = " << digInc
Chris@1408 112 << std::endl;
Chris@1408 113 std::cerr << "fixed = " << fixed << ", inc = " << inc
Chris@1408 114 << ", precInc = " << precInc << ", precRange = " << precRange
Chris@1408 115 << ", prec = " << prec << std::endl;
Chris@1408 116 std::cerr << "roundTo = " << roundTo << std::endl;
Chris@1411 117 #endif
Chris@1408 118
Chris@1407 119 inc = round(inc / roundTo) * roundTo;
Chris@1408 120 if (inc < roundTo) inc = roundTo;
Chris@1408 121
Chris@1407 122 double min = ceil(r.min / roundTo) * roundTo;
Chris@1407 123 if (min > r.max) min = r.max;
Chris@1407 124
Chris@1408 125 Ticks t { min, inc, roundTo, fixed, prec, {} };
Chris@1407 126 explode(r, t);
Chris@1407 127 return t;
Chris@1407 128 }
Chris@1407 129
Chris@1407 130 private:
Chris@1407 131 static void explode(const Range &r, Ticks &t) {
Chris@1411 132 #ifdef DEBUG_SCALE_TICK_INTERVALS
Chris@1407 133 std::cerr << "initial = " << t.initial << ", spacing = " << t.spacing
Chris@1407 134 << ", roundTo = " << t.roundTo << ", fixed = " << t.fixed
Chris@1407 135 << ", precision = " << t.precision << std::endl;
Chris@1411 136 #endif
Chris@1407 137 auto makeTick = [&](double value) {
Chris@1407 138 const int buflen = 40;
Chris@1407 139 char buffer[buflen];
Chris@1407 140 snprintf(buffer, buflen,
Chris@1407 141 t.fixed ? "%.*f" : "%.*e",
Chris@1407 142 t.precision, value);
Chris@1407 143 return Tick({ value, std::string(buffer) });
Chris@1407 144 };
Chris@1411 145 double eps = 1e-7;
Chris@1411 146 if (t.spacing < eps * 10.0) {
Chris@1411 147 eps = t.spacing / 10.0;
Chris@1411 148 }
Chris@1412 149 int n = 0;
Chris@1412 150 while (true) {
Chris@1412 151 double value = t.initial + n * t.spacing;
Chris@1407 152 value = t.roundTo * round(value / t.roundTo);
Chris@1412 153 if (value >= r.max + eps) {
Chris@1412 154 break;
Chris@1412 155 }
Chris@1407 156 t.ticks.push_back(makeTick(value));
Chris@1412 157 ++n;
Chris@1407 158 }
Chris@1407 159 }
Chris@1407 160 };
Chris@1407 161
Chris@1407 162 #endif