Chris@1407: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@1407: Chris@1407: /* Chris@1407: Sonic Visualiser Chris@1407: An audio file viewer and annotation editor. Chris@1407: Centre for Digital Music, Queen Mary, University of London. Chris@1407: This file copyright 2006-2017 Chris Cannam and QMUL. Chris@1407: Chris@1407: This program is free software; you can redistribute it and/or Chris@1407: modify it under the terms of the GNU General Public License as Chris@1407: published by the Free Software Foundation; either version 2 of the Chris@1407: License, or (at your option) any later version. See the file Chris@1407: COPYING included with this distribution for more information. Chris@1407: */ Chris@1407: Chris@1407: #ifndef SV_SCALE_TICK_INTERVALS_H Chris@1407: #define SV_SCALE_TICK_INTERVALS_H Chris@1407: Chris@1407: #include Chris@1407: #include Chris@1407: #include Chris@1407: Chris@1417: #define DEBUG_SCALE_TICK_INTERVALS 1 Chris@1411: Chris@1411: #ifdef DEBUG_SCALE_TICK_INTERVALS Chris@1407: #include Chris@1411: #endif Chris@1407: Chris@1414: #include "LogRange.h" Chris@1414: Chris@1407: class ScaleTickIntervals Chris@1407: { Chris@1407: public: Chris@1407: struct Range { Chris@1407: double min; // start of value range Chris@1407: double max; // end of value range Chris@1412: int n; // number of divisions (approximate only) Chris@1407: }; Chris@1407: Chris@1407: struct Tick { Chris@1407: double value; // value this tick represents Chris@1407: std::string label; // value as written Chris@1407: }; Chris@1407: Chris@1417: typedef std::vector Ticks; Chris@1407: Chris@1407: static Ticks linear(Range r) { Chris@1414: return linearTicks(r); Chris@1414: } Chris@1407: Chris@1414: static Ticks logarithmic(Range r) { Chris@1414: return logTicks(r); Chris@1414: } Chris@1414: Chris@1414: private: Chris@1417: struct Instruction { Chris@1417: double initial; // value of first tick Chris@1417: double limit; // max from original range Chris@1417: double spacing; // increment between ticks Chris@1417: double roundTo; // what all displayed values should be rounded to Chris@1417: bool fixed; // whether to use fixed precision (%f rather than %e) Chris@1417: int precision; // number of dp (%f) or sf (%e) Chris@1417: bool logUnmap; // true if values represent logs of display values Chris@1417: }; Chris@1417: Chris@1417: static Instruction linearInstruction(Range r) Chris@1414: { Chris@1407: if (r.n < 1) { Chris@1407: return {}; Chris@1407: } Chris@1407: if (r.max < r.min) { Chris@1417: return linearInstruction({ r.max, r.min, r.n }); Chris@1407: } Chris@1407: Chris@1407: double inc = (r.max - r.min) / r.n; Chris@1407: if (inc == 0) { Chris@1411: #ifdef DEBUG_SCALE_TICK_INTERVALS Chris@1411: std::cerr << "inc == 0, using trivial range" << std::endl; Chris@1411: #endif Chris@1411: double roundTo = r.min; Chris@1411: if (roundTo <= 0.0) { Chris@1411: roundTo = 1.0; Chris@1411: } Chris@1417: return { r.min, r.max, 1.0, roundTo, true, 1, false }; Chris@1407: } Chris@1408: Chris@1408: double digInc = log10(inc); Chris@1408: double digMax = log10(fabs(r.max)); Chris@1408: double digMin = log10(fabs(r.min)); Chris@1408: Chris@1409: int precInc = int(trunc(digInc)); Chris@1409: if (double(precInc) != digInc) { Chris@1409: precInc -= 1; Chris@1409: } Chris@1408: Chris@1408: bool fixed = false; Chris@1408: if (precInc > -4 && precInc < 4) { Chris@1408: fixed = true; Chris@1408: } else if ((digMax >= -3.0 && digMax <= 2.0) && Chris@1408: (digMin >= -3.0 && digMin <= 3.0)) { Chris@1408: fixed = true; Chris@1408: } Chris@1408: Chris@1408: int precRange = int(ceil(digMax - digInc)); Chris@1408: Chris@1408: int prec = 1; Chris@1408: Chris@1408: if (fixed) { Chris@1415: if (digInc < 0) { Chris@1410: prec = -precInc; Chris@1415: } else { Chris@1410: prec = 0; Chris@1408: } Chris@1408: } else { Chris@1408: prec = precRange; Chris@1408: } Chris@1408: Chris@1411: double roundTo = pow(10.0, precInc); Chris@1411: Chris@1411: #ifdef DEBUG_SCALE_TICK_INTERVALS Chris@1408: std::cerr << "\nmin = " << r.min << ", max = " << r.max << ", n = " << r.n Chris@1408: << ", inc = " << inc << std::endl; Chris@1408: std::cerr << "digMax = " << digMax << ", digInc = " << digInc Chris@1408: << std::endl; Chris@1408: std::cerr << "fixed = " << fixed << ", inc = " << inc Chris@1408: << ", precInc = " << precInc << ", precRange = " << precRange Chris@1408: << ", prec = " << prec << std::endl; Chris@1408: std::cerr << "roundTo = " << roundTo << std::endl; Chris@1411: #endif Chris@1408: Chris@1407: inc = round(inc / roundTo) * roundTo; Chris@1408: if (inc < roundTo) inc = roundTo; Chris@1408: Chris@1407: double min = ceil(r.min / roundTo) * roundTo; Chris@1407: if (min > r.max) min = r.max; Chris@1407: Chris@1413: if (!fixed && min != 0.0) { Chris@1413: double digNewMin = log10(fabs(min)); Chris@1413: if (digNewMin < digInc) { Chris@1413: prec = int(ceil(digMax - digNewMin)); Chris@1413: #ifdef DEBUG_SCALE_TICK_INTERVALS Chris@1413: std::cerr << "min is smaller than increment, adjusting prec to " Chris@1413: << prec << std::endl; Chris@1413: #endif Chris@1413: } Chris@1413: } Chris@1413: Chris@1417: return { min, r.max, inc, roundTo, fixed, prec, false }; Chris@1407: } Chris@1407: Chris@1414: static Ticks linearTicks(Range r) { Chris@1417: Instruction instruction = linearInstruction(r); Chris@1417: Ticks ticks = explode(instruction); Chris@1417: return ticks; Chris@1414: } Chris@1414: Chris@1414: static Ticks logTicks(Range r) { Chris@1414: Range mapped(r); Chris@1414: LogRange::mapRange(mapped.min, mapped.max); Chris@1417: Instruction instruction = linearInstruction(mapped); Chris@1417: instruction.logUnmap = true; Chris@1414: if (fabs(mapped.min - mapped.max) > 3) { Chris@1417: instruction.fixed = false; Chris@1414: } Chris@1417: Ticks ticks = explode(instruction); Chris@1417: return ticks; Chris@1414: } Chris@1414: Chris@1414: static Tick makeTick(bool fixed, int precision, double value) { Chris@1414: const int buflen = 40; Chris@1414: char buffer[buflen]; Chris@1414: snprintf(buffer, buflen, Chris@1414: fixed ? "%.*f" : "%.*e", Chris@1414: precision, value); Chris@1414: return Tick({ value, std::string(buffer) }); Chris@1414: } Chris@1414: Chris@1417: static Ticks explode(Instruction instruction) { Chris@1417: Chris@1411: #ifdef DEBUG_SCALE_TICK_INTERVALS Chris@1417: std::cerr << "initial = " << instruction.initial Chris@1417: << ", limit = " << instruction.limit Chris@1417: << ", spacing = " << instruction.spacing Chris@1417: << ", roundTo = " << instruction.roundTo Chris@1417: << ", fixed = " << instruction.fixed Chris@1417: << ", precision = " << instruction.precision Chris@1417: << ", logUnmap = " << instruction.logUnmap Chris@1417: << std::endl; Chris@1411: #endif Chris@1417: Chris@1417: if (instruction.spacing == 0.0) { Chris@1417: return {}; Chris@1414: } Chris@1417: Chris@1411: double eps = 1e-7; Chris@1417: if (instruction.spacing < eps * 10.0) { Chris@1417: eps = instruction.spacing / 10.0; Chris@1411: } Chris@1417: Chris@1417: double max = instruction.limit; Chris@1412: int n = 0; Chris@1417: Chris@1417: Ticks ticks; Chris@1417: Chris@1412: while (true) { Chris@1417: double value = instruction.initial + n * instruction.spacing; Chris@1417: value = instruction.roundTo * round(value / instruction.roundTo); Chris@1414: if (value >= max + eps) { Chris@1412: break; Chris@1412: } Chris@1417: if (instruction.logUnmap) { Chris@1414: value = pow(10.0, value); Chris@1414: } Chris@1417: ticks.push_back(makeTick(instruction.fixed, Chris@1417: instruction.precision, Chris@1417: value)); Chris@1412: ++n; Chris@1407: } Chris@1417: Chris@1417: return ticks; Chris@1407: } Chris@1407: }; Chris@1407: Chris@1407: #endif