annotate base/ScaleTickIntervals.h @ 1418:e7cb4fb2aee4 scale-ticks

Rework log scale calculation, update to changed interface
author Chris Cannam
date Thu, 04 May 2017 15:37:43 +0100
parents 359147a50853
children e7e626a87a1e
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@1417 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@1417 45 typedef std::vector<Tick> Ticks;
Chris@1418 46
Chris@1418 47 /**
Chris@1418 48 * Return a set of ticks that divide the range r linearly into
Chris@1418 49 * roughly r.n equal divisions, in such a way as to yield
Chris@1418 50 * reasonably human-readable labels.
Chris@1418 51 */
Chris@1407 52 static Ticks linear(Range r) {
Chris@1414 53 return linearTicks(r);
Chris@1414 54 }
Chris@1407 55
Chris@1418 56 /**
Chris@1418 57 * Return a set of ticks that divide the range r into roughly r.n
Chris@1418 58 * logarithmic divisions, in such a way as to yield reasonably
Chris@1418 59 * human-readable labels.
Chris@1418 60 */
Chris@1414 61 static Ticks logarithmic(Range r) {
Chris@1418 62 LogRange::mapRange(r.min, r.max);
Chris@1418 63 return logarithmicAlready(r);
Chris@1418 64 }
Chris@1418 65
Chris@1418 66 /**
Chris@1418 67 * Return a set of ticks that divide the range r into roughly r.n
Chris@1418 68 * logarithmic divisions, on the asssumption that r.min and r.max
Chris@1418 69 * already represent the logarithms of the boundary values rather
Chris@1418 70 * than the values themselves.
Chris@1418 71 */
Chris@1418 72 static Ticks logarithmicAlready(Range r) {
Chris@1414 73 return logTicks(r);
Chris@1414 74 }
Chris@1418 75
Chris@1414 76 private:
Chris@1418 77 enum Display {
Chris@1418 78 Fixed,
Chris@1418 79 Scientific,
Chris@1418 80 Auto
Chris@1418 81 };
Chris@1418 82
Chris@1417 83 struct Instruction {
Chris@1417 84 double initial; // value of first tick
Chris@1417 85 double limit; // max from original range
Chris@1417 86 double spacing; // increment between ticks
Chris@1417 87 double roundTo; // what all displayed values should be rounded to
Chris@1418 88 Display display; // whether to use fixed precision (%e, %f, or %g)
Chris@1417 89 int precision; // number of dp (%f) or sf (%e)
Chris@1417 90 bool logUnmap; // true if values represent logs of display values
Chris@1417 91 };
Chris@1417 92
Chris@1417 93 static Instruction linearInstruction(Range r)
Chris@1414 94 {
Chris@1418 95 Display display = Auto;
Chris@1418 96
Chris@1407 97 if (r.max < r.min) {
Chris@1417 98 return linearInstruction({ r.max, r.min, r.n });
Chris@1407 99 }
Chris@1418 100 if (r.n < 1 || r.max == r.min) {
Chris@1418 101 return { r.min, r.min, 1.0, r.min, display, 1, false };
Chris@1418 102 }
Chris@1407 103
Chris@1407 104 double inc = (r.max - r.min) / r.n;
Chris@1408 105
Chris@1408 106 double digInc = log10(inc);
Chris@1408 107 double digMax = log10(fabs(r.max));
Chris@1408 108 double digMin = log10(fabs(r.min));
Chris@1408 109
Chris@1418 110 int precInc = int(floor(digInc));
Chris@1418 111 double roundTo = pow(10.0, precInc);
Chris@1408 112
Chris@1408 113 if (precInc > -4 && precInc < 4) {
Chris@1418 114 display = Fixed;
Chris@1418 115 } else if ((digMax >= -2.0 && digMax <= 3.0) &&
Chris@1408 116 (digMin >= -3.0 && digMin <= 3.0)) {
Chris@1418 117 display = Fixed;
Chris@1418 118 } else {
Chris@1418 119 display = Scientific;
Chris@1408 120 }
Chris@1408 121
Chris@1408 122 int precRange = int(ceil(digMax - digInc));
Chris@1408 123
Chris@1408 124 int prec = 1;
Chris@1408 125
Chris@1418 126 if (display == Fixed) {
Chris@1415 127 if (digInc < 0) {
Chris@1410 128 prec = -precInc;
Chris@1415 129 } else {
Chris@1410 130 prec = 0;
Chris@1408 131 }
Chris@1408 132 } else {
Chris@1408 133 prec = precRange;
Chris@1408 134 }
Chris@1408 135
Chris@1411 136 #ifdef DEBUG_SCALE_TICK_INTERVALS
Chris@1408 137 std::cerr << "\nmin = " << r.min << ", max = " << r.max << ", n = " << r.n
Chris@1408 138 << ", inc = " << inc << std::endl;
Chris@1408 139 std::cerr << "digMax = " << digMax << ", digInc = " << digInc
Chris@1408 140 << std::endl;
Chris@1418 141 std::cerr << "display = " << display << ", inc = " << inc
Chris@1408 142 << ", precInc = " << precInc << ", precRange = " << precRange
Chris@1408 143 << ", prec = " << prec << std::endl;
Chris@1408 144 std::cerr << "roundTo = " << roundTo << std::endl;
Chris@1411 145 #endif
Chris@1418 146
Chris@1418 147 double min = r.min;
Chris@1408 148
Chris@1418 149 if (roundTo != 0.0) {
Chris@1418 150 inc = round(inc / roundTo) * roundTo;
Chris@1418 151 if (inc < roundTo) inc = roundTo;
Chris@1418 152 min = ceil(min / roundTo) * roundTo;
Chris@1418 153 if (min > r.max) min = r.max;
Chris@1418 154 }
Chris@1407 155
Chris@1418 156 if (display == Scientific && min != 0.0) {
Chris@1413 157 double digNewMin = log10(fabs(min));
Chris@1413 158 if (digNewMin < digInc) {
Chris@1413 159 prec = int(ceil(digMax - digNewMin));
Chris@1413 160 #ifdef DEBUG_SCALE_TICK_INTERVALS
Chris@1413 161 std::cerr << "min is smaller than increment, adjusting prec to "
Chris@1413 162 << prec << std::endl;
Chris@1413 163 #endif
Chris@1413 164 }
Chris@1413 165 }
Chris@1413 166
Chris@1418 167 return { min, r.max, inc, roundTo, display, prec, false };
Chris@1418 168 }
Chris@1418 169
Chris@1418 170 static Instruction logInstruction(Range r)
Chris@1418 171 {
Chris@1418 172 Display display = Auto;
Chris@1418 173
Chris@1418 174 if (r.n < 1) {
Chris@1418 175 return {};
Chris@1418 176 }
Chris@1418 177 if (r.max < r.min) {
Chris@1418 178 return logInstruction({ r.max, r.min, r.n });
Chris@1418 179 }
Chris@1418 180 if (r.max == r.min) {
Chris@1418 181 return { r.min, r.max, 1.0, r.min, display, 1, true };
Chris@1418 182 }
Chris@1418 183
Chris@1418 184 double inc = (r.max - r.min) / r.n;
Chris@1418 185
Chris@1418 186 double digInc = log10(inc);
Chris@1418 187 int precInc = int(floor(digInc));
Chris@1418 188 double roundTo = pow(10.0, precInc);
Chris@1418 189
Chris@1418 190 if (roundTo != 0.0) {
Chris@1418 191 inc = round(inc / roundTo) * roundTo;
Chris@1418 192 if (inc < roundTo) inc = roundTo;
Chris@1418 193 }
Chris@1418 194
Chris@1418 195 // if inc is close to giving us powers of two, nudge it
Chris@1418 196 if (fabs(inc - 0.301) < 0.01) {
Chris@1418 197 inc = log10(2.0);
Chris@1418 198 }
Chris@1418 199
Chris@1418 200 // smallest increment as displayed
Chris@1418 201 double minDispInc =
Chris@1418 202 LogRange::unmap(r.min + inc) - LogRange::unmap(r.min);
Chris@1418 203
Chris@1418 204 int prec = 1;
Chris@1418 205
Chris@1418 206 if (minDispInc > 0.0) {
Chris@1418 207 prec = int(floor(log10(minDispInc)));
Chris@1418 208 if (prec < 0) prec = -prec;
Chris@1418 209 }
Chris@1418 210
Chris@1418 211 if (r.max >= -2.0 && r.max <= 3.0 &&
Chris@1418 212 r.min >= -3.0 && r.min <= 3.0) {
Chris@1418 213 display = Fixed;
Chris@1418 214 if (prec == 0) prec = 1;
Chris@1418 215 }
Chris@1418 216
Chris@1418 217 #ifdef DEBUG_SCALE_TICK_INTERVALS
Chris@1418 218 std::cerr << "\nmin = " << r.min << ", max = " << r.max << ", n = " << r.n
Chris@1418 219 << ", inc = " << inc << ", minDispInc = " << minDispInc
Chris@1418 220 << ", digInc = " << digInc << std::endl;
Chris@1418 221 std::cerr << "display = " << display << ", inc = " << inc
Chris@1418 222 << ", precInc = " << precInc
Chris@1418 223 << ", prec = " << prec << std::endl;
Chris@1418 224 std::cerr << "roundTo = " << roundTo << std::endl;
Chris@1418 225 #endif
Chris@1418 226
Chris@1418 227 double min = r.min;
Chris@1418 228 if (inc != 0.0) {
Chris@1418 229 min = ceil(r.min / inc) * inc;
Chris@1418 230 if (min > r.max) min = r.max;
Chris@1418 231 }
Chris@1418 232
Chris@1418 233 return { min, r.max, inc, 0.0, display, prec, true };
Chris@1407 234 }
Chris@1407 235
Chris@1414 236 static Ticks linearTicks(Range r) {
Chris@1417 237 Instruction instruction = linearInstruction(r);
Chris@1417 238 Ticks ticks = explode(instruction);
Chris@1417 239 return ticks;
Chris@1414 240 }
Chris@1414 241
Chris@1414 242 static Ticks logTicks(Range r) {
Chris@1418 243 Instruction instruction = logInstruction(r);
Chris@1417 244 Ticks ticks = explode(instruction);
Chris@1417 245 return ticks;
Chris@1414 246 }
Chris@1418 247
Chris@1418 248 static Tick makeTick(Display display, int precision, double value) {
Chris@1414 249 const int buflen = 40;
Chris@1414 250 char buffer[buflen];
Chris@1414 251 snprintf(buffer, buflen,
Chris@1418 252 display == Auto ? "%.*g" :
Chris@1418 253 display == Fixed ? "%.*f" :
Chris@1418 254 "%.*e",
Chris@1414 255 precision, value);
Chris@1414 256 return Tick({ value, std::string(buffer) });
Chris@1414 257 }
Chris@1414 258
Chris@1417 259 static Ticks explode(Instruction instruction) {
Chris@1417 260
Chris@1411 261 #ifdef DEBUG_SCALE_TICK_INTERVALS
Chris@1417 262 std::cerr << "initial = " << instruction.initial
Chris@1417 263 << ", limit = " << instruction.limit
Chris@1417 264 << ", spacing = " << instruction.spacing
Chris@1417 265 << ", roundTo = " << instruction.roundTo
Chris@1418 266 << ", display = " << instruction.display
Chris@1417 267 << ", precision = " << instruction.precision
Chris@1417 268 << ", logUnmap = " << instruction.logUnmap
Chris@1417 269 << std::endl;
Chris@1411 270 #endif
Chris@1417 271
Chris@1417 272 if (instruction.spacing == 0.0) {
Chris@1417 273 return {};
Chris@1414 274 }
Chris@1417 275
Chris@1411 276 double eps = 1e-7;
Chris@1417 277 if (instruction.spacing < eps * 10.0) {
Chris@1417 278 eps = instruction.spacing / 10.0;
Chris@1411 279 }
Chris@1417 280
Chris@1417 281 double max = instruction.limit;
Chris@1412 282 int n = 0;
Chris@1417 283
Chris@1417 284 Ticks ticks;
Chris@1417 285
Chris@1412 286 while (true) {
Chris@1417 287 double value = instruction.initial + n * instruction.spacing;
Chris@1414 288 if (value >= max + eps) {
Chris@1412 289 break;
Chris@1412 290 }
Chris@1417 291 if (instruction.logUnmap) {
Chris@1414 292 value = pow(10.0, value);
Chris@1414 293 }
Chris@1418 294 if (instruction.roundTo != 0.0) {
Chris@1418 295 value = instruction.roundTo * round(value / instruction.roundTo);
Chris@1418 296 }
Chris@1418 297 ticks.push_back(makeTick(instruction.display,
Chris@1417 298 instruction.precision,
Chris@1417 299 value));
Chris@1412 300 ++n;
Chris@1407 301 }
Chris@1417 302
Chris@1417 303 return ticks;
Chris@1407 304 }
Chris@1407 305 };
Chris@1407 306
Chris@1407 307 #endif