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