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;
--- a/files.pri	Tue Mar 07 13:52:37 2017 +0000
+++ b/files.pri	Wed May 03 13:02:08 2017 +0100
@@ -26,6 +26,7 @@
            base/RecentFiles.h \
            base/ResourceFinder.h \
            base/RingBuffer.h \
+           base/ScaleTickIntervals.h \
            base/Scavenger.h \
            base/Selection.h \
            base/Serialiser.h \