Mercurial > hg > svcore
changeset 1407:25ed6dde2ce0 scale-ticks
Scale tick labeller, and tests (some failing so far)
author | Chris Cannam |
---|---|
date | Wed, 03 May 2017 13:02:08 +0100 |
parents | 09751743647e |
children | f89365917d02 |
files | base/ScaleTickIntervals.h base/test/TestScaleTickIntervals.h base/test/files.pri base/test/svcore-base-test.cpp files.pri |
diffstat | 5 files changed, 262 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/ScaleTickIntervals.h Wed May 03 13:02:08 2017 +0100 @@ -0,0 +1,112 @@ +/* -*- 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> + +#include <iostream> + + +class ScaleTickIntervals +{ +public: + struct Range { + double min; // start of value range + double max; // end of value range + int n; // number of divisions requested (will be n+1 ticks) + }; + + 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) { + + if (r.n < 1) { + return {}; + } + if (r.max < r.min) { + return linear({ r.max, r.min, r.n }); + } + + double inc = (r.max - r.min) / r.n; + if (inc == 0) { + Ticks t { r.min, 1.0, r.min, false, 1, {} }; + explode(r, t); + return t; + } + + double ilg = log10(inc); + int prec = int((ilg > 0.0) ? round(ilg) : trunc(ilg)) - 1; + int dp = 0, sf = 0; + bool fixed = false; + if (prec < 0) { + dp = -prec; + sf = 1; // was 2, but should probably vary + } else { + sf = prec; + } + if (sf == 0) { + sf = 1; + } + if (prec > -4 && prec < 4) { + fixed = true; + } + + double roundTo = pow(10.0, prec); + inc = round(inc / roundTo) * roundTo; + double min = ceil(r.min / roundTo) * roundTo; + if (min > r.max) min = r.max; + + Ticks t { min, inc, roundTo, fixed, fixed ? dp : sf, {} }; + explode(r, t); + return t; + } + +private: + static void explode(const Range &r, Ticks &t) { + std::cerr << "initial = " << t.initial << ", spacing = " << t.spacing + << ", roundTo = " << t.roundTo << ", fixed = " << t.fixed + << ", precision = " << t.precision << std::endl; + auto makeTick = [&](double value) { + const int buflen = 40; + char buffer[buflen]; + snprintf(buffer, buflen, + t.fixed ? "%.*f" : "%.*e", + t.precision, value); + return Tick({ value, std::string(buffer) }); + }; + for (double value = t.initial; value <= r.max; value += t.spacing) { + value = t.roundTo * round(value / t.roundTo); + t.ticks.push_back(makeTick(value)); + } + } +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/TestScaleTickIntervals.h Wed May 03 13:02:08 2017 +0100 @@ -0,0 +1,142 @@ +/* -*- 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 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 TEST_SCALE_TICK_INTERVALS_H +#define TEST_SCALE_TICK_INTERVALS_H + +#include "../ScaleTickIntervals.h" + +#include <QObject> +#include <QtTest> +#include <QDir> + +#include <iostream> + +using namespace std; + +class TestScaleTickIntervals : public QObject +{ + Q_OBJECT + + void printDiff(vector<ScaleTickIntervals::Tick> ticks, + vector<ScaleTickIntervals::Tick> expected) { + + cerr << "Have " << ticks.size() << " ticks, expected " + << expected.size() << endl; + for (int i = 0; i < int(expected.size()); ++i) { + if (i < int(ticks.size())) { + cerr << i << ": have " << ticks[i].value << " \"" + << ticks[i].label << "\", expected " + << expected[i].value << " \"" << expected[i].label + << "\"" << endl; + } + } + } + + void compareTicks(vector<ScaleTickIntervals::Tick> ticks, + vector<ScaleTickIntervals::Tick> expected) + { + for (int i = 0; i < int(expected.size()); ++i) { + if (i < int(ticks.size())) { + if (ticks[i].label != expected[i].label || + ticks[i].value != expected[i].value) { + printDiff(ticks, expected); + } + QCOMPARE(ticks[i].label, expected[i].label); + QCOMPARE(ticks[i].value, expected[i].value); + } + } + QCOMPARE(ticks.size(), expected.size()); + } + +private slots: + void linear_0_1_10() + { + auto ticks = ScaleTickIntervals::linear({ 0, 1, 10 }); + vector<ScaleTickIntervals::Tick> expected { + { 0.0, "0.00" }, + { 0.1, "0.10" }, + { 0.2, "0.20" }, + { 0.3, "0.30" }, + { 0.4, "0.40" }, + { 0.5, "0.50" }, + { 0.6, "0.60" }, + { 0.7, "0.70" }, + { 0.8, "0.80" }, + { 0.9, "0.90" }, + { 1.0, "1.00" } + }; + compareTicks(ticks.ticks, expected); + } + + void linear_0_0p1_5() + { + auto ticks = ScaleTickIntervals::linear({ 0, 0.1, 5 }); + vector<ScaleTickIntervals::Tick> expected { + { 0.0, "0.00" }, + { 0.02, "0.02" }, + { 0.04, "0.04" }, + { 0.06, "0.06" }, + { 0.08, "0.08" }, + { 0.1, "0.10" } + }; + compareTicks(ticks.ticks, expected); + } + + void linear_0_0p01_5() + { + auto ticks = ScaleTickIntervals::linear({ 0, 0.01, 5 }); + vector<ScaleTickIntervals::Tick> expected { + { 0.00, "0.000" }, + { 0.002, "0.002" }, + { 0.004, "0.004" }, + { 0.006, "0.006" }, + { 0.008, "0.008" }, + { 0.01, "0.010" } + }; + compareTicks(ticks.ticks, expected); + } + + void linear_0_0p001_5() + { + auto ticks = ScaleTickIntervals::linear({ 0, 0.001, 5 }); + vector<ScaleTickIntervals::Tick> expected { + { 0.000, "0.0e+00" }, + { 0.0002, "2.0e-04" }, + { 0.0004, "4.0e-04" }, + { 0.0006, "6.0e-04" }, + { 0.0008, "8.0e-04" }, + { 0.001, "1.0e-03" } + }; + compareTicks(ticks.ticks, expected); + } + + void linear_1_1p001_5() + { + auto ticks = ScaleTickIntervals::linear({ 1, 1.001, 5 }); + vector<ScaleTickIntervals::Tick> expected { + { 1.000, "1.0000" }, + { 1.0002, "1.0002" }, + { 1.0004, "1.0004" }, + { 1.0006, "1.0006" }, + { 1.0008, "1.0008" }, + { 1.001, "1.0010" } + }; + compareTicks(ticks.ticks, expected); + } +}; + +#endif + +
--- a/base/test/files.pri Tue Mar 07 13:52:37 2017 +0000 +++ b/base/test/files.pri Wed May 03 13:02:08 2017 +0100 @@ -4,6 +4,7 @@ TestRangeMapper.h \ TestOurRealTime.h \ TestPitch.h \ + TestScaleTickIntervals.h \ TestStringBits.h \ TestVampRealTime.h
--- a/base/test/svcore-base-test.cpp Tue Mar 07 13:52:37 2017 +0000 +++ b/base/test/svcore-base-test.cpp Wed May 03 13:02:08 2017 +0100 @@ -14,6 +14,7 @@ #include "TestLogRange.h" #include "TestRangeMapper.h" #include "TestPitch.h" +#include "TestScaleTickIntervals.h" #include "TestStringBits.h" #include "TestOurRealTime.h" #include "TestVampRealTime.h" @@ -66,6 +67,11 @@ if (QTest::qExec(&t, argc, argv) == 0) ++good; else ++bad; } + { + TestScaleTickIntervals t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } if (bad > 0) { cerr << "\n********* " << bad << " test suite(s) failed!\n" << endl;