annotate base/ScaleTickIntervals.h @ 1422:91001e2bb96a

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