annotate base/ScaleTickIntervals.h @ 1414:c57994e1edd7 scale-ticks

Add logarithmic ticks. This is getting complicated!
author Chris Cannam
date Thu, 04 May 2017 10:14:56 +0100
parents c6fa111b4553
children 12316a9bcc8f
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@1413 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@1414 29 #include "LogRange.h"
Chris@1414 30
Chris@1407 31 class ScaleTickIntervals
Chris@1407 32 {
Chris@1407 33 public:
Chris@1407 34 struct Range {
Chris@1407 35 double min; // start of value range
Chris@1407 36 double max; // end of value range
Chris@1412 37 int n; // number of divisions (approximate only)
Chris@1407 38 };
Chris@1407 39
Chris@1407 40 struct Tick {
Chris@1407 41 double value; // value this tick represents
Chris@1407 42 std::string label; // value as written
Chris@1407 43 };
Chris@1407 44
Chris@1407 45 struct Ticks {
Chris@1407 46 double initial; // value of first tick
Chris@1407 47 double spacing; // increment between ticks
Chris@1407 48 double roundTo; // what all displayed values should be rounded to
Chris@1407 49 bool fixed; // whether to use fixed precision (%f rather than %e)
Chris@1407 50 int precision; // number of dp (%f) or sf (%e)
Chris@1407 51 std::vector<Tick> ticks; // computed tick values and labels
Chris@1407 52 };
Chris@1407 53
Chris@1407 54 static Ticks linear(Range r) {
Chris@1414 55 return linearTicks(r);
Chris@1414 56 }
Chris@1407 57
Chris@1414 58 static Ticks logarithmic(Range r) {
Chris@1414 59 return logTicks(r);
Chris@1414 60 }
Chris@1414 61
Chris@1414 62 private:
Chris@1414 63 static Ticks unexplodedTicks(Range r)
Chris@1414 64 {
Chris@1407 65 if (r.n < 1) {
Chris@1407 66 return {};
Chris@1407 67 }
Chris@1407 68 if (r.max < r.min) {
Chris@1414 69 return unexplodedTicks({ r.max, r.min, r.n });
Chris@1407 70 }
Chris@1407 71
Chris@1407 72 double inc = (r.max - r.min) / r.n;
Chris@1407 73 if (inc == 0) {
Chris@1411 74 #ifdef DEBUG_SCALE_TICK_INTERVALS
Chris@1411 75 std::cerr << "inc == 0, using trivial range" << std::endl;
Chris@1411 76 #endif
Chris@1411 77 double roundTo = r.min;
Chris@1411 78 if (roundTo <= 0.0) {
Chris@1411 79 roundTo = 1.0;
Chris@1411 80 }
Chris@1411 81 Ticks t { r.min, 1.0, roundTo, true, 1, {} };
Chris@1407 82 return t;
Chris@1407 83 }
Chris@1408 84
Chris@1408 85 double digInc = log10(inc);
Chris@1408 86 double digMax = log10(fabs(r.max));
Chris@1408 87 double digMin = log10(fabs(r.min));
Chris@1408 88
Chris@1409 89 int precInc = int(trunc(digInc));
Chris@1409 90 if (double(precInc) != digInc) {
Chris@1409 91 precInc -= 1;
Chris@1409 92 }
Chris@1408 93
Chris@1408 94 bool fixed = false;
Chris@1408 95 if (precInc > -4 && precInc < 4) {
Chris@1408 96 fixed = true;
Chris@1408 97 } else if ((digMax >= -3.0 && digMax <= 2.0) &&
Chris@1408 98 (digMin >= -3.0 && digMin <= 3.0)) {
Chris@1408 99 fixed = true;
Chris@1408 100 }
Chris@1408 101
Chris@1408 102 int precRange = int(ceil(digMax - digInc));
Chris@1408 103
Chris@1408 104 int prec = 1;
Chris@1408 105
Chris@1408 106 if (fixed) {
Chris@1410 107 if (precInc < 0) {
Chris@1410 108 prec = -precInc;
Chris@1410 109 } else if (precInc > 0) {
Chris@1410 110 prec = 0;
Chris@1408 111 }
Chris@1408 112 } else {
Chris@1408 113 prec = precRange;
Chris@1408 114 }
Chris@1408 115
Chris@1411 116 double roundTo = pow(10.0, precInc);
Chris@1411 117
Chris@1411 118 #ifdef DEBUG_SCALE_TICK_INTERVALS
Chris@1408 119 std::cerr << "\nmin = " << r.min << ", max = " << r.max << ", n = " << r.n
Chris@1408 120 << ", inc = " << inc << std::endl;
Chris@1408 121 std::cerr << "digMax = " << digMax << ", digInc = " << digInc
Chris@1408 122 << std::endl;
Chris@1408 123 std::cerr << "fixed = " << fixed << ", inc = " << inc
Chris@1408 124 << ", precInc = " << precInc << ", precRange = " << precRange
Chris@1408 125 << ", prec = " << prec << std::endl;
Chris@1408 126 std::cerr << "roundTo = " << roundTo << std::endl;
Chris@1411 127 #endif
Chris@1408 128
Chris@1407 129 inc = round(inc / roundTo) * roundTo;
Chris@1408 130 if (inc < roundTo) inc = roundTo;
Chris@1408 131
Chris@1407 132 double min = ceil(r.min / roundTo) * roundTo;
Chris@1407 133 if (min > r.max) min = r.max;
Chris@1407 134
Chris@1413 135 if (!fixed && min != 0.0) {
Chris@1413 136 double digNewMin = log10(fabs(min));
Chris@1413 137 if (digNewMin < digInc) {
Chris@1413 138 prec = int(ceil(digMax - digNewMin));
Chris@1413 139 #ifdef DEBUG_SCALE_TICK_INTERVALS
Chris@1413 140 std::cerr << "min is smaller than increment, adjusting prec to "
Chris@1413 141 << prec << std::endl;
Chris@1413 142 #endif
Chris@1413 143 }
Chris@1413 144 }
Chris@1413 145
Chris@1408 146 Ticks t { min, inc, roundTo, fixed, prec, {} };
Chris@1407 147 return t;
Chris@1407 148 }
Chris@1407 149
Chris@1414 150 static Ticks linearTicks(Range r) {
Chris@1414 151 Ticks t = unexplodedTicks(r);
Chris@1414 152 explode(r, t, false);
Chris@1414 153 return t;
Chris@1414 154 }
Chris@1414 155
Chris@1414 156 static Ticks logTicks(Range r) {
Chris@1414 157 Range mapped(r);
Chris@1414 158 LogRange::mapRange(mapped.min, mapped.max);
Chris@1414 159 Ticks t = unexplodedTicks(mapped);
Chris@1414 160 if (fabs(mapped.min - mapped.max) > 3) {
Chris@1414 161 t.fixed = false;
Chris@1414 162 }
Chris@1414 163 explode(mapped, t, true);
Chris@1414 164 return t;
Chris@1414 165 }
Chris@1414 166
Chris@1414 167 static Tick makeTick(bool fixed, int precision, double value) {
Chris@1414 168 const int buflen = 40;
Chris@1414 169 char buffer[buflen];
Chris@1414 170 snprintf(buffer, buflen,
Chris@1414 171 fixed ? "%.*f" : "%.*e",
Chris@1414 172 precision, value);
Chris@1414 173 return Tick({ value, std::string(buffer) });
Chris@1414 174 }
Chris@1414 175
Chris@1414 176 static void explode(const Range &r, Ticks &t, bool logUnmap) {
Chris@1411 177 #ifdef DEBUG_SCALE_TICK_INTERVALS
Chris@1407 178 std::cerr << "initial = " << t.initial << ", spacing = " << t.spacing
Chris@1407 179 << ", roundTo = " << t.roundTo << ", fixed = " << t.fixed
Chris@1407 180 << ", precision = " << t.precision << std::endl;
Chris@1411 181 #endif
Chris@1414 182 if (t.spacing == 0.0) {
Chris@1414 183 return;
Chris@1414 184 }
Chris@1411 185 double eps = 1e-7;
Chris@1411 186 if (t.spacing < eps * 10.0) {
Chris@1411 187 eps = t.spacing / 10.0;
Chris@1411 188 }
Chris@1414 189 double max = std::max(r.min, r.max);
Chris@1412 190 int n = 0;
Chris@1412 191 while (true) {
Chris@1412 192 double value = t.initial + n * t.spacing;
Chris@1407 193 value = t.roundTo * round(value / t.roundTo);
Chris@1414 194 if (value >= max + eps) {
Chris@1412 195 break;
Chris@1412 196 }
Chris@1414 197 if (logUnmap) {
Chris@1414 198 value = pow(10.0, value);
Chris@1414 199 }
Chris@1414 200 t.ticks.push_back(makeTick(t.fixed, t.precision, value));
Chris@1412 201 ++n;
Chris@1407 202 }
Chris@1407 203 }
Chris@1407 204 };
Chris@1407 205
Chris@1407 206 #endif